mirror of
https://github.com/vector-im/element-call.git
synced 2026-10-16 23:45:50 +00:00
WIP
This commit is contained in:
+3
-3
@@ -59,10 +59,10 @@
|
|||||||
"developer_mode": {
|
"developer_mode": {
|
||||||
"always_show_iphone_earpiece": "Show iPhone earpiece option on all platforms",
|
"always_show_iphone_earpiece": "Show iPhone earpiece option on all platforms",
|
||||||
"crypto_version": "Crypto version: {{version}}",
|
"crypto_version": "Crypto version: {{version}}",
|
||||||
"custom_livekit_url": {
|
"custom_transport": {
|
||||||
"current_url": "Currently set to: ",
|
"current": "Currently set to: ",
|
||||||
"from_config": "Currently, no overwrite is set. Url from config is used.",
|
"from_config": "Currently, no overwrite is set. Url from config is used.",
|
||||||
"label": "Custom Livekit-url",
|
"label": "Custom transport JSON",
|
||||||
"reset": "Reset overwrite",
|
"reset": "Reset overwrite",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"saving": "Saving..."
|
"saving": "Saving..."
|
||||||
|
|||||||
+1
-1
@@ -104,7 +104,7 @@
|
|||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"loglevel": "^1.9.1",
|
"loglevel": "^1.9.1",
|
||||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#robin/livekit-updates",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#robin/livekit-updates",
|
||||||
"matrix-widget-api": "^1.18.0",
|
"matrix-widget-api": "^1.20.0",
|
||||||
"node-stdlib-browser": "^1.3.1",
|
"node-stdlib-browser": "^1.3.1",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"oxfmt": "^0.66.0",
|
"oxfmt": "^0.66.0",
|
||||||
|
|||||||
Generated
+2
-10
@@ -194,8 +194,8 @@ importers:
|
|||||||
specifier: github:matrix-org/matrix-js-sdk#robin/livekit-updates
|
specifier: github:matrix-org/matrix-js-sdk#robin/livekit-updates
|
||||||
version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/621b225e227dc6dc2b1c9d53413235d8999e6d54
|
version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/621b225e227dc6dc2b1c9d53413235d8999e6d54
|
||||||
matrix-widget-api:
|
matrix-widget-api:
|
||||||
specifier: ^1.18.0
|
specifier: ^1.20.0
|
||||||
version: 1.19.0
|
version: 1.20.0
|
||||||
node-stdlib-browser:
|
node-stdlib-browser:
|
||||||
specifier: ^1.3.1
|
specifier: ^1.3.1
|
||||||
version: 1.3.1
|
version: 1.3.1
|
||||||
@@ -4678,9 +4678,6 @@ packages:
|
|||||||
version: 42.3.0
|
version: 42.3.0
|
||||||
engines: {node: '>=22.0.0'}
|
engines: {node: '>=22.0.0'}
|
||||||
|
|
||||||
matrix-widget-api@1.19.0:
|
|
||||||
resolution: {integrity: sha512-Qe7L6G1mQJphBC47/Bzp5ljPIK1UARgLiuGgS+/KmEs/eaNHkQ3+ArD0/0kvMV+d6kCGAZ9OKRqsnuPjizI+tw==}
|
|
||||||
|
|
||||||
matrix-widget-api@1.20.0:
|
matrix-widget-api@1.20.0:
|
||||||
resolution: {integrity: sha512-qwFP37rgns7mPesM5/vvZgknzb+kvyRB5BISv5sCuBxsGMceMr+Kogf7OkjRxBXdMCPBFuf6gIuYRe5S+lpzQw==}
|
resolution: {integrity: sha512-qwFP37rgns7mPesM5/vvZgknzb+kvyRB5BISv5sCuBxsGMceMr+Kogf7OkjRxBXdMCPBFuf6gIuYRe5S+lpzQw==}
|
||||||
|
|
||||||
@@ -10244,11 +10241,6 @@ snapshots:
|
|||||||
sdp-transform: 3.0.0
|
sdp-transform: 3.0.0
|
||||||
unhomoglyph: 1.0.6
|
unhomoglyph: 1.0.6
|
||||||
|
|
||||||
matrix-widget-api@1.19.0:
|
|
||||||
dependencies:
|
|
||||||
'@types/events': 3.0.3
|
|
||||||
events: 3.3.0
|
|
||||||
|
|
||||||
matrix-widget-api@1.20.0:
|
matrix-widget-api@1.20.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/events': 3.0.3
|
'@types/events': 3.0.3
|
||||||
|
|||||||
@@ -20,17 +20,19 @@ export type ClientGetTokenParts = Pick<
|
|||||||
"_unstable_getLivekitToken"
|
"_unstable_getLivekitToken"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export interface GetSFUConfigParams {
|
||||||
|
client: ClientGetTokenParts;
|
||||||
|
membership: CallMembershipIdentityParts;
|
||||||
|
url: string;
|
||||||
|
roomId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getSFUConfig({
|
export async function getSFUConfig({
|
||||||
client,
|
client,
|
||||||
membership,
|
membership,
|
||||||
url,
|
url,
|
||||||
roomId,
|
roomId,
|
||||||
}: {
|
}: GetSFUConfigParams): Promise<SFUConfig> {
|
||||||
client: ClientGetTokenParts;
|
|
||||||
membership: CallMembershipIdentityParts;
|
|
||||||
url: string;
|
|
||||||
roomId: string;
|
|
||||||
}): Promise<SFUConfig> {
|
|
||||||
const res = await doNetworkOperationWithRetry(async () =>
|
const res = await doNetworkOperationWithRetry(async () =>
|
||||||
client._unstable_getLivekitToken({
|
client._unstable_getLivekitToken({
|
||||||
url,
|
url,
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ import * as csApi from "./csApi";
|
|||||||
import { type SFUConfig } from "./types";
|
import { type SFUConfig } from "./types";
|
||||||
import { NoMatrix2AuthorizationService } from "../../utils/errors";
|
import { NoMatrix2AuthorizationService } from "../../utils/errors";
|
||||||
|
|
||||||
export { ClientOpenIDParts } from "./openID";
|
export type { ClientOpenIDParts } from "./openID";
|
||||||
export { ClientGetTokenParts, ClientDelegationParts } from "./csApi";
|
export type { ClientGetTokenParts, ClientDelegationParts } from "./csApi";
|
||||||
export * from "./types";
|
export type * from "./types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether an error means "this homeserver does not implement the
|
* Checks whether an error means "this homeserver does not implement the
|
||||||
|
|||||||
@@ -17,21 +17,40 @@ import {
|
|||||||
import fetchMock from "fetch-mock";
|
import fetchMock from "fetch-mock";
|
||||||
import { MatrixError } from "matrix-js-sdk";
|
import { MatrixError } from "matrix-js-sdk";
|
||||||
|
|
||||||
import { getSFUConfigWithOpenID, type ClientOpenIDParts } from "./openIDSFU";
|
import {
|
||||||
import { testJWTToken } from "../utils/test-fixtures";
|
getSFUConfig,
|
||||||
import { ownMemberMock } from "../utils/test";
|
type GetSFUConfigParams,
|
||||||
import { FailToGetOpenIdToken } from "../utils/errors";
|
type ClientOpenIDParts,
|
||||||
|
} from "./openID";
|
||||||
|
import { testJWTToken } from "../../utils/test-fixtures";
|
||||||
|
import { ownMemberMock } from "../../utils/test";
|
||||||
|
import { FailToGetOpenIdToken } from "../../utils/errors";
|
||||||
|
import { logger } from "matrix-js-sdk/lib/logger";
|
||||||
|
|
||||||
const sfuUrl = "https://sfu.example.org";
|
const serviceUrl = "https://sfu.example.org";
|
||||||
|
|
||||||
describe("getSFUConfigWithOpenID", () => {
|
const delayParams = {
|
||||||
|
delayEndpointBaseUrl: "https://matrix.homeserverserver.org",
|
||||||
|
delayId: "mock_delay_id",
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("getSFUConfig", () => {
|
||||||
let matrixClient: MockedObject<ClientOpenIDParts>;
|
let matrixClient: MockedObject<ClientOpenIDParts>;
|
||||||
|
let params: GetSFUConfigParams;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fetchMock.catch(404);
|
fetchMock.catch(404);
|
||||||
matrixClient = {
|
matrixClient = {
|
||||||
getOpenIdToken: vitest.fn(),
|
getOpenIdToken: vitest.fn(),
|
||||||
getDeviceId: vitest.fn(),
|
getDeviceId: vitest.fn(),
|
||||||
};
|
};
|
||||||
|
params = {
|
||||||
|
client: matrixClient,
|
||||||
|
membership: ownMemberMock,
|
||||||
|
serviceUrl: "https://sfu.example.org",
|
||||||
|
roomId: "!example_room_id",
|
||||||
|
role: "subscriber",
|
||||||
|
logger,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vitest.clearAllMocks();
|
vitest.clearAllMocks();
|
||||||
@@ -42,18 +61,13 @@ describe("getSFUConfigWithOpenID", () => {
|
|||||||
fetchMock.post("https://sfu.example.org/sfu/get", () => {
|
fetchMock.post("https://sfu.example.org/sfu/get", () => {
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: { url: sfuUrl, jwt: testJWTToken },
|
body: { url: serviceUrl, jwt: testJWTToken },
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const config = await getSFUConfigWithOpenID(
|
const config = await getSFUConfig(params);
|
||||||
matrixClient,
|
|
||||||
ownMemberMock,
|
|
||||||
"https://sfu.example.org",
|
|
||||||
"!example_room_id",
|
|
||||||
);
|
|
||||||
expect(config).toEqual({
|
expect(config).toEqual({
|
||||||
jwt: testJWTToken,
|
jwt: testJWTToken,
|
||||||
url: sfuUrl,
|
url: serviceUrl,
|
||||||
livekitIdentity: "@me:example.org:ABCDEF",
|
livekitIdentity: "@me:example.org:ABCDEF",
|
||||||
livekitAlias: "!example_room_id",
|
livekitAlias: "!example_room_id",
|
||||||
});
|
});
|
||||||
@@ -71,12 +85,7 @@ describe("getSFUConfigWithOpenID", () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await getSFUConfigWithOpenID(
|
await getSFUConfig(params);
|
||||||
matrixClient,
|
|
||||||
ownMemberMock,
|
|
||||||
"https://sfu.example.org",
|
|
||||||
"!example_room_id",
|
|
||||||
);
|
|
||||||
} catch (ex: unknown) {
|
} catch (ex: unknown) {
|
||||||
expect(ex).toBeInstanceOf(FailToGetOpenIdToken);
|
expect(ex).toBeInstanceOf(FailToGetOpenIdToken);
|
||||||
expect((ex as FailToGetOpenIdToken).cause).toBeInstanceOf(MatrixError);
|
expect((ex as FailToGetOpenIdToken).cause).toBeInstanceOf(MatrixError);
|
||||||
@@ -116,23 +125,14 @@ describe("getSFUConfigWithOpenID", () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: { url: sfuUrl, jwt: testJWTToken },
|
body: { url: serviceUrl, jwt: testJWTToken },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{ overwriteRoutes: true },
|
{ overwriteRoutes: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
// Note: Assuming getSFUConfigWithOpenID eventually calls getLiveKitJWT
|
// Note: Assuming getSFUConfig eventually calls getLiveKitJWT
|
||||||
const config = await getSFUConfigWithOpenID(
|
const config = await getSFUConfig({ ...params, ...delayParams });
|
||||||
matrixClient,
|
|
||||||
ownMemberMock,
|
|
||||||
"https://sfu.example.org",
|
|
||||||
"!example_room_id",
|
|
||||||
{
|
|
||||||
delayEndpointBaseUrl: "https://matrix.homeserverserver.org",
|
|
||||||
delayId: "mock_delay_id",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(config.jwt).toBe(testJWTToken);
|
expect(config.jwt).toBe(testJWTToken);
|
||||||
expect(callCount).toBe(2);
|
expect(callCount).toBe(2);
|
||||||
@@ -153,7 +153,7 @@ describe("getSFUConfigWithOpenID", () => {
|
|||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: { url: sfuUrl, jwt: testJWTToken },
|
body: { url: serviceUrl, jwt: testJWTToken },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -164,21 +164,12 @@ describe("getSFUConfigWithOpenID", () => {
|
|||||||
{ overwriteRoutes: true },
|
{ overwriteRoutes: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const config = await getSFUConfigWithOpenID(
|
const config = await getSFUConfig({ ...params, ...delayParams });
|
||||||
matrixClient,
|
|
||||||
ownMemberMock,
|
|
||||||
"https://sfu.example.org",
|
|
||||||
"!example_room_id",
|
|
||||||
{
|
|
||||||
delayEndpointBaseUrl: "https://homeserverserver.org/cs_api",
|
|
||||||
delayId: "mock_delay_id",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Prüfe das Ergebnis
|
// Prüfe das Ergebnis
|
||||||
expect(config).toMatchObject({
|
expect(config).toMatchObject({
|
||||||
jwt: testJWTToken,
|
jwt: testJWTToken,
|
||||||
url: sfuUrl,
|
url: serviceUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
void (await fetchMock.flush());
|
void (await fetchMock.flush());
|
||||||
@@ -204,16 +195,7 @@ describe("getSFUConfigWithOpenID", () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await getSFUConfigWithOpenID(
|
await getSFUConfig({ ...params, ...delayParams });
|
||||||
matrixClient,
|
|
||||||
ownMemberMock,
|
|
||||||
"https://sfu.example.org",
|
|
||||||
"!example_room_id",
|
|
||||||
{
|
|
||||||
delayEndpointBaseUrl: "https://matrix.homeserverserver.org",
|
|
||||||
delayId: "mock_delay_id",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
expect(ex).toBeInstanceOf(FailToGetOpenIdToken);
|
expect(ex).toBeInstanceOf(FailToGetOpenIdToken);
|
||||||
expect((ex as FailToGetOpenIdToken).cause).toBeInstanceOf(MatrixError);
|
expect((ex as FailToGetOpenIdToken).cause).toBeInstanceOf(MatrixError);
|
||||||
@@ -251,7 +233,7 @@ describe("getSFUConfigWithOpenID", () => {
|
|||||||
fetchMock.post("https://sfu.example.org/get_token", () => {
|
fetchMock.post("https://sfu.example.org/get_token", () => {
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: { url: sfuUrl, jwt: testJWTToken },
|
body: { url: serviceUrl, jwt: testJWTToken },
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
fetchMock.post("https://sfu.example.org/sfu/get", () => {
|
fetchMock.post("https://sfu.example.org/sfu/get", () => {
|
||||||
@@ -261,16 +243,7 @@ describe("getSFUConfigWithOpenID", () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await getSFUConfigWithOpenID(
|
await getSFUConfig({ ...params, ...delayParams });
|
||||||
matrixClient,
|
|
||||||
ownMemberMock,
|
|
||||||
"https://sfu.example.org",
|
|
||||||
"!example_room_id",
|
|
||||||
{
|
|
||||||
delayEndpointBaseUrl: "https://matrix.homeserverserver.org",
|
|
||||||
delayId: "mock_delay_id",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
expect(ex).toBeInstanceOf(FailToGetOpenIdToken);
|
expect(ex).toBeInstanceOf(FailToGetOpenIdToken);
|
||||||
expect((ex as FailToGetOpenIdToken).cause).toEqual(
|
expect((ex as FailToGetOpenIdToken).cause).toEqual(
|
||||||
@@ -309,18 +282,13 @@ describe("getSFUConfigWithOpenID", () => {
|
|||||||
fetchMock.post("https://sfu.example.org/sfu/get", () => {
|
fetchMock.post("https://sfu.example.org/sfu/get", () => {
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: { url: sfuUrl, jwt: testJWTToken },
|
body: { url: serviceUrl, jwt: testJWTToken },
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const config = await getSFUConfigWithOpenID(
|
const config = await getSFUConfig({ ...params, ...delayParams });
|
||||||
matrixClient,
|
|
||||||
ownMemberMock,
|
|
||||||
"https://sfu.example.org",
|
|
||||||
"!example_room_id",
|
|
||||||
);
|
|
||||||
expect(config).toEqual({
|
expect(config).toEqual({
|
||||||
jwt: testJWTToken,
|
jwt: testJWTToken,
|
||||||
url: sfuUrl,
|
url: serviceUrl,
|
||||||
livekitIdentity: "@me:example.org:ABCDEF",
|
livekitIdentity: "@me:example.org:ABCDEF",
|
||||||
livekitAlias: "!example_room_id",
|
livekitAlias: "!example_room_id",
|
||||||
});
|
});
|
||||||
|
|||||||
+31
-22
@@ -24,22 +24,40 @@ export type ClientOpenIDParts = Pick<
|
|||||||
"getOpenIdToken" | "getDeviceId"
|
"getOpenIdToken" | "getDeviceId"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export interface GetSFUConfigParams {
|
||||||
|
client: ClientOpenIDParts;
|
||||||
|
/**
|
||||||
|
* Our own membership identity parts used to send to the JWT service.
|
||||||
|
*/
|
||||||
|
membership: CallMembershipIdentityParts;
|
||||||
|
/**
|
||||||
|
* The base URL of the LiveKit JWT service.
|
||||||
|
*/
|
||||||
|
serviceUrl: string;
|
||||||
|
/**
|
||||||
|
* The room ID used in the request. This is NOT the livekit_alias. The JWT
|
||||||
|
* service will provide the alias. It maps Matrix room IDs to LiveKit aliases.
|
||||||
|
*/
|
||||||
|
roomId: string;
|
||||||
|
/**
|
||||||
|
* Whether we want to publish or only subscribe on this transport.
|
||||||
|
*/
|
||||||
|
role: "publisher" | "subscriber";
|
||||||
|
/**
|
||||||
|
* The base URL of the Matrix homeserver, for delayed leave delegation.
|
||||||
|
*/
|
||||||
|
delayEndpointBaseUrl?: string;
|
||||||
|
/**
|
||||||
|
* The delay ID of the leave event to be delegated to the JWT service.
|
||||||
|
*/
|
||||||
|
delayId?: string;
|
||||||
|
logger: Logger;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a bearer token from the homeserver and then use it to authenticate
|
* Gets a bearer token from the homeserver and then use it to authenticate
|
||||||
* to the matrix RTC backend in order to get acces to the SFU.
|
* to the matrix RTC backend in order to get acces to the SFU.
|
||||||
* It has built-in retry for calls to the homeserver with a backoff policy.
|
* It has built-in retry for calls to the homeserver with a backoff policy.
|
||||||
* @param client The Matrix client
|
|
||||||
* @param membership Our own membership identity parts used to send to jwt service.
|
|
||||||
* @param serviceUrl The URL of the livekit SFU service
|
|
||||||
* @param roomId The room id used in the jwt request. This is NOT the livekit_alias. The jwt service will provide the alias. It maps matrix room ids <-> Livekit aliases.
|
|
||||||
* @param opts Additional options to modify which endpoint with which data will be used to acquire the jwt token.
|
|
||||||
* @param opts.matrixRTCMode Determines which version of the JWT endpoint to use, which affects whether the
|
|
||||||
* RTC backend identity is based on string concatenation (legacy) or a hash (Matrix 2.0).
|
|
||||||
* This function by default uses whatever is possible with the current jwt service installed next to the SFU.
|
|
||||||
* For remote connections this does not matter, since we will not publish there we can rely on the newest option.
|
|
||||||
* @param opts.delayEndpointBaseUrl The URL of the matrix homeserver.
|
|
||||||
* @param opts.delayId The delay id used for the jwt service to manage.
|
|
||||||
* @param logger optional logger.
|
|
||||||
* @returns Object containing the token information
|
* @returns Object containing the token information
|
||||||
* @throws FailToGetOpenIdToken
|
* @throws FailToGetOpenIdToken
|
||||||
*/
|
*/
|
||||||
@@ -52,16 +70,7 @@ export async function getSFUConfig({
|
|||||||
delayEndpointBaseUrl,
|
delayEndpointBaseUrl,
|
||||||
delayId,
|
delayId,
|
||||||
logger,
|
logger,
|
||||||
}: {
|
}: GetSFUConfigParams): Promise<SFUConfig> {
|
||||||
client: ClientOpenIDParts;
|
|
||||||
membership: CallMembershipIdentityParts;
|
|
||||||
serviceUrl: string;
|
|
||||||
roomId: string;
|
|
||||||
role: "publisher" | "subscriber";
|
|
||||||
delayEndpointBaseUrl?: string;
|
|
||||||
delayId?: string;
|
|
||||||
logger: Logger;
|
|
||||||
}): Promise<SFUConfig> {
|
|
||||||
let openIdToken: IOpenIDToken;
|
let openIdToken: IOpenIDToken;
|
||||||
try {
|
try {
|
||||||
openIdToken = await doNetworkOperationWithRetry(async () =>
|
openIdToken = await doNetworkOperationWithRetry(async () =>
|
||||||
|
|||||||
+13
-9
@@ -694,15 +694,19 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
tab={settingsTab}
|
tab={settingsTab}
|
||||||
onTabChange={setSettingsTab}
|
onTabChange={setSettingsTab}
|
||||||
developerSettingsVm={developerSettingsVm}
|
developerSettingsVm={developerSettingsVm}
|
||||||
livekitRooms={allConnections
|
livekitRooms={allConnections.map(
|
||||||
.getConnections()
|
({ livekitRoom: room, livekitAlias, transport, role }) => ({
|
||||||
.map((connectionItem) => ({
|
room,
|
||||||
room: connectionItem.livekitRoom,
|
livekitAlias,
|
||||||
livekitAlias: connectionItem.livekitAlias,
|
role,
|
||||||
// TODO compute is local or tag it in the livekit room items already
|
url:
|
||||||
isLocal: undefined,
|
// It should be relatively clear to the user which kind of URL
|
||||||
url: connectionItem.transport.livekit_service_url,
|
// this is, based on the scheme (ws:// vs. http://)
|
||||||
}))}
|
"url" in transport
|
||||||
|
? transport.url
|
||||||
|
: transport.livekit_service_url,
|
||||||
|
}),
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ import {
|
|||||||
} from "./DeveloperSettingsTab";
|
} from "./DeveloperSettingsTab";
|
||||||
import { outOfCallDeveloperSettingsTabViewModel } from "./DeveloperSettingsTabViewModel";
|
import { outOfCallDeveloperSettingsTabViewModel } from "./DeveloperSettingsTabViewModel";
|
||||||
import { createStaticViewModel } from "../state/ViewModel";
|
import { createStaticViewModel } from "../state/ViewModel";
|
||||||
import { getSFUConfigWithOpenID } from "../livekit/openIDSFU";
|
|
||||||
import {
|
import {
|
||||||
customLivekitUrl as customLivekitUrlSetting,
|
customTransport as customTransportSetting,
|
||||||
enableExtendedLivekitLogs as enableExtendedLivekitLogsSetting,
|
enableExtendedLivekitLogs as enableExtendedLivekitLogsSetting,
|
||||||
matrixRTCMode as matrixRTCModeSetting,
|
matrixRTCMode as matrixRTCModeSetting,
|
||||||
} from "./settings";
|
} from "./settings";
|
||||||
import { MatrixRTCMode } from "../config/ConfigOptions";
|
import { MatrixRTCMode } from "../config/ConfigOptions";
|
||||||
import { mockConfig } from "../utils/test";
|
import { mockConfig } from "../utils/test";
|
||||||
|
import { getSFUConfig } from "../livekit/auth";
|
||||||
|
|
||||||
// Mock url params hook to avoid environment-dependent snapshot churn.
|
// Mock url params hook to avoid environment-dependent snapshot churn.
|
||||||
vi.mock("../UrlParams", () => ({
|
vi.mock("../UrlParams", () => ({
|
||||||
@@ -36,8 +36,8 @@ vi.mock("../UrlParams", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// IMPORTANT: mock the same specifier used by DeveloperSettingsTab
|
// IMPORTANT: mock the same specifier used by DeveloperSettingsTab
|
||||||
vi.mock("../livekit/openIDSFU", () => ({
|
vi.mock("../livekit/auth", () => ({
|
||||||
getSFUConfigWithOpenID: vi.fn().mockResolvedValue({
|
getSFUConfig: vi.fn().mockResolvedValue({
|
||||||
url: "mock-url",
|
url: "mock-url",
|
||||||
jwt: "mock-jwt",
|
jwt: "mock-jwt",
|
||||||
}),
|
}),
|
||||||
@@ -126,7 +126,7 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
});
|
});
|
||||||
describe("custom livekit url", () => {
|
describe("custom livekit url", () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
customLivekitUrlSetting.setValue(null);
|
customTransportSetting.setValue(null);
|
||||||
});
|
});
|
||||||
const client = {
|
const client = {
|
||||||
doesServerSupportUnstableFeature: vi.fn().mockResolvedValue(true),
|
doesServerSupportUnstableFeature: vi.fn().mockResolvedValue(true),
|
||||||
@@ -153,9 +153,9 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
|
|
||||||
const saveButton = screen.getByRole("button", { name: "Save" });
|
const saveButton = screen.getByRole("button", { name: "Save" });
|
||||||
await user.click(saveButton);
|
await user.click(saveButton);
|
||||||
expect(getSFUConfigWithOpenID).not.toHaveBeenCalled();
|
expect(getSFUConfig).not.toHaveBeenCalled();
|
||||||
|
|
||||||
expect(customLivekitUrlSetting.getValue()).toBe(null);
|
expect(customTransportSetting.getValue()).toBe(null);
|
||||||
});
|
});
|
||||||
it("will not update custom livekit url without text in input", async () => {
|
it("will not update custom livekit url without text in input", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
@@ -176,9 +176,9 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
|
|
||||||
const saveButton = screen.getByRole("button", { name: "Save" });
|
const saveButton = screen.getByRole("button", { name: "Save" });
|
||||||
await user.click(saveButton);
|
await user.click(saveButton);
|
||||||
expect(getSFUConfigWithOpenID).not.toHaveBeenCalled();
|
expect(getSFUConfig).not.toHaveBeenCalled();
|
||||||
|
|
||||||
expect(customLivekitUrlSetting.getValue()).toBe(null);
|
expect(customTransportSetting.getValue()).toBe(null);
|
||||||
});
|
});
|
||||||
it("will not update custom livekit url when pressing cancel", async () => {
|
it("will not update custom livekit url when pressing cancel", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
@@ -202,9 +202,9 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
name: "Reset overwrite",
|
name: "Reset overwrite",
|
||||||
});
|
});
|
||||||
await user.click(cancelButton);
|
await user.click(cancelButton);
|
||||||
expect(getSFUConfigWithOpenID).not.toHaveBeenCalled();
|
expect(getSFUConfig).not.toHaveBeenCalled();
|
||||||
|
|
||||||
expect(customLivekitUrlSetting.getValue()).toBe(null);
|
expect(customTransportSetting.getValue()).toBe(null);
|
||||||
});
|
});
|
||||||
it("will update custom livekit url", async () => {
|
it("will update custom livekit url", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
@@ -226,14 +226,14 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
|
|
||||||
const saveButton = screen.getByRole("button", { name: "Save" });
|
const saveButton = screen.getByRole("button", { name: "Save" });
|
||||||
await user.click(saveButton);
|
await user.click(saveButton);
|
||||||
expect(getSFUConfigWithOpenID).toHaveBeenCalledWith(
|
expect(getSFUConfig).toHaveBeenCalledWith(
|
||||||
expect.anything(),
|
expect.anything(),
|
||||||
expect.anything(),
|
expect.anything(),
|
||||||
"wss://example.livekit.valid",
|
"wss://example.livekit.valid",
|
||||||
"#testRoom",
|
"#testRoom",
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(customLivekitUrlSetting.getValue()).toBe(
|
expect(customTransportSetting.getValue()).toBe(
|
||||||
"wss://example.livekit.valid",
|
"wss://example.livekit.valid",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -256,14 +256,14 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
await user.type(input, "wss://example.livekit.valid");
|
await user.type(input, "wss://example.livekit.valid");
|
||||||
|
|
||||||
const saveButton = screen.getByRole("button", { name: "Save" });
|
const saveButton = screen.getByRole("button", { name: "Save" });
|
||||||
(getSFUConfigWithOpenID as Mock).mockImplementation(() => {
|
(getSFUConfig as Mock).mockImplementation(() => {
|
||||||
throw new Error("Invalid URL");
|
throw new Error("Invalid URL");
|
||||||
});
|
});
|
||||||
await user.click(saveButton);
|
await user.click(saveButton);
|
||||||
expect(
|
expect(
|
||||||
screen.getByText("invalid URL (did not update)"),
|
screen.getByText("invalid URL (did not update)"),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
expect(customLivekitUrlSetting.getValue()).toBe(null);
|
expect(customTransportSetting.getValue()).toBe(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import {
|
|||||||
muteAllAudio as muteAllAudioSetting,
|
muteAllAudio as muteAllAudioSetting,
|
||||||
alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting,
|
alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting,
|
||||||
matrixRTCMode as matrixRTCModeSetting,
|
matrixRTCMode as matrixRTCModeSetting,
|
||||||
customLivekitUrl as customLivekitUrlSetting,
|
customTransport as customTransportSetting,
|
||||||
advancedScreenShare as advancedScreenShareSetting,
|
advancedScreenShare as advancedScreenShareSetting,
|
||||||
screenShareResolution as screenShareResolutionSetting,
|
screenShareResolution as screenShareResolutionSetting,
|
||||||
screenShareFramerate as screenShareFramerateSetting,
|
screenShareFramerate as screenShareFramerateSetting,
|
||||||
@@ -67,9 +67,9 @@ import styles from "./DeveloperSettingsTab.module.css";
|
|||||||
import settingsStyles from "./SettingsModal.module.css";
|
import settingsStyles from "./SettingsModal.module.css";
|
||||||
import { Slider } from "../Slider";
|
import { Slider } from "../Slider";
|
||||||
import { useUrlParams } from "../UrlParams";
|
import { useUrlParams } from "../UrlParams";
|
||||||
import { getSFUConfigWithOpenID } from "../livekit/openIDSFU";
|
|
||||||
import { useBehavior } from "../useBehavior";
|
import { useBehavior } from "../useBehavior";
|
||||||
import { type ViewModel } from "../state/ViewModel.ts";
|
import { type ViewModel } from "../state/ViewModel.ts";
|
||||||
|
import { getSFUConfig } from "../livekit/auth/index.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The state of MatrixRTC's media key rotation.
|
* The state of MatrixRTC's media key rotation.
|
||||||
@@ -167,16 +167,18 @@ export const DeveloperSettingsTab: FC<Props> = ({
|
|||||||
enableExtendedLivekitLogsSetting,
|
enableExtendedLivekitLogsSetting,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [customLivekitUrlUpdateError, setCustomLivekitUrlUpdateError] =
|
const [customTransportUpdateError, setCustomLivekitUrlUpdateError] = useState<
|
||||||
useState<string | null>(null);
|
string | null
|
||||||
const [customLivekitUrl, setCustomLivekitUrl] = useSetting(
|
>(null);
|
||||||
customLivekitUrlSetting,
|
const [customTransport, setCustomTransport] = useSetting(
|
||||||
|
customTransportSetting,
|
||||||
|
);
|
||||||
|
const [customTransportTextBuffer, setCustomLivekitUrlTextBuffer] = useState(
|
||||||
|
() => JSON.stringify(customTransport),
|
||||||
);
|
);
|
||||||
const [customLivekitUrlTextBuffer, setCustomLivekitUrlTextBuffer] =
|
|
||||||
useState(customLivekitUrl);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCustomLivekitUrlTextBuffer(customLivekitUrl);
|
setCustomLivekitUrlTextBuffer(JSON.stringify(customTransport));
|
||||||
}, [customLivekitUrl]);
|
}, [customTransport]);
|
||||||
|
|
||||||
const [muteAllAudio, setMuteAllAudio] = useSetting(muteAllAudioSetting);
|
const [muteAllAudio, setMuteAllAudio] = useSetting(muteAllAudioSetting);
|
||||||
|
|
||||||
@@ -491,23 +493,23 @@ export const DeveloperSettingsTab: FC<Props> = ({
|
|||||||
<EditInPlace
|
<EditInPlace
|
||||||
onSubmit={(e) => e.preventDefault()}
|
onSubmit={(e) => e.preventDefault()}
|
||||||
helpLabel={
|
helpLabel={
|
||||||
customLivekitUrl === null
|
customTransport === null
|
||||||
? t("developer_mode.custom_livekit_url.from_config")
|
? t("developer_mode.custom_transport.from_config")
|
||||||
: t("developer_mode.custom_livekit_url.current_url") +
|
: t("developer_mode.custom_transport.current") +
|
||||||
customLivekitUrl
|
JSON.stringify(customTransport)
|
||||||
}
|
}
|
||||||
label={t("developer_mode.custom_livekit_url.label")}
|
label={t("developer_mode.custom_transport.label")}
|
||||||
saveButtonLabel={t("developer_mode.custom_livekit_url.save")}
|
saveButtonLabel={t("developer_mode.custom_transport.save")}
|
||||||
savingLabel={t("developer_mode.custom_livekit_url.saving")}
|
savingLabel={t("developer_mode.custom_transport.saving")}
|
||||||
cancelButtonLabel={t("developer_mode.custom_livekit_url.reset")}
|
cancelButtonLabel={t("developer_mode.custom_transport.reset")}
|
||||||
onSave={useCallback(
|
onSave={useCallback(
|
||||||
async (e: React.FormEvent<HTMLFormElement>): Promise<void> => {
|
async (e: React.FormEvent<HTMLFormElement>): Promise<void> => {
|
||||||
if (
|
if (
|
||||||
roomId === undefined ||
|
roomId === undefined ||
|
||||||
customLivekitUrlTextBuffer === "" ||
|
customTransportTextBuffer === "" ||
|
||||||
customLivekitUrlTextBuffer === null
|
customTransportTextBuffer === null
|
||||||
) {
|
) {
|
||||||
setCustomLivekitUrl(null);
|
setCustomTransport(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,21 +520,27 @@ export const DeveloperSettingsTab: FC<Props> = ({
|
|||||||
if (userId === null || deviceId === null) {
|
if (userId === null || deviceId === null) {
|
||||||
throw new Error("Invalid user or device ID");
|
throw new Error("Invalid user or device ID");
|
||||||
}
|
}
|
||||||
await getSFUConfigWithOpenID(
|
const transport = JSON.parse(customTransportTextBuffer);
|
||||||
|
// Validate the transport by attempting to get a token for it
|
||||||
|
await getSFUConfig({
|
||||||
client,
|
client,
|
||||||
{ userId, deviceId, memberId: "" },
|
membership: { userId, deviceId, memberId: "" },
|
||||||
customLivekitUrlTextBuffer,
|
transport,
|
||||||
roomId,
|
roomId,
|
||||||
);
|
role: "publisher",
|
||||||
|
logger,
|
||||||
|
});
|
||||||
setCustomLivekitUrlUpdateError(null);
|
setCustomLivekitUrlUpdateError(null);
|
||||||
setCustomLivekitUrl(customLivekitUrlTextBuffer);
|
setCustomTransport(transport);
|
||||||
} catch {
|
} catch {
|
||||||
setCustomLivekitUrlUpdateError("invalid URL (did not update)");
|
setCustomLivekitUrlUpdateError(
|
||||||
|
"invalid transport (did not update)",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[customLivekitUrlTextBuffer, setCustomLivekitUrl, client, roomId],
|
[customTransportTextBuffer, setCustomTransport, client, roomId],
|
||||||
)}
|
)}
|
||||||
value={customLivekitUrlTextBuffer ?? ""}
|
value={customTransportTextBuffer ?? ""}
|
||||||
onChange={useCallback(
|
onChange={useCallback(
|
||||||
(event: ChangeEvent<HTMLInputElement>): void => {
|
(event: ChangeEvent<HTMLInputElement>): void => {
|
||||||
setCustomLivekitUrlTextBuffer(event.target.value);
|
setCustomLivekitUrlTextBuffer(event.target.value);
|
||||||
@@ -541,14 +549,14 @@ export const DeveloperSettingsTab: FC<Props> = ({
|
|||||||
)}
|
)}
|
||||||
onCancel={useCallback(
|
onCancel={useCallback(
|
||||||
(e: React.FormEvent<HTMLFormElement>) => {
|
(e: React.FormEvent<HTMLFormElement>) => {
|
||||||
setCustomLivekitUrl(null);
|
setCustomTransport(null);
|
||||||
},
|
},
|
||||||
[setCustomLivekitUrl],
|
[setCustomTransport],
|
||||||
)}
|
)}
|
||||||
serverInvalid={customLivekitUrlUpdateError !== null}
|
serverInvalid={customTransportUpdateError !== null}
|
||||||
>
|
>
|
||||||
{customLivekitUrlUpdateError !== null && (
|
{customTransportUpdateError !== null && (
|
||||||
<ErrorMessage>{customLivekitUrlUpdateError}</ErrorMessage>
|
<ErrorMessage>{customTransportUpdateError}</ErrorMessage>
|
||||||
)}
|
)}
|
||||||
</EditInPlace>
|
</EditInPlace>
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ interface Props {
|
|||||||
livekitRooms?: {
|
livekitRooms?: {
|
||||||
room: LivekitRoom;
|
room: LivekitRoom;
|
||||||
url: string;
|
url: string;
|
||||||
isLocal?: boolean;
|
role: "publisher" | "subscriber";
|
||||||
}[];
|
}[];
|
||||||
/** Only available while in a call. Used by the developer tab. */
|
/** Only available while in a call. Used by the developer tab. */
|
||||||
developerSettingsVm?: ViewModel<DeveloperSettingsSnapshot>;
|
developerSettingsVm?: ViewModel<DeveloperSettingsSnapshot>;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
|||||||
import { type Behavior } from "../state/Behavior";
|
import { type Behavior } from "../state/Behavior";
|
||||||
import { useBehavior } from "../useBehavior";
|
import { useBehavior } from "../useBehavior";
|
||||||
import { MatrixRTCMode } from "../config/ConfigOptions";
|
import { MatrixRTCMode } from "../config/ConfigOptions";
|
||||||
|
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||||
|
|
||||||
export class Setting<T> {
|
export class Setting<T> {
|
||||||
public constructor(
|
public constructor(
|
||||||
@@ -153,8 +154,8 @@ export const matrixRTCMode = new Setting<MatrixRTCMode>(
|
|||||||
MatrixRTCMode.Compatibility,
|
MatrixRTCMode.Compatibility,
|
||||||
);
|
);
|
||||||
|
|
||||||
export const customLivekitUrl = new Setting<string | null>(
|
export const customTransport = new Setting<UnstableLivekitTransport | null>(
|
||||||
"custom-livekit-url",
|
"custom-transport",
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import {
|
|||||||
timer,
|
timer,
|
||||||
takeUntil,
|
takeUntil,
|
||||||
from,
|
from,
|
||||||
|
concatWith,
|
||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
import { type Logger, logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
import { type Logger, logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
||||||
import {
|
import {
|
||||||
@@ -383,7 +384,7 @@ export interface CallViewModel {
|
|||||||
* key is generated when someone joins or leaves.
|
* key is generated when someone joins or leaves.
|
||||||
*/
|
*/
|
||||||
keyRotationSuppressed$: Behavior<boolean>;
|
keyRotationSuppressed$: Behavior<boolean>;
|
||||||
allConnections$: Behavior<ConnectionManagerData>;
|
allConnections$: Behavior<Connection[]>;
|
||||||
/** Participants sorted by livekit room so they can be used in the audio rendering */
|
/** Participants sorted by livekit room so they can be used in the audio rendering */
|
||||||
livekitRoomItems$: Behavior<LivekitRoomItem[]>;
|
livekitRoomItems$: Behavior<LivekitRoomItem[]>;
|
||||||
/** use the layout instead, this is just for the sdk export. */
|
/** use the layout instead, this is just for the sdk export. */
|
||||||
@@ -591,6 +592,7 @@ export function createCallViewModel$(
|
|||||||
const connectionManager: IConnectionManager = {
|
const connectionManager: IConnectionManager = {
|
||||||
connectionManagerData$: scope.behavior(
|
connectionManagerData$: scope.behavior(
|
||||||
localTransport$.pipe(
|
localTransport$.pipe(
|
||||||
|
concatWith(NEVER), // So the Observable doesn't complete prematurely
|
||||||
mapScoped("connectionManager$", (scope, localTransport) =>
|
mapScoped("connectionManager$", (scope, localTransport) =>
|
||||||
createConnectionManager$({
|
createConnectionManager$({
|
||||||
scope,
|
scope,
|
||||||
@@ -738,13 +740,16 @@ export function createCallViewModel$(
|
|||||||
});
|
});
|
||||||
|
|
||||||
const allConnections$ = scope.behavior(
|
const allConnections$ = scope.behavior(
|
||||||
connectionManager.connectionManagerData$.pipe(map((d) => d.value)),
|
connectionManager.connectionManagerData$.pipe(
|
||||||
|
map((d) => d.value.getConnections()),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
const livekitRoomItems$ = scope.behavior<LivekitRoomItem[]>(
|
const livekitRoomItems$ = scope.behavior<LivekitRoomItem[]>(
|
||||||
allConnections$.pipe(
|
allConnections$.pipe(
|
||||||
switchMap((connections) =>
|
switchMap((connections) => {
|
||||||
combineLatest(
|
if (connections.length === 0) return of([]);
|
||||||
connections.getConnections().map((connection) =>
|
return combineLatest(
|
||||||
|
connections.map((connection) =>
|
||||||
remoteMatrixLivekitMembers$.pipe(
|
remoteMatrixLivekitMembers$.pipe(
|
||||||
switchMap((members) =>
|
switchMap((members) =>
|
||||||
combineLatest(members.value.map((m) => m.participant.value$)),
|
combineLatest(members.value.map((m) => m.participant.value$)),
|
||||||
@@ -758,8 +763,8 @@ export function createCallViewModel$(
|
|||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import {
|
|||||||
type CallViewModelOptions,
|
type CallViewModelOptions,
|
||||||
} from "./CallViewModel";
|
} from "./CallViewModel";
|
||||||
import {
|
import {
|
||||||
exampleSfuConfig,
|
|
||||||
exampleTransport,
|
exampleTransport,
|
||||||
mockConfig,
|
mockConfig,
|
||||||
MockConnection,
|
MockConnection,
|
||||||
@@ -209,24 +208,21 @@ export function withCallViewModel(mode: MatrixRTCMode) {
|
|||||||
livekitRoomFactory,
|
livekitRoomFactory,
|
||||||
connectionState$,
|
connectionState$,
|
||||||
windowSize$,
|
windowSize$,
|
||||||
localTransport: {
|
localTransport: exampleTransport,
|
||||||
transport: exampleTransport,
|
|
||||||
sfuConfig: exampleSfuConfig,
|
|
||||||
},
|
|
||||||
connectionFactory: {
|
connectionFactory: {
|
||||||
createConnection(
|
createConnection(
|
||||||
scope,
|
scope,
|
||||||
|
role,
|
||||||
transport,
|
transport,
|
||||||
ownMembershipIdentity,
|
ownMembershipIdentity,
|
||||||
logger,
|
logger,
|
||||||
sfuConfig,
|
|
||||||
) {
|
) {
|
||||||
return new MockConnection(
|
return new MockConnection(
|
||||||
{
|
{
|
||||||
scope,
|
scope,
|
||||||
|
role,
|
||||||
transport,
|
transport,
|
||||||
ownMembershipIdentity,
|
ownMembershipIdentity,
|
||||||
existingSFUConfig: sfuConfig,
|
|
||||||
client: room.client,
|
client: room.client,
|
||||||
roomId: room.roomId,
|
roomId: room.roomId,
|
||||||
livekitRoomFactory,
|
livekitRoomFactory,
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ import { ConnectionState, type Connection } from "../remoteMembers/Connection";
|
|||||||
import { type Publisher } from "./Publisher";
|
import { type Publisher } from "./Publisher";
|
||||||
import { initializeWidget } from "../../../widget";
|
import { initializeWidget } from "../../../widget";
|
||||||
import { nullHostBridge } from "../../../HostBridge";
|
import { nullHostBridge } from "../../../HostBridge";
|
||||||
import { type LocalTransport } from "./LocalTransport";
|
import { type SFUConfig } from "../../../livekit/auth";
|
||||||
import * as openIDSFU from "../../../livekit/openIDSFU";
|
import * as livekitAuth from "../../../livekit/auth";
|
||||||
|
|
||||||
initializeWidget();
|
initializeWidget();
|
||||||
|
|
||||||
@@ -110,6 +110,7 @@ const delegatedTimings: ResolvedDelayedLeaveTimings = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const mockedClient = {
|
const mockedClient = {
|
||||||
|
baseUrl: "https://matrix.example.org",
|
||||||
getDomain: vi.fn().mockReturnValue("example.org"),
|
getDomain: vi.fn().mockReturnValue("example.org"),
|
||||||
getDeviceId: vi.fn().mockReturnValue("AAAA"),
|
getDeviceId: vi.fn().mockReturnValue("AAAA"),
|
||||||
getOpenIdToken: vi.fn().mockResolvedValue({
|
getOpenIdToken: vi.fn().mockResolvedValue({
|
||||||
@@ -118,6 +119,7 @@ const mockedClient = {
|
|||||||
matrix_server_name: "localhost",
|
matrix_server_name: "localhost",
|
||||||
expires_in: 10000,
|
expires_in: 10000,
|
||||||
}),
|
}),
|
||||||
|
_unstable_delegateDelayedLeave: vi.fn().mockResolvedValue({}),
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("enterRTCSession", () => {
|
describe("enterRTCSession", () => {
|
||||||
@@ -315,19 +317,17 @@ describe("LocalMembership", () => {
|
|||||||
scope.end();
|
scope.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockTransportConfig = {
|
const mockTransport: UnstableLivekitTransport = {
|
||||||
livekit_service_url: "a",
|
type: "livekit",
|
||||||
|
url: "https://sfu.example.org",
|
||||||
} as UnstableLivekitTransport;
|
} as UnstableLivekitTransport;
|
||||||
|
|
||||||
const mockTransport = {
|
const mockSFUConfig: SFUConfig = {
|
||||||
transport: mockTransportConfig,
|
jwt: "foo",
|
||||||
sfuConfig: {
|
livekitAlias: "bar",
|
||||||
jwt: "foo",
|
livekitIdentity: "baz",
|
||||||
livekitAlias: "bar",
|
url: "bro",
|
||||||
livekitIdentity: "baz",
|
};
|
||||||
url: "bro",
|
|
||||||
},
|
|
||||||
} as LocalTransport;
|
|
||||||
|
|
||||||
const connectionTransportAConnected = {
|
const connectionTransportAConnected = {
|
||||||
livekitRoom: mockLivekitRoom({
|
livekitRoom: mockLivekitRoom({
|
||||||
@@ -337,7 +337,7 @@ describe("LocalMembership", () => {
|
|||||||
} as unknown as LocalParticipant,
|
} as unknown as LocalParticipant,
|
||||||
}),
|
}),
|
||||||
state$: constant(ConnectionState.LivekitConnected),
|
state$: constant(ConnectionState.LivekitConnected),
|
||||||
transport: mockTransportConfig,
|
transport: mockTransport,
|
||||||
} as Connection;
|
} as Connection;
|
||||||
const connectionTransportAConnecting = {
|
const connectionTransportAConnecting = {
|
||||||
...connectionTransportAConnected,
|
...connectionTransportAConnected,
|
||||||
@@ -346,8 +346,8 @@ describe("LocalMembership", () => {
|
|||||||
} as unknown as Connection;
|
} as unknown as Connection;
|
||||||
|
|
||||||
const authCallSpy = vi
|
const authCallSpy = vi
|
||||||
.spyOn(openIDSFU, "getSFUConfigWithOpenID")
|
.spyOn(livekitAuth, "getSFUConfig")
|
||||||
.mockImplementation(() => mockedClient.getOpenIdToken());
|
.mockResolvedValue(mockSFUConfig);
|
||||||
afterEach(() => authCallSpy.mockClear());
|
afterEach(() => authCallSpy.mockClear());
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
@@ -386,7 +386,7 @@ describe("LocalMembership", () => {
|
|||||||
await flushPromises();
|
await flushPromises();
|
||||||
// Joins with timings appropriate for the level of delegation support
|
// Joins with timings appropriate for the level of delegation support
|
||||||
expect(joinMatrixRTC).toHaveBeenCalledWith(
|
expect(joinMatrixRTC).toHaveBeenCalledWith(
|
||||||
mockTransportConfig,
|
mockTransport,
|
||||||
delayedLeaveTimings,
|
delayedLeaveTimings,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -480,7 +480,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<LocalTransport>();
|
const localTransport$ = new Subject<UnstableLivekitTransport>();
|
||||||
|
|
||||||
const connectionManagerData$ = new BehaviorSubject(
|
const connectionManagerData$ = new BehaviorSubject(
|
||||||
new Epoch(connectionManagerData),
|
new Epoch(connectionManagerData),
|
||||||
@@ -553,7 +553,7 @@ describe("LocalMembership", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
(
|
(
|
||||||
connectionManagerData2.getConnectionForTransport(mockTransportConfig)!
|
connectionManagerData2.getConnectionForTransport(mockTransport)!
|
||||||
.state$ as BehaviorSubject<ConnectionState>
|
.state$ as BehaviorSubject<ConnectionState>
|
||||||
).next(ConnectionState.LivekitConnected);
|
).next(ConnectionState.LivekitConnected);
|
||||||
expect(localMembership.localMemberState$.value).toStrictEqual({
|
expect(localMembership.localMemberState$.value).toStrictEqual({
|
||||||
@@ -858,7 +858,7 @@ describe("LocalMembership", () => {
|
|||||||
const setScreenShareEnabled = vi.fn().mockRejectedValue(error);
|
const setScreenShareEnabled = vi.fn().mockRejectedValue(error);
|
||||||
const connection = {
|
const connection = {
|
||||||
state$: constant(ConnectionState.LivekitConnected),
|
state$: constant(ConnectionState.LivekitConnected),
|
||||||
transport: mockTransportConfig,
|
transport: mockTransport,
|
||||||
livekitRoom: mockLivekitRoom({
|
livekitRoom: mockLivekitRoom({
|
||||||
localParticipant: mockLocalParticipant({
|
localParticipant: mockLocalParticipant({
|
||||||
isScreenShareEnabled: false,
|
isScreenShareEnabled: false,
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ import {
|
|||||||
MatrixRTCTransportMissingError,
|
MatrixRTCTransportMissingError,
|
||||||
FailToGetOpenIdToken,
|
FailToGetOpenIdToken,
|
||||||
} from "../../../utils/errors";
|
} from "../../../utils/errors";
|
||||||
import * as openIDSFU from "../../../livekit/openIDSFU";
|
import * as livekitAuth from "../../../livekit/auth";
|
||||||
import { customLivekitUrl } from "../../../settings/settings";
|
import { customTransport } from "../../../settings/settings";
|
||||||
import { testJWTToken } from "../../../utils/test-fixtures";
|
import { testJWTToken } from "../../../utils/test-fixtures";
|
||||||
import { MatrixRTCMode } from "../../../config/ConfigOptions";
|
import { type MatrixClient } from "matrix-js-sdk";
|
||||||
|
|
||||||
describe("LocalTransport", () => {
|
describe("LocalTransport", () => {
|
||||||
const openIdResponse: openIDSFU.SFUConfig = {
|
const sfuConfig: livekitAuth.SFUConfig = {
|
||||||
url: "https://lk.example.org",
|
url: "https://lk.example.org",
|
||||||
jwt: testJWTToken,
|
jwt: testJWTToken,
|
||||||
livekitAlias: "Akph4alDMhen",
|
livekitAlias: "Akph4alDMhen",
|
||||||
@@ -40,42 +40,25 @@ describe("LocalTransport", () => {
|
|||||||
it("throws if config is missing", async () => {
|
it("throws if config is missing", async () => {
|
||||||
await expect(
|
await expect(
|
||||||
getLocalTransport({
|
getLocalTransport({
|
||||||
roomId: "!room:example.org",
|
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
||||||
client: {
|
getDomain: () => "example.org",
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
|
||||||
getDomain: () => "example.org",
|
|
||||||
// These won't be called in this error path but satisfy the type
|
|
||||||
getOpenIdToken: vi.fn(),
|
|
||||||
getDeviceId: vi.fn(),
|
|
||||||
},
|
|
||||||
ownMembershipIdentity: ownMemberMock,
|
|
||||||
matrixRTCMode: MatrixRTCMode.Compatibility,
|
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow(new MatrixRTCTransportMissingError("example.org"));
|
).rejects.toThrow(new MatrixRTCTransportMissingError("example.org"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws FailToGetOpenIdToken when OpenID fetch fails", async () => {
|
it("threes if SFU config fetch fails", async () => {
|
||||||
// Provide a valid config so makeTransportInternal resolves a transport
|
// Provide a valid config so makeTransportInternal resolves a transport
|
||||||
mockConfig({
|
mockConfig({
|
||||||
livekit: { livekit_service_url: "https://lk.example.org" },
|
livekit: { livekit_service_url: "https://lk.example.org" },
|
||||||
});
|
});
|
||||||
vi.spyOn(openIDSFU, "getSFUConfigWithOpenID").mockRejectedValue(
|
vi.spyOn(livekitAuth, "getSFUConfig").mockRejectedValue(
|
||||||
new FailToGetOpenIdToken(new Error("no openid")),
|
new FailToGetOpenIdToken(new Error("no openid")),
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
getLocalTransport({
|
getLocalTransport({
|
||||||
roomId: "!example_room_id",
|
getDomain: () => "example.org",
|
||||||
client: {
|
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
||||||
getDomain: () => "example.org",
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
|
||||||
getOpenIdToken: vi.fn(),
|
|
||||||
getDeviceId: vi.fn(),
|
|
||||||
},
|
|
||||||
ownMembershipIdentity: ownMemberMock,
|
|
||||||
matrixRTCMode: MatrixRTCMode.Compatibility,
|
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow(new FailToGetOpenIdToken(new Error("no openid")));
|
).rejects.toThrow(new FailToGetOpenIdToken(new Error("no openid")));
|
||||||
});
|
});
|
||||||
@@ -86,7 +69,7 @@ describe("LocalTransport", () => {
|
|||||||
livekit: { livekit_service_url: "https://lk.example.org" },
|
livekit: { livekit_service_url: "https://lk.example.org" },
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.spyOn(openIDSFU, "getSFUConfigWithOpenID").mockResolvedValue({
|
vi.spyOn(livekitAuth, "getSFUConfig").mockResolvedValue({
|
||||||
url: "https://lk.example.org",
|
url: "https://lk.example.org",
|
||||||
jwt: "jwt",
|
jwt: "jwt",
|
||||||
livekitAlias: "Akph4alDMhen",
|
livekitAlias: "Akph4alDMhen",
|
||||||
@@ -95,16 +78,8 @@ describe("LocalTransport", () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
await getLocalTransport({
|
await getLocalTransport({
|
||||||
roomId: "!room:example.org",
|
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
||||||
client: {
|
getDomain: () => "example.org",
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
|
||||||
getDomain: () => "example.org",
|
|
||||||
getOpenIdToken: vi.fn(),
|
|
||||||
getDeviceId: vi.fn(),
|
|
||||||
},
|
|
||||||
ownMembershipIdentity: ownMemberMock,
|
|
||||||
matrixRTCMode: MatrixRTCMode.Compatibility,
|
|
||||||
}),
|
}),
|
||||||
).toStrictEqual({
|
).toStrictEqual({
|
||||||
transport: {
|
transport: {
|
||||||
@@ -120,26 +95,16 @@ describe("LocalTransport", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
type LocalTransportProps = Parameters<typeof getLocalTransport>[0];
|
|
||||||
|
|
||||||
describe("transport configuration mechanisms", () => {
|
describe("transport configuration mechanisms", () => {
|
||||||
let localTransportOpts: LocalTransportProps & {
|
let client: MockedObject<
|
||||||
client: MockedObject<LocalTransportProps["client"]>;
|
Pick<MatrixClient, "getDomain" | "_unstable_getRTCTransports">
|
||||||
};
|
>;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockConfig({});
|
mockConfig({});
|
||||||
customLivekitUrl.setValue(customLivekitUrl.defaultValue);
|
customTransport.setValue(customTransport.defaultValue);
|
||||||
localTransportOpts = {
|
client = {
|
||||||
ownMembershipIdentity: ownMemberMock,
|
getDomain: vi.fn().mockReturnValue("example.org"),
|
||||||
roomId: "!example_room_id",
|
_unstable_getRTCTransports: vi.fn().mockResolvedValue([]),
|
||||||
matrixRTCMode: MatrixRTCMode.Compatibility,
|
|
||||||
client: {
|
|
||||||
getDomain: vi.fn().mockReturnValue("example.org"),
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
_unstable_getRTCTransports: vi.fn().mockResolvedValue([]),
|
|
||||||
getOpenIdToken: vi.fn(),
|
|
||||||
getDeviceId: vi.fn(),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -151,11 +116,9 @@ describe("LocalTransport", () => {
|
|||||||
mockConfig({
|
mockConfig({
|
||||||
livekit: { livekit_service_url: "https://lk.example.org" },
|
livekit: { livekit_service_url: "https://lk.example.org" },
|
||||||
});
|
});
|
||||||
vi.spyOn(openIDSFU, "getSFUConfigWithOpenID").mockResolvedValue(
|
vi.spyOn(livekitAuth, "getSFUConfig").mockResolvedValue(sfuConfig);
|
||||||
openIdResponse,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(await getLocalTransport(localTransportOpts)).toStrictEqual({
|
expect(await getLocalTransport(client)).toStrictEqual({
|
||||||
transport: {
|
transport: {
|
||||||
livekit_service_url: "https://lk.example.org",
|
livekit_service_url: "https://lk.example.org",
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
@@ -170,12 +133,13 @@ describe("LocalTransport", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("supports getting transport via user settings", async () => {
|
it("supports getting transport via user settings", async () => {
|
||||||
customLivekitUrl.setValue("https://lk.example.org");
|
customTransport.setValue({
|
||||||
vi.spyOn(openIDSFU, "getSFUConfigWithOpenID").mockResolvedValue(
|
type: "livekit",
|
||||||
openIdResponse,
|
livekit_service_url: "https://lk.example.org",
|
||||||
);
|
});
|
||||||
|
vi.spyOn(livekitAuth, "getSFUConfig").mockResolvedValue(sfuConfig);
|
||||||
|
|
||||||
expect(await getLocalTransport(localTransportOpts)).toStrictEqual({
|
expect(await getLocalTransport(client)).toStrictEqual({
|
||||||
transport: {
|
transport: {
|
||||||
livekit_service_url: "https://lk.example.org",
|
livekit_service_url: "https://lk.example.org",
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
@@ -190,14 +154,12 @@ describe("LocalTransport", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("supports getting transport via backend", async () => {
|
it("supports getting transport via backend", async () => {
|
||||||
localTransportOpts.client._unstable_getRTCTransports.mockResolvedValue([
|
client._unstable_getRTCTransports.mockResolvedValue([
|
||||||
{ type: "livekit", livekit_service_url: "https://lk.example.org" },
|
{ type: "livekit", livekit_service_url: "https://lk.example.org" },
|
||||||
]);
|
]);
|
||||||
vi.spyOn(openIDSFU, "getSFUConfigWithOpenID").mockResolvedValue(
|
vi.spyOn(livekitAuth, "getSFUConfig").mockResolvedValue(sfuConfig);
|
||||||
openIdResponse,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(await getLocalTransport(localTransportOpts)).toStrictEqual({
|
expect(await getLocalTransport(client)).toStrictEqual({
|
||||||
transport: {
|
transport: {
|
||||||
livekit_service_url: "https://lk.example.org",
|
livekit_service_url: "https://lk.example.org",
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
@@ -212,14 +174,14 @@ describe("LocalTransport", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("fails fast if the openID request fails for backend config", async () => {
|
it("fails fast if the openID request fails for backend config", async () => {
|
||||||
localTransportOpts.client._unstable_getRTCTransports.mockResolvedValue([
|
client._unstable_getRTCTransports.mockResolvedValue([
|
||||||
{ type: "livekit", livekit_service_url: "https://lk.example.org" },
|
{ type: "livekit", livekit_service_url: "https://lk.example.org" },
|
||||||
]);
|
]);
|
||||||
vi.spyOn(openIDSFU, "getSFUConfigWithOpenID").mockRejectedValue(
|
vi.spyOn(livekitAuth, "getSFUConfig").mockRejectedValue(
|
||||||
new FailToGetOpenIdToken(new Error("Test driven error")),
|
new FailToGetOpenIdToken(new Error("Test driven error")),
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(getLocalTransport(localTransportOpts)).rejects.toThrow(
|
await expect(getLocalTransport(client)).rejects.toThrow(
|
||||||
expect.any(FailToGetOpenIdToken),
|
expect.any(FailToGetOpenIdToken),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -227,17 +189,8 @@ describe("LocalTransport", () => {
|
|||||||
it("throws if no options are available", async () => {
|
it("throws if no options are available", async () => {
|
||||||
await expect(
|
await expect(
|
||||||
getLocalTransport({
|
getLocalTransport({
|
||||||
ownMembershipIdentity: ownMemberMock,
|
getDomain: () => "example.org",
|
||||||
roomId: "!example_room_id",
|
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
||||||
matrixRTCMode: MatrixRTCMode.Compatibility,
|
|
||||||
client: {
|
|
||||||
getDomain: () => "example.org",
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
|
||||||
// These won't be called in this error path but satisfy the type
|
|
||||||
getOpenIdToken: vi.fn(),
|
|
||||||
getDeviceId: vi.fn(),
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow(new MatrixRTCTransportMissingError("example.org"));
|
).rejects.toThrow(new MatrixRTCTransportMissingError("example.org"));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
|||||||
|
|
||||||
import { Config } from "../../../config/Config.ts";
|
import { Config } from "../../../config/Config.ts";
|
||||||
import { MatrixRTCTransportMissingError } from "../../../utils/errors.ts";
|
import { MatrixRTCTransportMissingError } from "../../../utils/errors.ts";
|
||||||
import { customLivekitUrl } from "../../../settings/settings.ts";
|
import { customTransport as customTransportSetting } from "../../../settings/settings.ts";
|
||||||
import { RtcTransportAutoDiscovery } from "./RtcTransportAutoDiscovery.ts";
|
import { RtcTransportAutoDiscovery } from "./RtcTransportAutoDiscovery.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,16 +22,15 @@ export async function getLocalTransport(
|
|||||||
client: Pick<MatrixClient, "getDomain" | "_unstable_getRTCTransports">,
|
client: Pick<MatrixClient, "getDomain" | "_unstable_getRTCTransports">,
|
||||||
): Promise<UnstableLivekitTransport> {
|
): Promise<UnstableLivekitTransport> {
|
||||||
const discovery = new RtcTransportAutoDiscovery({
|
const discovery = new RtcTransportAutoDiscovery({
|
||||||
client: client,
|
client,
|
||||||
resolvedConfig: Config.get(),
|
resolvedConfig: Config.get(),
|
||||||
logger: rootLogger.getChild("[LocalTransport]"),
|
logger: rootLogger.getChild("[LocalTransport]"),
|
||||||
});
|
});
|
||||||
const customUrl = customLivekitUrl.value$.value;
|
|
||||||
|
|
||||||
// Respect the user's custom URL, if set
|
// Respect the user's custom transport, if set
|
||||||
const transport: UnstableLivekitTransport | null = customUrl
|
const transport =
|
||||||
? { type: "livekit", livekit_service_url: customUrl }
|
customTransportSetting.value$.value ??
|
||||||
: await discovery.discoverPreferredTransport();
|
(await discovery.discoverPreferredTransport());
|
||||||
|
|
||||||
if (transport === null)
|
if (transport === null)
|
||||||
throw new MatrixRTCTransportMissingError(client.getDomain() ?? "");
|
throw new MatrixRTCTransportMissingError(client.getDomain() ?? "");
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import {
|
|||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
import fetchMock from "fetch-mock";
|
import fetchMock from "fetch-mock";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import { type IOpenIDToken } from "matrix-js-sdk";
|
import { MatrixError, type IOpenIDToken } from "matrix-js-sdk";
|
||||||
import { logger, type Logger } from "matrix-js-sdk/lib/logger";
|
import { logger, type Logger } from "matrix-js-sdk/lib/logger";
|
||||||
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||||
|
|
||||||
@@ -38,17 +38,20 @@ import {
|
|||||||
type ConnectionOpts,
|
type ConnectionOpts,
|
||||||
} from "./Connection.ts";
|
} from "./Connection.ts";
|
||||||
import { ObservableScope } from "../../ObservableScope.ts";
|
import { ObservableScope } from "../../ObservableScope.ts";
|
||||||
import { type ClientOpenIDParts } from "../../../livekit/openIDSFU.ts";
|
|
||||||
import {
|
import {
|
||||||
ElementCallError,
|
ElementCallError,
|
||||||
FailToGetOpenIdToken,
|
FailToGetOpenIdToken,
|
||||||
} from "../../../utils/errors.ts";
|
} from "../../../utils/errors.ts";
|
||||||
import { testJWTToken } from "../../../utils/test-fixtures.ts";
|
import { testJWTToken } from "../../../utils/test-fixtures.ts";
|
||||||
import { mockRemoteParticipant, ownMemberMock } from "../../../utils/test.ts";
|
import { mockRemoteParticipant, ownMemberMock } from "../../../utils/test.ts";
|
||||||
|
import {
|
||||||
|
type ClientGetTokenParts,
|
||||||
|
type ClientOpenIDParts,
|
||||||
|
} from "../../../livekit/auth";
|
||||||
|
|
||||||
let testScope: ObservableScope;
|
let testScope: ObservableScope;
|
||||||
|
|
||||||
let client: MockedObject<ClientOpenIDParts>;
|
let client: MockedObject<ClientGetTokenParts & ClientOpenIDParts>;
|
||||||
|
|
||||||
let fakeLivekitRoom: MockedObject<LivekitRoom>;
|
let fakeLivekitRoom: MockedObject<LivekitRoom>;
|
||||||
|
|
||||||
@@ -57,14 +60,14 @@ let fakeLocalParticipant: MockedObject<LocalParticipant>;
|
|||||||
|
|
||||||
const ROOM_ID = "!roomID:example.org";
|
const ROOM_ID = "!roomID:example.org";
|
||||||
|
|
||||||
const livekitFocus: UnstableLivekitTransport = {
|
const transport: UnstableLivekitTransport = {
|
||||||
livekit_service_url: "https://matrix-rtc.example.org/livekit/jwt",
|
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
|
url: "wss://matrix-rtc.m.localhost/livekit/sfu",
|
||||||
};
|
};
|
||||||
|
|
||||||
function setupTest(): void {
|
function setupTest(): void {
|
||||||
testScope = new ObservableScope();
|
testScope = new ObservableScope();
|
||||||
client = vi.mocked<ClientOpenIDParts>({
|
client = vi.mocked<ClientGetTokenParts & ClientOpenIDParts>({
|
||||||
getOpenIdToken: vi.fn().mockResolvedValue({
|
getOpenIdToken: vi.fn().mockResolvedValue({
|
||||||
access_token: "rYsmGUEwNjKgJYyeNUkZseJN",
|
access_token: "rYsmGUEwNjKgJYyeNUkZseJN",
|
||||||
token_type: "Bearer",
|
token_type: "Bearer",
|
||||||
@@ -72,7 +75,8 @@ function setupTest(): void {
|
|||||||
expires_in: 3600,
|
expires_in: 3600,
|
||||||
}),
|
}),
|
||||||
getDeviceId: vi.fn().mockReturnValue("ABCDEF"),
|
getDeviceId: vi.fn().mockReturnValue("ABCDEF"),
|
||||||
} as unknown as ClientOpenIDParts);
|
_unstable_getLivekitToken: vi.fn().mockResolvedValue({ jwt: testJWTToken }),
|
||||||
|
});
|
||||||
|
|
||||||
localParticipantEventEmiter = new EventEmitter();
|
localParticipantEventEmiter = new EventEmitter();
|
||||||
|
|
||||||
@@ -118,22 +122,13 @@ function setupRemoteConnection(): Connection {
|
|||||||
const opts: ConnectionOpts = {
|
const opts: ConnectionOpts = {
|
||||||
client: client,
|
client: client,
|
||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
transport: livekitFocus,
|
role: "subscriber",
|
||||||
|
transport: transport,
|
||||||
scope: testScope,
|
scope: testScope,
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
livekitRoomFactory: () => fakeLivekitRoom,
|
livekitRoomFactory: () => fakeLivekitRoom,
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchMock.post(`${livekitFocus.livekit_service_url}/sfu/get`, () => {
|
|
||||||
return {
|
|
||||||
status: 200,
|
|
||||||
body: {
|
|
||||||
url: "wss://matrix-rtc.m.localhost/livekit/sfu",
|
|
||||||
jwt: testJWTToken,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
fakeLivekitRoom.connect.mockImplementation(async (): Promise<void> => {
|
fakeLivekitRoom.connect.mockImplementation(async (): Promise<void> => {
|
||||||
const changeEv = RoomEvent.ConnectionStateChanged;
|
const changeEv = RoomEvent.ConnectionStateChanged;
|
||||||
|
|
||||||
@@ -174,7 +169,8 @@ describe("Start connection states", () => {
|
|||||||
const opts: ConnectionOpts = {
|
const opts: ConnectionOpts = {
|
||||||
client: client,
|
client: client,
|
||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
transport: livekitFocus,
|
role: "subscriber",
|
||||||
|
transport: transport,
|
||||||
scope: testScope,
|
scope: testScope,
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
livekitRoomFactory: () => fakeLivekitRoom,
|
livekitRoomFactory: () => fakeLivekitRoom,
|
||||||
@@ -191,7 +187,8 @@ describe("Start connection states", () => {
|
|||||||
const opts: ConnectionOpts = {
|
const opts: ConnectionOpts = {
|
||||||
client: client,
|
client: client,
|
||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
transport: livekitFocus,
|
role: "subscriber",
|
||||||
|
transport: transport,
|
||||||
scope: testScope,
|
scope: testScope,
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
livekitRoomFactory: () => fakeLivekitRoom,
|
livekitRoomFactory: () => fakeLivekitRoom,
|
||||||
@@ -228,7 +225,7 @@ describe("Start connection states", () => {
|
|||||||
capturedState = capturedStates.pop();
|
capturedState = capturedStates.pop();
|
||||||
if (capturedState instanceof Error) {
|
if (capturedState instanceof Error) {
|
||||||
expect(capturedState.message).toEqual("Something went wrong");
|
expect(capturedState.message).toEqual("Something went wrong");
|
||||||
expect(connection.transport).toEqual(livekitFocus);
|
expect(connection.transport).toEqual(transport);
|
||||||
} else {
|
} else {
|
||||||
expect.fail("Expected FailedToStart state but got " + capturedState);
|
expect.fail("Expected FailedToStart state but got " + capturedState);
|
||||||
}
|
}
|
||||||
@@ -241,7 +238,8 @@ describe("Start connection states", () => {
|
|||||||
const opts: ConnectionOpts = {
|
const opts: ConnectionOpts = {
|
||||||
client: client,
|
client: client,
|
||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
transport: livekitFocus,
|
role: "subscriber",
|
||||||
|
transport: transport,
|
||||||
scope: testScope,
|
scope: testScope,
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
livekitRoomFactory: () => fakeLivekitRoom,
|
livekitRoomFactory: () => fakeLivekitRoom,
|
||||||
@@ -256,16 +254,15 @@ describe("Start connection states", () => {
|
|||||||
onTestFinished(() => s.unsubscribe());
|
onTestFinished(() => s.unsubscribe());
|
||||||
|
|
||||||
const deferredSFU = Promise.withResolvers<void>();
|
const deferredSFU = Promise.withResolvers<void>();
|
||||||
// mock the /sfu/get call
|
client._unstable_getLivekitToken.mockImplementation(async () => {
|
||||||
fetchMock.post(`${livekitFocus.livekit_service_url}/sfu/get`, async () => {
|
|
||||||
await deferredSFU.promise;
|
await deferredSFU.promise;
|
||||||
return {
|
throw new MatrixError(
|
||||||
status: 500,
|
{
|
||||||
body: {
|
|
||||||
errcode: "M_LOOKUP_FAILED",
|
errcode: "M_LOOKUP_FAILED",
|
||||||
error: "Failed to look up user info from homeserver",
|
error: "Failed to look up user info from homeserver",
|
||||||
},
|
},
|
||||||
};
|
500,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.start().catch(() => {
|
connection.start().catch(() => {
|
||||||
@@ -288,7 +285,7 @@ describe("Start connection states", () => {
|
|||||||
expect(capturedState.cause.message).toContain(
|
expect(capturedState.cause.message).toContain(
|
||||||
"Failed to look up user info from homeserver",
|
"Failed to look up user info from homeserver",
|
||||||
);
|
);
|
||||||
expect(connection.transport).toEqual(livekitFocus);
|
expect(connection.transport).toEqual(transport);
|
||||||
} else {
|
} else {
|
||||||
expect.fail("Expected FailedToStart state but got " + capturedState);
|
expect.fail("Expected FailedToStart state but got " + capturedState);
|
||||||
}
|
}
|
||||||
@@ -301,7 +298,8 @@ describe("Start connection states", () => {
|
|||||||
const opts: ConnectionOpts = {
|
const opts: ConnectionOpts = {
|
||||||
client: client,
|
client: client,
|
||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
transport: livekitFocus,
|
role: "subscriber",
|
||||||
|
transport: transport,
|
||||||
scope: testScope,
|
scope: testScope,
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
livekitRoomFactory: () => fakeLivekitRoom,
|
livekitRoomFactory: () => fakeLivekitRoom,
|
||||||
@@ -316,17 +314,6 @@ describe("Start connection states", () => {
|
|||||||
onTestFinished(() => s.unsubscribe());
|
onTestFinished(() => s.unsubscribe());
|
||||||
|
|
||||||
const deferredSFU = Promise.withResolvers<void>();
|
const deferredSFU = Promise.withResolvers<void>();
|
||||||
// mock the /sfu/get call
|
|
||||||
fetchMock.post(`${livekitFocus.livekit_service_url}/sfu/get`, () => {
|
|
||||||
return {
|
|
||||||
status: 200,
|
|
||||||
body: {
|
|
||||||
url: "wss://matrix-rtc.m.localhost/livekit/sfu",
|
|
||||||
jwt: testJWTToken,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
fakeLivekitRoom.connect.mockImplementation(async () => {
|
fakeLivekitRoom.connect.mockImplementation(async () => {
|
||||||
await deferredSFU.promise;
|
await deferredSFU.promise;
|
||||||
throw new Error("Failed to connect to livekit");
|
throw new Error("Failed to connect to livekit");
|
||||||
@@ -353,7 +340,7 @@ describe("Start connection states", () => {
|
|||||||
expect(capturedState.cause.message).toContain(
|
expect(capturedState.cause.message).toContain(
|
||||||
"Failed to connect to livekit",
|
"Failed to connect to livekit",
|
||||||
);
|
);
|
||||||
expect(connection.transport).toEqual(livekitFocus);
|
expect(connection.transport).toEqual(transport);
|
||||||
} else {
|
} else {
|
||||||
expect.fail(
|
expect.fail(
|
||||||
"Expected FailedToStart state but got " + JSON.stringify(capturedState),
|
"Expected FailedToStart state but got " + JSON.stringify(capturedState),
|
||||||
@@ -550,7 +537,8 @@ describe("remote track logging", () => {
|
|||||||
{
|
{
|
||||||
client,
|
client,
|
||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
transport: livekitFocus,
|
role: "subscriber",
|
||||||
|
transport: transport,
|
||||||
scope: testScope,
|
scope: testScope,
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
livekitRoomFactory: () => fakeLivekitRoom,
|
livekitRoomFactory: () => fakeLivekitRoom,
|
||||||
|
|||||||
@@ -102,7 +102,10 @@ export class Connection {
|
|||||||
*/
|
*/
|
||||||
public readonly state$: Behavior<ConnectionState | Error> = this._state$;
|
public readonly state$: Behavior<ConnectionState | Error> = this._state$;
|
||||||
|
|
||||||
private readonly role: "publisher" | "subscriber";
|
/**
|
||||||
|
* Whether we want to publish or only subscribe on this connection.
|
||||||
|
*/
|
||||||
|
public readonly role: "publisher" | "subscriber";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The media transport to connect to.
|
* The media transport to connect to.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||||
import { BehaviorSubject, NEVER } from "rxjs";
|
import { BehaviorSubject } from "rxjs";
|
||||||
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||||
import { type RemoteParticipant } from "livekit-client";
|
import { type RemoteParticipant } from "livekit-client";
|
||||||
import { logger } from "matrix-js-sdk/lib/logger";
|
import { logger } from "matrix-js-sdk/lib/logger";
|
||||||
@@ -74,7 +74,7 @@ describe("connections$ stream", () => {
|
|||||||
const { connectionManagerData$ } = createConnectionManager$({
|
const { connectionManagerData$ } = createConnectionManager$({
|
||||||
scope: testScope,
|
scope: testScope,
|
||||||
connectionFactory: fakeConnectionFactory,
|
connectionFactory: fakeConnectionFactory,
|
||||||
localTransport$: NEVER,
|
localTransport: TRANSPORT_1,
|
||||||
remoteTransports$: behavior("a", {
|
remoteTransports$: behavior("a", {
|
||||||
a: new Epoch([TRANSPORT_1, TRANSPORT_2], 0),
|
a: new Epoch([TRANSPORT_1, TRANSPORT_2], 0),
|
||||||
}),
|
}),
|
||||||
@@ -115,7 +115,7 @@ describe("connections$ stream", () => {
|
|||||||
const { connectionManagerData$ } = createConnectionManager$({
|
const { connectionManagerData$ } = createConnectionManager$({
|
||||||
scope: testScope,
|
scope: testScope,
|
||||||
connectionFactory: fakeConnectionFactory,
|
connectionFactory: fakeConnectionFactory,
|
||||||
localTransport$: NEVER,
|
localTransport: TRANSPORT_1,
|
||||||
remoteTransports$: behavior("abcdef", {
|
remoteTransports$: behavior("abcdef", {
|
||||||
a: new Epoch([TRANSPORT_1], 0),
|
a: new Epoch([TRANSPORT_1], 0),
|
||||||
b: new Epoch([TRANSPORT_1], 1),
|
b: new Epoch([TRANSPORT_1], 1),
|
||||||
@@ -162,7 +162,7 @@ describe("connections$ stream", () => {
|
|||||||
const { connectionManagerData$ } = createConnectionManager$({
|
const { connectionManagerData$ } = createConnectionManager$({
|
||||||
scope: testScope,
|
scope: testScope,
|
||||||
connectionFactory: fakeConnectionFactory,
|
connectionFactory: fakeConnectionFactory,
|
||||||
localTransport$: NEVER,
|
localTransport: TRANSPORT_1,
|
||||||
remoteTransports$: behavior("abc", {
|
remoteTransports$: behavior("abc", {
|
||||||
a: new Epoch([TRANSPORT_1], 0),
|
a: new Epoch([TRANSPORT_1], 0),
|
||||||
b: new Epoch([TRANSPORT_1, TRANSPORT_2], 1),
|
b: new Epoch([TRANSPORT_1, TRANSPORT_2], 1),
|
||||||
@@ -226,7 +226,12 @@ describe("connectionManagerData$ stream", () => {
|
|||||||
let fakeRemoteParticipantsStreams: Map<string, Behavior<RemoteParticipant[]>>;
|
let fakeRemoteParticipantsStreams: Map<string, Behavior<RemoteParticipant[]>>;
|
||||||
|
|
||||||
function keyForTransport(transport: UnstableLivekitTransport): string {
|
function keyForTransport(transport: UnstableLivekitTransport): string {
|
||||||
return `${transport.livekit_service_url}`;
|
return JSON.stringify([
|
||||||
|
"url" in transport ? transport.url : undefined,
|
||||||
|
"livekit_service_url" in transport
|
||||||
|
? transport.livekit_service_url
|
||||||
|
: undefined,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -297,7 +302,7 @@ describe("connectionManagerData$ stream", () => {
|
|||||||
const { connectionManagerData$ } = createConnectionManager$({
|
const { connectionManagerData$ } = createConnectionManager$({
|
||||||
scope: testScope,
|
scope: testScope,
|
||||||
connectionFactory: fakeConnectionFactory,
|
connectionFactory: fakeConnectionFactory,
|
||||||
localTransport$: NEVER,
|
localTransport: TRANSPORT_1,
|
||||||
remoteTransports$: behavior("a", {
|
remoteTransports$: behavior("a", {
|
||||||
a: new Epoch([TRANSPORT_1, TRANSPORT_2], 0),
|
a: new Epoch([TRANSPORT_1, TRANSPORT_2], 0),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ 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 { ClientOpenIDParts } from "../../../livekit/openIDSFU.ts";
|
import {
|
||||||
|
type ClientGetTokenParts,
|
||||||
|
type ClientOpenIDParts,
|
||||||
|
} from "../../../livekit/auth";
|
||||||
import {
|
import {
|
||||||
exampleTransport,
|
exampleTransport,
|
||||||
mockMediaDevices,
|
mockMediaDevices,
|
||||||
@@ -32,6 +35,7 @@ import {
|
|||||||
cameraBitrate,
|
cameraBitrate,
|
||||||
cameraCodec,
|
cameraCodec,
|
||||||
} from "../../../settings/settings.ts";
|
} from "../../../settings/settings.ts";
|
||||||
|
import { testJWTToken } from "../../../utils/test-fixtures.ts";
|
||||||
|
|
||||||
// At the top of your test file, after imports
|
// At the top of your test file, after imports
|
||||||
vi.mock("livekit-client", async (importOriginal) => {
|
vi.mock("livekit-client", async (importOriginal) => {
|
||||||
@@ -51,13 +55,14 @@ vi.mock("livekit-client", async (importOriginal) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let testScope: ObservableScope;
|
let testScope: ObservableScope;
|
||||||
let mockClient: ClientOpenIDParts;
|
let mockClient: ClientGetTokenParts & ClientOpenIDParts;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
testScope = new ObservableScope();
|
testScope = new ObservableScope();
|
||||||
mockClient = {
|
mockClient = {
|
||||||
getOpenIdToken: vi.fn().mockReturnValue(""),
|
getOpenIdToken: vi.fn().mockReturnValue(""),
|
||||||
getDeviceId: vi.fn().mockReturnValue("DEV000"),
|
getDeviceId: vi.fn().mockReturnValue("DEV000"),
|
||||||
|
_unstable_getLivekitToken: vi.fn().mockResolvedValue({ jwt: testJWTToken }),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -89,6 +94,7 @@ describe("ECConnectionFactory - Audio inputs options", () => {
|
|||||||
);
|
);
|
||||||
ecConnectionFactory.createConnection(
|
ecConnectionFactory.createConnection(
|
||||||
testScope,
|
testScope,
|
||||||
|
"subscriber",
|
||||||
exampleTransport,
|
exampleTransport,
|
||||||
ownMemberMock,
|
ownMemberMock,
|
||||||
logger,
|
logger,
|
||||||
@@ -137,6 +143,7 @@ describe("ECConnectionFactory - ControlledAudioDevice", () => {
|
|||||||
);
|
);
|
||||||
ecConnectionFactory.createConnection(
|
ecConnectionFactory.createConnection(
|
||||||
testScope,
|
testScope,
|
||||||
|
"subscriber",
|
||||||
exampleTransport,
|
exampleTransport,
|
||||||
ownMemberMock,
|
ownMemberMock,
|
||||||
logger,
|
logger,
|
||||||
@@ -172,6 +179,7 @@ describe("ECConnectionFactory - Camera quality settings", () => {
|
|||||||
);
|
);
|
||||||
ecConnectionFactory.createConnection(
|
ecConnectionFactory.createConnection(
|
||||||
testScope,
|
testScope,
|
||||||
|
"subscriber",
|
||||||
exampleTransport,
|
exampleTransport,
|
||||||
ownMemberMock,
|
ownMemberMock,
|
||||||
logger,
|
logger,
|
||||||
@@ -209,6 +217,7 @@ describe("ECConnectionFactory - Camera quality settings", () => {
|
|||||||
);
|
);
|
||||||
ecConnectionFactory.createConnection(
|
ecConnectionFactory.createConnection(
|
||||||
testScope,
|
testScope,
|
||||||
|
"subscriber",
|
||||||
exampleTransport,
|
exampleTransport,
|
||||||
ownMemberMock,
|
ownMemberMock,
|
||||||
logger,
|
logger,
|
||||||
@@ -247,6 +256,7 @@ describe("ECConnectionFactory - Camera quality settings", () => {
|
|||||||
);
|
);
|
||||||
ecConnectionFactory.createConnection(
|
ecConnectionFactory.createConnection(
|
||||||
testScope,
|
testScope,
|
||||||
|
"subscriber",
|
||||||
exampleTransport,
|
exampleTransport,
|
||||||
ownMemberMock,
|
ownMemberMock,
|
||||||
logger,
|
logger,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { test, vi, expect, beforeEach, afterEach } from "vitest";
|
import { test, vi, expect, beforeEach, afterEach } from "vitest";
|
||||||
import { BehaviorSubject, NEVER } from "rxjs";
|
import { BehaviorSubject } from "rxjs";
|
||||||
import { type Room as LivekitRoom } from "livekit-client";
|
import { type Room as LivekitRoom } from "livekit-client";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import fetchMock from "fetch-mock";
|
import fetchMock from "fetch-mock";
|
||||||
@@ -19,8 +19,12 @@ import {
|
|||||||
trackEpoch,
|
trackEpoch,
|
||||||
} from "../../ObservableScope.ts";
|
} from "../../ObservableScope.ts";
|
||||||
import { ECConnectionFactory } from "./ConnectionFactory.ts";
|
import { ECConnectionFactory } from "./ConnectionFactory.ts";
|
||||||
import { type ClientOpenIDParts } from "../../../livekit/openIDSFU.ts";
|
|
||||||
import {
|
import {
|
||||||
|
type ClientGetTokenParts,
|
||||||
|
type ClientOpenIDParts,
|
||||||
|
} from "../../../livekit/auth";
|
||||||
|
import {
|
||||||
|
exampleTransport,
|
||||||
mockMediaDevices,
|
mockMediaDevices,
|
||||||
mockRtcMembership,
|
mockRtcMembership,
|
||||||
ownMemberMock,
|
ownMemberMock,
|
||||||
@@ -40,7 +44,7 @@ import { localRtcMember, testJWTToken } from "../../../utils/test-fixtures.ts";
|
|||||||
|
|
||||||
let testScope: ObservableScope;
|
let testScope: ObservableScope;
|
||||||
let ecConnectionFactory: ECConnectionFactory;
|
let ecConnectionFactory: ECConnectionFactory;
|
||||||
let mockClient: ClientOpenIDParts;
|
let mockClient: ClientGetTokenParts & ClientOpenIDParts;
|
||||||
let lkRoomFactory: () => LivekitRoom;
|
let lkRoomFactory: () => LivekitRoom;
|
||||||
|
|
||||||
const createdMockLivekitRooms: Map<string, LivekitRoom> = new Map();
|
const createdMockLivekitRooms: Map<string, LivekitRoom> = new Map();
|
||||||
@@ -50,6 +54,7 @@ beforeEach(() => {
|
|||||||
mockClient = {
|
mockClient = {
|
||||||
getOpenIdToken: vi.fn().mockReturnValue(""),
|
getOpenIdToken: vi.fn().mockReturnValue(""),
|
||||||
getDeviceId: vi.fn().mockReturnValue("DEV000"),
|
getDeviceId: vi.fn().mockReturnValue("DEV000"),
|
||||||
|
_unstable_getLivekitToken: vi.fn().mockResolvedValue({ jwt: testJWTToken }),
|
||||||
};
|
};
|
||||||
|
|
||||||
lkRoomFactory = vi.fn().mockImplementation(() => {
|
lkRoomFactory = vi.fn().mockImplementation(() => {
|
||||||
@@ -123,7 +128,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$: NEVER,
|
localTransport: exampleTransport,
|
||||||
remoteTransports$: membershipsAndTransports.transports$,
|
remoteTransports$: membershipsAndTransports.transports$,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
|
|||||||
+5
-11
@@ -78,7 +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 SFUConfig } from "../livekit/openIDSFU";
|
import { type SFUConfig } from "../livekit/auth";
|
||||||
|
|
||||||
export function withFakeTimers(continuation: () => void): void {
|
export function withFakeTimers(continuation: () => void): void {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
@@ -214,13 +214,6 @@ export const exampleTransport: UnstableLivekitTransport = {
|
|||||||
livekit_service_url: "https://lk.example.org",
|
livekit_service_url: "https://lk.example.org",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const exampleSfuConfig: SFUConfig = {
|
|
||||||
jwt: "foo",
|
|
||||||
livekitAlias: "bar",
|
|
||||||
livekitIdentity: "baz",
|
|
||||||
url: "bro",
|
|
||||||
};
|
|
||||||
|
|
||||||
export function mockRtcMembership(
|
export function mockRtcMembership(
|
||||||
user: string | RoomMember,
|
user: string | RoomMember,
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
@@ -261,11 +254,12 @@ export function mockRtcMembership(
|
|||||||
content: data,
|
content: data,
|
||||||
});
|
});
|
||||||
|
|
||||||
const membershipData = CallMembership.membershipDataFromMatrixEvent(event);
|
const backendIdentity = rtcBackendIdentity ?? `${userId}:${deviceId}`;
|
||||||
const cms = new CallMembership(
|
const cms = new CallMembership(
|
||||||
event,
|
event,
|
||||||
membershipData,
|
CallMembership.membershipDataFromMatrixEvent(event),
|
||||||
rtcBackendIdentity ?? `${userId}:${deviceId}`,
|
[backendIdentity],
|
||||||
|
backendIdentity,
|
||||||
);
|
);
|
||||||
vi.mocked(cms).getTransport = vi.fn().mockReturnValue(fociPreferred[0]);
|
vi.mocked(cms).getTransport = vi.fn().mockReturnValue(fociPreferred[0]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user