websocket13-jsonrpc

JSON-RPC 2.0 implementation on top of WebSocket13

Usage no npm install needed!

<script type="module">
  import websocket13Jsonrpc from 'https://cdn.skypack.dev/websocket13-jsonrpc';
</script>

README

websocket13-jsonrpc

npm version npm downloads dependencies license paypal

This is a server implementation of JSON-RPC 2.0 over WebSockets.

A browser client implementation is available under browser. If you want to write your own implementation, the only special consideration is that incoming connections must support the subprotocol jsonrpc-2.0.

Please note that this server implementation does not support JSON-RPC batches, since that practically isn't useful over a transport like WebSocket.

Exports

The module exports the following:

ConnectionState

See enum here

JsonRpcErrorCode

See enum here

Reserved JSON-RPC error codes are defined here.

WebSocketStatusCode

See enum here

RpcError

Constructed with 3 parameters:

  • message - A string containing an error message
  • code - A number containing an error code (MUST be an integer)
  • data - An optional value of any type to be sent along with the error

Example

const {RpcError} = require('websocket13-jsonrpc2');

throw new RpcError('File not found', 100, {filename: '/root/example.txt'});

WsRpcConnection

This class is not to be instantiated directly.

Properties

id

A string containing a random ID assigned to this connection. Connection IDs are guaranteed unique for any given set of active connections, but once a connection is closed its ID can be reused immediately.

remoteAddress

The remote IP address.

server

The WsRpcServer that spawned this connection.

state

The state of this connection. This is a value from ConnectionState

handshakeData

The data object from the handshake event for this connection.

groups

An array containing the string name of each group that this connection is a member of.

Methods

disconnect(statusCode[, reason])

  • statusCode - A value from WebSocketStatusCode
  • reason - An optional string reason for the closure

Closes an active connection. If the connection is already closed, does nothing.

getPeerCertificate([detailed])

Same as tls.TLSSocket.getPeerCertificate. Returns null if the current connection is not secure.

getSecurityProtocol()

Same as tls.TLSSocket.getProtocol. Returns null if the current connection is not secure.

data(key[, value])

  • key - String
  • value - Any type

Associate any arbitrary data with this connection. If value is undefined, returns the current value of key. If value is defined, sets key to that value and returns the previous value.

ping()

Sends a ping frame to the remote. Returns a Promise that is resolved with the time in milliseconds that it took to receive the reply.

Example

async function pingClient() {
    let latency = await client.ping();
    console.log(`Client ${client.id}'s latency is ${latency} milliseconds.`);
}

joinGroup(group)

  • group - String group name

Joins this connection to a group. Groups are used to broadcast messages to groups of connections all at once. For example, you might put connections in a particular chat room into one group, or you might put connections authenticated to a given user ID in a dedicated group.

Groups are ad-hoc and are created or destroyed as needed.

Returns true if this connection was joined to the group successfully, or false if it was already in the given group.

leaveGroup(group)

  • group - String group name

Leaves this connection from a group. If this was the last member of the group, the group is destroyed.

Returns true if this connection left the group successfully, or false if it was not in the given group.

notify(method[, params])

  • method - String method name
  • params - Any data type

Sends a notification to the remote. JSON-RPC notifications are messages which may not be responded to.

Returns true if the notification was sent, or false if the connection was not open.

invoke(method[, params])

  • method - String method name
  • params - Any data type

Sends a request to the remote. Returns a Promise that will be resolved with the result of the request, or rejected with a RpcError if the request fails.

Events

latency

  • pingTime - Round-trip latency in milliseconds

