node-simple-ipc
Version:
A Node.Js module for local Inter Process Communication
210 lines • 7.32 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NodeSimpleIpc = void 0;
const events_1 = require("events");
const errors_1 = require("./errors");
const utils_1 = require("./utils");
class NodeSimpleIpc {
/**
* Constructor.
*
* @param ipcProcess IPC process (NodeJS.process or ChildProcess). By default "process" will be used.
* @param options NodeSimpleIpc options.
*/
constructor(ipcProcess = process, options) {
// Store registered RPC endpoint names.
this.registeredRpcNames = {};
this.ipcProcess = ipcProcess;
this.options = Object.assign({ actTimeout: 30e3, startService: true }, options);
this.messageHandler = this.onIpcMessage.bind(this);
this.rpcEm = new events_1.EventEmitter();
this.eventsEm = new events_1.EventEmitter();
this.ipcProcess.on('message', this.messageHandler);
}
onIpcMessage(data) {
if ((0, utils_1.isIpcInput)(data)) {
// If RPC handler not found then send not found error.
if (!(data.name in this.registeredRpcNames)) {
this.sendOutput(data, {
data: undefined,
error: {
message: `RPC "${data.name}" not found.`,
},
});
return;
}
// data.name
return;
}
if ((0, utils_1.isIpcOutput)(data)) {
this.rpcEm.emit(data.correlationId, data);
return;
}
if ((0, utils_1.isIpcEvent)(data)) {
this.eventsEm.emit(data.name, data.data);
return;
}
}
/**
* Start a RPC request.
*
* @param name RPC name.
* @param data Request data (optional).
* @param options Request options.
* @returns The response.
*/
act(name, data, options) {
(0, utils_1.assertValidIpcName)(name);
const finOpts = Object.assign({ timeout: this.options.actTimeout }, options);
const correlationId = (0, utils_1.uniqueId)().toString();
let timeoutId;
return new Promise((resolve, reject) => {
// Event handler
const listenReply = (ipcOutput) => {
clearTimeout(timeoutId);
if (ipcOutput.error) {
reject(new errors_1.RemoteError(ipcOutput.error.message, ipcOutput.error));
}
else {
resolve(ipcOutput.data);
}
};
// Register event handler
this.rpcEm.once(correlationId, listenReply);
// Process timeout case
timeoutId = setTimeout(() => {
// Remove listener
this.rpcEm.off(correlationId, listenReply);
// Throw timeout error
reject(new errors_1.TimeoutError(`Reply timeout. IPC name: ${name}.`));
}, finOpts.timeout);
this.sendInput({
correlationId,
name,
data,
});
});
}
/**
* Add a RPC endpoint.
*
* @param name RPC name.
* @param handlerFn Handler function.
* @returns Function you can call to remove the RPC endpoint.
*/
add(name, handlerFn) {
(0, utils_1.assertValidIpcName)(name);
(0, utils_1.assertValidIpcHandler)(handlerFn);
if (name in this.registeredRpcNames) {
throw new Error(`The RPC named "${name}" already exists.`);
}
this.registeredRpcNames[name] = 1;
const messageListener = (message) => {
// Ignore all messages not related to this lib
if (!(0, utils_1.isIpcInput)(message)) {
return;
}
const ipcInput = message;
// Pass only messages related to this endpoint
if (ipcInput.name !== name) {
return;
}
// Convert sync function to async for catching all errors
new Promise((resolve) => resolve(handlerFn(ipcInput.data)))
.then((data) => {
this.sendOutput(ipcInput, { data });
})
.catch((err) => {
this.sendOutput(ipcInput, {
data: undefined,
error: (0, utils_1.serializeError)(err),
});
});
};
this.ipcProcess.on('message', messageListener);
return () => {
this.ipcProcess.off('message', messageListener);
delete this.registeredRpcNames[name];
};
}
/**
* Adds the listener function to the end of the listeners.
*
* @param event Event name.
* @param handler Listener function.
* @returns Function you can call to remove the event handler.
*/
on(event, handler) {
this.eventsEm.on(event, handler);
return () => {
this.eventsEm.off(event, handler);
};
}
/**
* Adds a one-time listener function for the event. The next time event is triggered, this listener is removed and then invoked.
*
* @param event Event name.
* @param handler Listener function.
* @returns Function you can call to remove the event handler.
*/
once(event, handler) {
this.eventsEm.once(event, handler);
return () => {
this.eventsEm.off(event, handler);
};
}
/**
* Removes the specified listener from the listener array.
*
* @param event Event name.
* @param handler Listener function.
* @returns Returns a reference to the NodeSimpleIpc.
*/
off(event, handler) {
this.eventsEm.off(event, handler);
}
/**
* Send an event over IPC.
*
* @param event Event name.
* @param data Event data (optional).
* @returns The sending result.
*/
emit(event, data) {
if (!this.ipcProcess.send)
throw new Error('The send() method is not defined.');
const ipcEvent = {
type: "E" /* Event */,
name: event,
data,
};
return this.ipcProcess.send(ipcEvent);
}
/**
* Send RPC output properties over IPC.
*
* @param input Input properties.
* @param partialOutput Output properties (only data and error).
* @returns The sending result.
*/
sendOutput(input, partialOutput) {
if (!this.ipcProcess.send)
throw new Error('The send() method is not defined.');
const output = Object.assign(Object.assign({}, partialOutput), { correlationId: input.correlationId, name: input.name, type: "O" /* Output */ });
return this.ipcProcess.send(output);
}
/**
* Send RPC input properties over IPC.
*
* @param partialInput Input properties.
* @returns The sending result.
*/
sendInput(partialInput) {
if (!this.ipcProcess.send)
throw new Error('The send() method is not defined.');
const input = Object.assign(Object.assign({}, partialInput), { type: "I" /* Input */ });
return this.ipcProcess.send(input);
}
}
exports.NodeSimpleIpc = NodeSimpleIpc;
//# sourceMappingURL=node-simple-ipc.js.map