import { StatusBar } from 'expo-status-bar'; import { useEffect, useMemo, useRef, useState } from 'react'; import { BackHandler, Linking, Platform, StyleSheet, View } from 'react-native'; import type { WebViewMessageEvent } from 'react-native-webview'; import { WebView } from 'react-native-webview'; import { handleMobileHostBridgeMessage, MOBILE_HOST_CAPABILITIES, } from './src/mobileHostBridge'; import { buildMobileShellUrlFromDeepLink } from './src/mobileShellDeepLink'; import { shouldOpenInMobileShellWebView } from './src/mobileShellNavigation'; import { buildMobileShellUrl } from './src/mobileShellUrl'; const defaultWebUrl = 'http://127.0.0.1:3000/'; const hostVersion = '0.1.0'; export default function App() { const webViewRef = useRef(null); const [canGoBack, setCanGoBack] = useState(false); const baseWebUrl = process.env.EXPO_PUBLIC_GENARRATIVE_WEB_URL || defaultWebUrl; const mobileShellUrlOptions = useMemo( () => ({ platform: Platform.OS === 'ios' ? 'ios' as const : 'android' as const, hostVersion, capabilities: MOBILE_HOST_CAPABILITIES, }), [], ); const [webUrl, setWebUrl] = useState(() => buildMobileShellUrl(baseWebUrl, mobileShellUrlOptions), ); const allowedWebOrigin = useMemo(() => new URL(webUrl).origin, [webUrl]); useEffect(() => { let disposed = false; const openDeepLink = (url: string | null | undefined) => { const nextUrl = buildMobileShellUrlFromDeepLink( url, baseWebUrl, mobileShellUrlOptions, ); setWebUrl(nextUrl); }; void Linking.getInitialURL().then((url) => { if (!disposed) { openDeepLink(url); } }); const subscription = Linking.addEventListener('url', (event) => { openDeepLink(event.url); }); return () => { disposed = true; subscription.remove(); }; }, [baseWebUrl, mobileShellUrlOptions]); useEffect(() => { const subscription = BackHandler.addEventListener( 'hardwareBackPress', () => { if (!canGoBack) { return false; } webViewRef.current?.goBack(); return true; }, ); return () => subscription.remove(); }, [canGoBack]); const handleMessage = (event: WebViewMessageEvent) => { void handleMobileHostBridgeMessage(event.nativeEvent.data, (response) => { webViewRef.current?.injectJavaScript( `window.dispatchEvent(new MessageEvent('message', { data: ${JSON.stringify( JSON.stringify(response), )} })); true;`, ); }); }; const handleShouldStartLoad = (request: { url: string }) => { if (shouldOpenInMobileShellWebView(request.url, allowedWebOrigin)) { return true; } void Linking.openURL(request.url).catch(() => undefined); return false; }; return ( setCanGoBack(event.canGoBack)} setSupportMultipleWindows={false} /> ); } const styles = StyleSheet.create({ root: { flex: 1, backgroundColor: '#fffdf9', }, });