Merge branch 'livekit' into toger5/dont-trap-in-invalid-config
This commit is contained in:
@@ -99,6 +99,13 @@ module.exports = {
|
|||||||
"jsdoc/require-param-description": "off",
|
"jsdoc/require-param-description": "off",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
files: ["playwright/**"],
|
||||||
|
rules: {
|
||||||
|
// Playwright as a `use` function that has nothing to do with React hooks.
|
||||||
|
"react-hooks/rules-of-hooks": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
settings: {
|
settings: {
|
||||||
react: {
|
react: {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ networks:
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
auth-service:
|
auth-service:
|
||||||
image: ghcr.io/element-hq/lk-jwt-service:pr_139
|
image: ghcr.io/element-hq/lk-jwt-service:sha-f8ddd00
|
||||||
pull_policy: always
|
pull_policy: always
|
||||||
hostname: auth-server
|
hostname: auth-server
|
||||||
environment:
|
environment:
|
||||||
@@ -25,7 +25,7 @@ services:
|
|||||||
- ecbackend
|
- ecbackend
|
||||||
|
|
||||||
auth-service-1:
|
auth-service-1:
|
||||||
image: ghcr.io/element-hq/lk-jwt-service:pr_139
|
image: ghcr.io/element-hq/lk-jwt-service:sha-f8ddd00
|
||||||
pull_policy: always
|
pull_policy: always
|
||||||
hostname: auth-server-1
|
hostname: auth-server-1
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -6,15 +6,10 @@ 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 {
|
import { type Page, test, expect, type JSHandle } from "@playwright/test";
|
||||||
type Browser,
|
|
||||||
type Page,
|
|
||||||
test,
|
|
||||||
expect,
|
|
||||||
type JSHandle,
|
|
||||||
} from "@playwright/test";
|
|
||||||
|
|
||||||
import type { MatrixClient } from "matrix-js-sdk";
|
import type { MatrixClient } from "matrix-js-sdk";
|
||||||
|
import { TestHelpers } from "../widget/test-helpers.ts";
|
||||||
|
|
||||||
export type UserBaseFixture = {
|
export type UserBaseFixture = {
|
||||||
mxId: string;
|
mxId: string;
|
||||||
@@ -31,10 +26,11 @@ export type BaseWidgetSetup = {
|
|||||||
export interface MyFixtures {
|
export interface MyFixtures {
|
||||||
asWidget: BaseWidgetSetup;
|
asWidget: BaseWidgetSetup;
|
||||||
callType: "room" | "dm";
|
callType: "room" | "dm";
|
||||||
|
addUser: (
|
||||||
|
username: string /**, homeserver: string*/,
|
||||||
|
) => Promise<UserBaseFixture>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PASSWORD = "foobarbaz1!";
|
|
||||||
|
|
||||||
// Minimal config.json for the local element-web instance
|
// Minimal config.json for the local element-web instance
|
||||||
const CONFIG_JSON = {
|
const CONFIG_JSON = {
|
||||||
default_server_config: {
|
default_server_config: {
|
||||||
@@ -68,85 +64,6 @@ const CONFIG_JSON = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the Element Call URL in the dev tool settings using `window.mxSettingsStore` via `page.evaluate`.
|
|
||||||
*/
|
|
||||||
const setDevToolElementCallDevUrl = process.env.USE_DOCKER
|
|
||||||
? async (page: Page): Promise<void> => {
|
|
||||||
await page.evaluate(() => {
|
|
||||||
window.mxSettingsStore.setValue(
|
|
||||||
"Developer.elementCallUrl",
|
|
||||||
null,
|
|
||||||
"device",
|
|
||||||
"http://localhost:8080/room",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
: async (page: Page): Promise<void> => {
|
|
||||||
await page.evaluate(() => {
|
|
||||||
window.mxSettingsStore.setValue(
|
|
||||||
"Developer.elementCallUrl",
|
|
||||||
null,
|
|
||||||
"device",
|
|
||||||
"https://localhost:3000/room",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a new user and returns page, clientHandle and mxId.
|
|
||||||
*/
|
|
||||||
async function registerUser(
|
|
||||||
browser: Browser,
|
|
||||||
username: string,
|
|
||||||
): Promise<{ page: Page; clientHandle: JSHandle<MatrixClient>; mxId: string }> {
|
|
||||||
const userContext = await browser.newContext({
|
|
||||||
reducedMotion: "reduce",
|
|
||||||
});
|
|
||||||
const page = await userContext.newPage();
|
|
||||||
await page.goto("http://localhost:8081/#/welcome");
|
|
||||||
await page.getByRole("link", { name: "Create Account" }).click();
|
|
||||||
await page.getByRole("textbox", { name: "Username" }).fill(username);
|
|
||||||
await page
|
|
||||||
.getByRole("textbox", { name: "Password", exact: true })
|
|
||||||
.fill(PASSWORD);
|
|
||||||
await page.getByRole("textbox", { name: "Confirm password" }).click();
|
|
||||||
await page.getByRole("textbox", { name: "Confirm password" }).fill(PASSWORD);
|
|
||||||
await page.getByRole("button", { name: "Register" }).click();
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole("heading", { name: `Welcome ${username}` }),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
const browserUnsupportedToast = page
|
|
||||||
.getByText("Element does not support this browser")
|
|
||||||
.locator("..")
|
|
||||||
.locator("..");
|
|
||||||
|
|
||||||
// Dismiss incompatible browser toast
|
|
||||||
const dismissButton = browserUnsupportedToast.getByRole("button", {
|
|
||||||
name: "Dismiss",
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
await expect(dismissButton).toBeVisible({ timeout: 700 });
|
|
||||||
await dismissButton.click();
|
|
||||||
} catch {
|
|
||||||
// dismissButton not visible, continue as normal
|
|
||||||
}
|
|
||||||
|
|
||||||
await setDevToolElementCallDevUrl(page);
|
|
||||||
|
|
||||||
const clientHandle = await page.evaluateHandle(() =>
|
|
||||||
window.mxMatrixClientPeg.get(),
|
|
||||||
);
|
|
||||||
const mxId = (await clientHandle.evaluate(
|
|
||||||
(cli: MatrixClient) => cli.getUserId(),
|
|
||||||
clientHandle,
|
|
||||||
))!;
|
|
||||||
|
|
||||||
return { page, clientHandle, mxId };
|
|
||||||
}
|
|
||||||
|
|
||||||
export const widgetTest = test.extend<MyFixtures>({
|
export const widgetTest = test.extend<MyFixtures>({
|
||||||
// allow per-test override: `widgetTest.use({ callType: "dm" })`
|
// allow per-test override: `widgetTest.use({ callType: "dm" })`
|
||||||
callType: ["room", { option: true }],
|
callType: ["room", { option: true }],
|
||||||
@@ -163,25 +80,16 @@ export const widgetTest = test.extend<MyFixtures>({
|
|||||||
page: ewPage1,
|
page: ewPage1,
|
||||||
clientHandle: brooksClientHandle,
|
clientHandle: brooksClientHandle,
|
||||||
mxId: brooksMxId,
|
mxId: brooksMxId,
|
||||||
} = await registerUser(browser, brooksDisplayName);
|
} = await TestHelpers.registerUser(browser, brooksDisplayName);
|
||||||
const {
|
const {
|
||||||
page: ewPage2,
|
page: ewPage2,
|
||||||
clientHandle: whistlerClientHandle,
|
clientHandle: whistlerClientHandle,
|
||||||
mxId: whistlerMxId,
|
mxId: whistlerMxId,
|
||||||
} = await registerUser(browser, whistlerDisplayName);
|
} = await TestHelpers.registerUser(browser, whistlerDisplayName);
|
||||||
|
|
||||||
// Invite the second user
|
// Invite the second user
|
||||||
await ewPage1
|
|
||||||
.getByRole("navigation", { name: "Room list" })
|
|
||||||
.getByRole("button", { name: "New conversation" })
|
|
||||||
.click();
|
|
||||||
|
|
||||||
if (callType === "room") {
|
if (callType === "room") {
|
||||||
await ewPage1.getByRole("menuitem", { name: "New Room" }).click();
|
await TestHelpers.createRoom("Welcome Room", ewPage1);
|
||||||
await ewPage1.getByRole("textbox", { name: "Name" }).fill("Welcome Room");
|
|
||||||
await ewPage1.getByRole("button", { name: "Create room" }).click();
|
|
||||||
await expect(ewPage1.getByText("You created this room.")).toBeVisible();
|
|
||||||
await expect(ewPage1.getByText("Encryption enabled")).toBeVisible();
|
|
||||||
|
|
||||||
await ewPage1
|
await ewPage1
|
||||||
.getByRole("button", { name: "Invite to this room", exact: true })
|
.getByRole("button", { name: "Invite to this room", exact: true })
|
||||||
@@ -211,6 +119,11 @@ export const widgetTest = test.extend<MyFixtures>({
|
|||||||
.getByRole("heading", { name: "Welcome Room" }),
|
.getByRole("heading", { name: "Welcome Room" }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
} else if (callType === "dm") {
|
} else if (callType === "dm") {
|
||||||
|
await ewPage1
|
||||||
|
.getByRole("navigation", { name: "Room list" })
|
||||||
|
.getByRole("button", { name: "New conversation" })
|
||||||
|
.click();
|
||||||
|
|
||||||
await ewPage1.getByRole("menuitem", { name: "Start chat" }).click();
|
await ewPage1.getByRole("menuitem", { name: "Start chat" }).click();
|
||||||
await ewPage1.getByRole("textbox", { name: "Search" }).click();
|
await ewPage1.getByRole("textbox", { name: "Search" }).click();
|
||||||
await ewPage1.getByRole("textbox", { name: "Search" }).fill(whistlerMxId);
|
await ewPage1.getByRole("textbox", { name: "Search" }).fill(whistlerMxId);
|
||||||
@@ -253,4 +166,28 @@ export const widgetTest = test.extend<MyFixtures>({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide a way to add additional users within a test.
|
||||||
|
* The returned user will be registered on the default homeserver, the name will be made unique by appending a timestamp.
|
||||||
|
*/
|
||||||
|
addUser: async ({ browser }, use) => {
|
||||||
|
await use(
|
||||||
|
async (
|
||||||
|
username: string /**, homeserver?: string*/,
|
||||||
|
): Promise<UserBaseFixture> => {
|
||||||
|
const uniqueSuffix = Date.now();
|
||||||
|
const { page, clientHandle, mxId } = await TestHelpers.registerUser(
|
||||||
|
browser,
|
||||||
|
`${username.toLowerCase()}_${uniqueSuffix}`,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
mxId,
|
||||||
|
displayName: username,
|
||||||
|
page,
|
||||||
|
clientHandle,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
160
playwright/widget/huddle-call.test.ts
Normal file
160
playwright/widget/huddle-call.test.ts
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 Element Creations Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
import { widgetTest } from "../fixtures/widget-user.ts";
|
||||||
|
import { TestHelpers } from "./test-helpers.ts";
|
||||||
|
|
||||||
|
widgetTest("Create and join a group call", async ({ addUser, browserName }) => {
|
||||||
|
test.skip(
|
||||||
|
browserName === "firefox",
|
||||||
|
"The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled",
|
||||||
|
);
|
||||||
|
|
||||||
|
test.slow(); // We are registering multiple users here, give it more time
|
||||||
|
|
||||||
|
const valere = await addUser("Valere");
|
||||||
|
const timo = await addUser("Timo");
|
||||||
|
const robin = await addUser("Robin");
|
||||||
|
const halfshot = await addUser("Halfshot");
|
||||||
|
const florian = await addUser("florian");
|
||||||
|
|
||||||
|
const roomName = "Group Call Room";
|
||||||
|
await TestHelpers.createRoom(roomName, valere.page, [
|
||||||
|
timo.mxId,
|
||||||
|
robin.mxId,
|
||||||
|
halfshot.mxId,
|
||||||
|
florian.mxId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (const user of [timo, robin, halfshot, florian]) {
|
||||||
|
// Accept the invite
|
||||||
|
// This isn't super stable to get this as this super generic locator,
|
||||||
|
// but it works for now.
|
||||||
|
await expect(
|
||||||
|
user.page.getByRole("option", { name: roomName }),
|
||||||
|
).toBeVisible();
|
||||||
|
await user.page.getByRole("option", { name: roomName }).click();
|
||||||
|
await user.page.getByRole("button", { name: "Accept" }).click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
user.page.getByRole("main").getByRole("heading", { name: roomName }),
|
||||||
|
).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the call as Valere
|
||||||
|
await TestHelpers.startCallInCurrentRoom(valere.page, false);
|
||||||
|
await expect(
|
||||||
|
valere.page.locator('iframe[title="Element Call"]'),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
valere.page
|
||||||
|
.locator('iframe[title="Element Call"]')
|
||||||
|
.contentFrame()
|
||||||
|
.getByTestId("lobby_joinCall"),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await valere.page
|
||||||
|
.locator('iframe[title="Element Call"]')
|
||||||
|
.contentFrame()
|
||||||
|
.getByTestId("lobby_joinCall")
|
||||||
|
.click();
|
||||||
|
|
||||||
|
for (const user of [timo, robin, halfshot, florian]) {
|
||||||
|
// THis is the header button that notifies about an ongoing call
|
||||||
|
await expect(user.page.getByText("Video call started")).toBeVisible();
|
||||||
|
await expect(user.page.getByRole("button", { name: "Join" })).toBeVisible();
|
||||||
|
await user.page.getByRole("button", { name: "Join" }).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const user of [timo, robin, halfshot, florian]) {
|
||||||
|
const frame = user.page
|
||||||
|
.locator('iframe[title="Element Call"]')
|
||||||
|
.contentFrame();
|
||||||
|
|
||||||
|
// No lobby, should start with video on
|
||||||
|
// The only way to know if it is muted or not is to look at the data-kind attribute..
|
||||||
|
const videoButton = frame.getByTestId("incall_videomute");
|
||||||
|
await expect(videoButton).toBeVisible();
|
||||||
|
// video should be off by default in a voice call
|
||||||
|
await expect(videoButton).toHaveAttribute("aria-label", /^Stop video$/);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should see 5 video tiles everywhere now
|
||||||
|
for (const user of [valere, timo, robin, halfshot, florian]) {
|
||||||
|
const frame = user.page
|
||||||
|
.locator('iframe[title="Element Call"]')
|
||||||
|
.contentFrame();
|
||||||
|
await expect(frame.getByTestId("videoTile")).toHaveCount(5);
|
||||||
|
for (const participant of [valere, timo, robin, halfshot, florian]) {
|
||||||
|
// Check the names are correct
|
||||||
|
await expect(frame.getByText(participant.displayName)).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is no other options than to wait for all media to be ready?
|
||||||
|
// Or it is too flaky :/
|
||||||
|
await user.page.waitForTimeout(5000);
|
||||||
|
// No one should be waiting for media
|
||||||
|
await expect(frame.getByText("Waiting for media...")).not.toBeVisible();
|
||||||
|
|
||||||
|
// There should be 5 video elements, visible and autoplaying
|
||||||
|
const videoElements = await frame.locator("video").all();
|
||||||
|
expect(videoElements.length).toBe(5);
|
||||||
|
await expect(frame.locator("video[autoplay]")).toHaveCount(5);
|
||||||
|
|
||||||
|
const blockDisplayCount = await frame
|
||||||
|
.locator("video")
|
||||||
|
.evaluateAll(
|
||||||
|
(videos: Element[]) =>
|
||||||
|
videos.filter(
|
||||||
|
(v: Element) => window.getComputedStyle(v).display === "block",
|
||||||
|
).length,
|
||||||
|
);
|
||||||
|
expect(blockDisplayCount).toBe(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quickly test muting one participant to see it reflects and that our asserts works
|
||||||
|
const florianFrame = florian.page
|
||||||
|
.locator('iframe[title="Element Call"]')
|
||||||
|
.contentFrame();
|
||||||
|
const florianMuteButton = florianFrame.getByTestId("incall_videomute");
|
||||||
|
await florianMuteButton.click();
|
||||||
|
// Now the button should indicate we can start video
|
||||||
|
await expect(florianMuteButton).toHaveAttribute(
|
||||||
|
"aria-label",
|
||||||
|
/^Start video$/,
|
||||||
|
);
|
||||||
|
|
||||||
|
// wait a bit for the state to propagate
|
||||||
|
await valere.page.waitForTimeout(3000);
|
||||||
|
{
|
||||||
|
const frame = valere.page
|
||||||
|
.locator('iframe[title="Element Call"]')
|
||||||
|
.contentFrame();
|
||||||
|
|
||||||
|
const videoElements = await frame.locator("video").all();
|
||||||
|
expect(videoElements.length).toBe(5);
|
||||||
|
|
||||||
|
const blockDisplayCount = await frame
|
||||||
|
.locator("video")
|
||||||
|
.evaluateAll(
|
||||||
|
(videos: Element[]) =>
|
||||||
|
videos.filter(
|
||||||
|
(v: Element) => window.getComputedStyle(v).display === "block",
|
||||||
|
).length,
|
||||||
|
);
|
||||||
|
|
||||||
|
// out of 5 ONLY 4 are visible (display:block) !!
|
||||||
|
// XXX we need to be better at our HTML markup and accessibility, it would make
|
||||||
|
// this kind of stuff way easier to test if we could look out for aria attributes.
|
||||||
|
expect(blockDisplayCount).toBe(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
await valere.page.pause();
|
||||||
|
});
|
||||||
156
playwright/widget/test-helpers.ts
Normal file
156
playwright/widget/test-helpers.ts
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 Element Creations Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
type Browser,
|
||||||
|
expect,
|
||||||
|
type JSHandle,
|
||||||
|
type Page,
|
||||||
|
} from "@playwright/test";
|
||||||
|
import { type MatrixClient } from "matrix-js-sdk";
|
||||||
|
|
||||||
|
const PASSWORD = "foobarbaz1!";
|
||||||
|
|
||||||
|
export class TestHelpers {
|
||||||
|
public static async startCallInCurrentRoom(
|
||||||
|
page: Page,
|
||||||
|
voice: boolean = false,
|
||||||
|
): Promise<void> {
|
||||||
|
const buttonName = voice ? "Voice call" : "Video call";
|
||||||
|
await expect(page.getByRole("button", { name: buttonName })).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: buttonName }).click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole("menuitem", { name: "Element Call" }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await page.getByRole("menuitem", { name: "Element Call" }).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a new user and returns page, clientHandle and mxId.
|
||||||
|
*/
|
||||||
|
public static async registerUser(
|
||||||
|
browser: Browser,
|
||||||
|
username: string,
|
||||||
|
): Promise<{
|
||||||
|
page: Page;
|
||||||
|
clientHandle: JSHandle<MatrixClient>;
|
||||||
|
mxId: string;
|
||||||
|
}> {
|
||||||
|
const userContext = await browser.newContext({
|
||||||
|
reducedMotion: "reduce",
|
||||||
|
});
|
||||||
|
const page = await userContext.newPage();
|
||||||
|
await page.goto("http://localhost:8081/#/welcome");
|
||||||
|
await page.getByRole("link", { name: "Create Account" }).click();
|
||||||
|
await page.getByRole("textbox", { name: "Username" }).fill(username);
|
||||||
|
await page
|
||||||
|
.getByRole("textbox", { name: "Password", exact: true })
|
||||||
|
.fill(PASSWORD);
|
||||||
|
await page.getByRole("textbox", { name: "Confirm password" }).click();
|
||||||
|
await page
|
||||||
|
.getByRole("textbox", { name: "Confirm password" })
|
||||||
|
.fill(PASSWORD);
|
||||||
|
await page.getByRole("button", { name: "Register" }).click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole("heading", { name: `Welcome ${username}` }),
|
||||||
|
).toBeVisible({
|
||||||
|
// Increase timeout as registration can be slow :/
|
||||||
|
timeout: 15_000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const browserUnsupportedToast = page
|
||||||
|
.getByText("Element does not support this browser")
|
||||||
|
.locator("..")
|
||||||
|
.locator("..");
|
||||||
|
|
||||||
|
// Dismiss incompatible browser toast
|
||||||
|
const dismissButton = browserUnsupportedToast.getByRole("button", {
|
||||||
|
name: "Dismiss",
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await expect(dismissButton).toBeVisible({ timeout: 700 });
|
||||||
|
await dismissButton.click();
|
||||||
|
} catch {
|
||||||
|
// dismissButton not visible, continue as normal
|
||||||
|
}
|
||||||
|
|
||||||
|
await TestHelpers.setDevToolElementCallDevUrl(page);
|
||||||
|
|
||||||
|
const clientHandle = await page.evaluateHandle(() =>
|
||||||
|
window.mxMatrixClientPeg.get(),
|
||||||
|
);
|
||||||
|
const mxId = (await clientHandle.evaluate(
|
||||||
|
(cli: MatrixClient) => cli.getUserId(),
|
||||||
|
clientHandle,
|
||||||
|
))!;
|
||||||
|
|
||||||
|
return { page, clientHandle, mxId };
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async createRoom(
|
||||||
|
name: string,
|
||||||
|
page: Page,
|
||||||
|
andInvite: string[] = [],
|
||||||
|
): Promise<void> {
|
||||||
|
await page.pause();
|
||||||
|
await page
|
||||||
|
.getByRole("navigation", { name: "Room list" })
|
||||||
|
.getByRole("button", { name: "New conversation" })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await page.getByRole("menuitem", { name: "New Room" }).click();
|
||||||
|
await page.getByRole("textbox", { name: "Name" }).fill(name);
|
||||||
|
await page.getByRole("button", { name: "Create room" }).click();
|
||||||
|
await expect(page.getByText("You created this room.")).toBeVisible();
|
||||||
|
await expect(page.getByText("Encryption enabled")).toBeVisible();
|
||||||
|
|
||||||
|
// Invite users if any
|
||||||
|
if (andInvite.length > 0) {
|
||||||
|
await page
|
||||||
|
.getByRole("button", { name: "Invite to this room", exact: true })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
const inviteInput = page.getByRole("dialog").getByRole("textbox");
|
||||||
|
for (const mxId of andInvite) {
|
||||||
|
await inviteInput.focus();
|
||||||
|
await inviteInput.fill(mxId);
|
||||||
|
await inviteInput.press("Enter");
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "Invite" }).click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current Element Web app to use the dev Element Call URL.
|
||||||
|
* @param page - The EW page
|
||||||
|
*/
|
||||||
|
public static async setDevToolElementCallDevUrl(page: Page): Promise<void> {
|
||||||
|
if (process.env.USE_DOCKER) {
|
||||||
|
await page.evaluate(() => {
|
||||||
|
window.mxSettingsStore.setValue(
|
||||||
|
"Developer.elementCallUrl",
|
||||||
|
null,
|
||||||
|
"device",
|
||||||
|
"http://localhost:8080/room",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await page.evaluate(() => {
|
||||||
|
window.mxSettingsStore.setValue(
|
||||||
|
"Developer.elementCallUrl",
|
||||||
|
null,
|
||||||
|
"device",
|
||||||
|
"https://localhost:3000/room",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
import { widgetTest } from "../fixtures/widget-user.ts";
|
import { widgetTest } from "../fixtures/widget-user.ts";
|
||||||
|
import { TestHelpers } from "./test-helpers.ts";
|
||||||
|
|
||||||
widgetTest.use({ callType: "dm" });
|
widgetTest.use({ callType: "dm" });
|
||||||
|
|
||||||
@@ -23,16 +24,7 @@ widgetTest(
|
|||||||
|
|
||||||
const { brooks, whistler } = asWidget;
|
const { brooks, whistler } = asWidget;
|
||||||
|
|
||||||
await expect(
|
await TestHelpers.startCallInCurrentRoom(brooks.page, true);
|
||||||
brooks.page.getByRole("button", { name: "Voice call" }),
|
|
||||||
).toBeVisible();
|
|
||||||
await brooks.page.getByRole("button", { name: "Voice call" }).click();
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
brooks.page.getByRole("menuitem", { name: "Element Call" }),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await brooks.page.getByRole("menuitem", { name: "Element Call" }).click();
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
brooks.page.locator('iframe[title="Element Call"]'),
|
brooks.page.locator('iframe[title="Element Call"]'),
|
||||||
@@ -123,16 +115,7 @@ widgetTest(
|
|||||||
|
|
||||||
const { brooks, whistler } = asWidget;
|
const { brooks, whistler } = asWidget;
|
||||||
|
|
||||||
await expect(
|
await TestHelpers.startCallInCurrentRoom(brooks.page, false);
|
||||||
brooks.page.getByRole("button", { name: "Video call" }),
|
|
||||||
).toBeVisible();
|
|
||||||
await brooks.page.getByRole("button", { name: "Video call" }).click();
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
brooks.page.getByRole("menuitem", { name: "Element Call" }),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await brooks.page.getByRole("menuitem", { name: "Element Call" }).click();
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
brooks.page.locator('iframe[title="Element Call"]'),
|
brooks.page.locator('iframe[title="Element Call"]'),
|
||||||
@@ -210,3 +193,48 @@ widgetTest(
|
|||||||
await expect(brooks.page.locator(".mx_BasicMessageComposer")).toBeVisible();
|
await expect(brooks.page.locator(".mx_BasicMessageComposer")).toBeVisible();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
widgetTest(
|
||||||
|
"Decline a new video call in DM as widget",
|
||||||
|
async ({ asWidget, browserName }) => {
|
||||||
|
test.skip(
|
||||||
|
browserName === "firefox",
|
||||||
|
"The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled",
|
||||||
|
);
|
||||||
|
|
||||||
|
test.slow(); // Triples the timeout
|
||||||
|
|
||||||
|
const { brooks, whistler } = asWidget;
|
||||||
|
|
||||||
|
await TestHelpers.startCallInCurrentRoom(brooks.page, false);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
brooks.page.locator('iframe[title="Element Call"]'),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
const brooksFrame = brooks.page
|
||||||
|
.locator('iframe[title="Element Call"]')
|
||||||
|
.contentFrame();
|
||||||
|
|
||||||
|
// We should show a ringing overlay, let's check for that
|
||||||
|
await expect(
|
||||||
|
brooksFrame.getByText(`Waiting for ${whistler.displayName} to join…`),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await expect(whistler.page.getByText("Incoming video call")).toBeVisible();
|
||||||
|
await whistler.page.getByRole("button", { name: "Decline" }).click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
whistler.page.locator('iframe[title="Element Call"]'),
|
||||||
|
).not.toBeVisible();
|
||||||
|
|
||||||
|
// The widget should be closed and the timeline should be back on screen
|
||||||
|
await expect(
|
||||||
|
brooks.page.locator('iframe[title="Element Call"]'),
|
||||||
|
).not.toBeVisible();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
brooks.page.getByText("This is the beginning of your"),
|
||||||
|
).toBeVisible();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@@ -842,6 +842,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
.getConnections()
|
.getConnections()
|
||||||
.map((connectionItem) => ({
|
.map((connectionItem) => ({
|
||||||
room: connectionItem.livekitRoom,
|
room: connectionItem.livekitRoom,
|
||||||
|
livekitAlias: connectionItem.livekitAlias,
|
||||||
// TODO compute is local or tag it in the livekit room items already
|
// TODO compute is local or tag it in the livekit room items already
|
||||||
isLocal: undefined,
|
isLocal: undefined,
|
||||||
url: connectionItem.transport.livekit_service_url,
|
url: connectionItem.transport.livekit_service_url,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ function createMockLivekitRoom(
|
|||||||
wsUrl: string,
|
wsUrl: string,
|
||||||
serverInfo: object,
|
serverInfo: object,
|
||||||
metadata: string,
|
metadata: string,
|
||||||
): { isLocal: boolean; url: string; room: LivekitRoom } {
|
): { isLocal: boolean; url: string; room: LivekitRoom; livekitAlias: string } {
|
||||||
const mockRoom = {
|
const mockRoom = {
|
||||||
serverInfo,
|
serverInfo,
|
||||||
metadata,
|
metadata,
|
||||||
@@ -49,6 +49,7 @@ function createMockLivekitRoom(
|
|||||||
isLocal: true,
|
isLocal: true,
|
||||||
url: wsUrl,
|
url: wsUrl,
|
||||||
room: mockRoom,
|
room: mockRoom,
|
||||||
|
livekitAlias: "TestAlias",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +73,7 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
room: LivekitRoom;
|
room: LivekitRoom;
|
||||||
url: string;
|
url: string;
|
||||||
isLocal?: boolean;
|
isLocal?: boolean;
|
||||||
|
livekitAlias: string;
|
||||||
}[] = [
|
}[] = [
|
||||||
createMockLivekitRoom(
|
createMockLivekitRoom(
|
||||||
"wss://local-sfu.example.org",
|
"wss://local-sfu.example.org",
|
||||||
@@ -80,6 +82,7 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
),
|
),
|
||||||
{
|
{
|
||||||
isLocal: false,
|
isLocal: false,
|
||||||
|
livekitAlias: "TestAlias2",
|
||||||
url: "wss://remote-sfu.example.org",
|
url: "wss://remote-sfu.example.org",
|
||||||
room: {
|
room: {
|
||||||
localParticipant: { identity: "localParticipantIdentity" },
|
localParticipant: { identity: "localParticipantIdentity" },
|
||||||
|
|||||||
@@ -51,7 +51,12 @@ import { getSFUConfigWithOpenID } from "../livekit/openIDSFU";
|
|||||||
interface Props {
|
interface Props {
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
roomId?: string;
|
roomId?: string;
|
||||||
livekitRooms?: { room: LivekitRoom; url: string; isLocal?: boolean }[];
|
livekitRooms?: {
|
||||||
|
room: LivekitRoom;
|
||||||
|
url: string;
|
||||||
|
isLocal?: boolean;
|
||||||
|
livekitAlias?: string;
|
||||||
|
}[];
|
||||||
env: ImportMetaEnv;
|
env: ImportMetaEnv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,6 +348,7 @@ export const DeveloperSettingsTab: FC<Props> = ({
|
|||||||
url: livekitRoom.url || "unknown",
|
url: livekitRoom.url || "unknown",
|
||||||
})}
|
})}
|
||||||
</h4>
|
</h4>
|
||||||
|
<p>LivekitAlias: {livekitRoom.livekitAlias}</p>
|
||||||
{livekitRoom.isLocal && <p>ws-url: {localSfuUrl?.href}</p>}
|
{livekitRoom.isLocal && <p>ws-url: {localSfuUrl?.href}</p>}
|
||||||
<p>
|
<p>
|
||||||
{t("developer_mode.livekit_server_info")}(
|
{t("developer_mode.livekit_server_info")}(
|
||||||
|
|||||||
@@ -355,6 +355,10 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
|||||||
<h4>
|
<h4>
|
||||||
LiveKit SFU: wss://local-sfu.example.org
|
LiveKit SFU: wss://local-sfu.example.org
|
||||||
</h4>
|
</h4>
|
||||||
|
<p>
|
||||||
|
LivekitAlias:
|
||||||
|
TestAlias
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
ws-url:
|
ws-url:
|
||||||
wss://local-sfu.example.org/
|
wss://local-sfu.example.org/
|
||||||
@@ -393,6 +397,10 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
|||||||
<h4>
|
<h4>
|
||||||
LiveKit SFU: wss://remote-sfu.example.org
|
LiveKit SFU: wss://remote-sfu.example.org
|
||||||
</h4>
|
</h4>
|
||||||
|
<p>
|
||||||
|
LivekitAlias:
|
||||||
|
TestAlias2
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
LiveKit Server Info
|
LiveKit Server Info
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -124,9 +124,9 @@ import {
|
|||||||
} from "./remoteMembers/ConnectionManager.ts";
|
} from "./remoteMembers/ConnectionManager.ts";
|
||||||
import {
|
import {
|
||||||
createMatrixLivekitMembers$,
|
createMatrixLivekitMembers$,
|
||||||
type TaggedParticipant,
|
|
||||||
type LocalMatrixLivekitMember,
|
type LocalMatrixLivekitMember,
|
||||||
type RemoteMatrixLivekitMember,
|
type RemoteMatrixLivekitMember,
|
||||||
|
type MatrixLivekitMember,
|
||||||
} from "./remoteMembers/MatrixLivekitMembers.ts";
|
} from "./remoteMembers/MatrixLivekitMembers.ts";
|
||||||
import {
|
import {
|
||||||
type AutoLeaveReason,
|
type AutoLeaveReason,
|
||||||
@@ -717,65 +717,38 @@ export function createCallViewModel$(
|
|||||||
matrixLivekitMembers,
|
matrixLivekitMembers,
|
||||||
duplicateTiles,
|
duplicateTiles,
|
||||||
]) {
|
]) {
|
||||||
let localUserMediaId: string | undefined = undefined;
|
const computeMediaId = (m: MatrixLivekitMember): string =>
|
||||||
// add local member if available
|
`${m.userId}:${m.membership$.value.deviceId}`;
|
||||||
if (localMatrixLivekitMember) {
|
|
||||||
|
const localUserMediaId = localMatrixLivekitMember
|
||||||
|
? computeMediaId(localMatrixLivekitMember)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const localAsArray = localMatrixLivekitMember
|
||||||
|
? [localMatrixLivekitMember]
|
||||||
|
: [];
|
||||||
|
const remoteWithoutLocal = matrixLivekitMembers.value.filter(
|
||||||
|
(m) => computeMediaId(m) !== localUserMediaId,
|
||||||
|
);
|
||||||
|
const allMatrixLivekitMembers = [
|
||||||
|
...localAsArray,
|
||||||
|
...remoteWithoutLocal,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const matrixLivekitMember of allMatrixLivekitMembers) {
|
||||||
const { userId, participant, connection$, membership$ } =
|
const { userId, participant, connection$, membership$ } =
|
||||||
localMatrixLivekitMember;
|
matrixLivekitMember;
|
||||||
|
const rtcId = membership$.value.rtcBackendIdentity; // rtcBackendIdentity
|
||||||
localUserMediaId = `${userId}:${membership$.value.deviceId}`;
|
const mediaId = computeMediaId(matrixLivekitMember);
|
||||||
const rtcBackendIdentity = membership$.value.rtcBackendIdentity;
|
|
||||||
for (let dup = 0; dup < 1 + duplicateTiles; dup++) {
|
for (let dup = 0; dup < 1 + duplicateTiles; dup++) {
|
||||||
yield {
|
yield {
|
||||||
keys: [
|
keys: [dup, mediaId, userId, participant, connection$, rtcId],
|
||||||
dup,
|
|
||||||
localUserMediaId,
|
|
||||||
userId,
|
|
||||||
participant satisfies TaggedParticipant as TaggedParticipant, // Widen the type safely
|
|
||||||
connection$,
|
|
||||||
rtcBackendIdentity,
|
|
||||||
],
|
|
||||||
data: undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// add remote members that are available
|
|
||||||
for (const {
|
|
||||||
userId,
|
|
||||||
participant,
|
|
||||||
connection$,
|
|
||||||
membership$,
|
|
||||||
} of matrixLivekitMembers.value) {
|
|
||||||
const userMediaId = `${userId}:${membership$.value.deviceId}`;
|
|
||||||
const rtcBackendIdentity = membership$.value.rtcBackendIdentity;
|
|
||||||
// skip local user as we added them manually before
|
|
||||||
if (userMediaId === localUserMediaId) continue;
|
|
||||||
|
|
||||||
for (let dup = 0; dup < 1 + duplicateTiles; dup++) {
|
|
||||||
yield {
|
|
||||||
keys: [
|
|
||||||
dup,
|
|
||||||
userMediaId,
|
|
||||||
userId,
|
|
||||||
participant,
|
|
||||||
connection$,
|
|
||||||
rtcBackendIdentity,
|
|
||||||
],
|
|
||||||
data: undefined,
|
data: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(
|
(scope, _, dup, mediaId, userId, participant, connection$, rtcId) => {
|
||||||
scope,
|
|
||||||
_data$,
|
|
||||||
dup,
|
|
||||||
userMediaId,
|
|
||||||
userId,
|
|
||||||
participant,
|
|
||||||
connection$,
|
|
||||||
rtcBackendIdentity,
|
|
||||||
) => {
|
|
||||||
const livekitRoom$ = scope.behavior(
|
const livekitRoom$ = scope.behavior(
|
||||||
connection$.pipe(map((c) => c?.livekitRoom)),
|
connection$.pipe(map((c) => c?.livekitRoom)),
|
||||||
);
|
);
|
||||||
@@ -790,9 +763,9 @@ export function createCallViewModel$(
|
|||||||
|
|
||||||
return new UserMedia(
|
return new UserMedia(
|
||||||
scope,
|
scope,
|
||||||
`${userMediaId}:${dup}`,
|
`${mediaId}:${dup}`,
|
||||||
userId,
|
userId,
|
||||||
rtcBackendIdentity,
|
rtcId,
|
||||||
participant,
|
participant,
|
||||||
options.encryptionSystem,
|
options.encryptionSystem,
|
||||||
livekitRoom$,
|
livekitRoom$,
|
||||||
@@ -801,8 +774,8 @@ export function createCallViewModel$(
|
|||||||
localMembership.reconnecting$,
|
localMembership.reconnecting$,
|
||||||
displayName$,
|
displayName$,
|
||||||
matrixMemberMetadataStore.createAvatarUrlBehavior$(userId),
|
matrixMemberMetadataStore.createAvatarUrlBehavior$(userId),
|
||||||
handsRaised$.pipe(map((v) => v[userMediaId]?.time ?? null)),
|
handsRaised$.pipe(map((v) => v[mediaId]?.time ?? null)),
|
||||||
reactions$.pipe(map((v) => v[userMediaId] ?? undefined)),
|
reactions$.pipe(map((v) => v[mediaId] ?? undefined)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -68,6 +68,27 @@ export enum JwtEndpointVersion {
|
|||||||
Matrix_2_0 = "matrix_2_0",
|
Matrix_2_0 = "matrix_2_0",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO livekit_alias-cleanup
|
||||||
|
// 1. We need to move away from transports map to connections!!!
|
||||||
|
//
|
||||||
|
// 2. We need to stop sending livekit_alias all together
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// 1.
|
||||||
|
// Transports are just the jwt service adress but do not contain the information which room on this transport to use.
|
||||||
|
// That requires slot and roomId.
|
||||||
|
//
|
||||||
|
// We need one connection per room on the transport.
|
||||||
|
//
|
||||||
|
// We need an object that contains:
|
||||||
|
// transport
|
||||||
|
// roomId
|
||||||
|
// slotId
|
||||||
|
//
|
||||||
|
// To map to the connections. Prosposal: `ConnectionIdentifier`
|
||||||
|
//
|
||||||
|
// 2.
|
||||||
|
// We need to make sure we do not sent livekit_alias in sticky events and that we drop all code for sending state events!
|
||||||
export interface LocalTransportWithSFUConfig {
|
export interface LocalTransportWithSFUConfig {
|
||||||
transport: LivekitTransport;
|
transport: LivekitTransport;
|
||||||
sfuConfig: SFUConfig;
|
sfuConfig: SFUConfig;
|
||||||
@@ -250,7 +271,18 @@ async function makeTransport(
|
|||||||
transport: {
|
transport: {
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
livekit_service_url: url,
|
livekit_service_url: url,
|
||||||
livekit_alias: sfuConfig.livekitAlias,
|
// WARNING PLS READ ME!!!
|
||||||
|
// This looks unintuitive especially considering that `sfuConfig.livekitAlias` exists.
|
||||||
|
// Why do we not use: `livekit_alias: sfuConfig.livekitAlias`
|
||||||
|
//
|
||||||
|
// - This is going to be used for sending our state event transport (focus_preferred)
|
||||||
|
// - In sticky events it is expected to NOT send this field at all. The transport is only the `type`, `livekit_service_url`
|
||||||
|
// - If we set it to the hased alias we get from the jwt, we will end up using the hashed alias as the body.roomId field
|
||||||
|
// in v0.16.0. (It will use oldest member transport. It is using the transport.livekit_alias as the body.roomId)
|
||||||
|
//
|
||||||
|
// TLDR this is a temporal field that allow for comaptibilty but the spec expects it to not exists. (but its existance also does not break anything)
|
||||||
|
// It is just named poorly: It was intetended to be the actual alias. But now we do pseudonymys ids so we use a hashed alias.
|
||||||
|
livekit_alias: roomId,
|
||||||
},
|
},
|
||||||
sfuConfig,
|
sfuConfig,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -117,6 +117,14 @@ export class Connection {
|
|||||||
*/
|
*/
|
||||||
public readonly remoteParticipants$: Behavior<RemoteParticipant[]>;
|
public readonly remoteParticipants$: Behavior<RemoteParticipant[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The alias of the LiveKit room.
|
||||||
|
*/
|
||||||
|
public get livekitAlias(): string | undefined {
|
||||||
|
return this._livekitAlias;
|
||||||
|
}
|
||||||
|
private _livekitAlias?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the connection has been stopped.
|
* Whether the connection has been stopped.
|
||||||
* @see Connection.stop
|
* @see Connection.stop
|
||||||
@@ -144,9 +152,10 @@ export class Connection {
|
|||||||
this._state$.next(ConnectionState.FetchingConfig);
|
this._state$.next(ConnectionState.FetchingConfig);
|
||||||
// We should already have this information after creating the localTransport.
|
// We should already have this information after creating the localTransport.
|
||||||
// only call getSFUConfigWithOpenID for connections where we do not have a token yet. (existingJwtTokenData === undefined)
|
// only call getSFUConfigWithOpenID for connections where we do not have a token yet. (existingJwtTokenData === undefined)
|
||||||
const { url, jwt } =
|
const { url, jwt, livekitAlias } =
|
||||||
this.existingSFUConfig ??
|
this.existingSFUConfig ??
|
||||||
(await this.getSFUConfigForRemoteConnection());
|
(await this.getSFUConfigForRemoteConnection());
|
||||||
|
this._livekitAlias = livekitAlias;
|
||||||
// If we were stopped while fetching the config, don't proceed to connect
|
// If we were stopped while fetching the config, don't proceed to connect
|
||||||
if (this.stopped) return;
|
if (this.stopped) return;
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export type TaggedParticipant =
|
|||||||
| LocalTaggedParticipant
|
| LocalTaggedParticipant
|
||||||
| RemoteTaggedParticipant;
|
| RemoteTaggedParticipant;
|
||||||
|
|
||||||
interface MatrixLivekitMember {
|
export interface MatrixLivekitMember {
|
||||||
membership$: Behavior<CallMembership>;
|
membership$: Behavior<CallMembership>;
|
||||||
connection$: Behavior<Connection | null>;
|
connection$: Behavior<Connection | null>;
|
||||||
// participantId: string; We do not want a participantId here since it will be generated by the jwt
|
// participantId: string; We do not want a participantId here since it will be generated by the jwt
|
||||||
@@ -143,9 +143,14 @@ export function areLivekitTransportsEqual<T extends LivekitTransport>(
|
|||||||
t1: T | null,
|
t1: T | null,
|
||||||
t2: T | null,
|
t2: T | null,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (t1 && t2) return t1.livekit_service_url === t2.livekit_service_url;
|
if (t1 && t2)
|
||||||
// In case we have different lk rooms in the same SFU (depends on the livekit authorization service)
|
return (
|
||||||
// It is only needed in case the livekit authorization service is not behaving as expected (or custom implementation)
|
t1.livekit_service_url === t2.livekit_service_url &&
|
||||||
|
// In case we have different lk rooms in the same SFU (depends on the livekit authorization service)
|
||||||
|
// It is only needed in case the livekit authorization service is not behaving as expected (or custom implementation)
|
||||||
|
// Also LivekitTransport is planned to become a `ConnectionIdentifier` which moves this equal somewhere else.
|
||||||
|
t1.livekit_alias === t2.livekit_alias
|
||||||
|
);
|
||||||
if (!t1 && !t2) return true;
|
if (!t1 && !t2) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
44
yarn.lock
44
yarn.lock
@@ -2903,16 +2903,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@floating-ui/dom@npm:1.6.13, @floating-ui/dom@npm:^1.0.0":
|
|
||||||
version: 1.6.13
|
|
||||||
resolution: "@floating-ui/dom@npm:1.6.13"
|
|
||||||
dependencies:
|
|
||||||
"@floating-ui/core": "npm:^1.6.0"
|
|
||||||
"@floating-ui/utils": "npm:^0.2.9"
|
|
||||||
checksum: 10c0/272242d2eb6238ffcee0cb1f3c66e0eafae804d5d7b449db5ecf904bc37d31ad96cf575a9e650b93c1190f64f49a684b1559d10e05ed3ec210628b19116991a9
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@floating-ui/dom@npm:1.7.4":
|
"@floating-ui/dom@npm:1.7.4":
|
||||||
version: 1.7.4
|
version: 1.7.4
|
||||||
resolution: "@floating-ui/dom@npm:1.7.4"
|
resolution: "@floating-ui/dom@npm:1.7.4"
|
||||||
@@ -2923,6 +2913,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@floating-ui/dom@npm:^1.0.0":
|
||||||
|
version: 1.6.13
|
||||||
|
resolution: "@floating-ui/dom@npm:1.6.13"
|
||||||
|
dependencies:
|
||||||
|
"@floating-ui/core": "npm:^1.6.0"
|
||||||
|
"@floating-ui/utils": "npm:^0.2.9"
|
||||||
|
checksum: 10c0/272242d2eb6238ffcee0cb1f3c66e0eafae804d5d7b449db5ecf904bc37d31ad96cf575a9e650b93c1190f64f49a684b1559d10e05ed3ec210628b19116991a9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@floating-ui/react-dom@npm:^2.0.0, @floating-ui/react-dom@npm:^2.1.2":
|
"@floating-ui/react-dom@npm:^2.0.0, @floating-ui/react-dom@npm:^2.1.2":
|
||||||
version: 2.1.2
|
version: 2.1.2
|
||||||
resolution: "@floating-ui/react-dom@npm:2.1.2"
|
resolution: "@floating-ui/react-dom@npm:2.1.2"
|
||||||
@@ -3213,21 +3213,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@livekit/components-core@npm:0.12.11":
|
"@livekit/components-core@npm:0.12.12, @livekit/components-core@npm:^0.12.0":
|
||||||
version: 0.12.11
|
|
||||||
resolution: "@livekit/components-core@npm:0.12.11"
|
|
||||||
dependencies:
|
|
||||||
"@floating-ui/dom": "npm:1.6.13"
|
|
||||||
loglevel: "npm:1.9.1"
|
|
||||||
rxjs: "npm:7.8.2"
|
|
||||||
peerDependencies:
|
|
||||||
livekit-client: ^2.15.14
|
|
||||||
tslib: ^2.6.2
|
|
||||||
checksum: 10c0/9c2ac3d30bb8cc9067ae0b2049784f81e90e57df9eabf7edbaf3c8ceb65a63f644a4e6abeb6cc38d3ebe52663d8dbb88535e01a965011f365d5ae1f3daf86052
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@livekit/components-core@npm:^0.12.0":
|
|
||||||
version: 0.12.12
|
version: 0.12.12
|
||||||
resolution: "@livekit/components-core@npm:0.12.12"
|
resolution: "@livekit/components-core@npm:0.12.12"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3242,10 +3228,10 @@ __metadata:
|
|||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@livekit/components-react@npm:^2.0.0":
|
"@livekit/components-react@npm:^2.0.0":
|
||||||
version: 2.9.16
|
version: 2.9.17
|
||||||
resolution: "@livekit/components-react@npm:2.9.16"
|
resolution: "@livekit/components-react@npm:2.9.17"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@livekit/components-core": "npm:0.12.11"
|
"@livekit/components-core": "npm:0.12.12"
|
||||||
clsx: "npm:2.1.1"
|
clsx: "npm:2.1.1"
|
||||||
events: "npm:^3.3.0"
|
events: "npm:^3.3.0"
|
||||||
jose: "npm:^6.0.12"
|
jose: "npm:^6.0.12"
|
||||||
@@ -3259,7 +3245,7 @@ __metadata:
|
|||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
"@livekit/krisp-noise-filter":
|
"@livekit/krisp-noise-filter":
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 10c0/4ba4ff473c5a29d3107412733a6676a3b708d70684ed463e9b34cda26abb3d2f317c2828a52e730837b756de9df3fc248260d6f390aedebfb6ec96ef63c7b151
|
checksum: 10c0/ba64ada37d4b3ce4d5ee7c5b2a6bddbffc17c2e641e95881aac9f02b4ff7428105e0a372d364e50ff124e988b7426d322d94caabdb55b634aebf0144d7e37f99
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user