From 177c18ddcdee05782a4facdf700713825f483b26 Mon Sep 17 00:00:00 2001 From: kdletters Date: Fri, 19 Jun 2026 22:01:57 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E5=8F=A3=E5=8E=9F=E7=94=9F=E5=AF=BC?= =?UTF-8?q?=E8=88=AA=E6=A1=A5=E6=8E=A5=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 Expo 外链打开和受控 WebView 导航收口到 navigation 模块 将 Tauri 外链打开和主窗口导航刷新收口到 navigation 模块 同步原生壳结构门禁、架构文档和共享记忆 --- apps/desktop-shell/scripts/check-config.mjs | 39 +++++++++- .../src-tauri/src/host_bridge/dispatch.rs | 60 ++------------ .../src-tauri/src/host_bridge/mod.rs | 1 + .../src-tauri/src/host_bridge/navigation.rs | 72 +++++++++++++++++ apps/mobile-shell/scripts/check-config.mjs | 42 +++++++++- apps/mobile-shell/src/host-bridge/dispatch.ts | 77 ++++-------------- .../src/host-bridge/navigation.ts | 78 +++++++++++++++++++ .../shared-memory/decision-log.md | 7 ++ ...ExpoReactNative与Tauri宿主壳方案-2026-06-17.md | 6 +- ...前端架构】宿主壳能力统一协议-2026-06-17.md | 6 +- scripts/check-native-shells.mjs | 2 + 11 files changed, 264 insertions(+), 126 deletions(-) create mode 100644 apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs create mode 100644 apps/mobile-shell/src/host-bridge/navigation.ts diff --git a/apps/desktop-shell/scripts/check-config.mjs b/apps/desktop-shell/scripts/check-config.mjs index 39d4eb1b5..11d5a3e99 100644 --- a/apps/desktop-shell/scripts/check-config.mjs +++ b/apps/desktop-shell/scripts/check-config.mjs @@ -86,6 +86,14 @@ const desktopHostBridgeDispatchSource = fs.readFileSync( desktopHostBridgeDispatchPath, 'utf8', ); +const desktopHostBridgeNavigationPath = new URL( + '../src-tauri/src/host_bridge/navigation.rs', + import.meta.url, +); +const desktopHostBridgeNavigationSource = fs.readFileSync( + desktopHostBridgeNavigationPath, + 'utf8', +); const desktopHostBridgeNotificationsPath = new URL( '../src-tauri/src/host_bridge/notifications.rs', import.meta.url, @@ -216,6 +224,7 @@ const expectedDesktopHostBridgeRustFiles = [ 'dispatch.rs', 'files.rs', 'mod.rs', + 'navigation.rs', 'notifications.rs', 'protocol.rs', 'share.rs', @@ -1650,13 +1659,38 @@ if ( } if ( - !desktopHostBridgeDispatchSource.includes('open_normalized_desktop_external_url(&app, url)') || + !desktopHostBridgeDispatchSource.includes( + 'open_desktop_host_bridge_external_url(&app, &request)', + ) || + !desktopHostBridgeDispatchSource.includes( + 'open_desktop_host_bridge_native_page(&app, &request)', + ) || + !desktopHostBridgeDispatchSource.includes( + 'reload_desktop_host_bridge_webview(&app, &request)', + ) || + desktopHostBridgeDispatchSource.includes('open_normalized_desktop_external_url(&app, url)') || + desktopHostBridgeDispatchSource.includes('normalize_external_url') || + desktopHostBridgeDispatchSource.includes('normalize_native_page_url') || desktopHostBridgeDispatchSource.includes('app.opener().open_url(') ) { throw new Error( - 'desktop shell app.openExternalUrl must use the shared external opener helper', + 'desktop shell navigation HostBridge methods must delegate to navigation module', ); } +for (const snippet of [ + 'open_desktop_host_bridge_external_url', + 'normalize_external_url', + 'open_normalized_desktop_external_url(app, url)', + 'open_desktop_host_bridge_native_page', + 'normalize_native_page_url', + 'window.navigate(url)', + 'reload_desktop_host_bridge_webview', + 'window.reload()', +]) { + if (!desktopHostBridgeNavigationSource.includes(snippet)) { + throw new Error(`desktop shell navigation module is missing ${snippet}`); + } +} if (config.build?.frontendDist !== '../../../dist') { throw new Error('desktop shell must package the root H5 dist'); @@ -1828,6 +1862,7 @@ const expectedRustHostBridgeFiles = [ 'dispatch.rs', 'files.rs', 'mod.rs', + 'navigation.rs', 'notifications.rs', 'protocol.rs', 'share.rs', diff --git a/apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs b/apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs index be6990808..edcb92438 100644 --- a/apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs +++ b/apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs @@ -7,16 +7,17 @@ use crate::host_bridge::files::{ import_document_file_payload, import_image_file_payload, import_text_file_payload, write_export_bytes_file, write_export_text_file, }; +use crate::host_bridge::navigation::{ + open_desktop_host_bridge_external_url, open_desktop_host_bridge_native_page, + reload_desktop_host_bridge_webview, +}; use crate::host_bridge::notifications::show_desktop_local_notification; use crate::host_bridge::protocol::{ failed, ok, required_string_payload, validate_request, HostBridgeRequest, HostBridgeResponse, HostBridgeRuntime, HOST_BRIDGE_VERSION, }; use crate::host_bridge::share::{share_text_from_request, DesktopShareState}; -use crate::shell::webview::{ - desktop_platform, normalize_external_url, normalize_native_page_url, - open_normalized_desktop_external_url, resolve_desktop_network_status, -}; +use crate::shell::webview::{desktop_platform, resolve_desktop_network_status}; use serde_json::json; use tauri::Manager; use tauri_plugin_dialog::DialogExt; @@ -67,56 +68,11 @@ pub(super) async fn execute_host_bridge_request( match request.method.as_str() { "app.openExternalUrl" => { - let url = match required_string_payload(&request, "url") - .ok() - .and_then(normalize_external_url) - { - Some(url) => url, - None => { - return failed( - request.id, - "invalid_request", - "url must use an allowed external protocol", - ) - } - }; - - match open_normalized_desktop_external_url(&app, url) { - Ok(()) => ok(request.id, json!(true)), - Err(error) => failed(request.id, "host_error", error.to_string()), - } + open_desktop_host_bridge_external_url(&app, &request) } "appearance.getColorScheme" => desktop_appearance_color_scheme(&app, &request), - "navigation.openNativePage" => { - let url = match required_string_payload(&request, "url") - .ok() - .and_then(normalize_native_page_url) - { - Some(url) => url, - None => { - return failed( - request.id, - "invalid_request", - "url must use an allowed same-origin H5 route", - ) - } - }; - - match app.get_webview_window("main") { - Some(window) => match window.navigate(url) { - Ok(()) => ok(request.id, json!(true)), - Err(error) => failed(request.id, "host_error", error.to_string()), - }, - None => failed(request.id, "host_error", "main window not found"), - } - } - "app.reloadWebView" => match app.get_webview_window("main") { - Some(window) => match window.reload() { - Ok(()) => ok(request.id, json!(true)), - Err(error) => failed(request.id, "host_error", error.to_string()), - }, - None => failed(request.id, "host_error", "main window not found"), - }, + "navigation.openNativePage" => open_desktop_host_bridge_native_page(&app, &request), + "app.reloadWebView" => reload_desktop_host_bridge_webview(&app, &request), "clipboard.writeText" => { let text = match required_string_payload(&request, "text") { Ok(text) => text, diff --git a/apps/desktop-shell/src-tauri/src/host_bridge/mod.rs b/apps/desktop-shell/src-tauri/src/host_bridge/mod.rs index c6d2c4268..276b1ee5b 100644 --- a/apps/desktop-shell/src-tauri/src/host_bridge/mod.rs +++ b/apps/desktop-shell/src-tauri/src/host_bridge/mod.rs @@ -4,6 +4,7 @@ mod badge; mod clipboard; mod dispatch; pub(crate) mod files; +mod navigation; mod notifications; pub(crate) mod protocol; mod share; diff --git a/apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs b/apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs new file mode 100644 index 000000000..09cddd9db --- /dev/null +++ b/apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs @@ -0,0 +1,72 @@ +use crate::host_bridge::protocol::{ + failed, ok, required_string_payload, HostBridgeRequest, HostBridgeResponse, +}; +use crate::shell::webview::{ + normalize_external_url, normalize_native_page_url, open_normalized_desktop_external_url, +}; +use serde_json::json; +use tauri::Manager; + +pub(crate) fn open_desktop_host_bridge_external_url( + app: &tauri::AppHandle, + request: &HostBridgeRequest, +) -> HostBridgeResponse { + let url = match required_string_payload(request, "url") + .ok() + .and_then(normalize_external_url) + { + Some(url) => url, + None => { + return failed( + request.id.clone(), + "invalid_request", + "url must use an allowed external protocol", + ) + } + }; + + match open_normalized_desktop_external_url(app, url) { + Ok(()) => ok(request.id.clone(), json!(true)), + Err(error) => failed(request.id.clone(), "host_error", error.to_string()), + } +} + +pub(crate) fn open_desktop_host_bridge_native_page( + app: &tauri::AppHandle, + request: &HostBridgeRequest, +) -> HostBridgeResponse { + let url = match required_string_payload(request, "url") + .ok() + .and_then(normalize_native_page_url) + { + Some(url) => url, + None => { + return failed( + request.id.clone(), + "invalid_request", + "url must use an allowed same-origin H5 route", + ) + } + }; + + match app.get_webview_window("main") { + Some(window) => match window.navigate(url) { + Ok(()) => ok(request.id.clone(), json!(true)), + Err(error) => failed(request.id.clone(), "host_error", error.to_string()), + }, + None => failed(request.id.clone(), "host_error", "main window not found"), + } +} + +pub(crate) fn reload_desktop_host_bridge_webview( + app: &tauri::AppHandle, + request: &HostBridgeRequest, +) -> HostBridgeResponse { + match app.get_webview_window("main") { + Some(window) => match window.reload() { + Ok(()) => ok(request.id.clone(), json!(true)), + Err(error) => failed(request.id.clone(), "host_error", error.to_string()), + }, + None => failed(request.id.clone(), "host_error", "main window not found"), + } +} diff --git a/apps/mobile-shell/scripts/check-config.mjs b/apps/mobile-shell/scripts/check-config.mjs index 04c86c4c3..d6a816882 100644 --- a/apps/mobile-shell/scripts/check-config.mjs +++ b/apps/mobile-shell/scripts/check-config.mjs @@ -22,6 +22,14 @@ const dispatchPath = new URL('../src/host-bridge/dispatch.ts', import.meta.url); const dispatchSource = fs.readFileSync(dispatchPath, 'utf8'); const hapticsPath = new URL('../src/host-bridge/haptics.ts', import.meta.url); const hapticsSource = fs.readFileSync(hapticsPath, 'utf8'); +const hostBridgeNavigationPath = new URL( + '../src/host-bridge/navigation.ts', + import.meta.url, +); +const hostBridgeNavigationSource = fs.readFileSync( + hostBridgeNavigationPath, + 'utf8', +); const notificationsPath = new URL('../src/host-bridge/notifications.ts', import.meta.url); const notificationsSource = fs.readFileSync(notificationsPath, 'utf8'); const protocolPath = new URL('../src/host-bridge/protocol.ts', import.meta.url); @@ -87,6 +95,7 @@ const requiredMobileShellSourceModules = [ 'host-bridge/dispatch.ts', 'host-bridge/files.ts', 'host-bridge/haptics.ts', + 'host-bridge/navigation.ts', 'host-bridge/notifications.ts', 'host-bridge/protocol.ts', 'host-bridge/scanner.ts', @@ -1621,10 +1630,12 @@ assertMobileDocumentPickerBoundary( ); if ( - !hostBridgeSource.includes( + !hostBridgeNavigationSource.includes( 'const externalUrlPayload = normalizeHostBridgeExternalUrlPayload(', ) || - !hostBridgeSource.includes('openMobileShellExternalNavigation(Linking, externalUrlPayload.url)') + !hostBridgeNavigationSource.includes( + 'openMobileShellExternalNavigation(navigator, externalUrlPayload.url)', + ) ) { throw new Error( 'mobile shell app.openExternalUrl must normalize payloads and use the shared external navigation helper', @@ -1855,7 +1866,32 @@ for (const snippet of [ } } -if (!dispatchSource.includes('openMobileShellExternalNavigation(Linking, externalUrlPayload.url)')) { +if ( + !dispatchSource.includes('openMobileHostBridgeExternalUrl(Linking, request.payload)') || + !dispatchSource.includes('openMobileHostBridgeNativePage(navigation, request.payload)') || + !dispatchSource.includes('reloadMobileHostBridgeWebView(navigation)') || + dispatchSource.includes('openMobileShellExternalNavigation') || + dispatchSource.includes('resolveMobileShellWebViewUrl') || + dispatchSource.includes('normalizeHostBridgeExternalUrlPayload') || + dispatchSource.includes('buildMobileShellUrl') +) { + throw new Error('mobile shell navigation HostBridge methods must delegate to navigation module'); +} +for (const snippet of [ + 'openMobileHostBridgeExternalUrl', + 'openMobileShellExternalNavigation(navigator, externalUrlPayload.url)', + 'normalizeHostBridgeExternalUrlPayload', + 'openMobileHostBridgeNativePage', + 'resolveMobileShellWebViewUrl', + 'buildMobileShellUrl(webViewUrl, navigation.urlOptions)', + 'reloadMobileHostBridgeWebView', +]) { + if (!hostBridgeNavigationSource.includes(snippet)) { + throw new Error(`mobile shell navigation module is missing ${snippet}`); + } +} + +if (!hostBridgeNavigationSource.includes('openMobileShellExternalNavigation(navigator, externalUrlPayload.url)')) { throw new Error( 'mobile shell HostBridge external URL flow must use the shared external navigation helper', ); diff --git a/apps/mobile-shell/src/host-bridge/dispatch.ts b/apps/mobile-shell/src/host-bridge/dispatch.ts index def36150b..26d810c82 100644 --- a/apps/mobile-shell/src/host-bridge/dispatch.ts +++ b/apps/mobile-shell/src/host-bridge/dispatch.ts @@ -5,17 +5,9 @@ import { type ClipboardWriteTextPayload, type HapticsImpactPayload, HOST_BRIDGE_VERSION, - type HostBridgeError, type HostBridgeRequest, - type NavigateNativePagePayload, - normalizeHostBridgeExternalUrlPayload, normalizeHostBridgeLocalNotification, - type OpenExternalUrlPayload, } from '../../../../packages/shared/src/contracts/hostBridge'; -import { - openMobileShellExternalNavigation, - resolveMobileShellWebViewUrl, -} from '../shell/navigation'; import { getMobileNetworkStatus } from '../shell/network'; import { MOBILE_SHELL_HOST_VERSION } from '../shell/runtime'; import { resolveMobileHostCapabilities } from './capabilities'; @@ -38,7 +30,6 @@ import { } from './protocol'; import { resetQrScannerForTest, scanQrCode } from './scanner'; import { openShare } from './share'; -import { buildMobileShellUrl } from '../shell/url'; import { showMobileLocalNotification } from './notifications'; import { readMobileClipboardText, @@ -47,6 +38,11 @@ import { import { runMobileHapticsImpact } from './haptics'; import { setMobileAppBadgeCount } from './badge'; import { getMobileAppearanceColorScheme } from './appearance'; +import { + openMobileHostBridgeExternalUrl, + openMobileHostBridgeNativePage, + reloadMobileHostBridgeWebView, +} from './navigation'; let currentShareTarget: unknown = null; let navigation: MobileHostBridgeNavigation | null = null; @@ -57,24 +53,6 @@ export function configureMobileHostBridgeNavigation( navigation = nextNavigation; } -async function openExternalUrl(payload: unknown) { - const externalUrlPayload = normalizeHostBridgeExternalUrlPayload( - (payload as OpenExternalUrlPayload | undefined)?.url, - ); - if (!externalUrlPayload) { - throw invalidRequest('url must use an allowed external protocol'); - } - - if (!(await openMobileShellExternalNavigation(Linking, externalUrlPayload.url))) { - throw { - code: 'host_error', - message: 'external URL cannot be opened', - } satisfies HostBridgeError; - } - - return true; -} - async function writeClipboard(payload: unknown) { if ( !(await writeMobileClipboardText( @@ -123,39 +101,6 @@ function getRuntime() { }; } -function openNativePage(payload: unknown) { - if (!navigation) { - throw unsupported('navigation.openNativePage'); - } - - const url = (payload as NavigateNativePagePayload | undefined)?.url; - if (typeof url !== 'string') { - throw invalidRequest('url is required'); - } - - const webViewUrl = resolveMobileShellWebViewUrl( - url, - navigation.allowedOrigin, - ); - if (!webViewUrl) { - throw invalidRequest('url must be an allowed same-origin web path'); - } - - navigation.openWebViewUrl( - buildMobileShellUrl(webViewUrl, navigation.urlOptions), - ); - return true; -} - -function reloadWebView() { - if (!navigation) { - throw unsupported('app.reloadWebView'); - } - - navigation.reloadWebView(); - return true; -} - export async function dispatchMobileHostBridgeRequest( request: HostBridgeRequest, ) { @@ -165,9 +110,12 @@ export async function dispatchMobileHostBridgeRequest( case 'appearance.getColorScheme': return ok(request, getMobileAppearanceColorScheme()); case 'app.openExternalUrl': - return ok(request, await openExternalUrl(request.payload)); + return ok( + request, + await openMobileHostBridgeExternalUrl(Linking, request.payload), + ); case 'app.reloadWebView': - return ok(request, reloadWebView()); + return ok(request, reloadMobileHostBridgeWebView(navigation)); case 'network.status': return ok(request, await getMobileNetworkStatus()); case 'clipboard.writeText': @@ -207,7 +155,10 @@ export async function dispatchMobileHostBridgeRequest( : null; return ok(request, true); case 'navigation.openNativePage': - return ok(request, openNativePage(request.payload)); + return ok( + request, + openMobileHostBridgeNativePage(navigation, request.payload), + ); case 'auth.requestLogin': case 'payment.request': return failure(request, unsupported(request.method)); diff --git a/apps/mobile-shell/src/host-bridge/navigation.ts b/apps/mobile-shell/src/host-bridge/navigation.ts new file mode 100644 index 000000000..adb22d7de --- /dev/null +++ b/apps/mobile-shell/src/host-bridge/navigation.ts @@ -0,0 +1,78 @@ +import { + type HostBridgeError, + type NavigateNativePagePayload, + normalizeHostBridgeExternalUrlPayload, + type OpenExternalUrlPayload, +} from '../../../../packages/shared/src/contracts/hostBridge'; +import { + type MobileShellExternalNavigator, + openMobileShellExternalNavigation, + resolveMobileShellWebViewUrl, +} from '../shell/navigation'; +import { buildMobileShellUrl } from '../shell/url'; +import { + type MobileHostBridgeNavigation, + invalidRequest, + unsupported, +} from './protocol'; + +export async function openMobileHostBridgeExternalUrl( + navigator: MobileShellExternalNavigator, + payload: unknown, +) { + const externalUrlPayload = normalizeHostBridgeExternalUrlPayload( + (payload as OpenExternalUrlPayload | undefined)?.url, + ); + if (!externalUrlPayload) { + throw invalidRequest('url must use an allowed external protocol'); + } + + if ( + !(await openMobileShellExternalNavigation(navigator, externalUrlPayload.url)) + ) { + throw { + code: 'host_error', + message: 'external URL cannot be opened', + } satisfies HostBridgeError; + } + + return true; +} + +export function openMobileHostBridgeNativePage( + navigation: MobileHostBridgeNavigation | null, + payload: unknown, +) { + if (!navigation) { + throw unsupported('navigation.openNativePage'); + } + + const url = (payload as NavigateNativePagePayload | undefined)?.url; + if (typeof url !== 'string') { + throw invalidRequest('url is required'); + } + + const webViewUrl = resolveMobileShellWebViewUrl( + url, + navigation.allowedOrigin, + ); + if (!webViewUrl) { + throw invalidRequest('url must be an allowed same-origin web path'); + } + + navigation.openWebViewUrl( + buildMobileShellUrl(webViewUrl, navigation.urlOptions), + ); + return true; +} + +export function reloadMobileHostBridgeWebView( + navigation: MobileHostBridgeNavigation | null, +) { + if (!navigation) { + throw unsupported('app.reloadWebView'); + } + + navigation.reloadWebView(); + return true; +} diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index db0d34665..25684fc4d 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -2785,3 +2785,10 @@ - 决策:`packages/shared/src/contracts/hostBridge.ts` 导出 `HOST_BRIDGE_DESKTOP_NETWORK_CHECK_TIMEOUT_MS`,作为桌面壳主站可达性探测超时的声明来源;Tauri Rust 侧保留同名职责镜像 `DESKTOP_NETWORK_CHECK_TIMEOUT_MS`,由 `apps/desktop-shell/scripts/check-config.mjs` 反查共享值并拒绝漂移。该边界只服务桌面宿主内部网络状态,不新增 H5 任意网络探测能力。 - 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`apps/desktop-shell/src-tauri/src/shell/network.rs`、`apps/desktop-shell/scripts/check-config.mjs`、宿主壳能力统一协议文档、Expo / Tauri HostBridge 方案文档。 - 验证方式:`npm run test -- packages/shared/src/contracts/hostBridge.test.ts`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。 + +## 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 细节。 +- 影响范围:`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`。 diff --git a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md index aaf8e88b2..7cd03cedf 100644 --- a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md +++ b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md @@ -64,11 +64,11 @@ src/ 已落地:`packages/shared/src/contracts/hostBridge.ts` 保存消息 envelope、method、payload 和错误码,H5、Expo 壳与 Tauri 壳共享同一份协议类型。 -三端宿主桥接层按职责对齐命名:微信小程序页面路由仍保留在 `miniprogram/pages/*`,`miniprogram/host-bridge/protocol.js` 只沉淀微信壳能力、页面 URL、结果 hash / storage key 和分享消息类型等常量,`dispatch.js` 只作为 `protocol`、`webView`、`payment`、`shareGrid`、`subscribeMessage` 的薄索引,真实协议归一、支付 / 订阅 / 分享结果编解码仍分别在 `webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js`,不把微信小程序硬改成 Expo / Tauri 的 request 总线;Page 生命周期、`wx.*` 容器调用、WebView 容器行为和页面工厂统一放在 `miniprogram/shell/webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js`,页面入口只做 `Page(createWechat...Page())` 装配。Expo 移动壳使用 `apps/mobile-shell/src/host-bridge/protocol.ts` 承接 envelope、request 校验、ok / failure 响应和 replay 基础类型,`capabilities.ts` 只引用共享 HostBridge capability profile 并选择 iOS 差异能力,`dispatch.ts` 承接 method 分发和宿主能力调用,`appearance.ts` 承接系统配色读取,`badge.ts` 承接受控角标能力,`files.ts` / `share.ts` / `scanner.ts` 分别承接文件、分享和扫码能力,`bridge.ts` 只作为 WebView message 入口、request id replay 编排和对外 facade;`apps/mobile-shell/App.tsx` 只装配 `apps/mobile-shell/src/shell/ShellApp.tsx`,由 `apps/mobile-shell/src/shell/*.ts(x)` 承接 WebView 容器、URL、导航、网络、生命周期、安全区、扫码 overlay 和 WebView policy。Tauri 桌面壳使用 `apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs` 承接 envelope、method 白名单、request 校验和 replay 状态,`appearance.rs` 承接窗口主题读取和 HostBridge 配色归一,`badge.rs` 承接受控任务栏角标能力,`capabilities.rs` 承接共享桌面 capability profile 的 Rust 运行时镜像,`dispatch.rs` 承接 method 分发和宿主能力调用,`files.rs` / `share.rs` / `notifications.rs` 分别承接文件、分享和本地通知能力,`mod.rs` 只保留模块声明、必要 re-export、`host_bridge_request` command facade 和 replay 编排;`apps/desktop-shell/src-tauri/src/shell/runtime.rs`、`url.rs`、`navigation.rs`、`network.rs`、`lifecycle.rs`、`file_drop.rs`、`events.rs`、`deep_link.rs`、`tray.rs`、`menu.rs`、`window_state.rs` 和 `webview.rs` 分别承接运行态、入口 URL、导航 / 下载、网络、生命周期、拖拽图片、HostBridge 事件注入、深链、托盘、应用菜单、窗口状态持久化和 WebView 门面,`apps/desktop-shell/src-tauri/src/app.rs` 承接 Tauri builder / plugin / window 装配,`main.rs` 只保留薄入口并调用 `app::run()`。 +三端宿主桥接层按职责对齐命名:微信小程序页面路由仍保留在 `miniprogram/pages/*`,`miniprogram/host-bridge/protocol.js` 只沉淀微信壳能力、页面 URL、结果 hash / storage key 和分享消息类型等常量,`dispatch.js` 只作为 `protocol`、`webView`、`payment`、`shareGrid`、`subscribeMessage` 的薄索引,真实协议归一、支付 / 订阅 / 分享结果编解码仍分别在 `webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js`,不把微信小程序硬改成 Expo / Tauri 的 request 总线;Page 生命周期、`wx.*` 容器调用、WebView 容器行为和页面工厂统一放在 `miniprogram/shell/webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js`,页面入口只做 `Page(createWechat...Page())` 装配。Expo 移动壳使用 `apps/mobile-shell/src/host-bridge/protocol.ts` 承接 envelope、request 校验、ok / failure 响应和 replay 基础类型,`capabilities.ts` 只引用共享 HostBridge capability profile 并选择 iOS 差异能力,`dispatch.ts` 承接 method 分发和宿主能力调用,`appearance.ts` 承接系统配色读取,`navigation.ts` 承接外链打开、受控 H5 跳转和 WebView 刷新,`badge.ts` 承接受控角标能力,`files.ts` / `share.ts` / `scanner.ts` 分别承接文件、分享和扫码能力,`bridge.ts` 只作为 WebView message 入口、request id replay 编排和对外 facade;`apps/mobile-shell/App.tsx` 只装配 `apps/mobile-shell/src/shell/ShellApp.tsx`,由 `apps/mobile-shell/src/shell/*.ts(x)` 承接 WebView 容器、URL、导航、网络、生命周期、安全区、扫码 overlay 和 WebView policy。Tauri 桌面壳使用 `apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs` 承接 envelope、method 白名单、request 校验和 replay 状态,`appearance.rs` 承接窗口主题读取和 HostBridge 配色归一,`navigation.rs` 承接外链打开、受控 H5 跳转和主窗口刷新,`badge.rs` 承接受控任务栏角标能力,`capabilities.rs` 承接共享桌面 capability profile 的 Rust 运行时镜像,`dispatch.rs` 承接 method 分发和宿主能力调用,`files.rs` / `share.rs` / `notifications.rs` 分别承接文件、分享和本地通知能力,`mod.rs` 只保留模块声明、必要 re-export、`host_bridge_request` command facade 和 replay 编排;`apps/desktop-shell/src-tauri/src/shell/runtime.rs`、`url.rs`、`navigation.rs`、`network.rs`、`lifecycle.rs`、`file_drop.rs`、`events.rs`、`deep_link.rs`、`tray.rs`、`menu.rs`、`window_state.rs` 和 `webview.rs` 分别承接运行态、入口 URL、导航 / 下载、网络、生命周期、拖拽图片、HostBridge 事件注入、深链、托盘、应用菜单、窗口状态持久化和 WebView 门面,`apps/desktop-shell/src-tauri/src/app.rs` 承接 Tauri builder / plugin / window 装配,`main.rs` 只保留薄入口并调用 `app::run()`。 -当前 `npm run check:native-shells` 锁定的生产文件清单为:微信桥接层 `dispatch.js`、`payment.js`、`protocol.js`、`shareGrid.js`、`subscribeMessage.js`、`webView.js`;微信 shell 层 `payment.js`、`shareGrid.js`、`subscribeMessage.js`、`webView.js`;微信页面包装层 `share-grid/index.js`、`share-grid/index.json`、`share-grid/index.wxml`、`share-grid/index.wxss`、`subscribe-message/index.js`、`subscribe-message/index.json`、`subscribe-message/index.wxml`、`subscribe-message/index.wxss`、`web-view/index.js`、`web-view/index.json`、`web-view/index.wxml`、`web-view/index.wxss`、`wechat-pay/index.js`、`wechat-pay/index.json`、`wechat-pay/index.wxml`、`wechat-pay/index.wxss`;移动源码根 `env.d.ts`;移动桥接层 `appearance.ts`、`badge.ts`、`bridge.ts`、`capabilities.ts`、`clipboard.ts`、`dispatch.ts`、`files.ts`、`haptics.ts`、`notifications.ts`、`protocol.ts`、`scanner.ts`、`share.ts`;移动 shell 层 `QrScannerOverlay.tsx`、`ShellApp.tsx`、`deepLink.ts`、`lifecycle.ts`、`loadFailure.ts`、`navigation.ts`、`network.ts`、`runtime.ts`、`safeArea.ts`、`url.ts`、`webViewGlobals.d.ts`、`webViewHistory.ts`、`webViewPolicy.ts`;桌面入口 `app.rs`、`main.rs`;桌面桥接层 `appearance.rs`、`badge.rs`、`capabilities.rs`、`clipboard.rs`、`dispatch.rs`、`files.rs`、`mod.rs`、`notifications.rs`、`protocol.rs`、`share.rs`;桌面 shell 层 `deep_link.rs`、`events.rs`、`file_drop.rs`、`lifecycle.rs`、`menu.rs`、`mod.rs`、`navigation.rs`、`network.rs`、`runtime.rs`、`tray.rs`、`url.rs`、`webview.rs`、`window_state.rs`。 +当前 `npm run check:native-shells` 锁定的生产文件清单为:微信桥接层 `dispatch.js`、`payment.js`、`protocol.js`、`shareGrid.js`、`subscribeMessage.js`、`webView.js`;微信 shell 层 `payment.js`、`shareGrid.js`、`subscribeMessage.js`、`webView.js`;微信页面包装层 `share-grid/index.js`、`share-grid/index.json`、`share-grid/index.wxml`、`share-grid/index.wxss`、`subscribe-message/index.js`、`subscribe-message/index.json`、`subscribe-message/index.wxml`、`subscribe-message/index.wxss`、`web-view/index.js`、`web-view/index.json`、`web-view/index.wxml`、`web-view/index.wxss`、`wechat-pay/index.js`、`wechat-pay/index.json`、`wechat-pay/index.wxml`、`wechat-pay/index.wxss`;移动源码根 `env.d.ts`;移动桥接层 `appearance.ts`、`badge.ts`、`bridge.ts`、`capabilities.ts`、`clipboard.ts`、`dispatch.ts`、`files.ts`、`haptics.ts`、`navigation.ts`、`notifications.ts`、`protocol.ts`、`scanner.ts`、`share.ts`;移动 shell 层 `QrScannerOverlay.tsx`、`ShellApp.tsx`、`deepLink.ts`、`lifecycle.ts`、`loadFailure.ts`、`navigation.ts`、`network.ts`、`runtime.ts`、`safeArea.ts`、`url.ts`、`webViewGlobals.d.ts`、`webViewHistory.ts`、`webViewPolicy.ts`;桌面入口 `app.rs`、`main.rs`;桌面桥接层 `appearance.rs`、`badge.rs`、`capabilities.rs`、`clipboard.rs`、`dispatch.rs`、`files.rs`、`mod.rs`、`navigation.rs`、`notifications.rs`、`protocol.rs`、`share.rs`;桌面 shell 层 `deep_link.rs`、`events.rs`、`file_drop.rs`、`lifecycle.rs`、`menu.rs`、`mod.rs`、`navigation.rs`、`network.rs`、`runtime.rs`、`tray.rs`、`url.rs`、`webview.rs`、`window_state.rs`。 -结构门禁按完整相对路径反查文档和目录:微信桥接层为 `miniprogram/host-bridge/dispatch.js`、`miniprogram/host-bridge/payment.js`、`miniprogram/host-bridge/protocol.js`、`miniprogram/host-bridge/shareGrid.js`、`miniprogram/host-bridge/subscribeMessage.js`、`miniprogram/host-bridge/webView.js`;微信 shell 层为 `miniprogram/shell/payment.js`、`miniprogram/shell/shareGrid.js`、`miniprogram/shell/subscribeMessage.js`、`miniprogram/shell/webView.js`;微信页面包装层为 `miniprogram/pages/share-grid/index.js`、`miniprogram/pages/share-grid/index.json`、`miniprogram/pages/share-grid/index.wxml`、`miniprogram/pages/share-grid/index.wxss`、`miniprogram/pages/subscribe-message/index.js`、`miniprogram/pages/subscribe-message/index.json`、`miniprogram/pages/subscribe-message/index.wxml`、`miniprogram/pages/subscribe-message/index.wxss`、`miniprogram/pages/web-view/index.js`、`miniprogram/pages/web-view/index.json`、`miniprogram/pages/web-view/index.wxml`、`miniprogram/pages/web-view/index.wxss`、`miniprogram/pages/wechat-pay/index.js`、`miniprogram/pages/wechat-pay/index.json`、`miniprogram/pages/wechat-pay/index.wxml`、`miniprogram/pages/wechat-pay/index.wxss`;移动源码根为 `apps/mobile-shell/src/env.d.ts`;移动桥接层为 `apps/mobile-shell/src/host-bridge/appearance.ts`、`apps/mobile-shell/src/host-bridge/badge.ts`、`apps/mobile-shell/src/host-bridge/bridge.ts`、`apps/mobile-shell/src/host-bridge/capabilities.ts`、`apps/mobile-shell/src/host-bridge/clipboard.ts`、`apps/mobile-shell/src/host-bridge/dispatch.ts`、`apps/mobile-shell/src/host-bridge/files.ts`、`apps/mobile-shell/src/host-bridge/haptics.ts`、`apps/mobile-shell/src/host-bridge/notifications.ts`、`apps/mobile-shell/src/host-bridge/protocol.ts`、`apps/mobile-shell/src/host-bridge/scanner.ts`、`apps/mobile-shell/src/host-bridge/share.ts`;移动 shell 层为 `apps/mobile-shell/src/shell/QrScannerOverlay.tsx`、`apps/mobile-shell/src/shell/ShellApp.tsx`、`apps/mobile-shell/src/shell/deepLink.ts`、`apps/mobile-shell/src/shell/lifecycle.ts`、`apps/mobile-shell/src/shell/loadFailure.ts`、`apps/mobile-shell/src/shell/navigation.ts`、`apps/mobile-shell/src/shell/network.ts`、`apps/mobile-shell/src/shell/runtime.ts`、`apps/mobile-shell/src/shell/safeArea.ts`、`apps/mobile-shell/src/shell/url.ts`、`apps/mobile-shell/src/shell/webViewGlobals.d.ts`、`apps/mobile-shell/src/shell/webViewHistory.ts`、`apps/mobile-shell/src/shell/webViewPolicy.ts`;桌面入口为 `apps/desktop-shell/src-tauri/src/app.rs`、`apps/desktop-shell/src-tauri/src/main.rs`;桌面桥接层为 `apps/desktop-shell/src-tauri/src/host_bridge/appearance.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/badge.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/capabilities.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/clipboard.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/files.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/mod.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/share.rs`;桌面 shell 层为 `apps/desktop-shell/src-tauri/src/shell/deep_link.rs`、`apps/desktop-shell/src-tauri/src/shell/events.rs`、`apps/desktop-shell/src-tauri/src/shell/file_drop.rs`、`apps/desktop-shell/src-tauri/src/shell/lifecycle.rs`、`apps/desktop-shell/src-tauri/src/shell/menu.rs`、`apps/desktop-shell/src-tauri/src/shell/mod.rs`、`apps/desktop-shell/src-tauri/src/shell/navigation.rs`、`apps/desktop-shell/src-tauri/src/shell/network.rs`、`apps/desktop-shell/src-tauri/src/shell/runtime.rs`、`apps/desktop-shell/src-tauri/src/shell/tray.rs`、`apps/desktop-shell/src-tauri/src/shell/url.rs`、`apps/desktop-shell/src-tauri/src/shell/webview.rs`、`apps/desktop-shell/src-tauri/src/shell/window_state.rs`。这些目录不得新增未登记子目录或生产入口;移动端和桌面端单端配置检查同样会拒绝未登记生产模块。 +结构门禁按完整相对路径反查文档和目录:微信桥接层为 `miniprogram/host-bridge/dispatch.js`、`miniprogram/host-bridge/payment.js`、`miniprogram/host-bridge/protocol.js`、`miniprogram/host-bridge/shareGrid.js`、`miniprogram/host-bridge/subscribeMessage.js`、`miniprogram/host-bridge/webView.js`;微信 shell 层为 `miniprogram/shell/payment.js`、`miniprogram/shell/shareGrid.js`、`miniprogram/shell/subscribeMessage.js`、`miniprogram/shell/webView.js`;微信页面包装层为 `miniprogram/pages/share-grid/index.js`、`miniprogram/pages/share-grid/index.json`、`miniprogram/pages/share-grid/index.wxml`、`miniprogram/pages/share-grid/index.wxss`、`miniprogram/pages/subscribe-message/index.js`、`miniprogram/pages/subscribe-message/index.json`、`miniprogram/pages/subscribe-message/index.wxml`、`miniprogram/pages/subscribe-message/index.wxss`、`miniprogram/pages/web-view/index.js`、`miniprogram/pages/web-view/index.json`、`miniprogram/pages/web-view/index.wxml`、`miniprogram/pages/web-view/index.wxss`、`miniprogram/pages/wechat-pay/index.js`、`miniprogram/pages/wechat-pay/index.json`、`miniprogram/pages/wechat-pay/index.wxml`、`miniprogram/pages/wechat-pay/index.wxss`;移动源码根为 `apps/mobile-shell/src/env.d.ts`;移动桥接层为 `apps/mobile-shell/src/host-bridge/appearance.ts`、`apps/mobile-shell/src/host-bridge/badge.ts`、`apps/mobile-shell/src/host-bridge/bridge.ts`、`apps/mobile-shell/src/host-bridge/capabilities.ts`、`apps/mobile-shell/src/host-bridge/clipboard.ts`、`apps/mobile-shell/src/host-bridge/dispatch.ts`、`apps/mobile-shell/src/host-bridge/files.ts`、`apps/mobile-shell/src/host-bridge/haptics.ts`、`apps/mobile-shell/src/host-bridge/navigation.ts`、`apps/mobile-shell/src/host-bridge/notifications.ts`、`apps/mobile-shell/src/host-bridge/protocol.ts`、`apps/mobile-shell/src/host-bridge/scanner.ts`、`apps/mobile-shell/src/host-bridge/share.ts`;移动 shell 层为 `apps/mobile-shell/src/shell/QrScannerOverlay.tsx`、`apps/mobile-shell/src/shell/ShellApp.tsx`、`apps/mobile-shell/src/shell/deepLink.ts`、`apps/mobile-shell/src/shell/lifecycle.ts`、`apps/mobile-shell/src/shell/loadFailure.ts`、`apps/mobile-shell/src/shell/navigation.ts`、`apps/mobile-shell/src/shell/network.ts`、`apps/mobile-shell/src/shell/runtime.ts`、`apps/mobile-shell/src/shell/safeArea.ts`、`apps/mobile-shell/src/shell/url.ts`、`apps/mobile-shell/src/shell/webViewGlobals.d.ts`、`apps/mobile-shell/src/shell/webViewHistory.ts`、`apps/mobile-shell/src/shell/webViewPolicy.ts`;桌面入口为 `apps/desktop-shell/src-tauri/src/app.rs`、`apps/desktop-shell/src-tauri/src/main.rs`;桌面桥接层为 `apps/desktop-shell/src-tauri/src/host_bridge/appearance.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/badge.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/capabilities.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/clipboard.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/files.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/mod.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/share.rs`;桌面 shell 层为 `apps/desktop-shell/src-tauri/src/shell/deep_link.rs`、`apps/desktop-shell/src-tauri/src/shell/events.rs`、`apps/desktop-shell/src-tauri/src/shell/file_drop.rs`、`apps/desktop-shell/src-tauri/src/shell/lifecycle.rs`、`apps/desktop-shell/src-tauri/src/shell/menu.rs`、`apps/desktop-shell/src-tauri/src/shell/mod.rs`、`apps/desktop-shell/src-tauri/src/shell/navigation.rs`、`apps/desktop-shell/src-tauri/src/shell/network.rs`、`apps/desktop-shell/src-tauri/src/shell/runtime.rs`、`apps/desktop-shell/src-tauri/src/shell/tray.rs`、`apps/desktop-shell/src-tauri/src/shell/url.rs`、`apps/desktop-shell/src-tauri/src/shell/webview.rs`、`apps/desktop-shell/src-tauri/src/shell/window_state.rs`。这些目录不得新增未登记子目录或生产入口;移动端和桌面端单端配置检查同样会拒绝未登记生产模块。 ## HostBridge 消息协议 diff --git a/docs/【前端架构】宿主壳能力统一协议-2026-06-17.md b/docs/【前端架构】宿主壳能力统一协议-2026-06-17.md index f25ab440b..8f812a289 100644 --- a/docs/【前端架构】宿主壳能力统一协议-2026-06-17.md +++ b/docs/【前端架构】宿主壳能力统一协议-2026-06-17.md @@ -37,11 +37,11 @@ AI H5 sandbox -> parent HostBridge adapter ``` -桥接层文件结构按宿主统一为“协议 / 能力清单 / 分发 / 宿主容器行为”四类职责。微信小程序不硬套 Expo / Tauri 的 request 总线:`miniprogram/host-bridge/protocol.js` 只沉淀微信壳能力、页面 URL、结果 hash / storage key 和分享消息类型等常量,`dispatch.js` 只作为 `protocol`、`webView`、`payment`、`shareGrid`、`subscribeMessage` 的薄索引,真实协议归一、支付 / 订阅 / 分享结果编解码仍分别放在 `webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js`;`miniprogram/shell/webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js` 承接 Page 生命周期、`wx.*` 容器调用、WebView 容器行为、支付页和订阅页装配,页面目录只保留 `Page(createWechat...Page())` 装配。Expo 移动壳使用 `apps/mobile-shell/src/host-bridge/protocol.ts` 承接 envelope、request 校验、ok / failure 响应和 replay 基础类型,`capabilities.ts` 只引用共享 HostBridge capability profile 并选择 iOS 差异能力,`dispatch.ts` 承接 method 分发和宿主能力调用,`appearance.ts` 承接系统配色读取,`badge.ts` 承接受控角标能力,`files.ts` / `share.ts` / `scanner.ts` 分别承接文件、分享和扫码能力,`bridge.ts` 只作为 WebView message 入口、request id replay 编排和对外 facade;`apps/mobile-shell/App.tsx` 只装配 `apps/mobile-shell/src/shell/ShellApp.tsx`,由 `apps/mobile-shell/src/shell/*.ts(x)` 承接 WebView 容器、URL、导航、网络、生命周期、安全区、扫码 overlay 和 WebView policy。Tauri 桌面壳使用 `apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs` 承接 envelope、method 白名单、request 校验和 replay 状态,`appearance.rs` 承接窗口主题读取和 HostBridge 配色归一,`badge.rs` 承接受控任务栏角标能力,`capabilities.rs` 承接共享桌面 capability profile 的 Rust 运行时镜像,`dispatch.rs` 承接 method 分发和宿主能力调用,`files.rs` / `share.rs` / `notifications.rs` 分别承接文件、分享和本地通知能力,`mod.rs` 只保留模块声明、必要 re-export、`host_bridge_request` command facade 和 replay 编排;`apps/desktop-shell/src-tauri/src/shell/runtime.rs`、`url.rs`、`navigation.rs`、`network.rs`、`lifecycle.rs`、`file_drop.rs`、`events.rs`、`deep_link.rs`、`tray.rs`、`menu.rs`、`window_state.rs` 和 `webview.rs` 分别承接运行态、入口 URL、导航 / 下载、网络、生命周期、拖拽图片、HostBridge 事件注入、深链、托盘、应用菜单、窗口状态持久化和 WebView 门面,`apps/desktop-shell/src-tauri/src/app.rs` 承接 Tauri builder / plugin / window 装配,`main.rs` 只保留薄入口并调用 `app::run()`。`npm run check:native-shells` 会检查这些目录清单。 +桥接层文件结构按宿主统一为“协议 / 能力清单 / 分发 / 宿主容器行为”四类职责。微信小程序不硬套 Expo / Tauri 的 request 总线:`miniprogram/host-bridge/protocol.js` 只沉淀微信壳能力、页面 URL、结果 hash / storage key 和分享消息类型等常量,`dispatch.js` 只作为 `protocol`、`webView`、`payment`、`shareGrid`、`subscribeMessage` 的薄索引,真实协议归一、支付 / 订阅 / 分享结果编解码仍分别放在 `webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js`;`miniprogram/shell/webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js` 承接 Page 生命周期、`wx.*` 容器调用、WebView 容器行为、支付页和订阅页装配,页面目录只保留 `Page(createWechat...Page())` 装配。Expo 移动壳使用 `apps/mobile-shell/src/host-bridge/protocol.ts` 承接 envelope、request 校验、ok / failure 响应和 replay 基础类型,`capabilities.ts` 只引用共享 HostBridge capability profile 并选择 iOS 差异能力,`dispatch.ts` 承接 method 分发和宿主能力调用,`appearance.ts` 承接系统配色读取,`navigation.ts` 承接外链打开、受控 H5 跳转和 WebView 刷新,`badge.ts` 承接受控角标能力,`files.ts` / `share.ts` / `scanner.ts` 分别承接文件、分享和扫码能力,`bridge.ts` 只作为 WebView message 入口、request id replay 编排和对外 facade;`apps/mobile-shell/App.tsx` 只装配 `apps/mobile-shell/src/shell/ShellApp.tsx`,由 `apps/mobile-shell/src/shell/*.ts(x)` 承接 WebView 容器、URL、导航、网络、生命周期、安全区、扫码 overlay 和 WebView policy。Tauri 桌面壳使用 `apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs` 承接 envelope、method 白名单、request 校验和 replay 状态,`appearance.rs` 承接窗口主题读取和 HostBridge 配色归一,`navigation.rs` 承接外链打开、受控 H5 跳转和主窗口刷新,`badge.rs` 承接受控任务栏角标能力,`capabilities.rs` 承接共享桌面 capability profile 的 Rust 运行时镜像,`dispatch.rs` 承接 method 分发和宿主能力调用,`files.rs` / `share.rs` / `notifications.rs` 分别承接文件、分享和本地通知能力,`mod.rs` 只保留模块声明、必要 re-export、`host_bridge_request` command facade 和 replay 编排;`apps/desktop-shell/src-tauri/src/shell/runtime.rs`、`url.rs`、`navigation.rs`、`network.rs`、`lifecycle.rs`、`file_drop.rs`、`events.rs`、`deep_link.rs`、`tray.rs`、`menu.rs`、`window_state.rs` 和 `webview.rs` 分别承接运行态、入口 URL、导航 / 下载、网络、生命周期、拖拽图片、HostBridge 事件注入、深链、托盘、应用菜单、窗口状态持久化和 WebView 门面,`apps/desktop-shell/src-tauri/src/app.rs` 承接 Tauri builder / plugin / window 装配,`main.rs` 只保留薄入口并调用 `app::run()`。`npm run check:native-shells` 会检查这些目录清单。 -当前 `npm run check:native-shells` 锁定的生产文件清单为:微信桥接层 `dispatch.js`、`payment.js`、`protocol.js`、`shareGrid.js`、`subscribeMessage.js`、`webView.js`;微信 shell 层 `payment.js`、`shareGrid.js`、`subscribeMessage.js`、`webView.js`;微信页面包装层 `share-grid/index.js`、`share-grid/index.json`、`share-grid/index.wxml`、`share-grid/index.wxss`、`subscribe-message/index.js`、`subscribe-message/index.json`、`subscribe-message/index.wxml`、`subscribe-message/index.wxss`、`web-view/index.js`、`web-view/index.json`、`web-view/index.wxml`、`web-view/index.wxss`、`wechat-pay/index.js`、`wechat-pay/index.json`、`wechat-pay/index.wxml`、`wechat-pay/index.wxss`;移动源码根 `env.d.ts`;移动桥接层 `appearance.ts`、`badge.ts`、`bridge.ts`、`capabilities.ts`、`clipboard.ts`、`dispatch.ts`、`files.ts`、`haptics.ts`、`notifications.ts`、`protocol.ts`、`scanner.ts`、`share.ts`;移动 shell 层 `QrScannerOverlay.tsx`、`ShellApp.tsx`、`deepLink.ts`、`lifecycle.ts`、`loadFailure.ts`、`navigation.ts`、`network.ts`、`runtime.ts`、`safeArea.ts`、`url.ts`、`webViewGlobals.d.ts`、`webViewHistory.ts`、`webViewPolicy.ts`;桌面入口 `app.rs`、`main.rs`;桌面桥接层 `appearance.rs`、`badge.rs`、`capabilities.rs`、`clipboard.rs`、`dispatch.rs`、`files.rs`、`mod.rs`、`notifications.rs`、`protocol.rs`、`share.rs`;桌面 shell 层 `deep_link.rs`、`events.rs`、`file_drop.rs`、`lifecycle.rs`、`menu.rs`、`mod.rs`、`navigation.rs`、`network.rs`、`runtime.rs`、`tray.rs`、`url.rs`、`webview.rs`、`window_state.rs`。 +当前 `npm run check:native-shells` 锁定的生产文件清单为:微信桥接层 `dispatch.js`、`payment.js`、`protocol.js`、`shareGrid.js`、`subscribeMessage.js`、`webView.js`;微信 shell 层 `payment.js`、`shareGrid.js`、`subscribeMessage.js`、`webView.js`;微信页面包装层 `share-grid/index.js`、`share-grid/index.json`、`share-grid/index.wxml`、`share-grid/index.wxss`、`subscribe-message/index.js`、`subscribe-message/index.json`、`subscribe-message/index.wxml`、`subscribe-message/index.wxss`、`web-view/index.js`、`web-view/index.json`、`web-view/index.wxml`、`web-view/index.wxss`、`wechat-pay/index.js`、`wechat-pay/index.json`、`wechat-pay/index.wxml`、`wechat-pay/index.wxss`;移动源码根 `env.d.ts`;移动桥接层 `appearance.ts`、`badge.ts`、`bridge.ts`、`capabilities.ts`、`clipboard.ts`、`dispatch.ts`、`files.ts`、`haptics.ts`、`navigation.ts`、`notifications.ts`、`protocol.ts`、`scanner.ts`、`share.ts`;移动 shell 层 `QrScannerOverlay.tsx`、`ShellApp.tsx`、`deepLink.ts`、`lifecycle.ts`、`loadFailure.ts`、`navigation.ts`、`network.ts`、`runtime.ts`、`safeArea.ts`、`url.ts`、`webViewGlobals.d.ts`、`webViewHistory.ts`、`webViewPolicy.ts`;桌面入口 `app.rs`、`main.rs`;桌面桥接层 `appearance.rs`、`badge.rs`、`capabilities.rs`、`clipboard.rs`、`dispatch.rs`、`files.rs`、`mod.rs`、`navigation.rs`、`notifications.rs`、`protocol.rs`、`share.rs`;桌面 shell 层 `deep_link.rs`、`events.rs`、`file_drop.rs`、`lifecycle.rs`、`menu.rs`、`mod.rs`、`navigation.rs`、`network.rs`、`runtime.rs`、`tray.rs`、`url.rs`、`webview.rs`、`window_state.rs`。 -结构门禁按完整相对路径反查文档和目录:微信桥接层为 `miniprogram/host-bridge/dispatch.js`、`miniprogram/host-bridge/payment.js`、`miniprogram/host-bridge/protocol.js`、`miniprogram/host-bridge/shareGrid.js`、`miniprogram/host-bridge/subscribeMessage.js`、`miniprogram/host-bridge/webView.js`;微信 shell 层为 `miniprogram/shell/payment.js`、`miniprogram/shell/shareGrid.js`、`miniprogram/shell/subscribeMessage.js`、`miniprogram/shell/webView.js`;微信页面包装层为 `miniprogram/pages/share-grid/index.js`、`miniprogram/pages/share-grid/index.json`、`miniprogram/pages/share-grid/index.wxml`、`miniprogram/pages/share-grid/index.wxss`、`miniprogram/pages/subscribe-message/index.js`、`miniprogram/pages/subscribe-message/index.json`、`miniprogram/pages/subscribe-message/index.wxml`、`miniprogram/pages/subscribe-message/index.wxss`、`miniprogram/pages/web-view/index.js`、`miniprogram/pages/web-view/index.json`、`miniprogram/pages/web-view/index.wxml`、`miniprogram/pages/web-view/index.wxss`、`miniprogram/pages/wechat-pay/index.js`、`miniprogram/pages/wechat-pay/index.json`、`miniprogram/pages/wechat-pay/index.wxml`、`miniprogram/pages/wechat-pay/index.wxss`;移动源码根为 `apps/mobile-shell/src/env.d.ts`;移动桥接层为 `apps/mobile-shell/src/host-bridge/appearance.ts`、`apps/mobile-shell/src/host-bridge/badge.ts`、`apps/mobile-shell/src/host-bridge/bridge.ts`、`apps/mobile-shell/src/host-bridge/capabilities.ts`、`apps/mobile-shell/src/host-bridge/clipboard.ts`、`apps/mobile-shell/src/host-bridge/dispatch.ts`、`apps/mobile-shell/src/host-bridge/files.ts`、`apps/mobile-shell/src/host-bridge/haptics.ts`、`apps/mobile-shell/src/host-bridge/notifications.ts`、`apps/mobile-shell/src/host-bridge/protocol.ts`、`apps/mobile-shell/src/host-bridge/scanner.ts`、`apps/mobile-shell/src/host-bridge/share.ts`;移动 shell 层为 `apps/mobile-shell/src/shell/QrScannerOverlay.tsx`、`apps/mobile-shell/src/shell/ShellApp.tsx`、`apps/mobile-shell/src/shell/deepLink.ts`、`apps/mobile-shell/src/shell/lifecycle.ts`、`apps/mobile-shell/src/shell/loadFailure.ts`、`apps/mobile-shell/src/shell/navigation.ts`、`apps/mobile-shell/src/shell/network.ts`、`apps/mobile-shell/src/shell/runtime.ts`、`apps/mobile-shell/src/shell/safeArea.ts`、`apps/mobile-shell/src/shell/url.ts`、`apps/mobile-shell/src/shell/webViewGlobals.d.ts`、`apps/mobile-shell/src/shell/webViewHistory.ts`、`apps/mobile-shell/src/shell/webViewPolicy.ts`;桌面入口为 `apps/desktop-shell/src-tauri/src/app.rs`、`apps/desktop-shell/src-tauri/src/main.rs`;桌面桥接层为 `apps/desktop-shell/src-tauri/src/host_bridge/appearance.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/badge.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/capabilities.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/clipboard.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/files.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/mod.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/share.rs`;桌面 shell 层为 `apps/desktop-shell/src-tauri/src/shell/deep_link.rs`、`apps/desktop-shell/src-tauri/src/shell/events.rs`、`apps/desktop-shell/src-tauri/src/shell/file_drop.rs`、`apps/desktop-shell/src-tauri/src/shell/lifecycle.rs`、`apps/desktop-shell/src-tauri/src/shell/menu.rs`、`apps/desktop-shell/src-tauri/src/shell/mod.rs`、`apps/desktop-shell/src-tauri/src/shell/navigation.rs`、`apps/desktop-shell/src-tauri/src/shell/network.rs`、`apps/desktop-shell/src-tauri/src/shell/runtime.rs`、`apps/desktop-shell/src-tauri/src/shell/tray.rs`、`apps/desktop-shell/src-tauri/src/shell/url.rs`、`apps/desktop-shell/src-tauri/src/shell/webview.rs`、`apps/desktop-shell/src-tauri/src/shell/window_state.rs`。这些目录不得新增未登记子目录或生产入口;移动端和桌面端单端配置检查同样会拒绝未登记生产模块。 +结构门禁按完整相对路径反查文档和目录:微信桥接层为 `miniprogram/host-bridge/dispatch.js`、`miniprogram/host-bridge/payment.js`、`miniprogram/host-bridge/protocol.js`、`miniprogram/host-bridge/shareGrid.js`、`miniprogram/host-bridge/subscribeMessage.js`、`miniprogram/host-bridge/webView.js`;微信 shell 层为 `miniprogram/shell/payment.js`、`miniprogram/shell/shareGrid.js`、`miniprogram/shell/subscribeMessage.js`、`miniprogram/shell/webView.js`;微信页面包装层为 `miniprogram/pages/share-grid/index.js`、`miniprogram/pages/share-grid/index.json`、`miniprogram/pages/share-grid/index.wxml`、`miniprogram/pages/share-grid/index.wxss`、`miniprogram/pages/subscribe-message/index.js`、`miniprogram/pages/subscribe-message/index.json`、`miniprogram/pages/subscribe-message/index.wxml`、`miniprogram/pages/subscribe-message/index.wxss`、`miniprogram/pages/web-view/index.js`、`miniprogram/pages/web-view/index.json`、`miniprogram/pages/web-view/index.wxml`、`miniprogram/pages/web-view/index.wxss`、`miniprogram/pages/wechat-pay/index.js`、`miniprogram/pages/wechat-pay/index.json`、`miniprogram/pages/wechat-pay/index.wxml`、`miniprogram/pages/wechat-pay/index.wxss`;移动源码根为 `apps/mobile-shell/src/env.d.ts`;移动桥接层为 `apps/mobile-shell/src/host-bridge/appearance.ts`、`apps/mobile-shell/src/host-bridge/badge.ts`、`apps/mobile-shell/src/host-bridge/bridge.ts`、`apps/mobile-shell/src/host-bridge/capabilities.ts`、`apps/mobile-shell/src/host-bridge/clipboard.ts`、`apps/mobile-shell/src/host-bridge/dispatch.ts`、`apps/mobile-shell/src/host-bridge/files.ts`、`apps/mobile-shell/src/host-bridge/haptics.ts`、`apps/mobile-shell/src/host-bridge/navigation.ts`、`apps/mobile-shell/src/host-bridge/notifications.ts`、`apps/mobile-shell/src/host-bridge/protocol.ts`、`apps/mobile-shell/src/host-bridge/scanner.ts`、`apps/mobile-shell/src/host-bridge/share.ts`;移动 shell 层为 `apps/mobile-shell/src/shell/QrScannerOverlay.tsx`、`apps/mobile-shell/src/shell/ShellApp.tsx`、`apps/mobile-shell/src/shell/deepLink.ts`、`apps/mobile-shell/src/shell/lifecycle.ts`、`apps/mobile-shell/src/shell/loadFailure.ts`、`apps/mobile-shell/src/shell/navigation.ts`、`apps/mobile-shell/src/shell/network.ts`、`apps/mobile-shell/src/shell/runtime.ts`、`apps/mobile-shell/src/shell/safeArea.ts`、`apps/mobile-shell/src/shell/url.ts`、`apps/mobile-shell/src/shell/webViewGlobals.d.ts`、`apps/mobile-shell/src/shell/webViewHistory.ts`、`apps/mobile-shell/src/shell/webViewPolicy.ts`;桌面入口为 `apps/desktop-shell/src-tauri/src/app.rs`、`apps/desktop-shell/src-tauri/src/main.rs`;桌面桥接层为 `apps/desktop-shell/src-tauri/src/host_bridge/appearance.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/badge.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/capabilities.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/clipboard.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/files.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/mod.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/share.rs`;桌面 shell 层为 `apps/desktop-shell/src-tauri/src/shell/deep_link.rs`、`apps/desktop-shell/src-tauri/src/shell/events.rs`、`apps/desktop-shell/src-tauri/src/shell/file_drop.rs`、`apps/desktop-shell/src-tauri/src/shell/lifecycle.rs`、`apps/desktop-shell/src-tauri/src/shell/menu.rs`、`apps/desktop-shell/src-tauri/src/shell/mod.rs`、`apps/desktop-shell/src-tauri/src/shell/navigation.rs`、`apps/desktop-shell/src-tauri/src/shell/network.rs`、`apps/desktop-shell/src-tauri/src/shell/runtime.rs`、`apps/desktop-shell/src-tauri/src/shell/tray.rs`、`apps/desktop-shell/src-tauri/src/shell/url.rs`、`apps/desktop-shell/src-tauri/src/shell/webview.rs`、`apps/desktop-shell/src-tauri/src/shell/window_state.rs`。这些目录不得新增未登记子目录或生产入口;移动端和桌面端单端配置检查同样会拒绝未登记生产模块。 Tauri 桌面壳启动时必须按 `label="main"` 解析 `tauri.conf.json` 主窗口配置,并在创建 WebView 前补写 `native_app`、`tauri_desktop` 和真实 capability 上下文;缺少主窗口配置时启动直接失败,不允许按 `windows[0]` 兜底或无主窗口静默运行。 diff --git a/scripts/check-native-shells.mjs b/scripts/check-native-shells.mjs index 9988e58fb..9a46a1f71 100644 --- a/scripts/check-native-shells.mjs +++ b/scripts/check-native-shells.mjs @@ -258,6 +258,7 @@ const expectedMobileHostBridgeFiles = [ 'dispatch.ts', 'files.ts', 'haptics.ts', + 'navigation.ts', 'notifications.ts', 'protocol.ts', 'scanner.ts', @@ -301,6 +302,7 @@ const expectedDesktopHostBridgeRustFiles = [ 'dispatch.rs', 'files.rs', 'mod.rs', + 'navigation.rs', 'notifications.rs', 'protocol.rs', 'share.rs',