UNPKG

remjson

Version:

JSON-RPC 1.0/2.0 compliant server and client

224 lines (182 loc) 6.05 kB
var Server = require('./server'); var utils = require('./utils'); var events = require('events'); /** * Constructor for a RemJson Client * @class Client * @extends require('events').EventEmitter * @param {Server} server An instance of Server * @param {Object} [options] * @param {Function} [options.reviver] Reviver function for JSON * @param {Function} [options.replacer] Replacer function for JSON * @param {Number} [options.version=2] JSON-RPC version to use (1|2) * @param {Function} [options.generator] Function to use for generating request IDs * @return {Client} * @api public */ var Client = function (server, options) { if (!(server instanceof Server) && arguments.length === 1) { options = server; server = null; } if (!(this instanceof Client)) return new Client(server, options); var defaults = { reviver: null, replacer: null, generator: utils.generateId, version: 2 }; this.options = utils.merge(defaults, options || {}); if (server) this.server = server; }; require('util').inherits(Client, events.EventEmitter); module.exports = Client; /** * HTTP client constructor * @type ClientHttp * @static */ Client.http = require('./client/http'); /** * HTTPS client constructor * @type ClientHttps * @static */ Client.https = require('./client/https'); /** * TCP client constructor * @type ClientTcp * @static */ Client.tcp = require('./client/tcp'); /** * TLS client constructor * @type ClientTls * @static */ Client.tls = require('./client/tls'); /** * MQTT client constructor * @type MqttClient * @static */ Client.mqtt = require('./client/mqtt'); /** * Creates a request and dispatches it if given a callback. * @param {String|Array} method A batch request if passed an Array, or a method name if passed a String * @param {Array|Object} params Parameters for the method * @param {String|Number} [id] Optional id. If undefined an id will be generated. If null it creates a notification request * @param {Object} [options] The options for sub client * @param {Function} [callback] Request callback. If specified, executes the request rather than only returning it. * @throws {TypeError} Invalid parameters * @return {Object} JSON-RPC 1.0 or 2.0 compatible request * @api public */ Client.prototype.request = function (method, params, id, options, callback) { var self = this; // is this a batch request? var isBatch = Array.isArray(method) && typeof(params) === 'function'; // JSON-RPC 1.0 doesn't support batching if (this.options.version === 1 && isBatch) { throw new TypeError('JSON-RPC 1.0 does not support batching') } // is this a raw request? var isRaw = !isBatch && method && typeof(method) === 'object' && typeof(params) === 'function'; if (isBatch || isRaw) { callback = params; request = method; } else { if (typeof id === 'function') { callback = id; options = undefined; id = undefined; // specifically undefined because "null" is a notification request } else if (id && typeof id === 'object') { // null is object callback = options; options = id; id = undefined; } if (typeof options === 'function') { callback = options; options = undefined; } if (id === undefined && options !== null && typeof options === 'object') { id = options.id; } var hasCallback = typeof(callback) === 'function'; try { var request = utils.request(method, params, id, { generator: this.options.generator, version: this.options.version }); } catch (err) { if (hasCallback) return callback(err); throw err; } // no callback means we should just return a raw request if (!hasCallback) { return request; } } this.emit('request', request); function done(err, response) { self.emit('response', request, response); self._parseResponse(err, response, callback); } if (this._request.length < 3) { this._request(request, done); } else { this._request(request, options, done); } // always return the raw request return request; }; /** * Executes a request on server bound directly * @param {Object} request A JSON-RPC 1.0 or 2.0 request * @param {Object|Function} [options] options for request * @param {Function} callback Request callback that will receive the server response as the second argument * @api private */ Client.prototype._request = function (request, options, callback) { var self = this; // serializes the request as a JSON string so that we get a copy and can run the replacer as intended utils.JSON.stringify(request, this.options, function (err, message) { if (err) throw err; self.server.call(message, function (error, success) { var response = error || success; callback(null, response); }); }); }; /** * Parses a response from a server * @param {Object} err Error to pass on that is unrelated to the actual response * @param {Object} response JSON-RPC 1.0 or 2.0 response * @param {Function} callback Callback that will receive different arguments depending on the amount of parameters * @api private */ Client.prototype._parseResponse = function (err, response, callback) { if (err) return callback(err); if (!response || typeof(response) !== 'object') { return callback(); } if (callback.length === 3) { // split callback arguments on error and response // is batch response? if (Array.isArray(response)) { // neccesary to split strictly on validity according to spec here var isError = function (res) { return typeof(res.error) !== 'undefined'; }; var isNotError = function (res) { return !isError(res); } callback(null, response.filter(isError), response.filter(isNotError)); } else { // split regardless of validity return callback(null, response.error, response.result); } } else { return callback(null, response); } };