diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index f91158c38a..6b7268d57e 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -61,6 +61,7 @@ import { setSentryUser } from "./sentry"; import SdkConfig from "./SdkConfig"; import { DialogOpener } from "./utils/DialogOpener"; import { Action } from "./dispatcher/actions"; +import AbstractLocalStorageSettingsHandler from "./settings/handlers/AbstractLocalStorageSettingsHandler"; const HOMESERVER_URL_KEY = "mx_hs_url"; const ID_SERVER_URL_KEY = "mx_is_url"; @@ -878,6 +879,7 @@ async function clearStorage(opts?: { deleteEverything?: boolean }): Promise(); - private objectCache = new Map(); + // Shared cache between all subclass instances + private static itemCache = new Map(); + private static objectCache = new Map(); + private static storageListenerBound = false; + + private static onStorageEvent = (e: StorageEvent) => { + if (e.key === null) { + AbstractLocalStorageSettingsHandler.clear(); + } else { + AbstractLocalStorageSettingsHandler.itemCache.delete(e.key); + AbstractLocalStorageSettingsHandler.objectCache.delete(e.key); + } + }; + + // Expose the clear event for Lifecycle to call, the storage listener only fires for changes from other tabs + public static clear() { + AbstractLocalStorageSettingsHandler.itemCache.clear(); + AbstractLocalStorageSettingsHandler.objectCache.clear(); + } protected constructor() { super(); - // Listen for storage changes from other tabs to bust the cache - window.addEventListener("storage", (e: StorageEvent) => { - if (e.key === null) { - this.itemCache.clear(); - this.objectCache.clear(); - } else { - this.itemCache.delete(e.key); - this.objectCache.delete(e.key); - } - }); + if (!AbstractLocalStorageSettingsHandler.storageListenerBound) { + AbstractLocalStorageSettingsHandler.storageListenerBound = true; + // Listen for storage changes from other tabs to bust the cache + window.addEventListener("storage", AbstractLocalStorageSettingsHandler.onStorageEvent); + } } protected getItem(key: string): any { - if (!this.itemCache.has(key)) { + if (!AbstractLocalStorageSettingsHandler.itemCache.has(key)) { const value = localStorage.getItem(key); - this.itemCache.set(key, value); + AbstractLocalStorageSettingsHandler.itemCache.set(key, value); return value; } - return this.itemCache.get(key); + return AbstractLocalStorageSettingsHandler.itemCache.get(key); } protected getObject(key: string): T | null { - if (!this.objectCache.has(key)) { + if (!AbstractLocalStorageSettingsHandler.objectCache.has(key)) { try { const value = JSON.parse(localStorage.getItem(key)); - this.objectCache.set(key, value); + AbstractLocalStorageSettingsHandler.objectCache.set(key, value); return value; } catch (err) { console.error("Failed to parse localStorage object", err); @@ -61,24 +73,24 @@ export default abstract class AbstractLocalStorageSettingsHandler extends Settin } } - return this.objectCache.get(key) as T; + return AbstractLocalStorageSettingsHandler.objectCache.get(key) as T; } protected setItem(key: string, value: any): void { - this.itemCache.set(key, value); + AbstractLocalStorageSettingsHandler.itemCache.set(key, value); localStorage.setItem(key, value); } protected setObject(key: string, value: object): void { - this.objectCache.set(key, value); + AbstractLocalStorageSettingsHandler.objectCache.set(key, value); localStorage.setItem(key, JSON.stringify(value)); } // handles both items and objects protected removeItem(key: string): void { localStorage.removeItem(key); - this.itemCache.delete(key); - this.objectCache.delete(key); + AbstractLocalStorageSettingsHandler.itemCache.delete(key); + AbstractLocalStorageSettingsHandler.objectCache.delete(key); } public isSupported(): boolean {