UNPKG

container.ts

Version:
323 lines 14.4 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __assign = (this && this.__assign) || Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); var process = require("process"); var RxJS_1 = require("../../container/RxJS"); var error_1 = require("../error"); var Process_1 = require("./Process"); /** Process message types. */ var EProcessMessageType; (function (EProcessMessageType) { EProcessMessageType[EProcessMessageType["Log"] = 0] = "Log"; EProcessMessageType[EProcessMessageType["Metric"] = 1] = "Metric"; EProcessMessageType[EProcessMessageType["CallRequest"] = 2] = "CallRequest"; EProcessMessageType[EProcessMessageType["CallResponse"] = 3] = "CallResponse"; EProcessMessageType[EProcessMessageType["Event"] = 4] = "Event"; EProcessMessageType[EProcessMessageType["Socket"] = 5] = "Socket"; EProcessMessageType[EProcessMessageType["User"] = 6] = "User"; })(EProcessMessageType = exports.EProcessMessageType || (exports.EProcessMessageType = {})); var ChildProcess = /** @class */ (function (_super) { __extends(ChildProcess, _super); function ChildProcess(options) { var _this = _super.call(this, options) || this; /** Socket handle received from parent process. */ _this.sockets = {}; /** Messages received from parent process. */ _this.messages$ = new RxJS_1.Subject(); /** Events received from parent process. */ _this.events$ = new RxJS_1.Subject(); _this.currentIdentifier = 0; // Listen for a socket message to accept handle. process.once("message", function (type, socket) { if (type === ChildProcess.EVENT.SOCKET) { // Configure socket default channel as message receiver. var channel = ChildProcess.DEFAULT.CHANNEL; _this.sockets[channel] = ChildProcess.socketConfigure({ socket: socket, onError: function (error) { return _this.log.error(error); }, onData: function (data) { return _this.messages$.next(data); }, }); } }); // Process messages. RxJS_1.Observable.fromEvent(process, "message") .subscribe(function (message) { return _this.messages$.next(message); }); // Listen for and handle messages from parent process. _this.messages$ .subscribe(function (message) { return _this.handleMessage(message); }); // Forward log and metric messages to parent process only. // Do not pass channel into send, defaults to parent. _this.container.logs$ .subscribe(function (log) { return _this.send(EProcessMessageType.Log, log); }); _this.container.metrics$ .subscribe(function (metric) { return _this.send(EProcessMessageType.Metric, metric); }); // Send status event on interval. RxJS_1.Observable.interval(ChildProcess.DEFAULT.STATUS_INTERVAL) .subscribe(function () { return _this.event(ChildProcess.EVENT.STATUS, { data: _this.status }); }); return _this; } /** Configure socket for interprocess communication. */ ChildProcess.socketConfigure = function (options) { // Set encoding to receive serialised string data. options.socket.setEncoding(ChildProcess.DEFAULT.ENCODING); // Socket observable events. // TODO(LOW): Fix fromEvent emitter types. var close$ = RxJS_1.Observable.fromEvent(options.socket, "close"); var data$ = RxJS_1.Observable.fromEvent(options.socket, "data"); // Subscribe to socket events until closed. data$.takeUntil(close$) .subscribe(function (data) { ChildProcess.socketDeserialise(data) .map(function (message) { return options.onData(message); }); }); return options.socket; }; /** Serialise input data for socket. */ ChildProcess.socketSerialise = function (data) { try { return JSON.stringify(data) + "\n"; } catch (error) { throw new Process_1.ProcessError(error); } }; /** Deserialise input data from socket. */ ChildProcess.socketDeserialise = function (data) { try { var packets = data.split(/\n/); var messages = []; if (packets.length > 1) { for (var i = 0; i < (packets.length - 1); i++) { messages.push(JSON.parse(packets[i])); } } return messages; } catch (error) { throw new Process_1.ProcessError(error); } }; /** Extract serialisable error properties to object. */ ChildProcess.serialiseError = function (error) { return new Process_1.ProcessError(error).serialise(); }; /** Convert serialised error to error instance. */ ChildProcess.deserialiseError = function (error) { return error_1.ErrorChain.deserialise(error) || new Process_1.ProcessError(); }; /** Send call request to process. */ ChildProcess.sendCallRequest = function (emitter, mod, target, method, id, options) { if (options === void 0) { options = {}; } var timeout = options.timeout || ChildProcess.DEFAULT.TIMEOUT; var args = options.args || []; // Send call request to process. var sendData = { id: id, target: target, method: method, args: args }; emitter.send(EProcessMessageType.CallRequest, sendData, options.channel); return ChildProcess.handleCallResponse(emitter.messages$, id, args, timeout); }; /** Handle method call requests. */ ChildProcess.handleCallRequest = function (emitter, mod, data, channel) { var type = EProcessMessageType.CallResponse; var responseData = { id: data.id }; try { // Retrieve target module and make subscribe call to method. var targetMod = mod.container.resolve(data.target); var method = targetMod[data.method].bind(targetMod); method.apply(void 0, data.args).subscribe({ next: function (next) { var nextData = Object.assign({ next: next }, responseData); emitter.send(type, nextData, channel); }, error: function (error) { error = ChildProcess.serialiseError(error); var errorData = Object.assign({ error: error }, responseData); emitter.send(type, errorData, channel); }, complete: function () { var completeData = Object.assign({ complete: true }, responseData); emitter.send(type, completeData, channel); }, }); } catch (error) { error = ChildProcess.serialiseError(error); var errorData = Object.assign({ error: error }, responseData); emitter.send(type, errorData, channel); } }; /** Handle method call responses. */ ChildProcess.handleCallResponse = function (messages$, id, args, timeout) { return messages$ .filter(function (message) { // Filter by message type and identifier. if (message.type === EProcessMessageType.CallResponse) { var data = message.data; return data.id === id; } return false; }) .map(function (message) { // Cast message data to type. var data = message.data; return data; }) .timeout(timeout) .takeWhile(function (data) { // Complete observable when complete message received. return !data.complete; }) .mergeMap(function (data) { // Throw error or emit next data. if (data.error != null) { var error = ChildProcess.deserialiseError(data.error); return RxJS_1.Observable.throw(error); } else { return RxJS_1.Observable.of(data.next); } }); }; /** Send event to process. */ ChildProcess.sendEvent = function (emitter, mod, name, options) { if (options === void 0) { options = {}; } // Send event request to child process. var sendData = __assign({ name: name }, options); emitter.send(EProcessMessageType.Event, sendData, options.channel); }; /** Listen for events from process. */ ChildProcess.listenForEvent = function (events$, name, channel) { return events$ .filter(function (event) { return (name === event.name) && (channel === event.channel); }) .map(function (event) { return event.data; }); }; /** Send message to channel process. */ ChildProcess.prototype.send = function (type, data, channel) { var socket = this.sockets[channel || ChildProcess.DEFAULT.CHANNEL]; if (socket != null) { socket.write(ChildProcess.socketSerialise({ type: type, data: data })); } else if (process.send != null) { process.send({ type: type, data: data }); } }; /** Make call to module.method in parent process. */ ChildProcess.prototype.call = function (target, method, options) { if (options === void 0) { options = {}; } return ChildProcess.sendCallRequest(this, this, target, method, this.nextIdentifier, options); }; /** Send event with optional data to parent process. */ ChildProcess.prototype.event = function (name, options) { if (options === void 0) { options = {}; } ChildProcess.sendEvent(this, this, name, options); }; /** Listen for event sent by parent process. */ ChildProcess.prototype.listen = function (name, channel) { return ChildProcess.listenForEvent(this.events$, name, channel); }; Object.defineProperty(ChildProcess.prototype, "nextIdentifier", { /** Incrementing counter for unique identifiers. */ get: function () { return ++this.currentIdentifier; }, enumerable: true, configurable: true }); /** Handle messages received from parent process. */ ChildProcess.prototype.handleMessage = function (message) { var _this = this; switch (message.type) { // Call request received from parent. case EProcessMessageType.CallRequest: { ChildProcess.handleCallRequest(this, this, message.data, message.channel); break; } // Send event on internal event bus. case EProcessMessageType.Event: { var event_1 = message.data; this.events$.next(event_1); break; } // Socket event received from parent. case EProcessMessageType.Socket: { var channel_1 = message.data; var listener_1 = function (type, socket) { if (type === ChildProcess.EVENT.SOCKET) { var socketChannel = _this.sockets[channel_1]; // End socket channel if it exists. if (socketChannel != null) { socketChannel.end(); _this.sockets[channel_1] = undefined; } // Configure socket as channel. socketChannel = ChildProcess.socketConfigure({ socket: socket, onError: function (error) { return _this.log.error(error); }, onData: function (data) { data.channel = channel_1; _this.messages$.next(data); }, }); // Remove process listener, send event to parent. _this.sockets[channel_1] = socketChannel; process.removeListener("message", listener_1); _this.event(ChildProcess.EVENT.CHANNEL, { data: message.data }); } }; // Listen for a socket message to accept handle. process.on("message", listener_1); break; } } }; /** Override process down handler to close socket if present. */ ChildProcess.prototype.onSignal = function (signal) { var _this = this; Object.keys(this.sockets).map(function (channel) { var socket = _this.sockets[channel]; if (socket != null) { socket.end(); } _this.sockets[channel] = undefined; }); return _super.prototype.onSignal.call(this, signal); }; /** Default module name. */ ChildProcess.moduleName = "ChildProcess"; /** Default values. */ ChildProcess.DEFAULT = { /** Default call method timeout (10s). */ TIMEOUT: 10000, /** Default interval to send status events (1m). */ STATUS_INTERVAL: 60000, /** Default socket channel. */ CHANNEL: "_", /** Socket data encoding. */ ENCODING: "utf8", }; /** Class event names. */ ChildProcess.EVENT = { SOCKET: "socket", CHANNEL: "channel", STATUS: "status", }; return ChildProcess; }(Process_1.Process)); exports.ChildProcess = ChildProcess; //# sourceMappingURL=ChildProcess.js.map