* Use rtc-focus branch of js-sdk * Update makeTransport to fetch backend transports and validate all transports before response. * Fix test * Add test * Loads more tests * Add tests for openid errors * improve comment * update to develop commit * Add JWT parsing * Use JWT * Cleanup * fixup tests * fixup tests * lint * lint lint * Fix `Reconnecting`
137 lines
3.5 KiB
TypeScript
137 lines
3.5 KiB
TypeScript
/*
|
|
Copyright 2023, 2024 New Vector Ltd.
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
import { type IOpenIDToken, type MatrixClient } from "matrix-js-sdk";
|
|
import { logger } from "matrix-js-sdk/lib/logger";
|
|
|
|
import { FailToGetOpenIdToken } from "../utils/errors";
|
|
import { doNetworkOperationWithRetry } from "../utils/matrix";
|
|
|
|
/**
|
|
* Configuration and access tokens provided by the SFU on successful authentication.
|
|
*/
|
|
export interface SFUConfig {
|
|
url: string;
|
|
jwt: string;
|
|
livekitAlias: string;
|
|
livekitIdentity: string;
|
|
}
|
|
|
|
/**
|
|
* Decoded details from the JWT.
|
|
*/
|
|
interface SFUJWTPayload {
|
|
/**
|
|
* Expiration time for the JWT.
|
|
* Note: This value is in seconds since Unix epoch.
|
|
*/
|
|
exp: number;
|
|
/**
|
|
* Name of the instance which authored the JWT
|
|
*/
|
|
iss: string;
|
|
/**
|
|
* Time at which the JWT can start to be used.
|
|
* Note: This value is in seconds since Unix epoch.
|
|
*/
|
|
nbf: number;
|
|
/**
|
|
* Subject. The Livekit alias in this context.
|
|
*/
|
|
sub: string;
|
|
/**
|
|
* The set of permissions for the user.
|
|
*/
|
|
video: {
|
|
canPublish: boolean;
|
|
canSubscribe: boolean;
|
|
room: string;
|
|
roomJoin: boolean;
|
|
};
|
|
}
|
|
|
|
// The bits we need from MatrixClient
|
|
export type OpenIDClientParts = Pick<
|
|
MatrixClient,
|
|
"getOpenIdToken" | "getDeviceId"
|
|
>;
|
|
/**
|
|
* Gets a bearer token from the homeserver and then use it to authenticate
|
|
* to the matrix RTC backend in order to get acces to the SFU.
|
|
* It has built-in retry for calls to the homeserver with a backoff policy.
|
|
* @param client
|
|
* @param serviceUrl
|
|
* @param matrixRoomId
|
|
* @returns Object containing the token information
|
|
* @throws FailToGetOpenIdToken
|
|
*/
|
|
export async function getSFUConfigWithOpenID(
|
|
client: OpenIDClientParts,
|
|
serviceUrl: string,
|
|
matrixRoomId: string,
|
|
): Promise<SFUConfig> {
|
|
let openIdToken: IOpenIDToken;
|
|
try {
|
|
openIdToken = await doNetworkOperationWithRetry(async () =>
|
|
client.getOpenIdToken(),
|
|
);
|
|
} catch (error) {
|
|
throw new FailToGetOpenIdToken(
|
|
error instanceof Error ? error : new Error("Unknown error"),
|
|
);
|
|
}
|
|
logger.debug("Got openID token", openIdToken);
|
|
|
|
logger.info(`Trying to get JWT for focus ${serviceUrl}...`);
|
|
const sfuConfig = await getLiveKitJWT(
|
|
client,
|
|
serviceUrl,
|
|
matrixRoomId,
|
|
openIdToken,
|
|
);
|
|
logger.info(`Got JWT from call's active focus URL.`);
|
|
|
|
// Pull the details from the JWT
|
|
const [, payloadStr] = sfuConfig.jwt.split(".");
|
|
// TODO: Prefer Uint8Array.fromBase64 when widely available
|
|
const payload = JSON.parse(global.atob(payloadStr)) as SFUJWTPayload;
|
|
return {
|
|
jwt: sfuConfig.jwt,
|
|
url: sfuConfig.url,
|
|
livekitAlias: payload.video.room,
|
|
// NOTE: Currently unused.
|
|
livekitIdentity: payload.sub,
|
|
};
|
|
}
|
|
|
|
async function getLiveKitJWT(
|
|
client: OpenIDClientParts,
|
|
livekitServiceURL: string,
|
|
roomName: string,
|
|
openIDToken: IOpenIDToken,
|
|
): Promise<{ url: string; jwt: string }> {
|
|
try {
|
|
const res = await fetch(livekitServiceURL + "/sfu/get", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
room: roomName,
|
|
openid_token: openIDToken,
|
|
device_id: client.getDeviceId(),
|
|
}),
|
|
});
|
|
if (!res.ok) {
|
|
throw new Error("SFU Config fetch failed with status code " + res.status);
|
|
}
|
|
return await res.json();
|
|
} catch (e) {
|
|
throw new Error("SFU Config fetch failed with exception", { cause: e });
|
|
}
|
|
}
|