zum
Version:
Zum is a simple event bus for NodeJS microservices using amp.
170 lines (142 loc) • 4.29 kB
JavaScript
;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
const Emitter = require('eventemitter3');
const createWildcard = require('wildcard-wrapper');
const { Stream: Parser } = require('amp');
const Message = require('amp-message');
const net = require('netly');
const dbg = require('debug');
const { HSK, SUB, UNS, PUB, ACK, HOST, SEND, BROADCAST } = require('./constants');
const { encrypt, decrypt } = require('./security');
const createTopic = require('./topic');
const debug = dbg('zum:server');
module.exports = async (options = {}) => {
if (typeof options === 'string') {
options = { host: options };
}
const wildcard = createWildcard();
const acks = {};
const topics = {};
const { host = HOST, secure = true, secret, algorithm } = options;
const emitter = new Emitter();
const server = net.bind(host);
const close = fn => server.close(fn);
const isAck = arg => arg[0] === 105 && arg[1] === 58;
const topic = (ns, sock, tp) => {
if (ns) {
wildcard.add(ns);
tp = topics[ns];
}
(tp || (topics[ns] = createTopic(ns))).add(sock);
};
const onerror = err => {
debug(err);
emitter.emit('error', err);
};
const onlistening = () => {
emitter.emit('listening', { host });
};
const processWildcards = (ev, args, type) => {
let matches = wildcard.match(ev, true);
if (SEND === type && matches.length > 0) {
matches = [matches.sort((a, b) => b.key.length - a.key.length)[0]];
}
matches.forEach(({ key, segs }) => {
const tp = topics[key];
let cb = null;
if (!tp) return;
if (isAck(args[0])) cb = args.shift();
args.shift();
args.unshift(segs[1]);
args.unshift(key);
if (cb) args.unshift(cb);
let msg = new Message(args);
if (secure) {
const buf = msg.toBuffer();
const secureBuf = [encrypt(buf, secret, algorithm)];
msg = new Message(secureBuf);
}
tp[type](msg.toBuffer());
});
};
const ondata = sock => sbuf => {
let buf = sbuf;
if (secure) {
const secureMsg = new Message(sbuf);
const secureBuf = secureMsg.args[0];
try {
buf = decrypt(secureBuf, secret, algorithm);
} catch (err) {
return;
}
}
const { args } = new Message(buf);
const ns = args[1];
let ev = args[0];
let tp = null;
switch (ev) {
case HSK:
{
const events = ns || [];
events.forEach(ns => topic(ns, sock));
break;
}
case ACK:
{
const _sock = acks[ns];
if (_sock && _sock.writable) {
_sock.write(sbuf);
delete acks[ns];
}
break;
}
case SUB:
topic(ns, sock);
break;
case UNS:
if (ns) {
wildcard.remove(ns);
tp = topics[ns];
}
tp = tp || tp.remove(sock);
break;
case PUB:
args.shift();
ev = args[0];
tp = topics[ev];
if (tp) tp.broadcast(sbuf);
emitter.emit(...args);
processWildcards(ev, args, BROADCAST);
break;
default:
if (isAck(ev)) {
acks[ev] = sock;
ev = ns;
}
tp = topics[ev];
if (tp) tp.send(sbuf);else processWildcards(ev, args, SEND);
emitter.emit(...args);
}
};
const ondisconnect = sock => () => {
Object.keys(topics).map(k => topics[k].remove(sock));
sock.removeAllListeners();
emitter.emit('disconnect', sock);
};
const onconnect = sock => {
const parser = new Parser();
sock.pipe(parser);
parser.on('data', ondata(sock));
emitter.emit('connect', sock);
sock.on('close', ondisconnect(sock));
};
server.on('listening', onlistening);
server.on('connection', onconnect);
server.on('error', onerror);
return new Promise((resolve, reject) => {
emitter.once('listening', info => {
resolve(Object.assign(emitter, _extends({ close }, info)));
});
emitter.on('error', reject);
});
};