mirror of
https://github.com/vector-im/element-call.git
synced 2026-10-16 23:45:50 +00:00
Retain compatiblity in compatiblity mode
This commit is contained in:
@@ -8,6 +8,7 @@ Please see LICENSE in the repository root for full details.
|
||||
import { expect, type Page, test } from "@playwright/test";
|
||||
|
||||
import { SpaHelpers } from "./spa-helpers";
|
||||
import { type RtcMode } from "./widget/test-helpers.ts";
|
||||
|
||||
const sfuUrl = "wss://matrix-rtc.m.localhost/livekit/sfu";
|
||||
const serviceUrl = "https://matrix-rtc.m.localhost/livekit/jwt";
|
||||
@@ -41,6 +42,8 @@ type Flow = "cs-api" | "service-legacy" | "service-default";
|
||||
const cases: {
|
||||
hostShape: Shape;
|
||||
guestShape: Shape;
|
||||
/** The MatrixRTC mode both sides use; the default is compatibility mode. */
|
||||
mode?: RtcMode;
|
||||
hostFlows: Flow[];
|
||||
guestFlows: Flow[];
|
||||
}[] = [
|
||||
@@ -51,8 +54,19 @@ const cases: {
|
||||
guestFlows: ["cs-api"],
|
||||
},
|
||||
{
|
||||
// In compatibility mode both publish a membership state event, so both
|
||||
// must keep the legacy participant identity that clients predating MSC4195
|
||||
// expect, which only the JWT service assigns.
|
||||
hostShape: "both",
|
||||
guestShape: "both",
|
||||
hostFlows: ["service-legacy"],
|
||||
guestFlows: ["service-legacy"],
|
||||
},
|
||||
{
|
||||
// In Matrix 2.0 mode nobody expects a legacy identity, so the CS API wins.
|
||||
hostShape: "both",
|
||||
guestShape: "both",
|
||||
mode: "2_0",
|
||||
hostFlows: ["cs-api"],
|
||||
guestFlows: ["cs-api"],
|
||||
},
|
||||
@@ -64,8 +78,8 @@ const cases: {
|
||||
},
|
||||
];
|
||||
|
||||
for (const { hostShape, guestShape, hostFlows, guestFlows } of cases) {
|
||||
test(`One to one call with ${hostShape} host and ${guestShape} guest transport`, async ({
|
||||
for (const { hostShape, guestShape, mode, hostFlows, guestFlows } of cases) {
|
||||
test(`One to one call with ${hostShape} host and ${guestShape} guest transport${mode === "2_0" ? " in Matrix 2.0 mode" : ""}`, async ({
|
||||
browser,
|
||||
page,
|
||||
browserName,
|
||||
@@ -76,7 +90,7 @@ for (const { hostShape, guestShape, hostFlows, guestFlows } of cases) {
|
||||
await advertiseTransport(page, shapes[hostShape]);
|
||||
const hostRequests = recordAuthRequests(page);
|
||||
await page.goto("/");
|
||||
await SpaHelpers.createCall(page, "Androl", "HelloCall", true);
|
||||
await SpaHelpers.createCall(page, "Androl", "HelloCall", true, mode);
|
||||
const inviteLink = await SpaHelpers.getCallInviteLink(page);
|
||||
|
||||
// The guest joins.
|
||||
@@ -84,7 +98,12 @@ for (const { hostShape, guestShape, hostFlows, guestFlows } of cases) {
|
||||
const guestPage = await guestContext.newPage();
|
||||
await advertiseTransport(guestPage, shapes[guestShape]);
|
||||
const guestRequests = recordAuthRequests(guestPage);
|
||||
await SpaHelpers.joinCallFromInviteLink(guestPage, inviteLink, "Pevara");
|
||||
await SpaHelpers.joinCallFromInviteLink(
|
||||
guestPage,
|
||||
inviteLink,
|
||||
"Pevara",
|
||||
mode,
|
||||
);
|
||||
|
||||
// Wait for the call to connect and render.
|
||||
await SpaHelpers.expectVideoTilesCount(page, 2);
|
||||
|
||||
@@ -21,6 +21,7 @@ import { delegateDelayedLeave, getSFUConfig } from ".";
|
||||
import { mocked } from "storybook/test";
|
||||
import { ConnectionError, MatrixError } from "matrix-js-sdk";
|
||||
import { NoMatrix2AuthorizationService } from "../../utils/errors";
|
||||
import { MatrixRTCMode } from "../../config/ConfigOptions";
|
||||
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
||||
|
||||
vi.mock("./openID", () => ({ getSFUConfig: vi.fn() }));
|
||||
@@ -83,6 +84,7 @@ describe("getSFUConfig", () => {
|
||||
membership,
|
||||
serverName: "example.org",
|
||||
role: "subscriber" as const,
|
||||
matrixRTCMode: MatrixRTCMode.Matrix_2_0,
|
||||
roomId,
|
||||
slotId,
|
||||
logger,
|
||||
@@ -169,6 +171,53 @@ describe("getSFUConfig", () => {
|
||||
expect(openID.getSFUConfig).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses OpenID when publishing in compatibility mode with url + livekit_service_url transport", async () => {
|
||||
// Subscribers predating MSC4195 expect a legacy participant identity from
|
||||
// anyone advertising a state event, which only the JWT service assigns
|
||||
expect(
|
||||
await getSFUConfig({
|
||||
...defaultParams,
|
||||
transport: urlAndServiceUrlTransport,
|
||||
role: "publisher",
|
||||
matrixRTCMode: MatrixRTCMode.Compatibility,
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
jwt: openIDJWTToken,
|
||||
livekitAlias: "via OpenID",
|
||||
}),
|
||||
);
|
||||
expect(client._unstable_getLivekitToken).not.toHaveBeenCalled();
|
||||
expect(openID.getSFUConfig).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ role: "publisher" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses CS API when publishing in compatibility mode with url transport", async () => {
|
||||
expect(
|
||||
await getSFUConfig({
|
||||
...defaultParams,
|
||||
transport: urlTransport,
|
||||
role: "publisher",
|
||||
matrixRTCMode: MatrixRTCMode.Compatibility,
|
||||
}),
|
||||
).toEqual(expect.objectContaining({ jwt: csApiJWTToken }));
|
||||
expect(client._unstable_getLivekitToken).toHaveBeenCalled();
|
||||
expect(openID.getSFUConfig).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses CS API when subscribing in compatibility mode with url + livekit_service_url transport", async () => {
|
||||
expect(
|
||||
await getSFUConfig({
|
||||
...defaultParams,
|
||||
transport: urlAndServiceUrlTransport,
|
||||
matrixRTCMode: MatrixRTCMode.Compatibility,
|
||||
}),
|
||||
).toEqual(expect.objectContaining({ jwt: csApiJWTToken }));
|
||||
expect(client._unstable_getLivekitToken).toHaveBeenCalled();
|
||||
expect(openID.getSFUConfig).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not fall back to OpenID on connection error", async () => {
|
||||
const client_ = {
|
||||
...client,
|
||||
@@ -198,6 +247,7 @@ describe("delegateDelayedLeave", () => {
|
||||
roomId,
|
||||
slotId,
|
||||
delayId: "delay123",
|
||||
matrixRTCMode: MatrixRTCMode.Matrix_2_0,
|
||||
logger,
|
||||
};
|
||||
|
||||
@@ -236,6 +286,23 @@ describe("delegateDelayedLeave", () => {
|
||||
expect(openID.getSFUConfig).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses OpenID in compatibility mode with url + livekit_service_url transport", async () => {
|
||||
// Must match the flow that got the token, so the identities line up
|
||||
await delegateDelayedLeave({
|
||||
...defaultParams,
|
||||
transport: urlAndServiceUrlTransport,
|
||||
matrixRTCMode: MatrixRTCMode.Compatibility,
|
||||
});
|
||||
expect(client._unstable_delegateDelayedLeave).not.toHaveBeenCalled();
|
||||
expect(openID.getSFUConfig).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
role: "publisher",
|
||||
delayId: "delay123",
|
||||
delayEndpointBaseUrl: client.baseUrl,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not fall back to OpenID with url + livekit_service_url transport", async () => {
|
||||
client._unstable_delegateDelayedLeave.mockRejectedValueOnce(
|
||||
unsupportedError,
|
||||
|
||||
+94
-46
@@ -14,6 +14,7 @@ import * as openID from "./openID";
|
||||
import * as csApi from "./csApi";
|
||||
import { type SFUConfig } from "./types";
|
||||
import { NoMatrix2AuthorizationService } from "../../utils/errors";
|
||||
import { MatrixRTCMode } from "../../config/ConfigOptions";
|
||||
|
||||
export type { ClientOpenIDParts } from "./openID";
|
||||
export type { ClientGetTokenParts, ClientDelegationParts } from "./csApi";
|
||||
@@ -66,6 +67,11 @@ export interface GetSFUConfigParams {
|
||||
* Whether we want to publish or only subscribe on the {@link transport}.
|
||||
*/
|
||||
role: "publisher" | "subscriber";
|
||||
/**
|
||||
* The MatrixRTC mode, which decides the format of our membership event and
|
||||
* so the participant identity a publisher must have.
|
||||
*/
|
||||
matrixRTCMode: MatrixRTCMode;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
@@ -80,52 +86,51 @@ export async function getSFUConfig({
|
||||
roomId,
|
||||
slotId,
|
||||
role,
|
||||
matrixRTCMode,
|
||||
logger,
|
||||
}: GetSFUConfigParams): Promise<SFUConfig> {
|
||||
if ("url" in transport) {
|
||||
try {
|
||||
return await csApi.getSFUConfig({
|
||||
client,
|
||||
membership,
|
||||
url: transport.url,
|
||||
serverName,
|
||||
roomId,
|
||||
slotId,
|
||||
});
|
||||
} catch (e) {
|
||||
if (isEndpointUnsupported(e)) {
|
||||
// Publishers should never fall back. If a homeserver advertises a `url`
|
||||
// but doesn't support MSC4195 itself, that's a misconfiguration which
|
||||
// the admin should know about so they can prepare for the future
|
||||
// deprecation of `livekit_service_url`.
|
||||
const mayFallBack =
|
||||
role === "subscriber" && "livekit_service_url" in transport;
|
||||
if (mayFallBack) {
|
||||
logger.warn(
|
||||
`Homeserver does not support the MSC4195 get_token endpoint, falling back to the OpenID flow with service ${transport.livekit_service_url}`,
|
||||
);
|
||||
// Fall through
|
||||
} else {
|
||||
logger.error(
|
||||
`Homeserver does not support the MSC4195 get_token endpoint, cannot get token for transport ${transport.url}`,
|
||||
);
|
||||
throw new NoMatrix2AuthorizationService(e as Error);
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
const viaOpenID = async (serviceUrl: string): Promise<SFUConfig> =>
|
||||
await openID.getSFUConfig({
|
||||
client,
|
||||
membership,
|
||||
serviceUrl,
|
||||
roomId,
|
||||
slotId,
|
||||
role,
|
||||
logger,
|
||||
});
|
||||
|
||||
return await openID.getSFUConfig({
|
||||
client,
|
||||
membership,
|
||||
serviceUrl: transport.livekit_service_url,
|
||||
roomId,
|
||||
slotId,
|
||||
role,
|
||||
logger,
|
||||
});
|
||||
const flow = chooseAuthFlow(transport, role, matrixRTCMode);
|
||||
if (flow.kind === "openID") return await viaOpenID(flow.serviceUrl);
|
||||
|
||||
try {
|
||||
return await csApi.getSFUConfig({
|
||||
client,
|
||||
membership,
|
||||
url: flow.url,
|
||||
serverName,
|
||||
roomId,
|
||||
slotId,
|
||||
});
|
||||
} catch (e) {
|
||||
if (!isEndpointUnsupported(e)) throw e;
|
||||
// Publishers should never fall back. If a homeserver advertises a `url`
|
||||
// but doesn't support MSC4195 itself, that's a misconfiguration which
|
||||
// the admin should know about so they can prepare for the future
|
||||
// deprecation of `livekit_service_url`.
|
||||
const mayFallBack =
|
||||
role === "subscriber" && "livekit_service_url" in transport;
|
||||
if (!mayFallBack) {
|
||||
logger.error(
|
||||
`Homeserver does not support the MSC4195 get_token endpoint, cannot get token for transport ${flow.url}`,
|
||||
);
|
||||
throw new NoMatrix2AuthorizationService(e as Error);
|
||||
}
|
||||
logger.warn(
|
||||
`Homeserver does not support the MSC4195 get_token endpoint, falling back to the OpenID flow with service ${transport.livekit_service_url}`,
|
||||
);
|
||||
return await viaOpenID(transport.livekit_service_url);
|
||||
}
|
||||
}
|
||||
|
||||
export interface DelegateDelayedLeaveParams {
|
||||
@@ -154,6 +159,10 @@ export interface DelegateDelayedLeaveParams {
|
||||
* The delay ID of the leave event to be delegated.
|
||||
*/
|
||||
delayId: string;
|
||||
/**
|
||||
* The MatrixRTC mode, which decides the format of our membership event.
|
||||
*/
|
||||
matrixRTCMode: MatrixRTCMode;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
@@ -168,17 +177,19 @@ export async function delegateDelayedLeave({
|
||||
roomId,
|
||||
slotId,
|
||||
delayId,
|
||||
matrixRTCMode,
|
||||
logger,
|
||||
}: DelegateDelayedLeaveParams): Promise<void> {
|
||||
// It's important that we use the same flow (CS API or OpenID) as whatever we
|
||||
// would've used to get a JWT token for this transport, since the participant
|
||||
// identity computed by the service needs to match.
|
||||
if ("url" in transport) {
|
||||
const flow = chooseAuthFlow(transport, "publisher", matrixRTCMode);
|
||||
if (flow.kind === "csApi") {
|
||||
try {
|
||||
await csApi.delegateDelayedLeave({
|
||||
client,
|
||||
membership,
|
||||
url: transport.url,
|
||||
url: flow.url,
|
||||
roomId,
|
||||
slotId,
|
||||
delayId,
|
||||
@@ -196,7 +207,7 @@ export async function delegateDelayedLeave({
|
||||
await openID.getSFUConfig({
|
||||
client,
|
||||
membership,
|
||||
serviceUrl: transport.livekit_service_url,
|
||||
serviceUrl: flow.serviceUrl,
|
||||
roomId,
|
||||
slotId,
|
||||
role: "publisher",
|
||||
@@ -206,3 +217,40 @@ export async function delegateDelayedLeave({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The flow with which the local member authenticates against a transport.
|
||||
*/
|
||||
export type AuthFlow =
|
||||
| { kind: "csApi"; url: string }
|
||||
| { kind: "openID"; serviceUrl: string };
|
||||
|
||||
/**
|
||||
* Chooses between the MSC4195 CS API and the legacy OpenID flow against the
|
||||
* transport's JWT service.
|
||||
*
|
||||
* Only the JWT service's `/sfu/get` endpoint assigns the legacy participant
|
||||
* identity that clients predating MSC4195 expect from anyone advertising a
|
||||
* membership state event, which is what compatibility mode sends. So a
|
||||
* publisher in compatibility mode keeps using the JWT service wherever the
|
||||
* transport still offers one, even if it also has a `url`. Subscribers may use
|
||||
* the CS API regardless, as nobody matches their identity.
|
||||
* @param transport The transport to authenticate against.
|
||||
* @param role Whether we want to publish or only subscribe on the transport.
|
||||
* @param matrixRTCMode The MatrixRTC mode, which decides the format of our
|
||||
* membership event.
|
||||
*/
|
||||
export function chooseAuthFlow(
|
||||
transport: UnstableLivekitTransport,
|
||||
role: "publisher" | "subscriber",
|
||||
matrixRTCMode: MatrixRTCMode,
|
||||
): AuthFlow {
|
||||
if ("url" in transport) {
|
||||
const useCsApi =
|
||||
!("livekit_service_url" in transport) ||
|
||||
role === "subscriber" ||
|
||||
matrixRTCMode === MatrixRTCMode.Matrix_2_0;
|
||||
if (useCsApi) return { kind: "csApi", url: transport.url };
|
||||
}
|
||||
return { kind: "openID", serviceUrl: transport.livekit_service_url };
|
||||
}
|
||||
|
||||
@@ -530,6 +530,7 @@ export const DeveloperSettingsTab: FC<Props> = ({
|
||||
roomId,
|
||||
slotId: "org.example.fake-slot#room",
|
||||
role: "publisher",
|
||||
matrixRTCMode: effectiveMatrixRTCMode,
|
||||
logger,
|
||||
});
|
||||
setCustomLivekitUrlUpdateError(null);
|
||||
@@ -540,7 +541,13 @@ export const DeveloperSettingsTab: FC<Props> = ({
|
||||
);
|
||||
}
|
||||
},
|
||||
[customTransportTextBuffer, setCustomTransport, client, roomId],
|
||||
[
|
||||
customTransportTextBuffer,
|
||||
setCustomTransport,
|
||||
client,
|
||||
roomId,
|
||||
effectiveMatrixRTCMode,
|
||||
],
|
||||
)}
|
||||
value={customTransportTextBuffer ?? ""}
|
||||
onChange={useCallback(
|
||||
|
||||
@@ -575,6 +575,7 @@ export function createCallViewModel$(
|
||||
client,
|
||||
matrixRoom.roomId,
|
||||
matrixRTCSession.slotId,
|
||||
matrixRTCMode,
|
||||
mediaDevices,
|
||||
trackProcessorState$,
|
||||
livekitKeyProvider,
|
||||
@@ -665,6 +666,7 @@ export function createCallViewModel$(
|
||||
).pipe(map(([delayId]) => delayId ?? null)),
|
||||
matrixRTCSession.delayId ?? null,
|
||||
),
|
||||
matrixRTCMode,
|
||||
logger: logger.getChild(`[${Date.now()}]`),
|
||||
});
|
||||
|
||||
|
||||
@@ -230,6 +230,7 @@ export function withCallViewModel(mode: MatrixRTCMode) {
|
||||
client: room.client,
|
||||
roomId: room.roomId,
|
||||
slotId: "m.call#ROOM",
|
||||
matrixRTCMode: mode,
|
||||
livekitRoomFactory,
|
||||
},
|
||||
logger,
|
||||
|
||||
@@ -346,21 +346,27 @@ describe("LocalMembership", () => {
|
||||
.mockResolvedValue();
|
||||
afterEach(() => delegationSpy.mockClear());
|
||||
|
||||
// The transport has both a `url` and a `livekit_service_url`. In Matrix 2.0
|
||||
// mode we publish through the CS API, so the homeserver delegates; in
|
||||
// compatibility mode we publish through the JWT service, so that delegates.
|
||||
it.each([
|
||||
["no", null, timings],
|
||||
["no", MatrixRTCMode.Compatibility, null, timings],
|
||||
["no", MatrixRTCMode.Matrix_2_0, null, timings],
|
||||
[
|
||||
"homeserver",
|
||||
MatrixRTCMode.Matrix_2_0,
|
||||
"https://matrix.example.org/_matrix/client/unstable/io.element.msc4195/rtc/livekit/delegate_delayed_leave",
|
||||
delegatedTimings,
|
||||
],
|
||||
[
|
||||
"transport",
|
||||
MatrixRTCMode.Compatibility,
|
||||
"https://jwt.example.org/delegate_delayed_leave",
|
||||
delegatedTimings,
|
||||
],
|
||||
])(
|
||||
"joins session with %s delegation support",
|
||||
async (_serviceName, delegationUrl, delayedLeaveTimings) => {
|
||||
"joins session with %s delegation support in %s mode",
|
||||
async (_serviceName, matrixRTCMode, delegationUrl, delayedLeaveTimings) => {
|
||||
const scope = testScope();
|
||||
const joinMatrixRTC = vi.fn();
|
||||
const delayId$ = new BehaviorSubject<string | null>(null);
|
||||
@@ -377,6 +383,7 @@ describe("LocalMembership", () => {
|
||||
joinMatrixRTC,
|
||||
localTransport$: constant(mockTransportLocator),
|
||||
delayId$,
|
||||
matrixRTCMode,
|
||||
});
|
||||
|
||||
localMembership.requestJoinAndPublish();
|
||||
@@ -403,6 +410,7 @@ describe("LocalMembership", () => {
|
||||
transport: mockTransport,
|
||||
roomId: "!test-room-id:example.org",
|
||||
delayId,
|
||||
matrixRTCMode,
|
||||
logger: expect.anything(),
|
||||
});
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ import {
|
||||
type ClientDelegationParts,
|
||||
delegateDelayedLeave,
|
||||
type ClientOpenIDParts,
|
||||
chooseAuthFlow,
|
||||
type TransportLocator,
|
||||
} from "../../../livekit/auth";
|
||||
|
||||
@@ -164,6 +165,8 @@ interface Props {
|
||||
hostBridge: HostBridge;
|
||||
baseUrl: string;
|
||||
delayId$: Behavior<string | null>;
|
||||
/** The MatrixRTC mode, which decides the format of our membership event. */
|
||||
matrixRTCMode: MatrixRTCMode;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
@@ -211,6 +214,7 @@ export const createLocalMembership$ = ({
|
||||
hostBridge,
|
||||
ownMembershipIdentity,
|
||||
delayId$,
|
||||
matrixRTCMode,
|
||||
}: Props): {
|
||||
/**
|
||||
* This request to start audio and video tracks.
|
||||
@@ -312,17 +316,17 @@ export const createLocalMembership$ = ({
|
||||
const joinParams$ = scope.behavior(
|
||||
localTransport$.pipe(
|
||||
switchMap(async ({ transport }) => {
|
||||
const transportSupportsDelegation =
|
||||
"livekit_service_url" in transport &&
|
||||
(await checkDelegationSupport(
|
||||
transport.livekit_service_url + "/delegate_delayed_leave",
|
||||
`transport ${transport.livekit_service_url}`,
|
||||
));
|
||||
return {
|
||||
transport,
|
||||
delegationSupported:
|
||||
transportSupportsDelegation || (await homeserverSupportsDelegation),
|
||||
};
|
||||
// Delegation goes through whichever service authenticates us as a
|
||||
// publisher, so only that service's support counts
|
||||
const flow = chooseAuthFlow(transport, "publisher", matrixRTCMode);
|
||||
const delegationSupported =
|
||||
flow.kind === "openID"
|
||||
? await checkDelegationSupport(
|
||||
flow.serviceUrl + "/delegate_delayed_leave",
|
||||
`transport ${flow.serviceUrl}`,
|
||||
)
|
||||
: await homeserverSupportsDelegation;
|
||||
return { transport, delegationSupported };
|
||||
}),
|
||||
),
|
||||
null,
|
||||
@@ -731,6 +735,7 @@ export const createLocalMembership$ = ({
|
||||
slotId: matrixRTCSession.slotId,
|
||||
roomId,
|
||||
delayId,
|
||||
matrixRTCMode,
|
||||
logger,
|
||||
});
|
||||
logger.info("Delayed leave successfully delegated");
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
} from "./Connection.ts";
|
||||
import { ObservableScope } from "../../ObservableScope.ts";
|
||||
import { FailToGetOpenIdToken } from "../../../utils/errors.ts";
|
||||
import { MatrixRTCMode } from "../../../config/ConfigOptions.ts";
|
||||
import { testJWTToken } from "../../../utils/test-fixtures.ts";
|
||||
import { mockRemoteParticipant, ownMemberMock } from "../../../utils/test.ts";
|
||||
import {
|
||||
@@ -123,6 +124,7 @@ function setupRemoteConnection(): Connection {
|
||||
client,
|
||||
roomId: ROOM_ID,
|
||||
slotId: SLOT_ID,
|
||||
matrixRTCMode: MatrixRTCMode.Matrix_2_0,
|
||||
role: "subscriber",
|
||||
transport,
|
||||
serverName: "m.localhost",
|
||||
@@ -172,6 +174,7 @@ describe("Start connection states", () => {
|
||||
client,
|
||||
roomId: ROOM_ID,
|
||||
slotId: SLOT_ID,
|
||||
matrixRTCMode: MatrixRTCMode.Matrix_2_0,
|
||||
role: "subscriber",
|
||||
transport,
|
||||
serverName: "m.localhost",
|
||||
@@ -192,6 +195,7 @@ describe("Start connection states", () => {
|
||||
client,
|
||||
roomId: ROOM_ID,
|
||||
slotId: SLOT_ID,
|
||||
matrixRTCMode: MatrixRTCMode.Matrix_2_0,
|
||||
role: "subscriber",
|
||||
transport,
|
||||
serverName: "m.localhost",
|
||||
@@ -247,6 +251,7 @@ describe("Start connection states", () => {
|
||||
client,
|
||||
roomId: ROOM_ID,
|
||||
slotId: SLOT_ID,
|
||||
matrixRTCMode: MatrixRTCMode.Matrix_2_0,
|
||||
role: "subscriber",
|
||||
transport,
|
||||
serverName: "m.localhost",
|
||||
@@ -311,6 +316,7 @@ describe("Start connection states", () => {
|
||||
client,
|
||||
roomId: ROOM_ID,
|
||||
slotId: SLOT_ID,
|
||||
matrixRTCMode: MatrixRTCMode.Matrix_2_0,
|
||||
role: "subscriber",
|
||||
transport,
|
||||
serverName: "m.localhost",
|
||||
@@ -561,6 +567,7 @@ describe("remote track logging", () => {
|
||||
client,
|
||||
roomId: ROOM_ID,
|
||||
slotId: SLOT_ID,
|
||||
matrixRTCMode: MatrixRTCMode.Matrix_2_0,
|
||||
role: "subscriber",
|
||||
transport,
|
||||
serverName: "m.localhost",
|
||||
|
||||
@@ -44,12 +44,15 @@ import {
|
||||
type ClientOpenIDParts,
|
||||
type SFUConfig,
|
||||
} from "../../../livekit/auth";
|
||||
import { type MatrixRTCMode } from "../../../config/ConfigOptions.ts";
|
||||
|
||||
export interface ConnectionOpts {
|
||||
/** The identity parts to use on this connection */
|
||||
ownMembershipIdentity: CallMembershipIdentityParts;
|
||||
/** Whether we want to publish or only subscribe on this connection. */
|
||||
role: "publisher" | "subscriber";
|
||||
/** The MatrixRTC mode, which decides the format of our membership event. */
|
||||
matrixRTCMode: MatrixRTCMode;
|
||||
/** The media transport to connect to. */
|
||||
transport: UnstableLivekitTransport;
|
||||
/** The name of the homeserver to which the {@link transport} belongs. */
|
||||
@@ -138,6 +141,7 @@ export class Connection {
|
||||
private readonly client: ClientGetTokenParts & ClientOpenIDParts;
|
||||
private readonly roomId: string;
|
||||
private readonly slotId: string;
|
||||
private readonly matrixRTCMode: MatrixRTCMode;
|
||||
private readonly logger: Logger;
|
||||
private readonly ownMembershipIdentity: CallMembershipIdentityParts;
|
||||
/**
|
||||
@@ -151,6 +155,7 @@ export class Connection {
|
||||
this.ownMembershipIdentity = opts.ownMembershipIdentity;
|
||||
this.roomId = opts.roomId;
|
||||
this.slotId = opts.slotId;
|
||||
this.matrixRTCMode = opts.matrixRTCMode;
|
||||
this.logger = logger.getChild(
|
||||
`[Connection ${JSON.stringify(opts.transport)}]`,
|
||||
);
|
||||
@@ -394,6 +399,7 @@ export class Connection {
|
||||
serverName: this.serverName,
|
||||
roomId: this.roomId,
|
||||
slotId: this.slotId,
|
||||
matrixRTCMode: this.matrixRTCMode,
|
||||
logger: this.logger,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
type ClientGetTokenParts,
|
||||
type ClientOpenIDParts,
|
||||
} from "../../../livekit/auth";
|
||||
import { type MatrixRTCMode } from "../../../config/ConfigOptions.ts";
|
||||
|
||||
// TODO evaluate if this should be done like the Publisher Factory
|
||||
export interface ConnectionFactory {
|
||||
@@ -61,6 +62,7 @@ export class ECConnectionFactory implements ConnectionFactory {
|
||||
* @param client - The OpenID client parts for authentication, needed to get openID and JWT tokens.
|
||||
* @param roomId - The ID of the Matrix room in which the session takes place.
|
||||
* @param slotId - The ID of the MatrixRTC slot in which the session takes place.
|
||||
* @param matrixRTCMode - The MatrixRTC mode, which decides the format of our membership event.
|
||||
* @param devices - Used for video/audio out/in capture options.
|
||||
* @param processorState$ - Effects like background blur (only for publishing connection?)
|
||||
* @param livekitKeyProvider - Optional key provider for end-to-end encryption.
|
||||
@@ -71,6 +73,7 @@ export class ECConnectionFactory implements ConnectionFactory {
|
||||
private client: ClientGetTokenParts & ClientOpenIDParts,
|
||||
private readonly roomId: string,
|
||||
private readonly slotId: string,
|
||||
private readonly matrixRTCMode: MatrixRTCMode,
|
||||
private devices: MediaDevices,
|
||||
private processorState$: Behavior<ProcessorState>,
|
||||
livekitKeyProvider: BaseKeyProvider | undefined,
|
||||
@@ -117,6 +120,7 @@ export class ECConnectionFactory implements ConnectionFactory {
|
||||
{
|
||||
roomId: this.roomId,
|
||||
slotId: this.slotId,
|
||||
matrixRTCMode: this.matrixRTCMode,
|
||||
role,
|
||||
transport,
|
||||
serverName,
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
cameraCodec,
|
||||
} from "../../../settings/settings.ts";
|
||||
import { testJWTToken } from "../../../utils/test-fixtures.ts";
|
||||
import { MatrixRTCMode } from "../../../config/ConfigOptions.ts";
|
||||
|
||||
// At the top of your test file, after imports
|
||||
vi.mock("livekit-client", async (importOriginal) => {
|
||||
@@ -84,6 +85,7 @@ describe("ECConnectionFactory - Audio inputs options", () => {
|
||||
mockClient,
|
||||
"!roomid:example.org",
|
||||
"m.call#ROOM",
|
||||
MatrixRTCMode.Matrix_2_0,
|
||||
mockMediaDevices({}),
|
||||
new BehaviorSubject<ProcessorState>({
|
||||
supported: true,
|
||||
@@ -129,6 +131,7 @@ describe("ECConnectionFactory - ControlledAudioDevice", () => {
|
||||
mockClient,
|
||||
"!roomid:example.org",
|
||||
"m.call#ROOM",
|
||||
MatrixRTCMode.Matrix_2_0,
|
||||
mockMediaDevices({
|
||||
audioOutput: {
|
||||
available$: constant(new Map<never, never>()),
|
||||
@@ -173,6 +176,7 @@ describe("ECConnectionFactory - Camera quality settings", () => {
|
||||
mockClient,
|
||||
"!roomid:example.org",
|
||||
"m.call#ROOM",
|
||||
MatrixRTCMode.Matrix_2_0,
|
||||
mockMediaDevices({}),
|
||||
new BehaviorSubject<ProcessorState>({
|
||||
supported: true,
|
||||
@@ -213,6 +217,7 @@ describe("ECConnectionFactory - Camera quality settings", () => {
|
||||
mockClient,
|
||||
"!roomid:example.org",
|
||||
"m.call#ROOM",
|
||||
MatrixRTCMode.Matrix_2_0,
|
||||
mockMediaDevices({}),
|
||||
new BehaviorSubject<ProcessorState>({
|
||||
supported: true,
|
||||
@@ -254,6 +259,7 @@ describe("ECConnectionFactory - Camera quality settings", () => {
|
||||
mockClient,
|
||||
"!roomid:example.org",
|
||||
"m.call#ROOM",
|
||||
MatrixRTCMode.Matrix_2_0,
|
||||
mockMediaDevices({}),
|
||||
new BehaviorSubject<ProcessorState>({
|
||||
supported: true,
|
||||
|
||||
@@ -38,6 +38,7 @@ import { createConnectionMap$ } from "./ConnectionMap.ts";
|
||||
import { membershipsAndTransports$ } from "../../SessionBehaviors.ts";
|
||||
import { localRtcMember, testJWTToken } from "../../../utils/test-fixtures.ts";
|
||||
import { deepCompare } from "matrix-js-sdk/lib/utils";
|
||||
import { MatrixRTCMode } from "../../../config/ConfigOptions.ts";
|
||||
|
||||
// Test the integration of ConnectionMap and MatrixLivekitMembers
|
||||
|
||||
@@ -75,6 +76,7 @@ beforeEach(() => {
|
||||
mockClient,
|
||||
"!roomid:example.org",
|
||||
"m.call#ROOM",
|
||||
MatrixRTCMode.Matrix_2_0,
|
||||
mockMediaDevices({}),
|
||||
new BehaviorSubject<ProcessorState>({
|
||||
supported: true,
|
||||
|
||||
Reference in New Issue
Block a user