新增 Expo 与 Tauri 原生宿主壳
新增 HostBridge 原生宿主契约和 H5 native_app transport 新增 Expo React Native 移动壳并收紧 WebView 外链边界 新增 Tauri 桌面壳并用 capability 收口受控命令 更新宿主壳方案、文档索引和共享记忆
This commit is contained in:
91
apps/mobile-shell/App.tsx
Normal file
91
apps/mobile-shell/App.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
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',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user