diff --git a/package.json b/package.json index e40a6aefe..09f95c1db 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "@playwright/test": "^1.61.1", "@rollup/plugin-inject": "^5.0.5", "@rollup/plugin-wasm": "^6.2.2", - "@sableclient/sable-call-embedded": "1.1.7", + "@sableclient/sable-call-embedded": "1.1.8", "@sentry/vite-plugin": "^5.3.0", "@tauri-apps/cli": "2.11.4", "@testing-library/jest-dom": "^6.9.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 73d8ac5e0..1f1b53ebc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -279,8 +279,8 @@ importers: specifier: ^6.2.2 version: 6.2.2(rollup@4.62.0) '@sableclient/sable-call-embedded': - specifier: 1.1.7 - version: 1.1.7 + specifier: 1.1.8 + version: 1.1.8 '@sentry/vite-plugin': specifier: ^5.3.0 version: 5.3.0(rollup@4.62.0) @@ -2615,8 +2615,8 @@ packages: cpu: [x64] os: [win32] - '@sableclient/sable-call-embedded@1.1.7': - resolution: {integrity: sha512-I2GSgGSUjY7ytsurTQ7JfBAsO8ZiytniknbNVzJfbN6MQjr69YDmj/UHtB2QUqwPqUmxHVxhtqVTiUgFx0k8mA==} + '@sableclient/sable-call-embedded@1.1.8': + resolution: {integrity: sha512-+95vxaqTxh5gIrIwMX+FWH4n+K82rv/hlVIO/KIhIl2udJ2+r/GESiAElnTXa1bom1+FWrdekNvkCEEuh7m2Jw==} '@sableclient/twemoji-font@1.0.4': resolution: {integrity: sha512-LzvQB/VZtv5KEq1VYq2azr7v7cYT8oklOVRGkAN2RNwa3sF0XcnqM3lBHSYRFXQleGLSUgs3gA0slbwkD2+sJw==} @@ -7705,7 +7705,7 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.62.0': optional: true - '@sableclient/sable-call-embedded@1.1.7': {} + '@sableclient/sable-call-embedded@1.1.8': {} '@sableclient/twemoji-font@1.0.4': {} diff --git a/src/app/plugins/call/CallEmbed.intent.test.ts b/src/app/plugins/call/CallEmbed.intent.test.ts index 815505eeb..4288fc416 100644 --- a/src/app/plugins/call/CallEmbed.intent.test.ts +++ b/src/app/plugins/call/CallEmbed.intent.test.ts @@ -95,6 +95,12 @@ describe('CallEmbed.getWidget', () => { baseUrl: 'https://matrix.example.com', getSafeUserId: () => '@alice:example.com', getDeviceId: () => 'ALICEDEVICE', + matrixRTC: { + getRoomSession: () => ({ + getOldestMembership: () => undefined, + memberships: [], + }), + }, } as never; it('adds ring notification delegation for starting DM calls in non-call rooms', () => { @@ -173,4 +179,44 @@ describe('CallEmbed.getWidget', () => { expect(url.searchParams.get('parentUrl')).toBe('https://tauri.localhost'); }); + + it("forwards the ongoing call's livekit transport as livekitServiceUrl param", () => { + const oldest = {}; + const mxWithOngoingCall = { + baseUrl: 'https://matrix.example.com', + getSafeUserId: () => '@alice:example.com', + getDeviceId: () => 'ALICEDEVICE', + matrixRTC: { + getRoomSession: () => ({ + getOldestMembership: () => oldest, + memberships: [ + { + getTransport: () => ({ + type: 'livekit', + livekit_service_url: 'https://lk.example.org', + }), + }, + ], + }), + }, + } as never; + const room = createRoom(false); + const widget = CallEmbed.getWidget( + mxWithOngoingCall, + room, + ElementCallIntent.JoinExisting, + 'dark' + ); + const url = new URL(widget.getCompleteUrl({ currentUserId: '@alice:example.com' })); + + expect(url.searchParams.get('livekitServiceUrl')).toBe('https://lk.example.org'); + }); + + it('adds no livekitServiceUrl param when there is no ongoing call', () => { + const room = createRoom(false); + const widget = CallEmbed.getWidget(mx, room, ElementCallIntent.StartCallVoice, 'dark'); + const url = new URL(widget.getCompleteUrl({ currentUserId: '@alice:example.com' })); + + expect(url.searchParams.get('livekitServiceUrl')).toBeNull(); + }); }); diff --git a/src/app/plugins/call/CallEmbed.ts b/src/app/plugins/call/CallEmbed.ts index 829b06481..8ba2c2787 100644 --- a/src/app/plugins/call/CallEmbed.ts +++ b/src/app/plugins/call/CallEmbed.ts @@ -93,6 +93,18 @@ export class CallEmbed { ); } + // Widget-mode discovery can't reach a transport (no access token, iframe + // fetch restrictions), so forward the ongoing call's own transport. + private static getOngoingCallLivekitServiceUrl(mx: MatrixClient, room: Room): string | undefined { + const session = mx.matrixRTC.getRoomSession(room); + const oldest = session.getOldestMembership(); + if (!oldest) return undefined; + const transport = session.memberships.map((m) => m.getTransport(oldest)).find(Boolean); + return transport?.type === 'livekit' + ? (transport as { livekit_service_url?: string }).livekit_service_url + : undefined; + } + static getWidget( mx: MatrixClient, room: Room, @@ -123,6 +135,11 @@ export class CallEmbed { header: 'none', }); + const ongoingLivekitServiceUrl = CallEmbed.getOngoingCallLivekitServiceUrl(mx, room); + if (ongoingLivekitServiceUrl) { + params.append('livekitServiceUrl', ongoingLivekitServiceUrl); + } + if (!room.isCallRoom() && CallEmbed.startingCall(intent)) { params.append('sendNotificationType', CallEmbed.dmCall(intent) ? 'ring' : 'notification'); params.append('waitForCallPickup', CallEmbed.dmCall(intent) ? 'true' : 'false');