UNPKG

butlerd

Version:

Node.js library for butlerd, the butler daemon

169 lines (168 loc) 5.94 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const split2 = require("split2"); const fs = require("fs"); const child_process_1 = require("child_process"); const debug = require("debug")("butlerd:instance"); const DEFAULT_TIMEOUT = 7000; // ms class Instance { constructor(butlerOpts) { this.exiting = false; this.cancelled = false; this.gracefullyExited = false; let log = (msg) => { debug(msg); if (butlerOpts.log) { butlerOpts.log(msg); } }; let onExit = () => { this.exiting = true; this.cancel(); }; process.on("exit", onExit); let resolveEndpoint; let rejectEndpoint; let endpointTimeout = butlerOpts.endpointTimeout || DEFAULT_TIMEOUT; let beforeEndpoint = Date.now(); this._endpointPromise = new Promise((resolve, reject) => { let timeout = setTimeout(() => { if (this.process) { log(`one timeout`); } else { log(`spawned butler, PID ${this.process.pid}`); } reject(new Error("timed out waiting for butlerd to listen")); }, endpointTimeout); rejectEndpoint = reject; resolveEndpoint = (endpoint) => { clearTimeout(timeout); resolve(endpoint); }; }); this._promise = new Promise((resolve, reject) => { let butlerArgs = [ "--json", "daemon", "--transport", "tcp", "--keep-alive", ]; if (debug.enabled) { butlerArgs = [...butlerArgs, "--verbose"]; } if (butlerOpts.args) { butlerArgs = [...butlerArgs, ...butlerOpts.args]; } log(`spawning butler with args ${butlerArgs.join(" ")}...`); let { butlerExecutable } = butlerOpts; let exists = false; try { exists = fs.existsSync(butlerExecutable); } catch (_) { // ignore } log(`using executable ${butlerExecutable} (${exists ? "exists on disk" : "does not exist on disk"})`); this.process = child_process_1.spawn(butlerExecutable, butlerArgs, { stdio: ["ignore", "pipe", "pipe"], }); log(`spawned butler, PID ${this.process.pid}`); let errLines = []; const onClose = (code, signal) => { process.removeListener("exit", onExit); log(`butler closed, signal ${signal}, code ${code}`); if (this.cancelled) { resolve(); return; } if (signal) { reject(new Error(`Killed by signal ${signal}`)); return; } if (code === 0) { this.gracefullyExited = true; resolve(); return; } reject(new Error(`butler exit code ${code}, error log:\n${errLines.join("\n")}`)); }; this.process.on("close", (code, signal) => { try { onClose(code, signal); } catch (e) { reject(e); } }); this.process.on("error", err => { if (this.exiting) { // swallow error if process is quitting anyway return; } log(`butler had error: ${err.stack ? err.stack : String(err)}`); reject(err); }); const processStdoutLine = (line) => { let data; try { data = JSON.parse(line); } catch (e) { log(`[out] ${line}`); return; } switch (data.type) { case "butlerd/listen-notification": { let elapsedMs = Date.now() - beforeEndpoint; log(`got endpoint, took ${elapsedMs.toFixed()}ms`); resolveEndpoint(data); break; } case "log": { log(`[${data.level}] ${data.message}`); break; } } }; this.process.stdout.pipe(split2()).on("data", (line) => { try { processStdoutLine(line); } catch (e) { reject(e); } }); const processStderrLine = (line) => { log(`[err] ${line}`); errLines.push(line); }; this.process.stderr.pipe(split2()).on("data", (line) => { try { processStderrLine(line); } catch (e) { reject(e); } }); }); this._promise.catch(e => rejectEndpoint(e)); } async getEndpoint() { return await this._endpointPromise; } cancel() { if (!this.cancelled) { this.cancelled = true; if (this.process) { this.process.kill("SIGINT"); } } return this._promise; } async promise() { await this._promise; } } exports.Instance = Instance;