jsonbird-websocket

JSONBird-WebSocket makes it easy to establish a JSON-RPC 2.0 client connection over WebSocket so that you can send and receive Remote Procedure Calls. It works in node.js and web browsers. If the connection closes or is unresponsive, an automatic reconnec

Usage no npm install needed!

<script type="module">
  import jsonbirdWebsocket from 'https://cdn.skypack.dev/jsonbird-websocket';
</script>

README

JSONBird-WebSocket

Build Status Coverage Status Greenkeeper badge

JSONBird-WebSocket makes it easy to establish a JSON-RPC 2.0 client connection over WebSocket so that you can send and receive Remote Procedure Calls. It works in node.js and web browsers. If the connection closes or is unresponsive, an automatic reconnection will occur after a delay. This delay will slowly increase to avoid spamming the server.

This library uses the JSONBird module, which is a more generic JSON-RPC 2.0 library, which can be used to create any kind of client or server over different transports.

Almost all behaviour is configurable, examples include:

  • Passing different options to the ws module, such as TLS options and HTTP headers
  • Stopping automatic reconnects based on the close code received from the server
  • Specifying a timeout per RPC call
  • Specifying a connection timeout
  • Specifying a different reconnect delay strategy (the default implementation includes exponential backoff and jitter)
  • Custom ping interval, method name, timeout and after how many failed pings the connection will be closed and reopened
  • Custom outgoing close codes for timeouts and internal errors
  • Performing RPC method calls from client to server, server to client, or bidirectional

However the default options should be good enough in most situations.

And some events are available that can be used to hook up logging or provide additional behaviour:

  • Connecting, opening and closing of the WebSocket connection
  • JSON-RPC 2.0 protocol errors
  • Ping failure & success

Support for HTML WebSocket in web browsers is provided thanks to the isomorphic-ws module. You can use browserify or webpack to generate a javascript bundle containing this library and the rest of your own code.

Installation

npm install --save jsonbird-websocket ws

The ws package does not have to be installed if you are only going to use this library for web browsers.

Pings

This library sends out ping messages as JSON-RPC method calls (NOT WebSocket pings), the other end is expected to reply to this method call as soon as possible (however the return value is ignored). The default name of this method is "jsonbird.ping".

This is an example of the message which will be sent out over the WebSocket connection periodically:

{"jsonrpc":"2.0","id":"10350 HJRolp3jG","method":"jsonbird.ping","params":[]}

The server is then expected to reply with:

{"jsonrpc":"2.0","id":"10350 HJRolp3jG","result":true}

These ping messages are used to ensure that the connection is healthy. If the server does not reply to pings the connection will eventually be closed (and then a new connection will be made).

Examples

const {WebSocketClient} = require('jsonbird-websocket');

async function example() {
  const rpc = new WebSocketClient({
    url: `ws://127.0.0.1:1234/`,
  });
  rpc.defaultTimeout = 5000;
  rpc.on('webSocketError', ({error}) => console.error('Connection failed', error));
  rpc.on('webSocketClose', ({code, reason}) => {
    console.log('Connection has been closed', code, reason);
    if (code === 1008) {
      // stop reconnecting when we receive this specific
      // close code from the server
      rpc.stop();
    }
  });

  // no connection will be made until .start()
  rpc.start();

  // Send an JSON-RPC 2.0 method call to the server:
  const result = await rpc.call('foo', 123, 456);
  console.log('result', result);

  // Start listening for method calls sent from the server to our client:
  rpc.method('sum', async (a, b) => a + b);
};

example().catch(console.error);

API Documentation

WebSocketClient

Kind: global class

new WebSocketClient([opts])

