let isLocalStorageEnabled = true; interface MemoryStore { [key: string]: string; } const memoryStorage: MemoryStore = {}; const subscriptions = new EventTarget(); export interface LocalStorageEvent extends CustomEvent { key: string; newValue: string; } export const getLocalStorage = (key: string): string | null => { if (!isLocalStorageEnabled) { return Object.prototype.hasOwnProperty.call(memoryStorage, key) ? memoryStorage[key] : null; } try { const value = window.localStorage.getItem(key); if (value && memoryStorage[key] !== value) { memoryStorage[key] = value; } return value; } catch (e) { // eslint-disable-next-line no-console console.warn('localStorage is diasabled using memory storage instead.', e); isLocalStorageEnabled = false; return getLocalStorage(key); } }; export const setLocalStorage = (key: string, newValue: string): void => { const event = new CustomEvent('local-storage-change', { detail: { key, newValue } }); if (!isLocalStorageEnabled) { memoryStorage[key] = newValue; subscriptions.dispatchEvent(event); return; } try { window.localStorage.setItem(key, newValue); memoryStorage[key] = newValue; subscriptions.dispatchEvent(event); } catch (e) { // eslint-disable-next-line no-console console.warn('localStorage is diasabled using memory storage instead.', e); isLocalStorageEnabled = false; return setLocalStorage(key, newValue); } }; export const isKeySet = (key: string): boolean => { if (isLocalStorageEnabled) { try { for (let i = 0; i < window.localStorage.length; i++) { if (window.localStorage.key(i) === key) { return true; } } } catch { // ignore the error } } return Object.keys(memoryStorage).includes(key); }; // This function allows you to subscribe to localStorage changes. By default // this includes changes that are made in other tabs/windows of the same // browser. The 'callback' will be called whenever a change is made to // localStorage at the given 'key'. An unsubscibe function is returned, which // you can call when you are ready to unsubscribe. export const subscribeToLocalStorage = ( key: string, callback: (value: string) => void, options?: { excludeExternal?: boolean } ): (() => void) => { const { excludeExternal } = options || {}; const updateHandler = (e: LocalStorageEvent | { detail: { key: string; newValue: string } }) => { const { key: eventKey, newValue } = 'detail' in e ? e.detail : e; if (eventKey === key) { callback(newValue); } }; // subscribe to events for this instance of the page only subscriptions.addEventListener( 'local-storage-change', updateHandler as EventListenerOrEventListenerObject ); // if not excluded, subscribe to events for all instances of the page !excludeExternal && window.addEventListener('storage', updateHandler as EventListenerOrEventListenerObject); // return a function that will be used to unsubscribe to these events. return () => { subscriptions.removeEventListener( 'local-storage-change', updateHandler as EventListenerOrEventListenerObject ); !excludeExternal && window.removeEventListener('storage', updateHandler as EventListenerOrEventListenerObject); }; };