统一原生扫码超时契约
将二维码扫码超时提升到共享 HostBridge 契约 让 H5 facade 和测试复用共享扫码超时常量 增加原生壳门禁拦截扫码超时字面量回退 补充宿主壳方案文档和共享决策记录
This commit is contained in:
@@ -1795,12 +1795,18 @@ if (!h5HostBridgeSource.includes('HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS,')) {
|
||||
if (!h5HostBridgeSource.includes('HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS,')) {
|
||||
throw new Error('H5 HostBridge facade must import shared user interaction timeout');
|
||||
}
|
||||
if (!h5HostBridgeSource.includes('HOST_BRIDGE_SCANNER_TIMEOUT_MS,')) {
|
||||
throw new Error('H5 HostBridge facade must import shared scanner timeout');
|
||||
}
|
||||
if (h5HostBridgeSource.includes('HOST_RUNTIME_REFRESH_TIMEOUT_MS')) {
|
||||
throw new Error('H5 HostBridge facade must not redeclare runtime refresh timeout');
|
||||
}
|
||||
if (h5HostBridgeSource.includes('timeoutMs: 30000')) {
|
||||
throw new Error('H5 HostBridge facade must not redeclare user interaction timeout');
|
||||
}
|
||||
if (h5HostBridgeSource.includes('timeoutMs: 60000')) {
|
||||
throw new Error('H5 HostBridge facade must not redeclare scanner timeout');
|
||||
}
|
||||
for (const snippet of [
|
||||
'HOST_BRIDGE_DEFAULT_REQUEST_TIMEOUT_MS',
|
||||
'HOST_BRIDGE_MAX_REQUEST_TIMEOUT_MS',
|
||||
|
||||
@@ -2754,3 +2754,10 @@
|
||||
- 决策:`scripts/check-native-shells.mjs` 新增微信 capability flow contract。每个微信 capability 必须对应真实 `miniprogram/host-bridge/*`、`miniprogram/shell/*`、`miniprogram/pages/*` 文件,源码中必须保留关键页面工厂或 `wx.login` / `wx.requestPayment` / `wx.requestVirtualPayment` / `wx.saveImageToPhotosAlbum` 等真实宿主调用,并且对应测试必须在 `check:native-shells` 的微信壳测试清单内。
|
||||
- 影响范围:`scripts/check-native-shells.mjs`、Expo / Tauri HostBridge 方案文档、共享开发流程记忆。
|
||||
- 验证方式:`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## 2026-06-19 原生宿主二维码扫码超时单一来源
|
||||
|
||||
- 背景:二维码扫码属于真实相机交互,等待时间应长于普通宿主请求;此前 H5 `scanHostQrCode()` 直接手写 `timeoutMs: 60000`,会让扫码等待边界和共享 HostBridge 契约漂移。
|
||||
- 决策:`packages/shared/src/contracts/hostBridge.ts` 导出 `HOST_BRIDGE_SCANNER_TIMEOUT_MS`,作为 H5 facade 发起 `scanner.scanQrCode` 请求的唯一超时来源;`src/services/host-bridge/hostBridge.ts` 必须导入共享常量,不得继续手写 `timeoutMs: 60000`。根级原生壳门禁和桌面壳配置检查会拒绝回退到本地字面量。
|
||||
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`src/services/host-bridge/hostBridge.ts`、`scripts/check-native-shells.mjs`、Expo / Tauri HostBridge 方案文档和共享开发流程记忆。
|
||||
- 验证方式:`npm run test -- packages/shared/src/contracts/hostBridge.test.ts src/services/host-bridge/hostBridge.test.ts`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
@@ -160,7 +160,7 @@ type HostBridgeEvent = {
|
||||
| `haptics.impact` | 轻量触感反馈 | 支持 | 不声明 |
|
||||
| `notification.showLocal` | 发送即时本地系统通知 | 支持 Expo Notifications | 支持 Rust 侧 Tauri notification |
|
||||
|
||||
每个 method 都必须有明确 payload schema、超时、错误码和能力开关;H5 看到不支持时回退到现有浏览器路径。
|
||||
每个 method 都必须有明确 payload schema、超时、错误码和能力开关;H5 看到不支持时回退到现有浏览器路径。H5 发起原生壳请求的超时边界统一从 `packages/shared/src/contracts/hostBridge.ts` 读取:普通请求使用 `HOST_BRIDGE_DEFAULT_REQUEST_TIMEOUT_MS`,传入值最多裁剪到 `HOST_BRIDGE_MAX_REQUEST_TIMEOUT_MS`;宿主 runtime 回读使用 `HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS`;文件导入 / 导出、图片选择 / 拍摄和音频导入 / 导出使用 `HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS`;二维码扫码使用 `HOST_BRIDGE_SCANNER_TIMEOUT_MS`。H5 facade 不得在调用点手写 `30000` 或 `60000` 等毫秒字面量,根级 `check:native-shells` 与桌面壳配置检查会拦截回退。
|
||||
|
||||
## Expo React Native 壳
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -23,6 +23,7 @@ import {
|
||||
HOST_BRIDGE_MAX_REQUEST_TIMEOUT_MS,
|
||||
HOST_BRIDGE_RESPONSE_CACHE_MAX,
|
||||
HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS,
|
||||
HOST_BRIDGE_SCANNER_TIMEOUT_MS,
|
||||
HOST_BRIDGE_TEXT_MIME_TYPES,
|
||||
HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS,
|
||||
isHostBridgeMethod,
|
||||
@@ -56,6 +57,7 @@ describe('HostBridge shared contract helpers', () => {
|
||||
expect(HOST_BRIDGE_MAX_REQUEST_TIMEOUT_MS).toBe(60000);
|
||||
expect(HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS).toBe(3000);
|
||||
expect(HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS).toBe(30000);
|
||||
expect(HOST_BRIDGE_SCANNER_TIMEOUT_MS).toBe(60000);
|
||||
});
|
||||
|
||||
test('固定宿主侧响应回放缓存边界', () => {
|
||||
|
||||
@@ -220,6 +220,7 @@ export const HOST_BRIDGE_DEFAULT_REQUEST_TIMEOUT_MS = 8000;
|
||||
export const HOST_BRIDGE_MAX_REQUEST_TIMEOUT_MS = 60000;
|
||||
export const HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS = 3000;
|
||||
export const HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS = 30000;
|
||||
export const HOST_BRIDGE_SCANNER_TIMEOUT_MS = 60000;
|
||||
export const HOST_BRIDGE_RESPONSE_CACHE_MAX = 128;
|
||||
export const HOST_BRIDGE_REQUEST_ID_MAX_LENGTH = 120;
|
||||
|
||||
|
||||
@@ -978,6 +978,7 @@ function assertH5HostBridgePayloadBoundaries() {
|
||||
'HOST_BRIDGE_AUDIO_MIME_TYPES',
|
||||
'HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS',
|
||||
'HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS',
|
||||
'HOST_BRIDGE_SCANNER_TIMEOUT_MS',
|
||||
]) {
|
||||
if (!h5HostBridgeSource.includes(`${sharedBoundary},`)) {
|
||||
throw new Error(
|
||||
@@ -1022,6 +1023,11 @@ function assertH5HostBridgePayloadBoundaries() {
|
||||
'H5 HostBridge facade must use shared HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS',
|
||||
);
|
||||
}
|
||||
if (h5HostBridgeSource.includes('timeoutMs: 60000')) {
|
||||
throw new Error(
|
||||
'H5 HostBridge facade must use shared HOST_BRIDGE_SCANNER_TIMEOUT_MS',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function assertH5NativeAppTransportTimeoutBoundaries() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { afterEach, describe, expect, test, vi } from 'vitest';
|
||||
import type { HostBridgeCapability } from '../../../packages/shared/src/contracts/hostBridge';
|
||||
import {
|
||||
HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS,
|
||||
HOST_BRIDGE_SCANNER_TIMEOUT_MS,
|
||||
HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS,
|
||||
} from '../../../packages/shared/src/contracts/hostBridge';
|
||||
import {
|
||||
@@ -1209,7 +1210,7 @@ describe('hostBridge', () => {
|
||||
expect(invoke).toHaveBeenCalledWith('host_bridge_request', {
|
||||
request: expect.objectContaining({
|
||||
method: 'scanner.scanQrCode',
|
||||
timeoutMs: 60000,
|
||||
timeoutMs: HOST_BRIDGE_SCANNER_TIMEOUT_MS,
|
||||
}),
|
||||
});
|
||||
expect(invoke).toHaveBeenCalledWith('host_bridge_request', {
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
HOST_BRIDGE_NATIVE_APP_QUERY,
|
||||
HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS,
|
||||
HOST_BRIDGE_RUNTIME_CONTEXT_QUERY_KEY,
|
||||
HOST_BRIDGE_SCANNER_TIMEOUT_MS,
|
||||
HOST_BRIDGE_TEXT_MIME_TYPES,
|
||||
HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS,
|
||||
HOST_BRIDGE_WECHAT_MINI_PROGRAM_SOURCE_QUERY,
|
||||
@@ -931,7 +932,7 @@ export async function scanHostQrCode() {
|
||||
await requestNativeAppHostBridge<ScannerScanQrCodeResult>(
|
||||
'scanner.scanQrCode',
|
||||
undefined,
|
||||
{ timeoutMs: 60000 },
|
||||
{ timeoutMs: HOST_BRIDGE_SCANNER_TIMEOUT_MS },
|
||||
);
|
||||
return normalizeHostBridgeQrCodeValue(result?.value) ?? false;
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user