UNPKG

@drift-labs/sdk

Version:
227 lines (226 loc) • 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EventSubscriber = void 0; const types_1 = require("./types"); const txEventCache_1 = require("./txEventCache"); const eventList_1 = require("./eventList"); const pollingLogProvider_1 = require("./pollingLogProvider"); const fetchLogs_1 = require("./fetchLogs"); const webSocketLogProvider_1 = require("./webSocketLogProvider"); const events_1 = require("events"); const sort_1 = require("./sort"); const parse_1 = require("./parse"); const eventsServerLogProvider_1 = require("./eventsServerLogProvider"); class EventSubscriber { constructor(connection, program, options = types_1.DefaultEventSubscriptionOptions) { var _a; this.connection = connection; this.program = program; this.options = options; this.awaitTxPromises = new Map(); this.awaitTxResolver = new Map(); this.options = Object.assign({}, types_1.DefaultEventSubscriptionOptions, options); this.address = (_a = this.options.address) !== null && _a !== void 0 ? _a : program.programId; this.txEventCache = new txEventCache_1.TxEventCache(this.options.maxTx); this.eventListMap = new Map(); this.eventEmitter = new events_1.EventEmitter(); this._currentProviderType = this.options.logProviderConfig.type; this.initializeLogProvider(); } get currentProviderType() { return this._currentProviderType; } initializeLogProvider(subscribe = false) { const logProviderConfig = this.options.logProviderConfig; if (this._currentProviderType === 'websocket') { this.logProvider = new webSocketLogProvider_1.WebSocketLogProvider( // @ts-ignore this.connection, this.address, this.options.commitment, this.options.logProviderConfig.resubTimeoutMs); } else if (this._currentProviderType === 'polling') { const frequency = 'frequency' in logProviderConfig ? logProviderConfig.frequency : logProviderConfig.fallbackFrequency; const batchSize = 'batchSize' in logProviderConfig ? logProviderConfig.batchSize : logProviderConfig.fallbackBatchSize; this.logProvider = new pollingLogProvider_1.PollingLogProvider( // @ts-ignore this.connection, this.address, this.options.commitment, frequency, batchSize); } else if (this._currentProviderType === 'events-server') { this.logProvider = new eventsServerLogProvider_1.EventsServerLogProvider(logProviderConfig.url, this.options.eventTypes, this.options.address ? this.options.address.toString() : undefined); } else { throw new Error(`Invalid log provider type: ${this._currentProviderType}`); } if (subscribe) { this.logProvider.subscribe((txSig, slot, logs, mostRecentBlockTime, txSigIndex) => { this.handleTxLogs(txSig, slot, logs, mostRecentBlockTime, this._currentProviderType === 'events-server', txSigIndex); }, true); } } populateInitialEventListMap() { for (const eventType of this.options.eventTypes) { this.eventListMap.set(eventType, new eventList_1.EventList(eventType, this.options.maxEventsPerType, (0, sort_1.getSortFn)(this.options.orderBy, this.options.orderDir), this.options.orderDir)); } } /** * Implements fallback logic for reconnecting to LogProvider. Currently terminates at polling, * could be improved to try the original type again after some cooldown. */ updateFallbackProviderType(reconnectAttempts, maxReconnectAttempts) { if (reconnectAttempts < maxReconnectAttempts) { return; } let nextProviderType = this._currentProviderType; if (this._currentProviderType === 'events-server') { nextProviderType = 'websocket'; } else if (this._currentProviderType === 'websocket') { nextProviderType = 'polling'; } else if (this._currentProviderType === 'polling') { nextProviderType = 'polling'; } console.log(`EventSubscriber: Failing over providerType ${this._currentProviderType} to ${nextProviderType}`); this._currentProviderType = nextProviderType; } async subscribe() { try { if (this.logProvider.isSubscribed()) { return true; } this.populateInitialEventListMap(); if (this.options.logProviderConfig.type === 'websocket' || this.options.logProviderConfig.type === 'events-server') { const logProviderConfig = this.options .logProviderConfig; if (this.logProvider.eventEmitter) { this.logProvider.eventEmitter.on('reconnect', async (reconnectAttempts) => { if (reconnectAttempts > logProviderConfig.maxReconnectAttempts) { console.log(`EventSubscriber: Reconnect attempts ${reconnectAttempts}/${logProviderConfig.maxReconnectAttempts}, reconnecting...`); this.logProvider.eventEmitter.removeAllListeners('reconnect'); await this.unsubscribe(); this.updateFallbackProviderType(reconnectAttempts, logProviderConfig.maxReconnectAttempts); this.initializeLogProvider(true); } }); } } this.logProvider.subscribe((txSig, slot, logs, mostRecentBlockTime, txSigIndex) => { this.handleTxLogs(txSig, slot, logs, mostRecentBlockTime, this._currentProviderType === 'events-server', txSigIndex); }, true); return true; } catch (e) { console.error('Error fetching previous txs in event subscriber'); console.error(e); return false; } } handleTxLogs(txSig, slot, logs, mostRecentBlockTime, fromEventsServer = false, txSigIndex = undefined) { if (!fromEventsServer && this.txEventCache.has(txSig)) { return; } const wrappedEvents = this.parseEventsFromLogs(txSig, slot, logs, txSigIndex); for (const wrappedEvent of wrappedEvents) { this.eventListMap.get(wrappedEvent.eventType).insert(wrappedEvent); } // dont emit event till we've added all the events to the eventListMap for (const wrappedEvent of wrappedEvents) { this.eventEmitter.emit('newEvent', wrappedEvent); } if (this.awaitTxPromises.has(txSig)) { this.awaitTxPromises.delete(txSig); this.awaitTxResolver.get(txSig)(); this.awaitTxResolver.delete(txSig); } if (!this.lastSeenSlot || slot > this.lastSeenSlot) { this.lastSeenTxSig = txSig; this.lastSeenSlot = slot; } if (this.lastSeenBlockTime === undefined || mostRecentBlockTime > this.lastSeenBlockTime) { this.lastSeenBlockTime = mostRecentBlockTime; } this.txEventCache.add(txSig, wrappedEvents); } async fetchPreviousTx(fetchMax) { if (!this.options.untilTx && !fetchMax) { return; } let txFetched = 0; let beforeTx = undefined; const untilTx = this.options.untilTx; while (txFetched < this.options.maxTx) { const response = await (0, fetchLogs_1.fetchLogs)( // @ts-ignore this.connection, this.address, this.options.commitment === 'finalized' ? 'finalized' : 'confirmed', beforeTx, untilTx); if (response === undefined) { break; } txFetched += response.transactionLogs.length; beforeTx = response.earliestTx; for (const { txSig, slot, logs } of response.transactionLogs) { this.handleTxLogs(txSig, slot, logs, response.mostRecentBlockTime); } } } async unsubscribe() { this.eventListMap.clear(); this.txEventCache.clear(); this.awaitTxPromises.clear(); this.awaitTxResolver.clear(); return await this.logProvider.unsubscribe(true); } parseEventsFromLogs(txSig, slot, logs, txSigIndex) { const records = []; // @ts-ignore const events = (0, parse_1.parseLogs)(this.program, logs); let runningEventIndex = 0; for (const event of events) { // @ts-ignore const expectRecordType = this.eventListMap.has(event.name); if (expectRecordType) { event.data.txSig = txSig; event.data.slot = slot; event.data.eventType = event.name; event.data.txSigIndex = txSigIndex !== undefined ? txSigIndex : runningEventIndex; records.push(event.data); } runningEventIndex++; } return records; } awaitTx(txSig) { if (this.awaitTxPromises.has(txSig)) { return this.awaitTxPromises.get(txSig); } if (this.txEventCache.has(txSig)) { return Promise.resolve(); } const promise = new Promise((resolve) => { this.awaitTxResolver.set(txSig, resolve); }); this.awaitTxPromises.set(txSig, promise); return promise; } getEventList(eventType) { return this.eventListMap.get(eventType); } /** * This requires the EventList be cast to an array, which requires reallocation of memory. * Would bias to using getEventList over getEvents * * @param eventType */ getEventsArray(eventType) { return this.eventListMap.get(eventType).toArray(); } getEventsByTx(txSig) { return this.txEventCache.get(txSig); } } exports.EventSubscriber = EventSubscriber;