diff --git a/apps/desktop-shell/scripts/check-config.mjs b/apps/desktop-shell/scripts/check-config.mjs index 8190ca348..1174a364f 100644 --- a/apps/desktop-shell/scripts/check-config.mjs +++ b/apps/desktop-shell/scripts/check-config.mjs @@ -221,6 +221,11 @@ function resolveHostCapabilitiesFromUrl(rawUrl) { .filter(Boolean); } +function resolveHostVersionFromUrl(rawUrl) { + const url = new URL(rawUrl, 'https://app.genarrative.world/'); + return url.searchParams.get('hostVersion'); +} + function assertSameList(actual, expected, label) { if ( actual.length !== expected.length || @@ -339,6 +344,10 @@ if (extractCargoPackageString(cargoManifest, 'version') !== config.version) { throw new Error('desktop shell Cargo package version must match tauri.conf.json version'); } +if (!main.includes('host_version: env!("CARGO_PKG_VERSION")')) { + throw new Error('desktop shell runtime response must use the Cargo package version'); +} + if (config.build?.frontendDist !== '../../../dist') { throw new Error('desktop shell must package the root H5 dist'); } @@ -435,6 +444,14 @@ for (const part of requiredUrlParts) { } } +if (resolveHostVersionFromUrl(mainWindow.url) !== config.version) { + throw new Error('desktop shell main window hostVersion must match tauri.conf.json version'); +} + +if (resolveHostVersionFromUrl(config.build?.devUrl ?? '') !== config.version) { + throw new Error('desktop shell dev hostVersion must match tauri.conf.json version'); +} + assertSameList( resolveHostCapabilitiesFromUrl(mainWindow.url), desktopCapabilities, diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index e27910392..291aacbf6 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -58,6 +58,7 @@ - 2026-06-18 Tauri 系统托盘:桌面壳启用真实 OS 托盘并复用品牌图标,托盘菜单只执行显示主窗口、刷新主窗口和退出应用,左键点击托盘图标恢复并聚焦主窗口;该能力归桌面壳自身,不进入 HostBridge capability,不向 H5 暴露托盘、菜单、shell 或任意窗口控制 API。托盘注册成功时主窗口关闭按钮只隐藏到托盘,必须通过托盘“退出”结束应用;托盘注册失败不得阻断主窗口启动,也不得拦截关闭,避免窗口消失后无法恢复。`check:native-shells` 和 Tauri cargo test 覆盖托盘配置、菜单动作映射和关闭策略。 - 2026-06-18 Tauri 单实例:桌面壳启用 `tauri-plugin-single-instance` 并要求该插件最先注册;重复启动 App 时第二实例退出,只唤醒、取消最小化并聚焦已有主窗口,不把第二实例 argv / cwd / 深链内容作为事件透传给 H5。桌面深链后续如需接入,必须先定义受控 URL 归一和宿主边界,不能借单实例回调直接开放任意启动参数。 - 2026-06-18 桌面壳安装包身份:Tauri 桌面壳的产品名固定为 `Genarrative`,应用 identifier 固定为 `world.genarrative.desktop`,Tauri 配置、`apps/desktop-shell/package.json` 与 Cargo package 版本统一为 `0.1.0`;Release 主窗口只加载打包的 `index.html` 和根 `dist` H5 资产,dev URL 只指向本机 Vite 调试入口。桌面壳 CSP 保持 `script-src 'self'`,不得加入 `unsafe-eval`、`tauri:` 或 `file:`,也不得在没有真实端点、签名密钥和发布流程前配置 updater;检查脚本会拒绝包身份、版本、CSP 或 updater 约束漂移。 +- 2026-06-18 桌面壳 HostBridge 版本边界:Tauri release / dev 入口 URL 的 `hostVersion` 必须与 `tauri.conf.json`、`apps/desktop-shell/package.json` 和 Cargo package 版本一致;`host.getRuntime` 回包继续使用 `env!("CARGO_PKG_VERSION")`,配置检查会拒绝入口 query 版本、Tauri 配置版本或 Rust runtime 版本来源分叉。 - 2026-06-18 桌面壳运行时平台 query:Tauri 静态配置中的 `hostPlatform=unknown` 只作为跨平台构建模板值;Rust `setup` 手动创建主窗口前必须把入口 URL 改写为当前 `macos` / `windows` / `linux`,保证 H5 首屏 query 与 `host.getRuntime` 回读的平台一致。第二实例参数、外部 deep link 或 H5 自报值不得覆盖该字段;桌面壳测试和配置检查会拒绝绕过该归一流程。 - 2026-06-18 桌面壳顶层导航边界:Tauri 主 WebView 只允许打包资产 URL 和 `https://app.genarrative.world` 同源 H5 route 留在主窗口;外域 `http:` / `https:`、`mailto:`、`tel:` 导航与 `window.open` 请求交给系统 opener 后拒绝 WebView 留壳;`javascript:`、`file:` 等危险协议直接拒绝。该规则不进入 HostBridge capability,不开放 opener JS guest API,配置检查和 cargo test 覆盖导航策略。 - 2026-06-18 桌面壳默认下载边界:Tauri 主 WebView 的下载事件默认拒绝网页自动下载和 `` 落盘,桌面文件保存只能通过 `file.exportText`、`file.exportImage`、`file.exportAudio` 等已声明 HostBridge method 进入 Rust 侧系统保存对话框,并继续执行 MIME、大小、文件名清洗和用户确认。该规则不进入 HostBridge capability,配置检查和 cargo test 覆盖下载拒绝策略。 diff --git a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md index 5c2db393d..133103b90 100644 --- a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md +++ b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md @@ -323,6 +323,8 @@ GameBridge 禁止: 2026-06-18 追加:桌面壳安装包身份固定为 `world.genarrative.desktop`,产品名为 `Genarrative`,Tauri、Node package 与 Cargo package 版本统一为 `0.1.0`。Release 主窗口只能从打包进二进制的 `index.html` 进入根 `dist` H5 资产,dev URL 只能指向本机 Vite 调试入口;CSP 必须保持 `script-src 'self'`,不得加入 `unsafe-eval`、`tauri:` 或 `file:` 这类扩大桌面攻击面的来源。当前不配置自动更新器,直到存在真实更新端点、签名密钥和发布流程再接入;`apps/desktop-shell/scripts/check-config.mjs` 会校验这些包身份、版本、CSP 和 updater 禁用约束。 +2026-06-18 追加:桌面壳 release / dev 入口 URL 的 `hostVersion` 必须与 Tauri `tauri.conf.json`、Node package 和 Cargo package 版本一致;`host.getRuntime` 回包继续使用 `env!("CARGO_PKG_VERSION")`,配置检查会拒绝入口 query 版本、Tauri 配置版本或 Rust runtime 版本来源分叉,避免桌面包升级时 H5 首屏上下文与 runtime 回读不一致。 + 2026-06-18 追加:桌面壳静态 Tauri 配置中的 `hostPlatform=unknown` 只作为跨平台构建模板值。Rust `setup` 手动创建主窗口前会把入口 URL 归一为当前 `macos` / `windows` / `linux`,保证 H5 首屏 query 与 `host.getRuntime` 回读的平台一致;不通过第二实例参数、外部 deep link 或 H5 自报值覆盖该平台字段。 2026-06-18 追加:桌面壳主 WebView 增加顶层导航边界。打包资产 URL 和 `https://app.genarrative.world` 同源 H5 route 可以继续留在主窗口;外域 `http:` / `https:`、`mailto:`、`tel:` 导航和 `window.open` 请求只交给系统 opener 后阻止留壳;`javascript:`、`file:` 等危险协议直接阻断。该规则不新增 HostBridge capability,也不开放 opener 插件 JS guest API,避免外域页面停留在带 `host_bridge_request` 权限的主 WebView 内。