electron-eval
Version:
Run code inside a hidden Electron window
234 lines (205 loc) • 8.18 kB
JavaScript
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);
;