diff --git a/src/livekit/openIDSFU.ts b/src/livekit/openIDSFU.ts index 3ae003fb1..87073e65f 100644 --- a/src/livekit/openIDSFU.ts +++ b/src/livekit/openIDSFU.ts @@ -10,6 +10,7 @@ import { logger } from "matrix-js-sdk/lib/logger"; import { FailToGetOpenIdToken } from "../utils/errors"; import { doNetworkOperationWithRetry } from "../utils/matrix"; +import { Config } from "../config/Config.ts"; export interface SFUConfig { url: string; @@ -19,7 +20,7 @@ export interface SFUConfig { // 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 @@ -35,6 +36,7 @@ export async function getSFUConfigWithOpenID( client: OpenIDClientParts, serviceUrl: string, matrixRoomId: string, + delayId: string, ): Promise { let openIdToken: IOpenIDToken; try { @@ -54,6 +56,7 @@ export async function getSFUConfigWithOpenID( serviceUrl, matrixRoomId, openIdToken, + delayId, ); logger.info(`Got JWT from call's active focus URL.`); @@ -65,18 +68,46 @@ async function getLiveKitJWT( livekitServiceURL: string, roomName: string, openIDToken: IOpenIDToken, + delayId: string, ): Promise { + // 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() + + 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); diff --git a/src/state/CallViewModel/CallViewModel.ts b/src/state/CallViewModel/CallViewModel.ts index 5cc33f5d4..065caa104 100644 --- a/src/state/CallViewModel/CallViewModel.ts +++ b/src/state/CallViewModel/CallViewModel.ts @@ -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"; diff --git a/src/state/CallViewModel/localMember/LocalMembership.ts b/src/state/CallViewModel/localMember/LocalMembership.ts index 60ae79b8e..8865aaca1 100644 --- a/src/state/CallViewModel/localMember/LocalMembership.ts +++ b/src/state/CallViewModel/localMember/LocalMembership.ts @@ -53,6 +53,9 @@ import { MatrixRTCMode } from "../../../settings/settings.ts"; 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 */ @@ -580,6 +583,28 @@ export const createLocalMembership$ = ({ }; } + 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; + 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, @@ -673,3 +698,23 @@ export async function enterRTCSession( await widget.api.transport.send(ElementWidgetActions.JoinCall, {}); } } + +async function getJWTTokenWithDelaydEventDelegation( + serviceUrl: string, + delayId: string, + client: MatrixClient, + matrixRoomId: string, + logger: Logger, +): Promise { + 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); + } +}