UNPKG

ipc-network

Version:

Inter-process communication network, allows multiple node process to exchange messages using unix-socket.

153 lines 7.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const nanoId = require("nanoid/async"); const events_1 = require("events"); const unix_dgram_socket_1 = require("unix-dgram-socket"); const IpcNetworkError_1 = require("./IpcNetworkError"); class IpcNetwork extends events_1.EventEmitter { constructor(processName, rpcCallback) { super(); this.rpcCallback = rpcCallback; this.jobsItems = {}; this.uniqBuffer = []; this.processName = (processName) ? processName : `pid-${process.ppid}-${process.pid}`; this.unixSocketPath = IpcNetwork.getSocketPathFromName(this.processName); this.unixSocket = new unix_dgram_socket_1.UnixDgramSocket(); this.unixSocket.on('error', (error) => { this.emit('error', error); }); this.unixSocket.on('message', (message, info) => { const remoteSocketName = IpcNetwork.getSocketNameFromPath(info.remoteSocket); const messageType = message.readUInt8(0); if (messageType === IpcNetwork.TYPE_MESSAGE) { this.emit('message', { message: message.slice(1), from: remoteSocketName, }); } else if (messageType === IpcNetwork.TYPE_RPC_JOB) { const jobId = message.slice(1, IpcNetwork.JOB_ID_LENGTH + 1).toString(unix_dgram_socket_1.UnixDgramSocket.payloadEncoding); const jobName = message.slice(1 + IpcNetwork.JOB_ID_LENGTH).toString(unix_dgram_socket_1.UnixDgramSocket.payloadEncoding); const socketPath = IpcNetwork.getSocketPathFromName(remoteSocketName); let sendResult = false; try { if (this.rpcCallback) { const jobResult = this.rpcCallback(jobName, remoteSocketName); const messageReply = IpcNetwork.composeMessage(IpcNetwork.TYPE_RPC_RESULT, jobResult, jobId); sendResult = this.unixSocket.send(messageReply, socketPath); } else { const noSupport = 'This process has disabled support for RPC commands.'; const messageReply = IpcNetwork.composeMessage(IpcNetwork.TYPE_RPC_ERROR, noSupport, jobId); sendResult = this.unixSocket.send(messageReply, socketPath); } } catch (error) { const messageReply = IpcNetwork.composeMessage(IpcNetwork.TYPE_RPC_ERROR, error.message, jobId); sendResult = this.unixSocket.send(messageReply, socketPath); } if (!sendResult) { this.emit('error', `Unable to send job results to remote process: ${remoteSocketName}`); } } else if (messageType === IpcNetwork.TYPE_RPC_RESULT || messageType === IpcNetwork.TYPE_RPC_ERROR) { const jobId = message.slice(1, IpcNetwork.JOB_ID_LENGTH + 1).toString(unix_dgram_socket_1.UnixDgramSocket.payloadEncoding); if (this.jobsItems[jobId]) { if (this.jobsItems[jobId].timeoutTimer) { clearTimeout(this.jobsItems[jobId].timeoutTimer); } const contentStart = 1 + IpcNetwork.JOB_ID_LENGTH; if (messageType === IpcNetwork.TYPE_RPC_ERROR) { const errorMessage = message.slice(contentStart).toString(unix_dgram_socket_1.UnixDgramSocket.payloadEncoding); this.jobsItems[jobId].reject(new IpcNetworkError_1.IpcNetworkError(IpcNetwork.ERROR_RPC_EXEC, errorMessage)); } else { this.jobsItems[jobId].resolve(message.slice(contentStart)); } delete this.jobsItems[jobId]; } } else { this.emit('error', new Error(`Received unknown message type from: ${remoteSocketName}`)); } }); } startListening() { this.unixSocket.bind(this.unixSocketPath); } stopListening() { this.unixSocket.close(); } send(data, socketName) { const socketPath = IpcNetwork.getSocketPathFromName(socketName); return this.unixSocket.send(IpcNetwork.composeMessage(IpcNetwork.TYPE_MESSAGE, data), socketPath); } sendRpc(jobName, socketName, timeout) { return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { let jobId; if (this.uniqBuffer.length) { jobId = this.uniqBuffer.pop(); } else { jobId = yield nanoId(IpcNetwork.JOB_ID_LENGTH); } const socketPath = IpcNetwork.getSocketPathFromName(socketName); const message = IpcNetwork.composeMessage(IpcNetwork.TYPE_RPC_JOB, jobName, jobId); let timeoutTimer; if (timeout) { timeoutTimer = setTimeout(() => { reject(new IpcNetworkError_1.IpcNetworkError(IpcNetwork.ERROR_RPC_TIMEOUT, `Job timeout (> ${timeout}ms)`)); delete this.jobsItems[jobId]; }, timeout); } this.jobsItems[jobId] = { resolve, reject, timeoutTimer }; const result = this.unixSocket.send(message, socketPath); if (!result) { delete this.jobsItems[jobId]; const errorMessage = `Unable to send job to remote process: ${socketPath}`; reject(new IpcNetworkError_1.IpcNetworkError(IpcNetwork.ERROR_RPC_SEND, errorMessage)); } while (this.uniqBuffer.length < IpcNetwork.JOB_ID_BUFFER) { this.uniqBuffer.push(yield nanoId(IpcNetwork.JOB_ID_LENGTH)); } })); } static composeMessage(messageType, messageData, jobId) { const resultBuffer = []; const type = Buffer.allocUnsafe(1); type.writeUInt8(messageType, 0); resultBuffer.push(type); if (jobId) { resultBuffer.push(Buffer.from(jobId, unix_dgram_socket_1.UnixDgramSocket.payloadEncoding)); } if (typeof messageData === 'string') { resultBuffer.push(Buffer.from(messageData, unix_dgram_socket_1.UnixDgramSocket.payloadEncoding)); } return Buffer.concat(resultBuffer); } static getSocketPathFromName(socketName) { return `@/tmp/nodejs/.internal/ipc-network/nodes/${socketName}`; } static getSocketNameFromPath(socketPath) { return socketPath.replace(IpcNetwork.getSocketPathFromName(''), ''); } } IpcNetwork.ERROR_RPC_SEND = 'rpc_send_error'; IpcNetwork.ERROR_RPC_TIMEOUT = 'rpc_timeout_error'; IpcNetwork.ERROR_RPC_EXEC = 'rpc_exec_error'; IpcNetwork.TYPE_MESSAGE = 0x01; IpcNetwork.TYPE_RPC_JOB = 0xA0; IpcNetwork.TYPE_RPC_RESULT = 0xA1; IpcNetwork.TYPE_RPC_ERROR = 0xAA; IpcNetwork.JOB_ID_LENGTH = 25; IpcNetwork.JOB_ID_BUFFER = 20; exports.IpcNetwork = IpcNetwork; //# sourceMappingURL=IpcNetwork.js.map