Param Type Default Description
[opts] object The effect of these options are documented at the getter/setter with the same name
opts.url string
[opts.createConnectionCallback] function ({WebSocket, url}) => new (require('isomorphic-ws'))(url)
[opts.reconnect] boolean true
[opts.reconnectDelayCallback] function x => 2**x * 100 * (Math.random() + 0.5)
[opts.reconnectCounterMax] number 8
[opts.connectTimeout] number 10000
[opts.consecutivePingFailClose] number 4
[opts.timeoutCloseCode] number 4100
[opts.internalErrorCloseCode] number 4101
[opts.jsonbird] object Options passed to the JSONBird constructor
[opts.jsonbird.receiveErrorStack] boolean false
[opts.jsonbird.sendErrorStack] boolean false
[opts.jsonbird.firstRequestId] number 0 The first request id to use
[opts.jsonbird.sessionId] string "randomString()"
[opts.jsonbird.endOfJSONWhitespace=] string
[opts.jsonbird.endOnFinish] boolean true
[opts.jsonbird.finishOnEnd] boolean true
[opts.jsonbird.pingReceive] boolean true
[opts.jsonbird.pingMethod] string "'jsonbird.ping'"
[opts.jsonbird.pingInterval] number 2000
[opts.jsonbird.pingTimeout] number 1000
[opts.jsonbird.pingNow] number Date.now Timer function used to figure out ping delays
[opts.jsonbird.setTimeout] function global.setTimeout
[opts.jsonbird.clearTimeout] function global.clearTimeout

webSocketClient.url

The URL to which to connect; this should be the URL to which the WebSocket server will respond.

Kind: instance property of WebSocketClient

Param Type
value string

webSocketClient.url ⇒ string

The URL to which to connect; this should be the URL to which the WebSocket server will respond.

Kind: instance property of WebSocketClient

webSocketClient.reconnect

If true, a new connection will be made (after a delay) if the connection closes for any reason (error, timeouts, explicit close)

Kind: instance property of WebSocketClient

Param Type
value boolean

webSocketClient.reconnect ⇒ boolean

If true, a new connection will be made (after a delay) if the connection closes for any reason (error, timeouts, explicit close)

Kind: instance property of WebSocketClient

webSocketClient.consecutivePingFailClose

If this amount of pings fail consecutively, the connection will be automatically closed. If reconnect is true a new connection will be established.

Kind: instance property of WebSocketClient

Param Type
value number

webSocketClient.consecutivePingFailClose ⇒ number

If this amount of pings fail consecutively, the connection will be automatically closed. If reconnect is true a new connection will be established.

Kind: instance property of WebSocketClient

webSocketClient.connectTimeout

Abort the connection if it takes longer than this many milliseconds to complete the connection attempt. This is the maximum amount of time that we will wait for the WebSocket readyState to transition from CONNECTING to OPEN

Kind: instance property of WebSocketClient

Param Type Description
value number milliseconds

webSocketClient.connectTimeout ⇒ number

Abort the connection if it takes longer than this many milliseconds to complete the connection attempt. This is the maximum amount of time that we will wait for the WebSocket readyState to transition from CONNECTING to OPEN

Kind: instance property of WebSocketClient
Returns: number - milliseconds

webSocketClient.timeoutCloseCode

The close code to send to the server when the connection is going to be closed because of a timeout

Kind: instance property of WebSocketClient

Param Type Description
value number 1000 or in the range 3000 and 4999 inclusive

webSocketClient.timeoutCloseCode ⇒ number

The close code to send to the server when the connection is going to be closed because of a timeout

Kind: instance property of WebSocketClient
Returns: number - 1000 or integer in the range 3000 and 4999 inclusive

webSocketClient.internalErrorCloseCode

The close code to send to the server when the connection is going to be closed because an error event was raised by the node.js stream api or jsonbird.

Kind: instance property of WebSocketClient

Param Type Description
value number 1000 or in the range 3000 and 4999 inclusive

webSocketClient.internalErrorCloseCode ⇒ number

The close code to send to the server when the connection is going to be closed because an error event was raised by the node.js stream api or jsonbird.

Kind: instance property of WebSocketClient
Returns: number - 1000 or in the range 3000 and 4999 inclusive

webSocketClient.createConnectionCallback

