diff --git a/apps/mobile-shell/scripts/check-config.mjs b/apps/mobile-shell/scripts/check-config.mjs index f4b88056f..f6831b93b 100644 --- a/apps/mobile-shell/scripts/check-config.mjs +++ b/apps/mobile-shell/scripts/check-config.mjs @@ -1646,6 +1646,7 @@ for (const snippet of [ "throw invalidRequest('target is required')", 'const normalizedTarget = normalizeHostBridgeShareOpenPayload(target);', "'share target is invalid'", + "message: 'share unavailable'", 'ok(request, true)', 'resetMobileHostBridgeShareTargetForTest', ]) { @@ -1669,8 +1670,10 @@ for (const snippet of [ 'resetMobileHostBridgeShareTargetForTest()', 'uses cached work target when share.open has no explicit payload', 'keeps the previous cached target when a new target is missing or invalid', + 'maps native share sheet failures to stable host errors', 'does not fall back to cached target when explicit payload is unsafe', 'rejects empty share requests before opening native share sheet', + "message: 'share unavailable'", 'https://app.genarrative.world/works/detail?work=PZ-00000001', 'javascript:alert(1)', 'expect(Share.share).not.toHaveBeenCalled()', diff --git a/apps/mobile-shell/src/host-bridge/share.test.ts b/apps/mobile-shell/src/host-bridge/share.test.ts index d6f0d2479..3ee1598f0 100644 --- a/apps/mobile-shell/src/host-bridge/share.test.ts +++ b/apps/mobile-shell/src/host-bridge/share.test.ts @@ -159,6 +159,22 @@ describe('mobile share helpers', () => { }); }); + test('maps native share sheet failures to stable host errors', async () => { + vi.mocked(Share.share).mockRejectedValueOnce(new Error('native share failed')); + + await expect( + openShare( + request('share.open', { + title: '测试作品', + url: 'https://app.genarrative.world/works/detail?work=PZ-1', + }), + ), + ).rejects.toMatchObject({ + code: 'host_error', + message: 'share unavailable', + }); + }); + test.each([ 'https://example.com/works/detail?work=PZ-1', '//example.com/works/detail?work=PZ-1', diff --git a/apps/mobile-shell/src/host-bridge/share.ts b/apps/mobile-shell/src/host-bridge/share.ts index 749616897..f2793b4a1 100644 --- a/apps/mobile-shell/src/host-bridge/share.ts +++ b/apps/mobile-shell/src/host-bridge/share.ts @@ -1,6 +1,7 @@ import { Share } from 'react-native'; import { + type HostBridgeError, type HostBridgeRequest, normalizeHostBridgeShareOpenPayload, } from '../../../../packages/shared/src/contracts/hostBridge'; @@ -57,10 +58,17 @@ export async function openShare(request: HostBridgeRequest) { const url = sharePayload?.url; const message = [sharePayload?.message, url].filter(Boolean).join('\n'); - await Share.share({ - title: sharePayload?.title, - message: message || url || sharePayload?.title || '', - url, - }); + try { + await Share.share({ + title: sharePayload?.title, + message: message || url || sharePayload?.title || '', + url, + }); + } catch { + throw { + code: 'host_error', + message: 'share unavailable', + } satisfies HostBridgeError; + } return ok(request, true); }