From 8a06976494c75ff410702bcf5a2924da4be38534 Mon Sep 17 00:00:00 2001 From: Robin Date: Sat, 3 Oct 2026 01:27:05 +0200 Subject: [PATCH] WIP --- src/livekit/auth/csApi.ts | 11 +- src/livekit/auth/index.ts | 79 ++++++------- src/livekit/auth/openID.test.ts | 4 +- src/livekit/auth/openID.ts | 110 ++++++++---------- src/room/CallEventAudioRenderer.test.tsx | 4 +- src/state/CallViewModel/CallViewModel.test.ts | 4 +- src/state/CallViewModel/CallViewModel.ts | 12 +- .../localMember/LocalMember.test.ts | 8 +- .../CallViewModel/localMember/LocalMember.ts | 16 ++- .../localMember/LocalTransport.ts | 83 ++++--------- .../RtcTransportAutoDiscovery.test.ts | 6 +- .../localMember/RtcTransportAutoDiscovery.ts | 12 +- .../remoteMembers/Connection.test.ts | 12 +- .../CallViewModel/remoteMembers/Connection.ts | 54 ++++----- .../remoteMembers/ConnectionFactory.ts | 10 +- .../remoteMembers/ConnectionManager.test.ts | 26 ++--- .../remoteMembers/ConnectionManager.ts | 20 ++-- .../remoteMembers/ECConnectionFactory.test.ts | 4 +- .../MatrixLivekitMembers.test.ts | 10 +- .../remoteMembers/MatrixLivekitMembers.ts | 6 +- .../remoteMembers/integration.test.ts | 20 ++-- src/state/SessionBehaviors.ts | 14 +-- src/utils/test.ts | 4 +- 23 files changed, 232 insertions(+), 297 deletions(-) diff --git a/src/livekit/auth/csApi.ts b/src/livekit/auth/csApi.ts index 728201e86..ec82490b7 100644 --- a/src/livekit/auth/csApi.ts +++ b/src/livekit/auth/csApi.ts @@ -8,7 +8,6 @@ Please see LICENSE in the repository root for full details. import { type MatrixClient } from "matrix-js-sdk"; import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; -import { type OpenIDClientParts } from "./openID.ts"; import { extractFullConfigFromToken, type SFUConfig } from "./types.ts"; import { doNetworkOperationWithRetry } from "../../utils/matrix.ts"; @@ -16,8 +15,10 @@ import { doNetworkOperationWithRetry } from "../../utils/matrix.ts"; // of being hardcoded here. (the legacy flow hardcodes it as well) const SLOT_ID = "m.call#ROOM"; -export type ClientGetTokenParts = OpenIDClientParts & - Pick; +export type ClientGetTokenParts = Pick< + MatrixClient, + "_unstable_getLivekitToken" +>; export async function getSFUConfig({ client, @@ -35,7 +36,7 @@ export async function getSFUConfig({ url, room_id: roomId, slot_id: "m.call#ROOM", - member: { id: membership.memberId }, + member_id: membership.memberId, }), ); return extractFullConfigFromToken({ url, jwt: res.jwt }); @@ -64,7 +65,7 @@ export async function delegateDelayedLeave({ url, room_id: roomId, slot_id: SLOT_ID, - member: { id: membership.memberId }, + member_id: membership.memberId, delay_id: delayId, }), ); diff --git a/src/livekit/auth/index.ts b/src/livekit/auth/index.ts index 9af2bb9fb..511c8e004 100644 --- a/src/livekit/auth/index.ts +++ b/src/livekit/auth/index.ts @@ -8,20 +8,16 @@ Please see LICENSE in the repository root for full details. import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; import { type Logger } from "matrix-js-sdk/lib/logger"; -import { - getSFUConfig as getSFUConfigWithOpenID, - type OpenIDClientParts, -} from "./openID"; -import { - type ClientDelegationParts, - delegateDelayedLeave as delegateDelayedLeaveWithCSApi, - type ClientGetTokenParts, - getSFUConfig as getSFUConfigWithCSApi, -} from "./csApi"; -import { type MatrixRTCMode } from "../../config/ConfigOptions"; import { MatrixError } from "matrix-js-sdk"; -import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; +import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; +import * as openID from "./openID"; +import * as csApi from "./csApi"; import { type SFUConfig } from "./types"; +import { NoMatrix2AuthorizationService } from "../../utils/errors"; + +export { ClientOpenIDParts } from "./openID"; +export { ClientGetTokenParts, ClientDelegationParts } from "./csApi"; +export * from "./types"; /** * Checks whether an error means "this homeserver does not implement the @@ -46,19 +42,19 @@ export async function getSFUConfig({ membership, transport, roomId, - matrixRTCMode, + role, logger, }: { - client: ClientGetTokenParts & OpenIDClientParts; + client: csApi.ClientGetTokenParts & openID.ClientOpenIDParts; membership: CallMembershipIdentityParts; - transport: LivekitTransport; + transport: UnstableLivekitTransport; roomId: string; - matrixRTCMode: MatrixRTCMode; + role: "publisher" | "subscriber"; logger: Logger; }): Promise { if ("url" in transport) { try { - return await getSFUConfigWithCSApi({ + return await csApi.getSFUConfig({ client, membership, url: transport.url, @@ -66,19 +62,18 @@ export async function getSFUConfig({ }); } catch (e) { if (isEndpointUnsupported(e)) { - // Alias transport before checking whether we can fall back, otherwise - // TypeScript gets confused about its type - const transport_ = transport; - if ("livekit_service_url" in transport_) { + const mayFallBack = + role === "subscriber" && "livekit_service_url" in transport; + if (mayFallBack) { logger.warn( - `Homeserver does not support the MSC4195 get_token endpoint, falling back to the OpenID flow with service ${transport_.livekit_service_url}`, + `Homeserver does not support the MSC4195 get_token endpoint, falling back to the OpenID flow with service ${transport.livekit_service_url}`, ); // Fall through } else { logger.error( `Homeserver does not support the MSC4195 get_token endpoint, cannot get token for transport ${transport.url}`, ); - throw e; + throw new NoMatrix2AuthorizationService(e as Error); } } else { throw e; @@ -86,18 +81,14 @@ export async function getSFUConfig({ } } - if ("livekit_service_url" in transport) { - return await getSFUConfigWithOpenID( - client, - membership, - transport.livekit_service_url, - roomId, - { matrixRTCMode }, - logger, - ); - } - - throw new Error(`Unrecognized transport: ${JSON.stringify(transport)}`); + return await openID.getSFUConfig({ + client, + membership, + serviceUrl: transport.livekit_service_url, + roomId, + role, + logger, + }); } export async function delegateDelayedLeave({ @@ -106,20 +97,18 @@ export async function delegateDelayedLeave({ transport, roomId, delayId, - matrixRTCMode, logger, }: { - client: ClientDelegationParts & OpenIDClientParts; + client: csApi.ClientDelegationParts & openID.ClientOpenIDParts; membership: CallMembershipIdentityParts; - transport: LivekitTransport; + transport: UnstableLivekitTransport; roomId: string; delayId: string; - matrixRTCMode: MatrixRTCMode; logger: Logger; }): Promise { if ("url" in transport) { try { - await delegateDelayedLeaveWithCSApi({ + await csApi.delegateDelayedLeave({ client, membership, url: transport.url, @@ -136,13 +125,15 @@ export async function delegateDelayedLeave({ } else { // This will technically cause the service to issue a new JWT token, but // it's safe to discard. We're only interested in triggering delegation. - await getSFUConfigWithOpenID( + await openID.getSFUConfig({ client, membership, - transport.livekit_service_url, + serviceUrl: transport.livekit_service_url, roomId, - { matrixRTCMode, delayEndpointBaseUrl: client.baseUrl, delayId }, + role: "publisher", + delayEndpointBaseUrl: client.baseUrl, + delayId, logger, - ); + }); } } diff --git a/src/livekit/auth/openID.test.ts b/src/livekit/auth/openID.test.ts index f759a3aac..2b500fc4f 100644 --- a/src/livekit/auth/openID.test.ts +++ b/src/livekit/auth/openID.test.ts @@ -17,7 +17,7 @@ import { import fetchMock from "fetch-mock"; import { MatrixError } from "matrix-js-sdk"; -import { getSFUConfigWithOpenID, type OpenIDClientParts } from "./openIDSFU"; +import { getSFUConfigWithOpenID, type ClientOpenIDParts } from "./openIDSFU"; import { testJWTToken } from "../utils/test-fixtures"; import { ownMemberMock } from "../utils/test"; import { FailToGetOpenIdToken } from "../utils/errors"; @@ -25,7 +25,7 @@ import { FailToGetOpenIdToken } from "../utils/errors"; const sfuUrl = "https://sfu.example.org"; describe("getSFUConfigWithOpenID", () => { - let matrixClient: MockedObject; + let matrixClient: MockedObject; beforeEach(() => { fetchMock.catch(404); matrixClient = { diff --git a/src/livekit/auth/openID.ts b/src/livekit/auth/openID.ts index a442613c8..93fdf31d7 100644 --- a/src/livekit/auth/openID.ts +++ b/src/livekit/auth/openID.ts @@ -13,17 +13,13 @@ import { import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; import { type Logger } from "matrix-js-sdk/lib/logger"; -import { - FailToGetOpenIdToken, - NoMatrix2AuthorizationService, -} from "../../utils/errors"; +import { FailToGetOpenIdToken } from "../../utils/errors"; import { doNetworkOperationWithRetry } from "../../utils/matrix"; import { Config } from "../../config/Config"; -import { MatrixRTCMode } from "../../config/ConfigOptions"; import { extractFullConfigFromToken, type SFUConfig } from "./types"; // The bits we need from MatrixClient -export type OpenIDClientParts = Pick< +export type ClientOpenIDParts = Pick< MatrixClient, "getOpenIdToken" | "getDeviceId" >; @@ -47,18 +43,25 @@ export type OpenIDClientParts = Pick< * @returns Object containing the token information * @throws FailToGetOpenIdToken */ -export async function getSFUConfig( - client: OpenIDClientParts, - membership: CallMembershipIdentityParts, - serviceUrl: string, - roomId: string, - opts?: { - matrixRTCMode?: MatrixRTCMode; - delayEndpointBaseUrl?: string; - delayId?: string; - }, - logger?: Logger, -): Promise { +export async function getSFUConfig({ + client, + membership, + serviceUrl, + roomId, + role, + delayEndpointBaseUrl, + delayId, + logger, +}: { + client: ClientOpenIDParts; + membership: CallMembershipIdentityParts; + serviceUrl: string; + roomId: string; + role: "publisher" | "subscriber"; + delayEndpointBaseUrl?: string; + delayId?: string; + logger: Logger; +}): Promise { let openIdToken: IOpenIDToken; try { openIdToken = await doNetworkOperationWithRetry(async () => @@ -69,65 +72,50 @@ export async function getSFUConfig( error instanceof Error ? error : new Error("Unknown error"), ); } - logger?.debug("Got openID token", openIdToken); + logger.debug("Got openID token", openIdToken); let sfuConfig: { url: string; jwt: string } | undefined; - const tryBothJwtEndpoints = opts?.matrixRTCMode === undefined; // This is for SFUs where we do not publish. - - const forceMatrix2Jwt = opts?.matrixRTCMode === MatrixRTCMode.Matrix_2_0; - - // We want to start using the new endpoint (with optional delay delegation) - // if we can use both or if we are forced to use the new one. - if (tryBothJwtEndpoints || forceMatrix2Jwt) { - try { - logger?.info( - `Trying to get JWT via default endpoint for focus ${serviceUrl}...`, - ); - const sfuConfig = await getLiveKitJWT( - membership, - serviceUrl, - roomId, - openIdToken, - opts?.delayEndpointBaseUrl, - opts?.delayId, - ); - - return extractFullConfigFromToken(sfuConfig); - } catch (e) { - logger?.debug(`Failed fetching jwt with matrix 2.0 endpoint:`, e); - // Make this throw a hard error in case we force the matrix2.0 endpoint. - if (forceMatrix2Jwt) { - throw new NoMatrix2AuthorizationService(e as Error); - } - } + let endpoint: "default" | "legacy"; + switch (role) { + case "publisher": + // When publishing a legacy transport (one with a `livekit_service_url`), + // subscribers will expect our participant identity to use the legacy + // `@user_id:device_id` format. Only the legacy JWT service endpoint + // assigns identities in this format, so we must use it. + endpoint = "legacy"; + break; + case "subscriber": + // Use the default endpoint as it's more likely to allow remote access. + endpoint = "default"; + break; } - // DEPRECATED - // here we either have a sfuConfig or we already exited because of `if (forceMatrix2) throw ...` - // The only case we can get into this condition is, if `forceMatrix2` is `false` try { - logger?.info( - `Trying to get JWT with legacy endpoint for focus ${serviceUrl}...`, + logger.info( + `Trying to get JWT with ${endpoint} endpoint for focus ${serviceUrl}...`, ); - sfuConfig = await getLiveKitJWTLegacy( - membership.deviceId, + sfuConfig = await ( + endpoint === "legacy" ? getLiveKitJWTLegacy : getLiveKitJWT + )( + membership, serviceUrl, roomId, openIdToken, - opts?.delayEndpointBaseUrl, - opts?.delayId, + delayEndpointBaseUrl, + delayId, ); - logger?.info(`Got JWT from call's active focus URL.`); + logger.info(`Got JWT from call's active focus URL.`); return extractFullConfigFromToken(sfuConfig); - } catch (ex) { + } catch (e) { + logger.error(`Failed fetching jwt with ${endpoint} endpoint:`, e); throw new FailToGetOpenIdToken( - ex instanceof Error ? ex : new Error(`Unknown error ${ex}`), + e instanceof Error ? e : new Error(`Unknown error ${e}`), ); } } async function getLiveKitJWTLegacy( - deviceId: string, + membership: CallMembershipIdentityParts, livekitServiceURL: string, matrixRoomId: string, openIDToken: IOpenIDToken, @@ -161,7 +149,7 @@ async function getLiveKitJWTLegacy( // However, the livekit room alias is provided as part of the JWT payload. room: matrixRoomId, openid_token: openIDToken, - device_id: deviceId, + device_id: membership.deviceId, ...delayParts, }), }); diff --git a/src/room/CallEventAudioRenderer.test.tsx b/src/room/CallEventAudioRenderer.test.tsx index 9ab64dfd6..1d8fd33d2 100644 --- a/src/room/CallEventAudioRenderer.test.tsx +++ b/src/room/CallEventAudioRenderer.test.tsx @@ -17,7 +17,7 @@ import { import { act } from "react"; import { type RoomMember } from "matrix-js-sdk"; import { - type LivekitTransport, + type UnstableLivekitTransport, type CallMembership, } from "matrix-js-sdk/lib/matrixrtc"; @@ -46,7 +46,7 @@ vitest.mock("../useAudioContext"); vitest.mock("../soundUtils"); vitest.mock("../rtcSessionHelpers", async (importOriginal) => ({ ...(await importOriginal()), - makeTransport: (): [LivekitTransport] => [exampleTransport], + makeTransport: (): [UnstableLivekitTransport] => [exampleTransport], })); afterEach(() => { diff --git a/src/state/CallViewModel/CallViewModel.test.ts b/src/state/CallViewModel/CallViewModel.test.ts index c7783d152..1b1c38d4a 100644 --- a/src/state/CallViewModel/CallViewModel.test.ts +++ b/src/state/CallViewModel/CallViewModel.test.ts @@ -31,7 +31,7 @@ import { type CallMembership, type IRTCNotificationContent, MatrixRTCSessionEvent, - type LivekitTransport, + type UnstableLivekitTransport, } from "matrix-js-sdk/lib/matrixrtc"; import { deepCompare } from "matrix-js-sdk/lib/utils"; @@ -97,7 +97,7 @@ vi.mock( "../state/CallViewModel/localMember/localTransport", async (importOriginal) => ({ ...(await importOriginal()), - makeTransport: async (): Promise => + makeTransport: async (): Promise => Promise.resolve(exampleTransport), }), ); diff --git a/src/state/CallViewModel/CallViewModel.ts b/src/state/CallViewModel/CallViewModel.ts index ea7f3cc78..c085def87 100644 --- a/src/state/CallViewModel/CallViewModel.ts +++ b/src/state/CallViewModel/CallViewModel.ts @@ -43,7 +43,7 @@ import { import { type Logger, logger as rootLogger } from "matrix-js-sdk/lib/logger"; import { MembershipManagerEvent, - type LivekitTransport, + type UnstableLivekitTransport, type MatrixRTCSession, type RTCCallIntent, type RTCNotificationType, @@ -568,6 +568,8 @@ export function createCallViewModel$( // membership advertises `${userId}:${deviceId}` as its `membershipID` // instead, so a uuid there names a member no peer can resolve. memberId: + // TODO: Make this conditional on the transport instead + // or else avoid using it in key messages matrixRTCMode === MatrixRTCMode.Matrix_2_0 ? uuidv4() : `${userId}:${deviceId}`, @@ -580,7 +582,6 @@ export function createCallViewModel$( ownMembershipIdentity, client, roomId: matrixRoom.roomId, - matrixRTCMode, }), ); @@ -624,7 +625,7 @@ export function createCallViewModel$( ), muteStates, joinMatrixRTC: ( - transport: LivekitTransport, + transport: UnstableLivekitTransport, delayedLeaveTimings: ResolvedDelayedLeaveTimings, ) => { return enterRTCSession( @@ -646,9 +647,7 @@ export function createCallViewModel$( mediaDevices, muteStates, trackProcessorState$, - logger.getChild( - "[Publisher " + connection.transport.livekit_service_url + "]", - ), + logger.getChild(`[Publisher ${JSON.stringify(connection.transport)}]`), controlledAudioDevices, ); }, @@ -671,7 +670,6 @@ export function createCallViewModel$( ).pipe(map(([delayId]) => delayId ?? null)), matrixRTCSession.delayId ?? null, ), - matrixRTCMode, logger: logger.getChild(`[${Date.now()}]`), }); diff --git a/src/state/CallViewModel/localMember/LocalMember.test.ts b/src/state/CallViewModel/localMember/LocalMember.test.ts index 282759fdd..fe4632d39 100644 --- a/src/state/CallViewModel/localMember/LocalMember.test.ts +++ b/src/state/CallViewModel/localMember/LocalMember.test.ts @@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details. import { Status as RTCMemberStatus, - type LivekitTransport, + type UnstableLivekitTransport, type MatrixRTCSession, } from "matrix-js-sdk/lib/matrixrtc"; import { @@ -121,7 +121,7 @@ const mockedClient = { }; describe("enterRTCSession", () => { - const transport: LivekitTransport = { + const transport: UnstableLivekitTransport = { livekit_service_url: "http://my-livekit-service-url.com", type: "livekit", }; @@ -252,7 +252,7 @@ describe("LocalMembership", () => { it("throws error on missing RTC config error", () => { withTestScheduler(({ scope, hot, expectObservable }) => { - const localTransport$ = scope.behavior( + const localTransport$ = scope.behavior( hot("1ms #", {}, new MatrixRTCTransportMissingError("domain.com")), null, ); @@ -317,7 +317,7 @@ describe("LocalMembership", () => { const mockTransportConfig = { livekit_service_url: "a", - } as LivekitTransport; + } as UnstableLivekitTransport; const mockTransport = { transport: mockTransportConfig, diff --git a/src/state/CallViewModel/localMember/LocalMember.ts b/src/state/CallViewModel/localMember/LocalMember.ts index 08dcc7549..b0f6ccc74 100644 --- a/src/state/CallViewModel/localMember/LocalMember.ts +++ b/src/state/CallViewModel/localMember/LocalMember.ts @@ -17,7 +17,7 @@ import { import { observeParticipantEvents } from "@livekit/components-core"; import { Status as RTCSessionStatus, - type LivekitTransport, + type UnstableLivekitTransport, type MatrixRTCSession, type RTCCallIntent, type RTCNotificationType, @@ -82,7 +82,8 @@ import { type LocalTransport } from "./LocalTransport.ts"; import { type ClientDelegationParts, delegateDelayedLeave, -} from "../../../livekit/auth/index.ts"; + type ClientOpenIDParts, +} from "../../../livekit/auth"; export enum TransportState { /** Not even a transport is available to the LocalMembership */ @@ -145,14 +146,14 @@ interface Props { connectionManager: IConnectionManager; createPublisherFactory: (connection: Connection) => Publisher; joinMatrixRTC: ( - transport: LivekitTransport, + transport: UnstableLivekitTransport, delayedLeaveTimings: ResolvedDelayedLeaveTimings, ) => void; homeserverConnected: HomeserverConnected; roomId: string; ownMembershipIdentity: CallMembershipIdentityParts; localTransport$: Observable; - client: ClientDelegationParts; + client: ClientDelegationParts & ClientOpenIDParts; matrixRTCSession: Pick; /** Whether to hide the screen-sharing button. */ hideScreensharing: boolean; @@ -160,7 +161,6 @@ interface Props { hostBridge: HostBridge; baseUrl: string; delayId$: Behavior; - matrixRTCMode: MatrixRTCMode; logger: Logger; } @@ -208,7 +208,6 @@ export const createLocalMembership$ = ({ hostBridge, ownMembershipIdentity, delayId$, - matrixRTCMode, }: Props): { /** * This request to start audio and video tracks. @@ -714,7 +713,6 @@ export const createLocalMembership$ = ({ transport: joinParams.transport, roomId, delayId, - matrixRTCMode, logger, }); logger.info("Delayed leave successfully delegated"); @@ -953,7 +951,7 @@ interface EnterRTCSessionOptions { * * @param rtcSession - The MatrixRTCSession to join. * @param ownMembershipIdentity - Options for entering the RTC session. - * @param transport - The LivekitTransport to use for this session. + * @param transport - The UnstableLivekitTransport to use for this session. * @param options - `encryptMedia`: Whether to encrypt media. `matrixRTCMode`: The * Matrix RTC mode to use. `delayedLeaveTimings`: The preferred timings for * delayed leave events. `sendNotificationType`: Whether and what kind of @@ -964,7 +962,7 @@ interface EnterRTCSessionOptions { export function enterRTCSession( rtcSession: MatrixRTCSession, ownMembershipIdentity: CallMembershipIdentityParts, - transport: LivekitTransport, + transport: UnstableLivekitTransport, { encryptMedia, matrixRTCMode, diff --git a/src/state/CallViewModel/localMember/LocalTransport.ts b/src/state/CallViewModel/localMember/LocalTransport.ts index 3534b3fec..07e2e743f 100644 --- a/src/state/CallViewModel/localMember/LocalTransport.ts +++ b/src/state/CallViewModel/localMember/LocalTransport.ts @@ -5,9 +5,9 @@ SPDX-License-IdFentifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ -import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; +import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; import { type MatrixClient } from "matrix-js-sdk"; -import { logger as rootLogger, type Logger } from "matrix-js-sdk/lib/logger"; +import { logger as rootLogger } from "matrix-js-sdk/lib/logger"; import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; import { Config } from "../../../config/Config.ts"; @@ -16,31 +16,31 @@ import { MatrixRTCTransportMissingError, NoMatrix2AuthorizationService, } from "../../../utils/errors.ts"; -import { - getSFUConfigWithOpenID, - type SFUConfig, - type OpenIDClientParts, -} from "../../../livekit/openIDSFU.ts"; import { customLivekitUrl } from "../../../settings/settings.ts"; import { RtcTransportAutoDiscovery } from "./RtcTransportAutoDiscovery.ts"; -import { type MatrixRTCMode } from "../../../config/ConfigOptions.ts"; +import { + getSFUConfig, + type ClientGetTokenParts, + type ClientOpenIDParts, + type SFUConfig, +} from "../../../livekit/auth"; interface Props { ownMembershipIdentity: CallMembershipIdentityParts; client: Pick & - OpenIDClientParts; + ClientGetTokenParts & + ClientOpenIDParts; // Used by the jwt service to create the livekit room and compute the livekit alias. roomId: string; - matrixRTCMode: MatrixRTCMode; } export interface LocalTransport { - transport: LivekitTransport; + transport: UnstableLivekitTransport; sfuConfig: SFUConfig; } export function isLocalTransport( - obj: LivekitTransport | LocalTransport, + obj: UnstableLivekitTransport | LocalTransport, ): obj is LocalTransport { return "transport" in obj && "sfuConfig" in obj; } @@ -58,7 +58,6 @@ export async function getLocalTransport({ ownMembershipIdentity, client, roomId, - matrixRTCMode, }: Props): Promise { const logger = rootLogger.getChild("[LocalTransport]"); const discovery = new RtcTransportAutoDiscovery({ @@ -69,7 +68,7 @@ export async function getLocalTransport({ const customUrl = customLivekitUrl.value$.value; // Respect the user's custom URL, if set - const transport: LivekitTransport | null = customUrl + const transport: UnstableLivekitTransport | null = customUrl ? { type: "livekit", livekit_service_url: customUrl } : await discovery.discoverPreferredTransport(); @@ -77,60 +76,26 @@ export async function getLocalTransport({ throw new MatrixRTCTransportMissingError(client.getDomain() ?? ""); try { - return await doOpenIdAndJWTFromUrl( + return { transport, - matrixRTCMode, - ownMembershipIdentity, - roomId, - client, - logger, - ); + sfuConfig: await getSFUConfig({ + client, + membership: ownMembershipIdentity, + transport, + roomId, + role: "publisher", + logger, + }), + }; } catch (e) { logger.error( - `Failed to authenticate to transport ${transport.livekit_service_url}`, + `Failed to authenticate to transport ${JSON.stringify(transport)}`, e, ); throw mapAuthErrorToUserFriendlyError(e); } } -/** - * Utility to ensure the user can authenticate with the SFU. - * We will call `getSFUConfigWithOpenID` once per transport here as it's our - * only mechanism of validation. This means we will also ask the - * homeserver for a OpenID token a few times. Since OpenID tokens are single - * use we don't want to risk any issues by re-using a token. - * - * @param transport The transport to authenticate with. - * @param matrixRTCMode Whether to force the JWT endpoint to be used. - * @param membership The identity of the local member. - * @param roomId The room ID to use for the JWT. - * @param client The client to use for the OpenID token. - * - * @throws FailToGetOpenIdToken, NoMatrix2AuthorizationService - */ -async function doOpenIdAndJWTFromUrl( - transport: LivekitTransport, - matrixRTCMode: MatrixRTCMode, - membership: CallMembershipIdentityParts, - roomId: string, - client: Pick & OpenIDClientParts, - logger?: Logger, -): Promise { - const sfuConfig = await getSFUConfigWithOpenID( - client, - membership, - transport.livekit_service_url, - roomId, - { matrixRTCMode }, - logger, - ); - return { - transport, - sfuConfig, - }; -} - function mapAuthErrorToUserFriendlyError(e: unknown): Error { if ( e instanceof FailToGetOpenIdToken || diff --git a/src/state/CallViewModel/localMember/RtcTransportAutoDiscovery.test.ts b/src/state/CallViewModel/localMember/RtcTransportAutoDiscovery.test.ts index 61df88a73..d3f246c97 100644 --- a/src/state/CallViewModel/localMember/RtcTransportAutoDiscovery.test.ts +++ b/src/state/CallViewModel/localMember/RtcTransportAutoDiscovery.test.ts @@ -16,7 +16,7 @@ import { import { MatrixError } from "matrix-js-sdk"; import { logger as rootLogger } from "matrix-js-sdk/lib/logger"; import { - type LivekitTransport, + type UnstableLivekitTransport, type Transport, } from "matrix-js-sdk/lib/matrixrtc"; @@ -28,12 +28,12 @@ import { type DiscoveryClient = RtcTransportAutoDiscoveryProps["client"]; -const backendTransport: LivekitTransport = { +const backendTransport: UnstableLivekitTransport = { type: "livekit", livekit_service_url: "https://backend.example.org", }; -const configTransport: LivekitTransport = { +const configTransport: UnstableLivekitTransport = { type: "livekit", livekit_service_url: "https://config.example.org", }; diff --git a/src/state/CallViewModel/localMember/RtcTransportAutoDiscovery.ts b/src/state/CallViewModel/localMember/RtcTransportAutoDiscovery.ts index aeb6b051a..f61a1b20b 100644 --- a/src/state/CallViewModel/localMember/RtcTransportAutoDiscovery.ts +++ b/src/state/CallViewModel/localMember/RtcTransportAutoDiscovery.ts @@ -5,8 +5,8 @@ SPDX-License-IdFentifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ import { - isLivekitTransport, - type LivekitTransport, + isUnstableLivekitTransport, + type UnstableLivekitTransport, } from "matrix-js-sdk/lib/matrixrtc"; import { type MatrixClient } from "matrix-js-sdk"; import { type Logger } from "matrix-js-sdk/lib/logger"; @@ -40,7 +40,7 @@ export class RtcTransportAutoDiscovery { this.logger = logger.getChild("[RtcTransportAutoDiscovery]"); } - public async discoverPreferredTransport(): Promise { + public async discoverPreferredTransport(): Promise { // 1) backend transports const backendTransport = await this.tryBackendTransports(); if (backendTransport) { @@ -67,7 +67,7 @@ export class RtcTransportAutoDiscovery { * This will not throw errors, but instead just log them and return null if the expected config is not found or malformed. * @private */ - private async tryBackendTransports(): Promise { + private async tryBackendTransports(): Promise { const client = this.client; // MSC4143: Attempt to fetch transports from backend. this.logger.info("First try to use getRTCTransports end point ..."); @@ -75,7 +75,7 @@ export class RtcTransportAutoDiscovery { const transportList = await doNetworkOperationWithRetry(async () => client._unstable_getRTCTransports(), ); - const first = transportList.find(isLivekitTransport); + const first = transportList.find(isUnstableLivekitTransport); if (first) { return first; } else { @@ -90,7 +90,7 @@ export class RtcTransportAutoDiscovery { return null; } - private tryConfigTransport(): LivekitTransport | null { + private tryConfigTransport(): UnstableLivekitTransport | null { const url = this.resolvedConfig.livekit?.livekit_service_url; if (url) { return { diff --git a/src/state/CallViewModel/remoteMembers/Connection.test.ts b/src/state/CallViewModel/remoteMembers/Connection.test.ts index 62dc17a31..139786567 100644 --- a/src/state/CallViewModel/remoteMembers/Connection.test.ts +++ b/src/state/CallViewModel/remoteMembers/Connection.test.ts @@ -30,7 +30,7 @@ import fetchMock from "fetch-mock"; import EventEmitter from "events"; import { type IOpenIDToken } from "matrix-js-sdk"; import { logger, type Logger } from "matrix-js-sdk/lib/logger"; -import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; +import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; import { Connection, @@ -38,7 +38,7 @@ import { type ConnectionOpts, } from "./Connection.ts"; import { ObservableScope } from "../../ObservableScope.ts"; -import { type OpenIDClientParts } from "../../../livekit/openIDSFU.ts"; +import { type ClientOpenIDParts } from "../../../livekit/openIDSFU.ts"; import { ElementCallError, FailToGetOpenIdToken, @@ -48,7 +48,7 @@ import { mockRemoteParticipant, ownMemberMock } from "../../../utils/test.ts"; let testScope: ObservableScope; -let client: MockedObject; +let client: MockedObject; let fakeLivekitRoom: MockedObject; @@ -57,14 +57,14 @@ let fakeLocalParticipant: MockedObject; const ROOM_ID = "!roomID:example.org"; -const livekitFocus: LivekitTransport = { +const livekitFocus: UnstableLivekitTransport = { livekit_service_url: "https://matrix-rtc.example.org/livekit/jwt", type: "livekit", }; function setupTest(): void { testScope = new ObservableScope(); - client = vi.mocked({ + client = vi.mocked({ getOpenIdToken: vi.fn().mockResolvedValue({ access_token: "rYsmGUEwNjKgJYyeNUkZseJN", token_type: "Bearer", @@ -72,7 +72,7 @@ function setupTest(): void { expires_in: 3600, }), getDeviceId: vi.fn().mockReturnValue("ABCDEF"), - } as unknown as OpenIDClientParts); + } as unknown as ClientOpenIDParts); localParticipantEventEmiter = new EventEmitter(); diff --git a/src/state/CallViewModel/remoteMembers/Connection.ts b/src/state/CallViewModel/remoteMembers/Connection.ts index cc84142d9..3329dfe5a 100644 --- a/src/state/CallViewModel/remoteMembers/Connection.ts +++ b/src/state/CallViewModel/remoteMembers/Connection.ts @@ -22,16 +22,11 @@ import { type Track, type TrackPublication, } from "livekit-client"; -import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; +import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; import { BehaviorSubject, map } from "rxjs"; import { type Logger } from "matrix-js-sdk/lib/logger"; import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; -import { - getSFUConfigWithOpenID, - type OpenIDClientParts, - type SFUConfig, -} from "../../../livekit/openIDSFU.ts"; import { type Behavior } from "../../Behavior.ts"; import { type ObservableScope } from "../../ObservableScope.ts"; import { @@ -42,6 +37,12 @@ import { SFURoomCreationRestrictedError, UnknownCallError, } from "../../../utils/errors.ts"; +import { + type ClientGetTokenParts, + getSFUConfig, + type ClientOpenIDParts, + type SFUConfig, +} from "../../../livekit/auth"; export interface ConnectionOpts { /** @@ -52,9 +53,9 @@ export interface ConnectionOpts { /** The identity parts to use on this connection */ ownMembershipIdentity: CallMembershipIdentityParts; /** The media transport to connect to. */ - transport: LivekitTransport; + transport: UnstableLivekitTransport; /** The Matrix client to use for OpenID and SFU config requests. */ - client: OpenIDClientParts; + client: ClientGetTokenParts & ClientOpenIDParts; /** The room ID this connection is associated with. */ roomId: string; /** The observable scope to use for this connection. */ @@ -107,7 +108,7 @@ export class Connection { /** * The media transport to connect to. */ - public readonly transport: LivekitTransport; + public readonly transport: UnstableLivekitTransport; public readonly livekitRoom: LivekitRoom; @@ -137,7 +138,7 @@ export class Connection { protected stopped = false; // TODO: can we just keep the ConnectionOpts object instead of spreading? - private readonly client: OpenIDClientParts; + private readonly client: ClientGetTokenParts & ClientOpenIDParts; private readonly roomId: string; private readonly logger: Logger; private readonly ownMembershipIdentity: CallMembershipIdentityParts; @@ -154,10 +155,10 @@ export class Connection { this.existingSFUConfig = opts.existingSFUConfig; this.roomId = opts.roomId; this.logger = logger.getChild( - "[Connection " + opts.transport.livekit_service_url + "]", + `[Connection ${JSON.stringify(opts.transport)}]`, ); this.logger.info( - `constructor: ${opts.transport.livekit_service_url} roomId: ${this.roomId} withSfuConfig?: ${opts.existingSFUConfig ? JSON.stringify(opts.existingSFUConfig) : "undefined"}`, + `constructor - roomId: ${this.roomId} withSfuConfig?: ${opts.existingSFUConfig ? JSON.stringify(opts.existingSFUConfig) : "undefined"}`, ); const { transport, client, scope } = opts; @@ -311,9 +312,7 @@ export class Connection { this.existingSFUConfig ?? (await this.getSFUConfigForRemoteConnection()); this.logger.debug( - "Starting Connection to: ", - this.transport.livekit_service_url, - "jwt: ", + "Starting Connection - jwt: ", jwt, "wss: ", url, @@ -398,15 +397,14 @@ export class Connection { protected async getSFUConfigForRemoteConnection(): Promise { // This will only be called for sfu's where we do not publish ourselves. // For the local connection we will use the existingJwtTokenData - return await getSFUConfigWithOpenID( - this.client, - this.ownMembershipIdentity, - this.transport.livekit_service_url, - this.roomId, - // dont pass any custom opts for the subscribe only connections - {}, - this.logger, - ); + return await getSFUConfig({ + client: this.client, + membership: this.ownMembershipIdentity, + transport: this.transport, + roomId: this.roomId, + role: "subscriber", + logger: this.logger, + }); } /** @@ -416,17 +414,13 @@ export class Connection { * If the connection is already stopped, this is a no-op. */ public async stop(): Promise { - this.logger.debug( - `stop: disconnecing from lk room ${this.transport.livekit_service_url}`, - ); + this.logger.debug("stop: disconnecing from lk room"); if (this.stopped) return; // Mark as stopped before disconnecting so that a connect() aborted by the // disconnect sees the flag and does not report the abort as an error. this.stopped = true; await this.livekitRoom.disconnect(); this._state$.next(ConnectionState.Stopped); - this.logger.debug( - `stop: DONE disconnecing from lk room ${this.transport.livekit_service_url}`, - ); + this.logger.debug("stop: DONE disconnecing from lk room"); } } diff --git a/src/state/CallViewModel/remoteMembers/ConnectionFactory.ts b/src/state/CallViewModel/remoteMembers/ConnectionFactory.ts index 96a28d5ad..4884dc44d 100644 --- a/src/state/CallViewModel/remoteMembers/ConnectionFactory.ts +++ b/src/state/CallViewModel/remoteMembers/ConnectionFactory.ts @@ -16,12 +16,12 @@ import { logger, type Logger } from "matrix-js-sdk/lib/logger"; // imported as inline to support worker when loaded from a cdn (cross domain) import E2EEWorker from "livekit-client/e2ee-worker?worker&inline"; import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; -import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; +import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; import { type ObservableScope } from "../../ObservableScope.ts"; import { Connection } from "./Connection.ts"; import type { - OpenIDClientParts, + ClientOpenIDParts, SFUConfig, } from "../../../livekit/openIDSFU.ts"; import type { MediaDevices } from "../../MediaDevices.ts"; @@ -44,7 +44,7 @@ import { export interface ConnectionFactory { createConnection( scope: ObservableScope, - transport: LivekitTransport, + transport: UnstableLivekitTransport, ownMembershipIdentity: CallMembershipIdentityParts, logger: Logger, sfuConfig?: SFUConfig, @@ -66,7 +66,7 @@ export class ECConnectionFactory implements ConnectionFactory { * @param livekitRoomFactory - Optional factory function (for testing) to create LivekitRoom instances. If not provided, a default factory is used. */ public constructor( - private client: OpenIDClientParts, + private client: ClientOpenIDParts, private readonly roomId: string, private devices: MediaDevices, private processorState$: Behavior, @@ -103,7 +103,7 @@ export class ECConnectionFactory implements ConnectionFactory { */ public createConnection( scope: ObservableScope, - transport: LivekitTransport, + transport: UnstableLivekitTransport, ownMembershipIdentity: CallMembershipIdentityParts, logger: Logger, sfuConfig?: SFUConfig, diff --git a/src/state/CallViewModel/remoteMembers/ConnectionManager.test.ts b/src/state/CallViewModel/remoteMembers/ConnectionManager.test.ts index e7d91c633..6b8d7feb9 100644 --- a/src/state/CallViewModel/remoteMembers/ConnectionManager.test.ts +++ b/src/state/CallViewModel/remoteMembers/ConnectionManager.test.ts @@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details. import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { BehaviorSubject, NEVER } from "rxjs"; -import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; +import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; import { type RemoteParticipant } from "livekit-client"; import { logger } from "matrix-js-sdk/lib/logger"; @@ -19,17 +19,17 @@ import { import { type ConnectionFactory } from "./ConnectionFactory.ts"; import { type Connection } from "./Connection.ts"; import { ownMemberMock, withTestScheduler } from "../../../utils/test.ts"; -import { areLivekitTransportsEqual } from "./MatrixLivekitMembers.ts"; +import { areUnstableLivekitTransportsEqual } from "./MatrixLivekitMembers.ts"; import { type Behavior } from "../../Behavior.ts"; // Some test constants -const TRANSPORT_1: LivekitTransport = { +const TRANSPORT_1: UnstableLivekitTransport = { type: "livekit", livekit_service_url: "https://lk.example.org", }; -const TRANSPORT_2: LivekitTransport = { +const TRANSPORT_2: UnstableLivekitTransport = { type: "livekit", livekit_service_url: "https://lk.sample.com", }; @@ -47,7 +47,7 @@ beforeEach(() => { vi.mocked(fakeConnectionFactory).createConnection = vi .fn() .mockImplementation( - (scope: ObservableScope, transport: LivekitTransport) => { + (scope: ObservableScope, transport: UnstableLivekitTransport) => { const mockConnection = { transport, remoteParticipants$: new BehaviorSubject([]), @@ -94,13 +94,13 @@ describe("connections$ stream", () => { ).toHaveBeenCalledTimes(2); const conn1 = connections.find((c) => - areLivekitTransportsEqual(c.transport, TRANSPORT_1), + areUnstableLivekitTransportsEqual(c.transport, TRANSPORT_1), ); expect(conn1).toBeDefined(); expect(conn1!.start).toHaveBeenCalled(); const conn2 = connections.find((c) => - areLivekitTransportsEqual(c.transport, TRANSPORT_2), + areUnstableLivekitTransportsEqual(c.transport, TRANSPORT_2), ); expect(conn2).toBeDefined(); expect(conn2!.start).toHaveBeenCalled(); @@ -141,12 +141,12 @@ describe("connections$ stream", () => { ).toHaveBeenCalledTimes(2); const conn2 = connections.find((c) => - areLivekitTransportsEqual(c.transport, TRANSPORT_2), + areUnstableLivekitTransportsEqual(c.transport, TRANSPORT_2), ); expect(conn2).toBeDefined(); const conn1 = connections.find((c) => - areLivekitTransportsEqual(c.transport, TRANSPORT_1), + areUnstableLivekitTransportsEqual(c.transport, TRANSPORT_1), ); expect(conn1).toBeDefined(); expect(conn1!.start).toHaveBeenCalledOnce(); @@ -187,7 +187,7 @@ describe("connections$ stream", () => { expect(connections.length).toBe(1); // The second connection should have been stopped has it is no longer needed. const connection2 = allCreatedConnections.find((c) => - areLivekitTransportsEqual(c.transport, TRANSPORT_2), + areUnstableLivekitTransportsEqual(c.transport, TRANSPORT_2), ); expect(connection2).toBeDefined(); expect(connection2!.stop).toHaveBeenCalled(); @@ -225,7 +225,7 @@ describe("connectionManagerData$ stream", () => { // Used in test to control fake connections' remoteParticipants$ streams let fakeRemoteParticipantsStreams: Map>; - function keyForTransport(transport: LivekitTransport): string { + function keyForTransport(transport: UnstableLivekitTransport): string { return `${transport.livekit_service_url}`; } @@ -233,7 +233,7 @@ describe("connectionManagerData$ stream", () => { fakeRemoteParticipantsStreams = new Map(); function getRemoteParticipantsFor( - transport: LivekitTransport, + transport: UnstableLivekitTransport, ): Behavior { return ( fakeRemoteParticipantsStreams.get(keyForTransport(transport)) ?? @@ -245,7 +245,7 @@ describe("connectionManagerData$ stream", () => { vi.mocked(fakeConnectionFactory).createConnection = vi .fn() .mockImplementation( - (scope: ObservableScope, transport: LivekitTransport) => { + (scope: ObservableScope, transport: UnstableLivekitTransport) => { const fakeRemoteParticipants$ = new BehaviorSubject< RemoteParticipant[] >([]); diff --git a/src/state/CallViewModel/remoteMembers/ConnectionManager.ts b/src/state/CallViewModel/remoteMembers/ConnectionManager.ts index 18f54f021..e4fe4c787 100644 --- a/src/state/CallViewModel/remoteMembers/ConnectionManager.ts +++ b/src/state/CallViewModel/remoteMembers/ConnectionManager.ts @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ -import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; +import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; import { combineLatest, map, @@ -25,7 +25,7 @@ import { type Behavior } from "../../Behavior.ts"; import { type Connection } from "./Connection.ts"; import { Epoch, type ObservableScope } from "../../ObservableScope.ts"; import { generateItemsWithEpoch } from "../../../utils/observable.ts"; -import { areLivekitTransportsEqual } from "./MatrixLivekitMembers.ts"; +import { areUnstableLivekitTransportsEqual } from "./MatrixLivekitMembers.ts"; import { type ConnectionFactory } from "./ConnectionFactory.ts"; import { isLocalTransport, @@ -56,7 +56,7 @@ export class ConnectionManagerData { } } - private getKey(transport: LivekitTransport): string { + private getKey(transport: UnstableLivekitTransport): string { // This is enough as a key because the ConnectionManager is already scoped by room. // We also do not need to consider the slotId at this point since each `MatrixRTCSession` is already scoped by `slotDescription: {id, application}`. return transport.livekit_service_url; @@ -67,13 +67,13 @@ export class ConnectionManagerData { } public getConnectionForTransport( - transport: LivekitTransport, + transport: UnstableLivekitTransport, ): Connection | null { return this.store.get(this.getKey(transport))?.connection ?? null; } public getParticipantsForTransport( - transport: LivekitTransport, + transport: UnstableLivekitTransport, ): RemoteParticipant[] { const key = this.getKey(transport); const existing = this.store.get(key); @@ -88,7 +88,7 @@ interface Props { scope: ObservableScope; connectionFactory: ConnectionFactory; localTransport$: Observable; - remoteTransports$: Behavior>; + remoteTransports$: Behavior>; logger: Logger; ownMembershipIdentity: CallMembershipIdentityParts; @@ -147,7 +147,7 @@ export function createConnectionManager$({ * externally this is modified via `registerTransports()`. */ const localAndRemoteTransports$: Behavior< - Epoch<(LivekitTransport | LocalTransport)[]> + Epoch<(UnstableLivekitTransport | LocalTransport)[]> > = scope.behavior( combineLatest([localTransportAsArray$, remoteTransports$]).pipe( // Combine local and remote transports into one transport array @@ -157,7 +157,7 @@ export function createConnectionManager$({ const remoteWithoutLocal = dedupedRemote.filter( (transport) => !localTransportAsArray.find((l) => - areLivekitTransportsEqual(l.transport, transport), + areUnstableLivekitTransportsEqual(l.transport, transport), ), ); logger.debug( @@ -271,11 +271,11 @@ export function createConnectionManager$({ return { connectionManagerData$ }; } -function removeDuplicateTransports( +function removeDuplicateTransports( transports: T[], ): T[] { return transports.reduce((acc, transport) => { - if (!acc.some((t) => areLivekitTransportsEqual(t, transport))) + if (!acc.some((t) => areUnstableLivekitTransportsEqual(t, transport))) acc.push(transport); return acc; }, [] as T[]); diff --git a/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts b/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts index 411ae2001..c8f511348 100644 --- a/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts +++ b/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts @@ -14,7 +14,7 @@ import EventEmitter from "events"; import { ObservableScope } from "../../ObservableScope.ts"; import { ECConnectionFactory } from "./ConnectionFactory.ts"; -import type { OpenIDClientParts } from "../../../livekit/openIDSFU.ts"; +import type { ClientOpenIDParts } from "../../../livekit/openIDSFU.ts"; import { exampleTransport, mockMediaDevices, @@ -51,7 +51,7 @@ vi.mock("livekit-client", async (importOriginal) => { }); let testScope: ObservableScope; -let mockClient: OpenIDClientParts; +let mockClient: ClientOpenIDParts; beforeEach(() => { testScope = new ObservableScope(); diff --git a/src/state/CallViewModel/remoteMembers/MatrixLivekitMembers.test.ts b/src/state/CallViewModel/remoteMembers/MatrixLivekitMembers.test.ts index 2e0051928..b709d3ff5 100644 --- a/src/state/CallViewModel/remoteMembers/MatrixLivekitMembers.test.ts +++ b/src/state/CallViewModel/remoteMembers/MatrixLivekitMembers.test.ts @@ -9,7 +9,7 @@ import { describe, test, expect, beforeEach, afterEach, vi } from "vitest"; import { logger } from "matrix-js-sdk/lib/logger"; import { type CallMembership, - type LivekitTransport, + type UnstableLivekitTransport, } from "matrix-js-sdk/lib/matrixrtc"; import { BehaviorSubject, combineLatest, map, type Observable } from "rxjs"; @@ -39,12 +39,12 @@ let testScope: ObservableScope; const fallbackMemberId = (userId: string, deviceId: string): string => `${userId}:${deviceId}`; -const transportA: LivekitTransport = { +const transportA: UnstableLivekitTransport = { type: "livekit", livekit_service_url: "https://lk.example.org", }; -const transportB: LivekitTransport = { +const transportB: UnstableLivekitTransport = { type: "livekit", livekit_service_url: "https://lk.sample.com", }; @@ -117,7 +117,7 @@ test("should signal participant not yet connected to livekit", async () => { function createEpochedMemberships$(m$: Observable): { memberships$: Observable>; membershipsWithTransport$: Observable< - Epoch<{ membership: CallMembership; transport?: LivekitTransport }[]> + Epoch<{ membership: CallMembership; transport?: UnstableLivekitTransport }[]> >; } { const memberships$ = m$.pipe(trackEpoch()); @@ -128,7 +128,7 @@ function createEpochedMemberships$(m$: Observable): { return { membership: m, transport: - tr?.type === "livekit" ? (tr as LivekitTransport) : undefined, + tr?.type === "livekit" ? (tr as UnstableLivekitTransport) : undefined, }; }); }), diff --git a/src/state/CallViewModel/remoteMembers/MatrixLivekitMembers.ts b/src/state/CallViewModel/remoteMembers/MatrixLivekitMembers.ts index 6c9fff026..c4b8ee947 100644 --- a/src/state/CallViewModel/remoteMembers/MatrixLivekitMembers.ts +++ b/src/state/CallViewModel/remoteMembers/MatrixLivekitMembers.ts @@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details. import { type LocalParticipant, type RemoteParticipant } from "livekit-client"; import { type CallMembership, - type LivekitTransport, + type UnstableLivekitTransport, } from "matrix-js-sdk/lib/matrixrtc"; import { combineLatest, filter, map } from "rxjs"; import { logger } from "matrix-js-sdk/lib/logger"; @@ -60,7 +60,7 @@ export interface RemoteMatrixLivekitMember extends MatrixLivekitMember { interface Props { scope: ObservableScope; membershipsWithTransport$: Behavior< - Epoch<{ membership: CallMembership; transport?: LivekitTransport }[]> + Epoch<{ membership: CallMembership; transport?: UnstableLivekitTransport }[]> >; connectionManager: IConnectionManager; localUser: { deviceId: string; userId: string }; @@ -165,7 +165,7 @@ export function createRemoteMatrixLivekitMembers$({ // TODO add back in the callviewmodel pauseWhen(this.pretendToBeDisconnected$) // TODO add this to the JS-SDK -export function areLivekitTransportsEqual( +export function areUnstableLivekitTransportsEqual( t1: T | null, t2: T | null, ): boolean { diff --git a/src/state/CallViewModel/remoteMembers/integration.test.ts b/src/state/CallViewModel/remoteMembers/integration.test.ts index 4c0f4fea7..760128a06 100644 --- a/src/state/CallViewModel/remoteMembers/integration.test.ts +++ b/src/state/CallViewModel/remoteMembers/integration.test.ts @@ -10,7 +10,7 @@ import { BehaviorSubject, NEVER } from "rxjs"; import { type Room as LivekitRoom } from "livekit-client"; import EventEmitter from "events"; import fetchMock from "fetch-mock"; -import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; +import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; import { logger } from "matrix-js-sdk/lib/logger"; import { @@ -19,7 +19,7 @@ import { trackEpoch, } from "../../ObservableScope.ts"; import { ECConnectionFactory } from "./ConnectionFactory.ts"; -import { type OpenIDClientParts } from "../../../livekit/openIDSFU.ts"; +import { type ClientOpenIDParts } from "../../../livekit/openIDSFU.ts"; import { mockMediaDevices, mockRtcMembership, @@ -28,7 +28,7 @@ import { } from "../../../utils/test.ts"; import { type ProcessorState } from "../../../livekit/TrackProcessorContext.tsx"; import { - areLivekitTransportsEqual, + areUnstableLivekitTransportsEqual, createRemoteMatrixLivekitMembers$, type RemoteMatrixLivekitMember, } from "./MatrixLivekitMembers.ts"; @@ -40,7 +40,7 @@ import { localRtcMember, testJWTToken } from "../../../utils/test-fixtures.ts"; let testScope: ObservableScope; let ecConnectionFactory: ECConnectionFactory; -let mockClient: OpenIDClientParts; +let mockClient: ClientOpenIDParts; let lkRoomFactory: () => LivekitRoom; const createdMockLivekitRooms: Map = new Map(); @@ -147,9 +147,9 @@ test("bob, carl, then bob joining no tracks yet", () => { }); expectObservable(item.connection$).toBe("a", { a: expect.toSatisfy((co) => - areLivekitTransportsEqual( + areUnstableLivekitTransportsEqual( co.transport, - bobMembership.transports[0]! as LivekitTransport, + bobMembership.transports[0]! as UnstableLivekitTransport, ), ), }); @@ -184,9 +184,9 @@ test("bob, carl, then bob joining no tracks yet", () => { expectObservable(item.connection$).toBe("a", { a: expect.toSatisfy((connection) => { expect( - areLivekitTransportsEqual( + areUnstableLivekitTransportsEqual( connection.transport, - carlMembership.transports[0]! as LivekitTransport, + carlMembership.transports[0]! as UnstableLivekitTransport, ), ).toBe(true); return true; @@ -214,9 +214,9 @@ test("bob, carl, then bob joining no tracks yet", () => { expectObservable(item.connection$).toBe("a", { a: expect.toSatisfy((connection) => { expect( - areLivekitTransportsEqual( + areUnstableLivekitTransportsEqual( connection.transport, - daveMembership.transports[0]! as LivekitTransport, + daveMembership.transports[0]! as UnstableLivekitTransport, ), ).toBe(true); return true; diff --git a/src/state/SessionBehaviors.ts b/src/state/SessionBehaviors.ts index 4ca1c5861..e227bcb3b 100644 --- a/src/state/SessionBehaviors.ts +++ b/src/state/SessionBehaviors.ts @@ -7,10 +7,10 @@ Please see LICENSE in the repository root for full details. import { type CallMembership, - type LivekitTransport, + type UnstableLivekitTransport, type MatrixRTCSession, MatrixRTCSessionEvent, - isLivekitTransport, + isUnstableLivekitTransport, } from "matrix-js-sdk/lib/matrixrtc"; import { fromEvent } from "rxjs"; @@ -31,15 +31,15 @@ export const membershipsAndTransports$ = ( memberships$: Behavior>, ): { membershipsWithTransport$: Behavior< - Epoch<{ membership: CallMembership; transport?: LivekitTransport }[]> + Epoch<{ membership: CallMembership; transport?: UnstableLivekitTransport }[]> >; - transports$: Behavior>; + transports$: Behavior>; } => { const membershipsWithTransport$: Behavior< Epoch< { membership: CallMembership; - transport: LivekitTransport | undefined; + transport: UnstableLivekitTransport | undefined; }[] > > = scope.behavior( @@ -49,14 +49,14 @@ export const membershipsAndTransports$ = ( const transport = membership.getTransport(); return { membership, - transport: isLivekitTransport(transport) ? transport : undefined, + transport: isUnstableLivekitTransport(transport) ? transport : undefined, }; }); }), ), ); - const transports$: Behavior> = scope.behavior( + const transports$: Behavior> = scope.behavior( membershipsWithTransport$.pipe( mapEpoch((mts) => mts.flatMap(({ transport: t }) => (t ? [t] : []))), ), diff --git a/src/utils/test.ts b/src/utils/test.ts index 6af9fd8c7..00e60c762 100644 --- a/src/utils/test.ts +++ b/src/utils/test.ts @@ -26,7 +26,7 @@ import { import { CallMembership, type LivekitFocusSelection, - type LivekitTransport, + type UnstableLivekitTransport, type MatrixRTCSession, MatrixRTCSessionEvent, type MatrixRTCSessionEventHandlerMap, @@ -209,7 +209,7 @@ export function mockEmitter(): EmitterMock { }; } -export const exampleTransport: LivekitTransport = { +export const exampleTransport: UnstableLivekitTransport = { type: "livekit", livekit_service_url: "https://lk.example.org", };