From 888c4bc4d58443e2b911ecebe2f012fe2aa74846 Mon Sep 17 00:00:00 2001 From: kdletters Date: Sat, 20 Jun 2026 09:31:23 +0800 Subject: [PATCH] =?UTF-8?q?=E9=94=81=E5=AE=9A=E7=A7=BB=E5=8A=A8=E5=A3=B3?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E5=A4=B1=E8=B4=A5=E6=B5=8B=E8=AF=95=E9=97=A8?= =?UTF-8?q?=E7=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移动壳配置检查反查加载失败同源和非主页面测试 共享决策同步移动壳错误页验收边界 --- apps/mobile-shell/scripts/check-config.mjs | 22 +++++++++++++++++++ .../shared-memory/decision-log.md | 1 + 2 files changed, 23 insertions(+) diff --git a/apps/mobile-shell/scripts/check-config.mjs b/apps/mobile-shell/scripts/check-config.mjs index 1ef235446..51cf074e7 100644 --- a/apps/mobile-shell/scripts/check-config.mjs +++ b/apps/mobile-shell/scripts/check-config.mjs @@ -143,6 +143,11 @@ const webViewHistoryPath = new URL('../src/shell/webViewHistory.ts', import.meta const webViewHistorySource = fs.readFileSync(webViewHistoryPath, 'utf8'); const loadFailurePath = new URL('../src/shell/loadFailure.ts', import.meta.url); const loadFailureSource = fs.readFileSync(loadFailurePath, 'utf8'); +const loadFailureTestPath = new URL( + '../src/shell/loadFailure.test.ts', + import.meta.url, +); +const loadFailureTestSource = fs.readFileSync(loadFailureTestPath, 'utf8'); const runtimePath = new URL('../src/shell/runtime.ts', import.meta.url); const runtimeSource = fs.readFileSync(runtimePath, 'utf8'); const safeAreaTestPath = new URL('../src/shell/safeArea.test.ts', import.meta.url); @@ -1434,6 +1439,23 @@ for (const snippet of [ } } +for (const snippet of [ + "describe('loadFailure'", + "test('归一化同源 HTTP 加载失败'", + "test('忽略外域和非页面加载失败'", + "test('只展示当前主页面失败'", + "url: 'https://example.com/'", + "url: 'about:blank'", + "url: 'javascript:alert(1)'", + "url: '/favicon.ico'", + "url: 'https://app.genarrative.world/assets/main.js'", + "https://app.genarrative.world/creation/puzzle", +]) { + if (!loadFailureTestSource.includes(snippet)) { + throw new Error(`mobile shell load failure tests missing ${snippet}`); + } +} + for (const snippet of [ 'MOBILE_WEBVIEW_BLOCKED_DOWNLOAD_PROTOCOLS', 'HOST_BRIDGE_MOBILE_WEBVIEW_BLOCKED_DOWNLOAD_PROTOCOLS', diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index cce212641..c5a3c7260 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -75,6 +75,7 @@ - 2026-06-18 原生壳网络状态:新增 `network.status` 与 `network.statusChanged` HostBridge capability,Expo 壳通过 `expo-network` 查询和订阅真实系统网络状态,Tauri 壳从 `WEB_APP_ORIGIN` 解析主站 host / port 后做短超时 TCP 可达性查询,并通过 WebView `online` / `offline` 注入变化事件;H5 统一使用 `getHostNetworkStatus()` / `subscribeHostNetworkStatusChange()`,不得直接读取 Expo / Tauri 私有网络 API。 - 2026-06-19 移动壳本地通知边界:Expo `notification.showLocal` 的 payload 归一、权限确认、iOS 仅 alert 且不请求 badge/sound、Android 固定 channel、即时调度、通知 handler 和成功响应包装统一收口在 `apps/mobile-shell/src/host-bridge/notifications.ts`;`dispatch.ts` 只负责把 HostBridge request 委托给 `showMobileHostBridgeLocalNotification(...)`,不得直接调用 `expo-notifications` 调度或权限 API,不得重新做通知 payload 归一,也不得包装本地通知成功响应。移动壳配置检查会覆盖该模块结构、固定 channel、即时调度形态和 dispatch 委托关系,避免后续混入远程推送、后台推送或散落的本地通知实现。 - 2026-06-18 移动壳 WebView 状态重放:Expo WebView 每次同源主站页面成功加载后都会补发当前 `app.lifecycle` 与 `network.statusChanged` 状态,覆盖首载、受控刷新、H5 刷新和系统回收 WebView 进程后的新 JS 上下文;补发不新增 HostBridge capability,也不向 `about:blank`、外域或错误页注入宿主状态。 +- 2026-06-20 移动壳加载失败边界:Expo 壳 `onError` / `onHttpError` 只对同源 H5 主页面展示原生失败兜底层,外域、`about:blank`、危险协议、favicon 和非当前主页面资源失败不得触发兜底。移动壳配置检查会反查 `loadFailure.test.ts` 的同源、favicon 和当前主页面测试,避免错误页策略漂移。 - 2026-06-18 桌面壳 WebView 状态重放:Tauri 主 WebView 每次页面加载完成后都会回放当前 `app.lifecycle` 并重新安装 `network.statusChanged` 监听脚本,覆盖托盘刷新、`app.reloadWebView` 和 H5 自刷新后的新 JS 上下文;桌面 runtime 同步声明 `host.events` 表示该真实事件通道可用,但仍不开放 Tauri event 插件或额外 command。 - 2026-06-18 桌面壳 H5 返回栈事件:Tauri 壳开始声明 `navigation.canGoBack`,但只通过固定注入脚本追踪当前 H5 文档内的 `pushState` / `replaceState` / `popstate` 路由栈并派发 HostBridge event;不把该能力实现为 request method,不开放 H5 到 Tauri 的 event 写入通道,也不声明跨文档 native back-forward list 真相。 - 2026-06-18 移动壳 H5 返回栈事件:Expo 壳开始用固定 WebView 注入脚本追踪当前 H5 文档内的 `pushState` / `replaceState` / `popstate` 路由栈,并通过内部 `genarrative.mobile.historyState` 消息回传给壳层;壳层把该状态与 `react-native-webview` 原生 `canGoBack` 合成为 HostBridge `navigation.canGoBack` 事件。Android 返回键优先回退 H5 当前文档路由栈,H5 不可回退时才走 WebView 原生 `goBack()`;该内部消息不是 HostBridge request method,不开放通用 H5 -> 原生事件通道,外域 / 危险页面消息仍在进入 HostBridge 前丢弃。