src/client-ws/ws.js
/* eslint no-console: 0 */
const WsBrowserClientProtocol = require("../client/protocol/ws-browser");
/**
* Creates an instance of WsBrowserClientFactory.
*
* For websocket client use in the browser.
*
* @extends EventTarget
*/
class WsBrowserClientFactory extends EventTarget {
/**
* @inheritdoc
* @param {Object} options Connection options for the factory class
* @param {string} [options.url="ws://127.0.0.1:8100"] IP of server to connect to
* @param {number} [options.version=2] JSON-RPC version to use (1|2)
* @param {string} [options.delimiter="\n"] Delimiter to use for requests
* @param {number} [options.timeout=30] Timeout for request response
* @param {number} [options.connectionTimeout=5000] Timeout for connection to server
* @param {number} [options.retries=2] Number of connection retry attempts
* @property {class} pcolInstance The [JsonRpcClientProtocol]{@link JsonRpcClientProtocol} instance
* @property {object} timeouts Key value pairs of request IDs to `setTimeout` instance
* @property {number} requestTimeout Same as `options.timeout`
* @property {number} remainingRetries Same as `options.retries`
* @property {number} connectionTimeout Same as `options.connectionTimeout`
* @property {string} url Same as `options.url`
*/
constructor(options) {
super();
if (!(this instanceof WsBrowserClientFactory)) {
return new WsBrowserClientFactory(options);
}
const defaults = {
url: "ws://127.0.0.1:8100",
version: 2,
delimiter: "\n",
timeout: 30,
connectionTimeout: 5000,
retries: 2
};
this.options = {
...defaults,
...(options || {})
};
this.pcolInstance = undefined;
this.timeouts = {};
this.url = this.options.url;
this.eventListenerList = {};
this.requestTimeout = this.options.timeout * 1000;
this.remainingRetries = this.options.retries;
this.connectionTimeout = this.options.connectionTimeout;
}
/**
* Set the `pcolInstance` for the client factory
*
* @example
* this.pcolInstance = new WsBrowserClientProtocol()
*/
buildProtocol() {
this.pcolInstance = new WsBrowserClientProtocol(
this,
this.options.version,
this.options.delimiter
);
}
/**
* Calls `connect()` on protocol instance.
* @returns {function} pcolInstance.connect()
*/
connect() {
if (this.pcolInstance) {
// not having this caused MaxEventListeners error
return Promise.reject(Error("client already connected"));
}
this.buildProtocol();
return this.pcolInstance.connect();
}
/**
* Calls `end()` on protocol instance
*
*/
end(cb) {
this.pcolInstance.end(cb);
}
/**
* Calls `message()` on the protocol instance
*
* @param {string} method Name of the method to use in the request
* @param {Array|JSON} params Params to send
* @param {boolean=} id If true it will use instances `message_id` for the request id, if false will generate a notification request
* @example
* client.message("hello", ["world"]) // returns {"jsonrpc": "2.0", "method": "hello", "params": ["world"], "id": 1}
* client.message("hello", ["world"], false) // returns {"jsonrpc": "2.0", "method": "hello", "params": ["world"]}
*/
message(method, params, id) {
return this.pcolInstance.message(method, params, id);
}
/**
* Calls `send()` method on protocol instance
*
* Promise will resolve when a response has been received for the request.
*
* Promise will reject if the server responds with an error object, or if
* the response is not received within the set `requestTimeout`
*
* @param {string} method Name of the method to use in the request
* @param {Array|JSON} params Params to send
* @returns Promise
* @example
* client.send("hello", {"foo": "bar"})
*/
send(method, params) {
return this.pcolInstance.send(method, params);
}
/**
* Calls `notify()` method on protocol instance
*
* Promise will resolve if the request was sucessfully sent, and reject if
* there was an error sending the request.
*
* @param {string} method Name of the method to use in the notification
* @param {Array|JSON} params Params to send
* @return Promise
* @example
* client.notify("hello", ["world"])
*/
notify(method, params) {
return this.pcolInstance.notify(method, params);
}
/**
* Calls `request()` method on protocol instance
*/
request() {
return this.pcolInstance.request();
}
/**
* Calls `batch()` method on protocol instance
*
* @param {JSON[]} requests Valid JSON-RPC batch request array
*/
batch(requests) {
return this.pcolInstance.batch(requests);
}
/**
* Clears pending timeouts kept in `timeouts` for the provided request IDs.
*
* @param {string[]|number[]} ids Array of request IDs
*/
cleanUp(ids) {
// clear pending timeouts for these request ids
clearTimeout(this.timeouts[ids]);
delete this.timeouts[ids];
}
/**
* Subscribe the function to the given event name
*
* @param {string} method Method to subscribe to
* @param {function} cb Name of callback function to invoke on event
*/
subscribe(method, cb) {
if (!this.eventListenerList[method]) {
this.eventListenerList[method] = [];
}
// add listener to event tracking list
this.eventListenerList[method].push({
type: method,
listener: cb
});
this.addEventListener(method, cb);
}
/**
* Unsubscribe the function from the given event name
*
* @param {string} method Method to unsubscribe from
* @param {function} cb Name of function to remove
*/
unsubscribe(method, cb) {
// remove listener
this.removeEventListener(method, cb);
// Find the event in the list and remove it
this._removeListener(method, cb);
// if no more events of the removed event method are left,remove the group
if (this.eventListenerList[method].length === 0) {
delete this.eventListenerList[method];
}
}
/**
* Unsubscribe all functions from given event name
*
* @param {string} method Method to unsubscribe all listeners from
*/
unsubscribeAll(method) {
if (!this.eventListenerList[method]) {
this.eventListenerList[method] = [];
}
// remove listener
for (let j = 0; j < this.eventListenerList[method].length; j += 1) {
const cb = this.eventListenerList[method][j].listener;
// remove listener
this.removeEventListener(method, cb);
// Find the event in the list and remove it
this._removeListener(method, cb);
}
delete this.eventListenerList[method];
}
/**
* Remmove the callback function from the given event listener
*
* @param {method} method Method name to remove listener for
* @param {function} cb Function name to remove listener
* @private
*/
_removeListener(method, cb) {
if (!this.eventListenerList[method]) {
this.eventListenerList[method] = [];
}
for (let i = 0; i < this.eventListenerList[method].length; i += 1) {
if (this.eventListenerList[method][i].listener === cb) {
this.eventListenerList[method].splice(i, 1);
break;
}
}
}
/**
* Get the list of event listeners attached to the given event name.
*
* @param {string} name The name of the event to retrieve listeners for
* @returns {function|function[]}
*/
getEventListeners(name) {
// return requested listeners by name or all them
if (name === undefined) {
return this.eventListenerList;
}
return this.eventListenerList[name];
}
}
module.exports = WsBrowserClientFactory;