UNPKG

@drift-labs/sdk

Version:
172 lines (171 loc) 7.38 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebSocketAccountSubscriber = void 0; const utils_1 = require("./utils"); class WebSocketAccountSubscriber { constructor(accountName, program, accountPublicKey, decodeBuffer, resubOpts, commitment) { var _a; this.isUnsubscribing = false; this.accountName = accountName; this.logAccountName = `${accountName}-${accountPublicKey.toBase58()}`; this.program = program; this.accountPublicKey = accountPublicKey; this.decodeBufferFn = decodeBuffer; this.resubOpts = resubOpts; if (((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs) < 1000) { console.log(`resubTimeoutMs should be at least 1000ms to avoid spamming resub ${this.logAccountName}`); } this.receivingData = false; this.commitment = commitment !== null && commitment !== void 0 ? commitment : this.program.provider.opts.commitment; } async subscribe(onChange) { var _a, _b; if (this.listenerId != null || this.isUnsubscribing) { if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) { console.log(`[${this.logAccountName}] Subscribe returning early - listenerId=${this.listenerId}, isUnsubscribing=${this.isUnsubscribing}`); } return; } this.onChange = onChange; if (!this.dataAndSlot) { await this.fetch(); } this.listenerId = this.program.provider.connection.onAccountChange(this.accountPublicKey, (accountInfo, context) => { var _a; if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs) { this.receivingData = true; clearTimeout(this.timeoutId); this.handleRpcResponse(context, accountInfo); this.setTimeout(); } else { this.handleRpcResponse(context, accountInfo); } }, this.commitment); if ((_b = this.resubOpts) === null || _b === void 0 ? void 0 : _b.resubTimeoutMs) { this.receivingData = true; this.setTimeout(); } } setData(data, slot) { const newSlot = slot || 0; if (this.dataAndSlot && this.dataAndSlot.slot > newSlot) { return; } this.dataAndSlot = { data, slot, }; } setTimeout() { var _a; if (!this.onChange) { throw new Error('onChange callback function must be set'); } this.timeoutId = setTimeout(async () => { var _a, _b, _c, _d; if (this.isUnsubscribing) { // If we are in the process of unsubscribing, do not attempt to resubscribe if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) { console.log(`[${this.logAccountName}] Timeout fired but isUnsubscribing=true, skipping resubscribe`); } return; } if (this.receivingData) { if ((_b = this.resubOpts) === null || _b === void 0 ? void 0 : _b.logResubMessages) { console.log(`No ws data from ${this.logAccountName} in ${this.resubOpts.resubTimeoutMs}ms, resubscribing - listenerId=${this.listenerId}, isUnsubscribing=${this.isUnsubscribing}`); } await this.unsubscribe(true); this.receivingData = false; await this.subscribe(this.onChange); if ((_c = this.resubOpts) === null || _c === void 0 ? void 0 : _c.logResubMessages) { console.log(`[${this.logAccountName}] Resubscribe completed - receivingData=${this.receivingData}, listenerId=${this.listenerId}, isUnsubscribing=${this.isUnsubscribing}`); } } else { if ((_d = this.resubOpts) === null || _d === void 0 ? void 0 : _d.logResubMessages) { console.log(`[${this.logAccountName}] Timeout fired but receivingData=false, skipping resubscribe`); } } }, (_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs); } async fetch() { const rpcResponse = await this.program.provider.connection.getAccountInfoAndContext(this.accountPublicKey, this.program.provider.opts.commitment); this.handleRpcResponse(rpcResponse.context, rpcResponse === null || rpcResponse === void 0 ? void 0 : rpcResponse.value); } handleRpcResponse(context, accountInfo) { const newSlot = context.slot; let newBuffer = undefined; if (accountInfo) { newBuffer = accountInfo.data; } if (!this.bufferAndSlot) { this.bufferAndSlot = { buffer: newBuffer, slot: newSlot, }; if (newBuffer) { const account = this.decodeBuffer(newBuffer); this.dataAndSlot = { data: account, slot: newSlot, }; this.onChange(account); } return; } if (newSlot < this.bufferAndSlot.slot) { return; } const oldBuffer = this.bufferAndSlot.buffer; if (newBuffer && (!oldBuffer || !newBuffer.equals(oldBuffer))) { this.bufferAndSlot = { buffer: newBuffer, slot: newSlot, }; const account = this.decodeBuffer(newBuffer); this.dataAndSlot = { data: account, slot: newSlot, }; this.onChange(account); } } decodeBuffer(buffer) { if (this.decodeBufferFn) { return this.decodeBufferFn(buffer); } else { return this.program.account[this.accountName].coder.accounts.decode((0, utils_1.capitalize)(this.accountName), buffer); } } unsubscribe(onResub = false) { if (!onResub && this.resubOpts) { this.resubOpts.resubTimeoutMs = undefined; } this.isUnsubscribing = true; clearTimeout(this.timeoutId); this.timeoutId = undefined; if (this.listenerId != null) { const promise = Promise.race([ this.program.provider.connection.removeAccountChangeListener(this.listenerId), new Promise((_, reject) => setTimeout(() => reject(new Error(`Unsubscribe timeout for account ${this.logAccountName}`)), 10000)), ]) .then(() => { this.listenerId = undefined; this.isUnsubscribing = false; }) .catch((error) => { console.error(`[${this.logAccountName}] Unsubscribe failed, forcing cleanup - listenerId=${this.listenerId}, isUnsubscribing=${this.isUnsubscribing}`, error); this.listenerId = undefined; this.isUnsubscribing = false; }); return promise; } else { this.isUnsubscribing = false; } } } exports.WebSocketAccountSubscriber = WebSocketAccountSubscriber;