UNPKG

@towns-protocol/sdk

Version:

For more details, visit the following resources:

121 lines 6.36 kB
import { PersistedSyncedStreamSchema, } from '@towns-protocol/proto'; import { Stream } from './stream'; import { bin_equal, bin_toHexString, dlog } from '@towns-protocol/dlog'; import { isDefined } from './check'; import { create } from '@bufbuild/protobuf'; export class SyncedStream extends Stream { log; get isUpToDate() { return this.streamsView.streamStatus.get(this.streamId).isUpToDate; } set isUpToDate(value) { this.streamsView.streamStatus.setIsUpToDate(this.streamId, value); } persistenceStore; constructor(userId, streamId, streamsView, clientEmitter, logEmitFromStream, persistenceStore) { super(userId, streamId, streamsView, clientEmitter, logEmitFromStream); this.log = dlog('csb:syncedStream', { defaultEnabled: false }).extend(userId); this.persistenceStore = persistenceStore; } async initializeFromPersistence(persistedData) { const loadedStream = persistedData ?? (await this.persistenceStore.loadStream(this.streamId)); if (!loadedStream) { this.log('No persisted data found for stream', this.streamId, persistedData); return false; } try { super.initialize(loadedStream.persistedSyncedStream.syncCookie, loadedStream.persistedSyncedStream.minipoolEvents, loadedStream.snapshot, loadedStream.miniblocks, loadedStream.prependedMiniblocks, loadedStream.miniblocks[0].header.prevSnapshotMiniblockNum, loadedStream.cleartexts); } catch (e) { this.log('Error initializing from persistence', this.streamId, e); return false; } return true; } // TODO: possibly a bug. Stream interface expects a non promise initialize. // eslint-disable-next-line @typescript-eslint/no-misused-promises async initialize(nextSyncCookie, events, snapshot, miniblocks, prependedMiniblocks, prevSnapshotMiniblockNum, cleartexts) { super.initialize(nextSyncCookie, events, snapshot, miniblocks, prependedMiniblocks, prevSnapshotMiniblockNum, cleartexts); const cachedSyncedStream = create(PersistedSyncedStreamSchema, { syncCookie: nextSyncCookie, lastSnapshotMiniblockNum: miniblocks[0].header.miniblockNum, minipoolEvents: events, lastMiniblockNum: miniblocks[miniblocks.length - 1].header.miniblockNum, }); await this.persistenceStore.saveSyncedStream(this.streamId, cachedSyncedStream); await this.persistenceStore.saveMiniblocks(this.streamId, miniblocks, 'forward'); await this.persistenceStore.saveSnapshot(this.streamId, miniblocks[0].header.miniblockNum, snapshot); this.markUpToDate(); } async initializeFromResponse(response) { this.log('initializing from response', this.streamId); const cleartexts = await this.persistenceStore.getCleartexts(response.eventIds); await this.initialize(response.streamAndCookie.nextSyncCookie, response.streamAndCookie.events, response.snapshot, response.streamAndCookie.miniblocks, [], response.prevSnapshotMiniblockNum, cleartexts); this.markUpToDate(); } async appendEvents(events, nextSyncCookie, snapshot, cleartexts) { const minipoolEvents = new Map(Array.from(this.view.minipoolEvents.entries()) .filter(([_, event]) => isDefined(event.remoteEvent)) .map(([hash, event]) => [hash, event.remoteEvent])); await super.appendEvents(events, nextSyncCookie, snapshot, cleartexts); for (const event of events) { const payload = event.event.payload; switch (payload.case) { case 'miniblockHeader': { await this.onMiniblockHeader(payload.value, event, event.hash, snapshot, minipoolEvents); break; } default: minipoolEvents.set(event.hashStr, event); break; } } this.markUpToDate(); } async onMiniblockHeader(miniblockHeader, miniblockEvent, hash, snapshot, viewMinipoolEvents) { this.log('Received miniblock header', miniblockHeader.miniblockNum.toString(), this.streamId); const eventHashes = miniblockHeader.eventHashes.map(bin_toHexString); const events = eventHashes.map((hash) => viewMinipoolEvents.get(hash)).filter(isDefined); if (events.length !== eventHashes.length) { throw new Error(`Couldn't find event for hash in miniblock ${miniblockHeader.miniblockNum.toString()} ${eventHashes.join(', ')} ${Array.from(viewMinipoolEvents.keys()).join(', ')}`); } const miniblock = { hash: hash, header: miniblockHeader, events: [...events, miniblockEvent], }; if (snapshot && bin_equal(snapshot.hash, miniblockHeader.snapshotHash)) { await this.persistenceStore.saveSnapshot(this.streamId, miniblock.header.miniblockNum, snapshot.snapshot); } else if (miniblockHeader.snapshot !== undefined) { await this.persistenceStore.saveSnapshot(this.streamId, miniblockHeader.miniblockNum, miniblockHeader.snapshot); } await this.persistenceStore.saveMiniblock(this.streamId, miniblock); const syncCookie = this.view.syncCookie; if (!syncCookie) { return; } const minipoolEvents = Array.from(this.view.minipoolEvents.values()); const lastSnapshotMiniblockNum = miniblock.header.snapshot !== undefined || miniblock.header.snapshotHash !== undefined ? miniblock.header.miniblockNum : miniblock.header.prevSnapshotMiniblockNum; const cachedSyncedStream = create(PersistedSyncedStreamSchema, { syncCookie: syncCookie, lastSnapshotMiniblockNum: lastSnapshotMiniblockNum, minipoolEvents: minipoolEvents, lastMiniblockNum: miniblock.header.miniblockNum, }); await this.persistenceStore.saveSyncedStream(this.streamId, cachedSyncedStream); } markUpToDate() { if (this.isUpToDate) { return; } this.isUpToDate = true; this.emit('streamUpToDate', this.streamId); } resetUpToDate() { this.isUpToDate = false; } } //# sourceMappingURL=syncedStream.js.map