Skip to content
45 changes: 38 additions & 7 deletions src/livekit/openIDSFU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import { FailToGetOpenIdToken } from "../utils/errors";
import { doNetworkOperationWithRetry } from "../utils/matrix";
import { Config } from "../config/Config.ts";

export interface SFUConfig {
url: string;
Expand All @@ -19,7 +20,7 @@
// The bits we need from MatrixClient
export type OpenIDClientParts = Pick<
MatrixClient,
"getOpenIdToken" | "getDeviceId"
"getOpenIdToken" | "getDeviceId" | "baseUrl" | "getUserId"
>;
/**
* Gets a bearer token from the homeserver and then use it to authenticate
Expand All @@ -35,6 +36,7 @@
client: OpenIDClientParts,
serviceUrl: string,
matrixRoomId: string,
delayId: string,
): Promise<SFUConfig> {
let openIdToken: IOpenIDToken;
try {
Expand All @@ -54,6 +56,7 @@
serviceUrl,
matrixRoomId,
openIdToken,
delayId,
);
logger.info(`Got JWT from call's active focus URL.`);

Expand All @@ -65,18 +68,46 @@
livekitServiceURL: string,
roomName: string,
openIDToken: IOpenIDToken,
delayId: string,
): Promise<SFUConfig> {
// TODO: rawId is missing a random component
// Note This random component needs to be stored/present during the lifetime this MatrixRTC client
let rawId = (client.getUserId() ?? "") + client.getDeviceId()

Check failure on line 75 in src/livekit/openIDSFU.ts

View workflow job for this annotation

GitHub Actions / Run unit tests

src/state/CallViewModel/remoteMembers/Connection.test.ts > Start connection states > shutting down the scope should stop the connection

TypeError: client.getUserId is not a function ❯ getLiveKitJWT src/livekit/openIDSFU.ts:75:23 ❯ getSFUConfigWithOpenID src/livekit/openIDSFU.ts:54:27 ❯ Connection.getSFUConfigWithOpenID src/state/CallViewModel/remoteMembers/Connection.ts:174:12 ❯ Connection.start src/state/CallViewModel/remoteMembers/Connection.ts:123:28 ❯ src/state/CallViewModel/remoteMembers/Connection.test.ts:356:5

Check failure on line 75 in src/livekit/openIDSFU.ts

View workflow job for this annotation

GitHub Actions / Run unit tests

src/state/CallViewModel/remoteMembers/Connection.test.ts > Start connection states > connection states happy path

TypeError: client.getUserId is not a function ❯ getLiveKitJWT src/livekit/openIDSFU.ts:75:23 ❯ getSFUConfigWithOpenID src/livekit/openIDSFU.ts:54:27 ❯ Connection.getSFUConfigWithOpenID src/state/CallViewModel/remoteMembers/Connection.ts:174:12 ❯ Connection.start src/state/CallViewModel/remoteMembers/Connection.ts:123:28 ❯ src/state/CallViewModel/remoteMembers/Connection.test.ts:338:5

Check failure on line 75 in src/livekit/openIDSFU.ts

View workflow job for this annotation

GitHub Actions / Run unit tests

Unhandled error

TypeError: client.getUserId is not a function ❯ getLiveKitJWT src/livekit/openIDSFU.ts:75:23 ❯ getSFUConfigWithOpenID src/livekit/openIDSFU.ts:54:27 ❯ processTicksAndRejections node:internal/process/task_queues:103:5 ❯ Connection.getSFUConfigWithOpenID src/state/CallViewModel/remoteMembers/Connection.ts:174:12 ❯ Connection.start src/state/CallViewModel/remoteMembers/Connection.ts:123:28 This error originated in "src/state/CallViewModel/remoteMembers/integration.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.

Check failure on line 75 in src/livekit/openIDSFU.ts

View workflow job for this annotation

GitHub Actions / Run unit tests

Unhandled error

TypeError: client.getUserId is not a function ❯ getLiveKitJWT src/livekit/openIDSFU.ts:75:23 ❯ getSFUConfigWithOpenID src/livekit/openIDSFU.ts:54:27 ❯ processTicksAndRejections node:internal/process/task_queues:103:5 ❯ Connection.getSFUConfigWithOpenID src/state/CallViewModel/remoteMembers/Connection.ts:174:12 ❯ Connection.start src/state/CallViewModel/remoteMembers/Connection.ts:123:28 This error originated in "src/state/CallViewModel/remoteMembers/integration.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.

const utf8 = new TextEncoder().encode(rawId);
let idHashed = await crypto.subtle.digest('SHA-256', utf8).then((hashBuffer) => {
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(bytes => bytes.toString(16).padStart(2, '0')).join('');
return hashHex;
});

let body = {
room_id: roomName,
slot_id: "m.call#ROOM",
openid_token: openIDToken,
member: {
id: idHashed,
claimed_user_id: client.getUserId(),
claimed_device_id: client.getDeviceId(),
}
};

if (delayId) {
const { features, matrix_rtc_session: matrixRtcSessionConfig } = Config.get();
const delay_timeout_ms = matrixRtcSessionConfig?.delayed_leave_event_delay_ms ?? 10000
body = {
...body,
...{delay_id: delayId, delay_timeout: delay_timeout_ms, delay_cs_api_url: client.baseUrl}
}
};

try {
const res = await fetch(livekitServiceURL + "/sfu/get", {
const res = await fetch(livekitServiceURL + "/get_token", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
room: roomName,
openid_token: openIDToken,
device_id: client.getDeviceId(),
}),
body: JSON.stringify(body),
});
if (!res.ok) {
throw new Error("SFU Config fetch failed with status code " + res.status);
Expand Down
1 change: 1 addition & 0 deletions src/state/CallViewModel/CallViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ import { createHomeserverConnected$ } from "./localMember/HomeserverConnected.ts
import {
createLocalMembership$,
enterRTCSession,
MatrixState,
RTCBackendState,
} from "./localMember/LocalMembership.ts";
import { createLocalTransport$ } from "./localMember/LocalTransport.ts";
Expand Down
45 changes: 45 additions & 0 deletions src/state/CallViewModel/localMember/LocalMembership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
import { Config } from "../../../config/Config.ts";
import { type Connection } from "../remoteMembers/Connection.ts";

import { getSFUConfigWithOpenID } from "../../../livekit/openIDSFU.ts"
import { type MatrixClient } from "matrix-js-sdk";

export enum RTCBackendState {
Error = "error",
/** Not even a transport is available to the LocalMembership */
Expand Down Expand Up @@ -580,6 +583,28 @@
};
}

