收口移动导航响应边界

将 Expo 外链打开、WebView 刷新和受控导航成功响应包装收口到 navigation 模块

让移动 dispatch 只委托导航请求

同步移动壳配置门禁和共享记忆
This commit is contained in:
2026-06-20 01:43:35 +08:00
parent b57998d55e
commit 941e7d979a
4 changed files with 24 additions and 17 deletions
+10 -3
View File
@@ -1952,9 +1952,12 @@ for (const snippet of [
}
if (
!dispatchSource.includes('openMobileHostBridgeExternalUrl(request.payload)') ||
!dispatchSource.includes('openMobileHostBridgeNativePage(navigation, request.payload)') ||
!dispatchSource.includes('reloadMobileHostBridgeWebView(navigation)') ||
!dispatchSource.includes('openMobileHostBridgeExternalUrl(request)') ||
!dispatchSource.includes('openMobileHostBridgeNativePage(request, navigation)') ||
!dispatchSource.includes('reloadMobileHostBridgeWebView(request, navigation)') ||
dispatchSource.includes('ok(request, await openMobileHostBridgeExternalUrl') ||
dispatchSource.includes('ok(request, reloadMobileHostBridgeWebView') ||
dispatchSource.includes('ok(\\n request,\\n openMobileHostBridgeNativePage') ||
dispatchSource.includes("from 'expo-linking'") ||
dispatchSource.includes('Linking.') ||
dispatchSource.includes('openMobileShellExternalNavigation') ||
@@ -1966,13 +1969,17 @@ if (
}
for (const snippet of [
'openMobileHostBridgeExternalUrl',
'request: HostBridgeRequest',
"import * as Linking from 'expo-linking'",
'openMobileShellExternalNavigation(Linking, externalUrlPayload.url)',
'(request.payload as OpenExternalUrlPayload | undefined)?.url',
'normalizeHostBridgeExternalUrlPayload',
'openMobileHostBridgeNativePage',
'(request.payload as NavigateNativePagePayload | undefined)?.url',
'resolveMobileShellWebViewUrl',
'buildMobileShellUrl(webViewUrl, navigation.urlOptions)',
'reloadMobileHostBridgeWebView',
'ok(request, true)',
]) {
if (!hostBridgeNavigationSource.includes(snippet)) {
throw new Error(`mobile shell navigation module is missing ${snippet}`);
@@ -54,9 +54,9 @@ export async function dispatchMobileHostBridgeRequest(
case 'appearance.getColorScheme':
return getMobileHostBridgeAppearanceColorScheme(request);
case 'app.openExternalUrl':
return ok(request, await openMobileHostBridgeExternalUrl(request.payload));
return openMobileHostBridgeExternalUrl(request);
case 'app.reloadWebView':
return ok(request, reloadMobileHostBridgeWebView(navigation));
return reloadMobileHostBridgeWebView(request, navigation);
case 'network.status':
return getMobileHostBridgeNetworkStatus(request);
case 'clipboard.writeText':
@@ -92,10 +92,7 @@ export async function dispatchMobileHostBridgeRequest(
case 'share.setTarget':
return setMobileHostBridgeShareTarget(request);
case 'navigation.openNativePage':
return ok(
request,
openMobileHostBridgeNativePage(navigation, request.payload),
);
return openMobileHostBridgeNativePage(request, navigation);
case 'auth.requestLogin':
case 'payment.request':
return failure(request, unsupported(request.method));
@@ -2,6 +2,7 @@ import * as Linking from 'expo-linking';
import {
type HostBridgeError,
type HostBridgeRequest,
type NavigateNativePagePayload,
normalizeHostBridgeExternalUrlPayload,
type OpenExternalUrlPayload,
@@ -15,14 +16,15 @@ import { buildMobileShellUrl } from '../shell/url';
import {
type MobileHostBridgeNavigation,
invalidRequest,
ok,
unsupported,
} from './protocol';
export async function openMobileHostBridgeExternalUrl(
payload: unknown,
request: HostBridgeRequest,
) {
const externalUrlPayload = normalizeHostBridgeExternalUrlPayload(
(payload as OpenExternalUrlPayload | undefined)?.url,
(request.payload as OpenExternalUrlPayload | undefined)?.url,
);
if (!externalUrlPayload) {
throw invalidRequest('url must use an allowed external protocol');
@@ -37,18 +39,18 @@ export async function openMobileHostBridgeExternalUrl(
} satisfies HostBridgeError;
}
return true;
return ok(request, true);
}
export function openMobileHostBridgeNativePage(
request: HostBridgeRequest,
navigation: MobileHostBridgeNavigation | null,
payload: unknown,
) {
if (!navigation) {
throw unsupported('navigation.openNativePage');
}
const url = (payload as NavigateNativePagePayload | undefined)?.url;
const url = (request.payload as NavigateNativePagePayload | undefined)?.url;
if (typeof url !== 'string') {
throw invalidRequest('url is required');
}
@@ -64,10 +66,11 @@ export function openMobileHostBridgeNativePage(
navigation.openWebViewUrl(
buildMobileShellUrl(webViewUrl, navigation.urlOptions),
);
return true;
return ok(request, true);
}
export function reloadMobileHostBridgeWebView(
request: HostBridgeRequest,
navigation: MobileHostBridgeNavigation | null,
) {
if (!navigation) {
@@ -75,5 +78,5 @@ export function reloadMobileHostBridgeWebView(
}
navigation.reloadWebView();
return true;
return ok(request, true);
}
@@ -2794,7 +2794,7 @@
## 2026-06-19 原生壳导航桥接边界
- 背景:`app.openExternalUrl``navigation.openNativePage``app.reloadWebView` 已是受控 HostBridge 能力,但移动端和桌面端 `dispatch` 仍直接承接外链归一、同源 H5 跳转、宿主上下文补写和 WebView 刷新细节,后续继续补壳能力时容易让分发层重新变厚。
- 决策:Expo 移动壳新增 `apps/mobile-shell/src/host-bridge/navigation.ts`,统一承接 HostBridge 外链打开、受控 H5 跳转WebView 刷新,底层继续复用 `src/shell/navigation.ts``src/shell/url.ts`Tauri 桌面壳新增 `apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs`,统一承接外链打开、同源 H5 跳转和主窗口刷新,底层继续复用 `shell::navigation` 的 URL 归一和宿主上下文补写。两端 `dispatch` 只保留 method 委托和响应包装,配置检查会拒绝分发层直接调用外链 opener、URL 归一WebView navigate / reload 细节。
- 决策:Expo 移动壳新增 `apps/mobile-shell/src/host-bridge/navigation.ts`,统一承接 HostBridge 外链打开、受控 H5 跳转WebView 刷新和成功响应包装,底层继续复用 `src/shell/navigation.ts``src/shell/url.ts`Tauri 桌面壳新增 `apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs`,统一承接外链打开、同源 H5 跳转和主窗口刷新,底层继续复用 `shell::navigation` 的 URL 归一和宿主上下文补写。两端 `dispatch` 只保留 method 委托,配置检查会拒绝分发层直接调用外链 opener、URL 归一WebView navigate / reload 细节或包装导航成功响应
- 影响范围:`apps/mobile-shell/src/host-bridge/navigation.ts``apps/mobile-shell/src/host-bridge/dispatch.ts``apps/mobile-shell/scripts/check-config.mjs``apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs``apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs``apps/desktop-shell/scripts/check-config.mjs``scripts/check-native-shells.mjs`、宿主壳能力统一协议文档、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run mobile-shell:typecheck``npm run mobile-shell:test -- src/host-bridge/bridge.test.ts``npm run desktop-shell:typecheck``cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml``npm run check:native-shells``npm run check:encoding``git diff --check`