Skip to main content
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
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