Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Place your settings in this file to overwrite default and user settings.
{
// Configure glob patterns for excluding files and folders.
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"*.min.js": true,
"*.js.map": true
}
}
8 changes: 6 additions & 2 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

[![Build Status](https://travis-ci.org/oxygens/jsonrpc-bidirectional.svg?branch=master)](https://travis-ci.org/oxygens/jsonrpc-bidirectional)

A main goal of this project is to have the JSONRPC server and client support __bidirectional JSON-RPC requests over a single WebSocket connection.__
A main goal of this project is to have the JSON-RPC server and client support __bidirectional JSON-RPC requests over a single WebSocket connection.__

Both the server and client support two __transports, HTTP and WebSocket__ ([websockets/ws](https://github.com/websockets/ws) compatible API), and allow more through plugin extensibility.
This library is tested in __browsers__ (at least Internet Explorer 10) and in __Node.js__ (at least 7.8, or 7.x with the --harmony flag.).

Both the server and client support two __transports, HTTP and WebSocket__, and allow more through plugin extensibility.

For WebSocket client support it expects W3C compatible WebSocket class instances (browser `WebSocket` and Node.js [websockets/ws](https://github.com/websockets/ws) `WebSocket`). On the Node.js side it is tested to work with [websockets/ws](https://github.com/websockets/ws). `WebSocketServer` implementations are supported only if 100% API compatible with `websockets/ws`, or made compatible through an adapter.

Plugins are allowed to replace the JSON-RPC protocol altogether, extend the protocol or wrap it.

Expand Down
8 changes: 0 additions & 8 deletions builds/browser/es5/jsonrpc.js

This file was deleted.

1 change: 0 additions & 1 deletion builds/browser/es5/jsonrpc.js.map

This file was deleted.

8 changes: 8 additions & 0 deletions builds/browser/es5/jsonrpc.min.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions builds/browser/es5/jsonrpc.min.js.map

Large diffs are not rendered by default.

7 changes: 1 addition & 6 deletions index_webpack.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
// Do not use const here, webpack/babel issues.
var objExports = {
module.exports = {
JSONRPC: require("./index")
};

delete objExports.JSONRPC.Plugins.Client.WebSocketTransport;
delete objExports.JSONRPC.Plugins.Client.ProcessStdIOTransport;

module.exports = objExports;
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "jsonrpc-bidirectional",
"description": "Bidirectional JSONRPC over web sockets or HTTP with extensive plugin support.",
"version": "2.1.3",
"version": "2.1.11",
"scripts": {
"build": "node build.js",
"test": "node tests/main.js",
Expand Down Expand Up @@ -37,7 +37,6 @@
},
"dependencies": {
"crypto-js": "^3.1.9-1",
"jssha": "^2.2.0",
"node-fetch": "^1.6.3",
"ws": "^2.2.3"
},
Expand All @@ -57,5 +56,9 @@
"phantom": "^4.0.2",
"webpack": "*",
"sleep-promise": "*"
},
"optionalDependencies": {
"bufferutil": "^3.0.0",
"utf-8-validate": "^3.0.1"
}
}
}
42 changes: 24 additions & 18 deletions src/BidirectionalWebsocketRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ JSONRPC.Utils = require("./Utils");

const EventEmitter = require("events");

const WebSocket = require("ws");


/**
* The "madeReverseCallsClient" event offers automatically instantiated API clients (API clients are instantiated for each connection, lazily).
Expand Down Expand Up @@ -57,7 +59,7 @@ class BidirectionalWebsocketRouter extends EventEmitter
*/
async addWebSocket(webSocket)
{
if(webSocket.readyState === webSocket.constructor.CLOSED)
if(webSocket.readyState === WebSocket.CLOSED)
{
// WebSocket.CLOSING should be followed by a closed event.
// WebSocket.OPEN is desired.
Expand All @@ -81,33 +83,37 @@ class BidirectionalWebsocketRouter extends EventEmitter

this._objSessions[nWebSocketConnectionID] = objSession;

webSocket.on(
webSocket.addEventListener(
"close",
(code, message) => {
(closeEvent) => {
//closeEvent.code;
//closeEvent.reason;
//closeEvent.wasClean;

delete this._objSessions[nWebSocketConnectionID];
}
);

webSocket.on(
webSocket.addEventListener(
"error",
(error) => {
delete this._objSessions[nWebSocketConnectionID];

if(webSocket.readyState === webSocket.constructor.OPEN)
if(webSocket.readyState === WebSocket.OPEN)
{
webSocket.close(
/* CloseEvent.Internal Error */ 1011,
/*CLOSE_NORMAL*/ 1000, // Chrome only supports 1000 or the 3000-3999 range ///* CloseEvent.Internal Error */ 1011,
error.message
);
}
}
);

webSocket.on(
webSocket.addEventListener(
"message",
async (strMessage) =>
async (messageEvent) =>
{
await this._routeMessage(strMessage, objSession);//.then(() => {}).catch(console.error);
await this._routeMessage(messageEvent.data, objSession);//.then(() => {}).catch(console.error);
}
);

Expand Down Expand Up @@ -222,7 +228,7 @@ class BidirectionalWebsocketRouter extends EventEmitter

console.log("[" + process.pid + "] Unclean state. Unable to match WebSocket message to an existing Promise or qualify it as a request or response.");
webSocket.close(
/* CloseEvent.Internal Error */ 1011,
/*CLOSE_NORMAL*/ 1000, // Chrome only supports 1000 or the 3000-3999 range ///* CloseEvent.Internal Error */ 1011,
"Unclean state. Unable to match WebSocket message to an existing Promise or qualify it as a request or response."
);

Expand All @@ -235,7 +241,7 @@ class BidirectionalWebsocketRouter extends EventEmitter
{
if(!this._jsonrpcServer)
{
if(webSocket.readyState === webSocket.constructor.OPEN)
if(webSocket.readyState === WebSocket.OPEN)
{
webSocket.send(JSON.stringify({
id: null,
Expand Down Expand Up @@ -290,7 +296,7 @@ class BidirectionalWebsocketRouter extends EventEmitter

await this._jsonrpcServer.processRequest(incomingRequest);

if(webSocket.readyState !== webSocket.constructor.OPEN)
if(webSocket.readyState !== WebSocket.OPEN)
{
console.error("webSocket.readyState: " + JSON.stringify(webSocket.readyState) + ". Request was " + strMessage + ". Attempted responding with " + JSON.stringify(incomingRequest.callResultToBeSerialized, undefined, "\t") + ".");
}
Expand All @@ -306,7 +312,7 @@ class BidirectionalWebsocketRouter extends EventEmitter
{
if(!this._jsonrpcServer)
{
if(webSocket.readyState === webSocket.constructor.OPEN)
if(webSocket.readyState === WebSocket.OPEN)
{
webSocket.send(JSON.stringify({
id: null,
Expand All @@ -319,10 +325,10 @@ class BidirectionalWebsocketRouter extends EventEmitter
}
}

if(webSocket.readyState === webSocket.constructor.OPEN)
if(webSocket.readyState === WebSocket.OPEN)
{
webSocket.close(
/* CloseEvent.Internal Error */ 1011,
/*CLOSE_NORMAL*/ 1000, // Chrome only supports 1000 or the 3000-3999 range ///* CloseEvent.Internal Error */ 1011,
"How can the client be not initialized, and yet getting responses from phantom requests? Closing websocket."
);
}
Expand Down Expand Up @@ -355,7 +361,7 @@ class BidirectionalWebsocketRouter extends EventEmitter
&& this._objSessions[nWebSocketConnectionID].clientWebSocketTransportPlugin === null
)
{
if(webSocket.readyState === webSocket.constructor.OPEN)
if(webSocket.readyState === WebSocket.OPEN)
{
webSocket.send(JSON.stringify({
id: null,
Expand All @@ -368,11 +374,11 @@ class BidirectionalWebsocketRouter extends EventEmitter
}
}

if(webSocket.readyState === webSocket.constructor.OPEN)
if(webSocket.readyState === WebSocket.OPEN)
{
console.log("[" + process.pid + "] Unclean state. Closing websocket.");
webSocket.close(
/* CloseEvent.Internal Error */ 1011,
/*CLOSE_NORMAL*/ 1000, // Chrome only supports 1000 or the 3000-3999 range ///* CloseEvent.Internal Error */ 1011,
"Unclean state. Closing websocket."
);
}
Expand Down
23 changes: 13 additions & 10 deletions src/Plugins/Client/WebSocketTransport.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ JSONRPC.Utils = require("../../Utils");

const assert = require("assert");

const WebSocket = require("ws");


module.exports =
class WebSocketTransport extends JSONRPC.ClientPluginBase
{
Expand Down Expand Up @@ -57,7 +60,7 @@ class WebSocketTransport extends JSONRPC.ClientPluginBase
console.error(error);
console.error("Unable to parse JSON. RAW remote response: " + strResponse);
this._webSocket.close(
/* CloseEvent.Internal Error */ 1011,
/*CLOSE_NORMAL*/ 1000, // Chrome only supports 1000 or the 3000-3999 range ///* CloseEvent.Internal Error */ 1011,
"Unable to parse JSON. RAW remote response: " + strResponse
);

Expand All @@ -74,7 +77,7 @@ class WebSocketTransport extends JSONRPC.ClientPluginBase
console.error(new Error("RAW remote message: " + strResponse));
console.log("[" + process.pid + "] Unclean state. Unable to match WebSocket message to an existing Promise or qualify it as a request.");
this.webSocket.close(
/* CloseEvent.Internal Error */ 1011,
/*CLOSE_NORMAL*/ 1000, // Chrome only supports 1000 or the 3000-3999 range ///* CloseEvent.Internal Error */ 1011,
"Unclean state. Unable to match WebSocket message to an existing Promise or qualify it as a request."
);

Expand Down Expand Up @@ -105,7 +108,7 @@ class WebSocketTransport extends JSONRPC.ClientPluginBase
return;
}

if(this.webSocket.readyState !== this.webSocket.constructor.OPEN)
if(this.webSocket.readyState !== WebSocket.OPEN)
{
throw new Error("WebSocket not connected.");
}
Expand Down Expand Up @@ -161,14 +164,14 @@ class WebSocketTransport extends JSONRPC.ClientPluginBase
{
//assert(webSocket.constructor.name === "WebSocket");

this._webSocket.on(
this._webSocket.addEventListener(
"close",
(code, message) => {
this.rejectAllPromises(new Error("WebSocket closed. Code: " + JSON.stringify(code) + ". Message: " + JSON.stringify(message) + "."));
(closeEvent) => {
this.rejectAllPromises(new Error("WebSocket closed. Code: " + JSON.stringify(closeEvent.code) + ". Message: " + JSON.stringify(closeEvent.reason) + ". wasClean: " + JSON.stringify(closeEvent.wasClean)));
}
);

this._webSocket.on(
this._webSocket.addEventListener(
"error",
(error) => {
this.rejectAllPromises(error);
Expand All @@ -177,10 +180,10 @@ class WebSocketTransport extends JSONRPC.ClientPluginBase

if(!this._bBidirectionalWebSocketMode)
{
this._webSocket.on(
this._webSocket.addEventListener(
"message",
async (strMessage) => {
await this.processResponse(strMessage);
async (messageEvent) => {
await this.processResponse(messageEvent.data);
}
);
}
Expand Down
19 changes: 18 additions & 1 deletion src/Server.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ class Server extends EventEmitter
*
* @param {http.Server} httpServer
* @param {string} strRootPath
* @param {boolean} bSharedWithWebSocketServer
*/
async attachToHTTPServer(httpServer, strRootPath)
async attachToHTTPServer(httpServer, strRootPath, bSharedWithWebSocketServer)
{
bSharedWithWebSocketServer = !!bSharedWithWebSocketServer;
assert(typeof strRootPath === "string", typeof strRootPath);

strRootPath = JSONRPC.EndpointBase.normalizePath(strRootPath);
Expand All @@ -57,6 +59,21 @@ class Server extends EventEmitter
return;
}

if(httpRequest.headers["sec-websocket-version"])
{
if(bSharedWithWebSocketServer)
{
// Do not call .end() here, or co-existing HTTP handlers on the same server will not have a chance to set headers or respond.
// httpResponse.end();
return;
}

console.error("Received websocket upgrade request, yet not sharing the HTTP connection with a WebSocket.");
httpResponse.statusCode = 403; //Forbidden.
httpResponse.end();
return;
}

try
{
// Default.
Expand Down
Loading