import { debounce, merge } from 'lodash'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; const OLD_KEY = 'gl-bulk-imports-import-state'; export const KEY = 'gl-bulk-imports-import-state-v2'; export const DEBOUNCE_INTERVAL = DEFAULT_DEBOUNCE_AND_THROTTLE_MS; export class LocalStorageCache { constructor({ storage = window.localStorage } = {}) { this.storage = storage; this.cache = this.loadCacheFromStorage(); try { // remove old storage data this.storage.removeItem(OLD_KEY); } catch { // empty catch intended } // cache for searching data by jobid this.jobsLookupCache = {}; } loadCacheFromStorage() { try { const storage = JSON.parse(this.storage.getItem(KEY)) ?? {}; Object.values(storage).forEach((entry) => { if (entry.progress && !('message' in entry.progress)) { // eslint-disable-next-line no-param-reassign entry.progress.message = ''; } }); return storage; } catch { return {}; } } set(webUrl, data) { this.cache[webUrl] = data; this.saveCacheToStorage(); // There are changes to jobIds, drop cache this.jobsLookupCache = {}; } get(webUrl) { return this.cache[webUrl]; } getCacheKeysByJobId(jobId) { // this is invoked by polling, so we would like to cache results if (!this.jobsLookupCache[jobId]) { this.jobsLookupCache[jobId] = Object.keys(this.cache).filter( (url) => this.cache[url]?.progress.id === jobId, ); } return this.jobsLookupCache[jobId]; } updateStatusByJobId(jobId, status) { this.getCacheKeysByJobId(jobId).forEach((webUrl) => this.set(webUrl, { ...(this.get(webUrl) ?? {}), progress: { id: jobId, status, }, }), ); this.saveCacheToStorage(); } saveCacheToStorage = debounce(() => { try { // storage might be changed in other tab so fetch first this.storage.setItem(KEY, JSON.stringify(merge({}, this.loadCacheFromStorage(), this.cache))); } catch { // empty catch intentional: storage might be unavailable or full } }, DEBOUNCE_INTERVAL); }