387a1c26e3
清理已跟踪原始日志与个人本地配置 修复前后端有效门禁、测试和构建问题 补齐生产 Jenkins、SpacetimeDB 本地命令与文档约束 收紧图片编辑器状态、附件与媒体引用测试 排除已下线旧创作入口测试并清理 warning
113 lines
2.6 KiB
TypeScript
113 lines
2.6 KiB
TypeScript
import {
|
|
buildMobileShellUrl,
|
|
type MobileShellBaseWebUrlOptions,
|
|
type MobileShellUrlOptions,
|
|
resolveMobileShellBaseWebUrl,
|
|
} from './url';
|
|
|
|
const supportedHosts = new Set(['open', 'app']);
|
|
|
|
type MobileShellDeepLinkResolutionStatus = 'default' | 'mapped' | 'rejected';
|
|
|
|
export type MobileShellDeepLinkResolution = {
|
|
status: MobileShellDeepLinkResolutionStatus;
|
|
url: string;
|
|
};
|
|
|
|
function extractPathFromNativeUrl(url: URL) {
|
|
if (supportedHosts.has(url.hostname)) {
|
|
return `${url.pathname}${url.search}${url.hash}`;
|
|
}
|
|
|
|
return `${url.hostname ? `/${url.hostname}` : ''}${url.pathname}${url.search}${url.hash}`;
|
|
}
|
|
|
|
function resolveTargetPath(rawUrl: string, webOrigin: string) {
|
|
try {
|
|
const url = new URL(rawUrl);
|
|
if (url.protocol === 'genarrative:') {
|
|
return extractPathFromNativeUrl(url);
|
|
}
|
|
|
|
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
return null;
|
|
}
|
|
|
|
if (url.origin !== webOrigin) {
|
|
return null;
|
|
}
|
|
|
|
return `${url.pathname}${url.search}${url.hash}`;
|
|
} catch {
|
|
if (!rawUrl.startsWith('/') && !rawUrl.startsWith('#')) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const relativeUrl = new URL(rawUrl, webOrigin);
|
|
if (relativeUrl.origin !== webOrigin) {
|
|
return null;
|
|
}
|
|
|
|
return `${relativeUrl.pathname}${relativeUrl.search}${relativeUrl.hash}`;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
export function buildMobileShellUrlFromDeepLink(
|
|
rawUrl: string | null | undefined,
|
|
baseWebUrl: string,
|
|
options: MobileShellUrlOptions,
|
|
baseWebUrlOptions: MobileShellBaseWebUrlOptions = {},
|
|
) {
|
|
return resolveMobileShellUrlFromDeepLink(
|
|
rawUrl,
|
|
baseWebUrl,
|
|
options,
|
|
baseWebUrlOptions,
|
|
).url;
|
|
}
|
|
|
|
export function resolveMobileShellUrlFromDeepLink(
|
|
rawUrl: string | null | undefined,
|
|
baseWebUrl: string,
|
|
options: MobileShellUrlOptions,
|
|
baseWebUrlOptions: MobileShellBaseWebUrlOptions = {},
|
|
): MobileShellDeepLinkResolution {
|
|
const normalizedBaseWebUrl = resolveMobileShellBaseWebUrl(
|
|
baseWebUrl,
|
|
baseWebUrlOptions,
|
|
);
|
|
const defaultUrl = buildMobileShellUrl(
|
|
normalizedBaseWebUrl,
|
|
options,
|
|
baseWebUrlOptions,
|
|
);
|
|
if (!rawUrl) {
|
|
return {
|
|
status: 'default',
|
|
url: defaultUrl,
|
|
};
|
|
}
|
|
|
|
const webOrigin = new URL(normalizedBaseWebUrl).origin;
|
|
const targetPath = resolveTargetPath(rawUrl, webOrigin);
|
|
if (!targetPath) {
|
|
return {
|
|
status: 'rejected',
|
|
url: defaultUrl,
|
|
};
|
|
}
|
|
|
|
return {
|
|
status: 'mapped',
|
|
url: buildMobileShellUrl(
|
|
new URL(targetPath, webOrigin).toString(),
|
|
options,
|
|
baseWebUrlOptions,
|
|
),
|
|
};
|
|
}
|