import _ from 'lodash'; import * as yup from 'yup'; const listenerSchema = yup.mixed().test('function', 'Invalid function', (value) => { return _.isFunction(value); }); type IListener = (value: unknown) => void; interface ILocalStorage { get: (key: string) => unknown | null; set: (key: string, value: unknown) => unknown | null; watch: (key: string, callback: IListener) => void; unwatch: (key: string, callback?: IListener) => void; } class LocalStorage implements ILocalStorage { #listeners: Array<{ key: string; callback: IListener }> = []; #canAccessLocalStorage(): boolean { return typeof window !== 'undefined'; } get(key: string): unknown | null { yup.string().required().validateSync(key); if (!this.#canAccessLocalStorage()) { return null; } else { const value = localStorage.getItem(key); return _.isNil(value) ? null : JSON.parse(value); } } set(key: string, value: unknown): unknown | null { yup.string().required().validateSync(key); if (this.#canAccessLocalStorage()) { localStorage.setItem(key, JSON.stringify(value)); return value; } else { return null; } } watch(key: string, callback: IListener): void { yup.string().required().validateSync(key); listenerSchema.validateSync(callback); if (this.#canAccessLocalStorage()) { this.#listeners.push({ key: key, callback: callback }); window.addEventListener('storage', (e) => { if (e.storageArea === localStorage && e.key === key) { callback(this.get(key)); } }); } } unwatch(key: string, callback?: IListener | undefined): void { yup.string().required().validateSync(key); listenerSchema.optional().default(_.identity).validateSync(callback); if (this.#canAccessLocalStorage()) { if (callback !== undefined) { this.#listeners = _.reject(this.#listeners, (listener) => { return listener.key === key && listener.callback === callback; }); } else { this.#listeners = _.reject(this.#listeners, (listener) => { return listener.key === key; }); } } } } export { LocalStorage };