mirror of
https://github.com/vector-im/element-call.git
synced 2026-10-16 23:45:50 +00:00
Account for server name when identifying transports
This commit is contained in:
@@ -29,6 +29,10 @@ export interface GetSFUConfigParams {
|
|||||||
* The WebSocket URL of the SFU for which we wish to get an access token.
|
* The WebSocket URL of the SFU for which we wish to get an access token.
|
||||||
*/
|
*/
|
||||||
url: string;
|
url: string;
|
||||||
|
/**
|
||||||
|
* The name of the homeserver to which the SFU belongs.
|
||||||
|
*/
|
||||||
|
serverName: string;
|
||||||
/**
|
/**
|
||||||
* The ID of the Matrix room in which the session takes place.
|
* The ID of the Matrix room in which the session takes place.
|
||||||
*/
|
*/
|
||||||
@@ -47,12 +51,14 @@ export async function getSFUConfig({
|
|||||||
client,
|
client,
|
||||||
membership,
|
membership,
|
||||||
url,
|
url,
|
||||||
|
serverName,
|
||||||
roomId,
|
roomId,
|
||||||
slotId,
|
slotId,
|
||||||
}: GetSFUConfigParams): Promise<SFUConfig> {
|
}: GetSFUConfigParams): Promise<SFUConfig> {
|
||||||
const res = await doNetworkOperationWithRetry(async () =>
|
const res = await doNetworkOperationWithRetry(async () =>
|
||||||
client._unstable_getLivekitToken({
|
client._unstable_getLivekitToken({
|
||||||
url,
|
url,
|
||||||
|
server_name: serverName,
|
||||||
room_id: roomId,
|
room_id: roomId,
|
||||||
slot_id: slotId,
|
slot_id: slotId,
|
||||||
member_id: membership.memberId,
|
member_id: membership.memberId,
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ export interface GetSFUConfigParams {
|
|||||||
* The transport for which we wish to get an access token.
|
* The transport for which we wish to get an access token.
|
||||||
*/
|
*/
|
||||||
transport: UnstableLivekitTransport;
|
transport: UnstableLivekitTransport;
|
||||||
|
/**
|
||||||
|
* The name of the homeserver to which the {@link transport} belongs.
|
||||||
|
*/
|
||||||
|
serverName: string;
|
||||||
/**
|
/**
|
||||||
* The ID of the Matrix room in which the session takes place.
|
* The ID of the Matrix room in which the session takes place.
|
||||||
*/
|
*/
|
||||||
@@ -72,6 +76,7 @@ export async function getSFUConfig({
|
|||||||
client,
|
client,
|
||||||
membership,
|
membership,
|
||||||
transport,
|
transport,
|
||||||
|
serverName,
|
||||||
roomId,
|
roomId,
|
||||||
slotId,
|
slotId,
|
||||||
role,
|
role,
|
||||||
@@ -83,6 +88,7 @@ export async function getSFUConfig({
|
|||||||
client,
|
client,
|
||||||
membership,
|
membership,
|
||||||
url: transport.url,
|
url: transport.url,
|
||||||
|
serverName,
|
||||||
roomId,
|
roomId,
|
||||||
slotId,
|
slotId,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,17 @@ 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 UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data identifying a transport and the homeserver to which it belongs, so
|
||||||
|
* consumers know how to reach it.
|
||||||
|
*/
|
||||||
|
export interface TransportLocator {
|
||||||
|
transport: UnstableLivekitTransport;
|
||||||
|
serverName: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration and access tokens provided by the SFU on successful authentication.
|
* Configuration and access tokens provided by the SFU on successful authentication.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -526,6 +526,7 @@ export const DeveloperSettingsTab: FC<Props> = ({
|
|||||||
client,
|
client,
|
||||||
membership: { userId, deviceId, memberId: "" },
|
membership: { userId, deviceId, memberId: "" },
|
||||||
transport,
|
transport,
|
||||||
|
serverName: client.getDomain()!,
|
||||||
roomId,
|
roomId,
|
||||||
slotId: "org.example.fake-slot#room",
|
slotId: "org.example.fake-slot#room",
|
||||||
role: "publisher",
|
role: "publisher",
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ import {
|
|||||||
type RingingMediaViewModel,
|
type RingingMediaViewModel,
|
||||||
} from "../media/RingingMediaViewModel.ts";
|
} from "../media/RingingMediaViewModel.ts";
|
||||||
import { type GridTileViewModel } from "../TileViewModel.ts";
|
import { type GridTileViewModel } from "../TileViewModel.ts";
|
||||||
|
import { type TransportLocator } from "../../livekit/auth";
|
||||||
|
|
||||||
//TODO
|
//TODO
|
||||||
// Larger rename
|
// Larger rename
|
||||||
@@ -215,7 +216,7 @@ export interface CallViewModelOptions {
|
|||||||
*/
|
*/
|
||||||
windowSize$: Behavior<{ width: number; height: number }>;
|
windowSize$: Behavior<{ width: number; height: number }>;
|
||||||
/** Optional value overriding the local transport, for testing purposes. */
|
/** Optional value overriding the local transport, for testing purposes. */
|
||||||
localTransport?: UnstableLivekitTransport;
|
localTransport?: TransportLocator;
|
||||||
/** Optional value overriding the connection factory, for testing purposes. */
|
/** Optional value overriding the connection factory, for testing purposes. */
|
||||||
connectionFactory?: ConnectionFactory;
|
connectionFactory?: ConnectionFactory;
|
||||||
/** The version & compatibility mode of MatrixRTC that we should use. */
|
/** The version & compatibility mode of MatrixRTC that we should use. */
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
type CallViewModelOptions,
|
type CallViewModelOptions,
|
||||||
} from "./CallViewModel";
|
} from "./CallViewModel";
|
||||||
import {
|
import {
|
||||||
exampleTransport,
|
exampleTransportLocator,
|
||||||
mockConfig,
|
mockConfig,
|
||||||
MockConnection,
|
MockConnection,
|
||||||
mockLivekitRoom,
|
mockLivekitRoom,
|
||||||
@@ -208,12 +208,13 @@ export function withCallViewModel(mode: MatrixRTCMode) {
|
|||||||
livekitRoomFactory,
|
livekitRoomFactory,
|
||||||
connectionState$,
|
connectionState$,
|
||||||
windowSize$,
|
windowSize$,
|
||||||
localTransport: exampleTransport,
|
localTransport: exampleTransportLocator,
|
||||||
connectionFactory: {
|
connectionFactory: {
|
||||||
createConnection(
|
createConnection(
|
||||||
scope,
|
scope,
|
||||||
role,
|
role,
|
||||||
transport,
|
transport,
|
||||||
|
serverName,
|
||||||
ownMembershipIdentity,
|
ownMembershipIdentity,
|
||||||
logger,
|
logger,
|
||||||
) {
|
) {
|
||||||
@@ -222,6 +223,7 @@ export function withCallViewModel(mode: MatrixRTCMode) {
|
|||||||
scope,
|
scope,
|
||||||
role,
|
role,
|
||||||
transport,
|
transport,
|
||||||
|
serverName,
|
||||||
ownMembershipIdentity,
|
ownMembershipIdentity,
|
||||||
client: room.client,
|
client: room.client,
|
||||||
roomId: room.roomId,
|
roomId: room.roomId,
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ describe("LocalMembership", () => {
|
|||||||
leave: vi.fn(),
|
leave: vi.fn(),
|
||||||
},
|
},
|
||||||
connectionManager: mockConnectionManager,
|
connectionManager: mockConnectionManager,
|
||||||
localTransport$: constant(mockTransport),
|
localTransport$: constant(mockTransportLocator),
|
||||||
});
|
});
|
||||||
const expextedLog =
|
const expextedLog =
|
||||||
"'not connected yet' while updating the call intent (this is expected on startup)";
|
"'not connected yet' while updating the call intent (this is expected on startup)";
|
||||||
@@ -321,7 +321,12 @@ describe("LocalMembership", () => {
|
|||||||
type: "livekit",
|
type: "livekit",
|
||||||
url: "https://sfu.example.org",
|
url: "https://sfu.example.org",
|
||||||
livekit_service_url: "https://jwt.example.org",
|
livekit_service_url: "https://jwt.example.org",
|
||||||
} as UnstableLivekitTransport;
|
};
|
||||||
|
|
||||||
|
const mockTransportLocator: livekitAuth.TransportLocator = {
|
||||||
|
transport: mockTransport,
|
||||||
|
serverName: "example.org",
|
||||||
|
};
|
||||||
|
|
||||||
const connectionTransportAConnected = {
|
const connectionTransportAConnected = {
|
||||||
livekitRoom: mockLivekitRoom({
|
livekitRoom: mockLivekitRoom({
|
||||||
@@ -375,7 +380,7 @@ describe("LocalMembership", () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
joinMatrixRTC,
|
joinMatrixRTC,
|
||||||
localTransport$: constant(mockTransport),
|
localTransport$: constant(mockTransportLocator),
|
||||||
delayId$,
|
delayId$,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -452,7 +457,7 @@ describe("LocalMembership", () => {
|
|||||||
connectionManager: {
|
connectionManager: {
|
||||||
connectionManagerData$: constant(new Epoch(connectionManagerData)),
|
connectionManagerData$: constant(new Epoch(connectionManagerData)),
|
||||||
},
|
},
|
||||||
localTransport$: constant(mockTransport),
|
localTransport$: constant(mockTransportLocator),
|
||||||
});
|
});
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
expect(publisherFactory).toHaveBeenCalledOnce();
|
expect(publisherFactory).toHaveBeenCalledOnce();
|
||||||
@@ -474,7 +479,7 @@ describe("LocalMembership", () => {
|
|||||||
it("tracks livekit state correctly", async () => {
|
it("tracks livekit state correctly", async () => {
|
||||||
const scope = new ObservableScope();
|
const scope = new ObservableScope();
|
||||||
const connectionManagerData = new ConnectionManagerData();
|
const connectionManagerData = new ConnectionManagerData();
|
||||||
const localTransport$ = new Subject<UnstableLivekitTransport>();
|
const localTransport$ = new Subject<livekitAuth.TransportLocator>();
|
||||||
|
|
||||||
const connectionManagerData$ = new BehaviorSubject(
|
const connectionManagerData$ = new BehaviorSubject(
|
||||||
new Epoch(connectionManagerData),
|
new Epoch(connectionManagerData),
|
||||||
@@ -522,7 +527,7 @@ describe("LocalMembership", () => {
|
|||||||
expect(localMembership.localMemberState$.value).toStrictEqual(
|
expect(localMembership.localMemberState$.value).toStrictEqual(
|
||||||
TransportState.Waiting,
|
TransportState.Waiting,
|
||||||
);
|
);
|
||||||
localTransport$.next(mockTransport);
|
localTransport$.next(mockTransportLocator);
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
expect(localMembership.localMemberState$.value).toStrictEqual({
|
expect(localMembership.localMemberState$.value).toStrictEqual({
|
||||||
matrix: RTCMemberStatus.Connected,
|
matrix: RTCMemberStatus.Connected,
|
||||||
@@ -547,7 +552,7 @@ describe("LocalMembership", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
(
|
(
|
||||||
connectionManagerData2.getConnectionForTransport(mockTransport)!
|
connectionManagerData2.getConnectionForTransport(mockTransportLocator)!
|
||||||
.state$ as BehaviorSubject<ConnectionState>
|
.state$ as BehaviorSubject<ConnectionState>
|
||||||
).next(ConnectionState.LivekitConnected);
|
).next(ConnectionState.LivekitConnected);
|
||||||
expect(localMembership.localMemberState$.value).toStrictEqual({
|
expect(localMembership.localMemberState$.value).toStrictEqual({
|
||||||
@@ -650,7 +655,7 @@ describe("LocalMembership", () => {
|
|||||||
connectionManager: {
|
connectionManager: {
|
||||||
connectionManagerData$: constant(new Epoch(connectionManagerData)),
|
connectionManagerData$: constant(new Epoch(connectionManagerData)),
|
||||||
},
|
},
|
||||||
localTransport$: constant(mockTransport),
|
localTransport$: constant(mockTransportLocator),
|
||||||
});
|
});
|
||||||
|
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
@@ -687,7 +692,7 @@ describe("LocalMembership", () => {
|
|||||||
connectionManager: {
|
connectionManager: {
|
||||||
connectionManagerData$: constant(new Epoch(connectionManagerData)),
|
connectionManagerData$: constant(new Epoch(connectionManagerData)),
|
||||||
},
|
},
|
||||||
localTransport$: constant(mockTransport),
|
localTransport$: constant(mockTransportLocator),
|
||||||
});
|
});
|
||||||
|
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
@@ -735,7 +740,7 @@ describe("LocalMembership", () => {
|
|||||||
connectionManager: {
|
connectionManager: {
|
||||||
connectionManagerData$: constant(new Epoch(connectionManagerData)),
|
connectionManagerData$: constant(new Epoch(connectionManagerData)),
|
||||||
},
|
},
|
||||||
localTransport$: constant(mockTransport),
|
localTransport$: constant(mockTransportLocator),
|
||||||
});
|
});
|
||||||
|
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
@@ -776,7 +781,7 @@ describe("LocalMembership", () => {
|
|||||||
connectionManager: {
|
connectionManager: {
|
||||||
connectionManagerData$: constant(new Epoch(connectionManagerData)),
|
connectionManagerData$: constant(new Epoch(connectionManagerData)),
|
||||||
},
|
},
|
||||||
localTransport$: constant(mockTransport),
|
localTransport$: constant(mockTransportLocator),
|
||||||
});
|
});
|
||||||
|
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
@@ -842,7 +847,7 @@ describe("LocalMembership", () => {
|
|||||||
connectionManager: {
|
connectionManager: {
|
||||||
connectionManagerData$: constant(new Epoch(connectionManagerData)),
|
connectionManagerData$: constant(new Epoch(connectionManagerData)),
|
||||||
},
|
},
|
||||||
localTransport$: constant(mockTransport),
|
localTransport$: constant(mockTransportLocator),
|
||||||
});
|
});
|
||||||
return { scope, localMembership };
|
return { scope, localMembership };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ import {
|
|||||||
type ClientDelegationParts,
|
type ClientDelegationParts,
|
||||||
delegateDelayedLeave,
|
delegateDelayedLeave,
|
||||||
type ClientOpenIDParts,
|
type ClientOpenIDParts,
|
||||||
|
type TransportLocator,
|
||||||
} from "../../../livekit/auth";
|
} from "../../../livekit/auth";
|
||||||
|
|
||||||
export enum TransportState {
|
export enum TransportState {
|
||||||
@@ -151,7 +152,7 @@ interface Props {
|
|||||||
homeserverConnected: HomeserverConnected;
|
homeserverConnected: HomeserverConnected;
|
||||||
roomId: string;
|
roomId: string;
|
||||||
ownMembershipIdentity: CallMembershipIdentityParts;
|
ownMembershipIdentity: CallMembershipIdentityParts;
|
||||||
localTransport$: Observable<UnstableLivekitTransport>;
|
localTransport$: Observable<TransportLocator>;
|
||||||
client: ClientDelegationParts & ClientOpenIDParts;
|
client: ClientDelegationParts & ClientOpenIDParts;
|
||||||
matrixRTCSession: Pick<
|
matrixRTCSession: Pick<
|
||||||
MatrixRTCSession,
|
MatrixRTCSession,
|
||||||
@@ -310,7 +311,7 @@ export const createLocalMembership$ = ({
|
|||||||
// to whether delayed event delegation is supported
|
// to whether delayed event delegation is supported
|
||||||
const joinParams$ = scope.behavior(
|
const joinParams$ = scope.behavior(
|
||||||
localTransport$.pipe(
|
localTransport$.pipe(
|
||||||
switchMap(async (transport) => {
|
switchMap(async ({ transport }) => {
|
||||||
const transportSupportsDelegation =
|
const transportSupportsDelegation =
|
||||||
"livekit_service_url" in transport &&
|
"livekit_service_url" in transport &&
|
||||||
(await checkDelegationSupport(
|
(await checkDelegationSupport(
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ SPDX-License-IdFentifier: 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 UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
|
||||||
import { type MatrixClient } from "matrix-js-sdk";
|
import { type MatrixClient } from "matrix-js-sdk";
|
||||||
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
||||||
|
|
||||||
@@ -13,6 +12,7 @@ import { Config } from "../../../config/Config.ts";
|
|||||||
import { MatrixRTCTransportMissingError } from "../../../utils/errors.ts";
|
import { MatrixRTCTransportMissingError } from "../../../utils/errors.ts";
|
||||||
import { customTransport as customTransportSetting } from "../../../settings/settings.ts";
|
import { customTransport as customTransportSetting } from "../../../settings/settings.ts";
|
||||||
import { RtcTransportAutoDiscovery } from "./RtcTransportAutoDiscovery.ts";
|
import { RtcTransportAutoDiscovery } from "./RtcTransportAutoDiscovery.ts";
|
||||||
|
import { type TransportLocator } from "../../../livekit/auth/types.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines the transport to advertise in our MatrixRTC membership and publish
|
* Determines the transport to advertise in our MatrixRTC membership and publish
|
||||||
@@ -20,7 +20,10 @@ import { RtcTransportAutoDiscovery } from "./RtcTransportAutoDiscovery.ts";
|
|||||||
*/
|
*/
|
||||||
export async function getLocalTransport(
|
export async function getLocalTransport(
|
||||||
client: Pick<MatrixClient, "getDomain" | "_unstable_getRTCTransports">,
|
client: Pick<MatrixClient, "getDomain" | "_unstable_getRTCTransports">,
|
||||||
): Promise<UnstableLivekitTransport> {
|
): Promise<TransportLocator> {
|
||||||
|
const serverName = client.getDomain();
|
||||||
|
if (serverName === null) throw new Error("No server name");
|
||||||
|
|
||||||
const discovery = new RtcTransportAutoDiscovery({
|
const discovery = new RtcTransportAutoDiscovery({
|
||||||
client,
|
client,
|
||||||
resolvedConfig: Config.get(),
|
resolvedConfig: Config.get(),
|
||||||
@@ -32,11 +35,10 @@ export async function getLocalTransport(
|
|||||||
customTransportSetting.value$.value ??
|
customTransportSetting.value$.value ??
|
||||||
(await discovery.discoverPreferredTransport());
|
(await discovery.discoverPreferredTransport());
|
||||||
|
|
||||||
if (transport === null)
|
if (transport === null) throw new MatrixRTCTransportMissingError(serverName);
|
||||||
throw new MatrixRTCTransportMissingError(client.getDomain() ?? "");
|
|
||||||
|
|
||||||
// TODO: Since this module no longer maps auth errors to fatal user-facing
|
// TODO: Since this module no longer maps auth errors to fatal user-facing
|
||||||
// errors (as it doesn't even perform auth), that needs to happen somewhere
|
// errors (as it doesn't even perform auth), that needs to happen somewhere
|
||||||
// else.
|
// else.
|
||||||
return transport;
|
return { transport, serverName };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,11 +121,12 @@ function setupTest(): void {
|
|||||||
|
|
||||||
function setupRemoteConnection(): Connection {
|
function setupRemoteConnection(): Connection {
|
||||||
const opts: ConnectionOpts = {
|
const opts: ConnectionOpts = {
|
||||||
client: client,
|
client,
|
||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
slotId: SLOT_ID,
|
slotId: SLOT_ID,
|
||||||
role: "subscriber",
|
role: "subscriber",
|
||||||
transport: transport,
|
transport,
|
||||||
|
serverName: "m.localhost",
|
||||||
scope: testScope,
|
scope: testScope,
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
livekitRoomFactory: () => fakeLivekitRoom,
|
livekitRoomFactory: () => fakeLivekitRoom,
|
||||||
@@ -169,11 +170,12 @@ describe("Start connection states", () => {
|
|||||||
setupTest();
|
setupTest();
|
||||||
|
|
||||||
const opts: ConnectionOpts = {
|
const opts: ConnectionOpts = {
|
||||||
client: client,
|
client,
|
||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
slotId: SLOT_ID,
|
slotId: SLOT_ID,
|
||||||
role: "subscriber",
|
role: "subscriber",
|
||||||
transport: transport,
|
transport,
|
||||||
|
serverName: "m.localhost",
|
||||||
scope: testScope,
|
scope: testScope,
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
livekitRoomFactory: () => fakeLivekitRoom,
|
livekitRoomFactory: () => fakeLivekitRoom,
|
||||||
@@ -188,11 +190,12 @@ describe("Start connection states", () => {
|
|||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
|
|
||||||
const opts: ConnectionOpts = {
|
const opts: ConnectionOpts = {
|
||||||
client: client,
|
client,
|
||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
slotId: SLOT_ID,
|
slotId: SLOT_ID,
|
||||||
role: "subscriber",
|
role: "subscriber",
|
||||||
transport: transport,
|
transport,
|
||||||
|
serverName: "m.localhost",
|
||||||
scope: testScope,
|
scope: testScope,
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
livekitRoomFactory: () => fakeLivekitRoom,
|
livekitRoomFactory: () => fakeLivekitRoom,
|
||||||
@@ -240,11 +243,12 @@ describe("Start connection states", () => {
|
|||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
|
|
||||||
const opts: ConnectionOpts = {
|
const opts: ConnectionOpts = {
|
||||||
client: client,
|
client,
|
||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
slotId: SLOT_ID,
|
slotId: SLOT_ID,
|
||||||
role: "subscriber",
|
role: "subscriber",
|
||||||
transport: transport,
|
transport,
|
||||||
|
serverName: "m.localhost",
|
||||||
scope: testScope,
|
scope: testScope,
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
livekitRoomFactory: () => fakeLivekitRoom,
|
livekitRoomFactory: () => fakeLivekitRoom,
|
||||||
@@ -301,11 +305,12 @@ describe("Start connection states", () => {
|
|||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
|
|
||||||
const opts: ConnectionOpts = {
|
const opts: ConnectionOpts = {
|
||||||
client: client,
|
client,
|
||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
slotId: SLOT_ID,
|
slotId: SLOT_ID,
|
||||||
role: "subscriber",
|
role: "subscriber",
|
||||||
transport: transport,
|
transport,
|
||||||
|
serverName: "m.localhost",
|
||||||
scope: testScope,
|
scope: testScope,
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
livekitRoomFactory: () => fakeLivekitRoom,
|
livekitRoomFactory: () => fakeLivekitRoom,
|
||||||
@@ -545,7 +550,8 @@ describe("remote track logging", () => {
|
|||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
slotId: SLOT_ID,
|
slotId: SLOT_ID,
|
||||||
role: "subscriber",
|
role: "subscriber",
|
||||||
transport: transport,
|
transport,
|
||||||
|
serverName: "m.localhost",
|
||||||
scope: testScope,
|
scope: testScope,
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
livekitRoomFactory: () => fakeLivekitRoom,
|
livekitRoomFactory: () => fakeLivekitRoom,
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ export interface ConnectionOpts {
|
|||||||
role: "publisher" | "subscriber";
|
role: "publisher" | "subscriber";
|
||||||
/** The media transport to connect to. */
|
/** The media transport to connect to. */
|
||||||
transport: UnstableLivekitTransport;
|
transport: UnstableLivekitTransport;
|
||||||
|
/** The name of the homeserver to which the {@link transport} belongs. */
|
||||||
|
serverName: string;
|
||||||
/** The Matrix client to use for OpenID and SFU config requests. */
|
/** The Matrix client to use for OpenID and SFU config requests. */
|
||||||
client: ClientGetTokenParts & ClientOpenIDParts;
|
client: ClientGetTokenParts & ClientOpenIDParts;
|
||||||
/** The ID of the Matrix room in which the session takes place. */
|
/** The ID of the Matrix room in which the session takes place. */
|
||||||
@@ -114,6 +116,11 @@ export class Connection {
|
|||||||
*/
|
*/
|
||||||
public readonly transport: UnstableLivekitTransport;
|
public readonly transport: UnstableLivekitTransport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the homeserver to which the {@link transport} belongs.
|
||||||
|
*/
|
||||||
|
public readonly serverName: string;
|
||||||
|
|
||||||
public readonly livekitRoom: LivekitRoom;
|
public readonly livekitRoom: LivekitRoom;
|
||||||
|
|
||||||
private scope: ObservableScope;
|
private scope: ObservableScope;
|
||||||
@@ -162,12 +169,13 @@ export class Connection {
|
|||||||
`[Connection ${JSON.stringify(opts.transport)}]`,
|
`[Connection ${JSON.stringify(opts.transport)}]`,
|
||||||
);
|
);
|
||||||
this.logger.debug(`constructor called`);
|
this.logger.debug(`constructor called`);
|
||||||
const { role, transport, client, scope } = opts;
|
const { role, transport, serverName, client, scope } = opts;
|
||||||
|
|
||||||
this.scope = scope;
|
this.scope = scope;
|
||||||
this.livekitRoom = opts.livekitRoomFactory();
|
this.livekitRoom = opts.livekitRoomFactory();
|
||||||
this.role = role;
|
this.role = role;
|
||||||
this.transport = transport;
|
this.transport = transport;
|
||||||
|
this.serverName = serverName;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
|
||||||
this.remoteParticipants$ = scope.behavior(
|
this.remoteParticipants$ = scope.behavior(
|
||||||
@@ -401,6 +409,7 @@ export class Connection {
|
|||||||
membership: this.ownMembershipIdentity,
|
membership: this.ownMembershipIdentity,
|
||||||
role: this.role,
|
role: this.role,
|
||||||
transport: this.transport,
|
transport: this.transport,
|
||||||
|
serverName: this.serverName,
|
||||||
roomId: this.roomId,
|
roomId: this.roomId,
|
||||||
slotId: this.slotId,
|
slotId: this.slotId,
|
||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export interface ConnectionFactory {
|
|||||||
scope: ObservableScope,
|
scope: ObservableScope,
|
||||||
role: "publisher" | "subscriber",
|
role: "publisher" | "subscriber",
|
||||||
transport: UnstableLivekitTransport,
|
transport: UnstableLivekitTransport,
|
||||||
|
serverName: string,
|
||||||
ownMembershipIdentity: CallMembershipIdentityParts,
|
ownMembershipIdentity: CallMembershipIdentityParts,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
): Connection;
|
): Connection;
|
||||||
@@ -98,6 +99,7 @@ export class ECConnectionFactory implements ConnectionFactory {
|
|||||||
*
|
*
|
||||||
* @param scope The observable scope (used for clean-up)
|
* @param scope The observable scope (used for clean-up)
|
||||||
* @param transport The transport to use for this connection.
|
* @param transport The transport to use for this connection.
|
||||||
|
* @param serverName The name of the homeserver to which the {@link transport} belongs.
|
||||||
* @param ownMembershipIdentity required to connect (using the jwt service) with the SFU.
|
* @param ownMembershipIdentity required to connect (using the jwt service) with the SFU.
|
||||||
* @param logger The logger instance to use for this connection.
|
* @param logger The logger instance to use for this connection.
|
||||||
* @param sfuConfig optional config in case we already have a token for this connection.
|
* @param sfuConfig optional config in case we already have a token for this connection.
|
||||||
@@ -107,6 +109,7 @@ export class ECConnectionFactory implements ConnectionFactory {
|
|||||||
scope: ObservableScope,
|
scope: ObservableScope,
|
||||||
role: "publisher" | "subscriber",
|
role: "publisher" | "subscriber",
|
||||||
transport: UnstableLivekitTransport,
|
transport: UnstableLivekitTransport,
|
||||||
|
serverName: string,
|
||||||
ownMembershipIdentity: CallMembershipIdentityParts,
|
ownMembershipIdentity: CallMembershipIdentityParts,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
): Connection {
|
): Connection {
|
||||||
@@ -116,6 +119,7 @@ export class ECConnectionFactory implements ConnectionFactory {
|
|||||||
slotId: this.slotId,
|
slotId: this.slotId,
|
||||||
role,
|
role,
|
||||||
transport,
|
transport,
|
||||||
|
serverName,
|
||||||
client: this.client,
|
client: this.client,
|
||||||
scope: scope,
|
scope: scope,
|
||||||
livekitRoomFactory: this.livekitRoomFactory,
|
livekitRoomFactory: this.livekitRoomFactory,
|
||||||
|
|||||||
@@ -15,23 +15,30 @@ import { Epoch, mapEpoch, ObservableScope } from "../../ObservableScope.ts";
|
|||||||
import {
|
import {
|
||||||
createConnectionManager$,
|
createConnectionManager$,
|
||||||
ConnectionManagerData,
|
ConnectionManagerData,
|
||||||
|
areUnstableLivekitTransportsEqual,
|
||||||
} 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 { ownMemberMock, withTestScheduler } from "../../../utils/test.ts";
|
import { ownMemberMock, withTestScheduler } from "../../../utils/test.ts";
|
||||||
import { areUnstableLivekitTransportsEqual } from "./MatrixLivekitMembers.ts";
|
|
||||||
import { type Behavior } from "../../Behavior.ts";
|
import { type Behavior } from "../../Behavior.ts";
|
||||||
|
import { type TransportLocator } from "../../../livekit/auth";
|
||||||
|
|
||||||
// Some test constants
|
// Some test constants
|
||||||
|
|
||||||
const TRANSPORT_1: UnstableLivekitTransport = {
|
const TRANSPORT_1: TransportLocator = {
|
||||||
type: "livekit",
|
transport: {
|
||||||
livekit_service_url: "https://lk.example.org",
|
type: "livekit",
|
||||||
|
livekit_service_url: "https://lk.example.org",
|
||||||
|
},
|
||||||
|
serverName: "example.org",
|
||||||
};
|
};
|
||||||
|
|
||||||
const TRANSPORT_2: UnstableLivekitTransport = {
|
const TRANSPORT_2: TransportLocator = {
|
||||||
type: "livekit",
|
transport: {
|
||||||
livekit_service_url: "https://lk.sample.com",
|
type: "livekit",
|
||||||
|
livekit_service_url: "https://lk.sample.com",
|
||||||
|
},
|
||||||
|
serverName: "sample.com",
|
||||||
};
|
};
|
||||||
|
|
||||||
let fakeConnectionFactory: ConnectionFactory;
|
let fakeConnectionFactory: ConnectionFactory;
|
||||||
@@ -94,13 +101,19 @@ describe("connections$ stream", () => {
|
|||||||
).toHaveBeenCalledTimes(2);
|
).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
const conn1 = connections.find((c) =>
|
const conn1 = connections.find((c) =>
|
||||||
areUnstableLivekitTransportsEqual(c.transport, TRANSPORT_1),
|
areUnstableLivekitTransportsEqual(
|
||||||
|
c.transport,
|
||||||
|
TRANSPORT_1.transport,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
expect(conn1).toBeDefined();
|
expect(conn1).toBeDefined();
|
||||||
expect(conn1!.start).toHaveBeenCalled();
|
expect(conn1!.start).toHaveBeenCalled();
|
||||||
|
|
||||||
const conn2 = connections.find((c) =>
|
const conn2 = connections.find((c) =>
|
||||||
areUnstableLivekitTransportsEqual(c.transport, TRANSPORT_2),
|
areUnstableLivekitTransportsEqual(
|
||||||
|
c.transport,
|
||||||
|
TRANSPORT_2.transport,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
expect(conn2).toBeDefined();
|
expect(conn2).toBeDefined();
|
||||||
expect(conn2!.start).toHaveBeenCalled();
|
expect(conn2!.start).toHaveBeenCalled();
|
||||||
@@ -141,12 +154,18 @@ describe("connections$ stream", () => {
|
|||||||
).toHaveBeenCalledTimes(2);
|
).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
const conn2 = connections.find((c) =>
|
const conn2 = connections.find((c) =>
|
||||||
areUnstableLivekitTransportsEqual(c.transport, TRANSPORT_2),
|
areUnstableLivekitTransportsEqual(
|
||||||
|
c.transport,
|
||||||
|
TRANSPORT_2.transport,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
expect(conn2).toBeDefined();
|
expect(conn2).toBeDefined();
|
||||||
|
|
||||||
const conn1 = connections.find((c) =>
|
const conn1 = connections.find((c) =>
|
||||||
areUnstableLivekitTransportsEqual(c.transport, TRANSPORT_1),
|
areUnstableLivekitTransportsEqual(
|
||||||
|
c.transport,
|
||||||
|
TRANSPORT_1.transport,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
expect(conn1).toBeDefined();
|
expect(conn1).toBeDefined();
|
||||||
expect(conn1!.start).toHaveBeenCalledOnce();
|
expect(conn1!.start).toHaveBeenCalledOnce();
|
||||||
@@ -187,7 +206,10 @@ describe("connections$ stream", () => {
|
|||||||
expect(connections.length).toBe(1);
|
expect(connections.length).toBe(1);
|
||||||
// The second connection should have been stopped has it is no longer needed.
|
// The second connection should have been stopped has it is no longer needed.
|
||||||
const connection2 = allCreatedConnections.find((c) =>
|
const connection2 = allCreatedConnections.find((c) =>
|
||||||
areUnstableLivekitTransportsEqual(c.transport, TRANSPORT_2),
|
areUnstableLivekitTransportsEqual(
|
||||||
|
c.transport,
|
||||||
|
TRANSPORT_2.transport,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
expect(connection2).toBeDefined();
|
expect(connection2).toBeDefined();
|
||||||
expect(connection2!.stop).toHaveBeenCalled();
|
expect(connection2!.stop).toHaveBeenCalled();
|
||||||
@@ -279,7 +301,7 @@ describe("connectionManagerData$ stream", () => {
|
|||||||
// Setup the fake participants streams behavior
|
// Setup the fake participants streams behavior
|
||||||
// ==============================
|
// ==============================
|
||||||
fakeRemoteParticipantsStreams.set(
|
fakeRemoteParticipantsStreams.set(
|
||||||
keyForTransport(TRANSPORT_1),
|
keyForTransport(TRANSPORT_1.transport),
|
||||||
behavior("oa-b", {
|
behavior("oa-b", {
|
||||||
o: [],
|
o: [],
|
||||||
a: [{ identity: "user1A" } as RemoteParticipant],
|
a: [{ identity: "user1A" } as RemoteParticipant],
|
||||||
@@ -291,7 +313,7 @@ describe("connectionManagerData$ stream", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fakeRemoteParticipantsStreams.set(
|
fakeRemoteParticipantsStreams.set(
|
||||||
keyForTransport(TRANSPORT_2),
|
keyForTransport(TRANSPORT_2.transport),
|
||||||
behavior("o-a", {
|
behavior("o-a", {
|
||||||
o: [],
|
o: [],
|
||||||
a: [{ identity: "user2A" } as RemoteParticipant],
|
a: [{ identity: "user2A" } as RemoteParticipant],
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ import {
|
|||||||
type ObservableScope,
|
type ObservableScope,
|
||||||
} from "../../ObservableScope.ts";
|
} from "../../ObservableScope.ts";
|
||||||
import { generateItemsWithEpoch } from "../../../utils/observable.ts";
|
import { generateItemsWithEpoch } from "../../../utils/observable.ts";
|
||||||
import { areUnstableLivekitTransportsEqual } from "./MatrixLivekitMembers.ts";
|
|
||||||
import { type ConnectionFactory } from "./ConnectionFactory.ts";
|
import { type ConnectionFactory } from "./ConnectionFactory.ts";
|
||||||
|
import { type TransportLocator } from "../../../livekit/auth/types.ts";
|
||||||
|
|
||||||
export class ConnectionManagerData {
|
export class ConnectionManagerData {
|
||||||
private readonly store: Map<
|
private readonly store: Map<
|
||||||
@@ -32,7 +32,7 @@ export class ConnectionManagerData {
|
|||||||
public constructor(private readonly logger?: Logger) {}
|
public constructor(private readonly logger?: Logger) {}
|
||||||
|
|
||||||
public add(connection: Connection, participants: RemoteParticipant[]): void {
|
public add(connection: Connection, participants: RemoteParticipant[]): void {
|
||||||
const key = this.getKey(connection.transport);
|
const key = this.getKey(connection);
|
||||||
const existing = this.store.get(key);
|
const existing = this.store.get(key);
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
this.store.set(key, { connection, participants });
|
this.store.set(key, { connection, participants });
|
||||||
@@ -46,7 +46,7 @@ export class ConnectionManagerData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getKey(transport: UnstableLivekitTransport): string {
|
private getKey({ transport, serverName }: TransportLocator): string {
|
||||||
// This is enough as a key because the ConnectionManager is already scoped by room.
|
// This is enough as a key because the ConnectionManager is already scoped by room.
|
||||||
// We also do not need to consider the slotId at this point since each `MatrixRTCSession` is already scoped by `slotDescription: {id, application}`.
|
// We also do not need to consider the slotId at this point since each `MatrixRTCSession` is already scoped by `slotDescription: {id, application}`.
|
||||||
return JSON.stringify([
|
return JSON.stringify([
|
||||||
@@ -54,6 +54,7 @@ export class ConnectionManagerData {
|
|||||||
"livekit_service_url" in transport
|
"livekit_service_url" in transport
|
||||||
? transport.livekit_service_url
|
? transport.livekit_service_url
|
||||||
: undefined,
|
: undefined,
|
||||||
|
serverName,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,13 +63,13 @@ export class ConnectionManagerData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getConnectionForTransport(
|
public getConnectionForTransport(
|
||||||
transport: UnstableLivekitTransport,
|
transport: TransportLocator,
|
||||||
): Connection | null {
|
): Connection | null {
|
||||||
return this.store.get(this.getKey(transport))?.connection ?? null;
|
return this.store.get(this.getKey(transport))?.connection ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getParticipantsForTransport(
|
public getParticipantsForTransport(
|
||||||
transport: UnstableLivekitTransport,
|
transport: TransportLocator,
|
||||||
): RemoteParticipant[] {
|
): RemoteParticipant[] {
|
||||||
const key = this.getKey(transport);
|
const key = this.getKey(transport);
|
||||||
const existing = this.store.get(key);
|
const existing = this.store.get(key);
|
||||||
@@ -79,11 +80,44 @@ export class ConnectionManagerData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpacks an {@link UnstableLivekitTransport} into its constituent `url` and
|
||||||
|
* `serviceUrl`, discarding the type information that at least one of them must
|
||||||
|
* be defined.
|
||||||
|
*/
|
||||||
|
function unpackTransport(transport: UnstableLivekitTransport): {
|
||||||
|
url: string | undefined;
|
||||||
|
serviceUrl: string | undefined;
|
||||||
|
} {
|
||||||
|
return {
|
||||||
|
url: "url" in transport ? transport.url : undefined,
|
||||||
|
serviceUrl:
|
||||||
|
"livekit_service_url" in transport
|
||||||
|
? transport.livekit_service_url
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@link UnstableLivekitTransport} from its constituent `url` and
|
||||||
|
* `serviceUrl`. This assumes that at least one of the two are defined.
|
||||||
|
*/
|
||||||
|
function repackTransport(
|
||||||
|
url: string | undefined,
|
||||||
|
serviceUrl: string | undefined,
|
||||||
|
): UnstableLivekitTransport {
|
||||||
|
return {
|
||||||
|
type: "livekit",
|
||||||
|
...(url === undefined ? {} : { url }),
|
||||||
|
...(serviceUrl === undefined ? {} : { livekit_service_url: serviceUrl }),
|
||||||
|
} as UnstableLivekitTransport;
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
scope: ObservableScope;
|
scope: ObservableScope;
|
||||||
connectionFactory: ConnectionFactory;
|
connectionFactory: ConnectionFactory;
|
||||||
localTransport: UnstableLivekitTransport;
|
localTransport: TransportLocator;
|
||||||
remoteTransports$: Behavior<Epoch<UnstableLivekitTransport[]>>;
|
remoteTransports$: Behavior<Epoch<TransportLocator[]>>;
|
||||||
|
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
ownMembershipIdentity: CallMembershipIdentityParts;
|
ownMembershipIdentity: CallMembershipIdentityParts;
|
||||||
@@ -129,11 +163,11 @@ export function createConnectionManager$({
|
|||||||
* All transports currently managed by the ConnectionManager. This list does
|
* All transports currently managed by the ConnectionManager. This list does
|
||||||
* not include duplicate transports.
|
* not include duplicate transports.
|
||||||
*/
|
*/
|
||||||
const localAndRemoteTransports$: Behavior<Epoch<UnstableLivekitTransport[]>> =
|
const localAndRemoteTransports$: Behavior<Epoch<TransportLocator[]>> =
|
||||||
scope.behavior(
|
scope.behavior(
|
||||||
remoteTransports$.pipe(
|
remoteTransports$.pipe(
|
||||||
mapEpoch((remoteTransports) =>
|
mapEpoch((remoteTransports) =>
|
||||||
removeDuplicateTransports([localTransport, ...remoteTransports]),
|
removeDuplicateLocators([localTransport, ...remoteTransports]),
|
||||||
),
|
),
|
||||||
tap((transports) =>
|
tap((transports) =>
|
||||||
logger.debug("localAndRemoteTransports$ = ", transports),
|
logger.debug("localAndRemoteTransports$ = ", transports),
|
||||||
@@ -149,30 +183,32 @@ export function createConnectionManager$({
|
|||||||
generateItemsWithEpoch(
|
generateItemsWithEpoch(
|
||||||
"ConnectionManager connections$",
|
"ConnectionManager connections$",
|
||||||
function* (transports) {
|
function* (transports) {
|
||||||
for (const transport of transports) {
|
for (const { transport, serverName } of transports) {
|
||||||
const role =
|
const role =
|
||||||
transport === localTransport
|
serverName === localTransport.serverName &&
|
||||||
|
areUnstableLivekitTransportsEqual(
|
||||||
|
transport,
|
||||||
|
localTransport.transport,
|
||||||
|
)
|
||||||
? ("publisher" as const)
|
? ("publisher" as const)
|
||||||
: ("subscriber" as const);
|
: ("subscriber" as const);
|
||||||
const url = "url" in transport ? transport.url : undefined;
|
// In order to maintain a consistent set of map keys, we must
|
||||||
const serviceUrl =
|
// temporarily downgrade `transport` to a weaker type
|
||||||
"livekit_service_url" in transport
|
const { url, serviceUrl } = unpackTransport(transport);
|
||||||
? transport.livekit_service_url
|
yield {
|
||||||
: undefined;
|
keys: [role, url, serviceUrl, serverName],
|
||||||
yield { keys: [role, url, serviceUrl], data: undefined };
|
data: undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(scope, _data$, role, url, serviceUrl) => {
|
(scope, _data$, role, url, serviceUrl, serverName) => {
|
||||||
const connection = connectionFactory.createConnection(
|
const connection = connectionFactory.createConnection(
|
||||||
scope,
|
scope,
|
||||||
role,
|
role,
|
||||||
{
|
// Pack the transport keys back into a stronger
|
||||||
type: "livekit",
|
// `UnstableLivekitTransport` type
|
||||||
...(url === undefined ? {} : { url }),
|
repackTransport(url, serviceUrl),
|
||||||
...(serviceUrl === undefined
|
serverName,
|
||||||
? {}
|
|
||||||
: { livekit_service_url: serviceUrl }),
|
|
||||||
} as UnstableLivekitTransport,
|
|
||||||
ownMembershipIdentity,
|
ownMembershipIdentity,
|
||||||
logger,
|
logger,
|
||||||
);
|
);
|
||||||
@@ -230,12 +266,65 @@ export function createConnectionManager$({
|
|||||||
return { connectionManagerData$ };
|
return { connectionManagerData$ };
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeDuplicateTransports<T extends UnstableLivekitTransport>(
|
// TODO add this to the JS-SDK
|
||||||
transports: T[],
|
export function areUnstableLivekitTransportsEqual<
|
||||||
): T[] {
|
T extends UnstableLivekitTransport,
|
||||||
return transports.reduce((acc, transport) => {
|
>(t1: T | null, t2: T | null): boolean {
|
||||||
if (!acc.some((t) => areUnstableLivekitTransportsEqual(t, transport)))
|
if (t1 && t2) {
|
||||||
acc.push(transport);
|
if ("url" in t1 !== "url" in t2) return false;
|
||||||
return acc;
|
if ("livekit_service_url" in t1 !== "livekit_service_url" in t2)
|
||||||
}, [] as T[]);
|
return false;
|
||||||
|
if (
|
||||||
|
"url" in t1 &&
|
||||||
|
t1.url !== (t2 as UnstableLivekitTransport & { url: string }).url
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
if (
|
||||||
|
"livekit_service_url" in t1 &&
|
||||||
|
t1.livekit_service_url !==
|
||||||
|
(t2 as UnstableLivekitTransport & { livekit_service_url: string })
|
||||||
|
.livekit_service_url
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !t1 && !t2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeDuplicateLocators(
|
||||||
|
locators: TransportLocator[],
|
||||||
|
): TransportLocator[] {
|
||||||
|
// Set tracking which locators we have added to `deduped` so far
|
||||||
|
// url -> serviceUrl -> serverNames
|
||||||
|
const byUrl = new Map<
|
||||||
|
string | undefined,
|
||||||
|
Map<string | undefined, Set<string>>
|
||||||
|
>();
|
||||||
|
const deduped: TransportLocator[] = [];
|
||||||
|
|
||||||
|
for (const locator of locators) {
|
||||||
|
const { transport, serverName } = locator;
|
||||||
|
const url = "url" in transport ? transport.url : undefined;
|
||||||
|
const serviceUrl =
|
||||||
|
"livekit_service_url" in transport
|
||||||
|
? transport.livekit_service_url
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
let byServiceUrl = byUrl.get(url);
|
||||||
|
if (byServiceUrl === undefined) {
|
||||||
|
byServiceUrl = new Map();
|
||||||
|
byUrl.set(url, byServiceUrl);
|
||||||
|
}
|
||||||
|
let serverNames = byServiceUrl.get(serviceUrl);
|
||||||
|
if (serverNames === undefined) {
|
||||||
|
serverNames = new Set();
|
||||||
|
byServiceUrl.set(serviceUrl, serverNames);
|
||||||
|
}
|
||||||
|
if (!serverNames.has(serverName)) {
|
||||||
|
serverNames.add(serverName);
|
||||||
|
deduped.push(locator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deduped;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ describe("ECConnectionFactory - Audio inputs options", () => {
|
|||||||
testScope,
|
testScope,
|
||||||
"subscriber",
|
"subscriber",
|
||||||
exampleTransport,
|
exampleTransport,
|
||||||
|
"example.org",
|
||||||
ownMemberMock,
|
ownMemberMock,
|
||||||
logger,
|
logger,
|
||||||
);
|
);
|
||||||
@@ -147,6 +148,7 @@ describe("ECConnectionFactory - ControlledAudioDevice", () => {
|
|||||||
testScope,
|
testScope,
|
||||||
"subscriber",
|
"subscriber",
|
||||||
exampleTransport,
|
exampleTransport,
|
||||||
|
"example.org",
|
||||||
ownMemberMock,
|
ownMemberMock,
|
||||||
logger,
|
logger,
|
||||||
);
|
);
|
||||||
@@ -184,6 +186,7 @@ describe("ECConnectionFactory - Camera quality settings", () => {
|
|||||||
testScope,
|
testScope,
|
||||||
"subscriber",
|
"subscriber",
|
||||||
exampleTransport,
|
exampleTransport,
|
||||||
|
"example.org",
|
||||||
ownMemberMock,
|
ownMemberMock,
|
||||||
logger,
|
logger,
|
||||||
);
|
);
|
||||||
@@ -223,6 +226,7 @@ describe("ECConnectionFactory - Camera quality settings", () => {
|
|||||||
testScope,
|
testScope,
|
||||||
"subscriber",
|
"subscriber",
|
||||||
exampleTransport,
|
exampleTransport,
|
||||||
|
"example.org",
|
||||||
ownMemberMock,
|
ownMemberMock,
|
||||||
logger,
|
logger,
|
||||||
);
|
);
|
||||||
@@ -263,6 +267,7 @@ describe("ECConnectionFactory - Camera quality settings", () => {
|
|||||||
testScope,
|
testScope,
|
||||||
"subscriber",
|
"subscriber",
|
||||||
exampleTransport,
|
exampleTransport,
|
||||||
|
"example.org",
|
||||||
ownMemberMock,
|
ownMemberMock,
|
||||||
logger,
|
logger,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,12 +18,7 @@ import {
|
|||||||
type RemoteMatrixLivekitMember,
|
type RemoteMatrixLivekitMember,
|
||||||
createRemoteMatrixLivekitMembers$,
|
createRemoteMatrixLivekitMembers$,
|
||||||
} from "./MatrixLivekitMembers.ts";
|
} from "./MatrixLivekitMembers.ts";
|
||||||
import {
|
import { Epoch, ObservableScope, trackEpoch } from "../../ObservableScope.ts";
|
||||||
Epoch,
|
|
||||||
mapEpoch,
|
|
||||||
ObservableScope,
|
|
||||||
trackEpoch,
|
|
||||||
} from "../../ObservableScope.ts";
|
|
||||||
import { ConnectionManagerData } from "./ConnectionManager.ts";
|
import { ConnectionManagerData } from "./ConnectionManager.ts";
|
||||||
import {
|
import {
|
||||||
flushPromises,
|
flushPromises,
|
||||||
@@ -33,6 +28,8 @@ import {
|
|||||||
import { type Connection } from "./Connection.ts";
|
import { type Connection } from "./Connection.ts";
|
||||||
import { constant } from "../../Behavior.ts";
|
import { constant } from "../../Behavior.ts";
|
||||||
import { localRtcMember } from "../../../utils/test-fixtures.ts";
|
import { localRtcMember } from "../../../utils/test-fixtures.ts";
|
||||||
|
import { type TransportLocator } from "../../../livekit/auth";
|
||||||
|
import { membershipsAndTransports$ } from "../../SessionBehaviors.ts";
|
||||||
|
|
||||||
let testScope: ObservableScope;
|
let testScope: ObservableScope;
|
||||||
|
|
||||||
@@ -117,21 +114,15 @@ test("should signal participant not yet connected to livekit", async () => {
|
|||||||
function createEpochedMemberships$(m$: Observable<CallMembership[]>): {
|
function createEpochedMemberships$(m$: Observable<CallMembership[]>): {
|
||||||
memberships$: Observable<Epoch<CallMembership[]>>;
|
memberships$: Observable<Epoch<CallMembership[]>>;
|
||||||
membershipsWithTransport$: Observable<
|
membershipsWithTransport$: Observable<
|
||||||
Epoch<{ membership: CallMembership; transport?: UnstableLivekitTransport }[]>
|
Epoch<
|
||||||
|
{ membership: CallMembership; transport: TransportLocator | undefined }[]
|
||||||
|
>
|
||||||
>;
|
>;
|
||||||
} {
|
} {
|
||||||
const memberships$ = m$.pipe(trackEpoch());
|
const memberships$ = testScope.behavior(m$.pipe(trackEpoch()));
|
||||||
const membershipsWithTransport$ = memberships$.pipe(
|
const { membershipsWithTransport$ } = membershipsAndTransports$(
|
||||||
mapEpoch((members) => {
|
testScope,
|
||||||
return members.map((m) => {
|
memberships$,
|
||||||
const tr = m.getTransport();
|
|
||||||
return {
|
|
||||||
membership: m,
|
|
||||||
transport:
|
|
||||||
tr?.type === "livekit" ? (tr as UnstableLivekitTransport) : undefined,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
memberships$,
|
memberships$,
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { type LocalParticipant, type RemoteParticipant } from "livekit-client";
|
import { type LocalParticipant, type RemoteParticipant } from "livekit-client";
|
||||||
import {
|
import { type CallMembership } from "matrix-js-sdk/lib/matrixrtc";
|
||||||
type CallMembership,
|
|
||||||
type UnstableLivekitTransport,
|
|
||||||
} from "matrix-js-sdk/lib/matrixrtc";
|
|
||||||
import { combineLatest, filter, map } from "rxjs";
|
import { combineLatest, filter, map } from "rxjs";
|
||||||
import { logger } from "matrix-js-sdk/lib/logger";
|
import { logger } from "matrix-js-sdk/lib/logger";
|
||||||
|
|
||||||
@@ -18,6 +15,7 @@ import { type IConnectionManager } from "./ConnectionManager";
|
|||||||
import { Epoch, type ObservableScope } from "../../ObservableScope";
|
import { Epoch, type ObservableScope } from "../../ObservableScope";
|
||||||
import { type Connection } from "./Connection";
|
import { type Connection } from "./Connection";
|
||||||
import { generateItemsWithEpoch } from "../../../utils/observable";
|
import { generateItemsWithEpoch } from "../../../utils/observable";
|
||||||
|
import { type TransportLocator } from "../../../livekit/auth";
|
||||||
|
|
||||||
interface LocalTaggedParticipant {
|
interface LocalTaggedParticipant {
|
||||||
type: "local";
|
type: "local";
|
||||||
@@ -61,7 +59,7 @@ interface Props {
|
|||||||
scope: ObservableScope;
|
scope: ObservableScope;
|
||||||
membershipsWithTransport$: Behavior<
|
membershipsWithTransport$: Behavior<
|
||||||
Epoch<
|
Epoch<
|
||||||
{ membership: CallMembership; transport?: UnstableLivekitTransport }[]
|
{ membership: CallMembership; transport: TransportLocator | undefined }[]
|
||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
connectionManager: IConnectionManager;
|
connectionManager: IConnectionManager;
|
||||||
@@ -165,28 +163,3 @@ export function createRemoteMatrixLivekitMembers$({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
export function areUnstableLivekitTransportsEqual<
|
|
||||||
T extends UnstableLivekitTransport,
|
|
||||||
>(t1: T | null, t2: T | null): boolean {
|
|
||||||
if (t1 && t2) {
|
|
||||||
if ("url" in t1 !== "url" in t2) return false;
|
|
||||||
if ("livekit_service_url" in t1 !== "livekit_service_url" in t2)
|
|
||||||
return false;
|
|
||||||
if (
|
|
||||||
"url" in t1 &&
|
|
||||||
t1.url !== (t2 as UnstableLivekitTransport & { url: string }).url
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
if (
|
|
||||||
"livekit_service_url" in t1 &&
|
|
||||||
t1.livekit_service_url !==
|
|
||||||
(t2 as UnstableLivekitTransport & { livekit_service_url: string })
|
|
||||||
.livekit_service_url
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return !t1 && !t2;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
type ClientOpenIDParts,
|
type ClientOpenIDParts,
|
||||||
} from "../../../livekit/auth";
|
} from "../../../livekit/auth";
|
||||||
import {
|
import {
|
||||||
exampleTransport,
|
exampleTransportLocator,
|
||||||
mockMediaDevices,
|
mockMediaDevices,
|
||||||
mockRtcMembership,
|
mockRtcMembership,
|
||||||
ownMemberMock,
|
ownMemberMock,
|
||||||
@@ -32,11 +32,13 @@ import {
|
|||||||
} from "../../../utils/test.ts";
|
} from "../../../utils/test.ts";
|
||||||
import { type ProcessorState } from "../../../livekit/TrackProcessorContext.tsx";
|
import { type ProcessorState } from "../../../livekit/TrackProcessorContext.tsx";
|
||||||
import {
|
import {
|
||||||
areUnstableLivekitTransportsEqual,
|
|
||||||
createRemoteMatrixLivekitMembers$,
|
createRemoteMatrixLivekitMembers$,
|
||||||
type RemoteMatrixLivekitMember,
|
type RemoteMatrixLivekitMember,
|
||||||
} from "./MatrixLivekitMembers.ts";
|
} from "./MatrixLivekitMembers.ts";
|
||||||
import { createConnectionManager$ } from "./ConnectionManager.ts";
|
import {
|
||||||
|
areUnstableLivekitTransportsEqual,
|
||||||
|
createConnectionManager$,
|
||||||
|
} from "./ConnectionManager.ts";
|
||||||
import { membershipsAndTransports$ } from "../../SessionBehaviors.ts";
|
import { membershipsAndTransports$ } from "../../SessionBehaviors.ts";
|
||||||
import { localRtcMember, testJWTToken } from "../../../utils/test-fixtures.ts";
|
import { localRtcMember, testJWTToken } from "../../../utils/test-fixtures.ts";
|
||||||
|
|
||||||
@@ -129,7 +131,7 @@ test("bob, carl, then bob joining no tracks yet", () => {
|
|||||||
const connectionManager = createConnectionManager$({
|
const connectionManager = createConnectionManager$({
|
||||||
scope: testScope,
|
scope: testScope,
|
||||||
connectionFactory: ecConnectionFactory,
|
connectionFactory: ecConnectionFactory,
|
||||||
localTransport: exampleTransport,
|
localTransport: exampleTransportLocator,
|
||||||
remoteTransports$: membershipsAndTransports.transports$,
|
remoteTransports$: membershipsAndTransports.transports$,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ Please see LICENSE in the repository root for full details.
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
type CallMembership,
|
type CallMembership,
|
||||||
type UnstableLivekitTransport,
|
|
||||||
type MatrixRTCSession,
|
type MatrixRTCSession,
|
||||||
MatrixRTCSessionEvent,
|
MatrixRTCSessionEvent,
|
||||||
isUnstableLivekitTransport,
|
isUnstableLivekitTransport,
|
||||||
@@ -21,6 +20,7 @@ import {
|
|||||||
type ObservableScope,
|
type ObservableScope,
|
||||||
} from "./ObservableScope";
|
} from "./ObservableScope";
|
||||||
import { type Behavior } from "./Behavior";
|
import { type Behavior } from "./Behavior";
|
||||||
|
import { type TransportLocator } from "../livekit/auth";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks the transports used by ourselves, plus all other MatrixRTC session
|
* Tracks the transports used by ourselves, plus all other MatrixRTC session
|
||||||
@@ -31,16 +31,15 @@ export const membershipsAndTransports$ = (
|
|||||||
memberships$: Behavior<Epoch<CallMembership[]>>,
|
memberships$: Behavior<Epoch<CallMembership[]>>,
|
||||||
): {
|
): {
|
||||||
membershipsWithTransport$: Behavior<
|
membershipsWithTransport$: Behavior<
|
||||||
Epoch<{ membership: CallMembership; transport?: UnstableLivekitTransport }[]>
|
Epoch<
|
||||||
|
{ membership: CallMembership; transport: TransportLocator | undefined }[]
|
||||||
|
>
|
||||||
>;
|
>;
|
||||||
transports$: Behavior<Epoch<UnstableLivekitTransport[]>>;
|
transports$: Behavior<Epoch<TransportLocator[]>>;
|
||||||
} => {
|
} => {
|
||||||
const membershipsWithTransport$: Behavior<
|
const membershipsWithTransport$: Behavior<
|
||||||
Epoch<
|
Epoch<
|
||||||
{
|
{ membership: CallMembership; transport: TransportLocator | undefined }[]
|
||||||
membership: CallMembership;
|
|
||||||
transport: UnstableLivekitTransport | undefined;
|
|
||||||
}[]
|
|
||||||
>
|
>
|
||||||
> = scope.behavior(
|
> = scope.behavior(
|
||||||
memberships$.pipe(
|
memberships$.pipe(
|
||||||
@@ -49,14 +48,19 @@ export const membershipsAndTransports$ = (
|
|||||||
const transport = membership.getTransport();
|
const transport = membership.getTransport();
|
||||||
return {
|
return {
|
||||||
membership,
|
membership,
|
||||||
transport: isUnstableLivekitTransport(transport) ? transport : undefined,
|
transport: isUnstableLivekitTransport(transport)
|
||||||
|
? {
|
||||||
|
transport,
|
||||||
|
serverName: membership.userId.replace(/^.*?:/, ""),
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const transports$: Behavior<Epoch<UnstableLivekitTransport[]>> = scope.behavior(
|
const transports$: Behavior<Epoch<TransportLocator[]>> = scope.behavior(
|
||||||
membershipsWithTransport$.pipe(
|
membershipsWithTransport$.pipe(
|
||||||
mapEpoch((mts) => mts.flatMap(({ transport: t }) => (t ? [t] : []))),
|
mapEpoch((mts) => mts.flatMap(({ transport: t }) => (t ? [t] : []))),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ import {
|
|||||||
type RemoteScreenShareViewModel,
|
type RemoteScreenShareViewModel,
|
||||||
} from "../state/media/RemoteScreenShareViewModel";
|
} from "../state/media/RemoteScreenShareViewModel";
|
||||||
import { Connection } from "../state/CallViewModel/remoteMembers/Connection";
|
import { Connection } from "../state/CallViewModel/remoteMembers/Connection";
|
||||||
|
import { type TransportLocator } from "../livekit/auth";
|
||||||
|
|
||||||
export function withFakeTimers(continuation: () => void): void {
|
export function withFakeTimers(continuation: () => void): void {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
@@ -213,6 +214,11 @@ export const exampleTransport: UnstableLivekitTransport = {
|
|||||||
livekit_service_url: "https://lk.example.org",
|
livekit_service_url: "https://lk.example.org",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const exampleTransportLocator: TransportLocator = {
|
||||||
|
transport: exampleTransport,
|
||||||
|
serverName: "example.org",
|
||||||
|
};
|
||||||
|
|
||||||
export function mockRtcMembership(
|
export function mockRtcMembership(
|
||||||
user: string | RoomMember,
|
user: string | RoomMember,
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user