Emitted periodically (unless you've disabled pings in options) with the latency of the connection.

WsRpcServer

This class instantiates a WebSocket server.

The constructor takes a single options object:

  • requireObjectParams - If passed and set to true, then incoming JSON-RPC messages will be rejected if their params are any data type except object (not including null) or array.
  • All other options from WS13.WebSocketServer are allowed, except protocols.

The requireObjectParams option is designed to allow you to do things like this without worrying about invalid incoming params causing a crash:

server.registerMethod('Add', (connection, [a, b]) => {
    return a + b;
});

Events

handshake

  • handshakeData
  • reject
  • accept

Same as websocket13's handshake event, with these exceptions:

  • accept() returns a WsRpcConnection instead of a WebSocket
  • It is not possible to override protocol in accept()

This event must be handled or else all incoming connections will stall.

connect

Emitted when a new connection is established.

disconnect

  • connection - The WsRpcConnection that disconnected
  • code - A value from WebSocketStatusCode
  • reason - A string, possibly empty, describing why they disconnected
  • initiatedByUs - A boolean indicating whether the disconnection was initiated by the server (true) or by the client (false)

Emitted when a remote disconnects.

Properties

connections

An array containing all currently-active WsRpcConnection objects.

groups

An array containing strings of the names of all groups that currently have members.

Methods

http(server)

Listen for WebSocket connections on this server. You can call this for more than one HTTP(S) server, but you shouldn't call it more than once per server. For example, if you're accepting both secure and insecure connections, you should call this once with an HTTP server, and once with an HTTPS server.

This binds to the server's upgrade event. If nothing else has bound to that event, then node-websocket13 will respond to bad handshakes with an HTTP 400 response. Otherwise, it will do nothing. Bad handshakes are those which match any of the following criteria:

  • Upgrade header does not match the value websocket (case-insensitively)
  • Connection header does not contain the value upgrade (case-insensitively, interpreting the header value as a comma-separated list)
    • For example, Connection: keep-alive, upgrade is valid, but Connection: keep-alive upgrade is not
  • Client HTTP version is not at least 1.1
  • Client request method is not GET
  • Client request is missing Sec-WebSocket-Key header or when base64-decoded, it is not 16 bytes in length
  • Client request is missing Sec-WebSocket-Version header or the header's value is not 13

groupMembers(group)

  • group - Either a group name or an array of group names

Returns an array of WsRpcConnection objects for the members in the given set of groups. If you pass a single string, returns the list of members of that group. If you pass an array of strings, returns a de-duplicated union of group members.

registerMethod(name, handler)

  • name - String method name
  • handler - A function to be invoked when the method is called

Registers a new method. When JSON-RPC messages invoking this method are received, the handler will be called with the signature (WsRpcConnection, any params).

Please note that unless the requireObjectParams option is set, params can be any JSON type (including null or undefined).

The handler function must return either a response value or a Promise which is resolved to the response value. If an error occurs while processing this method, you must throw (or reject the Promise with) a RpcError, which will be sent to the remote as an error response.

Example

const {RpcError, JsonRpcErrorCode} = require('websocket13-jsonrpc2');

server.registerMethod('Add', (connection, params) => {
    if (typeof params != 'object' || !Array.isArray(params) || params.length != 2 || typeof params[0] != 'number' || typeof params[1] != 'number') {
        throw new RpcError('Invalid params', JsonRpcErrorCode.InvalidParams);    
    }
    
    return params[0] + params[1];
});

server.registerMethod('AddAsync', async (connection, params) => {
    if (typeof params != 'object' || !Array.isArray(params) || params.length != 2 || typeof params[0] != 'number' || typeof params[1] != 'number') {
        throw new RpcError('Invalid params', JsonRpcErrorCode.InvalidParams);    
    }
    
    await new Promise((resolve) => setTimeout(resolve, 1000));
    return params[0] + params[1];
});

registerNotification(name, handler)

  • name - String name
  • handler - A function to be invoked when the notification is received

Registers a new notification. When JSON-RPC messages containing this notification are received, the handler will be called with the signature (WsRpcConnection, any params).

Please note that unless the requireObjectParams option is set, params can be any JSON type (including null or undefined).

As a JSON-RPC notification requires no response, handler should not return anything.

notify(group, name[, params])

  • group - String name of group or array of string names of groups to send notification to. Use null to send a notification to all active clients.
  • name - String name of notification to send
  • params - Any data type

Sends a JSON-RPC notification to an entire group at once. You can also pass an array of groups to send a notification to all members of all specified groups.

notifyAll(name[, params])

  • name - String name of notification to send
  • params - Any data type

Sends a JSON-RPC notification to all connected clients.

WsRpcOutgoingConnection

This class extends WsRpcConnection. Methods, properties, and events inherited from that class are not listed below, so you should check those docs as well.

Used to establish outgoing connections. You should instantiate a new instance of this class to establish a new outgoing connection to a JSON-RPC server.

The constructor takes two arguments:

  • url - The WebSocket URL you want to connect to (e.g. ws://example.com/?some=query)
  • options - Optional. An object with zero or more of these properties:
    • requireObjectParams - If passed and set to true, then incoming JSON-RPC messages will be rejected if their params are any data type except object (not including null) or array.
    • All other options from WS13.WebSocket are allowed, except protocols.

Example

const {WsRpcOutgoingConnection} = require('websocket13-jsonrpc');

let conn = new WsRpcOutgoingConnection('ws://127.0.0.1:8080', {pingInterval: 30000});

Properties

server

Always null for outgoing connections.

groups

Always [] (empty array) for outgoing connections.

Methods

joinGroup()

Outgoing connections cannot be joined to groups, so this method throws an Error if invoked.

leaveGroup()

Outgoing connections cannot be joined to groups, so this method throws an Error if invoked.

registerMethod(name, handler)

  • name - String
  • handler - Function

Functionally identical to WsRpcServer#registerMethod(name, handler). This is how you should register methods for outgoing connections.

registerNotification(name, handler)

  • name - String
  • handler - Function

Functionally identical to WsRpcServer#registerNotification(name, handler). This is how you should register notifications for outgoing connections.

Events

connected

Emitted when the connection is successfully established.

disconnected

  • code - A value from WebSocketStatusCode
  • reason - A string, possibly empty, desribing why we disconnected
  • initiatedByUs - A boolean indicating whether the disconnected was initiated by us/the client (true) or by the server (false)

Emitted when we disconnect from the server.

error

  • err - An Error object

Emitted when a fatal error causes our connection to fail (while connecting) or be disconnected (while connected). Under certain conditions, err may contain zero or more of these properties:

  • responseCode - The HTTP status code we received if the error occurred during the handshake
  • responseText - The HTTP status text we received if the error occurred during the handshake
  • httpVersion - The HTTP version employed by the server if the error occurred during the handshake
  • headers - An object containing the HTTP headers we received from the server if the error occurred during the handshake
  • expected - A string containing the Sec-WebSocket-Accept value we expected to receive, if the error occurred because we didn't
  • actual - A string containing the actual Sec-WebSocket-Accept value we received, if the error occurred because it didn't match what we expected
  • state - The connection state at the time of error. Always present.
  • code - A value from the WS13.StatusCode enum, if the error occurred after the WebSocket connection was established