renames + rewriting docs

This commit is contained in:
Timo K.
2026-10-08 13:53:56 +02:00
parent 1a999eaa54
commit 61e353d919
15 changed files with 1061 additions and 117 deletions
+25 -17
View File
@@ -6,13 +6,12 @@ Please see LICENSE in the repository root for full details.
*/
/**
* The public types of the SDK. `createRtcSession` in `session/RtcSession.ts`
* The public types of the SDK. `createMatrixRTCClient` in `session/MatrixRTCClient.ts`
* is the only way to obtain an implementation of them.
*/
import {
type CallMembership,
type RTCCallIntent,
type RTCNotificationType,
type Transport,
} from "matrix-js-sdk/lib/matrixrtc";
@@ -26,17 +25,26 @@ import { type MatrixRTCMode } from "../../src/config/ConfigOptions";
// ---------------------------------------------------------------------------
// Session
export interface RtcSessionOptions {
export interface MatrixRTCClientOptions {
encryptionSystem: EncryptionSystem;
/** Resolved by the host; the SDK reads neither config.json nor settings. */
matrixRTCMode: MatrixRTCMode;
/**
* MSC4075 notification sent with the join. Parameters of the MatrixRTC join
* itself, so they are here even though they are named after calls; reacting
* MSC4075 notification sent with the join. A parameter of the MatrixRTC
* join itself, so it is here even though it is named after calls; reacting
* to a notification (ringing, timeouts, declines) is the application's job.
*/
sendNotificationType?: RTCNotificationType;
callIntent?: RTCCallIntent;
/** The application the session belongs to, as named in the membership. Default `m.call`. */
application?: string;
/** The application's slot in the room. Default `ROOM`. */
slot?: string;
/**
* Whatever the application wants to say about itself in the membership.
* Opaque to the SDK. Today the js-sdk carries one key, `m.call.intent`
* (`"audio"` or `"video"`); anything else is dropped until it can.
*/
applicationData?: Record<string, unknown>;
}
/** What the local member publishes. */
@@ -49,7 +57,7 @@ export interface LocalMediaInputs {
videoProcessor$: Behavior<TrackProcessor<Track.Kind.Video> | undefined>;
}
export type SessionConnectionStatus =
export type ConnectionStatus =
| "waitingForTransport"
| "connecting"
| "connected"
@@ -60,10 +68,10 @@ export type SessionConnectionStatus =
* An error raised by the session. `cause` holds the underlying error, which
* lets a host that knows the backend tell the failures apart.
*/
export class RtcSessionError extends Error {
export class MatrixRTCError extends Error {
public constructor(message: string, options?: ErrorOptions) {
super(message, options);
this.name = "RtcSessionError";
this.name = "MatrixRTCError";
}
}
@@ -73,18 +81,18 @@ export class RtcSessionError extends Error {
* detached copy `joinRoom` returns for a room joined just now never receives
* the state the members are read from.
*/
export interface RtcSession {
export interface MatrixRTCClient {
join(): void;
leave(): void;
/** Collapsed view of the local member's state machine. */
status$: Behavior<SessionConnectionStatus>;
status$: Behavior<ConnectionStatus>;
connected$: Behavior<boolean>;
reconnecting$: Behavior<boolean>;
/** A transport, Matrix or connection error that stops the session. */
fatalError$: Behavior<RtcSessionError | null>;
fatalError$: Behavior<MatrixRTCError | null>;
localMember$: Behavior<LocalRtcMember | null>;
remoteMembers$: Behavior<RemoteRtcMember[]>;
localMember$: Behavior<LocalRTCMember | null>;
remoteMembers$: Behavior<RemoteRTCMember[]>;
/** `remoteMembers.length`, plus one for the local member once it exists. */
memberCount$: Behavior<number>;
@@ -135,7 +143,7 @@ export type ResolvedTransport =
// ---------------------------------------------------------------------------
// Members
export interface RtcMember {
export interface RTCMember {
local: boolean;
/** The identity the media backend knows this member by. */
id: string;
@@ -153,11 +161,11 @@ export interface RtcMember {
media$: Behavior<MemberMedia | null>;
}
export interface RemoteRtcMember extends RtcMember {
export interface RemoteRTCMember extends RTCMember {
local: false;
}
export interface LocalRtcMember extends RtcMember {
export interface LocalRTCMember extends RTCMember {
local: true;
media$: Behavior<LocalMemberMedia | null>;
sharingScreen$: Behavior<boolean>;
+2 -2
View File
@@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details.
import { type BaseKeyProvider, ExternalE2EEKeyProvider } from "livekit-client";
import { type Logger } from "matrix-js-sdk/lib/logger";
import { type MatrixRTCSession as JsSdkRtcSession } from "matrix-js-sdk/lib/matrixrtc";
import { type MatrixRTCSession as JsSdkRTCSession } from "matrix-js-sdk/lib/matrixrtc";
import { E2eeType } from "../../../src/e2ee/e2eeType";
import { MatrixKeyProvider } from "../../../src/e2ee/matrixKeyProvider";
@@ -16,7 +16,7 @@ import { type EncryptionSystem } from "../../../src/e2ee/sharedKeyManagement";
/** The LiveKit key provider for the encryption the host asked for, or none. */
export function createKeyProvider(
encryptionSystem: EncryptionSystem,
session: JsSdkRtcSession,
session: JsSdkRTCSession,
logger: Logger,
): BaseKeyProvider | undefined {
switch (encryptionSystem.kind) {
+16 -17
View File
@@ -12,7 +12,7 @@ import {
import { type Logger } from "matrix-js-sdk/lib/logger";
import {
type LivekitTransport,
type MatrixRTCSession as JsSdkRtcSession,
type MatrixRTCSession as JsSdkRTCSession,
type Status as RTCSessionStatus,
} from "matrix-js-sdk/lib/matrixrtc";
import { deepCompare } from "matrix-js-sdk/lib/utils";
@@ -41,8 +41,8 @@ import {
ConnectionState,
} from "../../../src/state/CallViewModel/remoteMembers/Connection";
import { type IConnectionManager } from "../../../src/state/CallViewModel/remoteMembers/ConnectionManager";
import { type RtcSessionError } from "../api";
import { toRtcSessionError } from "../utils/errors";
import { type MatrixRTCError } from "../api";
import { toMatrixRTCError } from "../utils/errors";
import { type LocalTransport } from "./LocalTransport";
import { type Publisher } from "./Publisher";
@@ -56,16 +56,16 @@ export enum PublishState {
}
export type LocalMemberMediaState =
| { connection: ConnectionState | RtcSessionError }
| { connection: ConnectionState | MatrixRTCError }
| PublishState
| RtcSessionError;
| MatrixRTCError;
export type LocalMemberState =
| RtcSessionError
| MatrixRTCError
| TransportState.Waiting
| {
media: LocalMemberMediaState;
matrix: RtcSessionError | RTCSessionStatus;
matrix: MatrixRTCError | RTCSessionStatus;
};
interface Props {
@@ -78,7 +78,7 @@ interface Props {
/** The membership manager giving up on keeping the membership alive. */
membershipManagerError$: Observable<unknown>;
matrixRTCSession: Pick<
JsSdkRtcSession,
JsSdkRTCSession,
"updateCallIntent" | "leaveRoomSession"
>;
cameraEnabled$: Behavior<boolean>;
@@ -121,10 +121,10 @@ export function createLocalMembership$({
}: Props): LocalMembership {
const logger = parentLogger.getChild("[LocalMember]");
const fatalTransportError$ = new Subject<RtcSessionError>();
const fatalTransportError$ = new Subject<MatrixRTCError>();
const localTransport$ = localTransportWithErrors$.pipe(
catchError((e: unknown) => {
fatalTransportError$.next(toRtcSessionError(e));
fatalTransportError$.next(toMatrixRTCError(e));
return NEVER;
}),
);
@@ -147,8 +147,8 @@ export function createLocalMembership$({
const joinRequested$ = new BehaviorSubject(false);
const publisher$ = new BehaviorSubject<Publisher | null>(null);
const publishError$ = new BehaviorSubject<RtcSessionError | null>(null);
const matrixError$ = new BehaviorSubject<RtcSessionError | null>(null);
const publishError$ = new BehaviorSubject<MatrixRTCError | null>(null);
const matrixError$ = new BehaviorSubject<MatrixRTCError | null>(null);
scope.reconcile(connection$, async (connection) => {
if (connection === null) return;
@@ -171,7 +171,7 @@ export function createLocalMembership$({
} else if (publisher.shouldPublish) await publisher.stopPublishing();
} catch (e) {
if (publishError$.value === null)
publishError$.next(toRtcSessionError(e));
publishError$.next(toMatrixRTCError(e));
else logger.error("Another publish error", e);
}
},
@@ -185,8 +185,7 @@ export function createLocalMembership$({
joinMatrixRTC(transport.transport);
} catch (e) {
logger.error("Failed to enter the session", e);
if (matrixError$.value === null)
matrixError$.next(toRtcSessionError(e));
if (matrixError$.value === null) matrixError$.next(toMatrixRTCError(e));
}
return Promise.resolve(async (): Promise<void> => {
try {
@@ -200,7 +199,7 @@ export function createLocalMembership$({
membershipManagerError$.pipe(scope.bind()).subscribe((e) => {
logger.error("The membership manager stopped", e);
if (matrixError$.value === null) matrixError$.next(toRtcSessionError(e));
if (matrixError$.value === null) matrixError$.next(toMatrixRTCError(e));
});
cameraEnabled$.pipe(scope.bind()).subscribe((videoEnabled) => {
@@ -224,7 +223,7 @@ export function createLocalMembership$({
return {
connection:
connectionState instanceof Error
? toRtcSessionError(connectionState)
? toMatrixRTCError(connectionState)
: (connectionState ?? ConnectionState.Initialized),
};
return shouldPublish
@@ -6,8 +6,11 @@ Please see LICENSE in the repository root for full details.
*/
import { type MatrixClient, type Room } from "matrix-js-sdk";
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
import { MatrixRTCSessionEvent } from "matrix-js-sdk/lib/matrixrtc";
import { type Logger, logger as rootLogger } from "matrix-js-sdk/lib/logger";
import {
MatrixRTCSessionEvent,
MatrixRTCSessionManager,
} from "matrix-js-sdk/lib/matrixrtc";
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
import { v4 as uuidv4 } from "uuid";
import { combineLatest, from, fromEvent, map } from "rxjs";
@@ -29,20 +32,20 @@ import {
import { filterBehavior, generateItems } from "../../../src/utils/observable";
import {
type LocalMediaInputs,
type LocalRtcMember,
type RemoteRtcMember,
type RtcSession,
RtcSessionError,
type RtcSessionOptions,
type LocalRTCMember,
type RemoteRTCMember,
type MatrixRTCClient,
MatrixRTCError,
type MatrixRTCClientOptions,
} from "../api";
import { LivekitConnectionFactory } from "./ConnectionFactory";
import { joinRtcSession, sessionTimings } from "./joinRtcSession";
import { joinJsSdkSession, sessionTimings } from "./joinJsSdkSession";
import { createKeyProvider } from "./KeyProvider";
import { createLocalMembership$ } from "./LocalMember";
import { getLocalTransport } from "./LocalTransport";
import {
createLocalRtcMember,
createRemoteRtcMember,
createLocalRTCMember,
createRemoteRTCMember,
membershipKeys,
} from "./Members";
import { Publisher } from "./Publisher";
@@ -57,21 +60,26 @@ import { createTransportRegistry } from "./Transports";
* js-sdk client, and only the SDK knows what that bridge needs. Holding the
* client keeps that change inside the SDK.
*/
export function createRtcSession(
export function createMatrixRTCClient(
scope: ObservableScope,
client: MatrixClient,
room: Room,
localMedia: LocalMediaInputs,
options: RtcSessionOptions,
): RtcSession {
const logger = rootLogger.getChild("[RtcSession]");
options: MatrixRTCClientOptions,
): MatrixRTCClient {
const logger = rootLogger.getChild("[MatrixRTCClient]");
const userId = client.getUserId();
const deviceId = client.getDeviceId();
if (!(userId && deviceId))
throw new RtcSessionError("The client has to be logged in");
throw new MatrixRTCError("The client has to be logged in");
const { encryptionSystem, matrixRTCMode } = options;
const jsSdkSession = client.matrixRTC.getRoomSession(room);
const jsSdkSession = sessionManager(
scope,
client,
options,
logger,
).getRoomSession(room);
const keyProvider = createKeyProvider(encryptionSystem, jsSdkSession, logger);
const ownMembershipIdentity: CallMembershipIdentityParts = {
@@ -136,11 +144,11 @@ export function createRtcSession(
),
),
joinMatrixRTC: (transport) =>
joinRtcSession(jsSdkSession, ownMembershipIdentity, transport, {
joinJsSdkSession(jsSdkSession, ownMembershipIdentity, transport, {
encryptMedia: keyProvider !== undefined,
matrixRTCMode,
sendNotificationType: options.sendNotificationType,
callIntent: options.callIntent,
applicationData: options.applicationData,
}),
membershipManagerError$: fromEvent(
jsSdkSession,
@@ -161,7 +169,7 @@ export function createRtcSession(
encryptionSystem,
};
const remoteMembers$ = scope.behavior<RemoteRtcMember[]>(
const remoteMembers$ = scope.behavior<RemoteRTCMember[]>(
createRemoteMatrixLivekitMembers$({
scope,
membershipsWithTransport$,
@@ -170,7 +178,7 @@ export function createRtcSession(
}).pipe(
map(({ value }) => value),
generateItems(
"RtcSession remoteMembers",
"MatrixRTCClient remoteMembers",
function* (members) {
for (const member of members)
yield {
@@ -179,7 +187,7 @@ export function createRtcSession(
};
},
(memberScope, member$) =>
createRemoteRtcMember(memberScope, member$.value, context),
createRemoteRTCMember(memberScope, member$.value, context),
),
),
);
@@ -196,9 +204,9 @@ export function createRtcSession(
filterBehavior((membership) => membership !== null),
),
);
const localMember$ = scope.behavior<LocalRtcMember | null>(
const localMember$ = scope.behavior<LocalRTCMember | null>(
mapScoped(scope, localMembership$, (memberScope, membership$) =>
createLocalRtcMember(memberScope, membership$, localMembership, context),
createLocalRTCMember(memberScope, membership$, localMembership, context),
).pipe(map((member) => member ?? null)),
);
@@ -233,3 +241,24 @@ export function createRtcSession(
connectedTransports$: transports.connected$,
};
}
/**
* The js-sdk keeps one session manager per slot. The client comes with the
* manager for the default slot; any other slot gets a manager of its own,
* which lives as long as the scope.
*/
function sessionManager(
scope: ObservableScope,
client: MatrixClient,
{ application = "m.call", slot = "ROOM" }: MatrixRTCClientOptions,
logger: Logger,
): MatrixRTCSessionManager {
if (application === "m.call" && slot === "ROOM") return client.matrixRTC;
const manager = new MatrixRTCSessionManager(logger, client, {
application,
id: slot,
});
manager.start();
scope.onEnd(() => manager.stop());
return manager;
}
+11 -11
View File
@@ -20,10 +20,10 @@ import { type createMatrixMemberMetadata$ } from "../../../src/state/CallViewMod
import { type RemoteMatrixLivekitMember } from "../../../src/state/CallViewModel/remoteMembers/MatrixLivekitMembers";
import { type EncryptionSystem } from "../../../src/e2ee/sharedKeyManagement";
import {
type LocalRtcMember,
type LocalRTCMember,
type MemberMedia,
type RemoteRtcMember,
type RtcMember,
type RemoteRTCMember,
type RTCMember,
} from "../api";
import {
createLivekitMemberMedia,
@@ -51,13 +51,13 @@ export function membershipKeys(
];
}
export function createRemoteRtcMember(
export function createRemoteRTCMember(
scope: ObservableScope,
member: RemoteMatrixLivekitMember,
context: MemberContext,
): RemoteRtcMember {
): RemoteRTCMember {
return {
...createRtcMember(scope, member.membership$, context),
...createRTCMember(scope, member.membership$, context),
local: false,
media$: mediaFor(
scope,
@@ -74,14 +74,14 @@ export function createRemoteRtcMember(
};
}
export function createLocalRtcMember(
export function createLocalRTCMember(
scope: ObservableScope,
membership$: Behavior<CallMembership>,
localMembership: LocalMembership,
context: MemberContext,
): LocalRtcMember {
): LocalRTCMember {
return {
...createRtcMember(scope, membership$, context),
...createRTCMember(scope, membership$, context),
local: true,
media$: mediaFor(
scope,
@@ -102,11 +102,11 @@ export function createLocalRtcMember(
};
}
function createRtcMember(
function createRTCMember(
scope: ObservableScope,
membership$: Behavior<CallMembership>,
{ metadata, transports }: MemberContext,
): Omit<RtcMember, "local" | "media$"> {
): Omit<RTCMember, "local" | "media$"> {
const { userId, deviceId, rtcBackendIdentity } = membership$.value;
return {
id: rtcBackendIdentity,
@@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details.
import {
type LivekitTransport,
type MatrixRTCSession as JsSdkRtcSession,
type MatrixRTCSession as JsSdkRTCSession,
type RTCCallIntent,
type RTCNotificationType,
} from "matrix-js-sdk/lib/matrixrtc";
@@ -22,7 +22,7 @@ interface Options {
encryptMedia: boolean;
matrixRTCMode: MatrixRTCMode;
sendNotificationType?: RTCNotificationType;
callIntent?: RTCCallIntent;
applicationData?: Record<string, unknown>;
}
/** The session's timing defaults; a later slice makes them an input. */
@@ -36,12 +36,21 @@ export const sessionTimings = {
* alive and retries on failure.
* @throws If the join could not be started.
*/
export function joinRtcSession(
session: JsSdkRtcSession,
export function joinJsSdkSession(
session: JsSdkRTCSession,
ownMembershipIdentity: CallMembershipIdentityParts,
transport: LivekitTransport,
{ encryptMedia, matrixRTCMode, sendNotificationType, callIntent }: Options,
{
encryptMedia,
matrixRTCMode,
sendNotificationType,
applicationData,
}: Options,
): void {
// The one piece of application data the js-sdk can put in a membership
const intent = applicationData?.["m.call.intent"];
const callIntent =
typeof intent === "string" ? (intent as RTCCallIntent) : undefined;
const timings = sessionTimings.delayed_leave;
// Give up on the network as soon as either the sync has been down for the
// grace period or the delayed leave has probably been sent, whichever is
+2 -2
View File
@@ -9,7 +9,7 @@ import { describe, expect, it } from "vitest";
import { Status } from "matrix-js-sdk/lib/matrixrtc";
import { ConnectionState } from "../../../src/state/CallViewModel/remoteMembers/Connection";
import { RtcSessionError } from "../api";
import { MatrixRTCError } from "../api";
import {
type LocalMemberState,
PublishState,
@@ -25,7 +25,7 @@ const connecting: LocalMemberState = {
media: { connection: ConnectionState.LivekitConnecting },
matrix: Status.Connecting,
};
const error = new RtcSessionError("gone");
const error = new MatrixRTCError("gone");
describe("sessionStatus", () => {
it.each<[string, LocalMemberState, boolean, boolean, boolean, string]>([
+3 -3
View File
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { type RtcSessionError, type SessionConnectionStatus } from "../api";
import { type MatrixRTCError, type ConnectionStatus } from "../api";
import { type LocalMemberState, TransportState } from "./LocalMember";
/** The local member's state, collapsed to what a host shows. */
@@ -14,7 +14,7 @@ export function sessionStatus(
joinRequested: boolean,
connected: boolean,
reconnecting: boolean,
): SessionConnectionStatus {
): ConnectionStatus {
if (fatalError(state) !== null) return "disconnected";
if (state === TransportState.Waiting) return "waitingForTransport";
if (connected) return "connected";
@@ -23,7 +23,7 @@ export function sessionStatus(
}
/** The error that stops the session, if the state holds one. */
export function fatalError(state: LocalMemberState): RtcSessionError | null {
export function fatalError(state: LocalMemberState): MatrixRTCError | null {
if (state === TransportState.Waiting) return null;
if (state instanceof Error) return state;
if (state.matrix instanceof Error) return state.matrix;
+10 -10
View File
@@ -7,29 +7,29 @@ Please see LICENSE in the repository root for full details.
import { describe, expect, it } from "vitest";
import { RtcSessionError } from "../api";
import { toRtcSessionError } from "./errors";
import { MatrixRTCError } from "../api";
import { toMatrixRTCError } from "./errors";
describe("toRtcSessionError", () => {
it("keeps an RtcSessionError as it is", () => {
const error = new RtcSessionError("gone");
expect(toRtcSessionError(error)).toBe(error);
describe("toMatrixRTCError", () => {
it("keeps an MatrixRTCError as it is", () => {
const error = new MatrixRTCError("gone");
expect(toMatrixRTCError(error)).toBe(error);
});
it("wraps another error as the cause", () => {
const cause = new Error("boom");
const error = toRtcSessionError(cause);
expect(error).toBeInstanceOf(RtcSessionError);
const error = toMatrixRTCError(cause);
expect(error).toBeInstanceOf(MatrixRTCError);
expect(error.message).toBe("boom");
expect(error.cause).toBe(cause);
});
it("falls back to the code of an error without a message", () => {
const cause = Object.assign(new Error(""), { code: "SFU_ERROR" });
expect(toRtcSessionError(cause).message).toBe("SFU_ERROR");
expect(toMatrixRTCError(cause).message).toBe("SFU_ERROR");
});
it("stringifies anything else", () => {
expect(toRtcSessionError("nope").message).toBe("nope");
expect(toMatrixRTCError("nope").message).toBe("nope");
});
});
+5 -5
View File
@@ -5,14 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { RtcSessionError } from "../api";
import { MatrixRTCError } from "../api";
/** Wraps whatever a backend threw into the SDK's error type, keeping it as the cause. */
export function toRtcSessionError(error: unknown): RtcSessionError {
if (error instanceof RtcSessionError) return error;
export function toMatrixRTCError(error: unknown): MatrixRTCError {
if (error instanceof MatrixRTCError) return error;
if (error instanceof Error)
return new RtcSessionError(messageOf(error), { cause: error });
return new RtcSessionError(String(error));
return new MatrixRTCError(messageOf(error), { cause: error });
return new MatrixRTCError(String(error));
}
/**