A callback which is called whenever this library wants to establish a new WebSocket connection. The callback is called with a single argument, an object containing the following properties:

  • "url" - The same value as this.url
  • "WebSocket" - The WebSocket class provided by the NPM package "isomorphic-ws"... If this library is used with browserify/webpack this will be equal to window.WebSocket. Otherwise this value will be equal to the NPM "ws" package.

Kind: instance property of WebSocketClient

Param Type
value function

webSocketClient.createConnectionCallback ⇒ function

A callback which is called whenever this library wants to establish a new WebSocket connection. The callback is called with a single argument, an object containing the following properties:

  • "url" - The same value as this.url
  • "WebSocket" - The WebSocket class provided by the NPM package "isomorphic-ws"... If this library is used with browserify/webpack this will be equal to window.WebSocket. Otherwise this value will be equal to the NPM "ws" package.

Kind: instance property of WebSocketClient

webSocketClient.reconnectDelayCallback

A callback which is called after a failed connection to determine the delay before the next connection attempt. The callback is called with a single argument, a number specifying the current reconnectCounter. This counter is increased by 1 whenever a connection attempt fails, and it is slowly decreased while the connection is healthy

The reconnectCounter is always a value between 0 and this.reconnectCounterMax inclusive. The callback must return the reconnect delay as a number in milliseconds.

Kind: instance property of WebSocketClient

Param Type
value function

webSocketClient.reconnectDelayCallback ⇒ function

A callback which is called after a failed connection to determine the delay before the next connection attempt. The callback is called with a single argument, a number specifying the current reconnectCounter. This counter is increased by 1 whenever a connection attempt fails, and it is slowly decreased while the connection is healthy

The reconnectCounter is always a value between 0 and this.reconnectCounterMax inclusive. The callback must return the reconnect delay as a number in milliseconds.

Kind: instance property of WebSocketClient

webSocketClient.reconnectCounterMax

The maximum value for the reconnectCounter (see reconnectDelayCallback). This can be used to easily set a maximum reconnect delay. For example if reconnectCounterMax is set to 8, and reconnectDelayCallback is set to the default value, the highest reconnect delay is: 2**8 * 100 * (Math.random() + 0.5) = random between 12800 and 38400

Kind: instance property of WebSocketClient

Param Type
value number

webSocketClient.reconnectCounterMax ⇒ number

The maximum value for the reconnectCounter (see reconnectDelayCallback). This can be used to easily set a maximum reconnect delay. For example if reconnectCounterMax is set to 8, and reconnectDelayCallback is set to the default value, the highest reconnect delay is: 2**8 * 100 * (Math.random() + 0.5) = random between 12800 and 38400

Kind: instance property of WebSocketClient

webSocketClient.receiveErrorStack ⇒ boolean

If true and a remote method throws, attempt to read stack trace information from the JSON-RPC error.data property. This stack trace information is then used to set the fileName, lineNumber, columnNumber and stack properties of our local Error object (the Error object that the .call() function will reject with).

Kind: instance property of WebSocketClient

webSocketClient.receiveErrorStack

If true and a remote method throws, attempt to read stack trace information from the JSON-RPC error.data property. This stack trace information is then used to set the fileName, lineNumber, columnNumber and stack properties of our local Error object (the Error object that the .call() function will reject with).

Kind: instance property of WebSocketClient

Param Type
value boolean

webSocketClient.sendErrorStack ⇒ boolean

If true, the fileName, lineNumber, columnNumber and stack of an Error thrown during a method is sent to the client using the JSON-RPC error.data property.

Kind: instance property of WebSocketClient

webSocketClient.sendErrorStack

If true, the fileName, lineNumber, columnNumber and stack of an Error thrown during a method is sent to the client using the JSON-RPC error.data property.

Kind: instance property of WebSocketClient

Param Type
value boolean

webSocketClient.defaultTimeout ⇒ number

The timeout to use for an outgoing method call unless a different timeout was explicitly specified to call().

Kind: instance property of WebSocketClient

webSocketClient.defaultTimeout

