UNPKG

@samouraiwallet/electrum-client

Version:
167 lines 5.57 kB
import { EventEmitter } from "node:events"; import net from "node:net"; import tls from "node:tls"; import * as util from "./util.js"; const TIMEOUT = 60000; export class Client { constructor(port, host, protocol, callbacks) { this.id = 0; this.host = host; this.port = port; this.status = 0; this.protocol = protocol; this.callback_message_queue = new Map(); this.subscribe = new EventEmitter(); this.mp = new util.MessageParser((body, n) => { this.onMessage(body, n); }); if (protocol !== "tcp" && protocol !== "tls" && protocol !== "ssl") { throw new Error("unknown protocol"); } this.onErrorCallback = callbacks?.onError ?? null; this.initSocket(); } initSocket() { this.conn = this.protocol === "tls" || this.protocol === "ssl" ? // @ts-expect-error new tls.TLSSocket(null, { rejectUnauthorized: false }) : new net.Socket(); this.conn.setTimeout(TIMEOUT); this.conn.setEncoding("utf8"); this.conn.setKeepAlive(true, 0); this.conn.setNoDelay(true); this.conn.on("connect", () => { this.conn?.setTimeout(0); this.onConnect(); }); this.conn.on("close", () => { this.onClose(); }); this.conn.on("data", (chunk) => { this.conn?.setTimeout(0); this.onRecv(chunk); }); this.conn.on("error", (e) => { this.onError(e); }); this.status = 0; } connect() { if (this.conn) { if (this.status === 1) { return Promise.resolve(); } this.status = 1; return this.connectSocket(this.conn, this.port, this.host); } return Promise.reject(new Error("There is no socket to initialize connection on.")); } connectSocket(conn, port, host) { return new Promise((resolve, reject) => { const errorHandler = (e) => reject(e); conn.on("error", errorHandler); conn.connect(port, host, () => { conn.removeListener("error", errorHandler); resolve(); }); }); } close() { if (this.status === 0) { return; } this.conn?.end(); this.conn?.destroy(); } request(method, params) { if (this.status === 0) { return Promise.reject(new Error("Connection to server lost, please retry")); } return new Promise((resolve, reject) => { const id = ++this.id; const content = util.makeRequest(method, params, id); this.callback_message_queue.set(id, util.createPromiseResult(resolve, reject)); this.conn?.write(`${content}\n`); }); } requestBatch(method, params, secondParam) { if (this.status === 0) { return Promise.reject(new Error("Connection to server lost, please retry")); } return new Promise((resolve, reject) => { const arguments_far_calls = {}; const contents = []; for (const param of params) { const id = ++this.id; if (secondParam == null) { contents.push(util.makeRequest(method, [param], id)); } else { contents.push(util.makeRequest(method, [param, secondParam], id)); } arguments_far_calls[id] = param; } const content = `[${contents.join(",")}]`; this.callback_message_queue.set(this.id, util.createPromiseResultBatch(resolve, reject, arguments_far_calls)); // callback will exist only for max id this.conn?.write(`${content}\n`); }); } response(msg) { let callback; if (!msg.id && msg[0] && msg[0].id) { // this is a response from batch request for (const m of msg) { if (m.id && this.callback_message_queue.has(m.id)) { callback = this.callback_message_queue.get(m.id); this.callback_message_queue.delete(m.id); } } } else { callback = this.callback_message_queue.get(msg.id); } if (callback) { this.callback_message_queue.delete(msg.id); if (msg.error) { callback(msg.error); } else { callback(null, msg.result || msg); } } else { console.log(msg); throw new Error("Error getting callback while handling response"); } } onMessage(body, n) { const msg = JSON.parse(body || ""); if (Array.isArray(msg)) { this.response(msg); } else if (msg.id == null) { this.subscribe.emit(msg.method, msg.params); } else { this.response(msg); } } onConnect() { } onClose() { for (const [key, fn] of this.callback_message_queue.entries()) { fn(new Error("close connect")); this.callback_message_queue.delete(key); } } onRecv(chunk) { this.mp.run(chunk); } onError(e) { if (this.onErrorCallback != null) { this.onErrorCallback(e); } } } //# sourceMappingURL=client.js.map