Skip to content

Commit 93dfb08

Browse files
committed
Simplify key local storage management.
1 parent 68396f8 commit 93dfb08

File tree

3 files changed

+47
-59
lines changed

3 files changed

+47
-59
lines changed

src/e2ee/sharedKeyManagement.ts

Lines changed: 37 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,56 +8,52 @@ Please see LICENSE in the repository root for full details.
88
import { useEffect, useMemo } from "react";
99

1010
import { setLocalStorageItem, useLocalStorage } from "../useLocalStorage";
11-
import { type UrlParams, getUrlParams, useUrlParams } from "../UrlParams";
11+
import { getUrlParams } from "../UrlParams";
1212
import { E2eeType } from "./e2eeType";
1313
import { useClient } from "../ClientContext";
1414

15+
/**
16+
* This setter will update the state for all `useRoomSharedKey` hooks
17+
*/
1518
export function saveKeyForRoom(roomId: string, password: string): void {
1619
setLocalStorageItem(getRoomSharedKeyLocalStorageKey(roomId), password);
1720
}
1821

1922
const getRoomSharedKeyLocalStorageKey = (roomId: string): string =>
2023
`room-shared-key-${roomId}`;
2124

22-
const useInternalRoomSharedKey = (roomId: string): string | null => {
23-
const key = getRoomSharedKeyLocalStorageKey(roomId);
24-
const [roomSharedKey] = useLocalStorage(key);
25-
26-
return roomSharedKey;
25+
/**
26+
* An upto-date shared key for the room. Either from local storage or the value from `setInitialValue`.
27+
* @param roomId
28+
* @param setInitialValue The value we get from the URL. The hook will overwrite the local storage value with this.
29+
* @returns [roomSharedKey, setRoomSharedKey] like a react useState hook.
30+
*/
31+
const useRoomSharedKey = (
32+
roomId: string,
33+
setInitialValue?: string,
34+
): [string | null, setKey: (key: string) => void] => {
35+
const [roomSharedKey, setRoomSharedKey] = useLocalStorage(
36+
getRoomSharedKeyLocalStorageKey(roomId),
37+
);
38+
useEffect(() => {
39+
// If setInitialValue is available, update the local storage (usually the password from the url).
40+
// This will update roomSharedKey but wont update the returned value since
41+
// that already defaults to setInitialValue.
42+
if (setInitialValue) setRoomSharedKey(setInitialValue);
43+
}, [setInitialValue, setRoomSharedKey]);
44+
45+
// make sure we never return the initial null value from `useLocalStorage`
46+
return [setInitialValue ?? roomSharedKey, setRoomSharedKey];
2747
};
2848

2949
export function getKeyForRoom(roomId: string): string | null {
30-
saveKeyFromUrlParams(getUrlParams());
31-
const key = getRoomSharedKeyLocalStorageKey(roomId);
32-
return localStorage.getItem(key);
50+
const { roomId: urlRoomId, password } = getUrlParams();
51+
if (roomId !== urlRoomId) return null;
52+
return (
53+
password ?? localStorage.getItem(getRoomSharedKeyLocalStorageKey(roomId))
54+
);
3355
}
3456

35-
function saveKeyFromUrlParams(urlParams: UrlParams): void {
36-
if (!urlParams.password || !urlParams.roomId) return;
37-
38-
// Take the key from the URL and save it.
39-
// It's important to always use the room ID specified in the URL
40-
// when saving keys rather than whatever the current room ID might be,
41-
// in case we've moved to a different room but the URL hasn't changed.
42-
saveKeyForRoom(urlParams.roomId, urlParams.password);
43-
}
44-
45-
/**
46-
* Extracts the room password from the URL if one is present, saving it in localstorage
47-
* and returning it in a tuple with the corresponding room ID from the URL.
48-
* @returns A tuple of the roomId and password from the URL if the URL has both,
49-
* otherwise [undefined, undefined]
50-
*/
51-
const useKeyFromUrl = (): [string, string] | [undefined, undefined] => {
52-
const urlParams = useUrlParams();
53-
54-
useEffect(() => saveKeyFromUrlParams(urlParams), [urlParams]);
55-
56-
return urlParams.roomId && urlParams.password
57-
? [urlParams.roomId, urlParams.password]
58-
: [undefined, undefined];
59-
};
60-
6157
export type Unencrypted = { kind: E2eeType.NONE };
6258
export type SharedSecret = { kind: E2eeType.SHARED_KEY; secret: string };
6359
export type PerParticipantE2EE = { kind: E2eeType.PER_PARTICIPANT };
@@ -66,12 +62,11 @@ export type EncryptionSystem = Unencrypted | SharedSecret | PerParticipantE2EE;
6662
export function useRoomEncryptionSystem(roomId: string): EncryptionSystem {
6763
const { client } = useClient();
6864

69-
// make sure we've extracted the key from the URL first
70-
// (and we still need to take the value it returns because
71-
// the effect won't run in time for it to save to localstorage in
72-
// time for us to read it out again).
73-
const [urlRoomId, passwordFromUrl] = useKeyFromUrl();
74-
const storedPassword = useInternalRoomSharedKey(roomId);
65+
const [storedPassword] = useRoomSharedKey(
66+
getRoomSharedKeyLocalStorageKey(roomId),
67+
getKeyForRoom(roomId) ?? undefined,
68+
);
69+
7570
const room = client?.getRoom(roomId);
7671
const e2eeSystem = <EncryptionSystem>useMemo(() => {
7772
if (!room) return { kind: E2eeType.NONE };
@@ -80,15 +75,10 @@ export function useRoomEncryptionSystem(roomId: string): EncryptionSystem {
8075
kind: E2eeType.SHARED_KEY,
8176
secret: storedPassword,
8277
};
83-
if (urlRoomId === roomId)
84-
return {
85-
kind: E2eeType.SHARED_KEY,
86-
secret: passwordFromUrl,
87-
};
8878
if (room.hasEncryptionStateEvent()) {
8979
return { kind: E2eeType.PER_PARTICIPANT };
9080
}
9181
return { kind: E2eeType.NONE };
92-
}, [passwordFromUrl, room, roomId, storedPassword, urlRoomId]);
82+
}, [room, storedPassword]);
9383
return e2eeSystem;
9484
}