The timeout to use for an outgoing method call unless a different timeout was explicitly specified to call().

Kind: instance property of WebSocketClient

Param Type
value number

webSocketClient.pingInterval ⇒ number

The time (in milliseconds) between each ping if isSendingPings is true. This time is in addition to the time spent waiting for the previous ping to settle.

Kind: instance property of WebSocketClient
Returns: number - milliseconds

webSocketClient.pingInterval

The time (in milliseconds) between each ping if isSendingPings is true. This time is in addition to the time spent waiting for the previous ping to settle.

Kind: instance property of WebSocketClient

Param Type Description
value number milliseconds

webSocketClient.pingTimeout ⇒ number

The maximum amount of time (in milliseconds) to wait for a ping method call to resolve.

Kind: instance property of WebSocketClient
Returns: number - milliseconds

webSocketClient.pingTimeout

The maximum amount of time (in milliseconds) to wait for a ping method call to resolve.

Kind: instance property of WebSocketClient

Param Type Description
value number milliseconds

webSocketClient.started ⇒ boolean

Returns true if this instance has been started. Which means that we are either setting up a connection, connected or waiting for a reconnect.

Kind: instance property of WebSocketClient

webSocketClient.hasActiveConnection ⇒ boolean

Returns true if there is an active WebSocket connection, in which case all RPC calls will be flushed out immediately and at which point we might receive RPC calls directed to us. If this property returns false, all outgoing RPC calls will be queued until we have a connection again

Kind: instance property of WebSocketClient

webSocketClient.method(name, func)

Registers a new method with the given name.

If the same method name is registered multiple times, earlier definitions will be overridden

Kind: instance method of WebSocketClient

Param Type Description
name string The method name
func function

webSocketClient.methods(objectOrMap)

Registers multiple methods using an object or Map.

Each key->value pair is registered as a method. Values that are not a function are ignored. The this object during a method call is set to the objectOrMap (unless a Map was used)

If the same method name is registered multiple times, earlier definitions will be overridden

Kind: instance method of WebSocketClient

Param Type
objectOrMap Object | Map

webSocketClient.notification(name, func)

Registers a notification with the given name.

A notification is a method for which the return value or thrown Error is ignored. A response object is never sent.

If the same method name is registered multiple times, all functions handlers will be called (in the same order as they were registered)

Kind: instance method of WebSocketClient

Param Type Description
name string The method name
func function

webSocketClient.notifications(objectOrMap)

Registers multiple notifications using an object or Map.

A notification is a method for which the return value or thrown Error is ignored. A response object is never sent.

If the same method name is registered multiple times, all functions handlers will be called (in the same order as they were registered)

Each key->value pair is registered as a notification. Values that are not a "function" are ignored. The this object during a method call is set to the objectOrMap (unless a Map was used)

If the same method name is registered multiple times, earlier definitions will be overridden

Kind: instance method of WebSocketClient

Param Type
objectOrMap Object | Map

webSocketClient.call(nameOrOptions, ...args) ⇒ Promise

Call a method on the remote instance, by sending a JSON-RPC request object to our write stream.

If no write stream has been set, the method call will be buffered until a write stream is set (setWriteStream). Note: if a read stream is never set, any call() will also never resolve.

Kind: instance method of WebSocketClient
Returns: Promise - A Promise which will resole with the return value of the remote method

Param Type Description
nameOrOptions string | Object The method name or an options object
nameOrOptions.name string The method name
nameOrOptions.timeout number A maximum time (in milliseconds) to wait for a response. The returned promise will reject after this time.
...args *

webSocketClient.bindCall(nameOrOptions) ⇒ function

Returns a new function which calls the given method name by binding the function to this RPC instance and the given method name (or options object).

For example:

const subtract = rpc.bindCall('subtract');
subtract(10, 3).then(result => console.log(result)) // 7

Kind: instance method of WebSocketClient

Param Type Description
nameOrOptions string | Object The method name or an options object
nameOrOptions.name string The method name
nameOrOptions.timeout number A maximum time (in milliseconds) to wait for a response. The returned promise will reject after this time.

