jaysonic
Version:
A feature rich JSON-RPC 1.0/2.0 compliant client and server library
397 lines (354 loc) • 12.8 kB
JavaScript
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
var MessageBuffer = require("../../util/buffer");
var _require = require("../../util/format"),
formatResponse = _require.formatResponse,
formatError = _require.formatError;
var _require2 = require("../../util/constants"),
ERR_CODES = _require2.ERR_CODES,
ERR_MSGS = _require2.ERR_MSGS;
/**
* Creates an instance of JsonRpcServerProtocol. This is the
* base protocol from which all others inherit.
*
*/
var JsonRpcServerProtocol = /*#__PURE__*/function () {
/**
* @param {class} factory Instance of [JsonRpcServerFactory]{@link JsonRpcServerFactory}
* @param {class} client Instance of `net.Socket`
* @param {(1|2)} version JSON-RPC version to use
* @param {string} delimiter Delimiter to use for `messageBuffer`
* @property {class} messageBuffer Instance of [MessageBuffer]{@link MessageBuffer}
* @property {string} event="data" The event name to listen for incoming data
*/
function JsonRpcServerProtocol(factory, client, version, delimiter) {
_classCallCheck(this, JsonRpcServerProtocol);
this.client = client;
this.factory = factory;
this.delimiter = delimiter;
this.version = version;
this.messageBuffer = new MessageBuffer(delimiter);
this.event = "data";
}
/**
* Registers the `event` data listener when client connects.
*
* Pushes received data into `messageBuffer` and calls
* [_waitForData]{@link JsonRpcServerProtocol#_waitForData}.
*
*/
_createClass(JsonRpcServerProtocol, [{
key: "clientConnected",
value: function clientConnected() {
var _this = this;
this.client.on(this.event, function (data) {
_this.messageBuffer.push(data);
_this._waitForData();
});
}
/**
* Accumulate data while [MessageBuffer.isFinished]{@link MessageBuffer#isFinished} is returning false.
*
* If the buffer returns a message it will be passed to [validateRequest]{@link JsonRpcServerProtocol#validateRequest}.
* If [validateRequest]{@link JsonRpcServerProtocol#validateRequest} returns a parsed result, then the result
* is passed to [_maybeHandleRequest]{@link JsonRpcServerProtocol#_maybeHandleRequest}.
* If [_maybeHandleRequest]{@link JsonRpcServerProtocol#_maybeHandleRequest} returns true, then
* [gotRequest]{@link JsonRpcServerProtocol#gotRequest} is called.<br/><br/>
*
* If any of the above throws an error, [gotError]{@link JsonRpcServerProtocol#gotError} is called.
*
* @private
*
*/
}, {
key: "_waitForData",
value: function _waitForData() {
while (!this.messageBuffer.isFinished()) {
var chunk = this.messageBuffer.handleData();
this._validateData(chunk);
}
}
/**
* Validates data returned from `messageBuffer`.
*
* Will call [gotError]{@link JsonRpcClientProtocol#gotError} if error thrown
* during validation.
*
* @param {string} chunk Data to validate
* @private
*
*/
}, {
key: "_validateData",
value: function _validateData(chunk) {
try {
var result = this.validateRequest(chunk);
var isMessage = this._maybeHandleRequest(result);
if (isMessage) {
this.gotRequest(result);
}
} catch (e) {
this.gotError(e);
}
}
/**
* Validate the request message
*
* @param {string} chunk
* @returns {JSON}
* @throws Will throw an error with a JSON-RPC error object if chunk cannot be parsed
*/
}, {
key: "validateRequest",
value: function validateRequest(chunk) {
try {
return JSON.parse(chunk);
} catch (e) {
throw new Error(formatError({
jsonrpc: this.version,
id: null,
code: ERR_CODES.parseError,
message: ERR_MSGS.parseError,
delimiter: this.delimiter
}));
}
}
/**
* Determines the type of request being made (batch, notification, request) and
* calls the corresponding function.
*
* @param {JSON} result Valid JSON-RPC request object
* @private
*/
}, {
key: "_maybeHandleRequest",
value: function _maybeHandleRequest(result) {
var _this2 = this;
if (Array.isArray(result)) {
if (result.length === 0) {
return this.writeToClient(formatError({
code: ERR_CODES.invalidRequest,
message: ERR_MSGS.invalidRequest,
delimiter: this.delimiter,
jsonrpc: this.version,
id: null
}));
} // possible batch request
this.gotBatchRequest(result).then(function (res) {
if (res.length !== 0) {
// if all the messages in the batch were notifications,
// then we wouldnt want to return anything
_this2.writeToClient(JSON.stringify(res) + _this2.delimiter);
}
});
} else if (result === Object(result) && !("id" in result)) {
// no id, so assume notification
this.gotNotification(result);
return false;
} else {
this.validateMessage(result);
return true;
}
}
/**
* Validates if there are any issues with the incoming request<br/>
*
*
* @param {JSON} message Valid JSON-RPC request object
* @throws Will throw an error for any of the below reasons
*
* Reason|Type
* ---|---
* message is not an object| Invalid Request
* the server does not have the required method| Method Not Found
* the params are not an array or object| Invalid Params
* the "jsonrpc" property was passed for a v1 server| Invalid Request
*/
}, {
key: "validateMessage",
value: function validateMessage(message) {
if (!(message === Object(message))) {
this._raiseError(ERR_MSGS.invalidRequest, ERR_CODES.invalidRequest, null, this.version);
} else if (!(typeof message.method === "string")) {
this._raiseError(ERR_MSGS.invalidRequest, ERR_CODES.invalidRequest, message.id, message.jsonrpc);
} else if (!(message.method in this.factory.methods)) {
this._raiseError(ERR_MSGS.methodNotFound, ERR_CODES.methodNotFound, message.id, message.jsonrpc);
} else if (message.params && !Array.isArray(message.params) && !(message.params === Object(message.params))) {
this._raiseError(ERR_MSGS.invalidParams, ERR_CODES.invalidParams, message.id, message.jsonrpc);
} else if (message.jsonrpc && this.version !== 2) {
this._raiseError(ERR_MSGS.invalidRequest, ERR_CODES.invalidRequest, message.id, this.version);
}
}
/**
* Send message to the client
*
* @param {string} message Stringified JSON-RPC message object
*/
}, {
key: "writeToClient",
value: function writeToClient(message) {
this.client.write(message);
}
/**
* Calls `emit` on factory with the event name being `message.method` and
* the date being `message`.
*
* @param {string} message JSON-RPC message object
*/
}, {
key: "gotNotification",
value: function gotNotification(message) {
this.factory.emit(message.method, message);
}
/**
* Attempts to get the result for the request object. Will
* send result to client if successful and will send an error
* otherwise.
*
* @param {JSON} message JSON-RPC message object
* @returns {Promise}
*/
}, {
key: "gotRequest",
value: function gotRequest(message) {
var _this3 = this;
return this.getResult(message).then(function (result) {
_this3.writeToClient(result);
})["catch"](function (error) {
_this3.gotError(Error(error));
});
}
/**
* Attempts to get the result for all requests in the batch.
* Will send result to client if successful and error otherwise.
*
* @param {JSON[]} requests Valid JSON-RPC batch request
* @returns {Promise[]}
*/
}, {
key: "gotBatchRequest",
value: function gotBatchRequest(requests) {
var _this4 = this;
var batchResponses = requests.map(function (request) {
try {
var isMessage = _this4._maybeHandleRequest(request);
if (isMessage) {
// if its a notification we dont want to return anything
return _this4.getResult(request).then(function (result) {
return JSON.parse(result);
})["catch"](function (error) {
return JSON.parse(error);
});
}
return null;
} catch (e) {
// basically reject the whole batch if any one thing fails
return JSON.parse(e.message);
}
}).filter(function (el) {
return el != null;
});
return Promise.all(batchResponses);
}
/**
* Get the result for the request. Calls the function associated
* with the method and returns the result.
*
* @param {JSON} message Valid JSON-RPC message object
* @returns {Promise}
*/
}, {
key: "getResult",
value: function getResult(message) {
var _this5 = this;
// function needs to be async since the method can be a promise
return new Promise(function (resolve, reject) {
var params = message.params;
var response = {
jsonrpc: message.jsonrpc,
id: message.id,
delimiter: _this5.delimiter
};
var error = {
jsonrpc: message.jsonrpc,
id: message.id,
delimiter: _this5.delimiter
};
try {
var methodResult = params ? _this5.factory.methods[message.method](params) : _this5.factory.methods[message.method]();
if (methodResult instanceof Promise || typeof methodResult.then === "function") {
methodResult.then(function (results) {
response.result = results;
resolve(formatResponse(response));
})["catch"](function (resError) {
error.code = ERR_CODES.internal;
error.message = "".concat(JSON.stringify(resError.message || resError));
error.data = resError.data;
reject(formatError(error));
});
} else {
response.result = methodResult;
resolve(formatResponse(response));
}
} catch (e) {
if (e instanceof TypeError) {
error.code = ERR_CODES.invalidParams;
error.message = ERR_MSGS.invalidParams; // error.data = e.message;
} else {
error.code = ERR_CODES.unknown;
error.message = ERR_MSGS.unknown; // error.data = e.message;
}
error.data = e.data;
reject(formatError(error));
}
});
}
/**
*
* @param {string} message Error message
* @param {number} code Error code
* @param {string|number} id Error message ID
* @throws Throws a JSON-RPC error object
* @private
*/
}, {
key: "_raiseError",
value: function _raiseError(message, code, id, version) {
var error = formatError({
jsonrpc: version,
delimiter: this.delimiter,
id: id,
code: code,
message: message
});
throw new Error(error);
}
/**
* Writes error to the client. Will send a JSON-RPC error object if the
* passed error cannot be parsed.
*
* @param {Error} error `Error` object instance where the message should be a JSON-RPC message object
*/
}, {
key: "gotError",
value: function gotError(error) {
var err;
try {
err = JSON.stringify(JSON.parse(error.message)) + this.delimiter;
} catch (e) {
err = formatError({
jsonrpc: this.version,
delimiter: this.delimiter,
id: null,
code: ERR_CODES.unknown,
message: JSON.stringify(error, Object.getOwnPropertyNames(error))
});
}
this.writeToClient(err);
}
}]);
return JsonRpcServerProtocol;
}();
module.exports = JsonRpcServerProtocol;