diff --git a/src/analytics/PosthogAnalytics.ts b/src/analytics/PosthogAnalytics.ts index 0ca23fb3..9d4f3f5c 100644 --- a/src/analytics/PosthogAnalytics.ts +++ b/src/analytics/PosthogAnalytics.ts @@ -20,7 +20,7 @@ import { MatrixClient } from "matrix-js-sdk"; import { Buffer } from "buffer"; import { widget } from "../widget"; -import { getSetting, setSetting, settingsBus } from "../settings/useSetting"; +import { getSetting, setSetting, getSettingKey } from "../settings/useSetting"; import { CallEndedTracker, CallStartedTracker, @@ -34,6 +34,7 @@ import { } from "./PosthogEvents"; import { Config } from "../config/Config"; import { getUrlParams } from "../UrlParams"; +import { localStorageBus } from "../useLocalStorage"; /* Posthog analytics tracking. * @@ -413,7 +414,7 @@ export class PosthogAnalytics { // * When the user changes their preferences on this device // Note that for new accounts, pseudonymousAnalyticsOptIn won't be set, so updateAnonymityFromSettings // won't be called (i.e. this.anonymity will be left as the default, until the setting changes) - settingsBus.on("opt-in-analytics", (optInAnalytics) => { + localStorageBus.on(getSettingKey("opt-in-analytics"), (optInAnalytics) => { this.updateAnonymityAndIdentifyUser(optInAnalytics); }); } diff --git a/src/e2ee/sharedKeyManagement.ts b/src/e2ee/sharedKeyManagement.ts index 0a819066..651012c7 100644 --- a/src/e2ee/sharedKeyManagement.ts +++ b/src/e2ee/sharedKeyManagement.ts @@ -14,7 +14,27 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { useSetting } from "../settings/useSetting"; +import { useEffect, useMemo } from "react"; +import { randomString } from "matrix-js-sdk/src/randomstring"; -export const useRoomSharedKey = (roomId: string, initialValue?: string) => - useSetting(`room-shared-key-${roomId}`, initialValue); +import { useEnableE2EE } from "../settings/useSetting"; +import { useLocalStorage } from "../useLocalStorage"; + +const getRoomSharedKeyLocalStorageKey = (roomId: string): string => + `room-shared-key-${roomId}`; + +export const useRoomSharedKey = ( + roomId: string +): [string | null, ((value: string) => void) | null] => { + const key = useMemo(() => getRoomSharedKeyLocalStorageKey(roomId), [roomId]); + const [e2eeEnabled] = useEnableE2EE(); + const [roomSharedKey, setRoomSharedKey] = useLocalStorage(key); + + useEffect(() => { + if ((roomSharedKey && roomSharedKey !== "") || !e2eeEnabled) return; + + setRoomSharedKey(randomString(32)); + }, [roomSharedKey, e2eeEnabled, setRoomSharedKey]); + + return e2eeEnabled ? [roomSharedKey, setRoomSharedKey] : [null, null]; +}; diff --git a/src/home/CallList.tsx b/src/home/CallList.tsx index f93e2ff8..99296c5d 100644 --- a/src/home/CallList.tsx +++ b/src/home/CallList.tsx @@ -109,7 +109,7 @@ function CallTile({ ); diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 2ab942d3..4c796e9f 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -245,14 +245,13 @@ export function GroupCallView({ }, [groupCall, state, leave]); const [e2eeSharedKey, setE2EESharedKey] = useRoomSharedKey( - groupCall.room.roomId, - password ?? undefined + groupCall.room.roomId ); useEffect(() => { - if (!password || password === e2eeSharedKey) return; + if (!password || password === "" || password === e2eeSharedKey) return; - setE2EESharedKey(password); + setE2EESharedKey?.(password); }, [password, e2eeSharedKey, setE2EESharedKey]); useEffect(() => { diff --git a/src/settings/useSetting.ts b/src/settings/useSetting.ts index c127554e..5d86a4b6 100644 --- a/src/settings/useSetting.ts +++ b/src/settings/useSetting.ts @@ -1,5 +1,5 @@ /* -Copyright 2022 New Vector Ltd +Copyright 2022 - 2023 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,59 +14,49 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { EventEmitter } from "events"; -import { useMemo, useState, useEffect, useCallback } from "react"; +import { useCallback, useMemo } from "react"; import { isE2EESupported } from "livekit-client"; import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; +import { + getLocalStorageItem, + setLocalStorageItem, + useLocalStorage, +} from "../useLocalStorage"; type Setting = [T, (value: T) => void]; type DisableableSetting = [T, ((value: T) => void) | null]; -// Bus to notify other useSetting consumers when a setting is changed -export const settingsBus = new EventEmitter(); - -const getSettingKey = (name: string): string => { +export const getSettingKey = (name: string): string => { return `matrix-setting-${name}`; }; // Like useState, but reads from and persists the value to localStorage export const useSetting = (name: string, defaultValue: T): Setting => { const key = useMemo(() => getSettingKey(name), [name]); - const [value, setValue] = useState(() => { - const item = localStorage.getItem(key); - return item == null ? defaultValue : JSON.parse(item); - }); + const [item, setItem] = useLocalStorage(key); - useEffect(() => { - settingsBus.on(name, setValue); - return () => { - settingsBus.off(name, setValue); - }; - }, [name, setValue]); + const value = useMemo( + () => (item == null ? defaultValue : JSON.parse(item)), + [item, defaultValue] + ); + const setValue = useCallback( + (value: T) => { + setItem(JSON.stringify(value)); + }, + [setItem] + ); - return [ - value, - useCallback( - (newValue: T) => { - setValue(newValue); - localStorage.setItem(key, JSON.stringify(newValue)); - settingsBus.emit(name, newValue); - }, - [name, key, setValue] - ), - ]; + return [value, setValue]; }; export const getSetting = (name: string, defaultValue: T): T => { - const item = localStorage.getItem(getSettingKey(name)); + const item = getLocalStorageItem(getSettingKey(name)); return item === null ? defaultValue : JSON.parse(item); }; -export const setSetting = (name: string, newValue: T) => { - localStorage.setItem(getSettingKey(name), JSON.stringify(newValue)); - settingsBus.emit(name, newValue); -}; +export const setSetting = (name: string, newValue: T) => + setLocalStorageItem(getSettingKey(name), JSON.stringify(newValue)); const canEnableSpatialAudio = () => { const { userAgent } = navigator; diff --git a/src/useLocalStorage.ts b/src/useLocalStorage.ts new file mode 100644 index 00000000..e7fe51ef --- /dev/null +++ b/src/useLocalStorage.ts @@ -0,0 +1,59 @@ +/* +Copyright 2023 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import EventEmitter from "events"; +import { useCallback, useEffect, useState } from "react"; + +type LocalStorageItem = ReturnType; + +// Bus to notify other useLocalStorage consumers when an item is changed +export const localStorageBus = new EventEmitter(); + +// Like useState, but reads from and persists the value to localStorage +export const useLocalStorage = ( + key: string +): [LocalStorageItem, (value: string) => void] => { + const [value, setValue] = useState(() => + localStorage.getItem(key) + ); + + useEffect(() => { + localStorageBus.on(key, setValue); + return () => { + localStorageBus.off(key, setValue); + }; + }, [key, setValue]); + + return [ + value, + useCallback( + (newValue: string) => { + setValue(newValue); + localStorage.setItem(key, newValue); + localStorageBus.emit(key, newValue); + }, + [key, setValue] + ), + ]; +}; + +export const getLocalStorageItem = (key: string): LocalStorageItem => + localStorage.getItem(key); + +export const setLocalStorageItem = (key: string, value: string): void => { + localStorage.setItem(key, value); + localStorageBus.emit(key, value); +};