@drift-labs/sdk
Version:
SDK for Drift Protocol
172 lines (171 loc) • 7.38 kB
JavaScript
"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;