diff --git a/scripts/check-native-shells.mjs b/scripts/check-native-shells.mjs index 70dab7944..dfab2a1aa 100644 --- a/scripts/check-native-shells.mjs +++ b/scripts/check-native-shells.mjs @@ -1165,6 +1165,21 @@ function assertH5HostBridgePayloadBoundaries() { 'H5 HostBridge facade must normalize clipboard.writeText payloads with the shared clipboard boundary', ); } + if ( + !h5HostBridgeSource.includes( + 'const clipboardText = normalizeHostBridgeClipboardText(', + ) || + !h5HostBridgeSource.includes( + "await requestNativeAppHostBridge(\n 'clipboard.readText',", + ) || + !h5HostBridgeSource.includes( + 'return clipboardText ?? false;', + ) + ) { + throw new Error( + 'H5 HostBridge facade must normalize clipboard.readText results with the shared clipboard boundary', + ); + } if ( !h5HostBridgeSource.includes( 'const style = normalizeHostBridgeHapticsImpactStyle(params.style);', diff --git a/src/services/host-bridge/hostBridge.test.ts b/src/services/host-bridge/hostBridge.test.ts index fb1ef6a8f..1a2527138 100644 --- a/src/services/host-bridge/hostBridge.test.ts +++ b/src/services/host-bridge/hostBridge.test.ts @@ -4,6 +4,7 @@ import { afterEach, describe, expect, test, vi } from 'vitest'; import type { HostBridgeCapability } from '../../../packages/shared/src/contracts/hostBridge'; import { + HOST_BRIDGE_CLIPBOARD_TEXT_MAX_LENGTH, HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS, HOST_BRIDGE_SCANNER_TIMEOUT_MS, HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS, @@ -1485,6 +1486,42 @@ describe('hostBridge', () => { }); }); + test('原生 App HostBridge 读取剪贴板结果按共享清单归一化', async () => { + const oversizedText = 'a'.repeat(HOST_BRIDGE_CLIPBOARD_TEXT_MAX_LENGTH + 5); + const invoke = vi.fn( + async (_command: string, args?: Record) => { + const request = (args as { request: { id: string } }).request; + return { + bridge: 'GenarrativeHostBridge', + version: 1, + id: request.id, + ok: true, + result: { + text: + invoke.mock.calls.length === 1 + ? 42 + : oversizedText, + }, + }; + }, + ); + window.__TAURI__ = { + core: { invoke: asTauriInvoke(invoke) }, + }; + window.history.replaceState( + null, + '', + nativeAppPath(['clipboard.readText']), + ); + + await expect(readHostClipboardText()).resolves.toBe(false); + await expect(readHostClipboardText()).resolves.toEqual({ + text: oversizedText.slice(0, HOST_BRIDGE_CLIPBOARD_TEXT_MAX_LENGTH), + }); + + expect(invoke).toHaveBeenCalledTimes(2); + }); + test('原生 App 宿主不支持能力时回退到 H5 路径', async () => { window.history.replaceState(null, '', nativeAppPath()); window.__TAURI__ = { diff --git a/src/services/host-bridge/hostBridge.ts b/src/services/host-bridge/hostBridge.ts index 59c9fc2d8..ab698d07d 100644 --- a/src/services/host-bridge/hostBridge.ts +++ b/src/services/host-bridge/hostBridge.ts @@ -889,13 +889,14 @@ export async function readHostClipboardText() { } try { - return normalizeHostBridgeClipboardText( + const clipboardText = normalizeHostBridgeClipboardText( ( await requestNativeAppHostBridge( 'clipboard.readText', ) )?.text, ); + return clipboardText ?? false; } catch { return false; }