@towns-protocol/sdk
Version:
For more details, visit the following resources:
121 lines • 6.36 kB
JavaScript
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