@adt/json-rpc-client
Version:
Json-rpc client with pluggable transport layers
361 lines (322 loc) • 9.27 kB
JavaScript
import { JsonRpcResponseResult, JsonRpcResponseError, JsonRpcError, JsonRpcRequest, JsonRpcEvent } from '@adt/json-rpc';
import EventEmitter from '@adt/event-emitter';
import MessageTracker from '@adt/message-tracker';
function _typeof(obj) {
"@babel/helpers - typeof";
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) {
return typeof obj;
};
} else {
_typeof = function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(obj);
}
var version = "#version#";
/**
* @constructor
* @param {Object} c Configuration
* @param {JsonRpcTransportProvider} c.transportProvider
* @param {Number} [c.messageCheckInterval=1000] c.messageCheckInterval Interval (in ms) of checking overdue requests with no response
* @param {Number} [c.messageTimeout=5000] Message timeout in milliseconds
* @param {Boolean} [c.reconnect=false] Reconnect flag
* @param {Number} [c.reconnectAfter=5000] Reconnect timeout in milliseconds
* @fires connected
* @fires connecting
* @fires disconnected
* @fires connectionerror
* @fires message
* @fires error
*/
function JsonRpcClient(c) {
if (typeof c === "undefined" || c === null) {
throw new Error("Missing configuration object");
}
if (typeof c.transportProvider === "undefined") {
throw new Error("Required param 'transportProvider' is not defined");
}
var that = this;
var emitter = EventEmitter();
var config = {
messageCheckInterval: "messageCheckInterval" in c ? c.messageCheckInterval : 1000,
messageTimeout: "messageTimeout" in c ? c.messageTimeout : 5000
};
var transportProvider = c.transportProvider;
transportProvider.onMessage(onMessage.bind(that));
var o = onDisconnect.bind(that);
transportProvider.onDisconnect(o);
var messageTracker = MessageTracker({
timeout: config.messageTimeout,
checkInterval: config.messageCheckInterval
});
var ret = Object.create(JsonRpcClient.prototype,
/** @lends JsonRpcClient.prototype */
{
version: {
get: function get() {
return version;
}
},
// TODO: Getter
connected: {
value: false,
writable: true
},
/**
* @type {WebSocket}
* @default null
*/
socket: {
value: null,
writable: true
},
/**
* @type {MessageTracker}
* @default null
*/
messageTracker: {
value: null,
writable: true
},
connect: {
value: connect
},
sendRequest: {
value: sendRequest
},
sendEvent: {
value: sendEvent
},
disconnect: {
value: disconnect
},
on: {
value: function value(evtName, cbf, context) {
return emitter.on(evtName, cbf, context);
}
}
});
/**
* Connects to remote endpoint by underlying transport channel
* @name JsonRpcClient#connect
* @function
* @this JsonRpcClient
* @return {Promise}
*/
function connect() {
return new Promise(function (resolve, reject) {
emitter.emit("connecting");
transportProvider.connect().then(function () {
emitter.emit("connected");
resolve();
})["catch"](function (e) {
reject(e);
});
});
}
/**
* Send request to the remote side
* If websocket is not connected then message will not be send and function returns rejected promise with JsonRpcClient.ERRORS.INVALID_STATE_ERR
* If request will be timeouted during waiting from response then returned promise will be rejected with JsonRpcError.ERRORS.TIMEOUT_EXCEEDED with request attached in data property
* @function
* @name JsonRpcClient#sendRequest
* @this JsonRpcClient
* @param {JsonRpcClient.JsonRpcRequest} req
* @param {Boolean} [enableCallbacks=false] If true, then callbacks assigned to message event will be executed before resolving/rejecting promise on response.
* @return Promise
*/
function sendRequest(req, enableCallbacks) {
var retPromise,
rejectWith = JsonRpcClient.ERRORS.TIMEOUT_EXCEEDED;
rejectWith.data = req;
if (transportProvider.isConnected() === true) {
retPromise = messageTracker.register({
message: req,
filter: filterMessage,
timeoutRejectWith: rejectWith,
params: {
enableCallbacks: enableCallbacks
},
context: this
}); // TODO: Czy powinienem tutaj pchać zaserializowane, czy może jednak JsonRpcRequest??? Chyba lepiej mieć obiekt...
transportProvider.send(req);
} else {
retPromise = Promise.reject(new JsonRpcResponseError({
id: req.id,
error: JsonRpcClient.ERRORS.INVALID_STATE_ERR
}));
}
return retPromise;
}
/**
* Sends JSON-RPC event to the remote side.
* On success method returns JsonRpcClient.ERRORS.NO_ERROR
* If websocket is not connected then message will not be send and function returns error JsonRpcClient.ERRORS.INVALID_STATE_ERR
* @function
* @name JsonRpcClient#sendEvent
* @param {JsonRpcClient.JsonRpcEvent}
* @this JsonRpcClient
* @return JsonRpcError
*/
function sendEvent(evt) {
if (transportProvider.isConnected() === true) {
transportProvider.send(evt);
} else {
return JsonRpcClient.ERRORS.INVALID_STATE_ERR;
}
return JsonRpcClient.ERRORS.NO_ERROR;
}
/**
* @this JsonRpcClient
* @param {JsonRpcElement}
*/
function onMessage(m) {
var msg,
rObj = {}; // If message tracker matched message, then eventually emit message is realized by filterFunction
// otherwise process message as event or request here
if (!messageTracker.matchMessage(m)) {
if (_typeof(m) === "object") {
if ("id" in m) {
rObj.id = m.id;
if ("result" in m) {
rObj.result = m.result;
msg = JsonRpcResponseResult(rObj);
}
if ("error" in m) {
msg = JsonRpcResponseError({
id: m.id,
error: JsonRpcError(m.error)
});
}
if ("method" in m) {
rObj.method = m.method;
rObj.params = m.params;
msg = JsonRpcRequest(rObj);
}
} else {
rObj.method = m.method;
rObj.params = m.params;
msg = JsonRpcEvent(rObj);
}
} else {
msg = m;
}
emitter.emit("message", msg);
}
}
function onDisconnect(evt) {
emitter.emit("disconnected", evt);
}
/**
* Disconnects underlying transport channel.
* @function
* @name JsonRpcClient#disconnect
* @this JsonRpcClient
*/
function disconnect() {
transportProvider.disconnect();
}
/**
* Function for filter messages in MessageTracker.
* This function is executed by message tracker in context of JsonRpcClient.
*
* @param {Object} o Parameters object
* @param {JsonRpcElement} o.message Message passed to matchMessage method
* @param {JsonRpcElement} o.current Iterated message from internal message register)
* @param {Function} o.resolve Resolving function
* @param {Function} o.reject Rejecting function
* @param {Object} o.params Additional params
* @this JsonRpcClient
*/
function filterMessage(o) {
var m = o === null || o === void 0 ? void 0 : o.message;
if (typeof m === "undefined" || m.id !== o.current.id) {
return;
}
var msg;
var err = false;
if ("result" in m) {
msg = JsonRpcResponseResult({
id: m.id,
result: m.result
});
}
if ("error" in m) {
err = true;
msg = JsonRpcResponseError({
id: m.id,
error: JsonRpcError(m.error)
});
}
if (msg !== undefined) {
if (o.params.enableCallbacks === true) {
emitter.emit("message", m);
}
if (err === true) {
o.reject(msg);
} else {
o.resolve(msg);
}
}
}
return ret;
}
JsonRpcClient.ERRORS = Object.create(null, {
NO_ERROR: {
value: Object.create(JsonRpcError.prototype, {
code: {
value: -32000,
enumerable: true
},
message: {
value: "No error",
enumerable: true
},
data: {
value: undefined,
enumerable: true,
writable: true
}
}),
enumerable: true
},
TIMEOUT_EXCEEDED: {
value: Object.create(JsonRpcError.prototype, {
code: {
value: -32001,
enumerable: true
},
message: {
value: "Waiting for response timeout exceeded",
enumerable: true
},
data: {
value: undefined,
enumerable: true,
writable: true
}
}),
enumerable: true
},
INVALID_STATE_ERR: {
value: Object.create(JsonRpcError.prototype, {
code: {
value: -32002,
enumerable: true
},
message: {
value: "WebSocket is already in CLOSING or CLOSED state",
enumerable: true
},
data: {
value: undefined,
enumerable: true,
writable: true
}
}),
enumerable: true
}
});
export default JsonRpcClient;