From 9895a8fb4f526e7bbd609eeae1ef399013f40962 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 2 Oct 2024 15:08:15 +0100 Subject: [PATCH] Disable ICE fallback based on well-known configuration (#111) * Refactor MatrixClientBackedController.ts Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Disable ICE fallback based on well-known configuration Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Add tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/LegacyCallHandler.tsx | 1 - .../tabs/user/VoiceUserSettingsTab.tsx | 6 +- src/settings/Settings.tsx | 2 + .../FallbackIceServerController.ts | 52 +++++++++++++++++ .../MatrixClientBackedController.ts | 8 +-- .../ServerSupportUnstableFeatureController.ts | 8 +-- .../FallbackIceServerController-test.ts | 57 +++++++++++++++++++ 7 files changed, 119 insertions(+), 15 deletions(-) create mode 100644 src/settings/controllers/FallbackIceServerController.ts create mode 100644 test/settings/controllers/FallbackIceServerController-test.ts diff --git a/src/LegacyCallHandler.tsx b/src/LegacyCallHandler.tsx index f509710518..a19a4d4573 100644 --- a/src/LegacyCallHandler.tsx +++ b/src/LegacyCallHandler.tsx @@ -765,7 +765,6 @@ export default class LegacyCallHandler extends EventEmitter { cancelButton: _t("action|ok"), onFinished: (allow) => { SettingsStore.setValue("fallbackICEServerAllowed", null, SettingLevel.DEVICE, allow); - cli.setFallbackICEServerAllowed(!!allow); }, }, undefined, diff --git a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx index 27e59c49d9..2b63336fa7 100644 --- a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx @@ -112,10 +112,6 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> { this.context.setForceTURN(!p2p); }; - private changeFallbackICEServerAllowed = (allow: boolean): void => { - this.context.setFallbackICEServerAllowed(allow); - }; - private renderDeviceOptions(devices: Array, category: MediaDeviceKindEnum): Array { return devices.map((d) => { return ( @@ -226,7 +222,7 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> { server: new URL(FALLBACK_ICE_SERVER).pathname, })} level={SettingLevel.DEVICE} - onChange={this.changeFallbackICEServerAllowed} + hideIfCannotSet /> diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 298102332e..834a735d8d 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -37,6 +37,7 @@ import ServerSupportUnstableFeatureController from "./controllers/ServerSupportU import { WatchManager } from "./WatchManager"; import { CustomTheme } from "../theme"; import AnalyticsController from "./controllers/AnalyticsController"; +import FallbackIceServerController from "./controllers/FallbackIceServerController"; export const defaultWatchManager = new WatchManager(); @@ -980,6 +981,7 @@ export const SETTINGS: { [setting: string]: ISetting } = { description: _td("settings|voip|enable_fallback_ice_server_description"), // This is a tri-state value, where `null` means "prompt the user". default: null, + controller: new FallbackIceServerController(), }, "showImages": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, diff --git a/src/settings/controllers/FallbackIceServerController.ts b/src/settings/controllers/FallbackIceServerController.ts new file mode 100644 index 0000000000..5fd8bee322 --- /dev/null +++ b/src/settings/controllers/FallbackIceServerController.ts @@ -0,0 +1,52 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only +Please see LICENSE files in the repository root for full details. +*/ + +import { ClientEvent, IClientWellKnown, MatrixClient } from "matrix-js-sdk/src/matrix"; + +import { SettingLevel } from "../SettingLevel"; +import SettingsStore from "../SettingsStore.ts"; +import MatrixClientBackedController from "./MatrixClientBackedController.ts"; + +/** + * Settings controller for the fallback ICE server setting. + * This setting may be forcibly disabled by well-known value ["io.element.voip"]["disable_fallback_ice"]. + * This controller will update the MatrixClient's knowledge when the setting is changed. + */ +export default class FallbackIceServerController extends MatrixClientBackedController { + private disabled = false; + + public constructor() { + super(); + } + + private checkWellKnown = (wellKnown: IClientWellKnown): void => { + this.disabled = !!wellKnown["io.element.voip"]?.["disable_fallback_ice"]; + }; + + protected async initMatrixClient(newClient: MatrixClient, oldClient?: MatrixClient): Promise { + oldClient?.off(ClientEvent.ClientWellKnown, this.checkWellKnown); + newClient.on(ClientEvent.ClientWellKnown, this.checkWellKnown); + const wellKnown = newClient.getClientWellKnown(); + if (wellKnown) this.checkWellKnown(wellKnown); + } + + public getValueOverride(): any { + if (this.disabled) { + return false; + } + + return null; // no override + } + + public get settingDisabled(): boolean | string { + return this.disabled; + } + + public onChange(_level: SettingLevel, _roomId: string | null, _newValue: any): void { + this.client?.setFallbackICEServerAllowed(!!SettingsStore.getValue("fallbackICEServerAllowed")); + } +} diff --git a/src/settings/controllers/MatrixClientBackedController.ts b/src/settings/controllers/MatrixClientBackedController.ts index cf4036873b..15cc58e25a 100644 --- a/src/settings/controllers/MatrixClientBackedController.ts +++ b/src/settings/controllers/MatrixClientBackedController.ts @@ -18,7 +18,7 @@ import SettingController from "./SettingController"; * This class performs no logic and should be overridden. */ export default abstract class MatrixClientBackedController extends SettingController { - private static _matrixClient: MatrixClient; + private static _matrixClient?: MatrixClient; private static instances: MatrixClientBackedController[] = []; public static set matrixClient(client: MatrixClient) { @@ -26,7 +26,7 @@ export default abstract class MatrixClientBackedController extends SettingContro MatrixClientBackedController._matrixClient = client; for (const instance of MatrixClientBackedController.instances) { - instance.initMatrixClient(oldClient, client); + instance.initMatrixClient(client, oldClient); } } @@ -36,9 +36,9 @@ export default abstract class MatrixClientBackedController extends SettingContro MatrixClientBackedController.instances.push(this); } - public get client(): MatrixClient { + public get client(): MatrixClient | undefined { return MatrixClientBackedController._matrixClient; } - protected abstract initMatrixClient(oldClient: MatrixClient, newClient: MatrixClient): void; + protected abstract initMatrixClient(newClient: MatrixClient, oldClient?: MatrixClient): void; } diff --git a/src/settings/controllers/ServerSupportUnstableFeatureController.ts b/src/settings/controllers/ServerSupportUnstableFeatureController.ts index 75f20632bd..f2ca2c0b07 100644 --- a/src/settings/controllers/ServerSupportUnstableFeatureController.ts +++ b/src/settings/controllers/ServerSupportUnstableFeatureController.ts @@ -6,8 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ -import { MatrixClient } from "matrix-js-sdk/src/matrix"; - import { SettingLevel } from "../SettingLevel"; import MatrixClientBackedController from "./MatrixClientBackedController"; import { WatchManager } from "../WatchManager"; @@ -53,9 +51,9 @@ export default class ServerSupportUnstableFeatureController extends MatrixClient this.watchers.notifyUpdate(this.settingName, null, level, settingValue); } - protected async initMatrixClient(oldClient: MatrixClient, newClient: MatrixClient): Promise { + protected async initMatrixClient(): Promise { // Check for stable version support first - if (this.stableVersion && (await this.client.isVersionSupported(this.stableVersion))) { + if (this.stableVersion && (await this.client!.isVersionSupported(this.stableVersion))) { this.disabled = false; return; } @@ -66,7 +64,7 @@ export default class ServerSupportUnstableFeatureController extends MatrixClient for (const featureGroup of this.unstableFeatureGroups) { const featureSupportList = await Promise.all( featureGroup.map(async (feature) => { - const isFeatureSupported = await this.client.doesServerSupportUnstableFeature(feature); + const isFeatureSupported = await this.client!.doesServerSupportUnstableFeature(feature); return isFeatureSupported; }), ); diff --git a/test/settings/controllers/FallbackIceServerController-test.ts b/test/settings/controllers/FallbackIceServerController-test.ts new file mode 100644 index 0000000000..3d7f2b70e0 --- /dev/null +++ b/test/settings/controllers/FallbackIceServerController-test.ts @@ -0,0 +1,57 @@ +/* +Copyright 2024 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only +Please see LICENSE files in the repository root for full details. +*/ + +import fetchMockJest from "fetch-mock-jest"; +import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/matrix"; + +import { SettingLevel } from "../../../src/settings/SettingLevel"; +import FallbackIceServerController from "../../../src/settings/controllers/FallbackIceServerController.ts"; +import MatrixClientBackedController from "../../../src/settings/controllers/MatrixClientBackedController.ts"; +import SettingsStore from "../../../src/settings/SettingsStore.ts"; + +describe("FallbackIceServerController", () => { + beforeEach(() => { + fetchMockJest.get("https://matrix.org/_matrix/client/versions", { versions: ["v1.4"] }); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("should update MatrixClient's state when the setting is updated", async () => { + const client = new MatrixClient({ + baseUrl: "https://matrix.org", + userId: "@alice:matrix.org", + accessToken: "token", + }); + MatrixClientBackedController.matrixClient = client; + + expect(client.isFallbackICEServerAllowed()).toBeFalsy(); + await SettingsStore.setValue("fallbackICEServerAllowed", null, SettingLevel.DEVICE, true); + expect(client.isFallbackICEServerAllowed()).toBeTruthy(); + }); + + it("should force the setting to be disabled if disable_fallback_ice=true", async () => { + const controller = new FallbackIceServerController(); + const client = new MatrixClient({ + baseUrl: "https://matrix.org", + userId: "@alice:matrix.org", + accessToken: "token", + }); + MatrixClientBackedController.matrixClient = client; + expect(controller.settingDisabled).toBeFalsy(); + + client["clientWellKnown"] = { + "io.element.voip": { + disable_fallback_ice: true, + }, + }; + client.emit(ClientEvent.ClientWellKnown, client["clientWellKnown"]); + + expect(controller.settingDisabled).toBeTruthy(); + }); +});