Skip to content
49 changes: 41 additions & 8 deletions src/settings/DeveloperSettingsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
InlineField,
Label,
RadioControl,
Text,
} from "@vector-im/compound-web";

import { FieldRow, InputField } from "../input/Input";
Expand All @@ -45,6 +46,7 @@ import {
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;
Expand Down Expand Up @@ -92,6 +94,8 @@ export const DeveloperSettingsTab: FC<Props> = ({
alwaysShowIphoneEarpieceSetting,
);

const [customLivekitUrlUpdateError, setCustomLivekitUrlUpdateError] =
useState<string | null>(null);
const [customLivekitUrl, setCustomLivekitUrl] = useSetting(
customLivekitUrlSetting,
);
Expand Down Expand Up @@ -217,6 +221,7 @@ export const DeveloperSettingsTab: FC<Props> = ({
/>{" "}
</FieldRow>
<EditInPlace
serverInvalid={customLivekitUrlUpdateError !== null}
onSubmit={(e) => e.preventDefault()}
helpLabel={
customLivekitUrl === null
Expand All @@ -229,14 +234,36 @@ export const DeveloperSettingsTab: FC<Props> = ({
savingLabel={t("developer_mode.custom_livekit_url.saving")}
cancelButtonLabel={t("developer_mode.custom_livekit_url.reset")}
onSave={useCallback(
(e: React.FormEvent<HTMLFormElement>) => {
setCustomLivekitUrl(
customLivekitUrlTextBuffer === ""
? null
: customLivekitUrlTextBuffer,
);
async (e: React.FormEvent<HTMLFormElement>): Promise<void> => {
if (
customLivekitUrlTextBuffer === "" ||
customLivekitUrlTextBuffer === null
) {
setCustomLivekitUrl(null);
return Promise.resolve();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to return an explicit resolve in an async function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the time I did not have the async call below this was needed i suspect ;) thanks for catching it.

}

try {
logger.debug("try setting");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assume all these debug logs will go before merge?

await getSFUConfigWithOpenID(
client,
customLivekitUrlTextBuffer,
"Test-room-alias-" + Date.now().toString() + client.getUserId(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Test-room-alias-" + Date.now().toString() + client.getUserId(),
`Test-room-alias-${Date.now()}${client.getUserId()}`,

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the room alias thing about, btw?

Copy link
Contributor Author

@toger5 toger5 Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be any id lk should use. But I am not sure the jwt service will use it for checking if we are part of the room. its saver to set this to the actual roomId.

);
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like an unusual thing to do? Usually inputs change the error state when the value changes, 4 seconds isn't even enough time to understand the error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, since you're not cancelling the timeout, you'll have these stack up if you try to save within that 2 second window.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think it should be fine to just show the error until the user has actually entered sth reasonable.
I was worried it might be confusing what is actually set right now if the control shows an error. But probably users will change it until they have set it to sth that works.

setTimeout(() => {
logger.debug("unsetting error");
setCustomLivekitUrlUpdateError(null);
}, 2000);
}
},
[setCustomLivekitUrl, customLivekitUrlTextBuffer],
[customLivekitUrlTextBuffer, setCustomLivekitUrl, client],
)}
value={customLivekitUrlTextBuffer ?? ""}
onChange={useCallback(
Expand All @@ -251,7 +278,13 @@ export const DeveloperSettingsTab: FC<Props> = ({
},
[setCustomLivekitUrl],
)}
/>
>
{customLivekitUrlUpdateError !== null && (
<Text size="sm" priority="low">
{customLivekitUrlUpdateError}
</Text>
)}
</EditInPlace>
<Heading as="h3" type="body" weight="semibold" size="lg">
{t("developer_mode.matrixRTCMode.title")}
</Heading>
Expand Down
7 changes: 6 additions & 1 deletion src/settings/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,20 @@ export class Setting<T> {

this._value$ = new BehaviorSubject(initialValue);
this.value$ = this._value$;
this._lastUpdateReason$ = new BehaviorSubject<string | null>(null);
this.lastUpdateReason$ = this._lastUpdateReason$;
}

private readonly key: string;

private readonly _value$: BehaviorSubject<T>;
private readonly _lastUpdateReason$: BehaviorSubject<string | null>;
public readonly value$: Behavior<T>;
public readonly lastUpdateReason$: Behavior<string | null>;

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 => {
Expand Down