@river-build/sdk
Version:
For more details, visit the following resources:
111 lines • 4.54 kB
JavaScript
import EventEmitter from 'events';
import { StreamStateView } from './streamStateView';
import { isLocalEvent } from './types';
export class Stream extends EventEmitter {
clientEmitter;
logEmitFromStream;
userId;
_view;
get view() {
return this._view;
}
stopped = false;
constructor(userId, streamId, clientEmitter, logEmitFromStream) {
super();
this.clientEmitter = clientEmitter;
this.logEmitFromStream = logEmitFromStream;
this.userId = userId;
this._view = new StreamStateView(userId, streamId);
}
get streamId() {
return this._view.streamId;
}
get syncCookie() {
return this.view.syncCookie;
}
/**
* NOTE: Separating initial rollup from the constructor allows consumer to subscribe to events
* on the new stream event and still access this object through Client.streams.
*/
initialize(nextSyncCookie, minipoolEvents, snapshot, miniblocks, prependedMiniblocks, prevSnapshotMiniblockNum, cleartexts) {
// grab any local events from the previous view that haven't been processed
const localEvents = this._view.timeline
.filter(isLocalEvent)
.filter((e) => e.hashStr.startsWith('~'));
this._view = new StreamStateView(this.userId, this.streamId);
this._view.initialize(nextSyncCookie, minipoolEvents, snapshot, miniblocks, prependedMiniblocks, prevSnapshotMiniblockNum, cleartexts, localEvents, this);
}
stop() {
this.removeAllListeners();
this.stopped = true;
}
async appendEvents(events, nextSyncCookie, cleartexts) {
this._view.appendEvents(events, nextSyncCookie, cleartexts, this);
}
prependEvents(miniblocks, cleartexts, terminus) {
this._view.prependEvents(miniblocks, cleartexts, terminus, this, this);
}
appendLocalEvent(channelMessage, status) {
return this._view.appendLocalEvent(channelMessage, status, this);
}
updateDecryptedContent(eventId, content) {
return this._view.updateDecryptedContent(eventId, content, this);
}
updateDecryptedContentError(eventId, content) {
return this._view.updateDecryptedContentError(eventId, content, this);
}
updateLocalEvent(localId, parsedEventHash, status) {
return this._view.updateLocalEvent(localId, parsedEventHash, status, this);
}
emit(event, ...args) {
if (this.stopped) {
return false;
}
this.logEmitFromStream(event, ...args);
this.clientEmitter.emit(event, ...args);
return super.emit(event, ...args);
}
/**
* Memberships are processed on block boundaries, so we need to wait for the next block to be processed
* passing an undefined userId will wait for the membership to be updated for the current user
*/
async waitForMembership(membership, inUserId) {
// check to see if we're already in that state
const userId = inUserId ?? this.userId;
// wait for a membership updated event, event, check again
await this.waitFor('streamMembershipUpdated', () => this._view.getMembers().isMember(membership, userId));
}
/**
* Wait for a stream event to be emitted
* optionally pass a condition function to check the event args
*/
async waitFor(event, condition, opts = { timeoutMs: 20000 }) {
if (condition()) {
return;
}
this.logEmitFromStream('waitFor', this.streamId, event);
return new Promise((resolve, reject) => {
// Set up the event listener
const handler = () => {
if (condition()) {
this.logEmitFromStream('waitFor success', this.streamId, event);
this.off(event, handler);
this.off('streamInitialized', handler);
clearTimeout(timeout);
resolve();
}
};
const timeoutError = new Error(`waitFor timeout waiting for ${event}`);
// Set up the timeout
const timeout = setTimeout(() => {
this.logEmitFromStream('waitFor timeout', this.streamId, event);
this.off(event, handler);
this.off('streamInitialized', handler);
reject(timeoutError);
}, opts.timeoutMs);
this.on(event, handler);
this.on('streamInitialized', handler);
});
}
}
//# sourceMappingURL=stream.js.map