Files
Genarrative/apps/mobile-shell/src/shell/loadFailure.ts
T
kdletters 8bdfd1c629 移动壳补齐加载失败兜底
新增同源主页面加载失败归一模型与测试

Expo WebView 接入 onError/onHttpError 原生重试层

更新原生壳门禁与宿主壳方案文档
2026-06-18 20:26:20 +08:00

111 lines
2.5 KiB
TypeScript

import { shouldOpenInMobileShellWebView } from './navigation';
export type MobileShellLoadFailureInput =
| {
type: 'native';
url: string;
code?: number | null;
description?: string | null;
}
| {
type: 'http';
url: string;
statusCode?: number | null;
description?: string | null;
};
export type MobileShellLoadFailure = {
type: 'native' | 'http';
url: string;
title: string;
detail: string;
retryLabel: string;
};
function normalizeDescription(value: string | null | undefined) {
const normalized = value?.replace(/\s+/g, ' ').trim();
if (!normalized) {
return null;
}
return normalized.length > 96
? `${normalized.slice(0, 96).trim()}...`
: normalized;
}
function sameDocumentUrl(
left: URL,
right: string | null | undefined,
allowedOrigin: string,
) {
if (!right) {
return true;
}
try {
const current = new URL(right, allowedOrigin);
return (
left.origin === current.origin &&
left.pathname === current.pathname &&
left.search === current.search
);
} catch {
return false;
}
}
function shouldShowLoadFailure(
rawUrl: string,
allowedOrigin: string,
currentPageUrl?: string | null,
) {
if (!shouldOpenInMobileShellWebView(rawUrl, allowedOrigin)) {
return false;
}
try {
const url = new URL(rawUrl, allowedOrigin);
return (
url.origin === allowedOrigin &&
url.pathname !== '/favicon.ico' &&
sameDocumentUrl(url, currentPageUrl, allowedOrigin)
);
} catch {
return false;
}
}
export function normalizeMobileShellLoadFailure(
input: MobileShellLoadFailureInput,
allowedOrigin: string,
currentPageUrl?: string | null,
): MobileShellLoadFailure | null {
if (!shouldShowLoadFailure(input.url, allowedOrigin, currentPageUrl)) {
return null;
}
const url = new URL(input.url, allowedOrigin).toString();
const description = normalizeDescription(input.description);
if (input.type === 'http') {
const statusCode =
Number.isInteger(input.statusCode) && input.statusCode
? input.statusCode
: null;
return {
type: 'http',
url,
title: statusCode ? `加载失败 ${statusCode}` : '加载失败',
detail: description ?? '服务器暂时没有返回可用页面',
retryLabel: '重试',
};
}
return {
type: 'native',
url,
title: '网络不可用',
detail: description ?? '当前页面没有加载成功',
retryLabel: '重试',
};
}