JSON-RPC 2.0 server over WebSockets
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.
The module exports the following:
ConnectionState
- An enum indicating the state an individual connection is inJsonRpcErrorCode
- An enum containing reserved JSON-RPC error codesWebSocketStatusCode
- An enum containing WebSocket closure status codesRpcError
- An extension to Error
used for responding to RPC calls with errorsWsRpcConnection
- An object representing individual connectionsWsRpcServer
- An object for running a serverWsRpcOutgoingConnection
- An object representing outgoing connectionsReserved JSON-RPC error codes are defined here.
Constructed with 3 parameters:
message
- A string containing an error messagecode
- A number containing an error code (MUST be an integer)data
- An optional value of any type to be sent along with the error
const {RpcError} = require('websocket13-jsonrpc2');
throw new RpcError('File not found', 100, {filename: '/root/example.txt'});
This class is not to be instantiated directly.
A string containing a UUIDv4 assigned to this connection.
The remote IP address.
The WsRpcServer
that spawned this connection.
The state of this connection. This is a value from ConnectionState
The data object from the handshake
event for this connection.
An array containing the string name of each group that this connection is a member of.
statusCode
- A value from WebSocketStatusCode
reason
- An optional string reason for the closureCloses an active connection. If the connection is already closed, does nothing.
Same as tls.TLSSocket.getPeerCertificate
.
Returns null
if the current connection is not secure.
Same as tls.TLSSocket.getProtocol
.
Returns null
if the current connection is not secure.
key
- Stringvalue
- Any typeAssociate 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.
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.
async function pingClient() {
let latency = await client.ping();
console.log(`Client ${client.id}'s latency is ${latency} milliseconds.`);
}
group
- String group nameJoins 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.
group
- String group nameLeaves 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.
method
- String method nameparams
- Any data typeSends 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.
method
- String method nameparams
- Any data typeSends 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.
pingTime
- Round-trip latency in millisecondsEmitted periodically (unless you’ve disabled pings in options) with the latency of the connection.
This class instantiates a WebSocket server.
The constructor takes a single options
object:
requireObjectParams
- See belowWS13.WebSocketServer
protocols
.If requireObjectParams
is set to true, then the following will happen:
params
property of incoming messages is undefined or null, it will be converted to {}
(empty object)params
property of incoming messages is not an object, array, undefined, or null, then the message will be"Invalid params"
params
property of outgoing messages is undefined or null, it will be converted to {}
(empty object)This 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]) => {
if (typeof a != 'number' || typeof b != 'number') {
throw new RpcError('An operand is not a number', JsonRpcErrorCode.InvalidParams);
}
return a + b;
});
The module cannot validate that the incoming params
object actually contains the keys you’re expecting, so you still
need to check their types and values yourself.
handshakeData
reject
accept
Same as websocket13’s handshake event,
with these exceptions:
accept()
returns a WsRpcConnection
instead of a WebSocket
protocol
in accept()
This event must be handled or else all incoming connections will stall.
connection
- The WsRpcConnection
that connectedEmitted when a new connection is established.
connection
- The WsRpcConnection
that disconnectedcode
- A value from WebSocketStatusCode
reason
- A string, possibly empty, describing why they disconnectedinitiatedByUs
- A boolean indicating whether the disconnection was initiated by the server (true) or by the client (false)Emitted when a remote disconnects.
An array containing all currently-active WsRpcConnection
objects.
An array containing strings of the names of all groups that currently have members.
server
- Either an http.Server
or an https.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)Connection: keep-alive, upgrade
is valid, but Connection: keep-alive upgrade
is notGET
Sec-WebSocket-Key
header or when base64-decoded, it is not 16 bytes in lengthSec-WebSocket-Version
header or the header’s value is not 13
group
- Either a group name or an array of group namesReturns 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.
name
- String method namehandler
- A function to be invoked when the method is calledRegisters a new method. When JSON-RPC messages invoking this method are received, the handler
will be called with the
signature (WsRpcConnection connection, 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.
By default, if a method invocation is received that does not match any registered method, a method not found error will
be sent back. If you want to process unregistered methods yourself, you can use the DEFAULT_HANDLER symbol.
const {RpcError, JsonRpcErrorCode, DEFAULT_HANDLER} = 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];
});
// This is only safe if you've enabled requireObjectParams, otherwise an incoming params value that isn't an object or
// array will cause a crash.
server.registerMethod('Subtract', (connection, [a, b]) => {
if (typeof a != 'number' || typeof b != 'number') {
throw new RpcError('Invalid params', JsonRpcErrorCode.InvalidParams);
}
return a - b;
});
server.registerMethod(DEFAULT_HANDLER, (connection, method, params) => {
console.log(`Client ${connection.id} invoked unregistered method ${method} with params ${params}`);
return 1;
});
name
- String namehandler
- A function to be invoked when the notification is receivedRegisters a new notification. When JSON-RPC messages containing this notification are received, the handler
will be
called with the signature (WsRpcConnection connection, 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.
You can also register a default handler for notifications in the same way as for methods.
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 sendparams
- Any data typeSends 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.
Returns a number indicating how many clients received this notification.
name
- String name of notification to sendparams
- Any data typeSends a JSON-RPC notification to all connected clients.
Returns a number indicating how many clients received this notification.
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
- See requireObjectParams
documentation for WsRpcServer
WS13.WebSocket
protocols
.
const {WsRpcOutgoingConnection} = require('websocket13-jsonrpc');
let conn = new WsRpcOutgoingConnection('ws://127.0.0.1:8080', {pingInterval: 30000});
Always null
for outgoing connections.
Always []
(empty array) for outgoing connections.
Outgoing connections cannot be joined to groups, so this method throws an Error if invoked.
Outgoing connections cannot be joined to groups, so this method throws an Error if invoked.
name
- Stringhandler
- FunctionFunctionally identical to WsRpcServer#registerMethod(name, handler)
.
This is how you should register methods for outgoing connections.
name
- Stringhandler
- FunctionFunctionally identical to WsRpcServer#registerNotification(name, handler)
.
This is how you should register notifications for outgoing connections.
details
- An object containing connection details. Identical to WS13.WebSocket#connected
Emitted when the connection is successfully established.
code
- A value from WebSocketStatusCode
reason
- A string, possibly empty, describing why we disconnectedinitiatedByUs
- 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.
err
- An Error
objectEmitted 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 handshakeresponseText
- The HTTP status text we received if the error occurred during the handshakehttpVersion
- The HTTP version employed by the server if the error occurred during the handshakeheaders
- An object containing the HTTP headers we received from the server if the error occurred during the handshakeexpected
- A string containing the Sec-WebSocket-Accept
value we expected to receive, if the error occurred because we didn’tactual
- A string containing the actual Sec-WebSocket-Accept
value we received, if the error occurred because it didn’t match what we expectedstate
- 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