补齐移动外链打开失败日志
移动 HostBridge 外链打开原生异常记录稳定日志 配置门禁反查外链异常不可静默 同步原生壳方案文档和共享决策记录
This commit is contained in:
@@ -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)`。
|
||||
|
||||
Reference in New Issue
Block a user