UNPKG

electron-eval

Version:

Run code inside a hidden Electron window

234 lines (205 loc) 8.18 kB
'use strict'; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var electron = require('electron'); var spawn = require('cross-spawn'); var path = require('path'); var EventEmitter = require('events'); var json = require('ndjson'); var headless; try { headless = require('headless'); } catch (err) {} module.exports = function (opts) { return new Daemon(opts); }; var i = 0; var Daemon = function (_EventEmitter) { _inherits(Daemon, _EventEmitter); function Daemon(opts) { _classCallCheck(this, Daemon); var _this = _possibleConstructorReturn(this, (Daemon.__proto__ || Object.getPrototypeOf(Daemon)).call(this)); opts = opts || {}; opts.daemonMain = opts.daemonMain || path.join(__dirname, '..', 'app', 'daemon.js'); opts.timeout = typeof opts.timeout === 'number' ? opts.timeout : 10e3; opts.windowOpts = opts.windowOpts || { show: false, skipTaskbar: true }; opts.headless = opts.headless != null ? opts.headless : null; if (opts.headless == null && process.platform === 'linux') { opts.headless = true; } if (opts.nodeIPC == null && process.platform !== 'linux') { opts.nodeIPC = true; } _this.queue = []; _this.ready = false; _this.closing = false; if (opts.headless) { _this._startHeadless(function (err) { if (err) return _this.error(err); _this._startElectron(opts); }); } else { _this._startElectron(opts); } return _this; } _createClass(Daemon, [{ key: 'eval', value: function _eval(code) { var _this2 = this; var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var cb = arguments[2]; if (typeof opts === 'function') { cb = opts; opts = {}; } var id = (i++).toString(36); this.once(id, function (res) { if (res.err) { var target = opts.mainProcess ? 'main process' : 'window'; var err = new Error('Error evaluating "' + code + '" ' + ('in "' + target + '": ' + res.err)); err.original = res.err; } if (cb) { if (err) return cb(err); return cb(null, res.res); } if (err) _this2.emit('error', err); }); if (!this.ready) return this.queue.push([code, opts, cb]); this.child.send({ id: id, opts: opts, code: code }); } }, { key: 'keepalive', value: function keepalive() { this.child.send(0); } }, { key: 'error', value: function error(err) { this.emit('error', err); this.close(); } }, { key: 'close', value: function close(signal) { var _this3 = this; if (this.closing) return; this.closing = true; if (this.xvfb) { process.kill(this.xvfb.pid, 'SIGKILL'); } if (this.child) { this.child.kill(signal); } this.eval = function (code) { var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var cb = arguments[2]; if (typeof opts === 'function') { cb = opts; opts = {}; } var error = new Error('Daemon already closed'); if (cb) { return cb(error); } _this3.emit('error', error); }; clearInterval(this.keepaliveInterval); } }, { key: '_startHeadless', value: function _startHeadless(cb) { var _this4 = this; if (headless == null) { return cb(new Error('Could not load "headless" module')); } var opts = { display: { width: 1024, height: 768, depth: 24 }, args: ['-nolisten', 'tcp'] }; headless(opts, function (err, child, display) { if (err) { var err2 = new Error('Could not start Xvfb: "' + err.message + '". \n' + 'The "xvfb" package is required to run "electron-eval" on Linux. ' + 'Please install it first ("sudo apt-get install xvfb").'); return cb(err2); } process.on('exit', function () { if (_this4.closing) return; process.kill(child.pid, 'SIGKILL'); }); _this4.xvfb = child; _this4.xDisplay = ':' + display; cb(null); }); } }, { key: '_startElectron', value: function _startElectron(opts, cb) { var _this5 = this; var env = {}; var exitStderr = ''; if (this.xDisplay) env.DISPLAY = this.xDisplay; var electronOpts = { env: env }; if (opts.nodeIPC) electronOpts.stdio = ['ipc']; this.child = spawn(opts.electron || electron, [opts.daemonMain], electronOpts); this.child.on('close', function (code) { if (_this5.closing) return; var err = 'electron-eval error: Electron process exited with code ' + code; if (exitStderr) err += '.\nStderr:\n' + exitStderr; _this5.error(new Error(err)); }); this.child.on('error', function (err) { return _this5.error(err); }); this.child.stderr.on('data', function (data) { exitStderr += '' + data.toString() + (exitStderr ? '\n' : ''); }); process.on('exit', function () { return _this5.child.kill(); }); if (!opts.nodeIPC) this._startIPC(); this.child.once('message', function (data) { _this5.keepaliveInterval = setInterval(_this5.keepalive.bind(_this5), opts.timeout / 2); _this5.keepaliveInterval.unref(); _this5.child.send(opts); _this5.child.once('message', function (data) { _this5.child.on('message', function (message) { return _this5.emit(message[0], message[1]); }); _this5.ready = true; _this5.queue.forEach(function (item) { return _this5.eval.apply(_this5, _toConsumableArray(item)); }); _this5.queue = null; _this5.emit('ready'); _this5.keepalive(); }); }); } }, { key: '_startIPC', value: function _startIPC() { var _this6 = this; var stdin = json.serialize(); stdin.on('error', function (err) { return _this6.error(err); }); stdin.pipe(this.child.stdin); var stdout = json.parse(); stdout.on('error', function (err) { return _this6.error(err); }); this.child.stdout.pipe(stdout); this.child.send = function (data) { return stdin.write(data); }; stdout.on('data', function (data) { return _this6.child.emit('message', data); }); } }]); return Daemon; }(EventEmitter);