matrixState$.pipe(scope.bind()).subscribe((matrixState) => {
if (matrixState.state !== MatrixState.Connected) return;

// UNSAVE. Arbitrary change some types to read properties we should not have access to (private)
// TODO this is bad and we need a proper solution to expose the delayId (or let the js-sdk take care of delegating the delayed event)

const sessionWithAccessToPrivateMembers = matrixRTCSession as unknown as {
membershipManager: { state: { delayId: string } },
room: { client: MatrixClient, roomId: string } ;
};

const delayId = sessionWithAccessToPrivateMembers.membershipManager.state.delayId;

Check failure on line 597 in src/state/CallViewModel/localMember/LocalMembership.ts

View workflow job for this annotation

GitHub Actions / Run unit tests

Unhandled error

TypeError: Cannot read properties of undefined (reading 'state') ❯ Object.next src/state/CallViewModel/localMember/LocalMembership.ts:597:73 ❯ ConsumerObserver.next node_modules/rxjs/src/internal/Subscriber.ts:155:25 ❯ SafeSubscriber.Subscriber._next node_modules/rxjs/src/internal/Subscriber.ts:113:22 ❯ SafeSubscriber.Subscriber.next node_modules/rxjs/src/internal/Subscriber.ts:71:12 ❯ node_modules/rxjs/src/internal/Subject.ts:67:20 ❯ Object.errorContext node_modules/rxjs/src/internal/util/errorContext.ts:29:5 ❯ BehaviorSubject.Subject.next node_modules/rxjs/src/internal/Subject.ts:60:5 ❯ BehaviorSubject.next node_modules/rxjs/src/internal/BehaviorSubject.ts:35:15 ❯ Object.next src/state/ObservableScope.ts:78:18 ❯ ConsumerObserver.next node_modules/rxjs/src/internal/Subscriber.ts:155:25 This error originated in "src/state/CallViewModel/localMember/LocalMembership.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "tracks livekit state correctly". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.
const roomId = sessionWithAccessToPrivateMembers.room.roomId
const mxClient = sessionWithAccessToPrivateMembers.room.client
const serviceUrl = localConnection$?.value?.transport?.livekit_service_url

//logger.debug("delayId is available", serviceUrl, delayId, roomId, mxClient);
if (serviceUrl) {
void getJWTTokenWithDelaydEventDelegation(serviceUrl, delayId, mxClient, roomId, logger);
}
});

return {
startTracks,
requestConnect,
Expand Down Expand Up @@ -673,3 +698,23 @@
await widget.api.transport.send(ElementWidgetActions.JoinCall, {});
}
}

async function getJWTTokenWithDelaydEventDelegation(
serviceUrl: string,
delayId: string,
client: MatrixClient,
matrixRoomId: string,
logger: Logger,
): Promise<void> {
try {
const sfuConfig = await getSFUConfigWithOpenID(
client,
serviceUrl,
matrixRoomId,
delayId,
);
logger.debug("SFU Config retrieved successfully with delayId:", delayId, sfuConfig);
} catch (error) {
logger.error("Failed to get SFU config with OpenID:", error);
}
}
Loading