webSocketClient.notify(nameOrOptions, ...args) ⇒ Promise

Execute a notification on the remote instance, by sending a JSON-RPC request object to our write stream.

If no write stream has been set, the method call will be buffered until a write stream is set (setWriteStream).

This function resolves as soon as the request object has been buffered, but does not wait for the remote instance to have actually received the request object.

Kind: instance method of WebSocketClient

Param Type Description
nameOrOptions string | Object The method name or an options object
nameOrOptions.name string The method name
...args *

webSocketClient.bindNotify(nameOrOptions) ⇒ function

Returns a new function which sends a notification with the given method name by binding the function to this RPC instance and the given method name (or options object).

For example:

const userDeleted = rpc.bindNotify('userDeleted');
userDeleted(123)

Kind: instance method of WebSocketClient

Param Type Description
nameOrOptions string | Object The method name or an options object
nameOrOptions.name string The method name
nameOrOptions.timeout number A maximum time (in milliseconds) to wait for a response. The returned promise will reject after this time.

webSocketClient.start()

Establish the WebSocket connection, and automatically reconnect after an network error or timeout.

Kind: instance method of WebSocketClient

webSocketClient.stop(code, reason)

Close the active WebSocket connection, and stop reconnecting. If there is no active connection the code and reason params are ignored.

Kind: instance method of WebSocketClient

Param Type Default Description
code number Must be equal to 1000 or in the range 3000 to 4999 inclusive
reason string "Normal Closure" Must be 123 bytes or less (utf8)

webSocketClient.closeConnection(code, reason) ⇒ boolean

Close the active WebSocket connection and reconnect if reconnects are enabled. If there is no active connection the code and reason params are ignored.

Kind: instance method of WebSocketClient

Param Type Description
code number Must be equal to 1000 or in the range 3000 to 4999 inclusive
reason string Must be 123 bytes or less (utf8)

"error" (error)

This event is fired if an uncaught error occurred

Most errors end up at the caller of our functions or at the remote peer, instead of this event. Note that if you do not listen for this event on node.js, your process might exit.

Kind: event emitted by WebSocketClient

Param Type
error Error

"protocolError" (error)

This event is fired if our peer sent us something that we were unable to parse.

These kind of errors do not end up at the 'error' event

Kind: event emitted by WebSocketClient

Param Type
error Error

"pingSuccess" (delay)

The most recent ping sent to our peer succeeded

Kind: event emitted by WebSocketClient

Param Type Description
delay number How long the ping took to resolve (in milliseconds)

"pingFail" (consecutiveFails, error)

The most recent ping sent to our peer timed out or resulted in an error

Kind: event emitted by WebSocketClient

Param Type Description
consecutiveFails number The amount of consecutive pings that failed
error Error

"webSocketConnecting"

The WebSocket connection is being established but is not yet open.

Kind: event emitted by WebSocketClient

"webSocketOpen"

The WebSocket connection is now open and all pending RPC calls will be flushed to the server

Kind: event emitted by WebSocketClient

"webSocketError" (error)

The WebSocket API raised an error.

Kind: event emitted by WebSocketClient

Param Type Description
error window.Event | ws.ErrorEvent When running in node.js this contains an ErrorEvent from the "ws" library, interesting properties include message (string) and error (Error) However when run in a browser, this will contain a plain Event without any useful error information.

"webSocketClose"

The WebSocket connection has been (half) closed by either side.

Kind: event emitted by WebSocketClient

Type
Object

"webSocketClose" (info)

This event is fired if the WebSocket connection has been closed. A new connection might be established after this event if the reconnect option is enabled.

Kind: event emitted by WebSocketClient

Param Type
info Object

Example

rpc = new WebSocketClient(...);
rpc.on('webSocketClose', ({code, reason}) => {
  if (reason === closeCodes.POLICY_VIOLATION) {
    rpc.stop(); // stop reconnecting
  }
});