锁定移动壳剪贴板失败响应

移动壳剪贴板写入失败返回稳定错误

移动壳剪贴板读取失败返回稳定错误

移动壳协议兜底测试改用外链原生异常触发

移动壳配置检查补充剪贴板失败边界反查
This commit is contained in:
2026-06-20 14:29:35 +08:00
parent e0d519686b
commit bdee19b510
4 changed files with 56 additions and 9 deletions
+7 -1
View File
@@ -2207,7 +2207,7 @@ if (
'const clipboardText = normalizeHostBridgeClipboardText(',
) ||
!clipboardSource.includes('Clipboard.setStringAsync(clipboardText.text)') ||
!clipboardSource.includes('await Clipboard.getStringAsync()')
!clipboardSource.includes('rawText = await Clipboard.getStringAsync()')
) {
throw new Error(
'mobile shell clipboard bridge must normalize text with the shared HostBridge clipboard boundary',
@@ -2236,6 +2236,8 @@ for (const snippet of [
'writeMobileClipboardText(text)',
'readMobileHostBridgeClipboardText',
'ok(request, await readMobileClipboardText())',
"message: 'clipboard write unavailable'",
"message: 'clipboard read unavailable'",
]) {
if (!clipboardSource.includes(snippet)) {
throw new Error(`mobile shell clipboard module is missing ${snippet}`);
@@ -2245,6 +2247,10 @@ if (!clipboardTestSource.includes("'猫'.repeat(100000)")) {
throw new Error('mobile shell clipboard tests must cover Unicode truncation');
}
for (const snippet of [
'maps native clipboard write failures to stable host errors',
"message: 'clipboard write unavailable'",
'maps native clipboard read failures to stable host errors',
"message: 'clipboard read unavailable'",
'rejects unavailable clipboard text without reporting a successful empty value',
'Clipboard.getStringAsync).mockResolvedValue(undefined as never)',
"code: 'host_error'",
@@ -587,13 +587,17 @@ describe('handleMobileHostBridgeMessage', () => {
});
test('原生异常对象不会透传非协议错误码', async () => {
vi.mocked(Clipboard.getStringAsync).mockRejectedValue({
code: 'native_clipboard_failure',
message: 'native clipboard failed',
vi.mocked(Linking.canOpenURL).mockRejectedValue({
code: 'native_linking_failure',
message: 'native linking failed',
nativeStackIOS: ['private native frame'],
});
const response = await send(request('clipboard.readText'));
const response = await send(
request('app.openExternalUrl', {
url: 'https://example.com/path',
}),
);
const failedResponse = expectFailed(response);
@@ -63,6 +63,17 @@ describe('mobile clipboard helpers', () => {
expect(Clipboard.setStringAsync).not.toHaveBeenCalled();
});
test('maps native clipboard write failures to stable host errors', async () => {
vi.mocked(Clipboard.setStringAsync).mockRejectedValueOnce(
new Error('native clipboard write failed'),
);
await expect(writeMobileClipboardText('作品号 PZ-1')).rejects.toMatchObject({
code: 'host_error',
message: 'clipboard write unavailable',
});
});
test('wraps HostBridge clipboard write responses', async () => {
const response = await writeMobileHostBridgeClipboardText(
request('clipboard.writeText', {
@@ -128,6 +139,17 @@ describe('mobile clipboard helpers', () => {
});
});
test('maps native clipboard read failures to stable host errors', async () => {
vi.mocked(Clipboard.getStringAsync).mockRejectedValueOnce(
new Error('native clipboard read failed'),
);
await expect(readMobileClipboardText()).rejects.toMatchObject({
code: 'host_error',
message: 'clipboard read unavailable',
});
});
test('wraps HostBridge clipboard read responses', async () => {
vi.mocked(Clipboard.getStringAsync).mockResolvedValue('作品号 PZ-1');
+19 -4
View File
@@ -15,7 +15,14 @@ export async function writeMobileClipboardText(rawText: unknown) {
return false;
}
await Clipboard.setStringAsync(clipboardText.text);
try {
await Clipboard.setStringAsync(clipboardText.text);
} catch {
throw {
code: 'host_error',
message: 'clipboard write unavailable',
} satisfies HostBridgeError;
}
return true;
}
@@ -37,9 +44,17 @@ export async function readMobileHostBridgeClipboardText(
}
export async function readMobileClipboardText(): Promise<ClipboardReadTextResult> {
const result = normalizeHostBridgeClipboardText(
await Clipboard.getStringAsync(),
);
let rawText: unknown;
try {
rawText = await Clipboard.getStringAsync();
} catch {
throw {
code: 'host_error',
message: 'clipboard read unavailable',
} satisfies HostBridgeError;
}
const result = normalizeHostBridgeClipboardText(rawText);
if (!result) {
throw {
code: 'host_error',