Compare commits

...
Author SHA1 Message Date
Valere 0a9798f522 do not retry on cors 2026-03-20 09:29:57 +01:00
Valere 29720d4376 make logger mandatory add some logs 2026-03-20 08:05:22 +01:00
Valere f244a00d6c sfu config: add some network retry and more logging 2026-03-19 18:53:34 +01:00
7 changed files with 65 additions and 23 deletions
+6
View File
@@ -15,6 +15,7 @@ import {
vitest,
} from "vitest";
import fetchMock from "fetch-mock";
import { logger } from "matrix-js-sdk/lib/logger";
import { getSFUConfigWithOpenID, type OpenIDClientParts } from "./openIDSFU";
import { testJWTToken } from "../utils/test-fixtures";
@@ -47,6 +48,7 @@ describe("getSFUConfigWithOpenID", () => {
ownMemberMock,
"https://sfu.example.org",
"!example_room_id",
logger,
);
expect(config).toEqual({
jwt: testJWTToken,
@@ -70,6 +72,7 @@ describe("getSFUConfigWithOpenID", () => {
ownMemberMock,
"https://sfu.example.org",
"!example_room_id",
logger,
);
} catch (ex) {
expect((ex as Error).message).toEqual(
@@ -100,6 +103,7 @@ describe("getSFUConfigWithOpenID", () => {
ownMemberMock,
"https://sfu.example.org",
"!example_room_id",
logger,
{
delayEndpointBaseUrl: "https://matrix.homeserverserver.org",
delayId: "mock_delay_id",
@@ -154,6 +158,7 @@ describe("getSFUConfigWithOpenID", () => {
ownMemberMock,
"https://sfu.example.org",
"!example_room_id",
logger,
{
delayEndpointBaseUrl: "https://matrix.homeserverserver.org",
delayId: "mock_delay_id",
@@ -204,6 +209,7 @@ describe("getSFUConfigWithOpenID", () => {
ownMemberMock,
"https://sfu.example.org",
"!example_room_id",
logger,
);
expect(config).toEqual({
jwt: testJWTToken,
+17 -9
View File
@@ -78,6 +78,7 @@ export type OpenIDClientParts = Pick<
* @param membership Our own membership identity parts used to send to jwt 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 logger optional logger.
* @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.
@@ -86,7 +87,6 @@ export type OpenIDClientParts = Pick<
* 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.
* @returns Object containing the token information
* @throws FailToGetOpenIdToken
*/
@@ -95,12 +95,12 @@ export async function getSFUConfigWithOpenID(
membership: CallMembershipIdentityParts,
serviceUrl: string,
roomId: string,
logger: Logger,
opts?: {
forceJwtEndpoint?: JwtEndpointVersion;
delayEndpointBaseUrl?: string;
delayId?: string;
},
logger?: Logger,
): Promise<SFUConfig> {
let openIdToken: IOpenIDToken;
try {
@@ -108,13 +108,12 @@ export async function getSFUConfigWithOpenID(
client.getOpenIdToken(),
);
} catch (error) {
logger.error("Failed to get openID token", 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}...`);
logger.debug("Got openID token", { ...openIdToken, access_token: "XXXXX" });
let sfuConfig: { url: string; jwt: string } | undefined;
@@ -123,6 +122,11 @@ export async function getSFUConfigWithOpenID(
const forceMatrix2Jwt =
opts?.forceJwtEndpoint === JwtEndpointVersion.Matrix_2_0;
logger.debug(
`Trying to get JWT for focus ${serviceUrl} / forceMatrix2Jwt:${forceMatrix2Jwt}...`,
opts,
);
// 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 (tryBothJwtEndpoints || forceMatrix2Jwt) {
@@ -135,16 +139,17 @@ export async function getSFUConfigWithOpenID(
opts?.delayEndpointBaseUrl,
opts?.delayId,
);
logger?.info(`Got JWT from call's active focus URL.`);
logger.info(`Got JWT from call's active focus URL.`);
} catch (e) {
logger.warn(`Failed fetching jwt with matrix 2.0 endpoint`, e);
if (e instanceof NotSupportedError) {
logger?.warn(
logger.warn(
`Failed fetching jwt with matrix 2.0 endpoint (retry with legacy) Not supported`,
e,
);
sfuConfig = undefined;
} else {
logger?.warn(
logger.warn(
`Failed fetching jwt with matrix 2.0 endpoint other issues ->`,
`(not going to try with legacy endpoint: forceOldJwtEndpoint is set to false, we did not get a not supported error from the sfu)`,
e,
@@ -161,13 +166,16 @@ export async function getSFUConfigWithOpenID(
// here we either have a sfuConfig or we alredy exited because of `if (forceMatrix2) throw ...`
// The only case we can get into this condition is, if `forceMatrix2` is `false`
if (sfuConfig === undefined) {
logger.debug(
`Trying to get JWT for focus ${serviceUrl} (legacy endpoint)...`,
);
sfuConfig = await getLiveKitJWT(
membership.deviceId,
serviceUrl,
roomId,
openIdToken,
);
logger?.info(`Got JWT from call's active focus URL.`);
logger.info(`Got JWT from call's active focus URL.`);
}
if (!sfuConfig) {
@@ -214,6 +214,7 @@ describe("DeveloperSettingsTab", () => {
expect.anything(),
"wss://example.livekit.valid",
"#testRoom",
expect.anything(),
);
expect(customLivekitUrlSetting.getValue()).toBe(
+1
View File
@@ -262,6 +262,7 @@ export const DeveloperSettingsTab: FC<Props> = ({
{ userId, deviceId, memberId: "" },
customLivekitUrlTextBuffer,
roomId,
logger,
);
setCustomLivekitUrlUpdateError(null);
setCustomLivekitUrl(customLivekitUrlTextBuffer);
@@ -42,6 +42,7 @@ import {
} from "../../../livekit/openIDSFU.ts";
import { areLivekitTransportsEqual } from "../remoteMembers/MatrixLivekitMembers.ts";
import { customLivekitUrl } from "../../../settings/settings.ts";
import { doNetworkOperationWithRetry } from "../../../utils/matrix.ts";
const logger = rootLogger.getChild("[LocalTransport]");
@@ -256,8 +257,8 @@ export const createLocalTransport$ = ({
ownMembershipIdentity,
transport.livekit_service_url,
roomId,
{ forceJwtEndpoint: JwtEndpointVersion.Legacy },
logger,
{ forceJwtEndpoint: JwtEndpointVersion.Legacy },
),
});
return from(fetchOldestMemberTransport()).pipe(startWith(null));
@@ -323,7 +324,9 @@ async function makeTransport(
forceJwtEndpoint: JwtEndpointVersion,
delayId?: string,
): Promise<LocalTransportWithSFUConfig> {
logger.trace("Searching for a preferred transport");
logger.trace(
`Searching for a preferred transport, forceJwtEndpoint: ${forceJwtEndpoint}`,
);
async function doOpenIdAndJWTFromUrl(
url: string,
@@ -333,12 +336,12 @@ async function makeTransport(
membership,
url,
roomId,
logger,
{
forceJwtEndpoint: forceJwtEndpoint,
delayEndpointBaseUrl: client.baseUrl,
delayId,
},
logger,
);
return {
transport: {
@@ -371,11 +374,18 @@ async function makeTransport(
for (const potentialTransport of transports) {
if (isLivekitTransportConfig(potentialTransport)) {
try {
logger.debug(
`Checking auth for transport: ${potentialTransport.livekit_service_url} `,
);
// This will call the jwt/sfu/get endpoint to pre create the livekit room.
return await doOpenIdAndJWTFromUrl(
potentialTransport.livekit_service_url,
);
} catch (ex) {
logger.error(
`Could not use SFU service "${potentialTransport.livekit_service_url}" as SFU`,
ex,
);
// Explictly throw these
if (ex instanceof FailToGetOpenIdToken) {
throw ex;
@@ -383,11 +393,12 @@ async function makeTransport(
if (ex instanceof NoMatrix2AuthorizationService) {
throw ex;
}
logger.debug(
`Could not use SFU service "${potentialTransport.livekit_service_url}" as SFU`,
ex,
);
}
} else {
logger.debug(
`Unsupported transport type: ${potentialTransport.type}, skipping: `,
potentialTransport,
);
}
}
return null;
@@ -396,7 +407,13 @@ async function makeTransport(
// MSC4143: Attempt to fetch transports from backend.
if ("_unstable_getRTCTransports" in client) {
try {
const transportList = await client._unstable_getRTCTransports();
const transportList = await doNetworkOperationWithRetry(
async () => await client._unstable_getRTCTransports(),
);
logger.info(
`Got ${transportList.length} transports from backend-configured endpoint: `,
transportList,
);
const selectedTransport = await getFirstUsableTransport(transportList);
if (selectedTransport) {
logger.info(
@@ -409,10 +426,11 @@ async function makeTransport(
if (ex instanceof MatrixError && ex.httpStatus === 404) {
// Expected, this is an unstable endpoint and it's not required.
// There will be expected 404 errors in the console. When we check if synapse supports the endpoint.
logger.debug(
logger.info(
"Matrix homeserver does not provide any RTC transports via `/rtc/transports` (will retry with well-known.)",
);
} else if (ex instanceof FailToGetOpenIdToken) {
logger.warn(`Failed to validate backend-configured SFU: ${ex}`);
throw ex;
} else {
// We got an error that wasn't just missing support for the feature, so log it loudly.
@@ -427,21 +445,28 @@ async function makeTransport(
// Legacy MSC4143 (to be removed) WELL_KNOWN: Prioritize the .well-known/matrix/client, if available.
const domain = client.getDomain();
if (domain) {
logger.debug("Fetching .well-known SFU from ", domain);
// we use AutoDiscovery instead of relying on the MatrixClient having already
// been fully configured and started
const wellKnownFoci = (await AutoDiscovery.getRawClientConfig(domain))?.[
FOCI_WK_KEY
];
const wellKnown = await doNetworkOperationWithRetry(
async () => await AutoDiscovery.getRawClientConfig(domain),
);
const wellKnownFoci = wellKnown?.[FOCI_WK_KEY];
const selectedTransport = Array.isArray(wellKnownFoci)
? await getFirstUsableTransport(wellKnownFoci)
: null;
if (selectedTransport) {
logger.info("Using .well-known SFU", selectedTransport);
return selectedTransport;
} else {
logger.info("No .well-known usable SFU found in ", wellKnown);
}
}
// CONFIG: Least prioritized; Load from config file
logger.debug("Last fallback fetching SFU from config");
const urlFromConf = Config.get().livekit?.livekit_service_url;
if (urlFromConf) {
try {
@@ -275,9 +275,9 @@ export class Connection {
this.ownMembershipIdentity,
this.transport.livekit_service_url,
this.roomId,
this.logger,
// dont pass any custom opts for the subscribe only connections
{},
this.logger,
);
}
+2 -1
View File
@@ -353,7 +353,8 @@ export async function doNetworkOperationWithRetry<T>(
return await operation();
} catch (e) {
currentRetryCount++;
const backoff = calculateRetryBackoff(e, currentRetryCount, true);
const backoff = calculateRetryBackoff(e, currentRetryCount,
false /** Do not retry CORS */);
if (backoff < 0) {
// Max number of retries reached, or error is not retryable. rethrow the error
throw e;