From 510922913e5e0d4d28b80349134387ad25ee6ddc Mon Sep 17 00:00:00 2001 From: kdletters Date: Sat, 20 Jun 2026 10:59:38 +0800 Subject: [PATCH] =?UTF-8?q?=E9=94=81=E5=AE=9A=E7=A7=BB=E5=8A=A8=E5=85=A5?= =?UTF-8?q?=E5=8F=A3=E5=AE=BF=E4=B8=BB=E4=B8=8A=E4=B8=8B=E6=96=87=E6=B8=85?= =?UTF-8?q?=E6=B4=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移动 URL 单测覆盖旧宿主 query 被清理 移动壳配置检查反查入口上下文清洗测试 共享决策日志补充移动入口 query 边界 --- apps/mobile-shell/scripts/check-config.mjs | 14 +++++++++++ apps/mobile-shell/src/shell/url.test.ts | 24 +++++++++++++++++++ .../shared-memory/decision-log.md | 1 + 3 files changed, 39 insertions(+) diff --git a/apps/mobile-shell/scripts/check-config.mjs b/apps/mobile-shell/scripts/check-config.mjs index 22798d81d..17c913a24 100644 --- a/apps/mobile-shell/scripts/check-config.mjs +++ b/apps/mobile-shell/scripts/check-config.mjs @@ -143,6 +143,8 @@ const hostBridgeSource = bridgeSourceFiles .join('\n'); const urlPath = new URL('../src/shell/url.ts', import.meta.url); const urlSource = fs.readFileSync(urlPath, 'utf8'); +const urlTestPath = new URL('../src/shell/url.test.ts', import.meta.url); +const urlTestSource = fs.readFileSync(urlTestPath, 'utf8'); const deepLinkPath = new URL('../src/shell/deepLink.ts', import.meta.url); const deepLinkSource = fs.readFileSync(deepLinkPath, 'utf8'); const navigationPath = new URL('../src/shell/navigation.ts', import.meta.url); @@ -1715,6 +1717,18 @@ for (const hardcodedHostContextSnippet of [ } } +for (const snippet of [ + '附加宿主上下文前会清理旧移动壳 query', + 'clientRuntime=browser&hostShell=old_shell&hostCapabilities=old', + "expect(builtUrl.match(/clientRuntime=/g)).toHaveLength(1)", + "expect(builtUrl.match(/hostShell=/g)).toHaveLength(1)", + "expect(builtUrl.match(/hostCapabilities=/g)).toHaveLength(1)", +]) { + if (!urlTestSource.includes(snippet)) { + throw new Error(`mobile shell URL tests must cover stale host-context cleanup: ${snippet}`); + } +} + for (const snippet of [ 'resolveMobileShellBaseWebUrl(baseWebUrl)', 'resolveTargetPath(rawUrl, webOrigin)', diff --git a/apps/mobile-shell/src/shell/url.test.ts b/apps/mobile-shell/src/shell/url.test.ts index f26b053b0..be8ca2e72 100644 --- a/apps/mobile-shell/src/shell/url.test.ts +++ b/apps/mobile-shell/src/shell/url.test.ts @@ -41,6 +41,30 @@ describe('buildMobileShellUrl', () => { expect(url.searchParams.get('work')).toBe('PZ-1'); }); + test('附加宿主上下文前会清理旧移动壳 query', () => { + const rawUrl = + 'https://app.genarrative.world/works/detail?clientRuntime=browser&hostShell=old_shell&hostCapabilities=old&work=PZ-1'; + const builtUrl = buildMobileShellUrl(rawUrl, { + platform: 'android', + hostVersion: '0.1.0', + capabilities: ['host.getRuntime', 'share.open'], + }); + const url = new URL(builtUrl); + + expect(url.searchParams.get('work')).toBe('PZ-1'); + expect(url.searchParams.get('clientRuntime')).toBe('native_app'); + expect(url.searchParams.get('hostShell')).toBe('expo_mobile'); + expect(url.searchParams.get('hostCapabilities')).toBe( + 'host.getRuntime,share.open', + ); + expect(builtUrl.match(/clientRuntime=/g)).toHaveLength(1); + expect(builtUrl.match(/hostShell=/g)).toHaveLength(1); + expect(builtUrl.match(/hostCapabilities=/g)).toHaveLength(1); + expect(builtUrl).not.toContain('clientRuntime=browser'); + expect(builtUrl).not.toContain('hostShell=old_shell'); + expect(builtUrl).not.toContain('hostCapabilities=old'); + }); + test('支持按平台注入不同能力清单', () => { const iosUrl = new URL( buildMobileShellUrl('https://app.genarrative.world/', { diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 934706178..0c0d24e98 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -2657,6 +2657,7 @@ - 背景:Expo 移动壳启动 URL 和 deep link 已经会给 H5 追加 `native_app`、`expo_mobile`、平台、版本和 capability query;但 H5 通过 HostBridge 调用 `navigation.openNativePage` 主动跳转同源 route 时,壳层只把裸同源 URL 交给 WebView,目标页首屏可能短暂或持续按普通浏览器运行态识别。 - 决策:`navigation.openNativePage` 仍只接受同源 H5 route,不新增真实原生页面、不放宽外域导航;通过校验后的目标 URL 在进入 WebView 前统一调用 `buildMobileShellUrl(...)` 重新附加当前 `MobileShellUrlOptions`,确保主动导航后的页面继续带 `clientRuntime=native_app`、`hostShell=expo_mobile`、真实平台、宿主版本和当前 capability 清单。 +- 2026-06-20 追加:`buildMobileShellUrl(...)` 对启动 URL、deep link 和主动导航目标补写宿主上下文时,必须先覆盖旧 `clientRuntime`、`hostShell`、`hostCapabilities` 等宿主 query,确保输出只包含当前 Expo 壳真实上下文;移动壳配置检查反查 URL 单测中的旧 query 清洗边界。 - 影响范围:`apps/mobile-shell/src/host-bridge/`、`apps/mobile-shell/src/shell/ShellApp.tsx`、Expo / Tauri HostBridge 方案文档。 - 验证方式:`npm run mobile-shell:test`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run typecheck -- --pretty false`、`npm run check:encoding`、`git diff --check`。