UNPKG

@vyer-technologies/offline-first

Version:
192 lines 7.26 kB
import { Cache } from '@vyer-technologies/cache-persist'; import { NetInfo } from '@vyer-technologies/netinfo'; const isServer = typeof window === 'undefined'; const defaultOfflineOptions = { manualExecution: false, start: (_mutations) => Promise.resolve(_mutations), finish: (_mutations, _error) => Promise.resolve(undefined), onExecute: (mutation) => Promise.resolve(mutation), onComplete: (_options) => Promise.resolve(true), onDiscard: (_options) => Promise.resolve(true), compare: (v1, v2) => v1.fetchTime - v2.fetchTime, onPublish: (offlineRecord) => Promise.resolve(offlineRecord), execute: (_offlineRecord) => Promise.reject(new Error('set execute offline options')), }; export class OfflineFirst { offlineStore; busy; offlineOptions = defaultOfflineOptions; online = isServer; rehydrated = isServer; promisesRestore; disposeListener; constructor(persistOptions = {}) { const persistOptionsStoreOffline = { prefix: 'offline-first', serialize: true, ...persistOptions, }; this.offlineStore = new Cache(persistOptionsStoreOffline); if (this.rehydrated) { this.promisesRestore = Promise.resolve(true); } } setOfflineOptions(offlineOptions) { this.offlineOptions = { ...defaultOfflineOptions, ...offlineOptions, }; } purge() { this.offlineStore.purge(); return this.offlineStore.flush().then(() => { this.notify(); return true; }); } isManualExecution() { const { manualExecution } = this.offlineOptions; return manualExecution; } dispose() { this.disposeListener && this.disposeListener(); this.promisesRestore = null; this.rehydrated = false; } hydrate() { if (!this.promisesRestore) { this.promisesRestore = Promise.all([NetInfo.fetch(), this.offlineStore.restore()]) .then((result) => { const finalize = () => { this.disposeListener = NetInfo.addEventListener((state) => { const { isConnected } = state; if (this.online !== isConnected && isConnected && !this.isManualExecution()) { this.process(); } this.online = isConnected; }); this.notify(); this.rehydrated = true; return true; }; const { isConnected } = result[0]; this.online = isConnected; if (isConnected && !this.isManualExecution()) { return this.process().then(finalize); } return finalize(); }) .catch((error) => { this.rehydrated = false; this.promisesRestore = null; throw error; }); } return this.promisesRestore; } isOnline() { return this.online; } subscribe(callback) { return this.offlineStore.subscribe(callback); } notify() { const { compare } = this.offlineOptions; this.offlineStore.notify({ state: Object.values(this.getState()).sort(compare) }); } getState() { return this.offlineStore.getState(); } remove(id) { this.offlineStore.remove(id); return this.offlineStore.flush().then(() => this.notify()); } set(id, value) { this.offlineStore.set(id, value); return this.offlineStore.flush().then(() => this.notify()); } getListMutation() { const { compare } = this.offlineOptions; const requests = Object.assign({}, this.getState()); return Object.values(requests).sort(compare); } process() { if (!this.busy) { const { start, finish, onExecute } = this.offlineOptions; const listMutation = this.getListMutation(); let parallelPromises = []; this.busy = start(listMutation).then(async (startMutations) => { try { for (const mutation of startMutations) { const processMutation = await onExecute(mutation); if (processMutation) { if (!processMutation.serial) { parallelPromises.push(this.executeMutation(processMutation)); } else { await Promise.all(parallelPromises) .then(() => this.executeMutation(processMutation)) .catch((error) => { throw error; }); parallelPromises = []; } } } if (parallelPromises.length > 0) { await Promise.all(parallelPromises); } return finish(this.getListMutation()); } catch (error) { return finish(this.getListMutation(), error); } finally { this.busy = undefined; } }); } return this.busy; } executeMutation(offlineRecord) { const { execute, onComplete, onDiscard } = this.offlineOptions; const { id } = offlineRecord; offlineRecord.error = undefined; offlineRecord.retry = offlineRecord.retry ? offlineRecord.retry + 1 : 0; return this.set(id, { ...offlineRecord }).then(() => { return execute(offlineRecord) .then(async (response) => { offlineRecord.error = undefined; return onComplete({ offlineRecord, response }).then((complete) => { if (complete) { return this.remove(id); } return this.set(id, { ...offlineRecord }); }); }) .catch((error) => { offlineRecord.error = error; return onDiscard({ offlineRecord, error }).then((discard) => { if (discard) { return this.remove(id); } return this.set(id, { ...offlineRecord }); }); }); }); } publish(options) { const { onPublish } = this.offlineOptions; const id = options.id ? options.id : `${this.offlineStore.getAllKeys().length}`; const { request, serial } = options; const fetchTime = Date.now(); return onPublish({ id, request, fetchTime, serial }).then((offlineRecord) => { return this.set(offlineRecord.id, offlineRecord) .then(() => offlineRecord) .catch((error) => { throw error; }); }); } } //# sourceMappingURL=index.js.map