rerpc
Version:
a simplified and flexible RPC system with unified model for client-to-server and server-to-server communication on top of HTTP and/or Socket.IO
135 lines (113 loc) • 3.28 kB
JavaScript
const debug = require('debug')('rerpc');
class RerpcServer {
constructor(options = {}) {
debug('RerpcServer', options);
this.registry = {};
this.context = {};
this.options = options;
this.prefix = '/rerpc';
Object.assign(this, options);
this.prefix = this.prefix || '';
}
register(descriptor) {
Object.keys(descriptor).forEach((fn) => {
debug('register', fn);
this.registry[fn] = descriptor[fn];
});
}
async invoke(fn, payload, context) {
debug('invoke', fn, !!this.registry[fn], payload);
if (!this.registry[fn]) {
let newFn;
newFn = `/${fn}`;
if (this.registry[newFn]) {
fn = newFn;
}
newFn = fn.replace(/^\//, '');
if (this.registry[newFn]) {
fn = newFn;
}
newFn = fn.replace(/\/$/, '');
if (this.registry[newFn]) {
fn = newFn;
}
newFn = fn.replace(/^\//, '').replace(/\/$/, '');
if (this.registry[newFn]) {
fn = newFn;
}
newFn = `/${fn.replace(/\/$/, '')}`;
if (this.registry[newFn]) {
fn = newFn;
}
}
if (!this.registry[fn]) {
throw new Error('FunctionNotFound');
}
const result = await this.registry[fn].call(Object.assign({}, this.context, context), payload);
debug('invoke', fn, result);
return result;
}
/* eslint-disable class-methods-use-this */
processPayload(payload) {
// @TODO convert date from string to Date
return payload;
}
/* eslint-enable class-methods-use-this */
/* eslint-disable class-methods-use-this */
processError(error) {
const { message, code, ...errorRest } = error;
return { message, code, ...errorRest };
}
/* eslint-enable class-methods-use-this */
attachToExpress(app) {
debug('attachToExpress');
app.post(`${this.prefix}/*`, async (req, res, next) => {
debug(req.query.fn, req.body);
try {
const fn = req.params[0];
const payload = this.processPayload(req.body);
const result = await this.invoke(fn, payload, {
transport: 'http',
req,
res,
next,
});
res.json({ $result: result });
} catch (error) {
this.handleErrorOnExpress(error, req, res, next);
}
});
}
handleErrorOnExpress(error, req, res /* , next */) {
debug('error', error);
res.status(error.status || 400).json({ $error: this.processError(error) });
}
attachToSocketIO(soc) {
debug('attachToSocketIO');
soc.use((packet, next) => {
if (!this.prefix && !(packet[0] in soc._events)) {
packet.unshift('/rerpc');
}
next();
});
soc.on(this.prefix || '/rerpc', async (fn, payload, ack) => {
try {
payload = this.processPayload(payload);
debug(fn, payload);
const result = await this.invoke(fn, payload, {
transport: 'socket.io',
soc,
ack,
});
ack({ $result: result });
} catch (error) {
this.handleErrorOnSocketIO(error, soc, ack);
}
});
}
handleErrorOnSocketIO(error, soc, ack) {
debug('error', error);
ack({ $error: this.processError(error) });
}
}
module.exports = options => new RerpcServer(options);