Make use of the new jwt service endpoint (with delayed event delegation)
This also does all the compatibility work. When to use which endpoint to authenticate agains a jwt service.
This commit is contained in:
@@ -7,9 +7,11 @@ Please see LICENSE in the repository root for full details.
|
|||||||
|
|
||||||
import { type IOpenIDToken, type MatrixClient } from "matrix-js-sdk";
|
import { type IOpenIDToken, type MatrixClient } from "matrix-js-sdk";
|
||||||
import { logger } from "matrix-js-sdk/lib/logger";
|
import { logger } from "matrix-js-sdk/lib/logger";
|
||||||
|
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
||||||
|
|
||||||
import { FailToGetOpenIdToken } from "../utils/errors";
|
import { FailToGetOpenIdToken } from "../utils/errors";
|
||||||
import { doNetworkOperationWithRetry } from "../utils/matrix";
|
import { doNetworkOperationWithRetry } from "../utils/matrix";
|
||||||
|
import { Config } from "../config/Config";
|
||||||
|
|
||||||
export interface SFUConfig {
|
export interface SFUConfig {
|
||||||
url: string;
|
url: string;
|
||||||
@@ -33,8 +35,12 @@ export type OpenIDClientParts = Pick<
|
|||||||
*/
|
*/
|
||||||
export async function getSFUConfigWithOpenID(
|
export async function getSFUConfigWithOpenID(
|
||||||
client: OpenIDClientParts,
|
client: OpenIDClientParts,
|
||||||
|
membership: CallMembershipIdentityParts,
|
||||||
serviceUrl: string,
|
serviceUrl: string,
|
||||||
matrixRoomId: string,
|
livekitRoomAlias: string,
|
||||||
|
matrix2jwt: boolean,
|
||||||
|
delayEndpointBaseUrl?: string,
|
||||||
|
delayId?: string,
|
||||||
): Promise<SFUConfig> {
|
): Promise<SFUConfig> {
|
||||||
let openIdToken: IOpenIDToken;
|
let openIdToken: IOpenIDToken;
|
||||||
try {
|
try {
|
||||||
@@ -49,21 +55,31 @@ export async function getSFUConfigWithOpenID(
|
|||||||
logger.debug("Got openID token", openIdToken);
|
logger.debug("Got openID token", openIdToken);
|
||||||
|
|
||||||
logger.info(`Trying to get JWT for focus ${serviceUrl}...`);
|
logger.info(`Trying to get JWT for focus ${serviceUrl}...`);
|
||||||
const sfuConfig = await getLiveKitJWT(
|
const args: [CallMembershipIdentityParts, string, string, IOpenIDToken] = [
|
||||||
client,
|
membership,
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
matrixRoomId,
|
livekitRoomAlias,
|
||||||
openIdToken,
|
openIdToken,
|
||||||
);
|
];
|
||||||
logger.info(`Got JWT from call's active focus URL.`);
|
if (matrix2jwt) {
|
||||||
|
const sfuConfig = await getLiveKitJWTWithDelayDelegation(
|
||||||
return sfuConfig;
|
...args,
|
||||||
|
delayEndpointBaseUrl,
|
||||||
|
delayId,
|
||||||
|
);
|
||||||
|
logger.info(`Got JWT from call's active focus URL.`);
|
||||||
|
return sfuConfig;
|
||||||
|
} else {
|
||||||
|
const sfuConfig = await getLiveKitJWT(...args);
|
||||||
|
logger.info(`Got JWT from call's active focus URL.`);
|
||||||
|
return sfuConfig;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLiveKitJWT(
|
async function getLiveKitJWT(
|
||||||
client: OpenIDClientParts,
|
membership: CallMembershipIdentityParts,
|
||||||
livekitServiceURL: string,
|
livekitServiceURL: string,
|
||||||
roomName: string,
|
livekitRoomAlias: string,
|
||||||
openIDToken: IOpenIDToken,
|
openIDToken: IOpenIDToken,
|
||||||
): Promise<SFUConfig> {
|
): Promise<SFUConfig> {
|
||||||
try {
|
try {
|
||||||
@@ -73,9 +89,9 @@ async function getLiveKitJWT(
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
room: roomName,
|
room: livekitRoomAlias,
|
||||||
openid_token: openIDToken,
|
openid_token: openIDToken,
|
||||||
device_id: client.getDeviceId(),
|
device_id: membership.deviceId,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
@@ -86,3 +102,53 @@ async function getLiveKitJWT(
|
|||||||
throw new Error("SFU Config fetch failed with exception " + e);
|
throw new Error("SFU Config fetch failed with exception " + e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getLiveKitJWTWithDelayDelegation(
|
||||||
|
membership: CallMembershipIdentityParts,
|
||||||
|
livekitServiceURL: string,
|
||||||
|
livekitRoomAlias: string,
|
||||||
|
openIDToken: IOpenIDToken,
|
||||||
|
delayEndpointBaseUrl?: string,
|
||||||
|
delayId?: string,
|
||||||
|
): Promise<SFUConfig> {
|
||||||
|
const { userId, deviceId, memberId } = membership;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
room_id: livekitRoomAlias,
|
||||||
|
slot_id: "m.call#ROOM",
|
||||||
|
openid_token: openIDToken,
|
||||||
|
member: {
|
||||||
|
id: memberId,
|
||||||
|
claimed_user_id: userId,
|
||||||
|
claimed_device_id: deviceId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let bodyDalayParts = {};
|
||||||
|
// Also check for empty string
|
||||||
|
if (delayId && delayEndpointBaseUrl) {
|
||||||
|
const delayTimeoutMs =
|
||||||
|
Config.get().matrix_rtc_session?.delayed_leave_event_delay_ms ?? 1000;
|
||||||
|
bodyDalayParts = {
|
||||||
|
delay_id: delayId,
|
||||||
|
delay_timeout: delayTimeoutMs,
|
||||||
|
delay_cs_api_url: delayEndpointBaseUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(livekitServiceURL + "/get_token", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ ...body, ...bodyDalayParts }),
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error("SFU Config fetch failed with status code " + res.status);
|
||||||
|
}
|
||||||
|
return await res.json();
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error("SFU Config fetch failed with exception " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,10 +41,12 @@ import {
|
|||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
||||||
import {
|
import {
|
||||||
|
MembershipManagerEvent,
|
||||||
type LivekitTransport,
|
type LivekitTransport,
|
||||||
type MatrixRTCSession,
|
type MatrixRTCSession,
|
||||||
} from "matrix-js-sdk/lib/matrixrtc";
|
} from "matrix-js-sdk/lib/matrixrtc";
|
||||||
import { type IWidgetApiRequest } from "matrix-widget-api";
|
import { type IWidgetApiRequest } from "matrix-widget-api";
|
||||||
|
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
LocalUserMediaViewModel,
|
LocalUserMediaViewModel,
|
||||||
@@ -98,7 +100,7 @@ import {
|
|||||||
type SpotlightLandscapeLayoutMedia,
|
type SpotlightLandscapeLayoutMedia,
|
||||||
type SpotlightPortraitLayoutMedia,
|
type SpotlightPortraitLayoutMedia,
|
||||||
} from "../layout-types.ts";
|
} from "../layout-types.ts";
|
||||||
import { ElementCallError } from "../../utils/errors.ts";
|
import { ElementCallError, UnknownCallError } from "../../utils/errors.ts";
|
||||||
import { type ObservableScope } from "../ObservableScope.ts";
|
import { type ObservableScope } from "../ObservableScope.ts";
|
||||||
import { createHomeserverConnected$ } from "./localMember/HomeserverConnected.ts";
|
import { createHomeserverConnected$ } from "./localMember/HomeserverConnected.ts";
|
||||||
import {
|
import {
|
||||||
@@ -375,8 +377,11 @@ export function createCallViewModel$(
|
|||||||
trackProcessorState$: Behavior<ProcessorState>,
|
trackProcessorState$: Behavior<ProcessorState>,
|
||||||
): CallViewModel {
|
): CallViewModel {
|
||||||
const client = matrixRoom.client;
|
const client = matrixRoom.client;
|
||||||
const userId = client.getUserId()!;
|
const userId = client.getUserId();
|
||||||
const deviceId = client.getDeviceId()!;
|
const deviceId = client.getDeviceId();
|
||||||
|
if (!(userId && deviceId))
|
||||||
|
throw new UnknownCallError(new Error("userId and deviceId are required"));
|
||||||
|
|
||||||
const livekitKeyProvider = getE2eeKeyProvider(
|
const livekitKeyProvider = getE2eeKeyProvider(
|
||||||
options.encryptionSystem,
|
options.encryptionSystem,
|
||||||
matrixRTCSession,
|
matrixRTCSession,
|
||||||
@@ -407,10 +412,29 @@ export function createCallViewModel$(
|
|||||||
memberships$,
|
memberships$,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const ownMembershipIdentity: CallMembershipIdentityParts = {
|
||||||
|
userId,
|
||||||
|
deviceId,
|
||||||
|
memberId: `${userId}:${deviceId}`,
|
||||||
|
};
|
||||||
|
|
||||||
const localTransport$ = createLocalTransport$({
|
const localTransport$ = createLocalTransport$({
|
||||||
scope: scope,
|
scope: scope,
|
||||||
memberships$: memberships$,
|
memberships$: memberships$,
|
||||||
|
ownMembershipIdentity,
|
||||||
client,
|
client,
|
||||||
|
useMatrix2$: scope.behavior(
|
||||||
|
options.matrixRTCMode$.pipe(map((v) => v === MatrixRTCMode.Matrix_2_0)),
|
||||||
|
),
|
||||||
|
delayId$: scope.behavior(
|
||||||
|
(
|
||||||
|
fromEvent(
|
||||||
|
matrixRTCSession,
|
||||||
|
MembershipManagerEvent.DelayIdChanged,
|
||||||
|
) as Observable<string | undefined>
|
||||||
|
).pipe(map((v) => v ?? null)),
|
||||||
|
matrixRTCSession.delayId ?? null,
|
||||||
|
),
|
||||||
roomId: matrixRoom.roomId,
|
roomId: matrixRoom.roomId,
|
||||||
useOldestMember$: scope.behavior(
|
useOldestMember$: scope.behavior(
|
||||||
options.matrixRTCMode$.pipe(map((v) => v === MatrixRTCMode.Legacy)),
|
options.matrixRTCMode$.pipe(map((v) => v === MatrixRTCMode.Legacy)),
|
||||||
@@ -455,6 +479,7 @@ export function createCallViewModel$(
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
ownMembershipIdentity,
|
||||||
});
|
});
|
||||||
|
|
||||||
const matrixLivekitMembers$ = createMatrixLivekitMembers$({
|
const matrixLivekitMembers$ = createMatrixLivekitMembers$({
|
||||||
@@ -485,6 +510,7 @@ export function createCallViewModel$(
|
|||||||
joinMatrixRTC: (transport: LivekitTransport) => {
|
joinMatrixRTC: (transport: LivekitTransport) => {
|
||||||
return enterRTCSession(
|
return enterRTCSession(
|
||||||
matrixRTCSession,
|
matrixRTCSession,
|
||||||
|
ownMembershipIdentity,
|
||||||
transport,
|
transport,
|
||||||
connectOptions$.value,
|
connectOptions$.value,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
mockLivekitRoom,
|
mockLivekitRoom,
|
||||||
mockMuteStates,
|
mockMuteStates,
|
||||||
withTestScheduler,
|
withTestScheduler,
|
||||||
|
ownMemberMock,
|
||||||
} from "../../../utils/test";
|
} from "../../../utils/test";
|
||||||
import {
|
import {
|
||||||
TransportState,
|
TransportState,
|
||||||
@@ -108,6 +109,7 @@ describe("LocalMembership", () => {
|
|||||||
|
|
||||||
enterRTCSession(
|
enterRTCSession(
|
||||||
mockedSession,
|
mockedSession,
|
||||||
|
ownMemberMock,
|
||||||
{
|
{
|
||||||
livekit_alias: "roomId",
|
livekit_alias: "roomId",
|
||||||
livekit_service_url: "http://my-well-known-service-url.com",
|
livekit_service_url: "http://my-well-known-service-url.com",
|
||||||
@@ -166,6 +168,7 @@ describe("LocalMembership", () => {
|
|||||||
|
|
||||||
enterRTCSession(
|
enterRTCSession(
|
||||||
mockedSession,
|
mockedSession,
|
||||||
|
ownMemberMock,
|
||||||
{
|
{
|
||||||
livekit_alias: "roomId",
|
livekit_alias: "roomId",
|
||||||
livekit_service_url: "http://my-well-known-service-url.com",
|
livekit_service_url: "http://my-well-known-service-url.com",
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
import { type Logger } from "matrix-js-sdk/lib/logger";
|
import { type Logger } from "matrix-js-sdk/lib/logger";
|
||||||
import { deepCompare } from "matrix-js-sdk/lib/utils";
|
import { deepCompare } from "matrix-js-sdk/lib/utils";
|
||||||
|
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
||||||
|
|
||||||
import { constant, type Behavior } from "../../Behavior.ts";
|
import { constant, type Behavior } from "../../Behavior.ts";
|
||||||
import { type IConnectionManager } from "../remoteMembers/ConnectionManager.ts";
|
import { type IConnectionManager } from "../remoteMembers/ConnectionManager.ts";
|
||||||
@@ -657,6 +658,7 @@ interface EnterRTCSessionOptions {
|
|||||||
// Exported for unit testing
|
// Exported for unit testing
|
||||||
export function enterRTCSession(
|
export function enterRTCSession(
|
||||||
rtcSession: MatrixRTCSession,
|
rtcSession: MatrixRTCSession,
|
||||||
|
ownMembershipIdentity: CallMembershipIdentityParts,
|
||||||
transport: LivekitTransport,
|
transport: LivekitTransport,
|
||||||
{ encryptMedia, matrixRTCMode }: EnterRTCSessionOptions,
|
{ encryptMedia, matrixRTCMode }: EnterRTCSessionOptions,
|
||||||
): void {
|
): void {
|
||||||
@@ -674,7 +676,8 @@ export function enterRTCSession(
|
|||||||
const multiSFU = matrixRTCMode !== MatrixRTCMode.Legacy;
|
const multiSFU = matrixRTCMode !== MatrixRTCMode.Legacy;
|
||||||
// Multi-sfu does not need a preferred foci list. just the focus that is actually used.
|
// Multi-sfu does not need a preferred foci list. just the focus that is actually used.
|
||||||
// TODO where/how do we track errors originating from the ongoing rtcSession?
|
// TODO where/how do we track errors originating from the ongoing rtcSession?
|
||||||
rtcSession.joinRoomSession(
|
rtcSession.joinRTCSession(
|
||||||
|
ownMembershipIdentity,
|
||||||
multiSFU ? [] : [transport],
|
multiSFU ? [] : [transport],
|
||||||
multiSFU ? transport : undefined,
|
multiSFU ? transport : undefined,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|||||||
import { type CallMembership } from "matrix-js-sdk/lib/matrixrtc";
|
import { type CallMembership } from "matrix-js-sdk/lib/matrixrtc";
|
||||||
import { BehaviorSubject } from "rxjs";
|
import { BehaviorSubject } from "rxjs";
|
||||||
|
|
||||||
import { mockConfig, flushPromises } from "../../../utils/test";
|
import { mockConfig, flushPromises, ownMemberMock } from "../../../utils/test";
|
||||||
import { createLocalTransport$ } from "./LocalTransport";
|
import { createLocalTransport$ } from "./LocalTransport";
|
||||||
import { constant } from "../../Behavior";
|
import { constant } from "../../Behavior";
|
||||||
import { Epoch, ObservableScope } from "../../ObservableScope";
|
import { Epoch, ObservableScope } from "../../ObservableScope";
|
||||||
@@ -32,10 +32,14 @@ describe("LocalTransport", () => {
|
|||||||
memberships$: constant(new Epoch<CallMembership[]>([])),
|
memberships$: constant(new Epoch<CallMembership[]>([])),
|
||||||
client: {
|
client: {
|
||||||
getDomain: () => "",
|
getDomain: () => "",
|
||||||
|
baseUrl: "example.org",
|
||||||
// These won't be called in this error path but satisfy the type
|
// These won't be called in this error path but satisfy the type
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
getDeviceId: vi.fn(),
|
getDeviceId: vi.fn(),
|
||||||
},
|
},
|
||||||
|
ownMembershipIdentity: ownMemberMock,
|
||||||
|
useMatrix2$: constant(false),
|
||||||
|
delayId$: constant("delay_id_mock"),
|
||||||
});
|
});
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
|
|
||||||
@@ -65,11 +69,15 @@ describe("LocalTransport", () => {
|
|||||||
useOldestMember$: constant(false),
|
useOldestMember$: constant(false),
|
||||||
memberships$: constant(new Epoch<CallMembership[]>([])),
|
memberships$: constant(new Epoch<CallMembership[]>([])),
|
||||||
client: {
|
client: {
|
||||||
|
baseUrl: "https://lk.example.org",
|
||||||
// Use empty domain to skip .well-known and use config directly
|
// Use empty domain to skip .well-known and use config directly
|
||||||
getDomain: () => "",
|
getDomain: () => "",
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
getDeviceId: vi.fn(),
|
getDeviceId: vi.fn(),
|
||||||
},
|
},
|
||||||
|
ownMembershipIdentity: ownMemberMock,
|
||||||
|
useMatrix2$: constant(false),
|
||||||
|
delayId$: constant("delay_id_mock"),
|
||||||
});
|
});
|
||||||
localTransport$.subscribe(
|
localTransport$.subscribe(
|
||||||
(o) => observations.push(o),
|
(o) => observations.push(o),
|
||||||
@@ -105,7 +113,11 @@ describe("LocalTransport", () => {
|
|||||||
getDomain: () => "",
|
getDomain: () => "",
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
getDeviceId: vi.fn(),
|
getDeviceId: vi.fn(),
|
||||||
|
baseUrl: "https://lk.example.org",
|
||||||
},
|
},
|
||||||
|
ownMembershipIdentity: ownMemberMock,
|
||||||
|
useMatrix2$: constant(false),
|
||||||
|
delayId$: constant("delay_id_mock"),
|
||||||
});
|
});
|
||||||
|
|
||||||
openIdResolver.resolve?.({ url: "https://lk.example.org", jwt: "jwt" });
|
openIdResolver.resolve?.({ url: "https://lk.example.org", jwt: "jwt" });
|
||||||
@@ -140,7 +152,11 @@ describe("LocalTransport", () => {
|
|||||||
getDomain: () => "",
|
getDomain: () => "",
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
getDeviceId: vi.fn(),
|
getDeviceId: vi.fn(),
|
||||||
|
baseUrl: "https://lk.example.org",
|
||||||
},
|
},
|
||||||
|
ownMembershipIdentity: ownMemberMock,
|
||||||
|
useMatrix2$: constant(false),
|
||||||
|
delayId$: constant("delay_id_mock"),
|
||||||
});
|
});
|
||||||
|
|
||||||
openIdResolver.resolve?.({ url: "https://lk.example.org", jwt: "jwt" });
|
openIdResolver.resolve?.({ url: "https://lk.example.org", jwt: "jwt" });
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
||||||
import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery";
|
import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery";
|
||||||
|
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
||||||
|
|
||||||
import { type Behavior } from "../../Behavior.ts";
|
import { type Behavior } from "../../Behavior.ts";
|
||||||
import { type Epoch, type ObservableScope } from "../../ObservableScope.ts";
|
import { type Epoch, type ObservableScope } from "../../ObservableScope.ts";
|
||||||
@@ -34,6 +35,7 @@ import {
|
|||||||
} from "../../../livekit/openIDSFU.ts";
|
} from "../../../livekit/openIDSFU.ts";
|
||||||
import { areLivekitTransportsEqual } from "../remoteMembers/MatrixLivekitMembers.ts";
|
import { areLivekitTransportsEqual } from "../remoteMembers/MatrixLivekitMembers.ts";
|
||||||
import { customLivekitUrl } from "../../../settings/settings.ts";
|
import { customLivekitUrl } from "../../../settings/settings.ts";
|
||||||
|
import { type LivekitTransportWithVersion } from "../remoteMembers/ConnectionManager.ts";
|
||||||
|
|
||||||
const logger = rootLogger.getChild("[LocalTransport]");
|
const logger = rootLogger.getChild("[LocalTransport]");
|
||||||
|
|
||||||
@@ -44,10 +46,13 @@ const logger = rootLogger.getChild("[LocalTransport]");
|
|||||||
*/
|
*/
|
||||||
interface Props {
|
interface Props {
|
||||||
scope: ObservableScope;
|
scope: ObservableScope;
|
||||||
|
ownMembershipIdentity: CallMembershipIdentityParts;
|
||||||
memberships$: Behavior<Epoch<CallMembership[]>>;
|
memberships$: Behavior<Epoch<CallMembership[]>>;
|
||||||
client: Pick<MatrixClient, "getDomain"> & OpenIDClientParts;
|
client: Pick<MatrixClient, "getDomain" | "baseUrl"> & OpenIDClientParts;
|
||||||
roomId: string;
|
roomId: string;
|
||||||
useOldestMember$: Behavior<boolean>;
|
useOldestMember$: Behavior<boolean>;
|
||||||
|
useMatrix2$: Behavior<boolean>;
|
||||||
|
delayId$: Behavior<string | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,20 +67,26 @@ interface Props {
|
|||||||
export const createLocalTransport$ = ({
|
export const createLocalTransport$ = ({
|
||||||
scope,
|
scope,
|
||||||
memberships$,
|
memberships$,
|
||||||
|
ownMembershipIdentity,
|
||||||
client,
|
client,
|
||||||
roomId,
|
roomId,
|
||||||
useOldestMember$,
|
useOldestMember$,
|
||||||
}: Props): Behavior<LivekitTransport | null> => {
|
useMatrix2$,
|
||||||
|
delayId$,
|
||||||
|
}: Props): Behavior<LivekitTransportWithVersion | null> => {
|
||||||
/**
|
/**
|
||||||
* The transport over which we should be actively publishing our media.
|
* The transport over which we should be actively publishing our media.
|
||||||
* undefined when not joined.
|
* undefined when not joined.
|
||||||
*/
|
*/
|
||||||
const oldestMemberTransport$ = scope.behavior(
|
const oldestMemberTransport$ = scope.behavior(
|
||||||
memberships$.pipe(
|
memberships$.pipe(
|
||||||
map(
|
map((memberships) => {
|
||||||
(memberships) =>
|
const oldestMember = memberships.value[0];
|
||||||
memberships.value[0]?.getTransport(memberships.value[0]) ?? null,
|
const t = oldestMember?.getTransport(memberships.value[0]);
|
||||||
),
|
if (!t) return null;
|
||||||
|
// Here we will use the matrix2 information from the oldest member transport.
|
||||||
|
return { ...t, useMatrix2: oldestMember.kind === "rtc" };
|
||||||
|
}),
|
||||||
first((t) => t != null && isLivekitTransport(t)),
|
first((t) => t != null && isLivekitTransport(t)),
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
@@ -87,12 +98,24 @@ export const createLocalTransport$ = ({
|
|||||||
*
|
*
|
||||||
* @throws MatrixRTCTransportMissingError | FailToGetOpenIdToken
|
* @throws MatrixRTCTransportMissingError | FailToGetOpenIdToken
|
||||||
*/
|
*/
|
||||||
const preferredTransport$: Behavior<LivekitTransport | null> = scope.behavior(
|
const preferredTransport$: Behavior<LivekitTransportWithVersion | null> =
|
||||||
customLivekitUrl.value$.pipe(
|
scope.behavior(
|
||||||
switchMap((customUrl) => from(makeTransport(client, roomId, customUrl))),
|
combineLatest([customLivekitUrl.value$, useMatrix2$, delayId$]).pipe(
|
||||||
),
|
switchMap(([customUrl, useMatrix2, delayId]) =>
|
||||||
null,
|
from(
|
||||||
);
|
makeTransport(
|
||||||
|
client,
|
||||||
|
ownMembershipIdentity,
|
||||||
|
roomId,
|
||||||
|
customUrl,
|
||||||
|
useMatrix2,
|
||||||
|
delayId ?? undefined,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The chosen transport we should advertise in our MatrixRTC membership.
|
* The chosen transport we should advertise in our MatrixRTC membership.
|
||||||
@@ -123,10 +146,13 @@ const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci";
|
|||||||
* @throws MatrixRTCTransportMissingError | FailToGetOpenIdToken
|
* @throws MatrixRTCTransportMissingError | FailToGetOpenIdToken
|
||||||
*/
|
*/
|
||||||
async function makeTransport(
|
async function makeTransport(
|
||||||
client: Pick<MatrixClient, "getDomain"> & OpenIDClientParts,
|
client: Pick<MatrixClient, "getDomain" | "baseUrl"> & OpenIDClientParts,
|
||||||
|
membership: CallMembershipIdentityParts,
|
||||||
roomId: string,
|
roomId: string,
|
||||||
urlFromDevSettings: string | null,
|
urlFromDevSettings: string | null,
|
||||||
): Promise<LivekitTransport> {
|
matrix2jwt = false,
|
||||||
|
delayId?: string,
|
||||||
|
): Promise<LivekitTransportWithVersion> {
|
||||||
let transport: LivekitTransport | undefined;
|
let transport: LivekitTransport | undefined;
|
||||||
logger.trace("Searching for a preferred transport");
|
logger.trace("Searching for a preferred transport");
|
||||||
//TODO refactor this to use the jwt service returned alias.
|
//TODO refactor this to use the jwt service returned alias.
|
||||||
@@ -176,13 +202,18 @@ async function makeTransport(
|
|||||||
transport = transportFromConf;
|
transport = transportFromConf;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!transport) throw new MatrixRTCTransportMissingError(domain ?? ""); // this will call the jwt/sfu/get endpoint to pre create the livekit room.
|
if (!transport) throw new MatrixRTCTransportMissingError(domain ?? "");
|
||||||
|
|
||||||
|
// this will call the jwt/sfu/get endpoint to pre create the livekit room.
|
||||||
await getSFUConfigWithOpenID(
|
await getSFUConfigWithOpenID(
|
||||||
client,
|
client,
|
||||||
|
membership,
|
||||||
transport.livekit_service_url,
|
transport.livekit_service_url,
|
||||||
transport.livekit_alias,
|
transport.livekit_alias,
|
||||||
|
matrix2jwt,
|
||||||
|
client.baseUrl,
|
||||||
|
delayId,
|
||||||
);
|
);
|
||||||
|
|
||||||
return transport;
|
return { ...transport, useMatrix2: matrix2jwt };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import EventEmitter from "events";
|
|||||||
import { type IOpenIDToken } from "matrix-js-sdk";
|
import { type IOpenIDToken } from "matrix-js-sdk";
|
||||||
import { logger } from "matrix-js-sdk/lib/logger";
|
import { logger } from "matrix-js-sdk/lib/logger";
|
||||||
|
|
||||||
import type { LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
|
||||||
import {
|
import {
|
||||||
Connection,
|
Connection,
|
||||||
ConnectionState,
|
ConnectionState,
|
||||||
@@ -39,7 +38,8 @@ import {
|
|||||||
ElementCallError,
|
ElementCallError,
|
||||||
FailToGetOpenIdToken,
|
FailToGetOpenIdToken,
|
||||||
} from "../../../utils/errors.ts";
|
} from "../../../utils/errors.ts";
|
||||||
import { mockRemoteParticipant } from "../../../utils/test.ts";
|
import { mockRemoteParticipant, ownMemberMock } from "../../../utils/test.ts";
|
||||||
|
import { type LivekitTransportWithVersion } from "./ConnectionManager.ts";
|
||||||
|
|
||||||
let testScope: ObservableScope;
|
let testScope: ObservableScope;
|
||||||
|
|
||||||
@@ -50,10 +50,11 @@ let fakeLivekitRoom: MockedObject<LivekitRoom>;
|
|||||||
let localParticipantEventEmiter: EventEmitter;
|
let localParticipantEventEmiter: EventEmitter;
|
||||||
let fakeLocalParticipant: MockedObject<LocalParticipant>;
|
let fakeLocalParticipant: MockedObject<LocalParticipant>;
|
||||||
|
|
||||||
const livekitFocus: LivekitTransport = {
|
const livekitFocus: LivekitTransportWithVersion = {
|
||||||
livekit_alias: "!roomID:example.org",
|
livekit_alias: "!roomID:example.org",
|
||||||
livekit_service_url: "https://matrix-rtc.example.org/livekit/jwt",
|
livekit_service_url: "https://matrix-rtc.example.org/livekit/jwt",
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
|
useMatrix2: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
function setupTest(): void {
|
function setupTest(): void {
|
||||||
@@ -137,7 +138,7 @@ function setupRemoteConnection(): Connection {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Connection(opts, logger);
|
return new Connection(opts, logger, ownMemberMock);
|
||||||
}
|
}
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -156,7 +157,7 @@ describe("Start connection states", () => {
|
|||||||
scope: testScope,
|
scope: testScope,
|
||||||
livekitRoomFactory: () => fakeLivekitRoom,
|
livekitRoomFactory: () => fakeLivekitRoom,
|
||||||
};
|
};
|
||||||
const connection = new Connection(opts, logger);
|
const connection = new Connection(opts, logger, ownMemberMock);
|
||||||
|
|
||||||
expect(connection.state$.getValue()).toEqual("Initialized");
|
expect(connection.state$.getValue()).toEqual("Initialized");
|
||||||
});
|
});
|
||||||
@@ -172,7 +173,7 @@ describe("Start connection states", () => {
|
|||||||
livekitRoomFactory: () => fakeLivekitRoom,
|
livekitRoomFactory: () => fakeLivekitRoom,
|
||||||
};
|
};
|
||||||
|
|
||||||
const connection = new Connection(opts, logger);
|
const connection = new Connection(opts, logger, ownMemberMock);
|
||||||
|
|
||||||
const capturedStates: (ConnectionState | Error)[] = [];
|
const capturedStates: (ConnectionState | Error)[] = [];
|
||||||
const s = connection.state$.subscribe((value) => {
|
const s = connection.state$.subscribe((value) => {
|
||||||
@@ -222,7 +223,7 @@ describe("Start connection states", () => {
|
|||||||
livekitRoomFactory: () => fakeLivekitRoom,
|
livekitRoomFactory: () => fakeLivekitRoom,
|
||||||
};
|
};
|
||||||
|
|
||||||
const connection = new Connection(opts, logger);
|
const connection = new Connection(opts, logger, ownMemberMock);
|
||||||
|
|
||||||
const capturedStates: (ConnectionState | Error)[] = [];
|
const capturedStates: (ConnectionState | Error)[] = [];
|
||||||
const s = connection.state$.subscribe((value) => {
|
const s = connection.state$.subscribe((value) => {
|
||||||
@@ -279,7 +280,7 @@ describe("Start connection states", () => {
|
|||||||
livekitRoomFactory: () => fakeLivekitRoom,
|
livekitRoomFactory: () => fakeLivekitRoom,
|
||||||
};
|
};
|
||||||
|
|
||||||
const connection = new Connection(opts, logger);
|
const connection = new Connection(opts, logger, ownMemberMock);
|
||||||
|
|
||||||
const capturedStates: (ConnectionState | Error)[] = [];
|
const capturedStates: (ConnectionState | Error)[] = [];
|
||||||
const s = connection.state$.subscribe((value) => {
|
const s = connection.state$.subscribe((value) => {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||||
import { BehaviorSubject, map } from "rxjs";
|
import { BehaviorSubject, map } from "rxjs";
|
||||||
import { type Logger } from "matrix-js-sdk/lib/logger";
|
import { type Logger } from "matrix-js-sdk/lib/logger";
|
||||||
|
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getSFUConfigWithOpenID,
|
getSFUConfigWithOpenID,
|
||||||
@@ -35,7 +36,7 @@ import {
|
|||||||
|
|
||||||
export interface ConnectionOpts {
|
export interface ConnectionOpts {
|
||||||
/** The media transport to connect to. */
|
/** The media transport to connect to. */
|
||||||
transport: LivekitTransport;
|
transport: LivekitTransport & { useMatrix2: boolean };
|
||||||
/** The Matrix client to use for OpenID and SFU config requests. */
|
/** The Matrix client to use for OpenID and SFU config requests. */
|
||||||
client: OpenIDClientParts;
|
client: OpenIDClientParts;
|
||||||
/** The observable scope to use for this connection. */
|
/** The observable scope to use for this connection. */
|
||||||
@@ -88,7 +89,7 @@ export class Connection {
|
|||||||
/**
|
/**
|
||||||
* The media transport to connect to.
|
* The media transport to connect to.
|
||||||
*/
|
*/
|
||||||
public readonly transport: LivekitTransport;
|
public readonly transport: LivekitTransport & { useMatrix2: boolean };
|
||||||
|
|
||||||
public readonly livekitRoom: LivekitRoom;
|
public readonly livekitRoom: LivekitRoom;
|
||||||
|
|
||||||
@@ -189,9 +190,18 @@ export class Connection {
|
|||||||
protected async getSFUConfigWithOpenID(): Promise<SFUConfig> {
|
protected async getSFUConfigWithOpenID(): Promise<SFUConfig> {
|
||||||
return await getSFUConfigWithOpenID(
|
return await getSFUConfigWithOpenID(
|
||||||
this.client,
|
this.client,
|
||||||
|
this.ownMembershipIdentity,
|
||||||
this.transport.livekit_service_url,
|
this.transport.livekit_service_url,
|
||||||
this.transport.livekit_alias,
|
this.transport.livekit_alias,
|
||||||
|
this.transport.useMatrix2,
|
||||||
);
|
);
|
||||||
|
// client: OpenIDClientParts,
|
||||||
|
// membership: CallMembershipIdentityParts,
|
||||||
|
// serviceUrl: string,
|
||||||
|
// livekitRoomAlias: string,
|
||||||
|
// matrix2jwt: boolean,
|
||||||
|
// delayEndpointBaseUrl?: string,
|
||||||
|
// delayId?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -220,7 +230,11 @@ export class Connection {
|
|||||||
*
|
*
|
||||||
* @param logger
|
* @param logger
|
||||||
*/
|
*/
|
||||||
public constructor(opts: ConnectionOpts, logger: Logger) {
|
public constructor(
|
||||||
|
opts: ConnectionOpts,
|
||||||
|
logger: Logger,
|
||||||
|
private ownMembershipIdentity: CallMembershipIdentityParts,
|
||||||
|
) {
|
||||||
this.logger = logger.getChild("[Connection]");
|
this.logger = logger.getChild("[Connection]");
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
`[Connection] Creating new connection to ${opts.transport.livekit_service_url} ${opts.transport.livekit_alias}`,
|
`[Connection] Creating new connection to ${opts.transport.livekit_service_url} ${opts.transport.livekit_alias}`,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
|
||||||
import {
|
import {
|
||||||
Room as LivekitRoom,
|
Room as LivekitRoom,
|
||||||
type RoomOptions,
|
type RoomOptions,
|
||||||
@@ -15,6 +14,7 @@ import {
|
|||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
import { type Logger } from "matrix-js-sdk/lib/logger";
|
import { type Logger } from "matrix-js-sdk/lib/logger";
|
||||||
import E2EEWorker from "livekit-client/e2ee-worker?worker";
|
import E2EEWorker from "livekit-client/e2ee-worker?worker";
|
||||||
|
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
||||||
|
|
||||||
import { type ObservableScope } from "../../ObservableScope.ts";
|
import { type ObservableScope } from "../../ObservableScope.ts";
|
||||||
import { Connection } from "./Connection.ts";
|
import { Connection } from "./Connection.ts";
|
||||||
@@ -23,13 +23,15 @@ import type { MediaDevices } from "../../MediaDevices.ts";
|
|||||||
import type { Behavior } from "../../Behavior.ts";
|
import type { Behavior } from "../../Behavior.ts";
|
||||||
import type { ProcessorState } from "../../../livekit/TrackProcessorContext.tsx";
|
import type { ProcessorState } from "../../../livekit/TrackProcessorContext.tsx";
|
||||||
import { defaultLiveKitOptions } from "../../../livekit/options.ts";
|
import { defaultLiveKitOptions } from "../../../livekit/options.ts";
|
||||||
|
import { type LivekitTransportWithVersion } from "./ConnectionManager.ts";
|
||||||
|
|
||||||
// TODO evaluate if this should be done like the Publisher Factory
|
// TODO evaluate if this should be done like the Publisher Factory
|
||||||
export interface ConnectionFactory {
|
export interface ConnectionFactory {
|
||||||
createConnection(
|
createConnection(
|
||||||
transport: LivekitTransport,
|
transport: LivekitTransportWithVersion,
|
||||||
scope: ObservableScope,
|
scope: ObservableScope,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
|
ownMembershipIdentity: CallMembershipIdentityParts,
|
||||||
): Connection;
|
): Connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,10 +79,19 @@ export class ECConnectionFactory implements ConnectionFactory {
|
|||||||
this.livekitRoomFactory = livekitRoomFactory ?? defaultFactory;
|
this.livekitRoomFactory = livekitRoomFactory ?? defaultFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param transport
|
||||||
|
* @param scope
|
||||||
|
* @param logger
|
||||||
|
* @param ownMembershipIdentity required to connect (using the jwt service) with the SFU.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
public createConnection(
|
public createConnection(
|
||||||
transport: LivekitTransport,
|
transport: LivekitTransportWithVersion,
|
||||||
scope: ObservableScope,
|
scope: ObservableScope,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
|
ownMembershipIdentity: CallMembershipIdentityParts,
|
||||||
): Connection {
|
): Connection {
|
||||||
return new Connection(
|
return new Connection(
|
||||||
{
|
{
|
||||||
@@ -90,6 +101,7 @@ export class ECConnectionFactory implements ConnectionFactory {
|
|||||||
livekitRoomFactory: this.livekitRoomFactory,
|
livekitRoomFactory: this.livekitRoomFactory,
|
||||||
},
|
},
|
||||||
logger,
|
logger,
|
||||||
|
ownMembershipIdentity,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,26 +14,29 @@ import { logger } from "matrix-js-sdk/lib/logger";
|
|||||||
import { Epoch, mapEpoch, ObservableScope } from "../../ObservableScope.ts";
|
import { Epoch, mapEpoch, ObservableScope } from "../../ObservableScope.ts";
|
||||||
import {
|
import {
|
||||||
createConnectionManager$,
|
createConnectionManager$,
|
||||||
|
type LivekitTransportWithVersion,
|
||||||
type ConnectionManagerData,
|
type ConnectionManagerData,
|
||||||
} from "./ConnectionManager.ts";
|
} from "./ConnectionManager.ts";
|
||||||
import { type ConnectionFactory } from "./ConnectionFactory.ts";
|
import { type ConnectionFactory } from "./ConnectionFactory.ts";
|
||||||
import { type Connection } from "./Connection.ts";
|
import { type Connection } from "./Connection.ts";
|
||||||
import { withTestScheduler } from "../../../utils/test.ts";
|
import { ownMemberMock, withTestScheduler } from "../../../utils/test.ts";
|
||||||
import { areLivekitTransportsEqual } from "./MatrixLivekitMembers.ts";
|
import { areLivekitTransportsEqual } from "./MatrixLivekitMembers.ts";
|
||||||
import { type Behavior } from "../../Behavior.ts";
|
import { type Behavior } from "../../Behavior.ts";
|
||||||
|
|
||||||
// Some test constants
|
// Some test constants
|
||||||
|
|
||||||
const TRANSPORT_1: LivekitTransport = {
|
const TRANSPORT_1: LivekitTransportWithVersion = {
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
livekit_service_url: "https://lk.example.org",
|
livekit_service_url: "https://lk.example.org",
|
||||||
livekit_alias: "!alias:example.org",
|
livekit_alias: "!alias:example.org",
|
||||||
|
useMatrix2: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const TRANSPORT_2: LivekitTransport = {
|
const TRANSPORT_2: LivekitTransportWithVersion = {
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
livekit_service_url: "https://lk.sample.com",
|
livekit_service_url: "https://lk.sample.com",
|
||||||
livekit_alias: "!alias:sample.com",
|
livekit_alias: "!alias:sample.com",
|
||||||
|
useMatrix2: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let fakeConnectionFactory: ConnectionFactory;
|
let fakeConnectionFactory: ConnectionFactory;
|
||||||
@@ -80,6 +83,7 @@ describe("connections$ stream", () => {
|
|||||||
a: new Epoch([TRANSPORT_1, TRANSPORT_2], 0),
|
a: new Epoch([TRANSPORT_1, TRANSPORT_2], 0),
|
||||||
}),
|
}),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
ownMembershipIdentity: ownMemberMock,
|
||||||
});
|
});
|
||||||
|
|
||||||
expectObservable(
|
expectObservable(
|
||||||
@@ -124,6 +128,7 @@ describe("connections$ stream", () => {
|
|||||||
f: new Epoch([TRANSPORT_1, TRANSPORT_2], 5),
|
f: new Epoch([TRANSPORT_1, TRANSPORT_2], 5),
|
||||||
}),
|
}),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
ownMembershipIdentity: ownMemberMock,
|
||||||
});
|
});
|
||||||
|
|
||||||
expectObservable(
|
expectObservable(
|
||||||
@@ -166,6 +171,7 @@ describe("connections$ stream", () => {
|
|||||||
c: new Epoch([TRANSPORT_1], 2),
|
c: new Epoch([TRANSPORT_1], 2),
|
||||||
}),
|
}),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
ownMembershipIdentity: ownMemberMock,
|
||||||
});
|
});
|
||||||
|
|
||||||
expectObservable(
|
expectObservable(
|
||||||
@@ -279,6 +285,7 @@ describe("connectionManagerData$ stream", () => {
|
|||||||
a: new Epoch([TRANSPORT_1, TRANSPORT_2], 0),
|
a: new Epoch([TRANSPORT_1, TRANSPORT_2], 0),
|
||||||
}),
|
}),
|
||||||
logger,
|
logger,
|
||||||
|
ownMembershipIdentity: ownMemberMock,
|
||||||
});
|
});
|
||||||
|
|
||||||
expectObservable(connectionManagerData$).toBe("abcd", {
|
expectObservable(connectionManagerData$).toBe("abcd", {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
|||||||
import { combineLatest, map, of, switchMap, tap } from "rxjs";
|
import { combineLatest, map, of, switchMap, tap } from "rxjs";
|
||||||
import { type Logger } from "matrix-js-sdk/lib/logger";
|
import { type Logger } from "matrix-js-sdk/lib/logger";
|
||||||
import { type RemoteParticipant } from "livekit-client";
|
import { type RemoteParticipant } from "livekit-client";
|
||||||
|
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
||||||
|
|
||||||
import { type Behavior } from "../../Behavior.ts";
|
import { type Behavior } from "../../Behavior.ts";
|
||||||
import { type Connection } from "./Connection.ts";
|
import { type Connection } from "./Connection.ts";
|
||||||
@@ -18,6 +19,10 @@ import { generateItemsWithEpoch } from "../../../utils/observable.ts";
|
|||||||
import { areLivekitTransportsEqual } from "./MatrixLivekitMembers.ts";
|
import { areLivekitTransportsEqual } from "./MatrixLivekitMembers.ts";
|
||||||
import { type ConnectionFactory } from "./ConnectionFactory.ts";
|
import { type ConnectionFactory } from "./ConnectionFactory.ts";
|
||||||
|
|
||||||
|
export type LivekitTransportWithVersion = LivekitTransport & {
|
||||||
|
useMatrix2: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export class ConnectionManagerData {
|
export class ConnectionManagerData {
|
||||||
private readonly store: Map<string, [Connection, RemoteParticipant[]]> =
|
private readonly store: Map<string, [Connection, RemoteParticipant[]]> =
|
||||||
new Map();
|
new Map();
|
||||||
@@ -59,8 +64,9 @@ export class ConnectionManagerData {
|
|||||||
interface Props {
|
interface Props {
|
||||||
scope: ObservableScope;
|
scope: ObservableScope;
|
||||||
connectionFactory: ConnectionFactory;
|
connectionFactory: ConnectionFactory;
|
||||||
inputTransports$: Behavior<Epoch<LivekitTransport[]>>;
|
inputTransports$: Behavior<Epoch<LivekitTransportWithVersion[]>>;
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
|
ownMembershipIdentity: CallMembershipIdentityParts;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - write test for scopes (do we really need to bind scope)
|
// TODO - write test for scopes (do we really need to bind scope)
|
||||||
@@ -87,6 +93,7 @@ export function createConnectionManager$({
|
|||||||
connectionFactory,
|
connectionFactory,
|
||||||
inputTransports$,
|
inputTransports$,
|
||||||
logger: parentLogger,
|
logger: parentLogger,
|
||||||
|
ownMembershipIdentity,
|
||||||
}: Props): IConnectionManager {
|
}: Props): IConnectionManager {
|
||||||
const logger = parentLogger.getChild("[ConnectionManager]");
|
const logger = parentLogger.getChild("[ConnectionManager]");
|
||||||
// TODO logger: only construct one logger from the client and make it compatible via a EC specific sing
|
// TODO logger: only construct one logger from the client and make it compatible via a EC specific sing
|
||||||
@@ -119,20 +126,26 @@ export function createConnectionManager$({
|
|||||||
function* (transports) {
|
function* (transports) {
|
||||||
for (const transport of transports)
|
for (const transport of transports)
|
||||||
yield {
|
yield {
|
||||||
keys: [transport.livekit_service_url, transport.livekit_alias],
|
keys: [
|
||||||
|
transport.livekit_service_url,
|
||||||
|
transport.livekit_alias,
|
||||||
|
transport.useMatrix2,
|
||||||
|
],
|
||||||
data: undefined,
|
data: undefined,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(scope, _data$, serviceUrl, alias) => {
|
(scope, _data$, serviceUrl, alias, useMatrix2) => {
|
||||||
logger.debug(`Creating connection to ${serviceUrl} (${alias})`);
|
logger.debug(`Creating connection to ${serviceUrl} (${alias})`);
|
||||||
const connection = connectionFactory.createConnection(
|
const connection = connectionFactory.createConnection(
|
||||||
{
|
{
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
livekit_service_url: serviceUrl,
|
livekit_service_url: serviceUrl,
|
||||||
livekit_alias: alias,
|
livekit_alias: alias,
|
||||||
|
useMatrix2,
|
||||||
},
|
},
|
||||||
scope,
|
scope,
|
||||||
logger,
|
logger,
|
||||||
|
ownMembershipIdentity,
|
||||||
);
|
);
|
||||||
// Start the connection immediately
|
// Start the connection immediately
|
||||||
// Use connection state to track connection progress
|
// Use connection state to track connection progress
|
||||||
@@ -187,12 +200,12 @@ export function createConnectionManager$({
|
|||||||
return { connectionManagerData$ };
|
return { connectionManagerData$ };
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeDuplicateTransports(
|
function removeDuplicateTransports<T extends LivekitTransport>(
|
||||||
transports: LivekitTransport[],
|
transports: T[],
|
||||||
): LivekitTransport[] {
|
): T[] {
|
||||||
return transports.reduce((acc, transport) => {
|
return transports.reduce((acc, transport) => {
|
||||||
if (!acc.some((t) => areLivekitTransportsEqual(t, transport)))
|
if (!acc.some((t) => areLivekitTransportsEqual(t, transport)))
|
||||||
acc.push(transport);
|
acc.push(transport);
|
||||||
return acc;
|
return acc;
|
||||||
}, [] as LivekitTransport[]);
|
}, [] as T[]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ import EventEmitter from "events";
|
|||||||
import { ObservableScope } from "../../ObservableScope.ts";
|
import { ObservableScope } from "../../ObservableScope.ts";
|
||||||
import { ECConnectionFactory } from "./ConnectionFactory.ts";
|
import { ECConnectionFactory } from "./ConnectionFactory.ts";
|
||||||
import type { OpenIDClientParts } from "../../../livekit/openIDSFU.ts";
|
import type { OpenIDClientParts } from "../../../livekit/openIDSFU.ts";
|
||||||
import { exampleTransport, mockMediaDevices } from "../../../utils/test.ts";
|
import {
|
||||||
|
exampleTransport,
|
||||||
|
mockMediaDevices,
|
||||||
|
ownMemberMock,
|
||||||
|
} from "../../../utils/test.ts";
|
||||||
import type { ProcessorState } from "../../../livekit/TrackProcessorContext.tsx";
|
import type { ProcessorState } from "../../../livekit/TrackProcessorContext.tsx";
|
||||||
import { constant } from "../../Behavior";
|
import { constant } from "../../Behavior";
|
||||||
|
|
||||||
@@ -72,7 +76,12 @@ describe("ECConnectionFactory - Audio inputs options", () => {
|
|||||||
echo,
|
echo,
|
||||||
noise,
|
noise,
|
||||||
);
|
);
|
||||||
ecConnectionFactory.createConnection(exampleTransport, testScope, logger);
|
ecConnectionFactory.createConnection(
|
||||||
|
exampleTransport,
|
||||||
|
testScope,
|
||||||
|
logger,
|
||||||
|
ownMemberMock,
|
||||||
|
);
|
||||||
|
|
||||||
// Check if Room was constructed with expected options
|
// Check if Room was constructed with expected options
|
||||||
expect(RoomConstructor).toHaveBeenCalledWith(
|
expect(RoomConstructor).toHaveBeenCalledWith(
|
||||||
@@ -113,7 +122,12 @@ describe("ECConnectionFactory - ControlledAudioDevice", () => {
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
ecConnectionFactory.createConnection(exampleTransport, testScope, logger);
|
ecConnectionFactory.createConnection(
|
||||||
|
exampleTransport,
|
||||||
|
testScope,
|
||||||
|
logger,
|
||||||
|
ownMemberMock,
|
||||||
|
);
|
||||||
|
|
||||||
// Check if Room was constructed with expected options
|
// Check if Room was constructed with expected options
|
||||||
expect(RoomConstructor).toHaveBeenCalledWith(
|
expect(RoomConstructor).toHaveBeenCalledWith(
|
||||||
|
|||||||
@@ -176,9 +176,9 @@ export function createMatrixLivekitMembers$({
|
|||||||
// TODO add back in the callviewmodel pauseWhen(this.pretendToBeDisconnected$)
|
// TODO add back in the callviewmodel pauseWhen(this.pretendToBeDisconnected$)
|
||||||
|
|
||||||
// TODO add this to the JS-SDK
|
// TODO add this to the JS-SDK
|
||||||
export function areLivekitTransportsEqual(
|
export function areLivekitTransportsEqual<T extends LivekitTransport>(
|
||||||
t1: LivekitTransport | null,
|
t1: T | null,
|
||||||
t2: LivekitTransport | null,
|
t2: T | null,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (t1 && t2) return t1.livekit_service_url === t2.livekit_service_url;
|
if (t1 && t2) return t1.livekit_service_url === t2.livekit_service_url;
|
||||||
// In case we have different lk rooms in the same SFU (depends on the livekit authorization service)
|
// In case we have different lk rooms in the same SFU (depends on the livekit authorization service)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
mockCallMembership,
|
mockCallMembership,
|
||||||
mockComputeLivekitParticipantIdentity$,
|
mockComputeLivekitParticipantIdentity$,
|
||||||
mockMediaDevices,
|
mockMediaDevices,
|
||||||
|
ownMemberMock,
|
||||||
withTestScheduler,
|
withTestScheduler,
|
||||||
} from "../../../utils/test.ts";
|
} from "../../../utils/test.ts";
|
||||||
import { type ProcessorState } from "../../../livekit/TrackProcessorContext.tsx";
|
import { type ProcessorState } from "../../../livekit/TrackProcessorContext.tsx";
|
||||||
@@ -128,6 +129,7 @@ test("bob, carl, then bob joining no tracks yet", () => {
|
|||||||
connectionFactory: ecConnectionFactory,
|
connectionFactory: ecConnectionFactory,
|
||||||
inputTransports$: membershipsAndTransports.transports$,
|
inputTransports$: membershipsAndTransports.transports$,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
ownMembershipIdentity: ownMemberMock,
|
||||||
});
|
});
|
||||||
|
|
||||||
const matrixLivekitItems$ = createMatrixLivekitMembers$({
|
const matrixLivekitItems$ = createMatrixLivekitMembers$({
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ Please see LICENSE in the repository root for full details.
|
|||||||
import {
|
import {
|
||||||
type CallMembership,
|
type CallMembership,
|
||||||
isLivekitTransport,
|
isLivekitTransport,
|
||||||
type LivekitTransport,
|
|
||||||
type MatrixRTCSession,
|
type MatrixRTCSession,
|
||||||
MatrixRTCSessionEvent,
|
MatrixRTCSessionEvent,
|
||||||
} from "matrix-js-sdk/lib/matrixrtc";
|
} from "matrix-js-sdk/lib/matrixrtc";
|
||||||
@@ -21,15 +20,18 @@ import {
|
|||||||
type ObservableScope,
|
type ObservableScope,
|
||||||
} from "./ObservableScope";
|
} from "./ObservableScope";
|
||||||
import { type Behavior } from "./Behavior";
|
import { type Behavior } from "./Behavior";
|
||||||
|
import { type LivekitTransportWithVersion } from "./CallViewModel/remoteMembers/ConnectionManager";
|
||||||
|
|
||||||
export const membershipsAndTransports$ = (
|
export const membershipsAndTransports$ = (
|
||||||
scope: ObservableScope,
|
scope: ObservableScope,
|
||||||
memberships$: Behavior<Epoch<CallMembership[]>>,
|
memberships$: Behavior<Epoch<CallMembership[]>>,
|
||||||
): {
|
): {
|
||||||
membershipsWithTransport$: Behavior<
|
membershipsWithTransport$: Behavior<
|
||||||
Epoch<{ membership: CallMembership; transport?: LivekitTransport }[]>
|
Epoch<
|
||||||
|
{ membership: CallMembership; transport?: LivekitTransportWithVersion }[]
|
||||||
|
>
|
||||||
>;
|
>;
|
||||||
transports$: Behavior<Epoch<LivekitTransport[]>>;
|
transports$: Behavior<Epoch<LivekitTransportWithVersion[]>>;
|
||||||
} => {
|
} => {
|
||||||
/**
|
/**
|
||||||
* Lists the transports used by ourselves, plus all other MatrixRTC session
|
* Lists the transports used by ourselves, plus all other MatrixRTC session
|
||||||
@@ -47,7 +49,12 @@ export const membershipsAndTransports$ = (
|
|||||||
const transport = membership.getTransport(oldestMembership);
|
const transport = membership.getTransport(oldestMembership);
|
||||||
return {
|
return {
|
||||||
membership,
|
membership,
|
||||||
transport: isLivekitTransport(transport) ? transport : undefined,
|
transport: isLivekitTransport(transport)
|
||||||
|
? {
|
||||||
|
...transport,
|
||||||
|
useMatrix2: membership.kind === "rtc",
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
CallMembership,
|
CallMembership,
|
||||||
type LivekitFocusSelection,
|
type LivekitFocusSelection,
|
||||||
type LivekitTransport,
|
|
||||||
type MatrixRTCSession,
|
type MatrixRTCSession,
|
||||||
MatrixRTCSessionEvent,
|
MatrixRTCSessionEvent,
|
||||||
type MatrixRTCSessionEventHandlerMap,
|
type MatrixRTCSessionEventHandlerMap,
|
||||||
@@ -67,6 +66,7 @@ import { type MediaDevices } from "../state/MediaDevices";
|
|||||||
import { type Behavior, constant } from "../state/Behavior";
|
import { type Behavior, constant } from "../state/Behavior";
|
||||||
import { ObservableScope } from "../state/ObservableScope";
|
import { ObservableScope } from "../state/ObservableScope";
|
||||||
import { MuteStates } from "../state/MuteStates";
|
import { MuteStates } from "../state/MuteStates";
|
||||||
|
import { type LivekitTransportWithVersion } from "../state/CallViewModel/remoteMembers/ConnectionManager";
|
||||||
|
|
||||||
export function withFakeTimers(continuation: () => void): void {
|
export function withFakeTimers(continuation: () => void): void {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
@@ -197,10 +197,11 @@ export function mockEmitter<T>(): EmitterMock<T> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const exampleTransport: LivekitTransport = {
|
export const exampleTransport: LivekitTransportWithVersion = {
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
livekit_service_url: "https://lk.example.org",
|
livekit_service_url: "https://lk.example.org",
|
||||||
livekit_alias: "!alias:example.org",
|
livekit_alias: "!alias:example.org",
|
||||||
|
useMatrix2: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function mockCallMembership(
|
export function mockCallMembership(
|
||||||
@@ -256,6 +257,11 @@ export function mockRtcMembership(
|
|||||||
return cms;
|
return cms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ownMemberMock: CallMembershipIdentityParts = {
|
||||||
|
userId: "@alice:example.org",
|
||||||
|
deviceId: "DEVICE",
|
||||||
|
memberId: "@alice:example.org:DEVICE",
|
||||||
|
};
|
||||||
// Maybe it'd be good to move this to matrix-js-sdk? Our testing needs are
|
// Maybe it'd be good to move this to matrix-js-sdk? Our testing needs are
|
||||||
// rather simple, but if one util to mock a member is good enough for us, maybe
|
// rather simple, but if one util to mock a member is good enough for us, maybe
|
||||||
// it's useful for matrix-js-sdk consumers in general.
|
// it's useful for matrix-js-sdk consumers in general.
|
||||||
|
|||||||
@@ -10338,9 +10338,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#head=toger5/use-membershipID-for-session-state-events&commit=f5f1b8efb46b3d55a7eebfabb4a61496640b8b00":
|
"matrix-js-sdk@portal:/Users/timo/Projects/matrix-js-sdk::locator=element-call%40workspace%3A.":
|
||||||
version: 39.3.0
|
version: 0.0.0-use.local
|
||||||
resolution: "matrix-js-sdk@https://github.com/matrix-org/matrix-js-sdk.git#commit=f5f1b8efb46b3d55a7eebfabb4a61496640b8b00"
|
resolution: "matrix-js-sdk@portal:/Users/timo/Projects/matrix-js-sdk::locator=element-call%40workspace%3A."
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime": "npm:^7.12.5"
|
"@babel/runtime": "npm:^7.12.5"
|
||||||
"@matrix-org/matrix-sdk-crypto-wasm": "npm:^16.0.0"
|
"@matrix-org/matrix-sdk-crypto-wasm": "npm:^16.0.0"
|
||||||
@@ -10356,9 +10356,8 @@ __metadata:
|
|||||||
sdp-transform: "npm:^3.0.0"
|
sdp-transform: "npm:^3.0.0"
|
||||||
unhomoglyph: "npm:^1.0.6"
|
unhomoglyph: "npm:^1.0.6"
|
||||||
uuid: "npm:13"
|
uuid: "npm:13"
|
||||||
checksum: 10c0/9607b0c063c873a24c1a2d05cc7500d60c32556ec82b666ebaae5c5e829faf5bb7639780efddea7211e6b9873098bd53b97656f041e932e8b0de0c208ccabbff
|
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: soft
|
||||||
|
|
||||||
"matrix-widget-api@npm:^1.14.0":
|
"matrix-widget-api@npm:^1.14.0":
|
||||||
version: 1.15.0
|
version: 1.15.0
|
||||||
|
|||||||
Reference in New Issue
Block a user