对齐三端桥接层结构
拆分移动壳 HostBridge 协议、文件和分享模块 更新原生壳结构门禁和移动壳配置检查 同步宿主壳架构文档和共享决策记录
This commit is contained in:
@@ -8,6 +8,20 @@ const appPath = new URL('../App.tsx', import.meta.url);
|
||||
const appSource = fs.readFileSync(appPath, 'utf8');
|
||||
const bridgePath = new URL('../src/host-bridge/mobileHostBridge.ts', import.meta.url);
|
||||
const bridgeSource = fs.readFileSync(bridgePath, 'utf8');
|
||||
const bridgeDirPath = new URL('../src/host-bridge/', import.meta.url);
|
||||
const bridgeSourceFiles = fs
|
||||
.readdirSync(bridgeDirPath, { withFileTypes: true })
|
||||
.filter(
|
||||
(entry) =>
|
||||
entry.isFile() &&
|
||||
entry.name.endsWith('.ts') &&
|
||||
!entry.name.includes('.test.'),
|
||||
)
|
||||
.map((entry) => new URL(entry.name, bridgeDirPath))
|
||||
.sort((left, right) => left.pathname.localeCompare(right.pathname));
|
||||
const hostBridgeSource = bridgeSourceFiles
|
||||
.map((file) => fs.readFileSync(file, 'utf8'))
|
||||
.join('\n');
|
||||
const mobileShellUrlPath = new URL('../src/shell/mobileShellUrl.ts', import.meta.url);
|
||||
const mobileShellUrlSource = fs.readFileSync(mobileShellUrlPath, 'utf8');
|
||||
const mobileShellRuntimePath = new URL('../src/shell/mobileShellRuntime.ts', import.meta.url);
|
||||
@@ -193,7 +207,7 @@ function assertNoBlockedMobileChannelSnippets() {
|
||||
const sources = [
|
||||
['app.json', JSON.stringify(appConfig)],
|
||||
['App.tsx', appSource],
|
||||
['mobileHostBridge.ts', bridgeSource],
|
||||
['src/host-bridge', hostBridgeSource],
|
||||
['mobileShellUrl.ts', mobileShellUrlSource],
|
||||
['mobileShellRuntime.ts', mobileShellRuntimeSource],
|
||||
];
|
||||
@@ -441,11 +455,11 @@ const sharedMethods = extractStringArrayExport(
|
||||
);
|
||||
const handledMobileMethods = extractMobileBridgeHandledMethods(bridgeSource);
|
||||
const mobileCapabilities = extractStringArrayExport(
|
||||
bridgeSource,
|
||||
hostBridgeSource,
|
||||
'MOBILE_HOST_CAPABILITIES',
|
||||
);
|
||||
const iosMobileCapabilities = extractStringArrayExport(
|
||||
bridgeSource,
|
||||
hostBridgeSource,
|
||||
'IOS_MOBILE_HOST_CAPABILITIES',
|
||||
);
|
||||
const mobileCapabilitySet = new Set(mobileCapabilities);
|
||||
@@ -830,7 +844,7 @@ for (const forbiddenNotificationSnippet of [
|
||||
'addNotificationResponseReceivedListener',
|
||||
...blockedScheduledNotificationSnippets,
|
||||
]) {
|
||||
if (bridgeSource.includes(forbiddenNotificationSnippet)) {
|
||||
if (hostBridgeSource.includes(forbiddenNotificationSnippet)) {
|
||||
throw new Error(
|
||||
`mobile shell must not register remote or background notification flow: ${forbiddenNotificationSnippet}`,
|
||||
);
|
||||
@@ -883,7 +897,7 @@ for (const snippet of [
|
||||
'resolveMobileHostBridgeResponse',
|
||||
'rememberHostBridgeResponse',
|
||||
]) {
|
||||
if (!bridgeSource.includes(snippet)) {
|
||||
if (!hostBridgeSource.includes(snippet)) {
|
||||
throw new Error(`mobile shell HostBridge missing ${snippet}`);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,137 @@
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
import {
|
||||
HOST_BRIDGE_PROTOCOL,
|
||||
HOST_BRIDGE_VERSION,
|
||||
type HostBridgeCapability,
|
||||
type HostBridgeError,
|
||||
type HostBridgeMethod,
|
||||
type HostBridgeRequest,
|
||||
type HostBridgeResponse,
|
||||
isHostBridgeMethod,
|
||||
normalizeHostBridgeRequestId,
|
||||
} from '../../../../packages/shared/src/contracts/hostBridge';
|
||||
|
||||
export const HOST_BRIDGE_RESPONSE_CACHE_MAX = 128;
|
||||
|
||||
export const MOBILE_HOST_CAPABILITIES: HostBridgeCapability[] = [
|
||||
'host.getRuntime',
|
||||
'appearance.getColorScheme',
|
||||
'host.events',
|
||||
'app.lifecycle',
|
||||
'share.open',
|
||||
'share.setTarget',
|
||||
'navigation.openNativePage',
|
||||
'navigation.canGoBack',
|
||||
'app.reloadWebView',
|
||||
'app.openExternalUrl',
|
||||
'network.status',
|
||||
'network.statusChanged',
|
||||
'clipboard.writeText',
|
||||
'clipboard.readText',
|
||||
'file.exportText',
|
||||
'file.importText',
|
||||
'file.exportImage',
|
||||
'file.importImage',
|
||||
'file.captureImage',
|
||||
'file.importAudio',
|
||||
'file.exportAudio',
|
||||
'haptics.impact',
|
||||
'notification.showLocal',
|
||||
];
|
||||
|
||||
export const IOS_MOBILE_HOST_CAPABILITIES: HostBridgeCapability[] = [
|
||||
...MOBILE_HOST_CAPABILITIES,
|
||||
'app.setBadgeCount',
|
||||
];
|
||||
|
||||
export function resolveMobileHostCapabilities(platform = Platform.OS) {
|
||||
return platform === 'ios'
|
||||
? IOS_MOBILE_HOST_CAPABILITIES
|
||||
: MOBILE_HOST_CAPABILITIES;
|
||||
}
|
||||
|
||||
export type MobileHostBridgeNavigation = {
|
||||
allowedOrigin: string;
|
||||
openWebViewUrl: (url: string) => void;
|
||||
reloadWebView: () => void;
|
||||
};
|
||||
|
||||
export function unsupported(method: HostBridgeMethod): HostBridgeError {
|
||||
return {
|
||||
code: 'unsupported_method',
|
||||
message: `${method} unsupported in mobile shell`,
|
||||
};
|
||||
}
|
||||
|
||||
export function invalidRequest(message: string): HostBridgeError {
|
||||
return {
|
||||
code: 'invalid_request',
|
||||
message,
|
||||
};
|
||||
}
|
||||
|
||||
export function isHostBridgeRequest(value: unknown): value is HostBridgeRequest {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const candidate = value as Partial<HostBridgeRequest>;
|
||||
const requestId = normalizeHostBridgeRequestId(candidate.id);
|
||||
return (
|
||||
candidate.bridge === HOST_BRIDGE_PROTOCOL &&
|
||||
candidate.version === HOST_BRIDGE_VERSION &&
|
||||
requestId !== null &&
|
||||
isHostBridgeMethod(candidate.method)
|
||||
);
|
||||
}
|
||||
|
||||
export function parseRequest(raw: string) {
|
||||
try {
|
||||
return JSON.parse(raw) as unknown;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function ok<Result>(
|
||||
request: HostBridgeRequest,
|
||||
result?: Result,
|
||||
): HostBridgeResponse<Result> {
|
||||
return {
|
||||
bridge: HOST_BRIDGE_PROTOCOL,
|
||||
version: HOST_BRIDGE_VERSION,
|
||||
id: request.id,
|
||||
ok: true,
|
||||
result,
|
||||
};
|
||||
}
|
||||
|
||||
export function failure(
|
||||
request: Pick<HostBridgeRequest, 'id'>,
|
||||
error: HostBridgeError,
|
||||
): HostBridgeResponse {
|
||||
return {
|
||||
bridge: HOST_BRIDGE_PROTOCOL,
|
||||
version: HOST_BRIDGE_VERSION,
|
||||
id: request.id,
|
||||
ok: false,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeMobileHostBridgeError(error: unknown): HostBridgeError {
|
||||
if (
|
||||
error &&
|
||||
typeof error === 'object' &&
|
||||
'code' in error &&
|
||||
'message' in error
|
||||
) {
|
||||
return error as HostBridgeError;
|
||||
}
|
||||
|
||||
return {
|
||||
code: 'host_error',
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Share } from 'react-native';
|
||||
|
||||
import { type ShareOpenPayload } from '../../../../packages/shared/src/contracts/hostBridge';
|
||||
import { invalidRequest } from './mobileHostBridgeProtocol';
|
||||
|
||||
const WEB_APP_ORIGIN = 'https://app.genarrative.world';
|
||||
|
||||
function stringField(value: unknown, field: string) {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const fieldValue = (value as Record<string, unknown>)[field];
|
||||
if (typeof fieldValue !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const text = fieldValue.trim();
|
||||
return text || undefined;
|
||||
}
|
||||
|
||||
function shareTargetPayload(value: unknown) {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return value;
|
||||
}
|
||||
|
||||
const target = value as Record<string, unknown>;
|
||||
return target.target ?? value;
|
||||
}
|
||||
|
||||
function workDetailUrl(work: string) {
|
||||
return `${WEB_APP_ORIGIN}/works/detail?work=${encodeURIComponent(work)}`;
|
||||
}
|
||||
|
||||
function webAppPathUrl(path: string) {
|
||||
return new URL(path, WEB_APP_ORIGIN).toString();
|
||||
}
|
||||
|
||||
function normalizeSharePayload(value: unknown): ShareOpenPayload | null {
|
||||
const target = shareTargetPayload(value);
|
||||
const payload =
|
||||
target && typeof target === 'object'
|
||||
? (target as Record<string, unknown>).payload ?? target
|
||||
: target;
|
||||
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const title = stringField(payload, 'title');
|
||||
const message = stringField(payload, 'message');
|
||||
const directUrl = stringField(payload, 'url') ?? stringField(payload, 'href');
|
||||
const work = stringField(payload, 'work');
|
||||
const path = stringField(payload, 'path') ?? stringField(payload, 'targetPath');
|
||||
const url =
|
||||
directUrl ??
|
||||
(work ? workDetailUrl(work) : undefined) ??
|
||||
(path ? webAppPathUrl(path) : undefined);
|
||||
|
||||
if (!title && !message && !url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...(title ? { title } : {}),
|
||||
...(message ? { message } : {}),
|
||||
...(url ? { url } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export async function openShare(payload: unknown, currentShareTarget: unknown) {
|
||||
const sharePayload =
|
||||
normalizeSharePayload(payload) ?? normalizeSharePayload(currentShareTarget);
|
||||
if (!sharePayload) {
|
||||
throw invalidRequest('share target is required');
|
||||
}
|
||||
|
||||
const url = sharePayload?.url;
|
||||
const message = [sharePayload?.message, url].filter(Boolean).join('\n');
|
||||
|
||||
await Share.share({
|
||||
title: sharePayload?.title,
|
||||
message: message || url || sharePayload?.title || '',
|
||||
url,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -86,7 +86,7 @@
|
||||
- 2026-06-18 移动壳 WebView 安全开关:Expo 移动壳 WebView 必须显式禁用 JS 自动开窗、多窗口、文件访问、file URL 跨源访问、HTTPS 混合内容、第三方 Cookie、共享 Cookie 和 WebView 远程调试;同源主站页面才能留在带 HostBridge 的 WebView 内,外链只通过受控协议离开容器交给系统。配置检查和移动壳导航测试会拒绝这些边界被放宽。
|
||||
- 2026-06-18 移动壳 WebView 默认下载边界:Expo WebView 内网页自动下载和 `<a download>` 直接落盘默认关闭;壳层注入脚本阻断 download 链接,iOS `onFileDownload` 只丢弃不落盘,Android 包配置通过 `blockedPermissions` 移除外部存储读写、管理外部存储和请求安装包权限。移动端文本、图片、音频保存只能通过 `file.exportText`、`file.exportImage`、`file.exportAudio` 等 HostBridge 受控导出能力进入系统分享 / 保存面板。
|
||||
- 2026-06-18 移动壳 HostBridge 消息来源校验:Expo 移动壳 `onMessage` 必须根据 `event.nativeEvent.url` 校验消息来源,只有同源主站页面能进入 `handleMobileHostBridgeMessage`;`about:blank`、外域、协议降级和危险协议页面消息直接丢弃,不返回宿主能力错误细节。该规则与 WebView 导航留壳规则共用同源判断,配置检查和移动壳导航测试会拒绝移除。
|
||||
- 2026-06-18 三端桥接层目录同构:微信小程序、Expo 移动壳和 Tauri 桌面壳都按 `host-bridge / shell` 两层管理宿主桥接代码。微信 `miniprogram/host-bridge/wechatHostBridge*.js` 只放协议归一、支付 / 订阅 / 分享结果编解码和可测试桥接函数,`miniprogram/shell/wechatShell*.js` 承接 Page 生命周期、`wx.*` 容器调用、WebView 容器行为和页面工厂;页面目录只保留 `Page(createWechat...Page())` 装配。Tauri `shell/deep_link.rs`、`shell/tray.rs`、`shell/webview.rs` 分别承接深链、托盘和 WebView 容器行为。`npm run check:native-shells` 会校验微信、移动和桌面三端目录清单,新增宿主能力必须按同一边界落文件和测试。
|
||||
- 2026-06-18 三端桥接层目录同构:微信小程序、Expo 移动壳和 Tauri 桌面壳都按 `host-bridge / shell` 两层管理宿主桥接代码。微信 `miniprogram/host-bridge/wechatHostBridge*.js` 只放协议归一、支付 / 订阅 / 分享结果编解码和可测试桥接函数,`miniprogram/shell/wechatShell*.js` 承接 Page 生命周期、`wx.*` 容器调用、WebView 容器行为和页面工厂;页面目录只保留 `Page(createWechat...Page())` 装配。Expo `mobileHostBridgeProtocol.ts`、`mobileHostBridgeFiles.ts`、`mobileHostBridgeShare.ts` 和 facade `mobileHostBridge.ts` 分别对齐 Tauri `host_bridge/protocol.rs`、`files.rs`、`share.rs`、`mod.rs`。Tauri `shell/deep_link.rs`、`shell/tray.rs`、`shell/webview.rs` 分别承接深链、托盘和 WebView 容器行为。`npm run check:native-shells` 会校验微信、移动和桌面三端目录清单,新增宿主能力必须按同一边界落文件和测试。
|
||||
- 影响范围:`src/services/host-bridge/`、未来 `apps/mobile-shell/`、未来 `apps/desktop-shell/`、移动端支付 / 分享 / 深链 / 推送、桌面端系统能力、AI H5 sandbox 的 GameBridge 边界。
|
||||
- 验证方式:普通浏览器、小程序、Expo 壳、Tauri 壳都能返回正确 `getHostRuntime()`;未支持能力能回退 H5;固定玩法在各宿主中读取同一作品数据和运行态 snapshot;AI sandbox 无法直接调用 HostBridge;Tauri release 不允许任意远端页面调用桌面命令。
|
||||
- 关联文档:`docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md`、`docs/【前端架构】宿主壳能力统一协议-2026-06-17.md`。
|
||||
@@ -2475,6 +2475,6 @@
|
||||
## 2026-06-18 三端宿主桥接层文件结构对齐
|
||||
|
||||
- 背景:微信小程序壳、Expo 移动壳和 Tauri 桌面壳都在承接宿主能力;如果微信页面继续散落 `index.shared.js`,桌面端继续把桥接分发堆在 `main.rs`,后续新增登录、支付、文件、通知或 sandbox 转发能力时会很难跨端对照 owner。
|
||||
- 决策:三端桥接层按职责对齐。微信小程序页面路由不改,但可测试桥接逻辑统一放到 `miniprogram/host-bridge/wechatHostBridge*.js`,页面目录只保留生命周期和装配;Expo 移动壳拆成 `apps/mobile-shell/src/host-bridge/mobileHostBridge.ts` 负责协议分发,`apps/mobile-shell/src/shell/mobileShell*.ts` 负责 URL、导航、网络、生命周期、安全区和 WebView policy;Tauri 桌面壳拆成 `apps/desktop-shell/src-tauri/src/host_bridge/*.rs` 与 `apps/desktop-shell/src-tauri/src/shell/*.rs`,其中 deep link、tray、webview 分文件承接容器行为,`main.rs` 只做 builder、plugin、窗口和状态装配。`scripts/check-native-shells.mjs` 锁定三端桥接层目录清单。
|
||||
- 决策:三端桥接层按职责对齐。微信小程序页面路由不改,但可测试桥接逻辑统一放到 `miniprogram/host-bridge/wechatHostBridge*.js`,页面目录只保留生命周期和装配;Expo 移动壳拆成 `apps/mobile-shell/src/host-bridge/mobileHostBridgeProtocol.ts`、`mobileHostBridgeFiles.ts`、`mobileHostBridgeShare.ts` 和 facade `mobileHostBridge.ts`,分别负责协议 / 能力清单 / request 校验 / replay 基础、文件能力、分享能力和 method 分发,`apps/mobile-shell/src/shell/mobileShell*.ts` 负责 URL、导航、网络、生命周期、安全区和 WebView policy;Tauri 桌面壳拆成 `apps/desktop-shell/src-tauri/src/host_bridge/*.rs` 与 `apps/desktop-shell/src-tauri/src/shell/*.rs`,其中 deep link、tray、webview 分文件承接容器行为,`main.rs` 只做 builder、plugin、窗口和状态装配。`scripts/check-native-shells.mjs` 锁定三端桥接层目录清单。
|
||||
- 影响范围:`miniprogram/host-bridge/`、`miniprogram/pages/*/index.js`、`apps/mobile-shell/src/`、`apps/desktop-shell/src-tauri/src/`、`scripts/check-native-shells.mjs`、宿主壳方案文档。
|
||||
- 验证方式:`npm run test -- miniprogram/host-bridge/wechatHostBridgeWebView.test.js miniprogram/host-bridge/wechatHostBridgePayment.test.js miniprogram/host-bridge/wechatHostBridgeShareGrid.test.js miniprogram/host-bridge/wechatHostBridgeSubscribeMessage.test.js miniprogram/pages/web-view/index.style.test.js`、`npm run check:native-shells`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
@@ -64,7 +64,7 @@ src/
|
||||
|
||||
已落地:`packages/shared/src/contracts/hostBridge.ts` 保存消息 envelope、method、payload 和错误码,H5、Expo 壳与 Tauri 壳共享同一份协议类型。
|
||||
|
||||
三端宿主桥接层按职责对齐命名:微信小程序页面路由仍保留在 `miniprogram/pages/*`,协议归一、支付 / 订阅 / 分享结果编解码统一放在 `miniprogram/host-bridge/wechatHostBridge*.js`,Page 生命周期、`wx.*` 容器调用、WebView 容器行为和页面工厂统一放在 `miniprogram/shell/wechatShell*.js`,页面入口只做 `Page(createWechat...Page())` 装配;Expo 移动壳使用 `apps/mobile-shell/src/host-bridge/mobileHostBridge.ts` 承接协议分发,`apps/mobile-shell/src/shell/mobileShell*.ts` 承接 URL、导航、网络、生命周期、安全区和 WebView policy;Tauri 桌面壳使用 `apps/desktop-shell/src-tauri/src/host_bridge/*.rs` 承接协议、分发、文件和分享,`apps/desktop-shell/src-tauri/src/shell/*.rs` 承接 WebView、托盘和容器行为,`main.rs` 只保留 Tauri builder / plugin / window 装配。
|
||||
三端宿主桥接层按职责对齐命名:微信小程序页面路由仍保留在 `miniprogram/pages/*`,协议归一、支付 / 订阅 / 分享结果编解码统一放在 `miniprogram/host-bridge/wechatHostBridge*.js`,Page 生命周期、`wx.*` 容器调用、WebView 容器行为和页面工厂统一放在 `miniprogram/shell/wechatShell*.js`,页面入口只做 `Page(createWechat...Page())` 装配;Expo 移动壳使用 `apps/mobile-shell/src/host-bridge/mobileHostBridgeProtocol.ts` 承接 envelope、能力清单、request 校验和 replay 基础,`mobileHostBridgeFiles.ts` / `mobileHostBridgeShare.ts` 分别承接文件和分享能力,`mobileHostBridge.ts` 只作为对外 facade 与 method 分发入口,`apps/mobile-shell/src/shell/mobileShell*.ts` 承接 URL、导航、网络、生命周期、安全区和 WebView policy;Tauri 桌面壳使用 `apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs`、`files.rs`、`share.rs` 和 `mod.rs` 承接协议、文件、分享和分发,`apps/desktop-shell/src-tauri/src/shell/*.rs` 承接 WebView、托盘和容器行为,`main.rs` 只保留 Tauri builder / plugin / window 装配。
|
||||
|
||||
## HostBridge 消息协议
|
||||
|
||||
@@ -415,7 +415,7 @@ GameBridge 禁止:
|
||||
|
||||
2026-06-18 追加:移动壳 HostBridge 消息入口增加来源校验。`onMessage` 不只依赖导航拦截和 `originWhitelist`,还会读取 `event.nativeEvent.url`,只有同源主站页面才能进入 `handleMobileHostBridgeMessage`;`about:blank`、外域 URL、协议降级或危险协议页面发来的消息全部丢弃,不返回 HostBridge 错误细节。该校验与 `navigation.openNativePage` 共用同源规则,防止历史中间页或异常页面在带完整 HostBridge 的 WebView 中发起宿主能力请求。
|
||||
|
||||
2026-06-18 追加:微信、移动端和桌面端桥接层文件结构按职责对齐。微信小程序的 `web-view`、支付、九宫切图和订阅消息桥接逻辑统一迁入 `miniprogram/host-bridge/wechatHostBridge*.js`,页面目录只保留页面生命周期、WXML/WXSS 和装配;移动壳拆成 `apps/mobile-shell/src/host-bridge/mobileHostBridge.ts` 和 `apps/mobile-shell/src/shell/mobileShell*.ts`;桌面壳 Rust 源码拆成 `apps/desktop-shell/src-tauri/src/host_bridge/*.rs` 与 `apps/desktop-shell/src-tauri/src/shell/*.rs`,其中 `shell/deep_link.rs`、`shell/tray.rs`、`shell/webview.rs` 分别承接深链、托盘和 WebView 容器行为,薄 `main.rs` 只声明两个模块并装配 Tauri builder / plugin / window。根级 `npm run check:native-shells` 会锁定三端桥接层目录清单,避免后续把能力逻辑重新散落到页面或桌面入口。
|
||||
2026-06-18 追加:微信、移动端和桌面端桥接层文件结构按职责对齐。微信小程序的 `web-view`、支付、九宫切图和订阅消息桥接逻辑统一迁入 `miniprogram/host-bridge/wechatHostBridge*.js`,页面目录只保留页面生命周期、WXML/WXSS 和装配;移动壳拆成 `apps/mobile-shell/src/host-bridge/mobileHostBridgeProtocol.ts`、`mobileHostBridgeFiles.ts`、`mobileHostBridgeShare.ts` 和 facade `mobileHostBridge.ts`,与桌面端 `host_bridge/protocol.rs`、`files.rs`、`share.rs`、`mod.rs` 对齐;桌面壳 Rust 源码拆成 `apps/desktop-shell/src-tauri/src/host_bridge/*.rs` 与 `apps/desktop-shell/src-tauri/src/shell/*.rs`,其中 `shell/deep_link.rs`、`shell/tray.rs`、`shell/webview.rs` 分别承接深链、托盘和 WebView 容器行为,薄 `main.rs` 只声明两个模块并装配 Tauri builder / plugin / window。根级 `npm run check:native-shells` 会锁定三端桥接层目录清单,避免后续把能力逻辑重新散落到页面或桌面入口。
|
||||
|
||||
### Phase 4:宿主能力扩展
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ AI H5 sandbox
|
||||
-> parent HostBridge adapter
|
||||
```
|
||||
|
||||
桥接层文件结构按宿主统一为“协议 / 分发 / 宿主容器行为”三类职责。微信小程序的协议与可测试桥接函数统一放在 `miniprogram/host-bridge/wechatHostBridge*.js`,`miniprogram/shell/wechatShell*.js` 承接 Page 生命周期、`wx.*` 容器调用、WebView 容器行为、支付页和订阅页装配,页面目录只保留 `Page(createWechat...Page())` 装配;Expo 移动壳使用 `apps/mobile-shell/src/host-bridge/mobileHostBridge.ts` 承接协议分发,`apps/mobile-shell/src/shell/mobileShell*.ts` 承接 URL、导航、网络、生命周期、安全区和 WebView policy;Tauri 桌面壳使用 `apps/desktop-shell/src-tauri/src/host_bridge/*.rs` 承接协议、分发、文件和分享,`apps/desktop-shell/src-tauri/src/shell/*.rs` 承接 WebView、托盘和容器行为,`main.rs` 只保留 Tauri builder / plugin / window 装配。`npm run check:native-shells` 会检查这些目录清单。
|
||||
桥接层文件结构按宿主统一为“协议 / 分发 / 宿主容器行为”三类职责。微信小程序的协议与可测试桥接函数统一放在 `miniprogram/host-bridge/wechatHostBridge*.js`,`miniprogram/shell/wechatShell*.js` 承接 Page 生命周期、`wx.*` 容器调用、WebView 容器行为、支付页和订阅页装配,页面目录只保留 `Page(createWechat...Page())` 装配;Expo 移动壳使用 `apps/mobile-shell/src/host-bridge/mobileHostBridgeProtocol.ts` 承接 envelope、能力清单、request 校验和 replay 基础,`mobileHostBridgeFiles.ts` / `mobileHostBridgeShare.ts` 分别承接文件和分享能力,`mobileHostBridge.ts` 只作为对外 facade 与 method 分发入口,`apps/mobile-shell/src/shell/mobileShell*.ts` 承接 URL、导航、网络、生命周期、安全区和 WebView policy;Tauri 桌面壳使用 `apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs`、`files.rs`、`share.rs` 和 `mod.rs` 承接协议、文件、分享和分发,`apps/desktop-shell/src-tauri/src/shell/*.rs` 承接 WebView、托盘和容器行为,`main.rs` 只保留 Tauri builder / plugin / window 装配。`npm run check:native-shells` 会检查这些目录清单。
|
||||
|
||||
## 首批能力
|
||||
|
||||
|
||||
@@ -30,6 +30,9 @@ const expectedWechatShellFiles = [
|
||||
const expectedMobileHostBridgeFiles = [
|
||||
'mobileHostBridge.test.ts',
|
||||
'mobileHostBridge.ts',
|
||||
'mobileHostBridgeFiles.ts',
|
||||
'mobileHostBridgeProtocol.ts',
|
||||
'mobileHostBridgeShare.ts',
|
||||
];
|
||||
const expectedMobileShellFiles = [
|
||||
'mobileShellDeepLink.test.ts',
|
||||
|
||||
Reference in New Issue
Block a user