mirror of
https://github.com/Instadapp/Swap-Aggregator-Subgraph.git
synced 2024-07-29 21:57:12 +00:00
443 lines
12 KiB
JavaScript
443 lines
12 KiB
JavaScript
'use strict';
|
|
|
|
const events = require('events');
|
|
const isFunction = require('lodash/isFunction');
|
|
const jayson = require('../');
|
|
const utils = require('../utils');
|
|
|
|
/**
|
|
* Constructor for a Jayson Server
|
|
* @class Server
|
|
* @extends require('events').EventEmitter
|
|
* @param {Object<String,Function>} [methods] Methods to add
|
|
* @param {Object} [options]
|
|
* @param {Array|Object} [options.params] Passed to Jayson.Method as an option when created
|
|
* @param {Boolean} [options.useContext=false] Passed to Jayson.Method as an option when created
|
|
* @param {Function} [options.reviver] Reviver function for JSON
|
|
* @param {Function} [options.replacer] Replacer function for JSON
|
|
* @param {Function} [options.methodConstructor] Methods will be made instances of this class
|
|
* @param {String} [options.encoding="utf8"] Encoding to use
|
|
* @param {Number} [options.version=2] JSON-RPC version to use (1|2)
|
|
* @param {Function} [options.router] Function to use for routing methods
|
|
* @property {Object} options A reference to the internal options object that can be modified directly
|
|
* @property {Object} errorMessages Map of error code to error message pairs that will be used in server responses
|
|
* @property {ServerHttp} http HTTP interface constructor
|
|
* @property {ServerHttps} https HTTPS interface constructor
|
|
* @property {ServerTcp} tcp TCP interface constructor
|
|
* @property {ServerTls} tls TLS interface constructor
|
|
* @property {Middleware} middleware Middleware generator function
|
|
* @return {Server}
|
|
*/
|
|
const Server = function(methods, options) {
|
|
if(!(this instanceof Server)) {
|
|
return new Server(methods, options);
|
|
}
|
|
|
|
const defaults = {
|
|
reviver: null,
|
|
replacer: null,
|
|
encoding: 'utf8',
|
|
version: 2,
|
|
useContext: false,
|
|
methodConstructor: jayson.Method,
|
|
router: function(method) {
|
|
return this.getMethod(method);
|
|
}
|
|
};
|
|
|
|
this.options = utils.merge(defaults, options || {});
|
|
|
|
// bind router to the server
|
|
this.options.router = this.options.router.bind(this);
|
|
|
|
this._methods = {};
|
|
|
|
// adds methods passed to constructor
|
|
this.methods(methods || {});
|
|
|
|
// assigns interfaces to this instance
|
|
const interfaces = Server.interfaces;
|
|
for(let name in interfaces) {
|
|
this[name] = interfaces[name].bind(interfaces[name], this);
|
|
}
|
|
|
|
// copies error messages for defined codes into this instance
|
|
this.errorMessages = {};
|
|
for(let handle in Server.errors) {
|
|
const code = Server.errors[handle];
|
|
this.errorMessages[code] = Server.errorMessages[code];
|
|
}
|
|
|
|
};
|
|
require('util').inherits(Server, events.EventEmitter);
|
|
|
|
module.exports = Server;
|
|
|
|
/**
|
|
* Interfaces that will be automatically bound as properties of a Server instance
|
|
* @enum {Function}
|
|
* @static
|
|
*/
|
|
Server.interfaces = {
|
|
http: require('./http'),
|
|
https: require('./https'),
|
|
tcp: require('./tcp'),
|
|
tls: require('./tls'),
|
|
middleware: require('./middleware')
|
|
};
|
|
|
|
/**
|
|
* JSON-RPC specification errors that map to an integer code
|
|
* @enum {Number}
|
|
* @static
|
|
*/
|
|
Server.errors = {
|
|
PARSE_ERROR: -32700,
|
|
INVALID_REQUEST: -32600,
|
|
METHOD_NOT_FOUND: -32601,
|
|
INVALID_PARAMS: -32602,
|
|
INTERNAL_ERROR: -32603
|
|
};
|
|
|
|
/*
|
|
* Error codes that map to an error message
|
|
* @enum {String}
|
|
* @static
|
|
*/
|
|
Server.errorMessages = {};
|
|
Server.errorMessages[Server.errors.PARSE_ERROR] = 'Parse Error';
|
|
Server.errorMessages[Server.errors.INVALID_REQUEST] = 'Invalid request';
|
|
Server.errorMessages[Server.errors.METHOD_NOT_FOUND] = 'Method not found';
|
|
Server.errorMessages[Server.errors.INVALID_PARAMS] = 'Invalid method parameter(s)';
|
|
Server.errorMessages[Server.errors.INTERNAL_ERROR] = 'Internal error';
|
|
|
|
/**
|
|
* Adds a single method to the server
|
|
* @param {String} name Name of method to add
|
|
* @param {Function|Client} definition Function or Client for a relayed method
|
|
* @throws {TypeError} Invalid parameters
|
|
*/
|
|
Server.prototype.method = function(name, definition) {
|
|
const Method = this.options.methodConstructor;
|
|
|
|
const isRelay = definition instanceof jayson.Client;
|
|
const isMethod = definition instanceof Method;
|
|
const isDefinitionFunction = isFunction(definition);
|
|
|
|
// a valid method is either a function or a client (relayed method)
|
|
if(!isRelay && !isMethod && !isDefinitionFunction) {
|
|
throw new TypeError('method definition must be either a function, an instance of jayson.Client or an instance of jayson.Method');
|
|
}
|
|
|
|
if(!name || typeof(name) !== 'string') {
|
|
throw new TypeError('"' + name + '" must be a non-zero length string');
|
|
}
|
|
|
|
if(/^rpc\./.test(name)) {
|
|
throw new TypeError('"' + name + '" is a reserved method name');
|
|
}
|
|
|
|
// make instance of jayson.Method
|
|
if(!isRelay && !isMethod) {
|
|
definition = new Method(definition, {
|
|
params: this.options.params,
|
|
useContext: this.options.useContext
|
|
});
|
|
}
|
|
|
|
this._methods[name] = definition;
|
|
};
|
|
|
|
/**
|
|
* Adds a batch of methods to the server
|
|
* @param {Object} methods Methods to add
|
|
*/
|
|
Server.prototype.methods = function(methods) {
|
|
methods = methods || {};
|
|
|
|
for(let name in methods) {
|
|
this.method(name, methods[name]);
|
|
}
|
|
|
|
};
|
|
|
|
/**
|
|
* Checks if a method is registered with the server
|
|
* @param {String} name Name of method
|
|
* @return {Boolean}
|
|
*/
|
|
Server.prototype.hasMethod = function(name) {
|
|
return name in this._methods;
|
|
};
|
|
|
|
/**
|
|
* Removes a method from the server
|
|
* @param {String} name
|
|
*/
|
|
Server.prototype.removeMethod = function(name) {
|
|
if(this.hasMethod(name)) {
|
|
delete this._methods[name];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Gets a method from the server
|
|
* @param {String} name
|
|
* @return {Method}
|
|
*/
|
|
Server.prototype.getMethod = function(name) {
|
|
return this._methods[name];
|
|
};
|
|
|
|
/**
|
|
* Returns a JSON-RPC compatible error property
|
|
* @param {Number} [code=-32603] Error code
|
|
* @param {String} [message="Internal error"] Error message
|
|
* @param {Object} [data] Additional data that should be provided
|
|
* @return {Object}
|
|
*/
|
|
Server.prototype.error = function(code, message, data) {
|
|
if(typeof(code) !== 'number') {
|
|
code = Server.errors.INTERNAL_ERROR;
|
|
}
|
|
|
|
if(typeof(message) !== 'string') {
|
|
message = this.errorMessages[code] || '';
|
|
}
|
|
|
|
const error = { code: code, message: message };
|
|
if(typeof(data) !== 'undefined') {
|
|
error.data = data;
|
|
}
|
|
return error;
|
|
};
|
|
|
|
/**
|
|
* Calls a method on the server
|
|
* @param {Object|Array|String} request A JSON-RPC request object. Object for single request, Array for batches and String for automatic parsing (using the reviver option)
|
|
* @param {Object} [context] Optional context object passed to methods
|
|
* @param {Function} [originalCallback] Callback that receives one of two arguments: first is an error and the second a response
|
|
*/
|
|
Server.prototype.call = function(request, context, originalCallback) {
|
|
const self = this;
|
|
|
|
if(typeof(context) === 'function') {
|
|
originalCallback = context;
|
|
context = {};
|
|
}
|
|
|
|
if(typeof(context) === 'undefined') {
|
|
context = {};
|
|
}
|
|
|
|
if(typeof(originalCallback) !== 'function') {
|
|
originalCallback = function() {};
|
|
}
|
|
|
|
// compose the callback so that we may emit an event on every response
|
|
const callback = function(error, response) {
|
|
self.emit('response', request, response || error);
|
|
originalCallback.apply(null, arguments);
|
|
};
|
|
|
|
maybeParse(request, this.options, function(err, request) {
|
|
let error = null; // JSON-RPC error
|
|
|
|
if(err) {
|
|
error = self.error(Server.errors.PARSE_ERROR, null, err);
|
|
callback(utils.response(error, undefined, undefined, self.options.version));
|
|
return;
|
|
}
|
|
|
|
// is this a batch request?
|
|
if(utils.Request.isBatch(request)) {
|
|
|
|
// batch requests not allowed for version 1
|
|
if(self.options.version === 1) {
|
|
error = self.error(Server.errors.INVALID_REQUEST);
|
|
callback(utils.response(error, undefined, undefined, self.options.version));
|
|
return;
|
|
}
|
|
|
|
// special case if empty batch request
|
|
if(!request.length) {
|
|
error = self.error(Server.errors.INVALID_REQUEST);
|
|
callback(utils.response(error, undefined, undefined, self.options.version));
|
|
return;
|
|
}
|
|
self._batch(request, context, callback);
|
|
return;
|
|
}
|
|
|
|
self.emit('request', request);
|
|
|
|
// is the request valid?
|
|
if(!utils.Request.isValidRequest(request, self.options.version)) {
|
|
error = self.error(Server.errors.INVALID_REQUEST);
|
|
callback(utils.response(error, undefined, undefined, self.options.version));
|
|
return;
|
|
}
|
|
|
|
// from now on we are "notification-aware" and can deliberately ignore errors for such requests
|
|
const respond = function(error, result) {
|
|
if(utils.Request.isNotification(request)) {
|
|
callback();
|
|
return;
|
|
}
|
|
const response = utils.response(error, result, request.id, self.options.version);
|
|
if(response.error) {
|
|
callback(response);
|
|
} else {
|
|
callback(null, response);
|
|
}
|
|
};
|
|
|
|
const method = self._resolveRouter(request.method, request.params);
|
|
|
|
// are we attempting to invoke a relayed method?
|
|
if(method instanceof jayson.Client) {
|
|
return method.request(request.method, request.params, request.id, function(error, response) {
|
|
if(utils.Request.isNotification(request)) {
|
|
callback();
|
|
return;
|
|
}
|
|
callback(error, response);
|
|
});
|
|
}
|
|
|
|
// does the method exist?
|
|
if(!(method instanceof jayson.Method)) {
|
|
respond(self.error(Server.errors.METHOD_NOT_FOUND));
|
|
return;
|
|
}
|
|
|
|
// execute jayson.Method instance
|
|
method.execute(self, request.params, context, function(error, result) {
|
|
|
|
if(utils.Response.isValidError(error, self.options.version)) {
|
|
respond(error);
|
|
return;
|
|
}
|
|
|
|
// got an invalid error
|
|
if(error) {
|
|
respond(self.error(Server.errors.INTERNAL_ERROR));
|
|
return;
|
|
}
|
|
|
|
respond(null, result);
|
|
|
|
});
|
|
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Invoke the router
|
|
* @param {String} method Method to resolve
|
|
* @param {Array|Object} params Request params
|
|
* @return {Method}
|
|
*/
|
|
Server.prototype._resolveRouter = function(method, params) {
|
|
|
|
let router = this.options.router;
|
|
|
|
if(!isFunction(router)) {
|
|
router = function(method) {
|
|
return this.getMethod(method);
|
|
};
|
|
}
|
|
|
|
const resolved = router.call(this, method, params);
|
|
|
|
// got a jayson.Method or a jayson.Client, return it
|
|
if((resolved instanceof jayson.Method) || (resolved instanceof jayson.Client)) {
|
|
return resolved;
|
|
}
|
|
|
|
// got a regular function, make it an instance of jayson.Method
|
|
if(isFunction(resolved)) {
|
|
return new jayson.Method(resolved);
|
|
}
|
|
|
|
};
|
|
|
|
/**
|
|
* Evaluates a batch request
|
|
* @private
|
|
*/
|
|
Server.prototype._batch = function(requests, context, callback) {
|
|
const self = this;
|
|
|
|
const responses = [];
|
|
|
|
this.emit('batch', requests);
|
|
|
|
/**
|
|
* @ignore
|
|
*/
|
|
const maybeRespond = function() {
|
|
|
|
// done when we have filled up all the responses with a truthy value
|
|
const isDone = responses.every(function(response) { return response !== null; });
|
|
if(isDone) {
|
|
|
|
// filters away notifications
|
|
const filtered = responses.filter(function(res) {
|
|
return res !== true;
|
|
});
|
|
|
|
// only notifications in request means empty response
|
|
if(!filtered.length) {
|
|
return callback();
|
|
}
|
|
callback(null, filtered);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @ignore
|
|
*/
|
|
const wrapper = function(request, index) {
|
|
responses[index] = null;
|
|
return function() {
|
|
if(utils.Request.isValidRequest(request, self.options.version)) {
|
|
self.call(request, context, function(error, response) {
|
|
responses[index] = error || response || true;
|
|
maybeRespond();
|
|
});
|
|
} else {
|
|
const error = self.error(Server.errors.INVALID_REQUEST);
|
|
responses[index] = utils.response(error, undefined, undefined, self.options.version);
|
|
maybeRespond();
|
|
}
|
|
};
|
|
};
|
|
|
|
const stack = requests.map(function(request, index) {
|
|
// ignore possibly nested requests
|
|
if(utils.Request.isBatch(request)) {
|
|
return null;
|
|
}
|
|
return wrapper(request, index);
|
|
});
|
|
|
|
stack.forEach(function(method) {
|
|
if(typeof(method) === 'function') {
|
|
method();
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Parse "request" if it is a string, else just invoke callback
|
|
* @ignore
|
|
*/
|
|
function maybeParse(request, options, callback) {
|
|
if(typeof(request) === 'string') {
|
|
utils.JSON.parse(request, options, callback);
|
|
} else {
|
|
callback(null, request);
|
|
}
|
|
}
|