收口原生壳能力来源
新增共享 HostBridge capability profile,区分 Expo 移动通用、iOS 额外和 Tauri 桌面能力 移动壳能力清单改为引用共享 profile,入口 URL 类型支持 readonly 能力数组 两端配置检查和原生壳总门禁改为反查共享 profile,桌面 Rust 清单保持运行时镜像 补充 HostBridge profile 单测和 H5 readonly 能力合并类型兼容 同步 Expo/Tauri 壳方案、HostBridge 协议文档和项目决策记录
This commit is contained in:
@@ -929,6 +929,10 @@ const sharedMethods = extractStringArrayExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_METHODS',
|
||||
);
|
||||
const sharedDesktopCapabilities = extractStringArrayExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES',
|
||||
);
|
||||
const sharedHostBridgeProtocol = extractTsStringConst(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_PROTOCOL',
|
||||
@@ -1154,6 +1158,12 @@ if (unknownDesktopCapabilities.length > 0) {
|
||||
);
|
||||
}
|
||||
|
||||
assertSameList(
|
||||
desktopCapabilities,
|
||||
sharedDesktopCapabilities,
|
||||
'desktop shell HostBridge capability profile',
|
||||
);
|
||||
|
||||
for (const capability of sdkBackedCapabilities) {
|
||||
if (desktopCapabilities.includes(capability)) {
|
||||
throw new Error(
|
||||
|
||||
@@ -481,6 +481,14 @@ const sharedMethods = extractStringArrayExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_METHODS',
|
||||
);
|
||||
const sharedMobileBaseCapabilities = extractStringArrayExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES',
|
||||
);
|
||||
const sharedMobileIosCapabilities = extractStringArrayExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES',
|
||||
);
|
||||
const sharedHostBridgeProtocol = extractStringConstExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_PROTOCOL',
|
||||
@@ -498,14 +506,8 @@ const sharedPublicWebUrl = extractStringConstExport(
|
||||
'HOST_BRIDGE_PUBLIC_WEB_URL',
|
||||
);
|
||||
const handledMobileMethods = extractMobileBridgeHandledMethods(dispatchSource);
|
||||
const mobileCapabilities = extractStringArrayExport(
|
||||
hostBridgeSource,
|
||||
'MOBILE_HOST_CAPABILITIES',
|
||||
);
|
||||
const iosMobileCapabilities = extractStringArrayExport(
|
||||
hostBridgeSource,
|
||||
'IOS_MOBILE_HOST_CAPABILITIES',
|
||||
);
|
||||
const mobileCapabilities = sharedMobileBaseCapabilities;
|
||||
const iosMobileCapabilities = sharedMobileIosCapabilities;
|
||||
const mobileCapabilitySet = new Set(mobileCapabilities);
|
||||
const iosMobileCapabilitySet = new Set(iosMobileCapabilities);
|
||||
const sdkBackedCapabilities = ['auth.requestLogin', 'payment.request'];
|
||||
@@ -553,6 +555,22 @@ for (const localBoundary of forbiddenLocalPayloadBoundaryDeclarations) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const profileSource of [
|
||||
'HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES',
|
||||
'HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES',
|
||||
]) {
|
||||
if (!hostBridgeSource.includes(profileSource)) {
|
||||
throw new Error(`mobile shell must use shared HostBridge profile ${profileSource}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
/export const MOBILE_HOST_CAPABILITIES[^=]*= \[/.test(hostBridgeSource) ||
|
||||
/export const IOS_MOBILE_HOST_CAPABILITIES[^=]*= \[/.test(hostBridgeSource)
|
||||
) {
|
||||
throw new Error('mobile shell must not redeclare HostBridge capability profiles');
|
||||
}
|
||||
|
||||
const unknownHandledMobileMethods = handledMobileMethods.filter(
|
||||
(method) => !sharedMethods.includes(method),
|
||||
);
|
||||
|
||||
@@ -1,37 +1,15 @@
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
import type { HostBridgeCapability } from '../../../../packages/shared/src/contracts/hostBridge';
|
||||
import {
|
||||
HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES,
|
||||
HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES,
|
||||
} from '../../../../packages/shared/src/contracts/hostBridge';
|
||||
|
||||
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 MOBILE_HOST_CAPABILITIES =
|
||||
HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES;
|
||||
|
||||
export const IOS_MOBILE_HOST_CAPABILITIES: HostBridgeCapability[] = [
|
||||
...MOBILE_HOST_CAPABILITIES,
|
||||
'app.setBadgeCount',
|
||||
];
|
||||
export const IOS_MOBILE_HOST_CAPABILITIES =
|
||||
HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES;
|
||||
|
||||
export function resolveMobileHostCapabilities(platform = Platform.OS) {
|
||||
return platform === 'ios'
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
export type MobileShellUrlOptions = {
|
||||
platform: Extract<NativeHostPlatform, 'ios' | 'android'>;
|
||||
hostVersion: string;
|
||||
capabilities: HostBridgeCapability[];
|
||||
capabilities: readonly HostBridgeCapability[];
|
||||
};
|
||||
|
||||
export const DEFAULT_MOBILE_SHELL_WEB_URL = HOST_BRIDGE_PUBLIC_WEB_URL;
|
||||
|
||||
@@ -2438,6 +2438,13 @@
|
||||
- 影响范围:`apps/mobile-shell/scripts/check-config.mjs`、`apps/desktop-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档。
|
||||
- 验证方式:`npm run check:native-shells`、`npm run mobile-shell:typecheck`、`npm run desktop-shell:typecheck`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## 2026-06-19 原生壳 capability profile 来源收口
|
||||
|
||||
- 背景:Expo 移动壳、Tauri 桌面壳和方案文档都需要维护真实 capability 子集;如果移动端源码、桌面 Rust 镜像和文档各自手写完整清单,后续新增能力时容易出现入口 URL、`host.getRuntime` 回包、文档和门禁漂移。
|
||||
- 决策:`packages/shared/src/contracts/hostBridge.ts` 中的 `HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES`、`HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES` 和 `HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES` 是原生壳 capability profile 来源。Expo 移动壳只通过 `apps/mobile-shell/src/host-bridge/capabilities.ts` 引用共享 profile 并选择平台差异;Tauri 桌面壳 `capabilities.rs` 仍保留 Rust 运行时镜像,但 `apps/desktop-shell/scripts/check-config.mjs` 和 `npm run check:native-shells` 必须反查共享桌面 profile。新增 native capability 必须先进入共享白名单和对应平台 profile,再补真实壳实现、H5 fallback、测试和文档。
|
||||
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`apps/mobile-shell/src/host-bridge/capabilities.ts`、`apps/mobile-shell/scripts/check-config.mjs`、`apps/desktop-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、Expo / Tauri HostBridge 方案文档。
|
||||
- 验证方式:`npm run check:native-shells`、`npm run mobile-shell:typecheck`、`npm run desktop-shell:typecheck`、`npm run test -- packages/shared/src/contracts/hostBridge.test.ts`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## 2026-06-18 原生壳本地生成物边界
|
||||
|
||||
- 背景:`npm run check:native-shells` 会生成 Expo `.expo/` 日志、Expo export smoke 临时目录、Tauri schema、Tauri 自动生成权限和 Rust `target/` 产物。这些文件是本机工具输出,不是生产源码;如果进入生产壳敏感词扫描或被误提交,会让门禁受工具版本、构建日志或自动生成格式影响。
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
# 宿主壳能力统一协议
|
||||
|
||||
更新时间:`2026-06-18`
|
||||
更新时间:`2026-06-19`
|
||||
|
||||
## 背景
|
||||
|
||||
@@ -37,7 +37,7 @@ AI H5 sandbox
|
||||
-> parent HostBridge adapter
|
||||
```
|
||||
|
||||
桥接层文件结构按宿主统一为“协议 / 能力清单 / 分发 / 宿主容器行为”四类职责。微信小程序不硬套 Expo / Tauri 的 request 总线:`miniprogram/host-bridge/protocol.js` 只沉淀微信壳能力、页面 URL、结果 hash / storage key 和分享消息类型等常量,`dispatch.js` 只作为 `protocol`、`webView`、`payment`、`shareGrid`、`subscribeMessage` 的薄索引,真实协议归一、支付 / 订阅 / 分享结果编解码仍分别放在 `webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js`;`miniprogram/shell/webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js` 承接 Page 生命周期、`wx.*` 容器调用、WebView 容器行为、支付页和订阅页装配,页面目录只保留 `Page(createWechat...Page())` 装配。Expo 移动壳使用 `apps/mobile-shell/src/host-bridge/protocol.ts` 承接 envelope、request 校验、ok / failure 响应和 replay 基础类型,`capabilities.ts` 承接能力清单与 iOS 差异能力,`dispatch.ts` 承接 method 分发和宿主能力调用,`files.ts` / `share.ts` 分别承接文件和分享能力,`bridge.ts` 只作为 WebView message 入口、request id replay 编排和对外 facade;`apps/mobile-shell/App.tsx` 只装配 `apps/mobile-shell/src/shell/ShellApp.tsx`,由 `apps/mobile-shell/src/shell/*.ts(x)` 承接 WebView 容器、URL、导航、网络、生命周期、安全区和 WebView policy。Tauri 桌面壳使用 `apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs` 承接 envelope、method 白名单、request 校验和 replay 状态,`capabilities.rs` 承接能力清单,`dispatch.rs` 承接 method 分发和宿主能力调用,`files.rs` / `share.rs` 分别承接文件和分享能力,`mod.rs` 只保留模块声明、必要 re-export、`host_bridge_request` command facade 和 replay 编排;`apps/desktop-shell/src-tauri/src/shell/runtime.rs`、`url.rs`、`navigation.rs`、`network.rs`、`lifecycle.rs`、`file_drop.rs`、`events.rs`、`deep_link.rs`、`tray.rs` 和 `webview.rs` 分别承接运行态、入口 URL、导航 / 下载、网络、生命周期、拖拽图片、HostBridge 事件注入、深链、托盘和 WebView 门面,`main.rs` 只保留 Tauri builder / plugin / window 装配。`npm run check:native-shells` 会检查这些目录清单。
|
||||
桥接层文件结构按宿主统一为“协议 / 能力清单 / 分发 / 宿主容器行为”四类职责。微信小程序不硬套 Expo / Tauri 的 request 总线:`miniprogram/host-bridge/protocol.js` 只沉淀微信壳能力、页面 URL、结果 hash / storage key 和分享消息类型等常量,`dispatch.js` 只作为 `protocol`、`webView`、`payment`、`shareGrid`、`subscribeMessage` 的薄索引,真实协议归一、支付 / 订阅 / 分享结果编解码仍分别放在 `webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js`;`miniprogram/shell/webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js` 承接 Page 生命周期、`wx.*` 容器调用、WebView 容器行为、支付页和订阅页装配,页面目录只保留 `Page(createWechat...Page())` 装配。Expo 移动壳使用 `apps/mobile-shell/src/host-bridge/protocol.ts` 承接 envelope、request 校验、ok / failure 响应和 replay 基础类型,`capabilities.ts` 只引用共享 HostBridge capability profile 并选择 iOS 差异能力,`dispatch.ts` 承接 method 分发和宿主能力调用,`files.ts` / `share.ts` 分别承接文件和分享能力,`bridge.ts` 只作为 WebView message 入口、request id replay 编排和对外 facade;`apps/mobile-shell/App.tsx` 只装配 `apps/mobile-shell/src/shell/ShellApp.tsx`,由 `apps/mobile-shell/src/shell/*.ts(x)` 承接 WebView 容器、URL、导航、网络、生命周期、安全区和 WebView policy。Tauri 桌面壳使用 `apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs` 承接 envelope、method 白名单、request 校验和 replay 状态,`capabilities.rs` 承接共享桌面 capability profile 的 Rust 运行时镜像,`dispatch.rs` 承接 method 分发和宿主能力调用,`files.rs` / `share.rs` 分别承接文件和分享能力,`mod.rs` 只保留模块声明、必要 re-export、`host_bridge_request` command facade 和 replay 编排;`apps/desktop-shell/src-tauri/src/shell/runtime.rs`、`url.rs`、`navigation.rs`、`network.rs`、`lifecycle.rs`、`file_drop.rs`、`events.rs`、`deep_link.rs`、`tray.rs` 和 `webview.rs` 分别承接运行态、入口 URL、导航 / 下载、网络、生命周期、拖拽图片、HostBridge 事件注入、深链、托盘和 WebView 门面,`main.rs` 只保留 Tauri builder / plugin / window 装配。`npm run check:native-shells` 会检查这些目录清单。
|
||||
|
||||
## 首批能力
|
||||
|
||||
@@ -72,7 +72,7 @@ AI H5 sandbox
|
||||
2. `authService` 保留原导出,但内部委托 HostBridge,避免一次性改动 AuthGate。
|
||||
3. 分享弹窗、分享目标同步、九宫切图、微信小程序支付和订阅授权改用 HostBridge 通用接口;旧微信命名服务只作为兼容导出。
|
||||
4. 后续新增 `native_app` adapter 时只补桥接实现和测试,业务层不新增平台分叉;主 App 启动会触发一次 `host.getRuntime` 回读并订阅能力变化,避免裁剪壳或旧入口 URL 缺少 `hostCapabilities` 时长期隐藏真实可用能力。
|
||||
5. 每次新增或调整 native capability 后,必须运行 `npm run check:native-shells`,统一覆盖 H5 HostBridge 关键测试、三端桥接层文件结构门禁、Expo 壳 typecheck / test / config smoke / Metro export smoke、Tauri 壳 typecheck / cargo test、桌面 release `--no-bundle` 构建烟测,以及可分发壳与 H5 HostBridge 真实调用链的临时替身词扫描;排查单端问题时再单独运行 `npm run mobile-shell:typecheck`、`npm run mobile-shell:test`、`npm run mobile-shell:config`、`npm run mobile-shell:export`、`npm run desktop-shell:typecheck`、`npm run desktop-shell:test` 或 `npm run desktop-shell:build -- --no-bundle`。
|
||||
5. 每次新增或调整 native capability 后,必须先更新 `packages/shared/src/contracts/hostBridge.ts` 中对应 Expo / Tauri capability profile,再运行 `npm run check:native-shells`,统一覆盖 H5 HostBridge 关键测试、三端桥接层文件结构门禁、Expo 壳 typecheck / test / config smoke / Metro export smoke、Tauri 壳 typecheck / cargo test、桌面 release `--no-bundle` 构建烟测,以及可分发壳与 H5 HostBridge 真实调用链的临时替身词扫描;排查单端问题时再单独运行 `npm run mobile-shell:typecheck`、`npm run mobile-shell:test`、`npm run mobile-shell:config`、`npm run mobile-shell:export`、`npm run desktop-shell:typecheck`、`npm run desktop-shell:test` 或 `npm run desktop-shell:build -- --no-bundle`。
|
||||
|
||||
## 验收
|
||||
|
||||
@@ -81,7 +81,7 @@ AI H5 sandbox
|
||||
- 小程序支付仍跳转 `/pages/wechat-pay/index` 并保留支付结果 hash 回灌确认。
|
||||
- 小程序订阅授权仍跳转 `/pages/subscribe-message/index`,且返回不阻断生成主链路。
|
||||
- 普通浏览器分享、H5 支付和 Native 二维码支付不受影响。
|
||||
- 原生壳统一验收入口 `npm run check:native-shells` 通过,能力白名单、壳 runtime 回包、URL `hostCapabilities`、H5 fallback、三端桥接层结构、两端壳实现、Expo managed config、移动端 production bundle、桌面 release 构建入口,以及可分发壳与 H5 HostBridge 真实调用链的临时替身词扫描没有漂移;扫描范围包含微信小程序壳生产 `.js`、共享 HostBridge 契约、H5 native transport 和已接入的 H5 直接调用链文件。
|
||||
- 原生壳统一验收入口 `npm run check:native-shells` 通过,能力白名单、共享 capability profile、壳 runtime 回包、URL `hostCapabilities`、H5 fallback、三端桥接层结构、两端壳实现、Expo managed config、移动端 production bundle、桌面 release 构建入口,以及可分发壳与 H5 HostBridge 真实调用链的临时替身词扫描没有漂移;扫描范围包含微信小程序壳生产 `.js`、共享 HostBridge 契约、H5 native transport 和已接入的 H5 直接调用链文件。
|
||||
|
||||
## 后续
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import {
|
||||
HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES,
|
||||
HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES,
|
||||
HOST_BRIDGE_EXPO_MOBILE_IOS_EXTRA_CAPABILITIES,
|
||||
HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES,
|
||||
HOST_BRIDGE_PUBLIC_WEB_ORIGIN,
|
||||
HOST_BRIDGE_PUBLIC_WEB_URL,
|
||||
HOST_BRIDGE_TAURI_COMMAND,
|
||||
HOST_BRIDGE_CAPABILITIES,
|
||||
isHostBridgeMethod,
|
||||
isHostBridgeCapability,
|
||||
normalizeHostBridgeBadgeCount,
|
||||
@@ -90,6 +95,44 @@ describe('HostBridge shared contract helpers', () => {
|
||||
expect(isHostBridgeCapability(null)).toBe(false);
|
||||
});
|
||||
|
||||
test('原生壳 capability profile 来自共享白名单', () => {
|
||||
const profiles = [
|
||||
HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES,
|
||||
HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES,
|
||||
HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES,
|
||||
];
|
||||
for (const profile of profiles) {
|
||||
expect(profile.every((capability) =>
|
||||
HOST_BRIDGE_CAPABILITIES.includes(capability),
|
||||
)).toBe(true);
|
||||
expect(profile).not.toContain('auth.requestLogin');
|
||||
expect(profile).not.toContain('payment.request');
|
||||
}
|
||||
});
|
||||
|
||||
test('移动和桌面 profile 保留真实平台差异', () => {
|
||||
expect(HOST_BRIDGE_EXPO_MOBILE_IOS_EXTRA_CAPABILITIES).toEqual([
|
||||
'app.setBadgeCount',
|
||||
]);
|
||||
expect(HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES).toEqual([
|
||||
...HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES,
|
||||
...HOST_BRIDGE_EXPO_MOBILE_IOS_EXTRA_CAPABILITIES,
|
||||
]);
|
||||
expect(HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES).toContain(
|
||||
'file.captureImage',
|
||||
);
|
||||
expect(HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES).toContain('app.setTitle');
|
||||
expect(HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES).toContain(
|
||||
'file.imageDropped',
|
||||
);
|
||||
expect(HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES).not.toContain(
|
||||
'file.captureImage',
|
||||
);
|
||||
expect(HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES).not.toContain(
|
||||
'haptics.impact',
|
||||
);
|
||||
});
|
||||
|
||||
test('归一化 HostBridge request id', () => {
|
||||
expect(normalizeHostBridgeRequestId(' request-1 ')).toBe('request-1');
|
||||
expect(normalizeHostBridgeRequestId('')).toBeNull();
|
||||
|
||||
@@ -71,12 +71,74 @@ export function isHostBridgeCapability(
|
||||
);
|
||||
}
|
||||
|
||||
export const HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES: readonly 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 HOST_BRIDGE_EXPO_MOBILE_IOS_EXTRA_CAPABILITIES: readonly HostBridgeCapability[] = [
|
||||
'app.setBadgeCount',
|
||||
];
|
||||
|
||||
export const HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES: readonly HostBridgeCapability[] = [
|
||||
...HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES,
|
||||
...HOST_BRIDGE_EXPO_MOBILE_IOS_EXTRA_CAPABILITIES,
|
||||
];
|
||||
|
||||
export const HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES: readonly HostBridgeCapability[] = [
|
||||
'host.getRuntime',
|
||||
'appearance.getColorScheme',
|
||||
'host.events',
|
||||
'app.lifecycle',
|
||||
'share.open',
|
||||
'share.setTarget',
|
||||
'navigation.openNativePage',
|
||||
'navigation.canGoBack',
|
||||
'app.reloadWebView',
|
||||
'app.openExternalUrl',
|
||||
'app.setTitle',
|
||||
'app.setBadgeCount',
|
||||
'network.status',
|
||||
'network.statusChanged',
|
||||
'clipboard.writeText',
|
||||
'clipboard.readText',
|
||||
'file.exportText',
|
||||
'file.importText',
|
||||
'file.exportImage',
|
||||
'file.importImage',
|
||||
'file.importAudio',
|
||||
'file.exportAudio',
|
||||
'file.imageDropped',
|
||||
'notification.showLocal',
|
||||
];
|
||||
|
||||
export type HostBridgeRuntimeResult = {
|
||||
shell: NativeHostShell;
|
||||
platform: NativeHostPlatform;
|
||||
hostVersion: string | null;
|
||||
bridgeVersion: number;
|
||||
capabilities: HostBridgeCapability[];
|
||||
capabilities: readonly HostBridgeCapability[];
|
||||
};
|
||||
|
||||
export type HostBridgeRequest<Payload = unknown> = {
|
||||
|
||||
@@ -421,31 +421,35 @@ function assertNativeShellCapabilityPlan() {
|
||||
'decision log document',
|
||||
);
|
||||
|
||||
const mobileCapabilitySource = fs.readFileSync(
|
||||
'apps/mobile-shell/src/host-bridge/capabilities.ts',
|
||||
'utf8',
|
||||
);
|
||||
const desktopCapabilitySource = fs.readFileSync(
|
||||
'apps/desktop-shell/src-tauri/src/host_bridge/capabilities.rs',
|
||||
'utf8',
|
||||
);
|
||||
const sharedContractSource = fs.readFileSync(sharedHostBridgeContractPath, 'utf8');
|
||||
const sharedMethods = extractTsStringArray(sharedContractSource, 'HOST_BRIDGE_METHODS');
|
||||
const mobileCapabilities = extractTsStringArray(
|
||||
mobileCapabilitySource,
|
||||
'MOBILE_HOST_CAPABILITIES',
|
||||
);
|
||||
const sharedMethods = extractTsStringArray(
|
||||
fs.readFileSync(sharedHostBridgeContractPath, 'utf8'),
|
||||
'HOST_BRIDGE_METHODS',
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES',
|
||||
);
|
||||
const iosMobileCapabilities = extractTsStringArray(
|
||||
mobileCapabilitySource,
|
||||
'IOS_MOBILE_HOST_CAPABILITIES',
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES',
|
||||
);
|
||||
const iosExtraCapabilities = iosMobileCapabilities.filter(
|
||||
(capability) => !mobileCapabilities.includes(capability),
|
||||
);
|
||||
const sharedDesktopCapabilities = extractTsStringArray(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES',
|
||||
);
|
||||
const desktopCapabilities = extractRustCapabilities(desktopCapabilitySource);
|
||||
|
||||
assertSameList(
|
||||
desktopCapabilities,
|
||||
sharedDesktopCapabilities,
|
||||
'desktop shell runtime capability profile',
|
||||
);
|
||||
|
||||
assertSameList(
|
||||
extractDocumentMethodTable(planSource),
|
||||
[
|
||||
|
||||
@@ -162,7 +162,7 @@ function normalizeHostCapabilities(values: unknown): HostBridgeCapability[] {
|
||||
}
|
||||
|
||||
function mergeHostCapabilities(
|
||||
...capabilityLists: Array<HostBridgeCapability[] | null | undefined>
|
||||
...capabilityLists: Array<readonly HostBridgeCapability[] | null | undefined>
|
||||
) {
|
||||
return Array.from(
|
||||
new Set(capabilityLists.flatMap((capabilities) => capabilities ?? [])),
|
||||
|
||||
@@ -179,11 +179,13 @@ describe('nativeAppHostBridge', () => {
|
||||
|
||||
test('Tauri invoke 超时后的迟到回包不改变已拒绝结果', async () => {
|
||||
vi.useFakeTimers();
|
||||
let resolveInvoke:
|
||||
| ((response: HostBridgeResponse<{ handled: boolean }>) => void)
|
||||
| null = null;
|
||||
let resolveInvoke: (
|
||||
response: HostBridgeResponse<{ handled: boolean }>,
|
||||
) => void = () => {
|
||||
throw new Error('Tauri invoke promise was not created');
|
||||
};
|
||||
const invoke = vi.fn(
|
||||
() =>
|
||||
(_command: string, _args?: Record<string, unknown>) =>
|
||||
new Promise<HostBridgeResponse<{ handled: boolean }>>((resolve) => {
|
||||
resolveInvoke = resolve;
|
||||
}),
|
||||
@@ -206,7 +208,7 @@ describe('nativeAppHostBridge', () => {
|
||||
|
||||
const request = (invoke.mock.calls[0]?.[1] as { request: HostBridgeRequest })
|
||||
.request;
|
||||
resolveInvoke?.({
|
||||
resolveInvoke({
|
||||
bridge: HOST_BRIDGE_PROTOCOL,
|
||||
version: HOST_BRIDGE_VERSION,
|
||||
id: request.id,
|
||||
|
||||
Reference in New Issue
Block a user