ipc-node-go
Version:
An IPC implementation between Node and its child process (Golang binary) using the stdin / stdout as the transport.
158 lines (157 loc) • 4.67 kB
JavaScript
"use strict";
const events_1 = require("events");
const child_process_1 = require("child_process");
class IPC extends events_1.EventEmitter {
constructor(binPath) {
super();
this.binPath = binPath;
this.go = null;
this.closed = false;
/**
* The `Golang` process will be pinging at every 20 seconds
* and will wait another 20 seconds for reply via `pong` event name
* else it will kill it's process.
*/
this.on('ping', () => this.send('pong'));
}
/**
* Start the child process
* @param arg
*/
init(arg = []) {
this.closed = false;
const self = this;
const go = child_process_1.spawn(this.binPath, arg, {});
this.go = go;
go.stderr.setEncoding('utf8');
go.stdout.setEncoding('utf8');
// emit the errors
go.stderr.on('error', e => self.emit('error', e));
go.stderr.on('data', e => self.emit('log', e));
let outBuffer = '';
go.stdout.on('data', s => {
outBuffer += s;
if (s.endsWith('}\\n')) {
self._processData(outBuffer);
outBuffer = '';
}
});
go.once('close', _ => {
self.closed = true;
self.emit('close');
});
process.on('beforeExit', () => this.kill());
return this;
}
_processData(payload) {
let _data = this.parseJSON(payload);
if (Array.isArray(_data)) {
for (const item of _data) {
this.emit('data', item);
let { error, data, event } = item;
this.emit(event, data, error);
}
}
}
/**
* Kill the child process
*/
kill() {
try {
this.send('___EXIT___', null);
this.closed = true;
this.go.kill();
}
catch (error) { }
}
/**
* Send message to `Golang` process
* @param event
* @param data
*/
send(event, data = undefined) {
this._send(event, data, false);
}
/**
* sendRaw gives your access to a third `boolean` argument which
* is used to determine if this is a sendAndReceive action
*/
sendRaw(event, data, isSendAndReceive = false) {
this._send(event, data, isSendAndReceive);
}
/**
*
* @param event
* @param data
* @param SR this tells `Go` process if this message needs an acknowledgement
*/
_send(event, data, SR) {
try {
if (!this.go || this.closed)
return;
if (this.go && this.go.stdin.writable) {
let payload;
if (typeof data === 'object' || Array.isArray(data))
payload = JSON.stringify(data);
else
payload = data;
// We are converting this to `JSON` this to preserve the
// data types
let d = JSON.stringify({
event,
data: payload,
SR: !!SR
});
if (this.go.stdin.writable) {
this.go.stdin.write(d + '\n');
}
}
}
catch (error) {
this.emit('error', error);
}
}
/**
* Send and receive an acknowledgement through
* a callback from `Go` process
* @param event
* @param data
* @param cb
*/
sendAndReceive(event, data, cb) {
this._send(event, data, true);
let rc = event + '___RC___';
this.once(rc, (data, error) => {
if (typeof cb === 'function')
cb(error, data);
});
}
/**
* Receive and send back acknowledgement/data to `GO`
* a callback from `Go` process
* @param event
* @param data
* @param cb
*/
onReceiveAnSend(event, cb) {
let channel = event + '___RS___';
this.on(event, data => {
if (typeof cb === 'function')
cb(channel, data);
});
}
parseJSON(s) {
try {
let data = s.replace(/}\\n/g, '},');
if (data.endsWith(',')) {
data = data.slice(0, -1).trim();
}
return JSON.parse(`[${data}]`);
}
catch (error) {
this.emit('parse-error', error);
return null;
}
}
}
module.exports = IPC;