From c42afe86a69f0296758acbb89d712bc157f55553 Mon Sep 17 00:00:00 2001 From: kdletters Date: Sat, 20 Jun 2026 01:11:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E5=8F=A3=E7=A7=BB=E5=8A=A8=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E5=93=8D=E5=BA=94=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 Expo 本地通知成功响应包装收口到 notifications 模块 让移动 dispatch 只委托本地通知请求 同步移动壳配置门禁和共享记忆 --- apps/mobile-shell/scripts/check-config.mjs | 8 ++++++-- apps/mobile-shell/src/host-bridge/dispatch.ts | 5 +---- apps/mobile-shell/src/host-bridge/notifications.ts | 12 ++++++++---- docs/project-memory/shared-memory/decision-log.md | 2 +- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/apps/mobile-shell/scripts/check-config.mjs b/apps/mobile-shell/scripts/check-config.mjs index 2a4eddfa8..ca46f81e5 100644 --- a/apps/mobile-shell/scripts/check-config.mjs +++ b/apps/mobile-shell/scripts/check-config.mjs @@ -1543,7 +1543,8 @@ if ( } if ( - !dispatchSource.includes('showMobileHostBridgeLocalNotification(request.payload)') || + !dispatchSource.includes('showMobileHostBridgeLocalNotification(request)') || + dispatchSource.includes('ok(request, await showMobileHostBridgeLocalNotification') || dispatchSource.includes('normalizeHostBridgeLocalNotification') || dispatchSource.includes("from 'expo-notifications'") || dispatchSource.includes('Notifications.scheduleNotificationAsync') || @@ -1556,9 +1557,12 @@ if ( } for (const notificationSnippet of [ 'showMobileHostBridgeLocalNotification', - 'normalizeHostBridgeLocalNotification(payload)', + 'type HostBridgeRequest', + 'request: HostBridgeRequest', + 'normalizeHostBridgeLocalNotification(request.payload)', "invalidRequest('title is required')", 'showMobileLocalNotification(notification)', + 'ok(request, true)', ]) { if (!notificationsSource.includes(notificationSnippet)) { throw new Error(`mobile shell notifications module is missing ${notificationSnippet}`); diff --git a/apps/mobile-shell/src/host-bridge/dispatch.ts b/apps/mobile-shell/src/host-bridge/dispatch.ts index 11944c169..cbfade987 100644 --- a/apps/mobile-shell/src/host-bridge/dispatch.ts +++ b/apps/mobile-shell/src/host-bridge/dispatch.ts @@ -84,10 +84,7 @@ export async function dispatchMobileHostBridgeRequest( case 'haptics.impact': return runMobileHostBridgeHapticsImpact(request); case 'notification.showLocal': - return ok( - request, - await showMobileHostBridgeLocalNotification(request.payload), - ); + return showMobileHostBridgeLocalNotification(request); case 'app.setBadgeCount': return setMobileAppBadgeCount(request); case 'share.open': diff --git a/apps/mobile-shell/src/host-bridge/notifications.ts b/apps/mobile-shell/src/host-bridge/notifications.ts index 4bb1bcb3e..83ad682f9 100644 --- a/apps/mobile-shell/src/host-bridge/notifications.ts +++ b/apps/mobile-shell/src/host-bridge/notifications.ts @@ -4,10 +4,11 @@ import { Platform } from 'react-native'; import { HOST_BRIDGE_MOBILE_LOCAL_NOTIFICATION_CHANNEL_ID, type HostBridgeError, + type HostBridgeRequest, type LocalNotificationPayload, normalizeHostBridgeLocalNotification, } from '../../../../packages/shared/src/contracts/hostBridge'; -import { invalidRequest } from './protocol'; +import { invalidRequest, ok } from './protocol'; Notifications.setNotificationHandler({ handleNotification: async () => ({ @@ -72,11 +73,14 @@ export async function showMobileLocalNotification( return true; } -export async function showMobileHostBridgeLocalNotification(payload: unknown) { - const notification = normalizeHostBridgeLocalNotification(payload); +export async function showMobileHostBridgeLocalNotification( + request: HostBridgeRequest, +) { + const notification = normalizeHostBridgeLocalNotification(request.payload); if (!notification) { throw invalidRequest('title is required'); } - return showMobileLocalNotification(notification); + await showMobileLocalNotification(notification); + return ok(request, true); } diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 7ed039f47..11b8c2854 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -53,7 +53,7 @@ - 2026-06-19 原生壳外观查询边界:Expo `appearance.getColorScheme` 的系统配色读取、HostBridge 配色归一和成功响应包装统一收口在 `apps/mobile-shell/src/host-bridge/appearance.ts`;Tauri `appearance.getColorScheme` 的主窗口 `theme()` 读取、`light / dark / unknown` 映射和 HostBridge 响应包装统一收口在 `apps/desktop-shell/src-tauri/src/host_bridge/appearance.rs`。两端 `dispatch` 只负责委托对应 appearance 模块,配置检查会拒绝分发层直接读取系统配色、窗口主题或包装外观查询成功响应。 - 2026-06-18 原生壳生命周期事件:新增 `app.lifecycle` HostBridge capability,Expo 壳通过 React Native `AppState` 派发 `active` / `inactive` / `background`,Tauri 壳通过主窗口 focus / blur、托盘隐藏 / 恢复和页面加载重放派发统一状态;桌面隐藏到托盘或最小化都归一为 `background`,`hidden`、`minimized`、`focused`、`blurred` 只进入 `nativeState` 便于排障,不扩展共享 `state`。两端都声明 `host.events` 表示事件通过 HostBridge message 注入,但不把它作为 request method,也不开放 Tauri event 插件或 React Native 私有事件 API。H5 只通过 `subscribeHostAppLifecycle()` 订阅统一状态,后续游戏循环、音频和轮询暂停 / 恢复不得直接依赖 Expo / Tauri 平台细节。 - 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 和即时调度形态,避免后续混入远程推送、后台推送或散落的本地通知实现。 +- 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-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 真相。