Files
Genarrative/packages/shared/src/contracts/hostBridge.ts
kdletters a87f3dcc82 接入桌面壳窗口标题同步
HostBridge 契约新增 app.setTitle 方法和标题 payload

Tauri 桌面壳通过主窗口 API 同步非空窗口标题

桌面壳能力清单和配置守卫声明 app.setTitle

补充标题校验测试并更新宿主壳方案和团队共享决策记录
2026-06-17 22:36:52 +08:00

155 lines
3.1 KiB
TypeScript

export const HOST_BRIDGE_PROTOCOL = 'GenarrativeHostBridge';
export const HOST_BRIDGE_VERSION = 1;
export type HostShellKind = 'browser' | 'wechat_mini_program' | 'native_app';
export type NativeHostShell = 'expo_mobile' | 'tauri_desktop';
export type NativeHostPlatform =
| 'ios'
| 'android'
| 'macos'
| 'windows'
| 'linux'
| 'unknown';
export type HostBridgeMethod =
| 'host.getRuntime'
| 'auth.requestLogin'
| 'payment.request'
| 'share.setTarget'
| 'share.open'
| 'navigation.openNativePage'
| 'app.openExternalUrl'
| 'app.setTitle'
| 'clipboard.writeText'
| 'haptics.impact';
export type HostBridgeCapability =
| HostBridgeMethod
| 'host.events'
| 'navigation.canGoBack';
export type HostBridgeRuntimeResult = {
shell: NativeHostShell;
platform: NativeHostPlatform;
hostVersion: string | null;
bridgeVersion: number;
capabilities: HostBridgeCapability[];
};
export type HostBridgeRequest<Payload = unknown> = {
bridge: typeof HOST_BRIDGE_PROTOCOL;
version: typeof HOST_BRIDGE_VERSION;
id: string;
method: HostBridgeMethod;
payload?: Payload;
timeoutMs?: number;
};
export type HostBridgeError = {
code:
| 'invalid_request'
| 'unsupported_method'
| 'unsupported_capability'
| 'timeout'
| 'cancelled'
| 'host_error';
message: string;
};
export type HostBridgeResponse<Result = unknown> = {
bridge: typeof HOST_BRIDGE_PROTOCOL;
version: typeof HOST_BRIDGE_VERSION;
id: string;
} & (
| {
ok: true;
result?: Result;
}
| {
ok: false;
error: HostBridgeError;
}
);
export type HostBridgeEvent<Payload = unknown> = {
bridge: typeof HOST_BRIDGE_PROTOCOL;
version: typeof HOST_BRIDGE_VERSION;
event: string;
payload?: Payload;
};
export type NavigateNativePagePayload = {
url: string;
};
export type OpenExternalUrlPayload = {
url: string;
};
export type SetTitlePayload = {
title: string;
};
export const HOST_BRIDGE_EXTERNAL_URL_PROTOCOLS = [
'http:',
'https:',
'mailto:',
'tel:',
] as const;
export type HostBridgeExternalUrlProtocol =
(typeof HOST_BRIDGE_EXTERNAL_URL_PROTOCOLS)[number];
function hasHostBridgeControlCharacter(value: string) {
return [...value].some((character) => {
const codePoint = character.codePointAt(0) ?? 0;
return codePoint <= 31 || codePoint === 127;
});
}
export function normalizeHostBridgeExternalUrl(rawUrl: unknown) {
if (typeof rawUrl !== 'string') {
return null;
}
const urlText = rawUrl.trim();
if (!urlText || hasHostBridgeControlCharacter(urlText)) {
return null;
}
try {
const url = new URL(urlText);
if (
!HOST_BRIDGE_EXTERNAL_URL_PROTOCOLS.includes(
url.protocol as HostBridgeExternalUrlProtocol,
)
) {
return null;
}
return url.toString();
} catch {
return null;
}
}
export type ClipboardWriteTextPayload = {
text: string;
};
export type HapticsImpactPayload = {
style?: 'light' | 'medium' | 'heavy';
};
export type ShareSetTargetPayload = {
target: unknown;
};
export type ShareOpenPayload = {
title?: string;
message?: string;
url?: string;
};