Bidirectional JSONRPC over web sockets or HTTP with extensive plugin support.
npm install jsonrpc-bidirectionalJSONRPC.Plugins.Client.WebSocketTransport accepts connected W3C compatible WebSocket class instances (out of the box browser WebSocket and Node.js websockets/ws WebSocket).
WebSocketServer implementations are supported if API compatible with websockets/ws (constructor and events), or made compatible through an adapter.
JSONRPC.WebSocketAdapters.uws.WebSocketWrapper. All obtained uws.WebSocket instances (from the connection event, or instantiated directly) must be wrapped (and thus replaced) with an instance of this class. According to localhost benchmarks of this library, ws performs better than uws. __Warning:__ uws is buggy when calling .close() on the server (segmentation fault or infinite hang) and it also immediately closes connections with an empty reason string if very large payloads are sent. See tests/uws_bug*.
JSONRPC.Client has embeded support for HTTP requests, through fetch (polyfills for older browsers exist, and work just fine). JSONRPC.Server has the attachToHTTPServer method.
JSONRPC.Plugins.Client.WorkerTransport and JSONRPC.BidirectionalWorkerRouter accept Node.js cluster.Worker or standard browser Worker class instances.
tests/Tests/AllTests.runClusterTests() and tests/Browser/index.html for an example of how to setup servers and clients for master-worker asynchronous communication.
src/NodeClusterBase to automatically scale to all CPU cores using forked child processed.
JSONRPC.Plugins.Client.WorkerThreadTransport and JSONRPC.BidirectionalWorkerThreadRouter accept Node.js worker_threads.Worker class instances.
tests/Tests/AllTests.runThreadsTests() for an example of how to setup servers and clients for inter-thread asynchronous communication.
src/NodeWorkerThreadsBase to automatically scale to all CPU cores using worker threads.
JSONRPC.Plugins.Client.WebRTCTransport and JSONRPC.BidirectionalWebRTCRouter classes accept standard browser connected RTCConnection class instances.
tests/Tests/BrowserWebRTC/* for a browser side example. See tests/Tests/TestEndpoint for the server side mediator.
JSONRPC.Plugins.Client.WebSocketTransport for an example.
JSONRPC.ClientPluginBase and JSONRPC.ServerPluginBase. Plugins may be added on JSONRPC.Server and JSONRPC.Client instances using the .addPlugin() method.
JSONRPC.ClientPluginBase and JSONRPC.ServerPluginBase are also event names (event handlers have the same params) while JSONRPC.Server and JSONRPC.Client emit these events.
JSONRPC.Server exports methods of registered JSONRPC.EndpointBase subclass instances.
ws://localhost/api, http://localhost/api, as both have /api as path.
JavaScript
const JSONRPC = require("jsonrpc-bidirectional");
module.exports =
class TestEndpoint extends JSONRPC.EndpointBase
{
constructor()
{
super(
/strName/ "Test",
/strPath/ "/api",
/objReflection/ {}, // Reserved for future use.
/classReverseCallsClient/ JSONRPC.Client // This may be left undefined
);
// The class reference classReverseCallsClient must be specified to enable bidirectional JSON-RPC over a single WebSocket connection.
// If may be left undefined for one-way interrogation.
// It must contain a reference to a subclass of JSONRPC.Client or a reference to the JSONRPC.Client class itself.
}
async ping(incomingRequest, strReturn, bThrow)
{
if(bThrow)
{
throw new JSONRPC.Exception("You asked me to throw.");
}
// If using bidirectional JSON-RPC over a single WebSocket connection, a JSONRPC.Client subclass instance is available.
// It is an instance of the class specified in the constructor of this EndpointBase subclass, classReverseCallsClient.
// Also, it is attached to the same WebSocket connection of the current request.
await incomingRequest.reverseCallsClient.rpc("methodOnTheOtherSide", ["paramValue", true, false]);
return strReturn;
}
async divide(incomingRequest, nLeft, nRight)
{
return nLeft / nRight;
}
};
`
Extending the client
Extending the JSONRPC.Client base class makes the code more readable.
This client's function names and params correspond to what TestEndpoint exports (defined above).
`JavaScript
const JSONRPC = require("jsonrpc-bidirectional");
module.exports =
class TestClient extends JSONRPC.Client
{
/**
* @param {JSONRPC.IncomingRequest} incomingRequest
* @param {string} strReturn
* @param {boolean} bThrow
*
* @returns {string}
*/
async ping(strReturn, bThrow)
{
return this.rpc("ping", [...arguments]);
}
/**
* JSONRPC 2.0 notification.
* The server may keep processing "after" this function returns, as the server will never send a response.
*
* @param {JSONRPC.IncomingRequest} incomingRequest
*
* @returns null
*/
async pingFireAndForget()
{
return this.rpc("ping", [...arguments], /bNotification/ true);
}
/**
* @param {number} nLeft
* @param {number} nRight
*
* @returns {number}
*/
async divide(nLeft, nRight)
{
return this.rpc("divide", [...arguments]);
}
}
`
Simple JSONRPC.Server over HTTP
`JavaScript
const JSONRPC = require("jsonrpc-bidirectional");
const httpServer = http.createServer();
const jsonrpcServer = new JSONRPC.Server();
jsonrpcServer.registerEndpoint(new TestEndpoint());
jsonrpcServer.attachToHTTPServer(httpServer, "/api/");
// By default, JSONRPC.Server rejects all requests as not authenticated and not authorized.
jsonrpcServer.addPlugin(new JSONRPC.Plugins.Server.AuthenticationSkip());
jsonrpcServer.addPlugin(new JSONRPC.Plugins.Server.AuthorizeAll());
httpServer.listen(80);
`
Simple JSONRPC.Client over HTTP
`JavaScript
const JSONRPC = require("jsonrpc-bidirectional");
const testClient = new TestClient("http://localhost/api");
const fDivisionResult = await testClient.divide(2, 1);
`
Simple JSONRPC.Client over WebSocket
Non-bidirectional JSONRPC.Client over a WebSocket client connection.
The client automatically reconnects in case of a connection drop.
`JavaScript
const JSONRPC = require("jsonrpc-bidirectional");
const WebSocket = require("ws");
// The WebSocketTransport plugin requires that the WebSocket connection instance supports the close, error and message events of the https://github.com/websockets/ws API.
// If not using websockets/ws, the WebSocketTransport plugin may be extended to override WebSocketTransport._setupWebSocket().
const testClient = new TestClient("http://localhost/api");
// Reconnecting websocket transport.
const webSocketTransport = new JSONRPC.Plugins.Client.WebSocketTransport(
/ws/ null,
/bBidirectionalMode/ false,
{
bAutoReconnect: true,
strWebSocketURL: "ws://localhost/api"",
// Optional WebSocket extra-initialization after the WebSocket becomes "open" (connected).
fnWaitReadyOnConnected: async() => {
// Optional to authenticate the connection.
await testClient.rpcX({method: "login", params: ["admin", "password"], skipWaitReadyOnConnect: true});
}
}
);
testClient.addPlugin(webSocketTransport);
const fDivisionResult = await testClient.divide(2, 1);
`
Bidirectional JSON-RPC over WebSocket
This JSONRPC server and client can be bidirectional over __a single WebSocket connection__.
In other words, there may be a JSONRPC.Server instance __and__ a JSONRPC.Client instance at one end, and another pair (or more) at the other end.
At the WebSocket or __TCP/HTTP server__ connection end, the JSONRPC.Server will automatically instantiate a JSONRPC.Client subclass per connection (of the class specified by the serving endpoint).
Common:
`JavaScript
// BidirectionalWebsocketRouter and the WebSocketTransport plugin both require that the WebSocket connection instance supports the close, error and message events of the https://github.com/websockets/ws API.
// If not using websockets/ws, a wrapping adapter which emits the above events must be provided (if the WebSocket implementation is not already compatible with websockets/ws).
// BidirectionalWebsocketRouter also uses properties on WebSocket instances to get the URL path (needed to determine the endpoint), like this: webSocket.url ? webSocket.url : webSocket.upgradeReq.url.
const JSONRPC = require("jsonrpc-bidirectional");
const WebSocket = require("ws");
const WebSocketServer = WebSocket.Server;
`
__Site A. WebSocket server (accepts incoming TCP connections), JSONRPC server & client:__
`JavaScript
const jsonrpcServer = new JSONRPC.Server();
jsonrpcServer.registerEndpoint(new TestEndpoint()); // See "Define an endpoint" section above.
// By default, JSONRPC.Server rejects all requests as not authenticated and not authorized.
jsonrpcServer.addPlugin(new JSONRPC.Plugins.Server.AuthenticationSkip());
jsonrpcServer.addPlugin(new JSONRPC.Plugins.Server.AuthorizeAll());
const wsJSONRPCRouter = new JSONRPC.BidirectionalWebsocketRouter(jsonrpcServer);
// Optional.
wsJSONRPCRouter.on("madeReverseCallsClient", (clientReverseCalls) => { /add plugins or just setup the client even further/ });
// Alternatively reuse existing web server:
// const webSocketServer = new WebSocketServer({server: httpServerInstance});
const webSocketServer = new WebSocketServer({port: 8080});
webSocketServer.on("error", (error) => {console.error(error); process.exit(1);});
webSocketServer.on(
"connection",
async(webSocket, upgradeRequest) =>
{
const nWebSocketConnectionID = wsJSONRPCRouter.addWebSocketSync(webSocket, upgradeRequest);
// Do something with nWebSocketConnectionID and webSocket here, like register them as a pair with an authorization plugin.
// const clientForThisConnection = wsJSONRPCRouter.connectionIDToSingletonClient(nWebSocketConnectionID, JSONRPC.Client);
}
);
`
__Site B. WebSocket client (connects to the above WebSocket TCP server), JSONRPC server & client:__
`JavaScript
const jsonrpcServer = new JSONRPC.Server();
jsonrpcServer.registerEndpoint(new TestEndpoint()); // See "Define an endpoint" section above.
// By default, JSONRPC.Server rejects all requests as not authenticated and not authorized.
jsonrpcServer.addPlugin(new JSONRPC.Plugins.Server.AuthenticationSkip());
jsonrpcServer.addPlugin(new JSONRPC.Plugins.Server.AuthorizeAll());
const webSocket = new WebSocket("ws://localhost:8080/api");
await new Promise((fnResolve, fnReject) => {
webSocket.addEventListener("open", fnResolve);
webSocket.addEventListener("error", fnReject);
});
const wsJSONRPCRouter = new JSONRPC.BidirectionalWebsocketRouter(jsonrpcServer);
const nWebSocketConnectionID = wsJSONRPCRouter.addWebSocketSync(webSocket);
// Obtain single client. See above section "Extending the client" for the TestClient class (subclass of JSONRPC.Client).
const theOnlyClient = wsJSONRPCRouter.connectionIDToSingletonClient(nWebSocketConnectionID, TestClient);
await theOnlyClient.divide(3, 2);
`
__Site B (ES5 browser). WebSocket client (connects to the above WebSocket TCP server), JSONRPC server & client:__
`html
Tests
Open the developer tools console (F12 for most browsers, CTRL+SHIFT+I in Electron) to see errors or manually make calls.
``