diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index b51a7600c..b6e9fb583 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -3221,6 +3221,7 @@ - 背景:Expo / Tauri 壳会把 `clientRuntime`、`hostShell` 和 `hostCapabilities` 写入 H5 URL,用于保留宿主上下文和路由状态。如果 H5 在 `host.getRuntime` 异步回包前把 URL query 中的 `hostCapabilities` 作为真实能力来源,深链旧参数或伪造 query 会让首屏短暂展示或触发原生动作。 - 决策:H5 仍可用 URL query 判断宿主类型和保留上下文,但 `canUseNativeHostCapability` 只能信任真实 native bridge 存在且 `host.getRuntime` 已缓存的 capability;query 中的 `hostCapabilities` 不再参与能力门控。`host.getRuntime` 刷新只依赖真实 Expo WebView / Tauri invoke 注入,不依赖 query capability。桌面 Tauri 事件白名单必须等于桌面 capability 中已声明的事件子集,不得包含未声明的 `network.statusChanged`。 +- 2026-06-21 调整:H5 生产代码不得直接 import `src/services/host-bridge/nativeAppHostBridge.ts` 低层 transport;业务层、组件层和 wrapper 必须经 `src/services/host-bridge/hostBridge.ts` facade 使用原生能力,保证真实 runtime 回读、capability 门控、payload 归一和事件订阅门控始终生效。`scripts/check-native-shells.mjs` 负责扫描生产 H5 源码并拒绝绕过 facade 的直接 transport 依赖。 - 影响范围:`src/services/host-bridge/hostBridge.ts`、H5 HostBridge 消费测试、`apps/desktop-shell/src-tauri/src/shell/events.rs`、`scripts/check-native-shells.mjs`。 - 验证方式:`npm run test -- src/services/host-bridge/hostBridge.test.ts src/services/runtimeAudioFeedback.test.ts src/App.test.tsx src/components/common/CreativeAudioInputPanel.test.tsx src/components/common/PublishShareModal.test.tsx src/components/platform-entry/platformHostBridgeSync.test.ts`、`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml shell::events`、`npm run check:native-shells`。 diff --git a/scripts/check-native-shells.mjs b/scripts/check-native-shells.mjs index 46dba88fa..e6ce37ab3 100644 --- a/scripts/check-native-shells.mjs +++ b/scripts/check-native-shells.mjs @@ -22,6 +22,8 @@ const productionShellScanRoots = [ 'src/services/host-bridge', ]; const h5HostBridgeFacadeModule = 'src/services/host-bridge/hostBridge'; +const h5NativeAppHostBridgeTransportModule = + 'src/services/host-bridge/nativeAppHostBridge'; const h5HostBridgeScannedFacadeImports = new Set([ 'canUseHostShareGrid', 'captureHostImageFile', @@ -1913,6 +1915,27 @@ function collectH5HostBridgeCallChainFiles() { return [...scannedFiles].sort(); } +function assertH5NativeAppTransportFacadeBoundary() { + const sourceFiles = collectFiles('src', shouldScanH5ProductionSourceFile); + const directTransportImports = []; + + for (const file of sourceFiles) { + const source = fs.readFileSync(file, 'utf8'); + const imports = extractImportSpecifiers(source).map((specifier) => + importedModulePath(file, specifier), + ); + if (imports.includes(h5NativeAppHostBridgeTransportModule)) { + directTransportImports.push(file); + } + } + + if (directTransportImports.length > 0) { + throw new Error( + `H5 production code must use the HostBridge facade instead of native app transport directly: ${directTransportImports.join(', ')}`, + ); + } +} + function assertNoDevScaffoldTermsInFiles(files, terms) { for (const file of files) { const source = fs.readFileSync(file, 'utf8'); @@ -3964,6 +3987,9 @@ assertH5NativeAppTransportTimeoutBoundaries(); console.log('[check:native-shells] h5-native-app-message-source-boundaries'); assertH5NativeAppMessageSourceBoundaries(); +console.log('[check:native-shells] h5-native-app-transport-facade-boundary'); +assertH5NativeAppTransportFacadeBoundary(); + console.log('[check:native-shells] production-shell-dev-scaffold-scan'); assertNoProductionShellDevScaffoldTerms();