From d8ef351c59e5bc9149d22b1e3848128bf1b6c19b Mon Sep 17 00:00:00 2001 From: kdletters Date: Sat, 20 Jun 2026 11:58:28 +0800 Subject: [PATCH] =?UTF-8?q?=E9=94=81=E5=AE=9A=E7=A7=BB=E5=8A=A8=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E6=9D=83=E9=99=90=E8=AF=B7=E6=B1=82=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移动文件桥接把相册权限请求失败转为结构化 host_error 移动文件桥接把相机权限请求失败转为结构化 host_error 移动文件单测覆盖权限请求失败不启动原生选择器 移动壳配置检查反查权限请求失败边界 --- apps/mobile-shell/scripts/check-config.mjs | 5 +++++ .../src/host-bridge/files.test.ts | 20 +++++++++++++++++++ apps/mobile-shell/src/host-bridge/files.ts | 20 +++++++++++++++++-- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/apps/mobile-shell/scripts/check-config.mjs b/apps/mobile-shell/scripts/check-config.mjs index 4526e3b18..dbdb45c89 100644 --- a/apps/mobile-shell/scripts/check-config.mjs +++ b/apps/mobile-shell/scripts/check-config.mjs @@ -2079,9 +2079,14 @@ for (const snippet of [ "code: 'unsupported_capability'", "code: 'cancelled'", 'rejects image import before picker launch when library permission is denied', + 'rejects image import before picker launch when library permission request fails', + 'rejects image capture before camera launch when camera permission request fails', "message: 'photo library permission denied'", + "message: 'photo library permission unavailable'", 'expect(imageLibraryMock).not.toHaveBeenCalled()', "message: 'camera permission denied'", + "message: 'camera permission unavailable'", + 'expect(cameraMock).not.toHaveBeenCalled()', "mediaTypes: ['images']", "options: { encoding: 'base64' }", ]) { diff --git a/apps/mobile-shell/src/host-bridge/files.test.ts b/apps/mobile-shell/src/host-bridge/files.test.ts index 136cccaa0..080c92fd8 100644 --- a/apps/mobile-shell/src/host-bridge/files.test.ts +++ b/apps/mobile-shell/src/host-bridge/files.test.ts @@ -303,6 +303,26 @@ describe('mobile HostBridge file actions', () => { expect(imageLibraryMock).not.toHaveBeenCalled(); }); + test('rejects image import before picker launch when library permission request fails', async () => { + libraryPermissionMock.mockRejectedValueOnce(new Error('native permission failed')); + + await expect(importImageFile()).rejects.toMatchObject({ + code: 'host_error', + message: 'photo library permission unavailable', + }); + expect(imageLibraryMock).not.toHaveBeenCalled(); + }); + + test('rejects image capture before camera launch when camera permission request fails', async () => { + cameraPermissionMock.mockRejectedValueOnce(new Error('native permission failed')); + + await expect(captureImageFile()).rejects.toMatchObject({ + code: 'host_error', + message: 'camera permission unavailable', + }); + expect(cameraMock).not.toHaveBeenCalled(); + }); + test('imports audio and exports binary files through controlled payloads', async () => { fileBase64Data.set('file:///picked/voice.mp3', MP3_BASE64); fileSizes.set('file:///picked/voice.mp3', Buffer.byteLength('ID3\x04\x00\x00\x00\x00\x00\x10')); diff --git a/apps/mobile-shell/src/host-bridge/files.ts b/apps/mobile-shell/src/host-bridge/files.ts index 140a07ff4..acfa93392 100644 --- a/apps/mobile-shell/src/host-bridge/files.ts +++ b/apps/mobile-shell/src/host-bridge/files.ts @@ -270,7 +270,15 @@ export async function exportMobileHostBridgeImageFile( } export async function importImageFile(): Promise { - const permission = await ImagePicker.requestMediaLibraryPermissionsAsync(); + let permission: ImagePicker.MediaLibraryPermissionResponse; + try { + permission = await ImagePicker.requestMediaLibraryPermissionsAsync(); + } catch { + throw { + code: 'host_error', + message: 'photo library permission unavailable', + } satisfies HostBridgeError; + } if (permission.status !== ImagePicker.PermissionStatus.GRANTED) { throw { code: 'host_error', @@ -301,7 +309,15 @@ export async function importMobileHostBridgeImageFile( } export async function captureImageFile(): Promise { - const permission = await ImagePicker.requestCameraPermissionsAsync(); + let permission: ImagePicker.CameraPermissionResponse; + try { + permission = await ImagePicker.requestCameraPermissionsAsync(); + } catch { + throw { + code: 'host_error', + message: 'camera permission unavailable', + } satisfies HostBridgeError; + } if (permission.status !== ImagePicker.PermissionStatus.GRANTED) { throw { code: 'host_error',