import {CacheHandler, LitElementStateService, StateChange} from '../index.js'; import {isExceptionFromDeepReduce} from '../litElementState.helpers.js'; import {DeepPartial} from 'ts-essentials'; const LOCALSTORAGE_PREFIX = 'lit-state'; class LocalStorageCacheHandler implements CacheHandler { name = 'localstorage'; private localStorageKeys = new Set(); async load(stateServiceInstance: LitElementStateService): Promise> { const res = {} as DeepPartial; const fullPrefix = this.getFullPrefix(stateServiceInstance); for (const key in localStorage) { if (key.startsWith(fullPrefix)) { this.localStorageKeys.add(key); const path = key.substring(fullPrefix.length).split('.'); const entry = JSON.parse(localStorage.getItem(key)!); switch (entry.t) { case 'boolean': this.setValue(res, path, entry.v === true); break; case 'number': this.setValue(res, path, +entry.v); break; case 'string': case 'object': case 'array': this.setValue(res, path, entry.v); break; } } } return res as any; } private setValue(object: any, path: string[], value: any) { path.forEach((segment, index) => { if (index === path.length - 1) { object[segment] = value as any; } else { if (!object.hasOwnProperty(segment)) { object[segment] = {} as any; } object = object[segment]; } }); }; async set(change: StateChange, stateServiceInstance: LitElementStateService) { let prependedCount = 1; const path = [ LOCALSTORAGE_PREFIX ]; if (!!stateServiceInstance?.config?.cache?.name) { path.push(stateServiceInstance.config.cache.name); prependedCount++; } this.setRecursive(change, path, stateServiceInstance, prependedCount); } private setRecursive(change: any, path: string[], stateServiceInstance: LitElementStateService, prependedCount: number) { for (const key in change as any) { const fullPath = [ ...path, key ]; const pathString = fullPath.join('.'); let isCustomException = false; for (const regEx of stateServiceInstance.config?.cache?.exceptions || []) { if (regEx.test(key)) isCustomException = true; } if (!isCustomException && !isExceptionFromDeepReduce(change[key])) { if (change[key] && typeof change[key] === 'object') { if (Array.isArray(change[key]) || '_arrayOperation' in change[key]) { let newArray = stateServiceInstance.get(fullPath.slice(prependedCount) as any); localStorage.setItem(pathString, JSON.stringify({ v: newArray, t: 'array' })); if (!this.localStorageKeys.has(pathString)) this.localStorageKeys.add(pathString); } else { if ('_reducerMode' in change[key] && change[key]._reducerMode === 'replace') { this.unset(pathString); delete change[key]._reducerMode; } const newPart = { ...change[key] }; this.setRecursive(newPart, fullPath, stateServiceInstance, prependedCount); } } else { if (change[key] === null || change[key] === undefined) { this.unset(pathString); } else { localStorage.setItem(pathString, JSON.stringify({ v: change[key], t: typeof change[key] })); if (!this.localStorageKeys.has(pathString)) this.localStorageKeys.add(pathString); } } } else { this.unset(pathString); } } } private unset(path: string) { for (let localStorageKey of this.localStorageKeys.values()) { if (localStorageKey.startsWith(path)) { localStorage.removeItem(localStorageKey); this.localStorageKeys.delete(localStorageKey); } } } private getFullPrefix(stateServiceInstance: LitElementStateService): string { return `${LOCALSTORAGE_PREFIX}.${ stateServiceInstance.config?.cache?.name ? `${stateServiceInstance.config?.cache?.name}.` : '' }`; } } export { LocalStorageCacheHandler };