2025-11-04 20:24:15 +01:00
|
|
|
/*
|
2025-11-18 10:13:10 +01:00
|
|
|
Copyright 2025 Element Creations Ltd.
|
2025-11-04 20:24:15 +01:00
|
|
|
|
|
|
|
|
SPDX-License-IdFentifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
|
|
|
Please see LICENSE in the repository root for full details.
|
|
|
|
|
*/
|
|
|
|
|
|
2026-09-21 12:46:47 +02:00
|
|
|
import { type LivekitTransportConfig } from "matrix-js-sdk/lib/matrixrtc";
|
2026-04-02 14:38:49 +02:00
|
|
|
import { type MatrixClient } from "matrix-js-sdk";
|
2026-07-07 19:12:11 +02:00
|
|
|
import { logger as rootLogger, type Logger } from "matrix-js-sdk/lib/logger";
|
2025-12-17 09:53:49 +01:00
|
|
|
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
2025-11-04 20:24:15 +01:00
|
|
|
|
2025-11-07 08:44:44 +01:00
|
|
|
import { Config } from "../../../config/Config.ts";
|
2025-12-29 17:45:41 +00:00
|
|
|
import {
|
|
|
|
|
FailToGetOpenIdToken,
|
|
|
|
|
MatrixRTCTransportMissingError,
|
2026-01-09 13:38:26 +01:00
|
|
|
NoMatrix2AuthorizationService,
|
2025-12-29 17:45:41 +00:00
|
|
|
} from "../../../utils/errors.ts";
|
2025-11-20 14:42:12 +01:00
|
|
|
import {
|
|
|
|
|
getSFUConfigWithOpenID,
|
2026-01-09 13:38:26 +01:00
|
|
|
type SFUConfig,
|
2025-11-20 14:42:12 +01:00
|
|
|
type OpenIDClientParts,
|
|
|
|
|
} from "../../../livekit/openIDSFU.ts";
|
2025-11-21 14:02:15 +01:00
|
|
|
import { customLivekitUrl } from "../../../settings/settings.ts";
|
2026-04-02 14:38:49 +02:00
|
|
|
import { RtcTransportAutoDiscovery } from "./RtcTransportAutoDiscovery.ts";
|
2026-09-08 20:50:26 +02:00
|
|
|
import { type MatrixRTCMode } from "../../../config/ConfigOptions.ts";
|
2025-11-21 14:02:15 +01:00
|
|
|
|
2025-11-04 20:24:15 +01:00
|
|
|
interface Props {
|
2025-12-17 09:53:49 +01:00
|
|
|
ownMembershipIdentity: CallMembershipIdentityParts;
|
2026-09-08 20:54:27 +02:00
|
|
|
client: Pick<MatrixClient, "getDomain" | "_unstable_getRTCTransports"> &
|
2025-12-29 17:45:41 +00:00
|
|
|
OpenIDClientParts;
|
2026-01-28 14:22:21 +01:00
|
|
|
// Used by the jwt service to create the livekit room and compute the livekit alias.
|
2025-11-04 20:24:15 +01:00
|
|
|
roomId: string;
|
2026-09-08 20:50:26 +02:00
|
|
|
matrixRTCMode: MatrixRTCMode;
|
2025-11-04 20:24:15 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-13 12:44:17 +01:00
|
|
|
// TODO livekit_alias-cleanup
|
|
|
|
|
// 1. We need to move away from transports map to connections!!!
|
|
|
|
|
//
|
|
|
|
|
// 2. We need to stop sending livekit_alias all together
|
|
|
|
|
//
|
|
|
|
|
//
|
|
|
|
|
// 1.
|
|
|
|
|
// Transports are just the jwt service adress but do not contain the information which room on this transport to use.
|
|
|
|
|
// That requires slot and roomId.
|
|
|
|
|
//
|
|
|
|
|
// We need one connection per room on the transport.
|
|
|
|
|
//
|
|
|
|
|
// We need an object that contains:
|
|
|
|
|
// transport
|
|
|
|
|
// roomId
|
|
|
|
|
// slotId
|
|
|
|
|
//
|
|
|
|
|
// To map to the connections. Prosposal: `ConnectionIdentifier`
|
|
|
|
|
//
|
|
|
|
|
// 2.
|
|
|
|
|
// We need to make sure we do not sent livekit_alias in sticky events and that we drop all code for sending state events!
|
2026-09-21 12:46:47 +02:00
|
|
|
export interface LocalTransport {
|
2026-01-28 14:22:21 +01:00
|
|
|
transport: LivekitTransportConfig;
|
2026-01-09 13:38:26 +01:00
|
|
|
sfuConfig: SFUConfig;
|
|
|
|
|
}
|
2026-02-09 19:07:27 +01:00
|
|
|
|
2026-09-21 12:46:47 +02:00
|
|
|
export function isLocalTransport(
|
|
|
|
|
obj: LivekitTransportConfig | LocalTransport,
|
|
|
|
|
): obj is LocalTransport {
|
2026-01-09 13:38:26 +01:00
|
|
|
return "transport" in obj && "sfuConfig" in obj;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-04 20:24:15 +01:00
|
|
|
/**
|
2026-09-21 12:46:47 +02:00
|
|
|
* Connects to the JWT service and determines the transport that the local member should use.
|
2025-11-04 20:24:15 +01:00
|
|
|
*
|
2026-02-09 19:07:27 +01:00
|
|
|
* @prop useOldJwtEndpoint Whether to set forceOldJwtEndpoint on the returned transport and to use the old JWT endpoint.
|
2025-12-29 17:38:54 +01:00
|
|
|
* This is used when the connection manager needs to know if it has to use the legacy endpoint which implies a string concatenated rtcBackendIdentity.
|
|
|
|
|
* (which is expected for non sticky event based rtc member events)
|
2026-09-21 12:46:47 +02:00
|
|
|
* @returns The transport to advertise in our MatrixRTC membership and publish media on.
|
2025-11-20 14:42:12 +01:00
|
|
|
* @throws MatrixRTCTransportMissingError | FailToGetOpenIdToken
|
2025-11-04 20:24:15 +01:00
|
|
|
*/
|
2026-09-21 12:46:47 +02:00
|
|
|
export async function getLocalTransport({
|
2025-12-17 09:53:49 +01:00
|
|
|
ownMembershipIdentity,
|
2025-11-04 20:24:15 +01:00
|
|
|
client,
|
|
|
|
|
roomId,
|
2026-09-08 20:50:26 +02:00
|
|
|
matrixRTCMode,
|
2026-09-21 12:46:47 +02:00
|
|
|
}: Props): Promise<LocalTransport> {
|
2026-07-07 19:12:11 +02:00
|
|
|
const logger = rootLogger.getChild("[LocalTransport]");
|
2026-09-21 12:46:47 +02:00
|
|
|
const discovery = new RtcTransportAutoDiscovery({
|
2026-04-02 14:38:49 +02:00
|
|
|
client: client,
|
|
|
|
|
resolvedConfig: Config.get(),
|
|
|
|
|
logger: logger,
|
|
|
|
|
});
|
2026-09-21 12:46:47 +02:00
|
|
|
const customUrl = customLivekitUrl.value$.value;
|
2026-04-02 14:38:49 +02:00
|
|
|
|
2026-09-21 12:46:47 +02:00
|
|
|
// Respect the user's custom URL, if set
|
|
|
|
|
const transport: LivekitTransportConfig | null = customUrl
|
|
|
|
|
? { type: "livekit", livekit_service_url: customUrl }
|
|
|
|
|
: await discovery.discoverPreferredTransport();
|
2026-02-09 19:07:27 +01:00
|
|
|
|
2026-09-21 12:46:47 +02:00
|
|
|
if (transport === null)
|
|
|
|
|
throw new MatrixRTCTransportMissingError(client.getDomain() ?? "");
|
2026-04-02 14:38:49 +02:00
|
|
|
|
2026-09-21 12:46:47 +02:00
|
|
|
try {
|
|
|
|
|
return await doOpenIdAndJWTFromUrl(
|
|
|
|
|
transport,
|
|
|
|
|
matrixRTCMode,
|
|
|
|
|
ownMembershipIdentity,
|
|
|
|
|
roomId,
|
|
|
|
|
client,
|
|
|
|
|
logger,
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
logger.error(
|
|
|
|
|
`Failed to authenticate to transport ${transport.livekit_service_url}`,
|
|
|
|
|
e,
|
|
|
|
|
);
|
|
|
|
|
throw mapAuthErrorToUserFriendlyError(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-04 20:24:15 +01:00
|
|
|
|
2026-04-03 18:51:14 +02:00
|
|
|
/**
|
|
|
|
|
* Utility to ensure the user can authenticate with the SFU.
|
|
|
|
|
* We will call `getSFUConfigWithOpenID` once per transport here as it's our
|
|
|
|
|
* only mechanism of validation. This means we will also ask the
|
|
|
|
|
* homeserver for a OpenID token a few times. Since OpenID tokens are single
|
|
|
|
|
* use we don't want to risk any issues by re-using a token.
|
|
|
|
|
*
|
|
|
|
|
* @param transport The transport to authenticate with.
|
2026-09-08 20:50:26 +02:00
|
|
|
* @param matrixRTCMode Whether to force the JWT endpoint to be used.
|
2026-04-03 18:51:14 +02:00
|
|
|
* @param membership The identity of the local member.
|
|
|
|
|
* @param roomId The room ID to use for the JWT.
|
|
|
|
|
* @param client The client to use for the OpenID token.
|
|
|
|
|
*
|
|
|
|
|
* @throws FailToGetOpenIdToken, NoMatrix2AuthorizationService
|
|
|
|
|
*/
|
2026-04-02 14:38:49 +02:00
|
|
|
async function doOpenIdAndJWTFromUrl(
|
2026-04-02 16:09:18 +02:00
|
|
|
transport: LivekitTransportConfig,
|
2026-09-08 20:50:26 +02:00
|
|
|
matrixRTCMode: MatrixRTCMode,
|
2026-04-02 14:38:49 +02:00
|
|
|
membership: CallMembershipIdentityParts,
|
|
|
|
|
roomId: string,
|
2026-09-08 20:54:27 +02:00
|
|
|
client: Pick<MatrixClient, "_unstable_getRTCTransports"> & OpenIDClientParts,
|
2026-07-07 19:45:22 +02:00
|
|
|
logger?: Logger,
|
2026-09-21 12:46:47 +02:00
|
|
|
): Promise<LocalTransport> {
|
2026-04-02 14:38:49 +02:00
|
|
|
const sfuConfig = await getSFUConfigWithOpenID(
|
|
|
|
|
client,
|
|
|
|
|
membership,
|
2026-04-02 16:09:18 +02:00
|
|
|
transport.livekit_service_url,
|
2026-04-02 14:38:49 +02:00
|
|
|
roomId,
|
2026-09-08 20:54:27 +02:00
|
|
|
{ matrixRTCMode },
|
2026-04-02 14:38:49 +02:00
|
|
|
logger,
|
2026-03-31 11:38:21 +02:00
|
|
|
);
|
2026-04-02 14:38:49 +02:00
|
|
|
return {
|
2026-04-02 16:09:18 +02:00
|
|
|
transport,
|
2026-04-02 14:38:49 +02:00
|
|
|
sfuConfig,
|
|
|
|
|
};
|
2025-11-04 20:24:15 +01:00
|
|
|
}
|
2026-04-02 14:48:15 +02:00
|
|
|
|
2026-04-02 16:09:18 +02:00
|
|
|
function mapAuthErrorToUserFriendlyError(e: unknown): Error {
|
|
|
|
|
if (
|
|
|
|
|
e instanceof FailToGetOpenIdToken ||
|
|
|
|
|
e instanceof NoMatrix2AuthorizationService
|
|
|
|
|
) {
|
|
|
|
|
// rethrow as is
|
|
|
|
|
return e;
|
|
|
|
|
}
|
|
|
|
|
// Catch others and rethrow as FailToGetOpenIdToken that has user friendly message.
|
|
|
|
|
return new FailToGetOpenIdToken(
|
|
|
|
|
e instanceof Error ? e : new Error(String(e)),
|
|
|
|
|
);
|
|
|
|
|
}
|