新增 HostBridge 原生宿主契约和 H5 native_app transport 新增 Expo React Native 移动壳并收紧 WebView 外链边界 新增 Tauri 桌面壳并用 capability 收口受控命令 更新宿主壳方案、文档索引和共享记忆
92 lines
2.6 KiB
TypeScript
92 lines
2.6 KiB
TypeScript
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 { shouldOpenInMobileShellWebView } from './src/mobileShellNavigation';
|
|
import { buildMobileShellUrl } from './src/mobileShellUrl';
|
|
|
|
const defaultWebUrl = 'http://127.0.0.1:3000/';
|
|
|
|
export default function App() {
|
|
const webViewRef = useRef<WebView>(null);
|
|
const [canGoBack, setCanGoBack] = useState(false);
|
|
const webUrl = useMemo(
|
|
() =>
|
|
buildMobileShellUrl(
|
|
process.env.EXPO_PUBLIC_GENARRATIVE_WEB_URL || defaultWebUrl,
|
|
{
|
|
platform: Platform.OS === 'ios' ? 'ios' : 'android',
|
|
hostVersion: '0.1.0',
|
|
capabilities: MOBILE_HOST_CAPABILITIES,
|
|
},
|
|
),
|
|
[],
|
|
);
|
|
const allowedWebOrigin = useMemo(() => new URL(webUrl).origin, [webUrl]);
|
|
|
|
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 (
|
|
<View style={styles.root}>
|
|
<StatusBar style="auto" />
|
|
<WebView
|
|
ref={webViewRef}
|
|
source={{ uri: webUrl }}
|
|
javaScriptEnabled
|
|
domStorageEnabled
|
|
originWhitelist={[allowedWebOrigin]}
|
|
onMessage={handleMessage}
|
|
onShouldStartLoadWithRequest={handleShouldStartLoad}
|
|
onNavigationStateChange={(event) => setCanGoBack(event.canGoBack)}
|
|
setSupportMultipleWindows={false}
|
|
/>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
root: {
|
|
flex: 1,
|
|
backgroundColor: '#fffdf9',
|
|
},
|
|
});
|