diff --git a/src/livekit/openIDSFU.ts b/src/livekit/openIDSFU.ts index 785d80c7c..cf1f3c951 100644 --- a/src/livekit/openIDSFU.ts +++ b/src/livekit/openIDSFU.ts @@ -19,7 +19,7 @@ import { } from "../utils/errors"; import { doNetworkOperationWithRetry } from "../utils/matrix"; import { Config } from "../config/Config"; -import { JwtEndpointVersion } from "../state/CallViewModel/localMember/LocalTransport"; +import { MatrixRTCMode } from "../config/ConfigOptions"; /** * Configuration and access tokens provided by the SFU on successful authentication. @@ -80,11 +80,10 @@ export type OpenIDClientParts = Pick< * @param serviceUrl The URL of the livekit SFU service * @param roomId The room id used in the jwt request. This is NOT the livekit_alias. The jwt service will provide the alias. It maps matrix room ids <-> Livekit aliases. * @param opts Additional options to modify which endpoint with which data will be used to acquire the jwt token. - * @param opts.forceJwtEndpoint This will use the old jwt endpoint which will create the rtc backend identity based on string concatenation - * instead of a hash. + * @param opts.matrixRTCMode Determines which version of the JWT endpoint to use, which affects whether the + * RTC backend identity is based on string concatenation (legacy) or a hash (Matrix 2.0). * This function by default uses whatever is possible with the current jwt service installed next to the SFU. * For remote connections this does not matter, since we will not publish there we can rely on the newest option. - * For our own connection we can only use the hashed version if we also send the new matrix2.0 sticky events. * @param opts.delayEndpointBaseUrl The URL of the matrix homeserver. * @param opts.delayId The delay id used for the jwt service to manage. * @param logger optional logger. @@ -97,7 +96,7 @@ export async function getSFUConfigWithOpenID( serviceUrl: string, roomId: string, opts?: { - forceJwtEndpoint?: JwtEndpointVersion; + matrixRTCMode?: MatrixRTCMode; delayEndpointBaseUrl?: string; delayId?: string; }, @@ -116,10 +115,9 @@ export async function getSFUConfigWithOpenID( logger?.debug("Got openID token", openIdToken); let sfuConfig: { url: string; jwt: string } | undefined; - const tryBothJwtEndpoints = opts?.forceJwtEndpoint === undefined; // This is for SFUs where we do not publish. + const tryBothJwtEndpoints = opts?.matrixRTCMode === undefined; // This is for SFUs where we do not publish. - const forceMatrix2Jwt = - opts?.forceJwtEndpoint === JwtEndpointVersion.Matrix_2_0; + 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. diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 3ea88f81f..85fe66a74 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -135,7 +135,9 @@ export const ActiveCall: FC = (props) => { encryptionSystem: props.e2eeSystem, autoLeaveWhenOthersLeft, waitForCallPickup: waitForCallPickup && sendNotificationType === "ring", - matrixRTCMode$: matrixRTCModeSetting.value$, + // We merely sample the current mode here, so the user would need to + // manually rejoin to switch to a different one. + matrixRTCMode: matrixRTCModeSetting.value$.value, }, reactionsReader.raisedHands$, reactionsReader.reactions$, diff --git a/src/state/CallViewModel/CallViewModel.ts b/src/state/CallViewModel/CallViewModel.ts index 62f05a476..0bd33f213 100644 --- a/src/state/CallViewModel/CallViewModel.ts +++ b/src/state/CallViewModel/CallViewModel.ts @@ -54,7 +54,6 @@ import { type IMembershipManager } from "matrix-js-sdk/lib/matrixrtc/IMembership import { createToggle$, filterBehavior, - generateItem, generateItems, pauseWhen, } from "../../utils/observable"; @@ -113,7 +112,6 @@ import { } from "./localMember/LocalMember.ts"; import { createLocalTransport$, - JwtEndpointVersion, type LocalTransport, } from "./localMember/LocalTransport.ts"; import { @@ -191,7 +189,7 @@ export interface CallViewModelOptions { /** Optional value overriding the connection factory, for testing purposes. */ connectionFactory?: ConnectionFactory; /** The version & compatibility mode of MatrixRTC that we should use. */ - matrixRTCMode$?: Behavior; + matrixRTCMode?: MatrixRTCMode; /** Optional behavior overriding for the screensharing, for testing */ toggleScreensharing?: () => void; } @@ -453,10 +451,8 @@ export function createCallViewModel$( const configMatrixRTCMode = Config.get().matrix_rtc_mode as | MatrixRTCMode | undefined; - const matrixRTCMode$ = - configMatrixRTCMode !== undefined - ? constant(configMatrixRTCMode) - : (options.matrixRTCMode$ ?? constant(MatrixRTCMode.Compatibility)); + const matrixRTCMode = + configMatrixRTCMode ?? options.matrixRTCMode ?? MatrixRTCMode.Compatibility; // Each hbar seperates a block of input variables required for the CallViewModel to function. // The outputs of this block is written under the hbar. @@ -490,38 +486,26 @@ export function createCallViewModel$( memberId: uuidv4(), }; - const localTransport$ = scope.behavior( - matrixRTCMode$.pipe( - generateItem( - "CallViewModel localTransport$", - // Re-create LocalTransport whenever the mode changes - (mode) => ({ keys: [mode], data: undefined }), - (scope, _data$, mode) => - options.localTransport ?? - createLocalTransport$({ - scope: scope, - memberships$: memberships$, - ownMembershipIdentity, - client, - delayId$: scope.behavior( - ( - fromEvent( - matrixRTCSession, - MembershipManagerEvent.DelayIdChanged, - // The type of reemitted event includes the original emitted as the second arg. - ) as Observable<[string | undefined, IMembershipManager]> - ).pipe(map(([delayId]) => delayId ?? null)), - matrixRTCSession.delayId ?? null, - ), - roomId: matrixRoom.roomId, - forceJwtEndpoint: - mode === MatrixRTCMode.Matrix_2_0 - ? JwtEndpointVersion.Matrix_2_0 - : JwtEndpointVersion.Legacy, - }), + const localTransport = + options.localTransport ?? + createLocalTransport$({ + scope: scope, + memberships$: memberships$, + ownMembershipIdentity, + client, + delayId$: scope.behavior( + ( + fromEvent( + matrixRTCSession, + MembershipManagerEvent.DelayIdChanged, + // The type of reemitted event includes the original emitted as the second arg. + ) as Observable<[string | undefined, IMembershipManager]> + ).pipe(map(([delayId]) => delayId ?? null)), + matrixRTCSession.delayId ?? null, ), - ), - ); + roomId: matrixRoom.roomId, + matrixRTCMode, + }); const connectionFactory = options.connectionFactory ?? @@ -539,8 +523,7 @@ export function createCallViewModel$( scope: scope, connectionFactory: connectionFactory, localTransport$: scope.behavior( - localTransport$.pipe( - switchMap((t) => t.active$), + localTransport.active$.pipe( catchError((e: unknown) => { logger.info( "could not pass local transport to createConnectionManager$. localTransport$ threw an error", @@ -583,9 +566,7 @@ export function createCallViewModel$( transport, { encryptMedia: livekitKeyProvider !== undefined, - // We merely sample the current mode here, so the user would need to - // manually rejoin to switch to a different one - matrixRTCMode: matrixRTCMode$.value, + matrixRTCMode, delayedLeaveTimings, }, ); @@ -606,6 +587,7 @@ export function createCallViewModel$( localTransport$, roomId: matrixRoom.roomId, baseUrl: client.baseUrl, + matrixRTCMode, logger: logger.getChild(`[${Date.now()}]`), }); diff --git a/src/state/CallViewModel/CallViewModelTestUtils.ts b/src/state/CallViewModel/CallViewModelTestUtils.ts index 2e1525c06..5a3fa3378 100644 --- a/src/state/CallViewModel/CallViewModelTestUtils.ts +++ b/src/state/CallViewModel/CallViewModelTestUtils.ts @@ -238,7 +238,7 @@ export function withCallViewModel(mode: MatrixRTCMode) { ); }, }, - matrixRTCMode$: constant(mode), + matrixRTCMode: mode, ...options, }, raisedHands$, diff --git a/src/state/CallViewModel/localMember/LocalMember.test.ts b/src/state/CallViewModel/localMember/LocalMember.test.ts index 66cd8cda0..6fe2155bd 100644 --- a/src/state/CallViewModel/localMember/LocalMember.test.ts +++ b/src/state/CallViewModel/localMember/LocalMember.test.ts @@ -230,6 +230,7 @@ describe("LocalMembership", () => { }, roomId: "!test-room-id:example.org", baseUrl: "https://matrix.example.org", + matrixRTCMode: MATRIX_RTC_MODE, }; beforeEach(() => { diff --git a/src/state/CallViewModel/localMember/LocalMember.ts b/src/state/CallViewModel/localMember/LocalMember.ts index 36c69e6ed..9b9218c54 100644 --- a/src/state/CallViewModel/localMember/LocalMember.ts +++ b/src/state/CallViewModel/localMember/LocalMember.ts @@ -143,7 +143,7 @@ interface Props { ) => void; homeserverConnected: HomeserverConnected; roomId: string; - localTransport$: Behavior; + localTransport: LocalTransport; matrixRTCSession: Pick< MatrixRTCSession, "updateCallIntent" | "leaveRoomSession" @@ -163,7 +163,7 @@ interface Props { * @param props.createPublisherFactory Factory to create a publisher once we have a connection. * @param props.joinMatrixRTC Callback to join the matrix RTC session once we have a transport. * @param props.homeserverConnected The homeserver connected state. - * @param props.localTransport$ The transport to advertise in our membership. + * @param props.localTransport The transport to advertise in our membership. * @param props.logger The logger to use. * @param props.muteStates The mute states for video and audio. * @param props.matrixRTCSession The matrix RTC session to join. @@ -282,8 +282,7 @@ export const createLocalMembership$ = ({ // The transport that we will advertise in our membership, paired with info as // to whether delayed event delegation is supported - const joinParams$ = localTransport$.pipe( - switchMap((lt) => lt.advertised$), + const joinParams$ = localTransport.advertised$.pipe( catchError(handleTransportError), distinctUntilChanged(areLivekitTransportsEqual), switchMap((transport) => { @@ -304,16 +303,12 @@ export const createLocalMembership$ = ({ // Unwrap the local transport and set the state of the LocalMembership to error in case the transport is an error. const activeTransport$ = scope.behavior( - localTransport$.pipe( - switchMap((lt) => { - return combineLatest([lt.active$, lt.advertised$]).pipe( - map(([active, advertised]) => { - // Our policy is to not publish to another transport if our prefered transport is miss-configured - if (advertised == null) return null; + combineLatest([localTransport.active$, localTransport.advertised$]).pipe( + map(([active, advertised]) => { + // Our policy is to not publish to another transport if our prefered transport is miss-configured + if (advertised == null) return null; - return active?.transport ?? null; - }), - ); + return active?.transport ?? null; }), catchError(handleTransportError), distinctUntilChanged(areLivekitTransportsEqual), diff --git a/src/state/CallViewModel/localMember/LocalTransport.test.ts b/src/state/CallViewModel/localMember/LocalTransport.test.ts index 09f6ecec0..7f9ecb98a 100644 --- a/src/state/CallViewModel/localMember/LocalTransport.test.ts +++ b/src/state/CallViewModel/localMember/LocalTransport.test.ts @@ -41,6 +41,7 @@ import { import * as openIDSFU from "../../../livekit/openIDSFU"; import { customLivekitUrl } from "../../../settings/settings"; import { testJWTToken } from "../../../utils/test-fixtures"; +import { MatrixRTCMode } from "../../../config/ConfigOptions"; describe("LocalTransport", () => { const openIdResponse: openIDSFU.SFUConfig = { @@ -67,7 +68,7 @@ describe("LocalTransport", () => { getDeviceId: vi.fn(), }, ownMembershipIdentity: ownMemberMock, - forceJwtEndpoint: JwtEndpointVersion.Legacy, + matrixRTCMode: MatrixRTCMode.Compatibility, delayId$: constant("delay_id_mock"), }); await flushPromises(); @@ -108,7 +109,7 @@ describe("LocalTransport", () => { getDeviceId: vi.fn(), }, ownMembershipIdentity: ownMemberMock, - forceJwtEndpoint: JwtEndpointVersion.Legacy, + matrixRTCMode: MatrixRTCMode.Compatibility, delayId$: constant("delay_id_mock"), }); active$.subscribe( @@ -150,7 +151,7 @@ describe("LocalTransport", () => { baseUrl: "https://example.org", }, ownMembershipIdentity: ownMemberMock, - forceJwtEndpoint: JwtEndpointVersion.Legacy, + matrixRTCMode: MatrixRTCMode.Compatibility, delayId$: constant("delay_id_mock"), }); @@ -194,7 +195,7 @@ describe("LocalTransport", () => { ownMembershipIdentity: ownMemberMock, scope: testScope(), roomId: "!example_room_id", - forceJwtEndpoint: JwtEndpointVersion.Legacy, + matrixRTCMode: MatrixRTCMode.Compatibility, delayId$: constant(null), memberships$: constant(new Epoch([])), client: { @@ -306,7 +307,7 @@ describe("LocalTransport", () => { scope: testScope(), ownMembershipIdentity: ownMemberMock, roomId: "!example_room_id", - forceJwtEndpoint: JwtEndpointVersion.Legacy, + matrixRTCMode: MatrixRTCMode.Compatibility, delayId$: constant(null), memberships$: constant(new Epoch([])), client: { diff --git a/src/state/CallViewModel/localMember/LocalTransport.ts b/src/state/CallViewModel/localMember/LocalTransport.ts index f98a266fd..66be511a4 100644 --- a/src/state/CallViewModel/localMember/LocalTransport.ts +++ b/src/state/CallViewModel/localMember/LocalTransport.ts @@ -37,6 +37,7 @@ import { import { areLivekitTransportsEqual } from "../remoteMembers/MatrixLivekitMembers.ts"; import { customLivekitUrl } from "../../../settings/settings.ts"; import { RtcTransportAutoDiscovery } from "./RtcTransportAutoDiscovery.ts"; +import { type MatrixRTCMode } from "../../../config/ConfigOptions.ts"; /* * It figures out “which LiveKit focus URL/alias the local user should use,” @@ -53,15 +54,10 @@ interface Props { OpenIDClientParts; // Used by the jwt service to create the livekit room and compute the livekit alias. roomId: string; - forceJwtEndpoint: JwtEndpointVersion; + matrixRTCMode: MatrixRTCMode; delayId$: Behavior; } -export enum JwtEndpointVersion { - Legacy = "legacy", - Matrix_2_0 = "matrix_2_0", -} - // TODO livekit_alias-cleanup // 1. We need to move away from transports map to connections!!! // @@ -122,7 +118,7 @@ export const createLocalTransport$ = ({ ownMembershipIdentity, client, roomId, - forceJwtEndpoint, + matrixRTCMode, delayId$, }: Props): LocalTransport => { const logger = rootLogger.getChild("[LocalTransport]"); @@ -167,7 +163,7 @@ export const createLocalTransport$ = ({ try { return await doOpenIdAndJWTFromUrl( transport, - forceJwtEndpoint, + matrixRTCMode, ownMembershipIdentity, roomId, client, @@ -219,7 +215,7 @@ export const createLocalTransport$ = ({ * use we don't want to risk any issues by re-using a token. * * @param transport The transport to authenticate with. - * @param forceJwtEndpoint Whether to force the JWT endpoint to be used. + * @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. @@ -229,7 +225,7 @@ export const createLocalTransport$ = ({ */ async function doOpenIdAndJWTFromUrl( transport: LivekitTransportConfig, - forceJwtEndpoint: JwtEndpointVersion, + matrixRTCMode: MatrixRTCMode, membership: CallMembershipIdentityParts, roomId: string, client: Pick< @@ -246,7 +242,7 @@ async function doOpenIdAndJWTFromUrl( transport.livekit_service_url, roomId, { - forceJwtEndpoint: forceJwtEndpoint, + matrixRTCMode, delayEndpointBaseUrl: client.baseUrl, delayId, }, diff --git a/src/utils/observable.ts b/src/utils/observable.ts index d4ea90b22..8468b3fbb 100644 --- a/src/utils/observable.ts +++ b/src/utils/observable.ts @@ -227,38 +227,6 @@ export function filterBehavior( ); } -/** - * Maps a changing input value to an item whose lifetime is tied to a certain - * computed key. The item may capture some dynamic data from the input. - */ -export function generateItem< - Input, - Keys extends [unknown, ...unknown[]], - Data, - Item, ->( - name: string, - generator: (input: Input) => { keys: readonly [...Keys]; data: Data }, - factory: ( - scope: ObservableScope, - data$: Behavior, - ...keys: Keys - ) => Item, -): OperatorFunction { - return (input$) => - input$.pipe( - generateItemsInternal( - name, - function* (input) { - yield generator(input); - }, - factory, - (items) => items, - ), - map(([item]) => item), - ); -} - function generateItemsInternal< Input, Keys extends [unknown, ...unknown[]], diff --git a/src/utils/test-viewmodel.ts b/src/utils/test-viewmodel.ts index f53910024..974879110 100644 --- a/src/utils/test-viewmodel.ts +++ b/src/utils/test-viewmodel.ts @@ -174,7 +174,7 @@ export function getBasicCallViewModelEnvironment( setE2EEEnabled: async () => Promise.resolve(), }), connectionState$: constant(ConnectionState.Connected), - matrixRTCMode$: constant(MatrixRTCMode.Compatibility), + matrixRTCMode: MatrixRTCMode.Compatibility, ...callViewModelOptions, }, handRaisedSubject$,