src/home/useGroupCallRooms.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
type MatrixRTCSession,
2222
} from "matrix-js-sdk/lib/matrixrtc";
2323

24-
import { getKeyForRoom } from "../e2ee/sharedKeyManagement";
24+
import { getKeyForRoom, saveKeyForRoom } from "../e2ee/sharedKeyManagement";
2525

2626
export interface GroupCallRoom {
2727
roomAlias?: string;
@@ -81,11 +81,13 @@ function sortRooms(client: MatrixClient, rooms: Room[]): Room[] {
8181
}
8282

8383
const roomIsJoinable = (room: Room): boolean => {
84-
if (!room.hasEncryptionStateEvent() && !getKeyForRoom(room.roomId)) {
85-
// if we have an non encrypted room (no encryption state event) we need a locally stored shared key.
84+
const password = getKeyForRoom(room.roomId);
85+
if (!room.hasEncryptionStateEvent() && !password) {
86+
// if we have a non encrypted room (no encryption state event) we need a locally stored shared key.
8687
// in case this key also does not exists we cannot join the room.
8788
return false;
8889
}
90+
if (password) saveKeyForRoom(room.roomId, password);
8991
// otherwise we can always join rooms because we will automatically decide if we want to use perParticipant or password
9092
switch (room.getJoinRule()) {
9193
case JoinRule.Public:

src/useLocalStorage.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ type LocalStorageItem = ReturnType<typeof localStorage.getItem>;
1313
// Bus to notify other useLocalStorage consumers when an item is changed
1414
export const localStorageBus = new EventEmitter();
1515

16-
// Like useState, but reads from and persists the value to localStorage
16+
/**
17+
* Like useState, but reads from and persists the value to localStorage
18+
* this hook will not update when we write to localStorage.setItem(key, value) directly.
19+
* For the hook to react either use the returned setter or `saveKeyForRoom`.
20+
*/
1721
export const useLocalStorage = (
1822
key: string,
1923
): [LocalStorageItem, (value: string) => void] => {
@@ -42,14 +46,6 @@ export const useLocalStorage = (
4246
};
4347

4448
export const setLocalStorageItem = (key: string, value: string): void => {
45-
// Avoid unnecessary updates. Not avoiding them so can cause unexpected state updates across hooks.
46-
// For instance:
47-
// - In call view uses useRoomEncryptionSystem
48-
// - This will set the key again.
49-
// - All other instances of useRoomEncryptionSystem will now do a useMemo update of the e2eeSystem
50-
// - because the dependency `storedPassword = useInternalRoomSharedKey(roomId);` would change.
51-
if (localStorage.getItem(key) === value) return;
52-
5349
localStorage.setItem(key, value);
5450
localStorageBus.emit(key, value);
5551
};

0 commit comments

Comments
 (0)