diff --git a/apps/desktop-shell/scripts/check-config.mjs b/apps/desktop-shell/scripts/check-config.mjs index d1ffd3d7a..cf208e0d1 100644 --- a/apps/desktop-shell/scripts/check-config.mjs +++ b/apps/desktop-shell/scripts/check-config.mjs @@ -1550,6 +1550,8 @@ for (const expectedShareSnippet of [ 'DesktopSharePayload::Invalid', '"share target is invalid"', 'set_desktop_host_bridge_share_target', + 'share_text_from_value(target)', + 'share_target_payload_must_be_valid_before_cache', 'open_desktop_host_bridge_share', 'write_desktop_clipboard_text(app, &share_text)', '"copied_to_clipboard"', diff --git a/apps/desktop-shell/src-tauri/src/host_bridge/share.rs b/apps/desktop-shell/src-tauri/src/host_bridge/share.rs index 35729087a..2f51bf42a 100644 --- a/apps/desktop-shell/src-tauri/src/host_bridge/share.rs +++ b/apps/desktop-shell/src-tauri/src/host_bridge/share.rs @@ -144,6 +144,23 @@ pub(crate) fn set_desktop_host_bridge_share_target( let Some(target) = target else { return failed(request.id.clone(), "invalid_request", "target is required"); }; + match share_text_from_value(target) { + DesktopSharePayload::Valid(_) => {} + DesktopSharePayload::Invalid => { + return failed( + request.id.clone(), + "invalid_request", + "share target is invalid", + ) + } + DesktopSharePayload::Empty => { + return failed( + request.id.clone(), + "invalid_request", + "share target is required", + ) + } + } let share_state = app.state::(); let response = match share_state.target.lock() { @@ -305,6 +322,28 @@ mod tests { ); } + #[test] + fn share_target_payload_must_be_valid_before_cache() { + assert!(matches!( + share_text_from_value(&json!({})), + DesktopSharePayload::Empty + )); + assert!(matches!( + share_text_from_value(&json!({ + "title": "危险作品", + "url": "https://example.com/works/detail?work=PZ-1" + })), + DesktopSharePayload::Invalid + )); + assert!(matches!( + share_text_from_value(&json!({ + "title": "测试作品", + "work": "PZ-1" + })), + DesktopSharePayload::Valid(_) + )); + } + #[test] fn share_text_requires_payload_or_stored_target() { let state = DesktopShareState::default(); diff --git a/apps/mobile-shell/scripts/check-config.mjs b/apps/mobile-shell/scripts/check-config.mjs index 06b6d1432..2f0d41a40 100644 --- a/apps/mobile-shell/scripts/check-config.mjs +++ b/apps/mobile-shell/scripts/check-config.mjs @@ -1319,6 +1319,8 @@ for (const snippet of [ 'setMobileHostBridgeShareTarget', 'request: HostBridgeRequest', "throw invalidRequest('target is required')", + 'const normalizedTarget = normalizeHostBridgeShareOpenPayload(target);', + "'share target is invalid'", 'ok(request, true)', 'resetMobileHostBridgeShareTargetForTest', ]) { diff --git a/apps/mobile-shell/src/host-bridge/bridge.test.ts b/apps/mobile-shell/src/host-bridge/bridge.test.ts index 090dade71..2bcce7d4a 100644 --- a/apps/mobile-shell/src/host-bridge/bridge.test.ts +++ b/apps/mobile-shell/src/host-bridge/bridge.test.ts @@ -922,7 +922,7 @@ describe('handleMobileHostBridgeMessage', () => { }); }); - test('share.setTarget 拒绝缺少目标且不清空已有目标', async () => { + test('share.setTarget 拒绝缺少或无效目标且不清空已有目标', async () => { expectOk( await send( request('share.setTarget', { @@ -941,6 +941,21 @@ describe('handleMobileHostBridgeMessage', () => { expect(expectFailed(invalid).error.message).toBe('target is required'); + const empty = await send(request('share.setTarget', { target: {} })); + + expect(expectFailed(empty).error.message).toBe('share target is required'); + + const unsafe = await send( + request('share.setTarget', { + target: { + title: '危险作品', + url: 'https://example.com/works/detail?work=PZ-1', + }, + }), + ); + + expect(expectFailed(unsafe).error.message).toBe('share target is invalid'); + expectOk(await send(request('share.open'))); expect(Share.share).toHaveBeenCalledWith({ title: '暖灯猫街', diff --git a/apps/mobile-shell/src/host-bridge/share.ts b/apps/mobile-shell/src/host-bridge/share.ts index 40a917207..749616897 100644 --- a/apps/mobile-shell/src/host-bridge/share.ts +++ b/apps/mobile-shell/src/host-bridge/share.ts @@ -17,6 +17,14 @@ export function setMobileHostBridgeShareTarget(request: HostBridgeRequest) { if (target === undefined) { throw invalidRequest('target is required'); } + const normalizedTarget = normalizeHostBridgeShareOpenPayload(target); + if (normalizedTarget.status !== 'valid') { + throw invalidRequest( + normalizedTarget.status === 'invalid' + ? 'share target is invalid' + : 'share target is required', + ); + } currentShareTarget = target; return ok(request, true);