Skip to content

Commit c369bba

Browse files
authored
Add saveUsername and make PreJoin backwards compatible (#695)
1 parent c930024 commit c369bba

File tree

10 files changed

+134
-115
lines changed

10 files changed

+134
-115
lines changed

examples/nextjs/pages/prejoin.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,19 @@ const PreJoinExample: NextPage = () => {
77

88
return (
99
<div data-lk-theme="default" style={{ height: '100vh' }}>
10-
<PreJoin />
10+
<PreJoin
11+
defaults={{ e2ee: true, videoDeviceId: '' }}
12+
onSubmit={(values) => {
13+
values.audioDeviceId;
14+
values.e2ee;
15+
values.sharedPassphrase;
16+
}}
17+
onValidate={(values) => {
18+
values.e2ee;
19+
values.sharedPassphrase;
20+
return true;
21+
}}
22+
/>
1123
</div>
1224
);
1325
};

packages/core/etc/components-core.api.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,19 @@ export function isTrackReferencePlaceholder(trackReference?: TrackReferenceOrPla
219219
export function isWeb(): boolean;
220220

221221
// @alpha
222-
export function loadUserChoices(defaults?: Partial<UserChoices>,
223-
preventLoad?: boolean): UserChoices;
222+
export function loadUserChoices(defaults?: Partial<LocalUserChoices>,
223+
preventLoad?: boolean): LocalUserChoices;
224+
225+
// @public
226+
export type LocalUserChoices = {
227+
videoEnabled: boolean;
228+
audioEnabled: boolean;
229+
videoDeviceId: string;
230+
audioDeviceId: string;
231+
username: string;
232+
e2ee: boolean;
233+
sharedPassphrase: string;
234+
};
224235

225236
// @public (undocumented)
226237
export const log: loglevel.Logger;
@@ -339,7 +350,7 @@ export function roomInfoObserver(room: Room): Observable<{
339350
export function roomObserver(room: Room): Observable<Room>;
340351

341352
// @alpha
342-
export function saveUserChoices(deviceSettings: UserChoices,
353+
export function saveUserChoices(userChoices: LocalUserChoices,
343354
preventSave?: boolean): void;
344355

345356
// @public (undocumented)
@@ -564,15 +575,6 @@ export type TrackSourceWithOptions = {
564575
// @public
565576
export function updatePages<T extends UpdatableItem>(currentList: T[], nextList: T[], maxItemsOnPage: number): T[];
566577

567-
// @public
568-
export type UserChoices = {
569-
videoInputEnabled: boolean;
570-
audioInputEnabled: boolean;
571-
videoInputDeviceId: string;
572-
audioInputDeviceId: string;
573-
username: string;
574-
};
575-
576578
// @public (undocumented)
577579
export type VideoSource = Track.Source.Camera | Track.Source.ScreenShare;
578580

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { saveUserChoices, loadUserChoices, type UserChoices } from './user-choices';
1+
export { saveUserChoices, loadUserChoices, type LocalUserChoices } from './user-choices';

packages/core/src/persistent-storage/local-storage-helpers.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { log } from '../logger';
22

3+
type JsonPrimitive = string | number | boolean | null;
4+
type JsonArray = JsonValue[];
5+
type JsonObject = { [key: string]: JsonValue };
6+
type JsonValue = JsonPrimitive | JsonArray | JsonObject;
7+
38
/**
4-
* Set an object to local storage by key
5-
* @param key - the key to set the object to local storage
6-
* @param value - the object to set to local storage
9+
* Persists a serializable object to local storage associated with the specified key.
710
* @internal
811
*/
9-
export function setLocalStorageObject<T extends object>(key: string, value: T): void {
12+
function saveToLocalStorage<T extends JsonValue>(key: string, value: T): void {
1013
if (typeof localStorage === 'undefined') {
1114
log.error('Local storage is not available.');
1215
return;
@@ -20,12 +23,10 @@ export function setLocalStorageObject<T extends object>(key: string, value: T):
2023
}
2124

2225
/**
23-
* Get an object from local storage by key
24-
* @param key - the key to retrieve the object from local storage
25-
* @returns the object retrieved from local storage, or null if the key does not exist
26+
* Retrieves a serializable object from local storage by its key.
2627
* @internal
2728
*/
28-
export function getLocalStorageObject<T extends object>(key: string): T | undefined {
29+
function loadFromLocalStorage<T extends JsonValue>(key: string): T | undefined {
2930
if (typeof localStorage === 'undefined') {
3031
log.error('Local storage is not available.');
3132
return undefined;
@@ -43,3 +44,16 @@ export function getLocalStorageObject<T extends object>(key: string): T | undefi
4344
return undefined;
4445
}
4546
}
47+
48+
/**
49+
* Generate a pair of functions to load and save a value of type T to local storage.
50+
* @internal
51+
*/
52+
export function createLocalStorageInterface<T extends JsonValue>(
53+
key: string,
54+
): { load: () => T | undefined; save: (value: T) => void } {
55+
return {
56+
load: () => loadFromLocalStorage<T>(key),
57+
save: (value: T) => saveToLocalStorage<T>(key, value),
58+
};
59+
}
Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,69 @@
11
import { cssPrefix } from '../constants';
2-
import { getLocalStorageObject, setLocalStorageObject } from './local-storage-helpers';
2+
import { createLocalStorageInterface } from './local-storage-helpers';
33

44
const USER_CHOICES_KEY = `${cssPrefix}-user-choices` as const;
55

66
/**
77
* Represents the user's choices for video and audio input devices,
88
* as well as their username.
99
*/
10-
export type UserChoices = {
10+
export type LocalUserChoices = {
1111
/**
1212
* Whether video input is enabled.
1313
* @defaultValue `true`
1414
*/
15-
videoInputEnabled: boolean;
15+
videoEnabled: boolean;
1616
/**
1717
* Whether audio input is enabled.
1818
* @defaultValue `true`
1919
*/
20-
audioInputEnabled: boolean;
20+
audioEnabled: boolean;
2121
/**
2222
* The device ID of the video input device to use.
2323
* @defaultValue `''`
2424
*/
25-
videoInputDeviceId: string;
25+
videoDeviceId: string;
2626
/**
2727
* The device ID of the audio input device to use.
2828
* @defaultValue `''`
2929
*/
30-
audioInputDeviceId: string;
30+
audioDeviceId: string;
3131
/**
3232
* The username to use.
3333
* @defaultValue `''`
3434
*/
3535
username: string;
36+
/** @deprecated This property will be removed without replacement. */
37+
e2ee: boolean;
38+
/** @deprecated This property will be removed without replacement. */
39+
sharedPassphrase: string;
3640
};
3741

38-
const defaultUserChoices: UserChoices = {
39-
videoInputEnabled: true,
40-
audioInputEnabled: true,
41-
videoInputDeviceId: '',
42-
audioInputDeviceId: '',
42+
const defaultUserChoices: LocalUserChoices = {
43+
videoEnabled: true,
44+
audioEnabled: true,
45+
videoDeviceId: '',
46+
audioDeviceId: '',
4347
username: '',
48+
e2ee: false,
49+
sharedPassphrase: '',
4450
} as const;
4551

52+
/**
53+
* The type of the object stored in local storage.
54+
* @remarks
55+
* TODO: Replace this type with `LocalUserChoices` after removing the deprecated properties from `LocalUserChoices`.
56+
* @internal
57+
*/
58+
type TempStorageType = Omit<LocalUserChoices, 'e2ee' | 'sharedPassphrase'>;
59+
const { load, save } = createLocalStorageInterface<TempStorageType>(USER_CHOICES_KEY);
60+
4661
/**
4762
* Saves user choices to local storage.
48-
* @param deviceSettings - The device settings to be stored.
4963
* @alpha
5064
*/
5165
export function saveUserChoices(
52-
deviceSettings: UserChoices,
66+
userChoices: LocalUserChoices,
5367
/**
5468
* Whether to prevent saving user choices to local storage.
5569
*/
@@ -58,35 +72,41 @@ export function saveUserChoices(
5872
if (preventSave === true) {
5973
return;
6074
}
61-
setLocalStorageObject(USER_CHOICES_KEY, deviceSettings);
75+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
76+
const { e2ee, sharedPassphrase, ...toSave } = userChoices;
77+
save(toSave);
6278
}
6379

6480
/**
6581
* Reads the user choices from local storage, or returns the default settings if none are found.
66-
* @param defaults - The default device settings to use if none are found in local storage.
67-
* @defaultValue `defaultUserChoices`
68-
*
82+
* @remarks
83+
* The deprecated parameters `e2ee` and `sharedPassphrase` are not read from local storage
84+
* and always return the value from the passed `defaults` or internal defaults.
6985
* @alpha
7086
*/
7187
export function loadUserChoices(
72-
defaults?: Partial<UserChoices>,
88+
defaults?: Partial<LocalUserChoices>,
7389
/**
7490
* Whether to prevent loading from local storage and return default values instead.
7591
* @defaultValue false
7692
*/
7793
preventLoad: boolean = false,
78-
): UserChoices {
79-
const fallback: UserChoices = {
80-
videoInputEnabled: defaults?.videoInputEnabled ?? defaultUserChoices.videoInputEnabled,
81-
audioInputEnabled: defaults?.audioInputEnabled ?? defaultUserChoices.audioInputEnabled,
82-
videoInputDeviceId: defaults?.videoInputDeviceId ?? defaultUserChoices.videoInputDeviceId,
83-
audioInputDeviceId: defaults?.audioInputDeviceId ?? defaultUserChoices.audioInputDeviceId,
94+
): LocalUserChoices {
95+
const fallback: LocalUserChoices = {
96+
videoEnabled: defaults?.videoEnabled ?? defaultUserChoices.videoEnabled,
97+
audioEnabled: defaults?.audioEnabled ?? defaultUserChoices.audioEnabled,
98+
videoDeviceId: defaults?.videoDeviceId ?? defaultUserChoices.videoDeviceId,
99+
audioDeviceId: defaults?.audioDeviceId ?? defaultUserChoices.audioDeviceId,
84100
username: defaults?.username ?? defaultUserChoices.username,
101+
e2ee: defaults?.e2ee ?? defaultUserChoices.e2ee,
102+
sharedPassphrase: defaults?.sharedPassphrase ?? defaultUserChoices.sharedPassphrase,
85103
};
86104

87105
if (preventLoad) {
88106
return fallback;
89107
} else {
90-
return getLocalStorageObject(USER_CHOICES_KEY) ?? fallback;
108+
const maybeLoadedObject = load();
109+
const result = { ...fallback, ...(maybeLoadedObject ?? {}) };
110+
return result;
91111
}
92112
}

packages/react/etc/components-react.api.md

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type { LocalAudioTrack } from 'livekit-client';
2020
import { LocalParticipant } from 'livekit-client';
2121
import type { LocalTrack } from 'livekit-client';
2222
import { LocalTrackPublication } from 'livekit-client';
23+
import { LocalUserChoices } from '@livekit/components-core';
2324
import type { LocalVideoTrack } from 'livekit-client';
2425
import type { MediaDeviceFailure } from 'livekit-client';
2526
import { MessageDecoder } from '@livekit/components-core';
@@ -48,7 +49,6 @@ import { TrackPublication } from 'livekit-client';
4849
import type { TrackReference } from '@livekit/components-core';
4950
import { TrackReferenceOrPlaceholder } from '@livekit/components-core';
5051
import type { TrackSourceWithOptions } from '@livekit/components-core';
51-
import { UserChoices } from '@livekit/components-core';
5252
import type { VideoCaptureOptions } from 'livekit-client';
5353
import type { VideoSource } from '@livekit/components-core';
5454
import type { WidgetState } from '@livekit/components-core';
@@ -327,16 +327,7 @@ export interface LiveKitRoomProps extends Omit<React_2.HTMLAttributes<HTMLDivEle
327327
// @internal (undocumented)
328328
export const LKFeatureContext: React_2.Context<FeatureFlags | undefined>;
329329

330-
// @public @deprecated (undocumented)
331-
export type LocalUserChoices = {
332-
username: string;
333-
videoEnabled: boolean;
334-
audioEnabled: boolean;
335-
videoDeviceId: string;
336-
audioDeviceId: string;
337-
e2ee: boolean;
338-
sharedPassphrase: string;
339-
};
330+
export { LocalUserChoices }
340331

341332
// @public
342333
export function MediaDeviceMenu({ kind, initialSelection, onActiveDeviceChange, tracks, requestPermissions, ...props }: MediaDeviceMenuProps): React_2.JSX.Element;
@@ -835,16 +826,17 @@ export interface UseParticipantTileProps<T extends HTMLElement> extends React_2.
835826

836827
// @alpha
837828
export function usePersistentUserChoices(options?: UsePersistentUserChoicesOptions): {
838-
userChoices: UserChoices;
829+
userChoices: LocalUserChoices;
839830
saveAudioInputEnabled: (isEnabled: boolean) => void;
840831
saveVideoInputEnabled: (isEnabled: boolean) => void;
841832
saveAudioInputDeviceId: (deviceId: string) => void;
842833
saveVideoInputDeviceId: (deviceId: string) => void;
834+
saveUsername: (username: string) => void;
843835
};
844836

845837
// @alpha
846838
export interface UsePersistentUserChoicesOptions {
847-
defaults?: Partial<UserChoices>;
839+
defaults?: Partial<LocalUserChoices>;
848840
preventLoad?: boolean;
849841
preventSave?: boolean;
850842
}
@@ -862,8 +854,6 @@ export function usePreviewDevice<T extends LocalVideoTrack | LocalAudioTrack>(en
862854
// @alpha (undocumented)
863855
export function usePreviewTracks(options: CreateLocalTracksOptions, onError?: (err: Error) => void): LocalTrack[] | undefined;
864856

865-
export { UserChoices }
866-
867857
// @public
868858
export function useRemoteParticipant(identity: string, options?: UseRemoteParticipantOptions): RemoteParticipant | undefined;
869859

packages/react/src/hooks/usePersistentUserChoices.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { UserChoices } from '@livekit/components-core';
1+
import type { LocalUserChoices } from '@livekit/components-core';
22
import { loadUserChoices, saveUserChoices } from '@livekit/components-core';
33
import * as React from 'react';
44

@@ -10,7 +10,7 @@ export interface UsePersistentUserChoicesOptions {
1010
/**
1111
* The default value to use if reading from local storage returns no results or fails.
1212
*/
13-
defaults?: Partial<UserChoices>;
13+
defaults?: Partial<LocalUserChoices>;
1414
/**
1515
* Whether to prevent saving to persistent storage.
1616
* @defaultValue false
@@ -29,21 +29,24 @@ export interface UsePersistentUserChoicesOptions {
2929
* @alpha
3030
*/
3131
export function usePersistentUserChoices(options: UsePersistentUserChoicesOptions = {}) {
32-
const [userChoices, setSettings] = React.useState<UserChoices>(
32+
const [userChoices, setSettings] = React.useState<LocalUserChoices>(
3333
loadUserChoices(options.defaults, options.preventLoad ?? false),
3434
);
3535

3636
const saveAudioInputEnabled = React.useCallback((isEnabled: boolean) => {
37-
setSettings((prev) => ({ ...prev, audioInputEnabled: isEnabled }));
37+
setSettings((prev) => ({ ...prev, audioEnabled: isEnabled }));
3838
}, []);
3939
const saveVideoInputEnabled = React.useCallback((isEnabled: boolean) => {
40-
setSettings((prev) => ({ ...prev, videoInputEnabled: isEnabled }));
40+
setSettings((prev) => ({ ...prev, videoEnabled: isEnabled }));
4141
}, []);
4242
const saveAudioInputDeviceId = React.useCallback((deviceId: string) => {
43-
setSettings((prev) => ({ ...prev, audioInputDeviceId: deviceId }));
43+
setSettings((prev) => ({ ...prev, audioDeviceId: deviceId }));
4444
}, []);
4545
const saveVideoInputDeviceId = React.useCallback((deviceId: string) => {
46-
setSettings((prev) => ({ ...prev, videoInputDeviceId: deviceId }));
46+
setSettings((prev) => ({ ...prev, videoDeviceId: deviceId }));
47+
}, []);
48+
const saveUsername = React.useCallback((username: string) => {
49+
setSettings((prev) => ({ ...prev, username: username }));
4750
}, []);
4851

4952
React.useEffect(() => {
@@ -56,5 +59,6 @@ export function usePersistentUserChoices(options: UsePersistentUserChoicesOption
5659
saveVideoInputEnabled,
5760
saveAudioInputDeviceId,
5861
saveVideoInputDeviceId,
62+
saveUsername,
5963
};
6064
}

packages/react/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ export type {
1313
ReceivedChatMessage,
1414
MessageDecoder,
1515
MessageEncoder,
16-
UserChoices,
16+
LocalUserChoices,
1717
} from '@livekit/components-core';

0 commit comments

Comments
 (0)