UNPKG

jinaga

Version:

Data management for web and mobile applications.

89 lines (82 loc) 3.12 kB
import { Network } from "../managers/NetworkManager"; import { Storage, FactEnvelope, FactReference } from "../storage"; import { Trace } from "../util/trace"; export class Subscriber { private refCount: number = 0; private bookmark: string = ""; private resolved: boolean = false; private disconnect: (() => void) | undefined; private timer: NodeJS.Timer | undefined; constructor( private readonly feed: string, private readonly network: Network, private readonly store: Storage, private readonly notifyFactsAdded: (envelopes: FactEnvelope[]) => Promise<void>, private readonly refreshIntervalSeconds: number ) {} addRef() { this.refCount++; return this.refCount === 1; } release() { this.refCount--; return this.refCount === 0; } async start(): Promise<void> { this.bookmark = await this.store.loadBookmark(this.feed); await new Promise<void>((resolve, reject) => { this.resolved = false; // Refresh the connection at the configured interval. this.disconnect = this.connectToFeed(resolve, reject); this.timer = setInterval(() => { if (this.disconnect) { this.disconnect(); } this.disconnect = this.connectToFeed(resolve, reject); }, this.refreshIntervalSeconds * 1000); }); } stop() { if (this.timer) { clearInterval(this.timer); this.timer = undefined; } if (this.disconnect) { this.disconnect(); this.disconnect = undefined; } } private connectToFeed(resolve: (value: void | PromiseLike<void>) => void, reject: (reason?: any) => void) { return this.network.streamFeed(this.feed, this.bookmark, async (factReferences, nextBookmark) => { const knownFactReferences: FactReference[] = await this.store.whichExist(factReferences); const unknownFactReferences: FactReference[] = factReferences.filter(fr => !knownFactReferences.includes(fr)); if (unknownFactReferences.length > 0) { const graph = await this.network.load(unknownFactReferences); await this.store.save(graph); if (graph.length > 0) { Trace.counter("facts_saved", graph.length); } await this.store.saveBookmark(this.feed, nextBookmark); this.bookmark = nextBookmark; await this.notifyFactsAdded(graph); } else { // Treat empty-reference responses as bookmark advance from WS graph transport if (nextBookmark && nextBookmark !== this.bookmark) { await this.store.saveBookmark(this.feed, nextBookmark); this.bookmark = nextBookmark; } } if (!this.resolved) { this.resolved = true; resolve(); } }, err => { // Do not reject on errors to allow FetchConnection's retry logic to work. // The promise will resolve when the first successful data is received. // Don't log AbortError as it's expected during periodic reconnection. if (err.name !== 'AbortError') { Trace.warn(`Subscriber connection error: ${err}`); } }, this.refreshIntervalSeconds); } }