UNPKG

butlerd

Version:

Node.js library for butlerd, the butler daemon

226 lines (225 loc) 8.24 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const support_1 = require("./support"); const net = require("net"); const split2 = require("split2"); exports.CONNECTION_TIMEOUT = 2000; // 2s timeouts, out to be enough for loopback connections.. const genericResult = support_1.createResult(); const MetaAuthenticate = support_1.createRequest("Meta.Authenticate"); const ProxyConnect = support_1.createRequest("Proxy.Connect"); class Conversation { constructor(client) { this.cancelled = false; this.closed = false; this.notificationHandlers = {}; this.missingNotificationHandlersWarned = {}; this.requestHandlers = {}; this.inboundRequests = {}; this.outboundRequests = {}; this.client = client; this.socket = new net.Socket(); } async connect() { let { endpoint } = this.client; const p = new Promise((resolve, reject) => { let sock = this.socket; let onConnect = () => { resolve(); }; setTimeout(() => { reject(support_1.RequestError.fromInternalCode(support_1.InternalCode.ConnectionTimedOut)); }, exports.CONNECTION_TIMEOUT); sock.on("error", e => { reject(e); }); sock.on("close", () => { this.close(); reject(support_1.RequestError.fromInternalCode(support_1.InternalCode.SocketClosed)); }); let { host, port } = this.client.proxy || this.client; sock.connect({ host, port }, onConnect); sock.pipe(split2(JSON.parse)).on("data", (message) => { this.handleMessage(message).catch(e => { this.client.warn(`While processing message: ${e.stack}`); }); }); }); p.catch(e => { }); // avoid unhandled rejections if (this.cancelled) { throw support_1.RequestError.fromInternalCode(support_1.InternalCode.ConversationCancelled); } await p; if (this.client.proxy) { await this.internalCall(ProxyConnect, { address: `${endpoint.tcp.address}`, }); } await this.internalCall(MetaAuthenticate, { secret: endpoint.secret }); } onRequest(rc, handler) { if (this.requestHandlers[rc.__method]) { throw new Error(`cannot register a second request handler for ${rc.__method}`); } this.requestHandlers[rc.__method] = handler; } onNotification(nc, handler) { if (this.notificationHandlers[nc.__method]) { throw new Error(`cannot register a second notification handler for ${nc.__method}`); } this.notificationHandlers[nc.__method] = handler; } async handleMessage(msg) { if (this.cancelled) { return; } if (typeof msg !== "object") { return; } if (msg.jsonrpc != "2.0") { return; } if (typeof msg.id === "undefined") { // we got a notification! const handler = this.notificationHandlers[msg.method]; if (!handler) { if (!this.missingNotificationHandlersWarned[msg.method]) { this.missingNotificationHandlersWarned[msg.method] = true; this.client.warn(`no handler for notification ${msg.method} (in ${this.firstMethod} convo)`); } return; } try { await Promise.resolve(handler(msg.params)); } catch (e) { this.client.warn(`notification handler error: ${e.stack}`); if (this.client.errorHandler) { this.client.errorHandler(e); } } return; } if (msg.method) { try { this.inboundRequests[msg.id] = true; let receivedAt = Date.now(); const handler = this.requestHandlers[msg.method]; if (!handler) { if (this.cancelled) { return; } this.sendResult(genericResult, msg.id, null, { code: support_1.StandardErrorCode.MethodNotFound, message: `no handler is registered for method ${msg.method}`, }); return; } try { const result = await handler(msg.params); if (this.cancelled) { return; } this.sendResult(genericResult, msg.id, result, undefined); } catch (e) { if (this.cancelled) { return; } this.sendResult(genericResult, msg.id, null, { code: support_1.StandardErrorCode.InternalError, message: `async error: ${e.message}`, data: { stack: e.stack, }, }); } } finally { delete this.inboundRequests[msg.id]; } return; } if (msg.result || msg.error) { let req = this.outboundRequests[msg.id]; delete this.outboundRequests[msg.id]; if (msg.error) { req.reject(new support_1.RequestError(msg.error)); } else { req.resolve(msg.result); } return; } if (this.cancelled) { return; } this.sendResult(genericResult, msg.id, null, { code: support_1.StandardErrorCode.InvalidRequest, message: "has id but doesn't have method, result, or error", }); } sendResult(rc, id, result, error) { const obj = rc(id, result, error); if (typeof obj.id !== "number") { throw new Error(`missing id in result ${JSON.stringify(obj)}`); } this.write(obj); } async call(rc, params) { if (!this.firstMethod) { this.firstMethod = rc({})(this.client).method; } return await this.internalCall(rc, params); } async internalCall(rc, params) { const obj = rc(params || {})(this.client); if (typeof obj.id !== "number") { throw new Error(`missing id in request ${JSON.stringify(obj)}`); } let method = obj.method; { let sentAt = Date.now(); try { const res = await new Promise((resolve, reject) => { this.outboundRequests[obj.id] = { resolve, reject }; this.write(obj); }); return res; } catch (err) { throw err; } finally { delete this.outboundRequests[obj.id]; } } } write(obj) { if (this.cancelled) { return; } let payload = JSON.stringify(obj); this.socket.write(payload); this.socket.write("\n"); } cancel() { if (this.cancelled) { return; } this.cancelled = true; for (const id of Object.keys(this.outboundRequests)) { let req = this.outboundRequests[parseInt(id, 10)]; req.reject(support_1.RequestError.fromInternalCode(support_1.InternalCode.ConversationCancelled)); } this.outboundRequests = {}; this.socket.end(); } close() { if (this.closed) { return; } this.cancel(); this.closed = true; } } exports.Conversation = Conversation;