ipc-network
Version:
Inter-process communication network, allows multiple node process to exchange messages using unix-socket.
153 lines • 7.8 kB
JavaScript
;
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