From bd3e91738e7849d3d884e9a7e73b5008ee8861cc Mon Sep 17 00:00:00 2001 From: Timo K Date: Thu, 27 Nov 2025 15:06:36 +0100 Subject: [PATCH 01/10] Reset overwrite url if it is invalid (does fail to reach sfu) --- locales/en/app.json | 1 + src/settings/DeveloperSettingsTab.tsx | 9 ++++--- src/settings/settings.ts | 20 ++++++++++++++- .../localMember/LocalTransport.ts | 25 +++++++++++++++---- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/locales/en/app.json b/locales/en/app.json index 1ff066ea..58291027 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -72,6 +72,7 @@ "save": "Save", "saving": "Saving..." }, + "custom_url_update_reason_invalid": "Auto reset, custom url was invalid!", "debug_tile_layout_label": "Debug tile layout", "device_id": "Device ID: {{id}}", "duplicate_tiles_label": "Number of additional tile copies per participant", diff --git a/src/settings/DeveloperSettingsTab.tsx b/src/settings/DeveloperSettingsTab.tsx index 254aaf0f..77775ad7 100644 --- a/src/settings/DeveloperSettingsTab.tsx +++ b/src/settings/DeveloperSettingsTab.tsx @@ -41,6 +41,7 @@ import { matrixRTCMode as matrixRTCModeSetting, customLivekitUrl as customLivekitUrlSetting, MatrixRTCMode, + useSettingWithLastUpdateReason, } from "./settings"; import type { Room as LivekitRoom } from "livekit-client"; import styles from "./DeveloperSettingsTab.module.css"; @@ -92,9 +93,8 @@ export const DeveloperSettingsTab: FC = ({ alwaysShowIphoneEarpieceSetting, ); - const [customLivekitUrl, setCustomLivekitUrl] = useSetting( - customLivekitUrlSetting, - ); + const [customLivekitUrl, setCustomLivekitUrl, customLivekitUrlUpdateReason] = + useSettingWithLastUpdateReason(customLivekitUrlSetting); const [customLivekitUrlTextBuffer, setCustomLivekitUrlTextBuffer] = useState(customLivekitUrl); useEffect(() => { @@ -220,7 +220,8 @@ export const DeveloperSettingsTab: FC = ({ onSubmit={(e) => e.preventDefault()} helpLabel={ customLivekitUrl === null - ? t("developer_mode.custom_livekit_url.from_config") + ? t("developer_mode.custom_livekit_url.from_config") + + (customLivekitUrlUpdateReason ?? "") : t("developer_mode.custom_livekit_url.current_url") + customLivekitUrl } diff --git a/src/settings/settings.ts b/src/settings/settings.ts index f85e1414..85af4579 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -34,15 +34,20 @@ export class Setting { this._value$ = new BehaviorSubject(initialValue); this.value$ = this._value$; + this._lastUpdateReason$ = new BehaviorSubject(null); + this.lastUpdateReason$ = this._lastUpdateReason$; } private readonly key: string; private readonly _value$: BehaviorSubject; + private readonly _lastUpdateReason$: BehaviorSubject; public readonly value$: Behavior; + public readonly lastUpdateReason$: Behavior; - public readonly setValue = (value: T): void => { + public readonly setValue = (value: T, reason?: string): void => { this._value$.next(value); + this._lastUpdateReason$.next(reason ?? null); localStorage.setItem(this.key, JSON.stringify(value)); }; public readonly getValue = (): T => { @@ -57,6 +62,19 @@ export function useSetting(setting: Setting): [T, (value: T) => void] { return [useBehavior(setting.value$), setting.setValue]; } +/** + * React hook that returns a settings's current value and a setter. + */ +export function useSettingWithLastUpdateReason( + setting: Setting, +): [T, (value: T) => void, string | null] { + return [ + useBehavior(setting.value$), + setting.setValue, + useBehavior(setting.lastUpdateReason$), + ]; +} + // null = undecided export const optInAnalytics = new Setting( "opt-in-analytics", diff --git a/src/state/CallViewModel/localMember/LocalTransport.ts b/src/state/CallViewModel/localMember/LocalTransport.ts index 0a85bbc1..8926e97b 100644 --- a/src/state/CallViewModel/localMember/LocalTransport.ts +++ b/src/state/CallViewModel/localMember/LocalTransport.ts @@ -23,6 +23,7 @@ import { } from "rxjs"; import { logger as rootLogger } from "matrix-js-sdk/lib/logger"; import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery"; +import { t } from "i18next"; import { type Behavior } from "../../Behavior.ts"; import { type Epoch, type ObservableScope } from "../../ObservableScope.ts"; @@ -178,11 +179,25 @@ async function makeTransport( if (!transport) throw new MatrixRTCTransportMissingError(domain ?? ""); // this will call the jwt/sfu/get endpoint to pre create the livekit room. - await getSFUConfigWithOpenID( - client, - transport.livekit_service_url, - transport.livekit_alias, - ); + try { + await getSFUConfigWithOpenID( + client, + transport.livekit_service_url, + transport.livekit_alias, + ); + } catch (e) { + if (urlFromDevSettings !== undefined) { + logger.error( + "Failed to get SFU config with dev settings overwrite, Resetting dev settings", + ); + customLivekitUrl.setValue( + null, + `\n${t("developer_mode.custom_url_update_reason_invalid")}`, + ); + } else { + throw e; + } + } return transport; } From ed4517703ff235f8667aa8e4c41c772e78df0c4c Mon Sep 17 00:00:00 2001 From: Timo K Date: Mon, 1 Dec 2025 19:33:51 +0100 Subject: [PATCH 02/10] better UX (valiate on save) --- src/settings/DeveloperSettingsTab.tsx | 58 +++++++++++++++++++++------ src/settings/settings.ts | 13 ------ 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/src/settings/DeveloperSettingsTab.tsx b/src/settings/DeveloperSettingsTab.tsx index 77775ad7..7f90c4c1 100644 --- a/src/settings/DeveloperSettingsTab.tsx +++ b/src/settings/DeveloperSettingsTab.tsx @@ -28,6 +28,7 @@ import { InlineField, Label, RadioControl, + Text, } from "@vector-im/compound-web"; import { FieldRow, InputField } from "../input/Input"; @@ -41,11 +42,11 @@ import { matrixRTCMode as matrixRTCModeSetting, customLivekitUrl as customLivekitUrlSetting, MatrixRTCMode, - useSettingWithLastUpdateReason, } from "./settings"; import type { Room as LivekitRoom } from "livekit-client"; import styles from "./DeveloperSettingsTab.module.css"; import { useUrlParams } from "../UrlParams"; +import { getSFUConfigWithOpenID } from "../livekit/openIDSFU"; interface Props { client: MatrixClient; @@ -93,8 +94,11 @@ export const DeveloperSettingsTab: FC = ({ alwaysShowIphoneEarpieceSetting, ); - const [customLivekitUrl, setCustomLivekitUrl, customLivekitUrlUpdateReason] = - useSettingWithLastUpdateReason(customLivekitUrlSetting); + const [customLivekitUrlUpdateError, setCustomLivekitUrlUpdateError] = + useState(null); + const [customLivekitUrl, setCustomLivekitUrl] = useSetting( + customLivekitUrlSetting, + ); const [customLivekitUrlTextBuffer, setCustomLivekitUrlTextBuffer] = useState(customLivekitUrl); useEffect(() => { @@ -217,11 +221,11 @@ export const DeveloperSettingsTab: FC = ({ />{" "} e.preventDefault()} helpLabel={ customLivekitUrl === null - ? t("developer_mode.custom_livekit_url.from_config") + - (customLivekitUrlUpdateReason ?? "") + ? t("developer_mode.custom_livekit_url.from_config") : t("developer_mode.custom_livekit_url.current_url") + customLivekitUrl } @@ -230,14 +234,36 @@ export const DeveloperSettingsTab: FC = ({ savingLabel={t("developer_mode.custom_livekit_url.saving")} cancelButtonLabel={t("developer_mode.custom_livekit_url.reset")} onSave={useCallback( - (e: React.FormEvent) => { - setCustomLivekitUrl( - customLivekitUrlTextBuffer === "" - ? null - : customLivekitUrlTextBuffer, - ); + async (e: React.FormEvent): Promise => { + if ( + customLivekitUrlTextBuffer === "" || + customLivekitUrlTextBuffer === null + ) { + setCustomLivekitUrl(null); + return Promise.resolve(); + } + + try { + logger.debug("try setting"); + await getSFUConfigWithOpenID( + client, + customLivekitUrlTextBuffer, + "Test-room-alias-" + Date.now().toString() + client.getUserId(), + ); + logger.debug("done setting! Success"); + setCustomLivekitUrlUpdateError(null); + setCustomLivekitUrl(customLivekitUrlTextBuffer); + } catch (e) { + logger.error("failed setting", e); + setCustomLivekitUrlUpdateError("invalid URL (did not update)"); + // automatically unset the error after 4 seconds (2 seconds will be for the save label) + setTimeout(() => { + logger.debug("unsetting error"); + setCustomLivekitUrlUpdateError(null); + }, 2000); + } }, - [setCustomLivekitUrl, customLivekitUrlTextBuffer], + [customLivekitUrlTextBuffer, setCustomLivekitUrl, client], )} value={customLivekitUrlTextBuffer ?? ""} onChange={useCallback( @@ -252,7 +278,13 @@ export const DeveloperSettingsTab: FC = ({ }, [setCustomLivekitUrl], )} - /> + > + {customLivekitUrlUpdateError !== null && ( + + {customLivekitUrlUpdateError} + + )} + {t("developer_mode.matrixRTCMode.title")} diff --git a/src/settings/settings.ts b/src/settings/settings.ts index 85af4579..03008dca 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -62,19 +62,6 @@ export function useSetting(setting: Setting): [T, (value: T) => void] { return [useBehavior(setting.value$), setting.setValue]; } -/** - * React hook that returns a settings's current value and a setter. - */ -export function useSettingWithLastUpdateReason( - setting: Setting, -): [T, (value: T) => void, string | null] { - return [ - useBehavior(setting.value$), - setting.setValue, - useBehavior(setting.lastUpdateReason$), - ]; -} - // null = undecided export const optInAnalytics = new Setting( "opt-in-analytics", From 5ceb140d13899bc4235a1626ed358c2be6da34fa Mon Sep 17 00:00:00 2001 From: Timo K Date: Mon, 1 Dec 2025 19:50:52 +0100 Subject: [PATCH 03/10] remove check in local transport --- .../localMember/LocalTransport.ts | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/state/CallViewModel/localMember/LocalTransport.ts b/src/state/CallViewModel/localMember/LocalTransport.ts index 8926e97b..0a85bbc1 100644 --- a/src/state/CallViewModel/localMember/LocalTransport.ts +++ b/src/state/CallViewModel/localMember/LocalTransport.ts @@ -23,7 +23,6 @@ import { } from "rxjs"; import { logger as rootLogger } from "matrix-js-sdk/lib/logger"; import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery"; -import { t } from "i18next"; import { type Behavior } from "../../Behavior.ts"; import { type Epoch, type ObservableScope } from "../../ObservableScope.ts"; @@ -179,25 +178,11 @@ async function makeTransport( if (!transport) throw new MatrixRTCTransportMissingError(domain ?? ""); // this will call the jwt/sfu/get endpoint to pre create the livekit room. - try { - await getSFUConfigWithOpenID( - client, - transport.livekit_service_url, - transport.livekit_alias, - ); - } catch (e) { - if (urlFromDevSettings !== undefined) { - logger.error( - "Failed to get SFU config with dev settings overwrite, Resetting dev settings", - ); - customLivekitUrl.setValue( - null, - `\n${t("developer_mode.custom_url_update_reason_invalid")}`, - ); - } else { - throw e; - } - } + await getSFUConfigWithOpenID( + client, + transport.livekit_service_url, + transport.livekit_alias, + ); return transport; } From c20b206ab2a05274350ec5b0936454c05b6cb70c Mon Sep 17 00:00:00 2001 From: Timo K Date: Mon, 1 Dec 2025 19:53:27 +0100 Subject: [PATCH 04/10] i18n --- locales/en/app.json | 1 - 1 file changed, 1 deletion(-) diff --git a/locales/en/app.json b/locales/en/app.json index 58291027..1ff066ea 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -72,7 +72,6 @@ "save": "Save", "saving": "Saving..." }, - "custom_url_update_reason_invalid": "Auto reset, custom url was invalid!", "debug_tile_layout_label": "Debug tile layout", "device_id": "Device ID: {{id}}", "duplicate_tiles_label": "Number of additional tile copies per participant", From df2f503a05d4e9f45cb7a2e01204cb83c7a2396b Mon Sep 17 00:00:00 2001 From: Timo K Date: Thu, 11 Dec 2025 18:00:23 +0100 Subject: [PATCH 05/10] review --- src/settings/DeveloperSettingsTab.test.tsx | 1 + src/settings/DeveloperSettingsTab.tsx | 26 ++++++++-------------- src/settings/SettingsModal.tsx | 1 + 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/settings/DeveloperSettingsTab.test.tsx b/src/settings/DeveloperSettingsTab.test.tsx index c18cf23b..7bdf7091 100644 --- a/src/settings/DeveloperSettingsTab.test.tsx +++ b/src/settings/DeveloperSettingsTab.test.tsx @@ -79,6 +79,7 @@ describe("DeveloperSettingsTab", () => { const { container } = render( , diff --git a/src/settings/DeveloperSettingsTab.tsx b/src/settings/DeveloperSettingsTab.tsx index 7f90c4c1..d16e3ef7 100644 --- a/src/settings/DeveloperSettingsTab.tsx +++ b/src/settings/DeveloperSettingsTab.tsx @@ -22,13 +22,13 @@ import { import { logger } from "matrix-js-sdk/lib/logger"; import { EditInPlace, + ErrorMessage, Root as Form, Heading, HelpMessage, InlineField, Label, RadioControl, - Text, } from "@vector-im/compound-web"; import { FieldRow, InputField } from "../input/Input"; @@ -50,6 +50,7 @@ import { getSFUConfigWithOpenID } from "../livekit/openIDSFU"; interface Props { client: MatrixClient; + roomId: string; livekitRooms?: { room: LivekitRoom; url: string; isLocal?: boolean }[]; env: ImportMetaEnv; } @@ -57,6 +58,7 @@ interface Props { export const DeveloperSettingsTab: FC = ({ client, livekitRooms, + roomId, env, }) => { const { t } = useTranslation(); @@ -221,7 +223,6 @@ export const DeveloperSettingsTab: FC = ({ />{" "} e.preventDefault()} helpLabel={ customLivekitUrl === null @@ -240,30 +241,22 @@ export const DeveloperSettingsTab: FC = ({ customLivekitUrlTextBuffer === null ) { setCustomLivekitUrl(null); - return Promise.resolve(); + return; } try { - logger.debug("try setting"); await getSFUConfigWithOpenID( client, customLivekitUrlTextBuffer, - "Test-room-alias-" + Date.now().toString() + client.getUserId(), + roomId, ); - logger.debug("done setting! Success"); setCustomLivekitUrlUpdateError(null); setCustomLivekitUrl(customLivekitUrlTextBuffer); - } catch (e) { - logger.error("failed setting", e); + } catch { setCustomLivekitUrlUpdateError("invalid URL (did not update)"); - // automatically unset the error after 4 seconds (2 seconds will be for the save label) - setTimeout(() => { - logger.debug("unsetting error"); - setCustomLivekitUrlUpdateError(null); - }, 2000); } }, - [customLivekitUrlTextBuffer, setCustomLivekitUrl, client], + [customLivekitUrlTextBuffer, setCustomLivekitUrl, client, roomId], )} value={customLivekitUrlTextBuffer ?? ""} onChange={useCallback( @@ -278,11 +271,10 @@ export const DeveloperSettingsTab: FC = ({ }, [setCustomLivekitUrl], )} + serverInvalid={customLivekitUrlUpdateError !== null} > {customLivekitUrlUpdateError !== null && ( - - {customLivekitUrlUpdateError} - + {customLivekitUrlUpdateError} )} diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx index 2b4078aa..30ac3618 100644 --- a/src/settings/SettingsModal.tsx +++ b/src/settings/SettingsModal.tsx @@ -213,6 +213,7 @@ export const SettingsModal: FC = ({ env={import.meta.env} client={client} livekitRooms={livekitRooms} + roomId={roomId} /> ), }; From c35b96062757bfeb5ff1a7c6786ec947ba8198c6 Mon Sep 17 00:00:00 2001 From: Timo K Date: Mon, 5 Jan 2026 10:42:08 +0100 Subject: [PATCH 06/10] fix lints --- src/settings/DeveloperSettingsTab.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/settings/DeveloperSettingsTab.tsx b/src/settings/DeveloperSettingsTab.tsx index d16e3ef7..0b77f095 100644 --- a/src/settings/DeveloperSettingsTab.tsx +++ b/src/settings/DeveloperSettingsTab.tsx @@ -50,7 +50,7 @@ import { getSFUConfigWithOpenID } from "../livekit/openIDSFU"; interface Props { client: MatrixClient; - roomId: string; + roomId?: string; livekitRooms?: { room: LivekitRoom; url: string; isLocal?: boolean }[]; env: ImportMetaEnv; } @@ -237,6 +237,7 @@ export const DeveloperSettingsTab: FC = ({ onSave={useCallback( async (e: React.FormEvent): Promise => { if ( + roomId === undefined || customLivekitUrlTextBuffer === "" || customLivekitUrlTextBuffer === null ) { From eb604735108d5bf6197d01ddb4256f5f9f349332 Mon Sep 17 00:00:00 2001 From: Timo K Date: Mon, 5 Jan 2026 14:17:25 +0100 Subject: [PATCH 07/10] add tests --- src/settings/DeveloperSettingsTab.test.tsx | 196 ++++++++++++++++++--- 1 file changed, 172 insertions(+), 24 deletions(-) diff --git a/src/settings/DeveloperSettingsTab.test.tsx b/src/settings/DeveloperSettingsTab.test.tsx index 7bdf7091..51255666 100644 --- a/src/settings/DeveloperSettingsTab.test.tsx +++ b/src/settings/DeveloperSettingsTab.test.tsx @@ -5,13 +5,17 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ -import { describe, expect, it, vi } from "vitest"; -import { render, waitFor } from "@testing-library/react"; +import { afterEach, describe, expect, it, type Mock, vi } from "vitest"; +import { render, waitFor, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { TooltipProvider } from "@vector-im/compound-web"; import type { MatrixClient } from "matrix-js-sdk"; import type { Room as LivekitRoom } from "livekit-client"; +// import { DeveloperSettingsTab } from "./DeveloperSettingsTab"; import { DeveloperSettingsTab } from "./DeveloperSettingsTab"; - +import { getSFUConfigWithOpenID } from "../livekit/openIDSFU"; +import { customLivekitUrl as customLivekitUrlSetting } from "./settings"; // Mock url params hook to avoid environment-dependent snapshot churn. vi.mock("../UrlParams", () => ({ useUrlParams: (): { mocked: boolean; answer: number } => ({ @@ -20,6 +24,14 @@ vi.mock("../UrlParams", () => ({ }), })); +// IMPORTANT: mock the same specifier used by DeveloperSettingsTab +vi.mock("../livekit/openIDSFU", () => ({ + getSFUConfigWithOpenID: vi.fn().mockResolvedValue({ + url: "mock-url", + jwt: "mock-jwt", + }), +})); + // Provide a minimal mock of a Livekit Room structure used by the component. function createMockLivekitRoom( wsUrl: string, @@ -52,30 +64,30 @@ function createMockMatrixClient(): MatrixClient { } describe("DeveloperSettingsTab", () => { + const livekitRooms: { + room: LivekitRoom; + url: string; + isLocal?: boolean; + }[] = [ + createMockLivekitRoom( + "wss://local-sfu.example.org", + { region: "local", version: "1.2.3" }, + "local-metadata", + ), + { + isLocal: false, + url: "wss://remote-sfu.example.org", + room: { + serverInfo: { region: "remote", version: "4.5.6" }, + metadata: "remote-metadata", + engine: { client: { ws: { url: "wss://remote-sfu.example.org" } } }, + } as unknown as LivekitRoom, + }, + ]; + it("renders and matches snapshot", async () => { const client = createMockMatrixClient(); - const livekitRooms: { - room: LivekitRoom; - url: string; - isLocal?: boolean; - }[] = [ - createMockLivekitRoom( - "wss://local-sfu.example.org", - { region: "local", version: "1.2.3" }, - "local-metadata", - ), - { - isLocal: false, - url: "wss://remote-sfu.example.org", - room: { - serverInfo: { region: "remote", version: "4.5.6" }, - metadata: "remote-metadata", - engine: { client: { ws: { url: "wss://remote-sfu.example.org" } } }, - } as unknown as LivekitRoom, - }, - ]; - const { container } = render( { expect(container).toMatchSnapshot(); }); + describe("custom livekit url", () => { + afterEach(() => { + customLivekitUrlSetting.setValue(null); + }); + const client = { + doesServerSupportUnstableFeature: vi.fn().mockResolvedValue(true), + getCrypto: () => ({ getVersion: (): string => "x" }), + getUserId: () => "@u:hs", + getDeviceId: () => "DEVICE", + } as unknown as MatrixClient; + it("will not update custom livekit url without roomId", async () => { + const user = userEvent.setup(); + + render( + + + , + ); + + const input = screen.getByLabelText("Custom Livekit-url"); + await user.clear(input); + await user.type(input, "wss://example.livekit.invalid"); + + const saveButton = screen.getByRole("button", { name: "Save" }); + await user.click(saveButton); + expect(getSFUConfigWithOpenID).not.toHaveBeenCalled(); + + expect(customLivekitUrlSetting.getValue()).toBe(null); + }); + it("will not update custom livekit url without text in input", async () => { + const user = userEvent.setup(); + + render( + + + , + ); + + const input = screen.getByLabelText("Custom Livekit-url"); + await user.clear(input); + + const saveButton = screen.getByRole("button", { name: "Save" }); + await user.click(saveButton); + expect(getSFUConfigWithOpenID).not.toHaveBeenCalled(); + + expect(customLivekitUrlSetting.getValue()).toBe(null); + }); + it("will not update custom livekit url when pressing cancel", async () => { + const user = userEvent.setup(); + + render( + + + , + ); + + const input = screen.getByLabelText("Custom Livekit-url"); + await user.clear(input); + await user.type(input, "wss://example.livekit.invalid"); + + const cancelButton = screen.getByRole("button", { + name: "Reset overwrite", + }); + await user.click(cancelButton); + expect(getSFUConfigWithOpenID).not.toHaveBeenCalled(); + + expect(customLivekitUrlSetting.getValue()).toBe(null); + }); + it("will update custom livekit url", async () => { + const user = userEvent.setup(); + + render( + + + , + ); + + const input = screen.getByLabelText("Custom Livekit-url"); + await user.clear(input); + await user.type(input, "wss://example.livekit.valid"); + + const saveButton = screen.getByRole("button", { name: "Save" }); + await user.click(saveButton); + expect(getSFUConfigWithOpenID).toHaveBeenCalledWith( + expect.anything(), + "wss://example.livekit.valid", + "#testRoom", + ); + + expect(customLivekitUrlSetting.getValue()).toBe( + "wss://example.livekit.valid", + ); + }); + it("will show error on invalid url", async () => { + const user = userEvent.setup(); + + render( + + + , + ); + + const input = screen.getByLabelText("Custom Livekit-url"); + await user.clear(input); + await user.type(input, "wss://example.livekit.valid"); + + const saveButton = screen.getByRole("button", { name: "Save" }); + (getSFUConfigWithOpenID as Mock).mockImplementation(() => { + throw new Error("Invalid URL"); + }); + await user.click(saveButton); + expect( + screen.getByText("invalid URL (did not update)"), + ).toBeInTheDocument(); + expect(customLivekitUrlSetting.getValue()).toBe(null); + }); + }); }); From 636f737cd3e5937e5bc0451943c216a415f204a3 Mon Sep 17 00:00:00 2001 From: Timo K Date: Tue, 13 Jan 2026 18:05:05 +0100 Subject: [PATCH 08/10] reduce diff --- src/settings/DeveloperSettingsTab.test.tsx | 46 +++++++++++----------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/settings/DeveloperSettingsTab.test.tsx b/src/settings/DeveloperSettingsTab.test.tsx index 9e3ae0c2..4820d14c 100644 --- a/src/settings/DeveloperSettingsTab.test.tsx +++ b/src/settings/DeveloperSettingsTab.test.tsx @@ -65,32 +65,32 @@ function createMockMatrixClient(): MatrixClient { } describe("DeveloperSettingsTab", () => { - const livekitRooms: { - room: LivekitRoom; - url: string; - isLocal?: boolean; - }[] = [ - createMockLivekitRoom( - "wss://local-sfu.example.org", - { region: "local", version: "1.2.3" }, - "local-metadata", - ), - { - isLocal: false, - url: "wss://remote-sfu.example.org", - room: { - localParticipant: { identity: "localParticipantIdentity" }, - remoteParticipants: new Map(), - serverInfo: { region: "remote", version: "4.5.6" }, - metadata: "remote-metadata", - engine: { client: { ws: { url: "wss://remote-sfu.example.org" } } }, - } as unknown as LivekitRoom, - }, - ]; - it("renders and matches snapshot", async () => { const client = createMockMatrixClient(); + const livekitRooms: { + room: LivekitRoom; + url: string; + isLocal?: boolean; + }[] = [ + createMockLivekitRoom( + "wss://local-sfu.example.org", + { region: "local", version: "1.2.3" }, + "local-metadata", + ), + { + isLocal: false, + url: "wss://remote-sfu.example.org", + room: { + localParticipant: { identity: "localParticipantIdentity" }, + remoteParticipants: new Map(), + serverInfo: { region: "remote", version: "4.5.6" }, + metadata: "remote-metadata", + engine: { client: { ws: { url: "wss://remote-sfu.example.org" } } }, + } as unknown as LivekitRoom, + }, + ]; + const { container } = render( Date: Tue, 13 Jan 2026 18:17:37 +0100 Subject: [PATCH 09/10] fix lint --- src/settings/DeveloperSettingsTab.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/settings/DeveloperSettingsTab.tsx b/src/settings/DeveloperSettingsTab.tsx index fe539571..d10e3362 100644 --- a/src/settings/DeveloperSettingsTab.tsx +++ b/src/settings/DeveloperSettingsTab.tsx @@ -246,8 +246,15 @@ export const DeveloperSettingsTab: FC = ({ } try { + const userId = client.getUserId(); + const deviceId = client.getDeviceId(); + + if (userId === null || deviceId === null) { + throw new Error("Invalid user or device ID"); + } await getSFUConfigWithOpenID( client, + { userId, deviceId, memberId: "" }, customLivekitUrlTextBuffer, roomId, ); From c376d9e5c1e37a310ee0c9c21edf4fece22ed930 Mon Sep 17 00:00:00 2001 From: Timo K Date: Fri, 16 Jan 2026 13:12:31 +0100 Subject: [PATCH 10/10] fix tests --- src/settings/DeveloperSettingsTab.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/settings/DeveloperSettingsTab.test.tsx b/src/settings/DeveloperSettingsTab.test.tsx index 1ddedc14..77ea81d6 100644 --- a/src/settings/DeveloperSettingsTab.test.tsx +++ b/src/settings/DeveloperSettingsTab.test.tsx @@ -210,6 +210,7 @@ describe("DeveloperSettingsTab", () => { const saveButton = screen.getByRole("button", { name: "Save" }); await user.click(saveButton); expect(getSFUConfigWithOpenID).toHaveBeenCalledWith( + expect.anything(), expect.anything(), "wss://example.livekit.valid", "#testRoom",