mirror of
https://github.com/vector-im/element-call.git
synced 2026-05-22 11:04:38 +00:00
173 lines
5.7 KiB
TypeScript
173 lines
5.7 KiB
TypeScript
/*
|
|
Copyright 2026 Element Creations Ltd.
|
|
|
|
SPDX-License-IdFentifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
import {
|
|
isLivekitTransportConfig,
|
|
type LivekitTransportConfig,
|
|
} from "matrix-js-sdk/lib/matrixrtc";
|
|
import { type IClientWellKnown, type MatrixClient } from "matrix-js-sdk";
|
|
import { type Logger } from "matrix-js-sdk/lib/logger";
|
|
|
|
import type { ResolvedConfigOptions } from "../../../config/ConfigOptions.ts";
|
|
import { doNetworkOperationWithRetry } from "../../../utils/matrix.ts";
|
|
|
|
type TransportDiscoveryClient = Pick<
|
|
MatrixClient,
|
|
"getDomain" | "_unstable_getRTCTransports" | "getAccessToken"
|
|
>;
|
|
|
|
export interface RtcTransportAutoDiscoveryProps {
|
|
client: TransportDiscoveryClient;
|
|
resolvedConfig: ResolvedConfigOptions;
|
|
wellKnownFetcher: (domain: string) => Promise<IClientWellKnown>;
|
|
logger: Logger;
|
|
}
|
|
|
|
export class RtcTransportAutoDiscovery {
|
|
private readonly client: TransportDiscoveryClient;
|
|
private readonly resolvedConfig: ResolvedConfigOptions;
|
|
private readonly wellKnownFetcher: (
|
|
domain: string,
|
|
) => Promise<IClientWellKnown>;
|
|
private readonly logger: Logger;
|
|
|
|
public constructor({
|
|
client,
|
|
resolvedConfig,
|
|
wellKnownFetcher,
|
|
logger,
|
|
}: RtcTransportAutoDiscoveryProps) {
|
|
this.client = client;
|
|
this.resolvedConfig = resolvedConfig;
|
|
this.wellKnownFetcher = wellKnownFetcher;
|
|
this.logger = logger.getChild("[RtcTransportAutoDiscovery]");
|
|
}
|
|
|
|
public async discoverPreferredTransport(): Promise<LivekitTransportConfig | null> {
|
|
// 1) backend transports
|
|
const backendTransport = await this.tryBackendTransports();
|
|
if (backendTransport) {
|
|
this.logger.info(
|
|
`Found backend transport: ${backendTransport.livekit_service_url}`,
|
|
);
|
|
return backendTransport;
|
|
}
|
|
|
|
this.logger.info("No backend transport found, falling back to well-known");
|
|
// 2) .well-known transports
|
|
const wellKnownTransport = await this.tryWellKnownTransports();
|
|
if (wellKnownTransport) {
|
|
this.logger.info(
|
|
`Found .well-known transport: ${wellKnownTransport.livekit_service_url}`,
|
|
);
|
|
return wellKnownTransport;
|
|
}
|
|
|
|
this.logger.info(
|
|
"No .well-known transport found, falling back to app config",
|
|
);
|
|
|
|
// 3) app config URL
|
|
const configTransport = this.tryConfigTransport();
|
|
if (configTransport) {
|
|
this.logger.info(
|
|
`Found app config transport: ${configTransport.livekit_service_url}`,
|
|
);
|
|
return configTransport;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Fetches the first rtc_foci from the backend.
|
|
* This will not throw errors, but instead just log them and return null if the expected config is not found or malformed.
|
|
* @private
|
|
*/
|
|
private async tryBackendTransports(): Promise<LivekitTransportConfig | null> {
|
|
const client = this.client;
|
|
// MSC4143: Attempt to fetch transports from backend.
|
|
// TODO: Workaround for an issue in the js-sdk RoomWidgetClient that
|
|
// is not yet implementing _unstable_getRTCTransports properly (via widget API new action).
|
|
// For now we just skip this call if we are in a widget.
|
|
// In widget mode the client is a `RoomWidgetClient` which has no access token (it is using the widget API).
|
|
// Could be removed once the js-sdk is fixed (https://github.com/matrix-org/matrix-js-sdk/issues/5245)
|
|
const isSPA = !!client.getAccessToken();
|
|
if (isSPA && "_unstable_getRTCTransports" in client) {
|
|
this.logger.info("First try to use getRTCTransports end point ...");
|
|
try {
|
|
const transportList = await doNetworkOperationWithRetry(async () =>
|
|
client._unstable_getRTCTransports(),
|
|
);
|
|
const first = transportList.filter(isLivekitTransportConfig)[0];
|
|
if (first) {
|
|
return first;
|
|
} else {
|
|
this.logger.info(
|
|
`No livekit transport found in getRTCTransports end point`,
|
|
transportList,
|
|
);
|
|
}
|
|
} catch (ex) {
|
|
this.logger.info(`Failed to use getRTCTransports end point: ${ex}`);
|
|
}
|
|
} else {
|
|
this.logger.debug(`getRTCTransports end point not available`);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Fetches the first rtc_foci from the .well-known/matrix/client.
|
|
* This will not throw errors, but instead just log them and return null if the expected config is not found or malformed.
|
|
* @private
|
|
*/
|
|
private async tryWellKnownTransports(): Promise<LivekitTransportConfig | null> {
|
|
// Legacy MSC4143 (to be removed) WELL_KNOWN: Prioritize the .well-known/matrix/client, if available.
|
|
const client = this.client;
|
|
const domain = client.getDomain();
|
|
if (domain) {
|
|
// we use AutoDiscovery instead of relying on the MatrixClient having already
|
|
// been fully configured and started
|
|
|
|
const wellKnownFoci = await this.wellKnownFetcher(domain);
|
|
|
|
const fociConfig = wellKnownFoci["org.matrix.msc4143.rtc_foci"];
|
|
if (fociConfig) {
|
|
if (!Array.isArray(fociConfig)) {
|
|
this.logger.warn(
|
|
`org.matrix.msc4143.rtc_foci is not an array in .well-known`,
|
|
);
|
|
} else {
|
|
return fociConfig[0];
|
|
}
|
|
} else {
|
|
this.logger.info(
|
|
`No .well-known "org.matrix.msc4143.rtc_foci" found for ${domain}`,
|
|
wellKnownFoci,
|
|
);
|
|
}
|
|
} else {
|
|
// Should never happen, but just in case
|
|
this.logger.warn(`No domain configured for client`);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private tryConfigTransport(): LivekitTransportConfig | null {
|
|
const url = this.resolvedConfig.livekit?.livekit_service_url;
|
|
if (url) {
|
|
return {
|
|
type: "livekit",
|
|
livekit_service_url: url,
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
}
|