收口移动壳通知桥接边界

将 Expo 本地通知 payload 归一收口到 notifications 模块

让移动壳 dispatch 只委托通知 HostBridge 请求

同步移动壳结构门禁、架构文档和共享记忆
This commit is contained in:
2026-06-19 22:24:41 +08:00
parent 1d6fda4696
commit b1c42885ba
6 changed files with 31 additions and 16 deletions
+12 -1
View File
@@ -1529,7 +1529,8 @@ if (
}
if (
!dispatchSource.includes('showMobileLocalNotification(notification)') ||
!dispatchSource.includes('showMobileHostBridgeLocalNotification(request.payload)') ||
dispatchSource.includes('normalizeHostBridgeLocalNotification') ||
dispatchSource.includes("from 'expo-notifications'") ||
dispatchSource.includes('Notifications.scheduleNotificationAsync') ||
dispatchSource.includes('Notifications.requestPermissionsAsync') ||
@@ -1539,6 +1540,16 @@ if (
'mobile shell dispatch must delegate local notification delivery to notifications.ts',
);
}
for (const notificationSnippet of [
'showMobileHostBridgeLocalNotification',
'normalizeHostBridgeLocalNotification(payload)',
"invalidRequest('title is required')",
'showMobileLocalNotification(notification)',
]) {
if (!notificationsSource.includes(notificationSnippet)) {
throw new Error(`mobile shell notifications module is missing ${notificationSnippet}`);
}
}
for (const snippet of [
'file.exportText',
+5 -12
View File
@@ -5,7 +5,6 @@ import {
type HapticsImpactPayload,
HOST_BRIDGE_VERSION,
type HostBridgeRequest,
normalizeHostBridgeLocalNotification,
} from '../../../../packages/shared/src/contracts/hostBridge';
import { MOBILE_SHELL_HOST_VERSION } from '../shell/runtime';
import { resolveMobileHostCapabilities } from './capabilities';
@@ -28,7 +27,7 @@ import {
} from './protocol';
import { resetQrScannerForTest, scanQrCode } from './scanner';
import { openShare } from './share';
import { showMobileLocalNotification } from './notifications';
import { showMobileHostBridgeLocalNotification } from './notifications';
import {
readMobileHostBridgeClipboardText,
writeMobileHostBridgeClipboardText,
@@ -64,15 +63,6 @@ async function runHaptics(payload: unknown) {
return true;
}
async function showLocalNotification(payload: unknown) {
const notification = normalizeHostBridgeLocalNotification(payload);
if (!notification) {
throw invalidRequest('title is required');
}
return showMobileLocalNotification(notification);
}
function getMobileRuntimePlatform() {
return Platform.OS === 'ios' ? 'ios' : 'android';
}
@@ -130,7 +120,10 @@ export async function dispatchMobileHostBridgeRequest(
case 'haptics.impact':
return ok(request, await runHaptics(request.payload));
case 'notification.showLocal':
return ok(request, await showLocalNotification(request.payload));
return ok(
request,
await showMobileHostBridgeLocalNotification(request.payload),
);
case 'app.setBadgeCount':
return ok(request, setMobileAppBadgeCount(request.payload));
case 'share.open':
@@ -5,7 +5,9 @@ import {
HOST_BRIDGE_MOBILE_LOCAL_NOTIFICATION_CHANNEL_ID,
type HostBridgeError,
type LocalNotificationPayload,
normalizeHostBridgeLocalNotification,
} from '../../../../packages/shared/src/contracts/hostBridge';
import { invalidRequest } from './protocol';
Notifications.setNotificationHandler({
handleNotification: async () => ({
@@ -69,3 +71,12 @@ export async function showMobileLocalNotification(
});
return true;
}
export async function showMobileHostBridgeLocalNotification(payload: unknown) {
const notification = normalizeHostBridgeLocalNotification(payload);
if (!notification) {
throw invalidRequest('title is required');
}
return showMobileLocalNotification(notification);
}
@@ -50,7 +50,7 @@
- 2026-06-19 原生壳外观查询边界:Expo `appearance.getColorScheme` 的系统配色读取和 HostBridge 配色归一统一收口在 `apps/mobile-shell/src/host-bridge/appearance.ts`Tauri `appearance.getColorScheme` 的主窗口 `theme()` 读取和 `light / dark / unknown` 映射统一收口在 `apps/desktop-shell/src-tauri/src/host_bridge/appearance.rs`。两端 `dispatch` 只负责委托对应 appearance 模块和映射响应,配置检查会拒绝分发层直接读取系统配色或窗口主题。
- 2026-06-18 原生壳生命周期事件:新增 `app.lifecycle` HostBridge capabilityExpo 壳通过 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 capabilityExpo 壳通过 `expo-network` 查询和订阅真实系统网络状态,Tauri 壳从 `WEB_APP_ORIGIN` 解析主站 host / port 后做短超时 TCP 可达性查询,并通过 WebView `online` / `offline` 注入变化事件;H5 统一使用 `getHostNetworkStatus()` / `subscribeHostNetworkStatusChange()`,不得直接读取 Expo / Tauri 私有网络 API。
- 2026-06-19 移动壳本地通知边界:Expo `notification.showLocal` 的权限确认、iOS 仅 alert 且不请求 badge/sound、Android 固定 channel、即时调度和通知 handler 统一收口在 `apps/mobile-shell/src/host-bridge/notifications.ts``dispatch.ts` 只负责 HostBridge payload 归一和调用 `showMobileLocalNotification(notification)`,不得直接调用 `expo-notifications` 调度或权限 API。移动壳配置检查会覆盖该模块结构、固定 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 和即时调度形态,避免后续混入远程推送、后台推送或散落的本地通知实现。
- 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 真相。
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long