UNPKG

jaysonic

Version:

A feature rich JSON-RPC 1.0/2.0 compliant client and server library

397 lines (354 loc) 12.8 kB
"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;