UNPKG

chia-agent

Version:
336 lines (335 loc) 12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getDaemon = getDaemon; const crypto_1 = require("crypto"); const logger_1 = require("../logger"); const connection_1 = require("./connection"); const index_1 = require("../config/index"); const DEFAULT_SERVICE_NAME = "wallet_ui"; let daemon = null; function getDaemon(serviceName) { if (daemon) { return daemon; } return (daemon = new Daemon(serviceName)); } // Gracefully disconnect from remote daemon server on Ctrl+C. const onProcessExit = () => { if (!daemon) { process.removeListener("SIGINT", onProcessExit); process.kill(process.pid, "SIGINT"); return; } setTimeout(async () => { try { if (daemon && daemon.connected && !daemon.closing) { (0, logger_1.getLogger)().debug("Detected Ctrl+C. Initiating shutdown..."); await daemon.close(); process.removeListener("SIGINT", onProcessExit); process.kill(process.pid, "SIGINT"); } } catch (e) { process.stderr.write(JSON.stringify(e)); process.exit(1); } }, 67); }; process.addListener("SIGINT", onProcessExit); class Daemon { get connected() { return Boolean(this._connectedUrl); } get closing() { return this._closing; } constructor(serviceName) { this._socket = null; this._connectedUrl = ""; this._responseQueue = {}; this._openEventListeners = []; this._messageEventListeners = []; this._errorEventListeners = []; this._closeEventListeners = []; this._messageListeners = {}; this._closing = false; this._subscriptions = []; this._serviceName = DEFAULT_SERVICE_NAME; this.onOpen = this.onOpen.bind(this); this.onError = this.onError.bind(this); this.onMessage = this.onMessage.bind(this); this.onClose = this.onClose.bind(this); this.onPing = this.onPing.bind(this); this.onPong = this.onPong.bind(this); this.onRejection = this.onRejection.bind(this); if (serviceName) { this._serviceName = serviceName; } } onRejection(e) { if (typeof e === "string") { (0, logger_1.getLogger)().error(`Error: ${e}`); } else if (e instanceof Error) { (0, logger_1.getLogger)().error(`Error ${e.name}: ${e.message}`); if (e.stack) { (0, logger_1.getLogger)().error(e.stack); } } else { try { (0, logger_1.getLogger)().error(`Error: ${JSON.stringify(e)}`); } catch (_e) { (0, logger_1.getLogger)().error("Unknown error"); } } return null; } /** * Connect to local daemon via websocket. * @param daemonServerURL * @param timeoutMs */ async connect(daemonServerURL, timeoutMs) { if (!daemonServerURL) { const config = (0, index_1.getConfig)(); const daemonHost = config["/ui/daemon_host"]; const daemonPort = config["/ui/daemon_port"]; daemonServerURL = `wss://${daemonHost}:${daemonPort}`; } if (this._connectedUrl === daemonServerURL) { return true; } else if (this._connectedUrl) { (0, logger_1.getLogger)().error("Connection is still active. Please close living connection first"); return false; } (0, logger_1.getLogger)().debug(`Opening websocket connection to ${daemonServerURL}`); const result = await (0, connection_1.open)(daemonServerURL, timeoutMs).catch(this.onRejection); if (!result) { return false; } this._socket = result.ws; this._socket.on("error", this.onError); this._socket.addEventListener("message", this.onMessage); this._socket.on("close", this.onClose); this._socket.on("ping", this.onPing); this._socket.on("pong", this.onPong); await this.onOpen(result.openEvent, daemonServerURL).catch(this.onRejection); return true; } async close() { return new Promise((resolve) => { if (this._closing || !this._socket) { return; } (0, logger_1.getLogger)().debug("Closing web socket connection"); this._socket.close(); this._closing = true; this._connectedUrl = ""; this._onClosePromise = resolve; // Resolved in onClose function. }); } async sendMessage(destination, command, data) { return new Promise((resolve, reject) => { if (!this.connected || !this._socket) { (0, logger_1.getLogger)().error("Tried to send message without active connection"); reject("Not connected"); return; } const message = this.createMessageTemplate(command, destination, data || {}); const reqId = message.request_id; this._responseQueue[reqId] = resolve; (0, logger_1.getLogger)().debug(`Sending message. dest=${destination} command=${command} reqId=${reqId}`); const messageStr = JSON.stringify(message); this._socket.send(messageStr, (err) => { if (err) { (0, logger_1.getLogger)().error(`Error while sending message: ${messageStr}`); (0, logger_1.getLogger)().error(JSON.stringify(err)); } }); }); } createMessageTemplate(command, destination, data) { return { command, data, ack: false, origin: this._serviceName, destination, request_id: (0, crypto_1.randomBytes)(32).toString("hex"), }; } async subscribe(service) { if (!this.connected || !this._socket) { (0, logger_1.getLogger)().error(`Tried to subscribe '${service}' without active connection`); throw new Error("Not connected"); } if (this._subscriptions.findIndex((s) => s === service) > -1) { return { command: "register_service", data: { success: true }, ack: true, origin: "daemon", destination: service, request_id: "", }; } let error; const result = await this.sendMessage("daemon", "register_service", { service, }).catch((e) => { error = e; return null; }); if (error || !result) { (0, logger_1.getLogger)().error("Failed to register agent service to daemon"); (0, logger_1.getLogger)().error(error instanceof Error ? `${error.name}: ${error.message}` : JSON.stringify(error)); throw new Error("Subscribe failed"); } this._subscriptions.push(service); return result; } addEventListener(type, listener) { if (type === "open") { this._openEventListeners.push(listener); } else if (type === "message") { this._messageEventListeners.push(listener); } else if (type === "error") { this._errorEventListeners.push(listener); } else if (type === "close") { this._closeEventListeners.push(listener); } } removeEventListener(type, listener) { let listeners; if (type === "open") { listeners = this._openEventListeners; } else if (type === "message") { listeners = this._messageEventListeners; } else if (type === "error") { listeners = this._errorEventListeners; } else if (type === "close") { listeners = this._closeEventListeners; } else { return; } const index = listeners.findIndex((l) => l === listener); if (index > -1) { listeners.splice(index, 1); } } clearAllEventListeners() { this._openEventListeners = []; this._messageEventListeners = []; this._errorEventListeners = []; this._closeEventListeners = []; this._messageListeners = {}; } /** * Add listener for message * @param {string} origin - Can be chia_farmer, chia_full_node, chia_wallet, etc. * @param listener - Triggered when a message arrives. */ addMessageListener(origin, listener) { const o = origin || "all"; if (!this._messageListeners[o]) { this._messageListeners[o] = []; } this._messageListeners[o].push(listener); // Returns removeMessageListener function. return () => { this.removeMessageListener(o, listener); }; } removeMessageListener(origin, listener) { const listeners = this._messageListeners[origin]; if (!listeners) { return; } const index = listeners.findIndex((l) => l === listener); if (index > -1) { listeners.splice(index, 1); } } clearAllMessageListeners() { this._messageListeners = {}; } async onOpen(event, url) { (0, logger_1.getLogger)().info("ws connection opened"); this._connectedUrl = url; this._openEventListeners.forEach((l) => l(event)); return this.subscribe(this._serviceName); } onError(error) { (0, logger_1.getLogger)().error(`ws connection error: ${error.message}`); this._errorEventListeners.forEach((l) => l(error)); } onMessage(event) { let payload; let request_id; let origin; let command; try { payload = JSON.parse(event.data); ({ request_id, origin, command } = payload); } catch (err) { (0, logger_1.getLogger)().error(`Failed to parse message data: ${JSON.stringify(err)}`); (0, logger_1.getLogger)().error(`payload: ${event.data}`); return; } (0, logger_1.getLogger)().debug(`Arrived message. origin=${origin} command=${command} reqId=${request_id}`); const resolver = this._responseQueue[request_id]; if (resolver) { delete this._responseQueue[request_id]; resolver(payload); } this._messageEventListeners.forEach((l) => l(event)); for (const o in this._messageListeners) { if (!Object.prototype.hasOwnProperty.call(this._messageListeners, o)) { continue; } const listeners = this._messageListeners[o]; if (origin === o || o === "all") { listeners.forEach((l) => l(payload)); } } } onClose(event) { if (this._socket) { this._socket.off("error", this.onError); this._socket.removeEventListener("message", this.onMessage); this._socket.off("close", this.onClose); this._socket.off("ping", this.onPing); this._socket.off("pong", this.onPong); this._socket = null; } this._closing = false; this._connectedUrl = ""; this._subscriptions = []; this._closeEventListeners.forEach((l) => l(event)); this.clearAllEventListeners(); (0, logger_1.getLogger)().info(`Closed ws connection. code:${event.code} wasClean:${event.wasClean} reason:${event.reason}`); if (this._onClosePromise) { this._onClosePromise(); this._onClosePromise = undefined; } } onPing() { (0, logger_1.getLogger)().debug("Received ping"); } onPong() { (0, logger_1.getLogger)().debug("Received pong"); } }