remjson
Version:
JSON-RPC 1.0/2.0 compliant server and client
224 lines (182 loc) • 6.05 kB
JavaScript
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);
}
};