收紧H5原生传输层访问边界

禁止 H5 生产代码直接依赖 nativeAppHostBridge 低层传输

要求业务侧通过 HostBridge facade 使用原生能力

同步项目决策日志中的 facade 边界约束
This commit is contained in:
2026-06-21 18:58:24 +08:00
parent 9081527a2b
commit 6b5e75a070
2 changed files with 27 additions and 0 deletions
@@ -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`。
+26
View File
@@ -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();