UNPKG

@ton-api/streaming

Version:
373 lines (364 loc) 11.6 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { AccountsObserver: () => AccountsObserver, BlocksObserver: () => BlocksObserver, MempoolObserver: () => MempoolObserver, TraceObserver: () => TraceObserver, WebsocketStreamProvider: () => WebsocketStreamProvider }); module.exports = __toCommonJS(src_exports); // src/observers/observer.ts var Observer = class { constructor(stream) { this.stream = stream; } subscribers = []; socketSubscription; subscribe(triggers, callback) { if (!Array.isArray(triggers)) { triggers = [triggers]; } const subscriber = { callback, triggers }; this.subscribers = this.subscribers.concat(subscriber); if (!this.socketSubscription) { this.socketSubscription = this.stream.subscribe(this.onEvent); } this.afterSubscribed(subscriber); return () => { this.subscribers = this.subscribers.filter((s) => s !== subscriber); if (!this.subscribers.length) { this.socketSubscription?.(); } this.afterUnsubscribed(subscriber); }; } send(...args) { return this.stream.send(...args); } onEvent = (rpcEvent) => { const event = this.extractEvent(rpcEvent); if (!event) { return; } this.subscribers.forEach((s) => { if (s.triggers.some((t) => this.tryTrigger(t, event))) { s.callback(event); } }); }; }; // src/observers/blocks-observer.ts var BlocksObserver = class extends Observer { subscribe(workchain, callback) { return super.subscribe(workchain, callback); } afterSubscribed(subscriber) { if (subscriber.triggers.length !== 1) { throw new Error("BlocksObserver supports only one trigger-workchain"); } const wc = subscriber.triggers[0]; const needAddWc = this.subscribers.filter((s) => s !== subscriber).every((s) => !s.triggers.includes(wc)); if (needAddWc) { this.send("subscribe_block", [`workchain=${wc}`]); } } afterUnsubscribed({ triggers }) { if (triggers.length !== 1) { throw new Error("BlocksObserver supports only one trigger-workchain"); } const wc = triggers[0]; const needRemoveWc = this.subscribers.every((s) => !s.triggers.includes(wc)); if (needRemoveWc) { this.send("unsubscribe_block", [`workchain=${wc}`]); } } extractEvent(rpcEvent) { if (rpcEvent.method !== "block") { return; } return rpcEvent.params; } tryTrigger(trigger, event) { return event.workchain === trigger; } }; // src/observers/accounts-observer.ts var AccountsObserver = class extends Observer { afterSubscribed(subscriber) { const triggersToAdd = subscriber.triggers.filter( (trigger) => this.subscribers.filter((s) => s !== subscriber).every((sub) => { sub.triggers.every((subTrigger) => { if (trigger.account === subTrigger.account) { if (!subTrigger.operations?.length) { return false; } const isForAllOps = !trigger.operations?.length; const isNotIncluded = trigger.operations.some( (op) => !subTrigger.operations.includes(op) ); return isForAllOps || isNotIncluded; } return true; }); }) ); if (triggersToAdd.length) { const params = subscriber.triggers.map((t) => { if (!t.operations?.length) { return t.account; } return `${t.account};operations=${t.operations.join(",")}`; }); this.send("subscribe_account", params); } } afterUnsubscribed({ triggers }) { const accountsToRemove = triggers.filter( (t) => this.subscribers.every((s) => s.triggers.every((st) => st.account !== t.account)) ).map((t) => t.account); if (accountsToRemove.length) { this.send("unsubscribe_account", accountsToRemove); } } extractEvent(rpcEvent) { if (rpcEvent.method !== "account_transaction") { return; } return rpcEvent.params; } tryTrigger(trigger, event) { if (event.account_id === trigger.account) { if (event.operation === void 0) { return true; } return !!trigger.operations?.includes(event.operation); } return false; } }; // src/observers/mempool-observer.ts var MempoolObserver = class extends Observer { needToRemoveAllSubscription = false; /** * * @param accounts list of accounts to watch (in raw-form) or a single account (in raw-form) or 'all' to watch all the accounts * @param callback */ subscribe(accounts, callback) { return super.subscribe(accounts, callback); } afterSubscribed(subscriber) { if (subscriber.triggers.includes("all")) { this.send("subscribe_mempool"); return; } const accountsToAdd = subscriber.triggers.filter( (t) => this.subscribers.filter((s) => s !== subscriber).every((s) => !s.triggers.includes(t)) ); if (accountsToAdd.length) { this.send("subscribe_mempool", accountsToAdd.join(",")); } } afterUnsubscribed({ triggers }) { let accountsToRemove = triggers.filter( (t) => this.subscribers.every((s) => !s.triggers.includes(t)) ); if (accountsToRemove.includes("all")) { if (!this.subscribers.length) { this.send("unsubscribe_mempool"); return; } else { this.needToRemoveAllSubscription = true; } } if (!this.subscribers.length && this.needToRemoveAllSubscription) { this.send("unsubscribe_mempool"); return; } accountsToRemove = accountsToRemove.filter((t) => t !== "all"); if (accountsToRemove.length) { this.send("unsubscribe_mempool", accountsToRemove.join(",")); } } extractEvent(rpcEvent) { if (rpcEvent.method !== "mempool_message") { return; } return rpcEvent.params; } tryTrigger(trigger, event) { if (trigger === "all") { return true; } if (!event.involved_accounts) { return false; } return event.involved_accounts.some((acc) => acc.includes(trigger)); } }; // src/observers/trace-observer.ts var TraceObserver = class extends Observer { afterSubscribed(subscriber) { const accountsToAdd = subscriber.triggers.filter( (t) => this.subscribers.filter((s) => s !== subscriber).every((s) => !s.triggers.includes(t)) ); if (accountsToAdd.length) { this.send("subscribe_trace", accountsToAdd); } } afterUnsubscribed({ triggers }) { const accountsToRemove = triggers.filter( (t) => this.subscribers.every((s) => !s.triggers.includes(t)) ); if (accountsToRemove.length) { this.send("unsubscribe_trace", accountsToRemove); } } extractEvent(rpcEvent) { if (rpcEvent.method !== "trace") { return; } return rpcEvent.params; } tryTrigger(trigger, event) { return event.accounts.some((acc) => acc.includes(trigger)); } }; // src/models/rpc.ts function isJsonRpcResponse(message) { return "id" in message; } function isJsonRpcResponseSuccess(message) { return "result" in message; } // src/stream-providers/websocket-stream-provider.ts var import_isomorphic_ws = require("@ton-api/isomorphic-ws"); var WebsocketStreamProvider = class { constructor(url) { this.url = url; } socket; isClosed = false; pendingRequests = /* @__PURE__ */ new Map(); lastId = 0; subscriptions = []; async open(options) { if (this.isClosed) { return; } this.socket = new WebSocket(this.url); return new Promise((resolve, reject) => { const timeout = options?.openingDeadlineMS ? setTimeout(() => { if (this.socket?.readyState !== WebSocket.OPEN) { reject(new Error("TonApi stream connection timeout")); this.close(); } }, options.openingDeadlineMS) : void 0; this.socket.onerror = () => reject; this.socket.onopen = () => { clearTimeout(timeout); this.isClosed = false; this.socket.onerror = this.errorsHandler.bind(this); this.socket.onmessage = this.messagesHandler.bind(this); resolve(); }; }); } async close() { this.isClosed = true; this.socket?.close(); } async send(method, params) { if (this.isClosed || !this.socket) { throw new Error("Can't subscribe to closed stream"); } this.lastId++; this.socket.send(JSON.stringify({ id: this.lastId, jsonrpc: "2.0", method, params })); return new Promise((res, rej) => { this.pendingRequests.set(this.lastId, { res, rej }); }); } subscribe(subscription) { this.subscriptions.push(subscription); return () => { this.subscriptions = this.subscriptions.filter((s) => s !== subscription); }; } errorsHandler(e) { if (!this.isClosed) { if (this.socket?.readyState === EventSource.CLOSED) { this.socket.close(); this.open(); return; } if (this.socket?.readyState === EventSource.CONNECTING) { console.debug("[TON_API_SDK_ERROR]: Stream error", JSON.stringify(e)); return; } } } async messagesHandler(e) { if (!this.isClosed) { let incomingEvent; try { incomingEvent = JSON.parse(e.data); if (!incomingEvent || typeof incomingEvent !== "object") { throw new Error("Rpc message must be an object"); } const isResponse = "id" in incomingEvent && incomingEvent.id !== void 0 && "method" in incomingEvent && ("result" in incomingEvent || "error" in incomingEvent && incomingEvent.error && typeof incomingEvent.error === "object" && "message" in incomingEvent.error); const isEvent = "params" in incomingEvent && incomingEvent.params && "method" in incomingEvent; if (!isResponse && !isEvent) { throw new Error("Wrong rpc message formt " + incomingEvent.toString()); } } catch (e2) { throw new Error(`TonApi stream message parse failed, message ${e2}`); } if (isJsonRpcResponse(incomingEvent)) { const id = Number(incomingEvent.id); if (!this.pendingRequests.has(id)) { console.debug( "[TON_API_SDK_ERROR]: Unknown rpc response", JSON.stringify(incomingEvent) ); return; } const { res, rej } = this.pendingRequests.get(id); if (isJsonRpcResponseSuccess(incomingEvent)) { res(); } else { rej(new Error(incomingEvent.error.message)); } this.pendingRequests.delete(id); return; } this.subscriptions.forEach((sub) => sub(incomingEvent)); } } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { AccountsObserver, BlocksObserver, MempoolObserver, TraceObserver, WebsocketStreamProvider });