补齐移动外链打开失败日志

移动 HostBridge 外链打开原生异常记录稳定日志

配置门禁反查外链异常不可静默

同步原生壳方案文档和共享决策记录
This commit is contained in:
2026-06-20 21:50:11 +08:00
parent 00dce5914b
commit 9e9f8c8fcb
5 changed files with 29 additions and 3 deletions
+6 -2
View File
@@ -2382,7 +2382,8 @@ if (
'const externalUrlPayload = normalizeHostBridgeExternalUrlPayload(',
) ||
!hostBridgeNavigationSource.includes('openMobileShellExternalNavigation(') ||
!hostBridgeNavigationSource.includes('externalUrlPayload.url')
!hostBridgeNavigationSource.includes('externalUrlPayload.url') ||
!hostBridgeNavigationSource.includes('logMobileHostBridgeNavigationFailure')
) {
throw new Error(
'mobile shell app.openExternalUrl must normalize payloads and use the shared external navigation helper',
@@ -2396,6 +2397,8 @@ for (const snippet of [
'Linking.openURL',
'javascript:alert(1)',
'external URL cannot be opened',
'mobile HostBridge navigation failed for external.open',
'expect(warnSpy).toHaveBeenCalledTimes(2)',
'converts native external open exceptions to stable host_error',
'navigation.openNativePage unsupported in mobile shell',
'app.reloadWebView unsupported in mobile shell',
@@ -2965,7 +2968,8 @@ for (const snippet of [
"import * as Linking from 'expo-linking'",
'openMobileShellExternalNavigation(',
'externalUrlPayload.url',
'} catch {',
"} catch (error) {",
"logMobileHostBridgeNavigationFailure('external.open', error)",
'opened = false;',
'(request.payload as OpenExternalUrlPayload | undefined)?.url',
'normalizeHostBridgeExternalUrlPayload',
@@ -42,6 +42,7 @@ function navigation(): MobileHostBridgeNavigation {
}
beforeEach(() => {
vi.restoreAllMocks();
vi.mocked(Linking.canOpenURL).mockReset();
vi.mocked(Linking.openURL).mockReset();
});
@@ -107,6 +108,8 @@ describe('mobile HostBridge navigation helpers', () => {
});
test('converts native external open exceptions to stable host_error', async () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
vi.mocked(Linking.canOpenURL).mockRejectedValueOnce(
new Error('native canOpenURL failed'),
);
@@ -122,6 +125,10 @@ describe('mobile HostBridge navigation helpers', () => {
message: 'external URL cannot be opened',
});
expect(Linking.openURL).not.toHaveBeenCalled();
expect(warnSpy).toHaveBeenCalledWith(
'mobile HostBridge navigation failed for external.open',
expect.any(Error),
);
vi.mocked(Linking.canOpenURL).mockResolvedValueOnce(true);
vi.mocked(Linking.openURL).mockRejectedValueOnce(
@@ -138,6 +145,7 @@ describe('mobile HostBridge navigation helpers', () => {
code: 'host_error',
message: 'external URL cannot be opened',
});
expect(warnSpy).toHaveBeenCalledTimes(2);
});
test('opens same-origin native page targets in the WebView with host context', () => {
@@ -20,6 +20,10 @@ import {
unsupported,
} from './protocol';
function logMobileHostBridgeNavigationFailure(label: string, error: unknown) {
console.warn(`mobile HostBridge navigation failed for ${label}`, error);
}
export async function openMobileHostBridgeExternalUrl(
request: HostBridgeRequest,
) {
@@ -36,7 +40,8 @@ export async function openMobileHostBridgeExternalUrl(
Linking,
externalUrlPayload.url,
);
} catch {
} catch (error) {
logMobileHostBridgeNavigationFailure('external.open', error);
opened = false;
}
if (!opened) {
@@ -2965,6 +2965,13 @@
- 影响范围:`apps/desktop-shell/src-tauri/src/shell/deep_link.rs``apps/desktop-shell/scripts/check-config.mjs`、宿主壳方案文档。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml shell::deep_link``npm run desktop-shell:typecheck``npm run check:native-shells``npm run check:encoding``git diff --check`
## 2026-06-20 移动 HostBridge 外链打开异常不可静默
- 背景:Expo 移动壳的 `app.openExternalUrl` 会调用系统 `Linking.canOpenURL` / `openURL` 离开 WebView;如果系统 API reject 后只返回 H5 稳定失败,真机上外链无反应时缺少宿主侧排查线索。
- 决策:`openMobileHostBridgeExternalUrl(...)` 捕获系统外链打开异常时必须记录 `mobile HostBridge navigation failed for external.open`HostBridge 回包仍只暴露稳定 `host_error: external URL cannot be opened`,不透传系统异常细节。配置检查反查日志 helper、`catch (error)` 和对应测试断言。
- 影响范围:`apps/mobile-shell/src/host-bridge/navigation.ts``apps/mobile-shell/src/host-bridge/navigation.test.ts``apps/mobile-shell/scripts/check-config.mjs`、宿主壳方案文档。
- 验证方式:`npm run mobile-shell:test -- src/host-bridge/navigation.test.ts``npm run mobile-shell:typecheck``npm run check:native-shells``npm run check:encoding``git diff --check`
## 2026-06-20 桌面壳生命周期窗口状态读取失败不可静默
- 背景:Tauri 桌面壳 `app.lifecycle` 事件会驱动 H5 游戏循环、背景音乐和固定玩法音频暂停 / 恢复;如果 `is_visible()``is_minimized()``is_focused()` 读取失败后静默使用默认值,生命周期状态可能错误且难以排查。
@@ -344,6 +344,8 @@ GameBridge 禁止:
2026-06-20 追加:移动壳剪贴板、触觉和网络查询的原生异常必须可观测。Expo Clipboard 写入 / 读取、Expo Haptics 触觉派发、Expo Network 状态查询失败时,移动壳必须分别记录 `mobile clipboard failed for ...``mobile haptics failed for ...``mobile network failed for ...` 日志;HostBridge 回包仍只暴露稳定 `clipboard write unavailable``clipboard read unavailable``haptics impact unavailable``network status unavailable` 语义。该约束不新增后台网络探测、任意系统能力或 H5 业务兜底路径。
2026-06-20 追加:移动壳 HostBridge 外链打开异常必须可观测。Expo `app.openExternalUrl` 捕获 `Linking.canOpenURL` / `openURL` 原生异常时记录 `mobile HostBridge navigation failed for external.open`HostBridge 回包仍只暴露稳定 `external URL cannot be opened` 语义,不透传系统异常细节。
2026-06-18 追加:移动壳 `haptics.impact` 只接受 `light``medium``heavy` 三档 impact style,缺省为 `light`;未知强度返回 `invalid_request`,不会静默降级成真实设备触觉反馈。桌面壳不声明该能力,H5 继续按 HostBridge fallback 处理。
2026-06-19 追加:移动壳 `haptics.impact` 的 HostBridge payload 解析、共享 style 归一、Expo style 映射和 `Haptics.impactAsync(...)` 真实调用统一收口在 `apps/mobile-shell/src/host-bridge/haptics.ts``dispatch.ts` 只按 method 委托 `runMobileHostBridgeHapticsImpact(request.payload)`