/** * Browser-agnostic abstraction of key-value web storage. * * @file * @copyright 2020 Aleksej Komarov * @license MIT */ export const IMPL_MEMORY = 0; export const IMPL_HUB_STORAGE = 1; type StorageImplementation = typeof IMPL_MEMORY | typeof IMPL_HUB_STORAGE; type StorageBackend = { impl: StorageImplementation; get(key: string): Promise; set(key: string, value: any): Promise; remove(key: string): Promise; clear(): Promise; }; const testGeneric = (testFn: () => boolean) => (): boolean => { try { return Boolean(testFn()); } catch { return false; } }; const testHubStorage = testGeneric(() => window.hubStorage && !!window.hubStorage.getItem); class MemoryBackend implements StorageBackend { private store: Record; public impl: StorageImplementation; constructor() { this.impl = IMPL_MEMORY; this.store = {}; } async get(key: string): Promise { return this.store[key]; } async set(key: string, value: any): Promise { this.store[key] = value; } async remove(key: string): Promise { this.store[key] = undefined; } async clear(): Promise { this.store = {}; } } class HubStorageBackend implements StorageBackend { public impl: StorageImplementation; constructor() { this.impl = IMPL_HUB_STORAGE; } async get(key: string): Promise { const value = await window.hubStorage.getItem('paradise-' + key); if (typeof value === 'string') { return JSON.parse(value); } return undefined; } async set(key: string, value: any): Promise { window.hubStorage.setItem('paradise-' + key, JSON.stringify(value)); } async remove(key: string): Promise { window.hubStorage.removeItem('paradise-' + key); } async clear(): Promise { window.hubStorage.clear(); } } /** * Web Storage Proxy object, which selects the best backend available * depending on the environment. */ class StorageProxy implements StorageBackend { private backendPromise: Promise; public impl: StorageImplementation = IMPL_MEMORY; constructor() { this.backendPromise = (async () => { if (testHubStorage()) { return new HubStorageBackend(); } console.warn('No supported storage backend found. Using in-memory storage.'); return new MemoryBackend(); })(); } async get(key: string): Promise { const backend = await this.backendPromise; return backend.get(key); } async set(key: string, value: any): Promise { const backend = await this.backendPromise; return backend.set(key, value); } async remove(key: string): Promise { const backend = await this.backendPromise; return backend.remove(key); } async clear(): Promise { const backend = await this.backendPromise; return backend.clear(); } } export const storage = new StorageProxy();