记录移动壳外链打开失败

移动 WebView 外链交给系统打开失败时记录错误

移动壳测试和配置检查拒绝静默吞掉外链失败

共享决策日志补充外链打开失败边界
This commit is contained in:
2026-06-20 10:03:20 +08:00
parent 53312d1b49
commit c2379e4d47
4 changed files with 51 additions and 1 deletions
@@ -1380,12 +1380,19 @@ for (const snippet of [
'handleBlockedFileDownload',
'onFileDownload={handleBlockedFileDownload}',
'setSupportMultipleWindows={false}',
'logMobileShellNavigationFailure',
'mobile shell navigation failed for',
'external_navigation.open',
]) {
if (!shellAppSource.includes(snippet)) {
throw new Error(`mobile shell ShellApp missing ${snippet}`);
}
}
if (shellAppSource.includes('catch(() => undefined)')) {
throw new Error('mobile shell ShellApp must not hide async navigation failures');
}
for (const snippet of [
"describe('mobile shell safe area'",
"test('protects the WebView from every device edge'",
@@ -2230,6 +2237,8 @@ for (const snippet of [
'shellHarness.networkListeners[0]?.({',
"type: 'genarrative.mobile.historyState'",
'mobile host event failed for network.statusChanged',
'external WebView navigation open failure is logged instead of hidden',
'mobile shell navigation failed for external_navigation.open',
]) {
if (!shellAppTestSource.includes(snippet)) {
throw new Error(`mobile shell ShellApp HostBridge event test missing ${snippet}`);
@@ -449,4 +449,32 @@ describe('ShellApp HostBridge event injection', () => {
warnSpy.mockRestore();
});
test('external WebView navigation open failure is logged instead of hidden', async () => {
const ShellApp = await importShellApp();
render(<ShellApp />);
const webViewProps = shellHarness.webViewProps.current as {
onShouldStartLoadWithRequest?: (request: { url: string }) => boolean;
};
const navigationError = new Error('system browser unavailable');
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
const { Linking } = await import('react-native');
vi.mocked(Linking.openURL).mockRejectedValueOnce(navigationError);
expect(
webViewProps.onShouldStartLoadWithRequest?.({
url: 'https://outside.example/work/1',
}),
).toBe(false);
await waitFor(() => {
expect(warnSpy).toHaveBeenCalledWith(
'mobile shell navigation failed for external_navigation.open',
navigationError,
);
});
warnSpy.mockRestore();
});
});
+7 -1
View File
@@ -77,6 +77,10 @@ function logMobileHostEventFailure(label: HostBridgeEventName, error: unknown) {
console.warn(`mobile host event failed for ${label}`, error);
}
function logMobileShellNavigationFailure(label: string, error: unknown) {
console.warn(`mobile shell navigation failed for ${label}`, error);
}
type MobileWebViewLoadErrorEvent = {
nativeEvent: {
url: string;
@@ -275,7 +279,9 @@ export default function ShellApp() {
}
void openMobileShellExternalNavigation(Linking, request.url).catch(
() => undefined,
(error: unknown) => {
logMobileShellNavigationFailure('external_navigation.open', error);
},
);
return false;
};
@@ -2944,6 +2944,13 @@
- 影响范围:`apps/mobile-shell/src/shell/ShellApp.tsx``apps/mobile-shell/src/shell/ShellApp.test.tsx``apps/mobile-shell/scripts/check-config.mjs`、宿主壳能力统一协议文档。
- 验证方式:`npm run mobile-shell:test -- src/shell/ShellApp.test.tsx``npm run mobile-shell:typecheck``npm run check:native-shells``npm run check:encoding``git diff --check`
## 2026-06-20 移动壳 WebView 外链打开失败不可静默
- 背景:Expo WebView 外域导航会离开带 HostBridge 的主站容器并交给系统浏览器或系统应用;如果 `Linking.openURL(...)` reject 后静默吞掉,用户会看到点击外链无反应且开发侧无法区分协议、系统能力或原生模块失败。
- 决策:`ShellApp` 的 WebView 外链分流必须继续复用 `openMobileShellExternalNavigation(Linking, request.url)`,但 Promise reject 路径必须调用 `logMobileShellNavigationFailure('external_navigation.open', error)` 记录错误;配置检查拒绝 `ShellApp` 重新出现 `catch(() => undefined)`,并反查外链失败日志测试。
- 影响范围:`apps/mobile-shell/src/shell/ShellApp.tsx``apps/mobile-shell/src/shell/ShellApp.test.tsx``apps/mobile-shell/scripts/check-config.mjs`
- 验证方式:`npm run mobile-shell:test -- src/shell/ShellApp.test.tsx``npm run mobile-shell:typecheck``npm run check:native-shells``npm run check:encoding``git diff --check`
## 2026-06-20 移动壳生命周期映射门禁
- 背景:Expo `AppState``active``background``inactive` 以及未知状态都会进入 `app.lifecycle` 事件;如果归一化映射漂移,H5 游戏循环、背景音乐和固定玩法音频会错误恢复或暂停。