Base JWT service endpoint choice directly on the MatrixRTC mode

Since we removed the extra 'legacy' mode, meaning there's nothing simplified anymore by translating things over to a JWT service version.

(Also, simplify some things by making the mode static for the duration of a call.)
This commit is contained in:
Robin
2026-09-08 21:22:52 +02:00
parent d38daef567
commit 5cfe7ea4df
10 changed files with 58 additions and 115 deletions
+6 -8
View File
@@ -19,7 +19,7 @@ import {
} from "../utils/errors"; } from "../utils/errors";
import { doNetworkOperationWithRetry } from "../utils/matrix"; import { doNetworkOperationWithRetry } from "../utils/matrix";
import { Config } from "../config/Config"; 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. * 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 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 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 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 * @param opts.matrixRTCMode Determines which version of the JWT endpoint to use, which affects whether the
* instead of a hash. * 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. * 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 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.delayEndpointBaseUrl The URL of the matrix homeserver.
* @param opts.delayId The delay id used for the jwt service to manage. * @param opts.delayId The delay id used for the jwt service to manage.
* @param logger optional logger. * @param logger optional logger.
@@ -97,7 +96,7 @@ export async function getSFUConfigWithOpenID(
serviceUrl: string, serviceUrl: string,
roomId: string, roomId: string,
opts?: { opts?: {
forceJwtEndpoint?: JwtEndpointVersion; matrixRTCMode?: MatrixRTCMode;
delayEndpointBaseUrl?: string; delayEndpointBaseUrl?: string;
delayId?: string; delayId?: string;
}, },
@@ -116,10 +115,9 @@ export async function getSFUConfigWithOpenID(
logger?.debug("Got openID token", openIdToken); logger?.debug("Got openID token", openIdToken);
let sfuConfig: { url: string; jwt: string } | undefined; 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 = const forceMatrix2Jwt = opts?.matrixRTCMode === MatrixRTCMode.Matrix_2_0;
opts?.forceJwtEndpoint === JwtEndpointVersion.Matrix_2_0;
// We want to start using the new endpoint (with optional delay delegation) // 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 we can use both or if we are forced to use the new one.
+3 -1
View File
@@ -135,7 +135,9 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
encryptionSystem: props.e2eeSystem, encryptionSystem: props.e2eeSystem,
autoLeaveWhenOthersLeft, autoLeaveWhenOthersLeft,
waitForCallPickup: waitForCallPickup && sendNotificationType === "ring", 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.raisedHands$,
reactionsReader.reactions$, reactionsReader.reactions$,
+9 -27
View File
@@ -54,7 +54,6 @@ import { type IMembershipManager } from "matrix-js-sdk/lib/matrixrtc/IMembership
import { import {
createToggle$, createToggle$,
filterBehavior, filterBehavior,
generateItem,
generateItems, generateItems,
pauseWhen, pauseWhen,
} from "../../utils/observable"; } from "../../utils/observable";
@@ -113,7 +112,6 @@ import {
} from "./localMember/LocalMember.ts"; } from "./localMember/LocalMember.ts";
import { import {
createLocalTransport$, createLocalTransport$,
JwtEndpointVersion,
type LocalTransport, type LocalTransport,
} from "./localMember/LocalTransport.ts"; } from "./localMember/LocalTransport.ts";
import { import {
@@ -191,7 +189,7 @@ export interface CallViewModelOptions {
/** Optional value overriding the connection factory, for testing purposes. */ /** Optional value overriding the connection factory, for testing purposes. */
connectionFactory?: ConnectionFactory; connectionFactory?: ConnectionFactory;
/** The version & compatibility mode of MatrixRTC that we should use. */ /** The version & compatibility mode of MatrixRTC that we should use. */
matrixRTCMode$?: Behavior<MatrixRTCMode>; matrixRTCMode?: MatrixRTCMode;
/** Optional behavior overriding for the screensharing, for testing */ /** Optional behavior overriding for the screensharing, for testing */
toggleScreensharing?: () => void; toggleScreensharing?: () => void;
} }
@@ -453,10 +451,8 @@ export function createCallViewModel$(
const configMatrixRTCMode = Config.get().matrix_rtc_mode as const configMatrixRTCMode = Config.get().matrix_rtc_mode as
| MatrixRTCMode | MatrixRTCMode
| undefined; | undefined;
const matrixRTCMode$ = const matrixRTCMode =
configMatrixRTCMode !== undefined configMatrixRTCMode ?? options.matrixRTCMode ?? MatrixRTCMode.Compatibility;
? constant(configMatrixRTCMode)
: (options.matrixRTCMode$ ?? constant(MatrixRTCMode.Compatibility));
// Each hbar seperates a block of input variables required for the CallViewModel to function. // Each hbar seperates a block of input variables required for the CallViewModel to function.
// The outputs of this block is written under the hbar. // The outputs of this block is written under the hbar.
@@ -490,13 +486,7 @@ export function createCallViewModel$(
memberId: uuidv4(), memberId: uuidv4(),
}; };
const localTransport$ = scope.behavior( const localTransport =
matrixRTCMode$.pipe(
generateItem(
"CallViewModel localTransport$",
// Re-create LocalTransport whenever the mode changes
(mode) => ({ keys: [mode], data: undefined }),
(scope, _data$, mode) =>
options.localTransport ?? options.localTransport ??
createLocalTransport$({ createLocalTransport$({
scope: scope, scope: scope,
@@ -514,14 +504,8 @@ export function createCallViewModel$(
matrixRTCSession.delayId ?? null, matrixRTCSession.delayId ?? null,
), ),
roomId: matrixRoom.roomId, roomId: matrixRoom.roomId,
forceJwtEndpoint: matrixRTCMode,
mode === MatrixRTCMode.Matrix_2_0 });
? JwtEndpointVersion.Matrix_2_0
: JwtEndpointVersion.Legacy,
}),
),
),
);
const connectionFactory = const connectionFactory =
options.connectionFactory ?? options.connectionFactory ??
@@ -539,8 +523,7 @@ export function createCallViewModel$(
scope: scope, scope: scope,
connectionFactory: connectionFactory, connectionFactory: connectionFactory,
localTransport$: scope.behavior( localTransport$: scope.behavior(
localTransport$.pipe( localTransport.active$.pipe(
switchMap((t) => t.active$),
catchError((e: unknown) => { catchError((e: unknown) => {
logger.info( logger.info(
"could not pass local transport to createConnectionManager$. localTransport$ threw an error", "could not pass local transport to createConnectionManager$. localTransport$ threw an error",
@@ -583,9 +566,7 @@ export function createCallViewModel$(
transport, transport,
{ {
encryptMedia: livekitKeyProvider !== undefined, encryptMedia: livekitKeyProvider !== undefined,
// We merely sample the current mode here, so the user would need to matrixRTCMode,
// manually rejoin to switch to a different one
matrixRTCMode: matrixRTCMode$.value,
delayedLeaveTimings, delayedLeaveTimings,
}, },
); );
@@ -606,6 +587,7 @@ export function createCallViewModel$(
localTransport$, localTransport$,
roomId: matrixRoom.roomId, roomId: matrixRoom.roomId,
baseUrl: client.baseUrl, baseUrl: client.baseUrl,
matrixRTCMode,
logger: logger.getChild(`[${Date.now()}]`), logger: logger.getChild(`[${Date.now()}]`),
}); });
@@ -238,7 +238,7 @@ export function withCallViewModel(mode: MatrixRTCMode) {
); );
}, },
}, },
matrixRTCMode$: constant(mode), matrixRTCMode: mode,
...options, ...options,
}, },
raisedHands$, raisedHands$,
@@ -230,6 +230,7 @@ describe("LocalMembership", () => {
}, },
roomId: "!test-room-id:example.org", roomId: "!test-room-id:example.org",
baseUrl: "https://matrix.example.org", baseUrl: "https://matrix.example.org",
matrixRTCMode: MATRIX_RTC_MODE,
}; };
beforeEach(() => { beforeEach(() => {
@@ -143,7 +143,7 @@ interface Props {
) => void; ) => void;
homeserverConnected: HomeserverConnected; homeserverConnected: HomeserverConnected;
roomId: string; roomId: string;
localTransport$: Behavior<LocalTransport>; localTransport: LocalTransport;
matrixRTCSession: Pick< matrixRTCSession: Pick<
MatrixRTCSession, MatrixRTCSession,
"updateCallIntent" | "leaveRoomSession" "updateCallIntent" | "leaveRoomSession"
@@ -163,7 +163,7 @@ interface Props {
* @param props.createPublisherFactory Factory to create a publisher once we have a connection. * @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.joinMatrixRTC Callback to join the matrix RTC session once we have a transport.
* @param props.homeserverConnected The homeserver connected state. * @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.logger The logger to use.
* @param props.muteStates The mute states for video and audio. * @param props.muteStates The mute states for video and audio.
* @param props.matrixRTCSession The matrix RTC session to join. * @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 // The transport that we will advertise in our membership, paired with info as
// to whether delayed event delegation is supported // to whether delayed event delegation is supported
const joinParams$ = localTransport$.pipe( const joinParams$ = localTransport.advertised$.pipe(
switchMap((lt) => lt.advertised$),
catchError(handleTransportError), catchError(handleTransportError),
distinctUntilChanged(areLivekitTransportsEqual), distinctUntilChanged(areLivekitTransportsEqual),
switchMap((transport) => { switchMap((transport) => {
@@ -304,17 +303,13 @@ export const createLocalMembership$ = ({
// Unwrap the local transport and set the state of the LocalMembership to error in case the transport is an error. // Unwrap the local transport and set the state of the LocalMembership to error in case the transport is an error.
const activeTransport$ = scope.behavior( const activeTransport$ = scope.behavior(
localTransport$.pipe( combineLatest([localTransport.active$, localTransport.advertised$]).pipe(
switchMap((lt) => {
return combineLatest([lt.active$, lt.advertised$]).pipe(
map(([active, advertised]) => { map(([active, advertised]) => {
// Our policy is to not publish to another transport if our prefered transport is miss-configured // Our policy is to not publish to another transport if our prefered transport is miss-configured
if (advertised == null) return null; if (advertised == null) return null;
return active?.transport ?? null; return active?.transport ?? null;
}), }),
);
}),
catchError(handleTransportError), catchError(handleTransportError),
distinctUntilChanged(areLivekitTransportsEqual), distinctUntilChanged(areLivekitTransportsEqual),
), ),
@@ -41,6 +41,7 @@ import {
import * as openIDSFU from "../../../livekit/openIDSFU"; import * as openIDSFU from "../../../livekit/openIDSFU";
import { customLivekitUrl } from "../../../settings/settings"; import { customLivekitUrl } from "../../../settings/settings";
import { testJWTToken } from "../../../utils/test-fixtures"; import { testJWTToken } from "../../../utils/test-fixtures";
import { MatrixRTCMode } from "../../../config/ConfigOptions";
describe("LocalTransport", () => { describe("LocalTransport", () => {
const openIdResponse: openIDSFU.SFUConfig = { const openIdResponse: openIDSFU.SFUConfig = {
@@ -67,7 +68,7 @@ describe("LocalTransport", () => {
getDeviceId: vi.fn(), getDeviceId: vi.fn(),
}, },
ownMembershipIdentity: ownMemberMock, ownMembershipIdentity: ownMemberMock,
forceJwtEndpoint: JwtEndpointVersion.Legacy, matrixRTCMode: MatrixRTCMode.Compatibility,
delayId$: constant("delay_id_mock"), delayId$: constant("delay_id_mock"),
}); });
await flushPromises(); await flushPromises();
@@ -108,7 +109,7 @@ describe("LocalTransport", () => {
getDeviceId: vi.fn(), getDeviceId: vi.fn(),
}, },
ownMembershipIdentity: ownMemberMock, ownMembershipIdentity: ownMemberMock,
forceJwtEndpoint: JwtEndpointVersion.Legacy, matrixRTCMode: MatrixRTCMode.Compatibility,
delayId$: constant("delay_id_mock"), delayId$: constant("delay_id_mock"),
}); });
active$.subscribe( active$.subscribe(
@@ -150,7 +151,7 @@ describe("LocalTransport", () => {
baseUrl: "https://example.org", baseUrl: "https://example.org",
}, },
ownMembershipIdentity: ownMemberMock, ownMembershipIdentity: ownMemberMock,
forceJwtEndpoint: JwtEndpointVersion.Legacy, matrixRTCMode: MatrixRTCMode.Compatibility,
delayId$: constant("delay_id_mock"), delayId$: constant("delay_id_mock"),
}); });
@@ -194,7 +195,7 @@ describe("LocalTransport", () => {
ownMembershipIdentity: ownMemberMock, ownMembershipIdentity: ownMemberMock,
scope: testScope(), scope: testScope(),
roomId: "!example_room_id", roomId: "!example_room_id",
forceJwtEndpoint: JwtEndpointVersion.Legacy, matrixRTCMode: MatrixRTCMode.Compatibility,
delayId$: constant(null), delayId$: constant(null),
memberships$: constant(new Epoch<CallMembership[]>([])), memberships$: constant(new Epoch<CallMembership[]>([])),
client: { client: {
@@ -306,7 +307,7 @@ describe("LocalTransport", () => {
scope: testScope(), scope: testScope(),
ownMembershipIdentity: ownMemberMock, ownMembershipIdentity: ownMemberMock,
roomId: "!example_room_id", roomId: "!example_room_id",
forceJwtEndpoint: JwtEndpointVersion.Legacy, matrixRTCMode: MatrixRTCMode.Compatibility,
delayId$: constant(null), delayId$: constant(null),
memberships$: constant(new Epoch<CallMembership[]>([])), memberships$: constant(new Epoch<CallMembership[]>([])),
client: { client: {
@@ -37,6 +37,7 @@ import {
import { areLivekitTransportsEqual } from "../remoteMembers/MatrixLivekitMembers.ts"; import { areLivekitTransportsEqual } from "../remoteMembers/MatrixLivekitMembers.ts";
import { customLivekitUrl } from "../../../settings/settings.ts"; import { customLivekitUrl } from "../../../settings/settings.ts";
import { RtcTransportAutoDiscovery } from "./RtcTransportAutoDiscovery.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,” * It figures out “which LiveKit focus URL/alias the local user should use,”
@@ -53,15 +54,10 @@ interface Props {
OpenIDClientParts; OpenIDClientParts;
// Used by the jwt service to create the livekit room and compute the livekit alias. // Used by the jwt service to create the livekit room and compute the livekit alias.
roomId: string; roomId: string;
forceJwtEndpoint: JwtEndpointVersion; matrixRTCMode: MatrixRTCMode;
delayId$: Behavior<string | null>; delayId$: Behavior<string | null>;
} }
export enum JwtEndpointVersion {
Legacy = "legacy",
Matrix_2_0 = "matrix_2_0",
}
// TODO livekit_alias-cleanup // TODO livekit_alias-cleanup
// 1. We need to move away from transports map to connections!!! // 1. We need to move away from transports map to connections!!!
// //
@@ -122,7 +118,7 @@ export const createLocalTransport$ = ({
ownMembershipIdentity, ownMembershipIdentity,
client, client,
roomId, roomId,
forceJwtEndpoint, matrixRTCMode,
delayId$, delayId$,
}: Props): LocalTransport => { }: Props): LocalTransport => {
const logger = rootLogger.getChild("[LocalTransport]"); const logger = rootLogger.getChild("[LocalTransport]");
@@ -167,7 +163,7 @@ export const createLocalTransport$ = ({
try { try {
return await doOpenIdAndJWTFromUrl( return await doOpenIdAndJWTFromUrl(
transport, transport,
forceJwtEndpoint, matrixRTCMode,
ownMembershipIdentity, ownMembershipIdentity,
roomId, roomId,
client, client,
@@ -219,7 +215,7 @@ export const createLocalTransport$ = ({
* use we don't want to risk any issues by re-using a token. * use we don't want to risk any issues by re-using a token.
* *
* @param transport The transport to authenticate with. * @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 membership The identity of the local member.
* @param roomId The room ID to use for the JWT. * @param roomId The room ID to use for the JWT.
* @param client The client to use for the OpenID token. * @param client The client to use for the OpenID token.
@@ -229,7 +225,7 @@ export const createLocalTransport$ = ({
*/ */
async function doOpenIdAndJWTFromUrl( async function doOpenIdAndJWTFromUrl(
transport: LivekitTransportConfig, transport: LivekitTransportConfig,
forceJwtEndpoint: JwtEndpointVersion, matrixRTCMode: MatrixRTCMode,
membership: CallMembershipIdentityParts, membership: CallMembershipIdentityParts,
roomId: string, roomId: string,
client: Pick< client: Pick<
@@ -246,7 +242,7 @@ async function doOpenIdAndJWTFromUrl(
transport.livekit_service_url, transport.livekit_service_url,
roomId, roomId,
{ {
forceJwtEndpoint: forceJwtEndpoint, matrixRTCMode,
delayEndpointBaseUrl: client.baseUrl, delayEndpointBaseUrl: client.baseUrl,
delayId, delayId,
}, },
-32
View File
@@ -227,38 +227,6 @@ export function filterBehavior<T, S extends T>(
); );
} }
/**
* 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<Data>,
...keys: Keys
) => Item,
): OperatorFunction<Input, Item> {
return (input$) =>
input$.pipe(
generateItemsInternal(
name,
function* (input) {
yield generator(input);
},
factory,
(items) => items,
),
map(([item]) => item),
);
}
function generateItemsInternal< function generateItemsInternal<
Input, Input,
Keys extends [unknown, ...unknown[]], Keys extends [unknown, ...unknown[]],
+1 -1
View File
@@ -174,7 +174,7 @@ export function getBasicCallViewModelEnvironment(
setE2EEEnabled: async () => Promise.resolve(), setE2EEEnabled: async () => Promise.resolve(),
}), }),
connectionState$: constant(ConnectionState.Connected), connectionState$: constant(ConnectionState.Connected),
matrixRTCMode$: constant(MatrixRTCMode.Compatibility), matrixRTCMode: MatrixRTCMode.Compatibility,
...callViewModelOptions, ...callViewModelOptions,
}, },
handRaisedSubject$, handRaisedSubject$,