> ## Documentation Index
> Fetch the complete documentation index at: https://developers.learn.ink/llms.txt
> Use this file to discover all available pages before exploring further.

# WebView — React Native

> WebView integration examples for React Native

The examples below demonstrate a complete `LearnInkWebView` component for React Native. Both call your own backend to fetch a sign-in token — your backend is responsible for calling the LearnInk Identify API securely.

Before using these, replace the following constants at the top of the file:

* `YOUR_BACKEND_AUTH_URL` — your backend endpoint that calls the LearnInk Identify API and returns a token
* `ORG_ID` — your LearnInk organisation ID

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    import React, { useState, useRef, useEffect } from "react"
    import { View, ActivityIndicator, StyleSheet, Alert, Modal } from "react-native"
    import { SafeAreaView } from "react-native-safe-area-context"
    import { WebView } from "react-native-webview"

    // Configuration constants
    const YOUR_BACKEND_AUTH_URL = "https://your-api.com/auth/learnink"
    const WEBVIEW_BASE_URL = "https://m.learn.ink"
    const ORG_ID = "acme"
    const REQUEST_TIMEOUT = 5000

    const MESSAGE_TYPES = {
      CLOSE: "CLOSE",
      SESSION_EXPIRED: "SESSION_EXPIRED",
    }

    const LearnInkWebView = ({ path, onClose, userId }) => {
      const [webViewUrl, setWebViewUrl] = useState(undefined)
      const [loading, setLoading] = useState(true)
      const webViewRef = useRef(null)

      useEffect(() => {
        authenticateUser()
      }, [userId])

      const buildWebViewUrl = (token) => {
        const params = new URLSearchParams({
          webview: "true",
          ...(token && { token }),
        })
        return `${WEBVIEW_BASE_URL}/${ORG_ID}/${path}?${params.toString()}`
      }

      const authenticateUser = async () => {
        setLoading(true)
        try {
          const token = await fetchAuthToken()
          setWebViewUrl(buildWebViewUrl(token))
        } catch (error) {
          console.error("Error authenticating user:", error)
          // Fall back to loading without a token if the user has an existing session
          setWebViewUrl(buildWebViewUrl())
        } finally {
          setLoading(false)
        }
      }

      const fetchAuthToken = async () => {
        const controller = new AbortController()
        const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT)
        try {
          const response = await fetch(YOUR_BACKEND_AUTH_URL, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              // Add your own auth headers here, e.g.:
              // "Authorization": `Bearer ${yourUserSessionToken}`,
            },
            body: JSON.stringify({ id: userId }),
            signal: controller.signal,
          })
          clearTimeout(timeoutId)
          if (response.ok) {
            const data = await response.json()
            return data.token
          } else {
            const error = await response.json()
            throw new Error(error.message || "Failed to authenticate")
          }
        } catch (error) {
          clearTimeout(timeoutId)
          throw error
        }
      }

      const handleMessage = (event) => {
        try {
          const message = JSON.parse(event.nativeEvent.data)
          switch (message.type) {
            case MESSAGE_TYPES.CLOSE:
              if (onClose) onClose()
              break
            case MESSAGE_TYPES.SESSION_EXPIRED:
              handleSessionExpired()
              break
            default:
              console.log("Unknown message type:", message.type)
          }
        } catch (error) {
          console.error("Error handling message:", error)
        }
      }

      const handleSessionExpired = async () => {
        setLoading(true)
        try {
          const token = await fetchAuthToken()
          setWebViewUrl(buildWebViewUrl(token))
        } catch (error) {
          console.error("Error refreshing session:", error)
          Alert.alert(
            "Session Refresh Failed",
            "Unable to refresh your session. Please close and try again.",
            [
              { text: "Close", onPress: onClose },
              { text: "Retry", onPress: handleSessionExpired },
            ]
          )
        } finally {
          setLoading(false)
        }
      }

      if (loading || !webViewUrl) {
        return (
          <View style={styles.loadingContainer}>
            <ActivityIndicator size="large" color="#007AFF" />
          </View>
        )
      }

      return (
        <Modal animationType="slide" transparent={false} visible={true} onRequestClose={onClose}>
          <SafeAreaView style={styles.webview}>
            <WebView
              ref={webViewRef}
              source={{ uri: webViewUrl }}
              onMessage={handleMessage}
              style={styles.webview}
              javaScriptEnabled={true}
              domStorageEnabled={true}
              startInLoadingState={true}
              renderLoading={() => (
                <View style={styles.loadingContainer}>
                  <ActivityIndicator size="large" color="#007AFF" />
                </View>
              )}
            />
          </SafeAreaView>
        </Modal>
      )
    }

    const styles = StyleSheet.create({
      webview: { flex: 1 },
      loadingContainer: {
        flex: 1,
        justifyContent: "center",
        alignItems: "center",
        backgroundColor: "#FFFFFF",
      },
    })

    export default LearnInkWebView
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    import React, { useState, useRef, useEffect } from "react"
    import { View, ActivityIndicator, StyleSheet, Alert, Modal } from "react-native"
    import { SafeAreaView } from "react-native-safe-area-context"
    import { WebView, WebViewMessageEvent } from "react-native-webview"

    const YOUR_BACKEND_AUTH_URL = "https://your-api.com/auth/learnink"
    const WEBVIEW_BASE_URL = "https://m.learn.ink"
    const ORG_ID = "acme"
    const REQUEST_TIMEOUT = 5000

    const MESSAGE_TYPES = {
      CLOSE: "CLOSE",
      SESSION_EXPIRED: "SESSION_EXPIRED",
    }

    interface Props {
      path: string
      onClose: () => void
      userId: string
    }

    const LearnInkWebView = ({ path, onClose, userId }: Props) => {
      const [webViewUrl, setWebViewUrl] = useState<string | undefined>(undefined)
      const [loading, setLoading] = useState<boolean>(true)
      const webViewRef = useRef<WebView | null>(null)

      useEffect(() => {
        authenticateUser()
      }, [userId])

      const buildWebViewUrl = (token?: string) => {
        const params = new URLSearchParams({
          webview: "true",
          ...(token && { token }),
        })
        return `${WEBVIEW_BASE_URL}/${ORG_ID}/${path}?${params.toString()}`
      }

      const authenticateUser = async () => {
        setLoading(true)
        try {
          const token = await fetchAuthToken()
          setWebViewUrl(buildWebViewUrl(token))
        } catch (error) {
          console.error("Error authenticating user:", error)
          setWebViewUrl(buildWebViewUrl())
        } finally {
          setLoading(false)
        }
      }

      const fetchAuthToken = async () => {
        const controller = new AbortController()
        const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT)
        try {
          const response = await fetch(YOUR_BACKEND_AUTH_URL, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              // "Authorization": `Bearer ${yourUserSessionToken}`,
            },
            body: JSON.stringify({ id: userId }),
            signal: controller.signal,
          })
          clearTimeout(timeoutId)
          if (response.ok) {
            const data = await response.json()
            return data.token
          } else {
            const error = await response.json()
            throw new Error(error.message || "Failed to authenticate")
          }
        } catch (error) {
          clearTimeout(timeoutId)
          throw error
        }
      }

      const handleMessage = (event: WebViewMessageEvent) => {
        try {
          const message = JSON.parse(event.nativeEvent.data)
          switch (message.type) {
            case MESSAGE_TYPES.CLOSE:
              if (onClose) onClose()
              break
            case MESSAGE_TYPES.SESSION_EXPIRED:
              handleSessionExpired()
              break
            default:
              console.log("Unknown message type:", message.type)
          }
        } catch (error) {
          console.error("Error handling message:", error)
        }
      }

      const handleSessionExpired = async () => {
        setLoading(true)
        try {
          const token = await fetchAuthToken()
          setWebViewUrl(buildWebViewUrl(token))
        } catch (error) {
          console.error("Error refreshing session:", error)
          Alert.alert(
            "Session Refresh Failed",
            "Unable to refresh your session. Please close and try again.",
            [
              { text: "Close", onPress: onClose },
              { text: "Retry", onPress: handleSessionExpired },
            ]
          )
        } finally {
          setLoading(false)
        }
      }

      if (loading || !webViewUrl) {
        return (
          <View style={styles.loadingContainer}>
            <ActivityIndicator size="large" color="#007AFF" />
          </View>
        )
      }

      return (
        <Modal animationType="slide" transparent={false} visible={true} onRequestClose={onClose}>
          <SafeAreaView style={styles.webview}>
            <WebView
              ref={webViewRef}
              source={{ uri: webViewUrl }}
              onMessage={handleMessage}
              style={styles.webview}
              javaScriptEnabled={true}
              domStorageEnabled={true}
              startInLoadingState={true}
              renderLoading={() => (
                <View style={styles.loadingContainer}>
                  <ActivityIndicator size="large" color="#007AFF" />
                </View>
              )}
            />
          </SafeAreaView>
        </Modal>
      )
    }

    const styles = StyleSheet.create({
      webview: { flex: 1 },
      loadingContainer: {
        flex: 1,
        justifyContent: "center",
        alignItems: "center",
        backgroundColor: "#FFFFFF",
      },
    })

    export default LearnInkWebView
    ```
  </Tab>
</Tabs>
