2018-05-16 20:41:15 +00:00
|
|
|
const child_process = require('child_process');
|
2018-07-27 21:33:50 +00:00
|
|
|
const constants = require('../../constants');
|
2018-05-23 15:06:58 +00:00
|
|
|
const path = require('path');
|
2018-07-27 21:33:50 +00:00
|
|
|
const utils = require('../../utils/utils');
|
2018-05-16 15:49:38 +00:00
|
|
|
|
2018-08-23 14:29:39 +00:00
|
|
|
let processCount = 1;
|
2018-05-16 20:41:15 +00:00
|
|
|
class ProcessLauncher {
|
2018-05-16 15:49:38 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor of ProcessLauncher. Forks the module and sets up the message handling
|
|
|
|
* @param {Object} options Options tp start the process
|
|
|
|
* * modulePath {String} Absolute path to the module to fork
|
|
|
|
* * logger {Object} Logger
|
|
|
|
* * events {Function} Events Emitter instance
|
|
|
|
* @return {ProcessLauncher} The ProcessLauncher instance
|
|
|
|
*/
|
2018-05-16 20:41:15 +00:00
|
|
|
constructor(options) {
|
2018-05-23 15:06:58 +00:00
|
|
|
this.name = path.basename(options.modulePath);
|
2018-08-23 14:29:39 +00:00
|
|
|
|
|
|
|
if (this._isDebug()) {
|
|
|
|
const childOptions = {stdio: 'pipe', execArgv: ['--inspect-brk=' + (60000 + processCount)]};
|
|
|
|
processCount++;
|
|
|
|
this.process = child_process.fork(options.modulePath, [], childOptions);
|
|
|
|
} else {
|
|
|
|
this.process = child_process.fork(options.modulePath);
|
|
|
|
}
|
2018-05-16 20:41:15 +00:00
|
|
|
this.logger = options.logger;
|
2018-05-16 15:25:06 +00:00
|
|
|
this.events = options.events;
|
2018-05-18 19:48:28 +00:00
|
|
|
this.silent = options.silent;
|
2018-05-23 15:06:58 +00:00
|
|
|
this.exitCallback = options.exitCallback;
|
2018-08-01 15:14:02 +00:00
|
|
|
this.embark = options.embark;
|
2018-05-16 20:41:15 +00:00
|
|
|
|
|
|
|
this.subscriptions = {};
|
|
|
|
this._subscribeToMessages();
|
2018-08-01 15:14:02 +00:00
|
|
|
if (this.embark) {
|
|
|
|
this._registerAsPlugin();
|
|
|
|
}
|
2018-05-16 20:41:15 +00:00
|
|
|
}
|
|
|
|
|
2018-08-23 14:29:39 +00:00
|
|
|
_isDebug() {
|
|
|
|
const argvString= process.execArgv.join();
|
|
|
|
return argvString.includes('--debug') || argvString.includes('--inspect');
|
|
|
|
}
|
|
|
|
|
2018-05-16 15:49:38 +00:00
|
|
|
// Subscribes to messages from the child process and delegates to the right methods
|
2018-05-16 20:41:15 +00:00
|
|
|
_subscribeToMessages() {
|
|
|
|
const self = this;
|
|
|
|
this.process.on('message', (msg) => {
|
|
|
|
if (msg.result === constants.process.log) {
|
2018-05-16 14:19:46 +00:00
|
|
|
return self._handleLog(msg);
|
|
|
|
}
|
|
|
|
if (msg.event) {
|
2018-05-18 19:48:28 +00:00
|
|
|
return self._handleEvent(msg);
|
2018-05-16 20:41:15 +00:00
|
|
|
}
|
2018-05-18 19:48:28 +00:00
|
|
|
self._checkSubscriptions(msg);
|
2018-05-16 14:19:46 +00:00
|
|
|
});
|
2018-05-23 14:51:42 +00:00
|
|
|
|
|
|
|
this.process.on('exit', (code) => {
|
2018-05-23 15:06:58 +00:00
|
|
|
if (self.exitCallback) {
|
|
|
|
return self.exitCallback(code);
|
|
|
|
}
|
|
|
|
if (code) {
|
|
|
|
this.logger.info(`Child Process ${this.name} exited with code ${code}`);
|
|
|
|
}
|
2018-05-23 14:51:42 +00:00
|
|
|
});
|
2018-05-16 14:19:46 +00:00
|
|
|
}
|
2018-05-16 20:41:15 +00:00
|
|
|
|
2018-08-01 15:14:02 +00:00
|
|
|
_registerAsPlugin() {
|
|
|
|
const self = this;
|
|
|
|
self.embark.registerAPICall(
|
|
|
|
'ws',
|
2018-08-01 16:53:37 +00:00
|
|
|
'/embark-api/process-logs/' + self.name,
|
2018-08-01 15:14:02 +00:00
|
|
|
(ws, _req) => {
|
|
|
|
self.events.on('log-' + self.name, function(logLevel, msg) {
|
|
|
|
ws.send(JSON.stringify({msg, msg_clear: msg.stripColors, logLevel}), () => {});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-05-16 15:49:38 +00:00
|
|
|
// Translates logs from the child process to the logger
|
2018-05-16 14:19:46 +00:00
|
|
|
_handleLog(msg) {
|
2018-08-01 15:14:02 +00:00
|
|
|
this.events.emit('log-' + this.name, msg.type, msg.message);
|
2018-05-23 14:51:42 +00:00
|
|
|
if (this.silent && msg.type !== 'error') {
|
2018-05-18 19:48:28 +00:00
|
|
|
return;
|
|
|
|
}
|
2018-05-16 14:19:46 +00:00
|
|
|
if (this.logger[msg.type]) {
|
2018-05-28 15:48:27 +00:00
|
|
|
return this.logger[msg.type](utils.normalizeInput(msg.message));
|
2018-05-16 14:19:46 +00:00
|
|
|
}
|
2018-05-28 15:48:27 +00:00
|
|
|
this.logger.debug(utils.normalizeInput(msg.message));
|
2018-05-16 14:19:46 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 15:49:38 +00:00
|
|
|
// Handle event calls from the child process
|
2018-05-16 14:19:46 +00:00
|
|
|
_handleEvent(msg) {
|
2018-05-16 15:25:06 +00:00
|
|
|
const self = this;
|
|
|
|
if (!self.events[msg.event]) {
|
|
|
|
self.logger.warn('Unknown event method called: ' + msg.event);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!msg.args || !Array.isArray(msg.args)) {
|
|
|
|
msg.args = [];
|
|
|
|
}
|
|
|
|
// Add callback in the args
|
|
|
|
msg.args.push((result) => {
|
|
|
|
self.process.send({
|
|
|
|
event: constants.process.events.response,
|
|
|
|
result,
|
|
|
|
eventId: msg.eventId
|
|
|
|
});
|
|
|
|
});
|
|
|
|
self.events[msg.event](msg.requestName, ...msg.args);
|
2018-05-16 14:19:46 +00:00
|
|
|
}
|
2018-05-16 20:41:15 +00:00
|
|
|
|
2018-05-16 15:49:38 +00:00
|
|
|
// Looks at the subscriptions to see if there is a callback to call
|
2018-05-16 14:19:46 +00:00
|
|
|
_checkSubscriptions(msg) {
|
|
|
|
const messageKeys = Object.keys(msg);
|
|
|
|
const subscriptionsKeys = Object.keys(this.subscriptions);
|
2018-05-18 18:11:29 +00:00
|
|
|
let subscriptionsForKey;
|
2018-05-16 14:19:46 +00:00
|
|
|
let messageKey;
|
|
|
|
// Find if the message contains a key that we are subscribed to
|
|
|
|
messageKeys.some(_messageKey => {
|
|
|
|
return subscriptionsKeys.some(subscriptionKey => {
|
|
|
|
if (_messageKey === subscriptionKey) {
|
2018-05-18 18:11:29 +00:00
|
|
|
subscriptionsForKey = this.subscriptions[subscriptionKey];
|
2018-05-16 14:19:46 +00:00
|
|
|
messageKey = _messageKey;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
});
|
2018-05-16 20:41:15 +00:00
|
|
|
|
2018-05-18 18:11:29 +00:00
|
|
|
if (subscriptionsForKey) {
|
2018-05-16 14:19:46 +00:00
|
|
|
// Find if we are subscribed to one of the values
|
2018-05-18 18:11:29 +00:00
|
|
|
let subsIndex = [];
|
|
|
|
const subscriptionsForValue = subscriptionsForKey.filter((sub, index) => {
|
2018-05-16 14:19:46 +00:00
|
|
|
if (msg[messageKey] === sub.value) {
|
2018-05-18 18:11:29 +00:00
|
|
|
subsIndex.push(index);
|
2018-05-16 14:19:46 +00:00
|
|
|
return true;
|
2018-05-16 20:41:15 +00:00
|
|
|
}
|
2018-05-16 14:19:46 +00:00
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
2018-05-18 18:11:29 +00:00
|
|
|
if (subscriptionsForValue.length) {
|
2018-05-16 14:19:46 +00:00
|
|
|
// We are subscribed to that message, call the callback
|
2018-05-18 18:11:29 +00:00
|
|
|
subscriptionsForValue.forEach((subscription, index) => {
|
|
|
|
subscription.callback(msg);
|
|
|
|
|
|
|
|
if (subscription.once) {
|
|
|
|
// Called only once, we can remove it
|
|
|
|
subscription = null;
|
|
|
|
this.subscriptions[messageKey].splice(subsIndex[index], 1);
|
|
|
|
}
|
|
|
|
});
|
2018-05-16 20:41:15 +00:00
|
|
|
}
|
2018-05-16 14:19:46 +00:00
|
|
|
}
|
2018-05-16 20:41:15 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 15:49:38 +00:00
|
|
|
/**
|
|
|
|
* Subscribe to a message using a key-value pair
|
|
|
|
* @param {String} key Message key to subscribe to
|
|
|
|
* @param {String} value Value that the above key must have for the callback to be called
|
|
|
|
* @param {Function} callback callback(response)
|
|
|
|
* @return {void}
|
|
|
|
*/
|
2018-05-18 18:11:29 +00:00
|
|
|
on(key, value, callback) {
|
2018-05-16 20:41:15 +00:00
|
|
|
if (this.subscriptions[key]) {
|
2018-05-16 18:06:34 +00:00
|
|
|
this.subscriptions[key].push({value, callback});
|
2018-05-16 20:41:15 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.subscriptions[key] = [{value, callback}];
|
|
|
|
}
|
|
|
|
|
2018-05-18 18:11:29 +00:00
|
|
|
/**
|
|
|
|
* Same as .on, but only triggers once
|
|
|
|
* @param {String} key Message key to subscribe to
|
|
|
|
* @param {String} value Value that the above key must have for the callback to be called
|
|
|
|
* @param {Function} callback callback(response)
|
|
|
|
* @return {void}
|
|
|
|
*/
|
|
|
|
once(key, value, callback) {
|
|
|
|
const obj = {value, callback, once: true};
|
|
|
|
if (this.subscriptions[key]) {
|
|
|
|
this.subscriptions[key].push(obj);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.subscriptions[key] = [obj];
|
|
|
|
}
|
|
|
|
|
2018-05-16 15:49:38 +00:00
|
|
|
/**
|
|
|
|
* Unsubscribes from a previously subscribed key-value pair (or key if no value)
|
|
|
|
* @param {String} key Message key to unsubscribe
|
|
|
|
* @param {String} value [Optional] Value of the key to unsubscribe
|
|
|
|
* If there is no value, unsubscribes from all the values of that key
|
|
|
|
* @return {void}
|
|
|
|
*/
|
2018-05-16 20:41:15 +00:00
|
|
|
unsubscribeTo(key, value) {
|
|
|
|
if (!value) {
|
|
|
|
this.subscriptions[key] = [];
|
|
|
|
}
|
|
|
|
if (this.subscriptions[key]) {
|
|
|
|
this.subscriptions[key].filter((val, index) => {
|
|
|
|
if (val.value === value) {
|
2018-05-16 18:06:34 +00:00
|
|
|
this.subscriptions[key].splice(index, 1);
|
2018-05-16 20:41:15 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-16 15:49:38 +00:00
|
|
|
/**
|
|
|
|
* Unsubscribes from all subscriptions
|
|
|
|
* @return {void}
|
|
|
|
*/
|
2018-05-16 18:06:34 +00:00
|
|
|
unsubscribeToAll() {
|
2018-05-16 20:41:15 +00:00
|
|
|
this.subscriptions = {};
|
|
|
|
}
|
|
|
|
|
2018-05-16 15:49:38 +00:00
|
|
|
/**
|
|
|
|
* Sends a message to the child process. Same as ChildProcess.send()
|
|
|
|
* @params {Object} message Message to send
|
|
|
|
* For other parameters, see:
|
|
|
|
* https://nodejs.org/api/child_process.html#child_process_subprocess_send_message_sendhandle_options_callback
|
|
|
|
* @return {void}
|
|
|
|
*/
|
2018-05-16 20:41:15 +00:00
|
|
|
send() {
|
2018-06-13 18:56:26 +00:00
|
|
|
if (!this.process.connected) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return this.process.send(...arguments);
|
2018-05-16 20:41:15 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 15:49:38 +00:00
|
|
|
/**
|
|
|
|
* Disconnects the child process. It will exit on its own
|
|
|
|
* @return {void}
|
|
|
|
*/
|
2018-05-16 20:41:15 +00:00
|
|
|
disconnect() {
|
|
|
|
this.process.disconnect();
|
|
|
|
}
|
|
|
|
|
2018-05-16 15:49:38 +00:00
|
|
|
/**
|
|
|
|
* Kills the child process
|
|
|
|
* https://nodejs.org/api/child_process.html#child_process_subprocess_kill_signal
|
|
|
|
* @return {void}
|
|
|
|
*/
|
2018-05-16 20:41:15 +00:00
|
|
|
kill() {
|
|
|
|
this.process.kill(...arguments);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = ProcessLauncher;
|