Merge upstream/livekit into branch

This commit is contained in:
Ryan Emmick
2026-06-11 22:11:21 -05:00
191 changed files with 13413 additions and 4649 deletions

View File

@@ -14,7 +14,14 @@ import type { MatrixClient } from "matrix-js-sdk";
import type { Room as LivekitRoom } from "livekit-client";
import { DeveloperSettingsTab } from "./DeveloperSettingsTab";
import { getSFUConfigWithOpenID } from "../livekit/openIDSFU";
import { customLivekitUrl as customLivekitUrlSetting } from "./settings";
import {
customLivekitUrl as customLivekitUrlSetting,
enableExtendedLivekitLogs as enableExtendedLivekitLogsSetting,
matrixRTCMode as matrixRTCModeSetting,
} from "./settings";
import { MatrixRTCMode } from "../config/ConfigOptions";
import { mockConfig } from "../utils/test";
// Mock url params hook to avoid environment-dependent snapshot churn.
vi.mock("../UrlParams", () => ({
useUrlParams: (): { mocked: boolean; answer: number } => ({
@@ -248,4 +255,161 @@ describe("DeveloperSettingsTab", () => {
expect(customLivekitUrlSetting.getValue()).toBe(null);
});
});
// Add this test inside the describe("DeveloperSettingsTab", () => { block,
// after the custom livekit url tests:
describe("enable extended livekit logs", () => {
afterEach(() => {
enableExtendedLivekitLogsSetting.setValue(false);
});
it("toggles extended livekit logs setting", async () => {
const user = userEvent.setup();
const client = createMockMatrixClient();
render(
<TooltipProvider>
<DeveloperSettingsTab
client={client}
env={{} as unknown as ImportMetaEnv}
/>
</TooltipProvider>,
);
const checkbox = screen.getByLabelText("Enable extended livekit logs");
// Initial state should be unchecked (default false)
expect(checkbox).not.toBeChecked();
expect(enableExtendedLivekitLogsSetting.getValue()).toBe(false);
// Click to enable
await user.click(checkbox);
expect(checkbox).toBeChecked();
expect(enableExtendedLivekitLogsSetting.getValue()).toBe(true);
// Click to disable
await user.click(checkbox);
expect(checkbox).not.toBeChecked();
expect(enableExtendedLivekitLogsSetting.getValue()).toBe(false);
});
it("Use the current setting value on render", () => {
const client = createMockMatrixClient();
// Set the value to true before rendering
enableExtendedLivekitLogsSetting.setValue(true);
render(
<TooltipProvider>
<DeveloperSettingsTab
client={client}
env={{} as unknown as ImportMetaEnv}
/>
</TooltipProvider>,
);
const checkbox = screen.getByLabelText("Enable extended livekit logs");
expect(checkbox).toBeChecked();
expect(enableExtendedLivekitLogsSetting.getValue()).toBe(true);
});
});
describe("matrix rtc mode", () => {
afterEach(() => {
matrixRTCModeSetting.setValue(MatrixRTCMode.Legacy);
vi.restoreAllMocks();
});
function getModeRadios(): {
legacy: HTMLInputElement;
compatibility: HTMLInputElement;
matrix20: HTMLInputElement;
} {
return {
legacy: screen.getByDisplayValue(
MatrixRTCMode.Legacy,
) as HTMLInputElement,
compatibility: screen.getByDisplayValue(
MatrixRTCMode.Compatibility,
) as HTMLInputElement,
matrix20: screen.getByDisplayValue(
MatrixRTCMode.Matrix_2_0,
) as HTMLInputElement,
};
}
it("radios reflect the localStorage setting when config does not force the mode", async () => {
mockConfig({});
matrixRTCModeSetting.setValue(MatrixRTCMode.Compatibility);
const client = createMockMatrixClient();
render(
<TooltipProvider>
<DeveloperSettingsTab
client={client}
env={{} as unknown as ImportMetaEnv}
/>
</TooltipProvider>,
);
await waitFor(() =>
expect(client.doesServerSupportUnstableFeature).toHaveBeenCalled(),
);
const radios = getModeRadios();
expect(radios.compatibility).toBeChecked();
expect(radios.legacy).not.toBeChecked();
expect(radios.matrix20).not.toBeChecked();
// None are disabled by config; only Matrix_2_0 may be disabled by sticky-events support.
expect(radios.legacy).not.toBeDisabled();
expect(radios.compatibility).not.toBeDisabled();
});
it.each([
MatrixRTCMode.Legacy,
MatrixRTCMode.Compatibility,
MatrixRTCMode.Matrix_2_0,
])(
"disables all radios and shows the config value (%s) as checked when matrix_rtc_mode is set",
async (configMode) => {
mockConfig({ matrix_rtc_mode: configMode });
// Local setting is intentionally different from the config value to
// prove config wins.
matrixRTCModeSetting.setValue(
configMode === MatrixRTCMode.Legacy
? MatrixRTCMode.Compatibility
: MatrixRTCMode.Legacy,
);
const client = createMockMatrixClient();
render(
<TooltipProvider>
<DeveloperSettingsTab
client={client}
env={{} as unknown as ImportMetaEnv}
/>
</TooltipProvider>,
);
await waitFor(() =>
expect(client.doesServerSupportUnstableFeature).toHaveBeenCalled(),
);
const radios = getModeRadios();
expect(radios.legacy).toBeDisabled();
expect(radios.compatibility).toBeDisabled();
expect(radios.matrix20).toBeDisabled();
const checkedValue = (
{
[MatrixRTCMode.Legacy]: radios.legacy,
[MatrixRTCMode.Compatibility]: radios.compatibility,
[MatrixRTCMode.Matrix_2_0]: radios.matrix20,
} as const
)[configMode];
expect(checkedValue).toBeChecked();
},
);
});
});

View File

@@ -35,6 +35,7 @@ import {
import { type Room as LivekitRoom } from "livekit-client";
import { FieldRow, InputField } from "../input/Input";
import { Config } from "../config/Config";
import {
type Setting,
useSetting,
@@ -45,7 +46,6 @@ import {
alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting,
matrixRTCMode as matrixRTCModeSetting,
customLivekitUrl as customLivekitUrlSetting,
MatrixRTCMode,
advancedScreenShare as advancedScreenShareSetting,
screenShareResolution as screenShareResolutionSetting,
screenShareFramerate as screenShareFramerateSetting,
@@ -60,7 +60,9 @@ import {
noiseSuppressionSetting,
autoGainControlSetting,
type VideoCodec,
enableExtendedLivekitLogs as enableExtendedLivekitLogsSetting,
} from "./settings";
import { MatrixRTCMode } from "../config/ConfigOptions";
import styles from "./DeveloperSettingsTab.module.css";
import settingsStyles from "./SettingsModal.module.css";
import { Slider } from "../Slider";
@@ -111,6 +113,11 @@ export const DeveloperSettingsTab: FC<Props> = ({
},
[setMatrixRTCMode],
);
const configMatrixRTCMode = Config.get().matrix_rtc_mode as
| MatrixRTCMode
| undefined;
const matrixRTCModeForced = configMatrixRTCMode !== undefined;
const effectiveMatrixRTCMode = configMatrixRTCMode ?? matrixRTCMode;
const [showConnectionStats, setShowConnectionStats] = useSetting(
showConnectionStatsSetting,
@@ -120,6 +127,10 @@ export const DeveloperSettingsTab: FC<Props> = ({
alwaysShowIphoneEarpieceSetting,
);
const [enableExtendedLivekitLogs, setEnableExtendedLivekitLogs] = useSetting(
enableExtendedLivekitLogsSetting,
);
const [customLivekitUrlUpdateError, setCustomLivekitUrlUpdateError] =
useState<string | null>(null);
const [customLivekitUrl, setCustomLivekitUrl] = useSetting(
@@ -423,7 +434,21 @@ export const DeveloperSettingsTab: FC<Props> = ({
},
[setAlwaysShowIphoneEarpiece],
)}
/>{" "}
/>
</FieldRow>
<FieldRow>
<InputField
id="enableLivekitExtendedLogs"
type="checkbox"
label="Enable extended livekit logs"
checked={enableExtendedLivekitLogs}
onChange={useCallback(
(event: ChangeEvent<HTMLInputElement>): void => {
setEnableExtendedLivekitLogs(event.target.checked);
},
[setEnableExtendedLivekitLogs],
)}
/>
</FieldRow>
<EditInPlace
onSubmit={(e) => e.preventDefault()}
@@ -491,13 +516,15 @@ export const DeveloperSettingsTab: FC<Props> = ({
<Heading as="h3" type="body" weight="semibold" size="lg">
{t("developer_mode.matrixRTCMode.title")}
</Heading>
{matrixRTCModeForced && <p>Your deployment overrides the mode.</p>}
<Form>
<InlineField
name={matrixRTCModeRadioGroup}
control={
<RadioControl
checked={matrixRTCMode === MatrixRTCMode.Legacy}
checked={effectiveMatrixRTCMode === MatrixRTCMode.Legacy}
value={MatrixRTCMode.Legacy}
disabled={matrixRTCModeForced}
onChange={onMatrixRTCModeChange}
/>
}
@@ -511,8 +538,9 @@ export const DeveloperSettingsTab: FC<Props> = ({
name={matrixRTCModeRadioGroup}
control={
<RadioControl
checked={matrixRTCMode === MatrixRTCMode.Compatibility}
checked={effectiveMatrixRTCMode === MatrixRTCMode.Compatibility}
value={MatrixRTCMode.Compatibility}
disabled={matrixRTCModeForced}
onChange={onMatrixRTCModeChange}
/>
}
@@ -526,9 +554,9 @@ export const DeveloperSettingsTab: FC<Props> = ({
name={matrixRTCModeRadioGroup}
control={
<RadioControl
checked={matrixRTCMode === MatrixRTCMode.Matrix_2_0}
checked={effectiveMatrixRTCMode === MatrixRTCMode.Matrix_2_0}
value={MatrixRTCMode.Matrix_2_0}
disabled={!stickyEventsSupported}
disabled={matrixRTCModeForced || !stickyEventsSupported}
onChange={onMatrixRTCModeChange}
/>
}

View File

@@ -100,7 +100,7 @@ export const SettingsModal: FC<Props> = ({
const devices = useMediaDevices();
useEffect(() => {
if (open) devices.requestDeviceNames();
if (open) devices.requestDeviceNames(); // No-op after the first call
}, [open, devices]);
const [soundVolume, setSoundVolume] = useSetting(soundEffectVolumeSetting);

View File

@@ -185,17 +185,53 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
Show iPhone earpiece option on all platforms
</label>
</div>
</div>
<form
class="_root_19upo_16"
<div
class="fieldRow"
>
<div
class="_field_19upo_26"
class="field checkboxField"
>
<input
aria-describedby="_r_6_"
id="enableLivekitExtendedLogs"
type="checkbox"
/>
<label
for="enableLivekitExtendedLogs"
>
<div
class="checkbox"
>
<svg
fill="none"
height="24"
stroke="#000"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m20 6-11 11-5-5"
/>
</svg>
</div>
Enable extended livekit logs
</label>
</div>
</div>
<form
class="_root_1o4d9_17"
>
<div
class="_field_1o4d9_27"
>
<label
class="_label_19upo_59"
for="radix-_r_6_"
class="_label_1o4d9_60"
for="radix-_r_7_"
>
Custom Livekit-url
</label>
@@ -203,17 +239,17 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
class="_controls_17lij_8"
>
<input
aria-describedby="radix-_r_7_"
aria-describedby="radix-_r_8_"
class="_control_d83jn_10"
id="radix-_r_6_"
id="radix-_r_7_"
name="input"
title=""
value=""
/>
</div>
<span
class="_message_19upo_85 _help-message_19upo_91"
id="radix-_r_7_"
class="_message_1o4d9_86 _help-message_1o4d9_92"
id="radix-_r_8_"
>
Currently, no overwrite is set. Url from well-known or config is used.
</span>
@@ -225,22 +261,22 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
MatrixRTC mode
</h3>
<form
class="_root_19upo_16"
class="_root_1o4d9_17"
>
<div
class="_inline-field_19upo_32"
class="_inline-field_1o4d9_33"
>
<div
class="_inline-field-control_19upo_44"
class="_inline-field-control_1o4d9_45"
>
<div
class="_container_1ug7n_10"
>
<input
aria-describedby="radix-_r_9_ radix-_r_b_ radix-_r_d_"
aria-describedby="radix-_r_a_ radix-_r_c_ radix-_r_e_"
checked=""
class="_input_1ug7n_18"
id="radix-_r_8_"
id="radix-_r_9_"
name="_r_0_"
title=""
type="radio"
@@ -252,35 +288,35 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
</div>
</div>
<div
class="_inline-field-body_19upo_38"
class="_inline-field-body_1o4d9_39"
>
<label
class="_label_19upo_59"
for="radix-_r_8_"
class="_label_1o4d9_60"
for="radix-_r_9_"
>
Legacy: state events & oldest membership SFU
</label>
<span
class="_message_19upo_85 _help-message_19upo_91"
id="radix-_r_9_"
class="_message_1o4d9_86 _help-message_1o4d9_92"
id="radix-_r_a_"
>
Compatible with old versions of EC that do not support multi SFU
</span>
</div>
</div>
<div
class="_inline-field_19upo_32"
class="_inline-field_1o4d9_33"
>
<div
class="_inline-field-control_19upo_44"
class="_inline-field-control_1o4d9_45"
>
<div
class="_container_1ug7n_10"
>
<input
aria-describedby="radix-_r_9_ radix-_r_b_ radix-_r_d_"
aria-describedby="radix-_r_a_ radix-_r_c_ radix-_r_e_"
class="_input_1ug7n_18"
id="radix-_r_a_"
id="radix-_r_b_"
name="_r_0_"
title=""
type="radio"
@@ -292,35 +328,35 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
</div>
</div>
<div
class="_inline-field-body_19upo_38"
class="_inline-field-body_1o4d9_39"
>
<label
class="_label_19upo_59"
for="radix-_r_a_"
class="_label_1o4d9_60"
for="radix-_r_b_"
>
Compatibility: state events & multi SFU
</label>
<span
class="_message_19upo_85 _help-message_19upo_91"
id="radix-_r_b_"
class="_message_1o4d9_86 _help-message_1o4d9_92"
id="radix-_r_c_"
>
Compatible with homeservers that do not support sticky events (but all other EC clients are v0.17.0 or later)
</span>
</div>
</div>
<div
class="_inline-field_19upo_32"
class="_inline-field_1o4d9_33"
>
<div
class="_inline-field-control_19upo_44"
class="_inline-field-control_1o4d9_45"
>
<div
class="_container_1ug7n_10"
>
<input
aria-describedby="radix-_r_9_ radix-_r_b_ radix-_r_d_"
aria-describedby="radix-_r_a_ radix-_r_c_ radix-_r_e_"
class="_input_1ug7n_18"
id="radix-_r_c_"
id="radix-_r_d_"
name="_r_0_"
title=""
type="radio"
@@ -332,17 +368,17 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
</div>
</div>
<div
class="_inline-field-body_19upo_38"
class="_inline-field-body_1o4d9_39"
>
<label
class="_label_19upo_59"
for="radix-_r_c_"
class="_label_1o4d9_60"
for="radix-_r_d_"
>
Matrix 2.0: sticky events & multi SFU
</label>
<span
class="_message_19upo_85 _help-message_19upo_91"
id="radix-_r_d_"
class="_message_1o4d9_86 _help-message_1o4d9_92"
id="radix-_r_e_"
>
Compatible only with homservers supporting sticky events and all EC clients v0.17.0 or later
</span>
@@ -372,9 +408,7 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
local
)
</p>
<pre
class="pre"
>
<pre>
{
"region": "local",
"version": "1.2.3"
@@ -384,9 +418,7 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
<p>
Local Participant
</p>
<pre
class="pre"
>
<pre>
localParticipantIdentity
</pre>
<p>
@@ -413,9 +445,7 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
remote
)
</p>
<pre
class="pre"
>
<pre>
{
"region": "remote",
"version": "4.5.6"
@@ -425,9 +455,7 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
<p>
Local Participant
</p>
<pre
class="pre"
>
<pre>
localParticipantIdentity
</pre>
<p>

View File

@@ -502,6 +502,13 @@ export async function init(): Promise<void> {
};
});
window.addEventListener("unhandledrejection", (event) => {
global.mx_rage_logger.log(
LogLevel.error,
`Unhandled promise rejection: ${event.reason}`,
);
});
return tryInitStorage();
}

View File

@@ -11,6 +11,7 @@ import { BehaviorSubject } from "rxjs";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { type Behavior } from "../state/Behavior";
import { useBehavior } from "../useBehavior";
import { MatrixRTCMode } from "../config/ConfigOptions";
export class Setting<T> {
public constructor(
@@ -142,17 +143,10 @@ export const alwaysShowIphoneEarpiece = new Setting<boolean>(
false,
);
export enum MatrixRTCMode {
Legacy = "legacy",
Compatibility = "compatibility",
/** This implies using
* - sticky events
* - hashed RTC backend identity
* - the new endpoint for the jwt token on the local membership (remote memberships will always try the new jwt endpoint first -> then the legacy one)
* - use the hashed identity for the local membership
*/
Matrix_2_0 = "matrix_2_0",
}
export const enableExtendedLivekitLogs = new Setting<boolean>(
"extended-livekit-logs",
false,
);
export const matrixRTCMode = new Setting<MatrixRTCMode>(
"matrix-rtc-mode",