From b95ee3f7227248c3f3033351da86e35d91351a4b Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 18 Feb 2018 20:05:58 +0100 Subject: [PATCH 001/265] Isolate each test case from one another --- test/integration.js | 142 ++++++++++++++++++++++---------------------- 1 file changed, 72 insertions(+), 70 deletions(-) diff --git a/test/integration.js b/test/integration.js index 73b55cd..99b5fea 100644 --- a/test/integration.js +++ b/test/integration.js @@ -6,16 +6,10 @@ var localStorage = require('localStorage'); // Add to the global scope like in browser. global.localStorage = localStorage; -var PORT = 8008; +var portNumber = 8008; -var clientOptions = { - hostname: '127.0.0.1', - port: PORT -}; - -var serverOptions = { - authKey: 'testkey' -}; +var clientOptions; +var serverOptions; var allowedUsers = { bob: true, @@ -24,8 +18,8 @@ var allowedUsers = { var TEN_DAYS_IN_SECONDS = 60 * 60 * 24 * 10; -var validSignedAuthTokenBob = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImJvYiIsImV4cCI6MTU5NjE0NzQ3NzQ3LCJpYXQiOjE1MDI3NDc3NDZ9.hjR769TX0vpDzZPl7a1UgudYtuUj8KikJ105IV3UHsc'; -var validSignedAuthTokenAlice = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNTE4NzI4MjU5LCJleHAiOjE1MTg4MTQ2NTl9.PUkz5_OvfVO9fMJo_2-rJtcDsEFHJCz6yDaMMb9R8Ls'; +var validSignedAuthTokenBob = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImJvYiIsImV4cCI6MzE2Mzc1ODk3OTA4MDMxMCwiaWF0IjoxNTAyNzQ3NzQ2fQ.dSZOfsImq4AvCu-Or3Fcmo7JNv1hrV3WqxaiSKkTtAo'; +var validSignedAuthTokenAlice = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNTE4NzI4MjU5LCJleHAiOjMxNjM3NTg5NzkwODAzMTB9.XxbzPPnnXrJfZrS0FJwb_EAhIu2VY5i7rGyUThtNLh4'; var invalidSignedAuthToken = 'fakebGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.fakec2VybmFtZSI6ImJvYiIsImlhdCI6MTUwMjYyNTIxMywiZXhwIjoxNTAyNzExNjEzfQ.fakemYcOOjM9bzmS4UYRvlWSk_lm3WGHvclmFjLbyOk'; var server, client; @@ -114,8 +108,16 @@ var destroyTestCase = function (next) { }; describe('Integration tests', function () { - before('Run the server before start', function (done) { - server = socketClusterServer.listen(PORT, serverOptions); + beforeEach('Run the server before start', function (done) { + clientOptions = { + hostname: '127.0.0.1', + port: portNumber + }; + serverOptions = { + authKey: 'testkey' + }; + + server = socketClusterServer.listen(portNumber, serverOptions); server.on('connection', connectionHandler); server.addMiddleware(server.MIDDLEWARE_AUTHENTICATE, function (req, next) { @@ -133,8 +135,9 @@ describe('Integration tests', function () { }); }); - after('Shut down server afterwards', function (done) { + afterEach('Shut down server afterwards', function (done) { server.close(); + portNumber++; done(); }); @@ -233,8 +236,8 @@ describe('Integration tests', function () { }); it('Token should be available inside login callback if token engine signing is synchronous', function (done) { - var port = 8009; - server = socketClusterServer.listen(port, { + portNumber++; + server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, authSignAsync: false }); @@ -242,7 +245,7 @@ describe('Integration tests', function () { server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); client.once('connect', function (statusA) { @@ -257,8 +260,8 @@ describe('Integration tests', function () { }); it('If token engine signing is asynchronous, authentication can be captured using the authenticate event', function (done) { - var port = 8010; - server = socketClusterServer.listen(port, { + portNumber++; + server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, authSignAsync: true }); @@ -266,7 +269,7 @@ describe('Integration tests', function () { server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); client.once('connect', function (statusA) { @@ -282,8 +285,8 @@ describe('Integration tests', function () { }); it('Should still work if token verification is asynchronous', function (done) { - var port = 8011; - server = socketClusterServer.listen(port, { + portNumber++; + server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, authVerifyAsync: false }); @@ -291,7 +294,7 @@ describe('Integration tests', function () { server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); client.once('connect', function (statusA) { @@ -313,8 +316,8 @@ describe('Integration tests', function () { }); it('Should set the correct expiry when using expiresIn option when creating a JWT with socket.setAuthToken', function (done) { - var port = 8012; - server = socketClusterServer.listen(port, { + portNumber++; + server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, authVerifyAsync: false }); @@ -322,7 +325,7 @@ describe('Integration tests', function () { server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); client.once('connect', function (statusA) { @@ -341,8 +344,8 @@ describe('Integration tests', function () { }); it('Should set the correct expiry when adding exp claim when creating a JWT with socket.setAuthToken', function (done) { - var port = 8013; - server = socketClusterServer.listen(port, { + portNumber++; + server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, authVerifyAsync: false }); @@ -350,7 +353,7 @@ describe('Integration tests', function () { server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); client.once('connect', function (statusA) { @@ -369,8 +372,8 @@ describe('Integration tests', function () { }); it('The exp claim should have priority over expiresIn option when using socket.setAuthToken', function (done) { - var port = 8014; - server = socketClusterServer.listen(port, { + portNumber++; + server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, authVerifyAsync: false }); @@ -378,7 +381,7 @@ describe('Integration tests', function () { server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); client.once('connect', function (statusA) { @@ -397,8 +400,8 @@ describe('Integration tests', function () { }); it('Should send back error if socket.setAuthToken tries to set both iss claim and issuer option', function (done) { - var port = 8015; - server = socketClusterServer.listen(port, { + portNumber++; + server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, authVerifyAsync: false }); @@ -408,7 +411,7 @@ describe('Integration tests', function () { server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); client.once('connect', function (statusA) { @@ -436,8 +439,8 @@ describe('Integration tests', function () { describe('Event flow', function () { it('Should support subscription batching', function (done) { - var port = 8016; - server = socketClusterServer.listen(port, { + portNumber++; + server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey }); server.on('connection', function (socket) { @@ -474,7 +477,7 @@ describe('Integration tests', function () { server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); var channelList = []; @@ -508,8 +511,8 @@ describe('Integration tests', function () { }); it('should remove client data from the server when client disconnects before authentication process finished', function (done) { - var port = 8017; - server = socketClusterServer.listen(port, { + portNumber++; + server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey }); server.setAuthEngine({ @@ -523,7 +526,7 @@ describe('Integration tests', function () { server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); var serverSocket; @@ -548,8 +551,8 @@ describe('Integration tests', function () { }); it('Client should not be able to subscribe to a channel before the handshake has completed', function (done) { - var port = 8018; - server = socketClusterServer.listen(port, { + portNumber++; + server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey }); server.setAuthEngine({ @@ -563,7 +566,7 @@ describe('Integration tests', function () { server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); @@ -598,8 +601,8 @@ describe('Integration tests', function () { }); it('Server-side socket disconnect event should not trigger if the socket did not complete the handshake; instead, it should trigger connectAbort', function (done) { - var port = 8019; - server = socketClusterServer.listen(port, { + portNumber++; + server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey }); server.setAuthEngine({ @@ -613,7 +616,7 @@ describe('Integration tests', function () { server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); @@ -667,8 +670,8 @@ describe('Integration tests', function () { }); it('Server-side socket disconnect event should trigger if the socket completed the handshake (not connectAbort)', function (done) { - var port = 8020; - server = socketClusterServer.listen(port, { + portNumber++; + server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey }); server.setAuthEngine({ @@ -682,7 +685,7 @@ describe('Integration tests', function () { server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); @@ -736,8 +739,8 @@ describe('Integration tests', function () { }); it('Server-side socket connect event and server connection event should trigger', function (done) { - var port = 8021; - server = socketClusterServer.listen(port, { + portNumber++; + server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey }); @@ -755,7 +758,7 @@ describe('Integration tests', function () { server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); @@ -817,8 +820,8 @@ describe('Integration tests', function () { }); it('The close event should trigger when the socket loses the connection before the handshake', function (done) { - var port = 8022; - server = socketClusterServer.listen(port, { + portNumber++; + server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey }); server.setAuthEngine({ @@ -832,7 +835,7 @@ describe('Integration tests', function () { server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); @@ -868,8 +871,8 @@ describe('Integration tests', function () { }); it('The close event should trigger when the socket loses the connection after the handshake', function (done) { - var port = 8023; - server = socketClusterServer.listen(port, { + portNumber++; + server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey }); server.setAuthEngine({ @@ -883,7 +886,7 @@ describe('Integration tests', function () { server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); @@ -919,8 +922,8 @@ describe('Integration tests', function () { }); it('Exchange is attached to socket before the handshake event is triggered', function (done) { - var port = 8024; - server = socketClusterServer.listen(port, { + portNumber++; + server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey }); @@ -929,7 +932,7 @@ describe('Integration tests', function () { server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); @@ -944,8 +947,8 @@ describe('Integration tests', function () { }); it('Server should be able to handle invalid #subscribe and #unsubscribe and #publish packets without crashing', function (done) { - var port = 8025; - server = socketClusterServer.listen(port, { + portNumber++; + server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey }); @@ -954,7 +957,7 @@ describe('Integration tests', function () { server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); @@ -1052,11 +1055,11 @@ describe('Integration tests', function () { }); describe('Middleware', function () { var middlewareFunction; - var port = 8026; var middlewareWasExecuted = false; beforeEach('Launch server without middleware before start', function (done) { - server = socketClusterServer.listen(port, { + portNumber++; + server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey }); server.on('ready', function () { @@ -1067,7 +1070,6 @@ describe('Integration tests', function () { afterEach('Shut down server afterwards', function (done) { destroyTestCase(function () { server.close(); - port++; done(); }); }); @@ -1084,7 +1086,7 @@ describe('Integration tests', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); @@ -1105,7 +1107,7 @@ describe('Integration tests', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, - port: port, + port: portNumber, multiplex: false }); From 06e3a16c50e9be25fd1b1cd9a0ccbae8500588b1 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 18 Feb 2018 21:44:31 +0100 Subject: [PATCH 002/265] Added test case for new MIDDLEWARE_HANDSHAKE_SC --- test/integration.js | 58 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/test/integration.js b/test/integration.js index 99b5fea..374e446 100644 --- a/test/integration.js +++ b/test/integration.js @@ -135,13 +135,9 @@ describe('Integration tests', function () { }); }); - afterEach('Shut down server afterwards', function (done) { + afterEach('Shut down client after each test', function (done) { server.close(); portNumber++; - done(); - }); - - afterEach('Shut down client after each test', function (done) { destroyTestCase(function () { global.localStorage.removeItem('socketCluster.authToken'); done(); @@ -1076,7 +1072,6 @@ describe('Integration tests', function () { describe('MIDDLEWARE_AUTHENTICATE', function () { - it('Should not run authenticate middleware if JWT token does not exist', function (done) { middlewareFunction = function (req, next) { middlewareWasExecuted = true; @@ -1118,5 +1113,56 @@ describe('Integration tests', function () { }); }); }); + + describe('MIDDLEWARE_HANDSHAKE_SC', function () { + it('Should trigger correct events if MIDDLEWARE_HANDSHAKE_SC blocks with an error', function (done) { + var middlewareWasExecuted = false; + var serverWarnings = []; + var clientErrors = []; + var abortStatus; + var abortReason; + + middlewareFunction = function (req, next) { + setTimeout(function () { + middlewareWasExecuted = true; + var err = new Error('SC handshake failed because of invalid query auth parameters'); + err.name = 'InvalidAuthQueryHandshakeError'; + next(err); + }, 100); + }; + server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_SC, middlewareFunction); + + server.on('warning', function (err) { + serverWarnings.push(err); + }); + + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + + client.on('error', function (err) { + clientErrors.push(err); + }); + + client.once('connectAbort', function (status, reason) { + abortStatus = status; + abortReason = reason; + }); + + setTimeout(function () { + assert.equal(middlewareWasExecuted, true); + assert.notEqual(clientErrors[0], null); + assert.equal(clientErrors[0].name, 'InvalidAuthQueryHandshakeError'); + assert.notEqual(clientErrors[1], null); + assert.equal(clientErrors[1].name, 'SocketProtocolError'); + assert.notEqual(serverWarnings[0], null); + assert.equal(serverWarnings[0].name, 'InvalidAuthQueryHandshakeError'); + assert.equal(abortStatus, 4003); + done(); + }, 200); + }); + }); }); }); From 4b3342323bf1763cfe820e60d042c6abdf41a78d Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 19 Feb 2018 01:04:46 +0100 Subject: [PATCH 003/265] Added support for MIDDLEWARE_HANDSHAKE_SC middleware with custom status codes --- scserver.js | 18 +++++-- test/integration.js | 118 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 125 insertions(+), 11 deletions(-) diff --git a/scserver.js b/scserver.js index e7baf90..c4ec897 100644 --- a/scserver.js +++ b/scserver.js @@ -561,9 +561,13 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { self._passThroughHandshakeSCMiddleware({ socket: scSocket - }, function (err) { + }, function (err, statusCode) { if (err) { - respond(err); + var handshakeError = scErrors.dehydrateError(err); + var clientSocketErrorStatus = { + code: statusCode + }; + respond(err, clientSocketErrorStatus); return; } self._processAuthToken(scSocket, signedAuthToken, function (err, isBadToken) { @@ -891,11 +895,17 @@ SCServer.prototype._passThroughHandshakeSCMiddleware = function (options, cb) { }; async.applyEachSeries(this._middleware[this.MIDDLEWARE_HANDSHAKE_SC], request, - function (err) { + function (err, results) { if (callbackInvoked) { self.emit('warning', new InvalidActionError('Callback for ' + self.MIDDLEWARE_HANDSHAKE_SC + ' middleware was already invoked')); } else { callbackInvoked = true; + var statusCode; + if (results.length) { + statusCode = results[results.length - 1] || 4008; + } else { + statusCode = 4008; + } if (err) { if (err === true) { err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + self.MIDDLEWARE_HANDSHAKE_SC + ' middleware', self.MIDDLEWARE_HANDSHAKE_SC); @@ -903,7 +913,7 @@ SCServer.prototype._passThroughHandshakeSCMiddleware = function (options, cb) { self.emit('warning', err); } } - cb(err); + cb(err, statusCode); } } ); diff --git a/test/integration.js b/test/integration.js index 374e446..afc47d3 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1120,13 +1120,12 @@ describe('Integration tests', function () { var serverWarnings = []; var clientErrors = []; var abortStatus; - var abortReason; middlewareFunction = function (req, next) { setTimeout(function () { middlewareWasExecuted = true; - var err = new Error('SC handshake failed because of invalid query auth parameters'); - err.name = 'InvalidAuthQueryHandshakeError'; + var err = new Error('SC handshake failed because the server was too lazy'); + err.name = 'TooLazyHandshakeError'; next(err); }, 100); }; @@ -1148,21 +1147,126 @@ describe('Integration tests', function () { client.once('connectAbort', function (status, reason) { abortStatus = status; - abortReason = reason; }); setTimeout(function () { assert.equal(middlewareWasExecuted, true); assert.notEqual(clientErrors[0], null); - assert.equal(clientErrors[0].name, 'InvalidAuthQueryHandshakeError'); + assert.equal(clientErrors[0].name, 'TooLazyHandshakeError'); assert.notEqual(clientErrors[1], null); assert.equal(clientErrors[1].name, 'SocketProtocolError'); assert.notEqual(serverWarnings[0], null); - assert.equal(serverWarnings[0].name, 'InvalidAuthQueryHandshakeError'); - assert.equal(abortStatus, 4003); + assert.equal(serverWarnings[0].name, 'TooLazyHandshakeError'); + assert.notEqual(abortStatus, null); + done(); + }, 200); + }); + + it('Should send back default 4008 status code if MIDDLEWARE_HANDSHAKE_SC blocks without providing a status code', function (done) { + var middlewareWasExecuted = false; + var abortStatus; + var abortReason; + + middlewareFunction = function (req, next) { + setTimeout(function () { + middlewareWasExecuted = true; + var err = new Error('SC handshake failed because the server was too lazy'); + err.name = 'TooLazyHandshakeError'; + next(err); + }, 100); + }; + server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_SC, middlewareFunction); + + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + + client.once('connectAbort', function (status, reason) { + abortStatus = status; + abortReason = reason; + }); + client.on('error', function (err) {}); + + setTimeout(function () { + assert.equal(middlewareWasExecuted, true); + assert.equal(abortStatus, 4008); + assert.equal(abortReason, 'TooLazyHandshakeError: SC handshake failed because the server was too lazy'); done(); }, 200); }); + + it('Should send back custom status code if MIDDLEWARE_HANDSHAKE_SC blocks by providing a status code', function (done) { + var middlewareWasExecuted = false; + var abortStatus; + var abortReason; + + middlewareFunction = function (req, next) { + setTimeout(function () { + middlewareWasExecuted = true; + var err = new Error('SC handshake failed because of invalid query auth parameters'); + err.name = 'InvalidAuthQueryHandshakeError'; + + // Pass custom 4501 status code as the second argument to the next() function. + // We will treat this code as a fatal authentication failure on the front end. + // A status code of 4500 or higher means that the client shouldn't try to reconnect. + next(err, 4501); + }, 100); + }; + server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_SC, middlewareFunction); + + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + + client.once('connectAbort', function (status, reason) { + abortStatus = status; + abortReason = reason; + }); + client.on('error', function (err) {}); + + setTimeout(function () { + assert.equal(middlewareWasExecuted, true); + assert.equal(abortStatus, 4501); + assert.equal(abortReason, 'InvalidAuthQueryHandshakeError: SC handshake failed because of invalid query auth parameters'); + done(); + }, 200); + }); + + it('Should connect with a delay if next() is called after a timeout inside the middleware function', function (done) { + var createConnectionTime = null; + var connectEventTime = null; + var abortStatus; + var abortReason; + + middlewareFunction = function (req, next) { + setTimeout(function () { + next(); + }, 500); + }; + server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_SC, middlewareFunction); + + createConnectionTime = Date.now(); + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + + client.once('connectAbort', function (status, reason) { + abortStatus = status; + abortReason = reason; + }); + client.once('connect', function () { + connectEventTime = Date.now(); + assert.equal(connectEventTime - createConnectionTime > 400, true); + done(); + }); + client.on('error', function (err) {}); + }); }); }); }); From eea0263340abc2d7b722d2d0cfe2c2bc8ceaed57 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 19 Feb 2018 01:08:43 +0100 Subject: [PATCH 004/265] Use new dependency style. Bump sc-errors, sc-auth and socketcluster-client dependencies. --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3dadc97..70fa6e7 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,10 @@ "base64id": "0.1.0", "component-emitter": "1.2.1", "lodash.clonedeep": "4.5.0", - "sc-auth": "~4.1.2", - "sc-errors": "~1.3.3", - "sc-formatter": "~3.0.2", - "sc-simple-broker": "~2.1.0", + "sc-auth": "^4.1.3", + "sc-errors": "^1.4.0", + "sc-formatter": "^3.0.2", + "sc-simple-broker": "^2.1.0", "uuid": "3.1.0", "uws": "9.14.0", "ws": "3.3.3" @@ -23,7 +23,7 @@ "devDependencies": { "localStorage": "^1.0.3", "mocha": "3.0.2", - "socketcluster-client": "^8.0.0" + "socketcluster-client": "^10.1.0" }, "scripts": { "test": "mocha --reporter spec --timeout 3000 --slow 3000" From b1fc5d91a58cd786d8b7e65f8f30eadc62573a42 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 19 Feb 2018 01:09:13 +0100 Subject: [PATCH 005/265] v10.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 70fa6e7..deba3d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "9.1.3", + "version": "10.0.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From fe3814175fb25076711b0210052df6f8280dbeae Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 19 Feb 2018 02:28:58 +0100 Subject: [PATCH 006/265] Don't log unknown reasons error; show the error code instead --- scsocket.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scsocket.js b/scsocket.js index ad5d773..1ba6ca8 100644 --- a/scsocket.js +++ b/scsocket.js @@ -220,7 +220,7 @@ SCSocket.prototype._onSCClose = function (code, data) { Emitter.prototype.emit.call(this, 'close', code, data); if (!SCSocket.ignoreStatuses[code]) { - var failureMessage; + var closeMessage; if (data) { var reasonString; if (typeof data == 'object') { @@ -232,11 +232,11 @@ SCSocket.prototype._onSCClose = function (code, data) { } else { reasonString = data; } - failureMessage = 'Socket connection failed: ' + reasonString; + closeMessage = 'Socket connection closed with status code ' + code + ' and reason: ' + reasonString; } else { - failureMessage = 'Socket connection failed for unknown reasons'; + closeMessage = 'Socket connection closed with status code ' + code; } - var err = new SocketProtocolError(SCSocket.errorStatuses[code] || failureMessage, code); + var err = new SocketProtocolError(SCSocket.errorStatuses[code] || closeMessage, code); Emitter.prototype.emit.call(this, 'error', err); } } From 3fc4a069fb5151276888c4b3d147681737711b52 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 19 Feb 2018 02:47:46 +0100 Subject: [PATCH 007/265] Removed unused code. Disconnect socket as soon as SC middleware blocks with error --- scserver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scserver.js b/scserver.js index c4ec897..baefd4f 100644 --- a/scserver.js +++ b/scserver.js @@ -563,11 +563,11 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { socket: scSocket }, function (err, statusCode) { if (err) { - var handshakeError = scErrors.dehydrateError(err); var clientSocketErrorStatus = { code: statusCode }; respond(err, clientSocketErrorStatus); + scSocket.disconnect(statusCode); return; } self._processAuthToken(scSocket, signedAuthToken, function (err, isBadToken) { From f9ab757b87298b9393588ec4725703f4d87c8878 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 19 Feb 2018 02:48:04 +0100 Subject: [PATCH 008/265] v10.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index deba3d8..9dd3145 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "10.0.0", + "version": "10.0.1", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 735e4bc6752ca4dab610f33d8a52d65dba88b73b Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 6 Mar 2018 22:37:01 +0100 Subject: [PATCH 009/265] Bump sc-auth --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9dd3145..ea3847d 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "base64id": "0.1.0", "component-emitter": "1.2.1", "lodash.clonedeep": "4.5.0", - "sc-auth": "^4.1.3", + "sc-auth": "^5.0.0", "sc-errors": "^1.4.0", "sc-formatter": "^3.0.2", "sc-simple-broker": "^2.1.0", From c07d030cbc2d023db57012802e79ec185ad5ca97 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 6 Mar 2018 22:42:48 +0100 Subject: [PATCH 010/265] v10.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ea3847d..cc9077a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "10.0.1", + "version": "10.1.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From b524e1669661f4bd84dd1261a0286d09e1859d9b Mon Sep 17 00:00:00 2001 From: Mega Date: Thu, 8 Mar 2018 08:10:45 +0500 Subject: [PATCH 011/265] bump dependencies --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index cc9077a..736124b 100644 --- a/package.json +++ b/package.json @@ -8,15 +8,15 @@ "url": "git://github.com/SocketCluster/socketcluster-server.git" }, "dependencies": { - "async": "2.0.0", - "base64id": "0.1.0", + "async": "2.3.0", + "base64id": "1.0.0", "component-emitter": "1.2.1", "lodash.clonedeep": "4.5.0", "sc-auth": "^5.0.0", "sc-errors": "^1.4.0", "sc-formatter": "^3.0.2", "sc-simple-broker": "^2.1.0", - "uuid": "3.1.0", + "uuid": "3.2.1", "uws": "9.14.0", "ws": "3.3.3" }, From 67d80aa1b0eaf0fc6796ceb7eef292a99847d14e Mon Sep 17 00:00:00 2001 From: Mega Date: Thu, 8 Mar 2018 08:13:11 +0500 Subject: [PATCH 012/265] add package-lock --- package-lock.json | 540 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 540 insertions(+) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..69f74a6 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,540 @@ +{ + "name": "socketcluster-server", + "version": "10.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.3.0.tgz", + "integrity": "sha1-EBPRBRBH3TIP4k5JTVxm7K9hR9k=", + "requires": { + "lodash": "4.17.5" + } + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=", + "dev": true + }, + "base64id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" + }, + "base64url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", + "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "clone": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "dev": true + }, + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": "1.0.1" + } + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + }, + "dependencies": { + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + } + } + }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true + }, + "ecdsa-sig-formatter": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz", + "integrity": "sha1-S8kmJ07Dtau1AW5+HWCSGsJisqE=", + "requires": { + "base64url": "2.0.0", + "safe-buffer": "5.1.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.5.tgz", + "integrity": "sha1-tCAqaQmbu00pKnwblbZoK2fr3JU=", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "jsonwebtoken": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.2.0.tgz", + "integrity": "sha512-1Wxh8ADP3cNyPl8tZ95WtraHXCAyXupgc0AhMHjU9er98BV+UcKsO7OJUjfhIu0Uba9A40n1oSx8dbJYrm+EoQ==", + "requires": { + "jws": "3.1.4", + "lodash.includes": "4.3.0", + "lodash.isboolean": "3.0.3", + "lodash.isinteger": "4.0.4", + "lodash.isnumber": "3.0.3", + "lodash.isplainobject": "4.0.6", + "lodash.isstring": "4.0.1", + "lodash.once": "4.1.1", + "ms": "2.1.1", + "xtend": "4.0.1" + } + }, + "jwa": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.5.tgz", + "integrity": "sha1-oFUs4CIHQs1S4VN3SjKQXDDnVuU=", + "requires": { + "base64url": "2.0.0", + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.9", + "safe-buffer": "5.1.1" + } + }, + "jws": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.4.tgz", + "integrity": "sha1-+ei5M46KhHJ31kRLFGT2GIDgUKI=", + "requires": { + "base64url": "2.0.0", + "jwa": "1.1.5", + "safe-buffer": "5.1.1" + } + }, + "linked-list": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz", + "integrity": "sha1-eYsP+X0bkqT9CEgPVa6k6dSdN78=", + "dev": true + }, + "localStorage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/localStorage/-/localStorage-1.0.3.tgz", + "integrity": "sha1-5riaV7t2ChVqOMyH4PJVD27UE9g=", + "dev": true + }, + "lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash.keys": "3.1.2" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basecreate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, + "lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true, + "requires": { + "lodash._baseassign": "3.2.0", + "lodash._basecreate": "3.0.3", + "lodash._isiterateecall": "3.0.9" + } + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.0.2.tgz", + "integrity": "sha1-Y6l/Phj00+ZZ1HphdnfQiYdFV/A=", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.2.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.5", + "glob": "7.0.5", + "growl": "1.9.2", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "sc-auth": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/sc-auth/-/sc-auth-5.0.0.tgz", + "integrity": "sha1-ySdiDQDMuJ5oAs9l+c1+MB/Psus=", + "requires": { + "jsonwebtoken": "8.2.0", + "sc-errors": "1.4.0" + } + }, + "sc-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.1.0.tgz", + "integrity": "sha512-zuIiFWr45oKcjqmyzUuodMPSiS9lYsgiuuvC0EOkqLZffw0aZMGj5FsD4OSbzV/BeTJYPwzEq0P/IJd/2pqcnw==", + "requires": { + "component-emitter": "1.2.1" + } + }, + "sc-errors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.0.tgz", + "integrity": "sha512-h+jRWx/xRJmkPFDd0IltoTl/QJ6hAr5Y+3ZVeBQRLuWZKe+dHdf2uVwFp2OYqlLQ7GHht4y9eXG2zOf2Ik6PTw==" + }, + "sc-formatter": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sc-formatter/-/sc-formatter-3.0.2.tgz", + "integrity": "sha512-9PbqYBpCq+OoEeRQ3QfFIGE6qwjjBcd2j7UjgDlhnZbtSnuGgHdcRklPKYGuYFH82V/dwd+AIpu8XvA1zqTd+A==" + }, + "sc-simple-broker": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-2.1.0.tgz", + "integrity": "sha512-/w1B+94bDnbHvlWPBr5CoxQ19dzTJR6glbs89RPc1h46sjdYuiZUdjmc7lwsRc/pDfDA7ZU8LzxVUQuxIY4xpw==", + "requires": { + "sc-channel": "1.1.0" + } + }, + "socketcluster-client": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-10.1.1.tgz", + "integrity": "sha512-jq2x+ZhC4yqy/BMvBZHLqgOjcxvrxOJABmMJ1sRAGzvxOCXsr8jk3XCHigLJdJCX4RzPpMkbvD+mPvuJP8O5dQ==", + "dev": true, + "requires": { + "base-64": "0.1.0", + "clone": "2.1.1", + "component-emitter": "1.2.1", + "linked-list": "0.1.0", + "querystring": "0.2.0", + "sc-channel": "1.2.0", + "sc-errors": "1.4.0", + "sc-formatter": "3.0.2", + "uuid": "3.1.0", + "ws": "3.3.3" + }, + "dependencies": { + "sc-channel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", + "integrity": "sha512-M3gdq8PlKg0zWJSisWqAsMmTVxYRTpVRqw4CWAdKBgAfVKumFcTjoCV0hYu7lgUXccCtCD8Wk9VkkE+IXCxmZA==", + "dev": true, + "requires": { + "component-emitter": "1.2.1" + } + }, + "uuid": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", + "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", + "dev": true + } + } + }, + "supports-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + }, + "uws": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/uws/-/uws-9.14.0.tgz", + "integrity": "sha512-HNMztPP5A1sKuVFmdZ6BPVpBQd5bUjNC8EFMFiICK+oho/OQsAJy5hnIx4btMHiOk8j04f/DbIlqnEZ9d72dqg==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "requires": { + "async-limiter": "1.0.0", + "safe-buffer": "5.1.1", + "ultron": "1.1.1" + } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + } + } +} From aee79f50f9b2f1e46e61b23cb0884972aa79d139 Mon Sep 17 00:00:00 2001 From: Mega Date: Sat, 10 Mar 2018 23:58:30 +0500 Subject: [PATCH 013/265] check if middleware does exist --- scserver.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scserver.js b/scserver.js index baefd4f..67da9f8 100644 --- a/scserver.js +++ b/scserver.js @@ -639,6 +639,9 @@ SCServer.prototype.generateId = function () { }; SCServer.prototype.addMiddleware = function (type, middleware) { + if (!this._middleware[type]) { + throw new InvalidArgumentsError(`Middleware type "${type}" is not supported. \nRead more: https://socketcluster.io/#!/docs/middleware-and-authorization`) + } this._middleware[type].push(middleware); }; From 2144a9213ae7828f14ac6308972685ff928baefd Mon Sep 17 00:00:00 2001 From: Mega Date: Sun, 11 Mar 2018 00:15:27 +0500 Subject: [PATCH 014/265] forgot to include the error itself :D --- scserver.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scserver.js b/scserver.js index 67da9f8..711863a 100644 --- a/scserver.js +++ b/scserver.js @@ -15,6 +15,7 @@ var AuthTokenInvalidError = scErrors.AuthTokenInvalidError; var AuthTokenNotBeforeError = scErrors.AuthTokenNotBeforeError; var AuthTokenError = scErrors.AuthTokenError; var SilentMiddlewareBlockedError = scErrors.SilentMiddlewareBlockedError; +var InvalidArgumentsError = scErrors.InvalidArgumentsError; var InvalidOptionsError = scErrors.InvalidOptionsError; var InvalidActionError = scErrors.InvalidActionError; var BrokerError = scErrors.BrokerError; From 54a51d22b4fe0e8cdacc52baf48798d993336020 Mon Sep 17 00:00:00 2001 From: Mega Date: Sun, 11 Mar 2018 01:28:35 +0500 Subject: [PATCH 015/265] requested changes --- scserver.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scserver.js b/scserver.js index 711863a..f1c04b2 100644 --- a/scserver.js +++ b/scserver.js @@ -641,7 +641,8 @@ SCServer.prototype.generateId = function () { SCServer.prototype.addMiddleware = function (type, middleware) { if (!this._middleware[type]) { - throw new InvalidArgumentsError(`Middleware type "${type}" is not supported. \nRead more: https://socketcluster.io/#!/docs/middleware-and-authorization`) + throw new InvalidArgumentsError(`Middleware type "${type}" is not supported`); + // Read more: https://socketcluster.io/#!/docs/middleware-and-authorization } this._middleware[type].push(middleware); }; From 36fb543ae6894047c9bbd829d84881858973211f Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 12 Mar 2018 00:32:01 +0100 Subject: [PATCH 016/265] Bump sc-simple-broker --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 736124b..591df17 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "sc-auth": "^5.0.0", "sc-errors": "^1.4.0", "sc-formatter": "^3.0.2", - "sc-simple-broker": "^2.1.0", + "sc-simple-broker": "^2.1.1", "uuid": "3.2.1", "uws": "9.14.0", "ws": "3.3.3" From 3c252e0cb1846a7c92ce73b1024c413fbb3c1aa0 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 12 Mar 2018 00:43:12 +0100 Subject: [PATCH 017/265] v10.1.1 --- package-lock.json | 41 ++++++++++++----------------------------- package.json | 4 ++-- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 69f74a6..76514c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "10.1.0", + "version": "10.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -431,9 +431,9 @@ } }, "sc-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.1.0.tgz", - "integrity": "sha512-zuIiFWr45oKcjqmyzUuodMPSiS9lYsgiuuvC0EOkqLZffw0aZMGj5FsD4OSbzV/BeTJYPwzEq0P/IJd/2pqcnw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", + "integrity": "sha512-M3gdq8PlKg0zWJSisWqAsMmTVxYRTpVRqw4CWAdKBgAfVKumFcTjoCV0hYu7lgUXccCtCD8Wk9VkkE+IXCxmZA==", "requires": { "component-emitter": "1.2.1" } @@ -449,17 +449,17 @@ "integrity": "sha512-9PbqYBpCq+OoEeRQ3QfFIGE6qwjjBcd2j7UjgDlhnZbtSnuGgHdcRklPKYGuYFH82V/dwd+AIpu8XvA1zqTd+A==" }, "sc-simple-broker": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-2.1.0.tgz", - "integrity": "sha512-/w1B+94bDnbHvlWPBr5CoxQ19dzTJR6glbs89RPc1h46sjdYuiZUdjmc7lwsRc/pDfDA7ZU8LzxVUQuxIY4xpw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-2.1.1.tgz", + "integrity": "sha512-m6JM2dxP6G9mjEVC2VEDWt7SG/uBLerJs1U193WOd7pZKmbZhcVlhuw0IdJcs7lDwGm0jlX6DA1LbEn0yUCQLA==", "requires": { - "sc-channel": "1.1.0" + "sc-channel": "1.2.0" } }, "socketcluster-client": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-10.1.1.tgz", - "integrity": "sha512-jq2x+ZhC4yqy/BMvBZHLqgOjcxvrxOJABmMJ1sRAGzvxOCXsr8jk3XCHigLJdJCX4RzPpMkbvD+mPvuJP8O5dQ==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-10.1.2.tgz", + "integrity": "sha512-6kv0DdT9kX9cgqxkZJ0t4WC4Gk2e21Fs1/sojrCY180Cl7hp76x8K7CrFynCFCItyxdhHABtCKSxpOBoCeU1gQ==", "dev": true, "requires": { "base-64": "0.1.0", @@ -470,25 +470,8 @@ "sc-channel": "1.2.0", "sc-errors": "1.4.0", "sc-formatter": "3.0.2", - "uuid": "3.1.0", + "uuid": "3.2.1", "ws": "3.3.3" - }, - "dependencies": { - "sc-channel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", - "integrity": "sha512-M3gdq8PlKg0zWJSisWqAsMmTVxYRTpVRqw4CWAdKBgAfVKumFcTjoCV0hYu7lgUXccCtCD8Wk9VkkE+IXCxmZA==", - "dev": true, - "requires": { - "component-emitter": "1.2.1" - } - }, - "uuid": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", - "dev": true - } } }, "supports-color": { diff --git a/package.json b/package.json index 591df17..24bdb9b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "10.1.0", + "version": "10.1.1", "description": "Server module for SocketCluster", "main": "index.js", "repository": { @@ -23,7 +23,7 @@ "devDependencies": { "localStorage": "^1.0.3", "mocha": "3.0.2", - "socketcluster-client": "^10.1.0" + "socketcluster-client": "^10.1.2" }, "scripts": { "test": "mocha --reporter spec --timeout 3000 --slow 3000" From a0ddae5290c8ee5653b2a855db741e10f63bbcf8 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 24 Mar 2018 19:42:21 +0100 Subject: [PATCH 018/265] Add test to verify order of unsubscribe/disonnect events --- test/integration.js | 46 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/test/integration.js b/test/integration.js index afc47d3..719a3fe 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1048,7 +1048,52 @@ describe('Integration tests', function () { }, 300); }); }); + + it('Unsubscribe event should trigger before private _disconnect event', function (done) { + portNumber++; + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey + }); + + var eventList = []; + + server.on('connection', function (socket) { + socket.on('unsubscribe', function (channel) { + eventList.push({ + type: 'unsubscribe', + channel: channel + }); + }); + socket.on('_disconnect', function (code, reason) { + eventList.push({ + type: '_disconnect', + code: code, + reason: reason + }); + assert.equal(eventList[0].type, 'unsubscribe'); + assert.equal(eventList[0].channel, 'foo'); + assert.equal(eventList[1].type, '_disconnect'); + + done(); + }); + }); + + server.on('ready', function () { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + + client.subscribe('foo').on('subscribe', function () { + setTimeout(function () { + client.disconnect(); + }, 50); + }); + }); + }); }); + describe('Middleware', function () { var middlewareFunction; var middlewareWasExecuted = false; @@ -1070,7 +1115,6 @@ describe('Integration tests', function () { }); }); - describe('MIDDLEWARE_AUTHENTICATE', function () { it('Should not run authenticate middleware if JWT token does not exist', function (done) { middlewareFunction = function (req, next) { From f51e5252815df932f6a0f48388ee4ae975ae9ff8 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 24 Mar 2018 23:21:57 +0100 Subject: [PATCH 019/265] Better isolation of test cases from each other --- test/integration.js | 127 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 110 insertions(+), 17 deletions(-) diff --git a/test/integration.js b/test/integration.js index 719a3fe..4ce9223 100644 --- a/test/integration.js +++ b/test/integration.js @@ -2,6 +2,7 @@ var assert = require('assert'); var socketClusterServer = require('../'); var socketCluster = require('socketcluster-client'); var localStorage = require('localStorage'); +var SCSimpleBroker = require('sc-simple-broker').SCSimpleBroker; // Add to the global scope like in browser. global.localStorage = localStorage; @@ -92,16 +93,20 @@ var connectionHandler = function (socket) { }; var destroyTestCase = function (next) { - if (client && client.state != client.CLOSED) { - client.once('disconnect', function () { - client.removeAllListeners('connectAbort'); - next(); - }); - client.once('connectAbort', function () { - client.removeAllListeners('disconnect'); + if (client) { + client.on('error', function (err) {}); + + if (client.state != client.CLOSED) { + client.once('close', function () { + client.removeAllListeners('close'); + client.removeAllListeners('connectAbort'); + client.removeAllListeners('disconnect'); + next(); + }); + client.disconnect(); + } else { next(); - }); - client.disconnect(); + } } else { next(); } @@ -111,6 +116,7 @@ describe('Integration tests', function () { beforeEach('Run the server before start', function (done) { clientOptions = { hostname: '127.0.0.1', + multiplex: false, port: portNumber }; serverOptions = { @@ -1049,7 +1055,7 @@ describe('Integration tests', function () { }); }); - it('Unsubscribe event should trigger before private _disconnect event', function (done) { + it('When default SCSimpleBroker broker engine is used, unsubscribe event should trigger before disconnect event', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey @@ -1064,15 +1070,71 @@ describe('Integration tests', function () { channel: channel }); }); - socket.on('_disconnect', function (code, reason) { + socket.on('disconnect', function (code, reason) { + eventList.push({ + type: 'disconnect', + code: code, + reason: reason + }); + assert.equal(eventList[0].type, 'unsubscribe'); + assert.equal(eventList[0].channel, 'foo'); + assert.equal(eventList[1].type, 'disconnect'); + + done(); + }); + }); + + server.on('ready', function () { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + + client.subscribe('foo').on('subscribe', function () { + setTimeout(function () { + client.disconnect(); + }, 200); + }); + }); + }); + + it('When custom broker engine is used (with async unsubscribe), unsubscribe event should trigger before disconnect event', function (done) { + portNumber++; + var customBrokerEngine = new SCSimpleBroker(); + var defaultUnsubscribeSocket = customBrokerEngine.unsubscribeSocket; + customBrokerEngine.unsubscribeSocket = function (socket, channel, callback) { + defaultUnsubscribeSocket.call(this, socket, channel, function () { + setTimeout(function () { + callback && callback(); + }, 100); + }); + }; + + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + brokerEngine: customBrokerEngine + }); + + var eventList = []; + + server.on('connection', function (socket) { + socket.on('unsubscribe', function (channel) { + eventList.push({ + type: 'unsubscribe', + channel: channel + }); + }); + socket.on('disconnect', function (code, reason) { eventList.push({ - type: '_disconnect', + type: 'disconnect', code: code, reason: reason }); + assert.equal(eventList[0].type, 'unsubscribe'); assert.equal(eventList[0].channel, 'foo'); - assert.equal(eventList[1].type, '_disconnect'); + assert.equal(eventList[1].type, 'disconnect'); done(); }); @@ -1088,9 +1150,41 @@ describe('Integration tests', function () { client.subscribe('foo').on('subscribe', function () { setTimeout(function () { client.disconnect(); - }, 50); + }, 200); + }); + }); + }); + + it('Socket should emit an error when trying to unsubscribe to a channel which it is not subscribed to', function (done) { + portNumber++; + + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey + }); + + var errorList = []; + + server.on('connection', function (socket) { + socket.on('error', function (err) { + errorList.push(err); }); }); + + server.on('ready', function () { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + + client.emit('#unsubscribe', 'bar'); + + setTimeout(function () { + assert.equal(errorList.length, 1); + assert.equal(errorList[0].name, 'BrokerError'); + done(); + }, 100); + }); }); }); @@ -1226,12 +1320,12 @@ describe('Integration tests', function () { port: portNumber, multiplex: false }); + client.on('error', function () {}); client.once('connectAbort', function (status, reason) { abortStatus = status; abortReason = reason; }); - client.on('error', function (err) {}); setTimeout(function () { assert.equal(middlewareWasExecuted, true); @@ -1265,12 +1359,12 @@ describe('Integration tests', function () { port: portNumber, multiplex: false }); + client.on('error', function () {}); client.once('connectAbort', function (status, reason) { abortStatus = status; abortReason = reason; }); - client.on('error', function (err) {}); setTimeout(function () { assert.equal(middlewareWasExecuted, true); @@ -1309,7 +1403,6 @@ describe('Integration tests', function () { assert.equal(connectEventTime - createConnectionTime > 400, true); done(); }); - client.on('error', function (err) {}); }); }); }); From 3876c3ebcf8b9b1be81b53f42a58d4f97296fc64 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 25 Mar 2018 00:51:59 +0100 Subject: [PATCH 020/265] When disconnecting a socket, unsubscribe event should always trigger before disconnect event on the server socket --- scserver.js | 120 +++++++++++++++----------------------------- test/integration.js | 58 ++++++++++++++++++++- 2 files changed, 97 insertions(+), 81 deletions(-) diff --git a/scserver.js b/scserver.js index f1c04b2..dff5233 100644 --- a/scserver.js +++ b/scserver.js @@ -215,28 +215,6 @@ SCServer.prototype._handleHandshakeTimeout = function (scSocket) { SCServer.prototype._subscribeSocket = function (socket, channelOptions, callback) { var self = this; - if (Array.isArray(channelOptions)) { - var tasks = []; - for (var i in channelOptions) { - if (channelOptions.hasOwnProperty(i)) { - (function (singleChannelOptions) { - tasks.push(function (cb) { - self._subscribeSocketToSingleChannel(socket, singleChannelOptions, cb); - }); - })(channelOptions[i]); - } - } - async.waterfall(tasks, function (err) { - callback && callback(err); - }); - } else { - this._subscribeSocketToSingleChannel(socket, channelOptions, callback); - } -}; - -SCServer.prototype._subscribeSocketToSingleChannel = function (socket, channelOptions, callback) { - var self = this; - if (!channelOptions) { callback && callback('Socket ' + socket.id + ' provided a malformated channel payload'); return; @@ -275,41 +253,25 @@ SCServer.prototype._subscribeSocketToSingleChannel = function (socket, channelOp }); }; -SCServer.prototype._unsubscribeSocket = function (socket, channels, callback) { - var self = this; - - if (channels == null) { - channels = []; - for (var channel in socket.channelSubscriptions) { - if (socket.channelSubscriptions.hasOwnProperty(channel)) { - channels.push(channel); - } +SCServer.prototype._unsubscribeSocketFromAllChannels = function (socket) { + var channels = []; + for (var channel in socket.channelSubscriptions) { + if (socket.channelSubscriptions.hasOwnProperty(channel)) { + channels.push(channel); } } - if (Array.isArray(channels)) { - var tasks = []; - var len = channels.length; - for (var i = 0; i < len; i++) { - (function (channel) { - tasks.push(function (cb) { - self._unsubscribeSocketFromSingleChannel(socket, channel, cb); - }); - })(channels[i]); - } - async.waterfall(tasks, function (err) { - callback && callback(err); - }); - } else { - this._unsubscribeSocketFromSingleChannel(socket, channels, callback); + var len = channels.length; + for (var i = 0; i < len; i++) { + this._unsubscribeSocket(socket, channels[i]); } }; -SCServer.prototype._unsubscribeSocketFromSingleChannel = function (socket, channel, callback) { - var self = this; - +SCServer.prototype._unsubscribeSocket = function (socket, channel) { if (typeof channel != 'string') { - callback && callback('Socket ' + socket.id + ' provided an invalid channel name'); - return; + throw new InvalidActionError('Socket ' + socket.id + ' tried to unsubscribe from an invalid channel name'); + } + if (!socket.channelSubscriptions[channel]) { + throw new InvalidActionError('Socket ' + socket.id + ' tried to unsubscribe from a channel which it is not subscribed to'); } delete socket.channelSubscriptions[channel]; @@ -317,11 +279,10 @@ SCServer.prototype._unsubscribeSocketFromSingleChannel = function (socket, chann socket.channelSubscriptionsCount--; } - this.brokerEngine.unsubscribeSocket(socket, channel, function (err) { - socket.emit('unsubscribe', channel); - self.emit('unsubscription', socket, channel); - callback && callback(err); - }); + this.brokerEngine.unsubscribeSocket(socket, channel); + + socket.emit('unsubscribe', channel); + this.emit('unsubscription', socket, channel); }; SCServer.prototype._processTokenError = function (err) { @@ -495,15 +456,18 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { }); scSocket.on('#unsubscribe', function (channel, res) { - self._unsubscribeSocket(scSocket, channel, function (err) { - if (err) { - var error = new BrokerError('Failed to unsubscribe socket from the ' + channel + ' channel - ' + err); - res(error); - scSocket.emit('error', error); - } else { - res(); - } - }); + var error; + try { + self._unsubscribeSocket(scSocket, channel); + } catch (err) { + error = new BrokerError('Failed to unsubscribe socket from the ' + channel + ' channel - ' + err.message); + } + if (error) { + res(error); + scSocket.emit('error', error); + } else { + res(); + } }); var cleanupSocket = function (type, code, data) { @@ -532,21 +496,17 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { self.pendingClientsCount--; } - self._unsubscribeSocket(scSocket, null, function (err) { - if (err) { - scSocket.emit('error', new BrokerError('Failed to unsubscribe socket from all channels - ' + err)); - } else { - if (type == 'disconnect') { - self.emit('_disconnection', scSocket, code, data); - self.emit('disconnection', scSocket, code, data); - } else if (type == 'abort') { - self.emit('_connectionAbort', scSocket, code, data); - self.emit('connectionAbort', scSocket, code, data); - } - self.emit('_closure', scSocket, code, data); - self.emit('closure', scSocket, code, data); - } - }); + self._unsubscribeSocketFromAllChannels(scSocket); + + if (type == 'disconnect') { + self.emit('_disconnection', scSocket, code, data); + self.emit('disconnection', scSocket, code, data); + } else if (type == 'abort') { + self.emit('_connectionAbort', scSocket, code, data); + self.emit('connectionAbort', scSocket, code, data); + } + self.emit('_closure', scSocket, code, data); + self.emit('closure', scSocket, code, data); }; scSocket.once('_disconnect', cleanupSocket.bind(scSocket, 'disconnect')); diff --git a/test/integration.js b/test/integration.js index 4ce9223..3714115 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1099,7 +1099,7 @@ describe('Integration tests', function () { }); }); - it('When custom broker engine is used (with async unsubscribe), unsubscribe event should trigger before disconnect event', function (done) { + it('When disconnecting a socket, the unsubscribe event should trigger before disconnect event (accounting for delayed unsubscribe by brokerEngine)', function (done) { portNumber++; var customBrokerEngine = new SCSimpleBroker(); var defaultUnsubscribeSocket = customBrokerEngine.unsubscribeSocket; @@ -1146,6 +1146,7 @@ describe('Integration tests', function () { port: portNumber, multiplex: false }); + client.on('error', function () {}); client.subscribe('foo').on('subscribe', function () { setTimeout(function () { @@ -1186,6 +1187,61 @@ describe('Integration tests', function () { }, 100); }); }); + + it('Socket should not receive messages from a channel which it has only just unsubscribed from (accounting for delayed unsubscribe by brokerEngine)', function (done) { + portNumber++; + var customBrokerEngine = new SCSimpleBroker(); + var defaultUnsubscribeSocket = customBrokerEngine.unsubscribeSocket; + customBrokerEngine.unsubscribeSocket = function (socket, channel, callback) { + defaultUnsubscribeSocket.call(this, socket, channel, function () { + setTimeout(function () { + callback && callback(); + }, 300); + }); + }; + + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + brokerEngine: customBrokerEngine + }); + + server.on('connection', function (socket) { + socket.on('unsubscribe', function (channelName) { + if (channelName === 'foo') { + server.exchange.publish('foo', 'hello'); + } + }); + }); + + server.on('ready', function () { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + // Stub the isSubscribed method so that it always returns true. + // That way the client will always invoke watchers whenever + // it receives a #publish event. + client.isSubscribed = function () { return true; }; + + var messageList = []; + + var fooChannel = client.subscribe('foo'); + + client.watch('foo', function (data) { + messageList.push(data); + }); + + fooChannel.on('subscribe', function () { + client.emit('#unsubscribe', 'foo'); + }); + + setTimeout(function () { + assert.equal(messageList.length, 0); + done(); + }, 200); + }); + }); }); describe('Middleware', function () { From 84de23d51615a0ca81d1d2644693dbe6d953b6d6 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 25 Mar 2018 00:55:01 +0100 Subject: [PATCH 021/265] v11.0.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 76514c0..89ae1e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "10.1.1", + "version": "11.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 24bdb9b..d53d6c7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "10.1.1", + "version": "11.0.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From a324df43c8de6965faf32acf47271364781a0dac Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 25 Mar 2018 16:24:53 +0200 Subject: [PATCH 022/265] Bump socketcluster-client for tests --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 89ae1e4..95cf845 100644 --- a/package-lock.json +++ b/package-lock.json @@ -457,9 +457,9 @@ } }, "socketcluster-client": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-10.1.2.tgz", - "integrity": "sha512-6kv0DdT9kX9cgqxkZJ0t4WC4Gk2e21Fs1/sojrCY180Cl7hp76x8K7CrFynCFCItyxdhHABtCKSxpOBoCeU1gQ==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-11.0.0.tgz", + "integrity": "sha512-7uNS1ih8hPtB7EuV3sUg4/lFWc6vxw52aUwifTIZ44ZoA8bykroLRC8lnTifuFGeNTHYqCrU0Ev6Zi5i05fSVg==", "dev": true, "requires": { "base-64": "0.1.0", diff --git a/package.json b/package.json index d53d6c7..2fa359b 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "devDependencies": { "localStorage": "^1.0.3", "mocha": "3.0.2", - "socketcluster-client": "^10.1.2" + "socketcluster-client": "^11.0.0" }, "scripts": { "test": "mocha --reporter spec --timeout 3000 --slow 3000" From 0c27f4f641bc374fb8ca981211147963925f45e0 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 14 Apr 2018 11:00:10 +0200 Subject: [PATCH 023/265] Use sc-uws as default WebSocket engine --- package-lock.json | 10 +++++----- package.json | 2 +- scserver.js | 4 ++-- scsocket.js | 3 ++- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 95cf845..2de1860 100644 --- a/package-lock.json +++ b/package-lock.json @@ -456,6 +456,11 @@ "sc-channel": "1.2.0" } }, + "sc-uws": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/sc-uws/-/sc-uws-10.0.0.tgz", + "integrity": "sha512-6SBfU/kXS+XyY7hUo6aosEeZdHwhWJ5lr1A4gbSWH1URbBvRLMSs4oXtbVgzNtcBT53h48j4HS0RMY8FuujL5A==" + }, "socketcluster-client": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-11.0.0.tgz", @@ -493,11 +498,6 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" }, - "uws": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/uws/-/uws-9.14.0.tgz", - "integrity": "sha512-HNMztPP5A1sKuVFmdZ6BPVpBQd5bUjNC8EFMFiICK+oho/OQsAJy5hnIx4btMHiOk8j04f/DbIlqnEZ9d72dqg==" - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 2fa359b..e03b1e7 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "sc-formatter": "^3.0.2", "sc-simple-broker": "^2.1.1", "uuid": "3.2.1", - "uws": "9.14.0", + "sc-uws": "^10.0.1", "ws": "3.3.3" }, "devDependencies": { diff --git a/scserver.js b/scserver.js index dff5233..4940491 100644 --- a/scserver.js +++ b/scserver.js @@ -29,7 +29,7 @@ var SCServer = function (options) { var opts = { brokerEngine: new SCSimpleBroker(), - wsEngine: 'uws', + wsEngine: 'sc-uws', wsEngineServerOptions: {}, maxPayload: null, allowClientPublish: true, @@ -373,7 +373,7 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { var self = this; if (this.options.wsEngine == 'ws') { - // Normalize ws module to match uws module. + // Normalize ws module to match sc-uws module. wsSocket.upgradeReq = upgradeReq; } diff --git a/scsocket.js b/scsocket.js index 1ba6ca8..aa23139 100644 --- a/scsocket.js +++ b/scsocket.js @@ -43,7 +43,8 @@ var SCSocket = function (id, server, socket) { this.request = this.socket.upgradeReq || {}; - if (this.server.options.wsEngine == 'uws') { + var wsEngine = this.server.options.wsEngine; + if (wsEngine == 'sc-uws' || wsEngine == 'uws') { this.request.connection = this.socket._socket; } if (this.request.connection) { From ccea271e53220fb4056bdd81f0e0db821da1c109 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 14 Apr 2018 11:14:17 +0200 Subject: [PATCH 024/265] Bump ws version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e03b1e7..ece4d68 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "sc-simple-broker": "^2.1.1", "uuid": "3.2.1", "sc-uws": "^10.0.1", - "ws": "3.3.3" + "ws": "5.1.1" }, "devDependencies": { "localStorage": "^1.0.3", From 5086aeb65f90d15ccd76b5d9fbbb410e3e4f05e1 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 14 Apr 2018 11:18:20 +0200 Subject: [PATCH 025/265] Make ws the default WebSocket engine --- scserver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scserver.js b/scserver.js index 4940491..fec69ef 100644 --- a/scserver.js +++ b/scserver.js @@ -29,7 +29,7 @@ var SCServer = function (options) { var opts = { brokerEngine: new SCSimpleBroker(), - wsEngine: 'sc-uws', + wsEngine: 'ws', wsEngineServerOptions: {}, maxPayload: null, allowClientPublish: true, From 013a7d01c4e825dcecaa100cb384875951f36f2e Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 14 Apr 2018 11:34:04 +0200 Subject: [PATCH 026/265] Allow specifying the wsEngine in tests --- test/integration.js | 52 +++++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/test/integration.js b/test/integration.js index 3714115..67f1f63 100644 --- a/test/integration.js +++ b/test/integration.js @@ -18,6 +18,7 @@ var allowedUsers = { }; var TEN_DAYS_IN_SECONDS = 60 * 60 * 24 * 10; +var WS_ENGINE = 'ws'; var validSignedAuthTokenBob = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImJvYiIsImV4cCI6MzE2Mzc1ODk3OTA4MDMxMCwiaWF0IjoxNTAyNzQ3NzQ2fQ.dSZOfsImq4AvCu-Or3Fcmo7JNv1hrV3WqxaiSKkTtAo'; var validSignedAuthTokenAlice = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNTE4NzI4MjU5LCJleHAiOjMxNjM3NTg5NzkwODAzMTB9.XxbzPPnnXrJfZrS0FJwb_EAhIu2VY5i7rGyUThtNLh4'; @@ -120,7 +121,8 @@ describe('Integration tests', function () { port: portNumber }; serverOptions = { - authKey: 'testkey' + authKey: 'testkey', + wsEngine: WS_ENGINE }; server = socketClusterServer.listen(portNumber, serverOptions); @@ -241,6 +243,7 @@ describe('Integration tests', function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, authSignAsync: false }); server.on('connection', connectionHandler); @@ -265,6 +268,7 @@ describe('Integration tests', function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, authSignAsync: true }); server.on('connection', connectionHandler); @@ -290,6 +294,7 @@ describe('Integration tests', function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, authVerifyAsync: false }); server.on('connection', connectionHandler); @@ -321,6 +326,7 @@ describe('Integration tests', function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, authVerifyAsync: false }); server.on('connection', connectionHandler); @@ -349,6 +355,7 @@ describe('Integration tests', function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, authVerifyAsync: false }); server.on('connection', connectionHandler); @@ -377,6 +384,7 @@ describe('Integration tests', function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, authVerifyAsync: false }); server.on('connection', connectionHandler); @@ -405,6 +413,7 @@ describe('Integration tests', function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, authVerifyAsync: false }); var warningMap = {}; @@ -443,7 +452,8 @@ describe('Integration tests', function () { it('Should support subscription batching', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE }); server.on('connection', function (socket) { connectionHandler(socket); @@ -515,7 +525,8 @@ describe('Integration tests', function () { it('should remove client data from the server when client disconnects before authentication process finished', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE }); server.setAuthEngine({ verifyToken: function (signedAuthToken, verificationKey, defaultVerificationOptions, callback) { @@ -555,7 +566,8 @@ describe('Integration tests', function () { it('Client should not be able to subscribe to a channel before the handshake has completed', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE }); server.setAuthEngine({ verifyToken: function (signedAuthToken, verificationKey, defaultVerificationOptions, callback) { @@ -605,7 +617,8 @@ describe('Integration tests', function () { it('Server-side socket disconnect event should not trigger if the socket did not complete the handshake; instead, it should trigger connectAbort', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE }); server.setAuthEngine({ verifyToken: function (signedAuthToken, verificationKey, defaultVerificationOptions, callback) { @@ -674,7 +687,8 @@ describe('Integration tests', function () { it('Server-side socket disconnect event should trigger if the socket completed the handshake (not connectAbort)', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE }); server.setAuthEngine({ verifyToken: function (signedAuthToken, verificationKey, defaultVerificationOptions, callback) { @@ -743,7 +757,8 @@ describe('Integration tests', function () { it('Server-side socket connect event and server connection event should trigger', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE }); var connectionEmitted = false; @@ -824,7 +839,8 @@ describe('Integration tests', function () { it('The close event should trigger when the socket loses the connection before the handshake', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE }); server.setAuthEngine({ verifyToken: function (signedAuthToken, verificationKey, defaultVerificationOptions, callback) { @@ -875,7 +891,8 @@ describe('Integration tests', function () { it('The close event should trigger when the socket loses the connection after the handshake', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE }); server.setAuthEngine({ verifyToken: function (signedAuthToken, verificationKey, defaultVerificationOptions, callback) { @@ -926,7 +943,8 @@ describe('Integration tests', function () { it('Exchange is attached to socket before the handshake event is triggered', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE }); server.on('connection', connectionHandler); @@ -951,7 +969,8 @@ describe('Integration tests', function () { it('Server should be able to handle invalid #subscribe and #unsubscribe and #publish packets without crashing', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE }); server.on('connection', connectionHandler); @@ -1058,7 +1077,8 @@ describe('Integration tests', function () { it('When default SCSimpleBroker broker engine is used, unsubscribe event should trigger before disconnect event', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE }); var eventList = []; @@ -1113,6 +1133,7 @@ describe('Integration tests', function () { server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, brokerEngine: customBrokerEngine }); @@ -1160,7 +1181,8 @@ describe('Integration tests', function () { portNumber++; server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE }); var errorList = []; @@ -1202,6 +1224,7 @@ describe('Integration tests', function () { server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, brokerEngine: customBrokerEngine }); @@ -1251,7 +1274,8 @@ describe('Integration tests', function () { beforeEach('Launch server without middleware before start', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE }); server.on('ready', function () { done(); From 135ee6e4023f6e0733379a788f25eb83d7e2575a Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 14 Apr 2018 11:35:35 +0200 Subject: [PATCH 027/265] v11.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ece4d68..adaf9ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "11.0.0", + "version": "11.1.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From e022817e971b7154fe184f27b8cc1e639c2b1677 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 25 Apr 2018 01:31:21 +0200 Subject: [PATCH 028/265] Add support for pingTimeoutDisabled option --- package-lock.json | 31 +++++------ package.json | 2 +- scserver.js | 2 + scsocket.js | 3 ++ test/integration.js | 128 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 146 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2de1860..4e43d48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "11.0.0", + "version": "11.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -457,14 +457,14 @@ } }, "sc-uws": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/sc-uws/-/sc-uws-10.0.0.tgz", - "integrity": "sha512-6SBfU/kXS+XyY7hUo6aosEeZdHwhWJ5lr1A4gbSWH1URbBvRLMSs4oXtbVgzNtcBT53h48j4HS0RMY8FuujL5A==" + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/sc-uws/-/sc-uws-10.0.1.tgz", + "integrity": "sha512-sLk//KBp/4Ao/7cVqwU3DbOYRZZfru9Izkc03Hlmvs7aydFoGM6+tMhWDrrhjwTUM9tXdjZldrS7mv5yzxsRmA==" }, "socketcluster-client": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-11.0.0.tgz", - "integrity": "sha512-7uNS1ih8hPtB7EuV3sUg4/lFWc6vxw52aUwifTIZ44ZoA8bykroLRC8lnTifuFGeNTHYqCrU0Ev6Zi5i05fSVg==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-11.2.0.tgz", + "integrity": "sha512-6yRBmS5aGWwZzCxvIU5tfTvFRUcJmQ9Mfh9vi07TDda7+h5OQ77ZHFYqzfjVQvEjaZm5D1Ja/tgPd04MkpPIwA==", "dev": true, "requires": { "base-64": "0.1.0", @@ -476,7 +476,7 @@ "sc-errors": "1.4.0", "sc-formatter": "3.0.2", "uuid": "3.2.1", - "ws": "3.3.3" + "ws": "5.1.1" } }, "supports-color": { @@ -488,11 +488,6 @@ "has-flag": "1.0.0" } }, - "ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" - }, "uuid": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", @@ -505,13 +500,11 @@ "dev": true }, "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz", + "integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==", "requires": { - "async-limiter": "1.0.0", - "safe-buffer": "5.1.1", - "ultron": "1.1.1" + "async-limiter": "1.0.0" } }, "xtend": { diff --git a/package.json b/package.json index adaf9ed..29ecbfd 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "devDependencies": { "localStorage": "^1.0.3", "mocha": "3.0.2", - "socketcluster-client": "^11.0.0" + "socketcluster-client": "^11.2.0" }, "scripts": { "test": "mocha --reporter spec --timeout 3000 --slow 3000" diff --git a/scserver.js b/scserver.js index fec69ef..b615fc3 100644 --- a/scserver.js +++ b/scserver.js @@ -36,6 +36,7 @@ var SCServer = function (options) { ackTimeout: 10000, handshakeTimeout: 10000, pingTimeout: 20000, + pingTimeoutDisabled: false, pingInterval: 8000, origins: '*:*', appName: uuid.v4(), @@ -82,6 +83,7 @@ var SCServer = function (options) { this.handshakeTimeout = opts.handshakeTimeout; this.pingInterval = opts.pingInterval; this.pingTimeout = opts.pingTimeout; + this.pingTimeoutDisabled = opts.pingTimeoutDisabled; this.allowClientPublish = opts.allowClientPublish; this.perMessageDeflate = opts.perMessageDeflate; this.httpServer = opts.httpServer; diff --git a/scsocket.js b/scsocket.js index aa23139..88f5d11 100644 --- a/scsocket.js +++ b/scsocket.js @@ -178,6 +178,9 @@ SCSocket.prototype._handleEventObject = function (obj, message) { }; SCSocket.prototype._resetPongTimeout = function () { + if (this.server.pingTimeoutDisabled) { + return; + } var self = this; clearTimeout(this._pingTimeoutTicker); diff --git a/test/integration.js b/test/integration.js index 67f1f63..f0abfdd 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1486,4 +1486,132 @@ describe('Integration tests', function () { }); }); }); + + describe('Ping/pong', function () { + describe('When when pingTimeoutDisabled is not set (false)', function () { + beforeEach('Launch server with ping options before start', function (done) { + portNumber++; + // Intentionally make pingInterval higher than pingTimeout, that + // way the client will never receive a ping or send back a pong. + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, + pingInterval: 2000, + pingTimeout: 500 + }); + server.on('ready', function () { + done(); + }); + }); + + afterEach('Shut down server afterwards', function (done) { + destroyTestCase(function () { + server.close(); + done(); + }); + }); + + it('Should disconnect socket if server does not receive a pong from client before timeout', function (done) { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + + var serverWarning = null; + server.on('warning', function (err) { + serverWarning = err; + }); + + var serverDisconnectionCode = null; + server.on('disconnection', function (socket, code) { + serverDisconnectionCode = code; + }); + + var clientError = null; + client.on('error', function (err) { + clientError = err; + }); + + var clientDisconnectCode = null; + client.on('disconnect', function (code) { + clientDisconnectCode = code; + }); + + setTimeout(function () { + assert.notEqual(clientError, null); + assert.equal(clientError.name, 'SocketProtocolError'); + assert.equal(clientDisconnectCode, 4000); + + assert.notEqual(serverWarning, null); + assert.equal(serverWarning.name, 'SocketProtocolError'); + assert.equal(serverDisconnectionCode, 4001); + done(); + }, 1000); + }); + }); + + describe('When when pingTimeoutDisabled is true', function () { + beforeEach('Launch server with ping options before start', function (done) { + portNumber++; + // Intentionally make pingInterval higher than pingTimeout, that + // way the client will never receive a ping or send back a pong. + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, + pingInterval: 2000, + pingTimeout: 500, + pingTimeoutDisabled: true + }); + server.on('ready', function () { + done(); + }); + }); + + afterEach('Shut down server afterwards', function (done) { + destroyTestCase(function () { + server.close(); + done(); + }); + }); + + it('Should not disconnect socket if server does not receive a pong from client before timeout', function (done) { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false, + pingTimeoutDisabled: true + }); + + var serverWarning = null; + server.on('warning', function (err) { + serverWarning = err; + }); + + var serverDisconnectionCode = null; + server.on('disconnection', function (socket, code) { + serverDisconnectionCode = code; + }); + + var clientError = null; + client.on('error', function (err) { + clientError = err; + }); + + var clientDisconnectCode = null; + client.on('disconnect', function (code) { + clientDisconnectCode = code; + }); + + setTimeout(function () { + assert.equal(clientError, null); + assert.equal(clientDisconnectCode, null); + + assert.equal(serverWarning, null); + assert.equal(serverDisconnectionCode, null); + done(); + }, 1000); + }); + }); + }); }); From 1e82b817fd0f8afba6c5d0aac5b606787f645598 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 25 Apr 2018 01:32:16 +0200 Subject: [PATCH 029/265] v11.2.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4e43d48..0645958 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "11.1.0", + "version": "11.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 29ecbfd..24bc990 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "11.1.0", + "version": "11.2.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 5504e9adebdbd4d8e32ad04371635232def41173 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 25 Apr 2018 16:07:05 +0200 Subject: [PATCH 030/265] Do not set ping interval if ping timeout is disabled --- scsocket.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scsocket.js b/scsocket.js index 88f5d11..2637e3b 100644 --- a/scsocket.js +++ b/scsocket.js @@ -75,7 +75,9 @@ var SCSocket = function (id, server, socket) { self._onSCClose(code, data); }); - this._pingIntervalTicker = setInterval(this._sendPing.bind(this), this.server.pingInterval); + if (!this.server.pingTimeoutDisabled) { + this._pingIntervalTicker = setInterval(this._sendPing.bind(this), this.server.pingInterval); + } this._resetPongTimeout(); // Receive incoming raw messages From 0dcfebf2622b9ebc7ed9f9e2db4a22839fdcfe68 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 25 Apr 2018 16:11:51 +0200 Subject: [PATCH 031/265] v11.2.1 --- package-lock.json | 28 ++++++++++++++-------------- package.json | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0645958..e49038c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "11.2.0", + "version": "11.2.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -15,7 +15,7 @@ "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + "integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=" }, "balanced-match": { "version": "1.0.0", @@ -42,7 +42,7 @@ "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", "dev": true, "requires": { "balanced-match": "1.0.0", @@ -350,7 +350,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "dev": true, "requires": { "brace-expansion": "1.1.11" @@ -393,7 +393,7 @@ "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=" }, "once": { "version": "1.4.0", @@ -419,7 +419,7 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" }, "sc-auth": { "version": "5.0.0", @@ -433,7 +433,7 @@ "sc-channel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", - "integrity": "sha512-M3gdq8PlKg0zWJSisWqAsMmTVxYRTpVRqw4CWAdKBgAfVKumFcTjoCV0hYu7lgUXccCtCD8Wk9VkkE+IXCxmZA==", + "integrity": "sha1-2SCfOpHj+mlMZrARzlXErYwwh9k=", "requires": { "component-emitter": "1.2.1" } @@ -441,17 +441,17 @@ "sc-errors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.0.tgz", - "integrity": "sha512-h+jRWx/xRJmkPFDd0IltoTl/QJ6hAr5Y+3ZVeBQRLuWZKe+dHdf2uVwFp2OYqlLQ7GHht4y9eXG2zOf2Ik6PTw==" + "integrity": "sha1-JUWY5W1EoCYBHNCdrNkNy4ewaI0=" }, "sc-formatter": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/sc-formatter/-/sc-formatter-3.0.2.tgz", - "integrity": "sha512-9PbqYBpCq+OoEeRQ3QfFIGE6qwjjBcd2j7UjgDlhnZbtSnuGgHdcRklPKYGuYFH82V/dwd+AIpu8XvA1zqTd+A==" + "integrity": "sha1-mr2xTnGHPOcVdxTTACR3u9szxOY=" }, "sc-simple-broker": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-2.1.1.tgz", - "integrity": "sha512-m6JM2dxP6G9mjEVC2VEDWt7SG/uBLerJs1U193WOd7pZKmbZhcVlhuw0IdJcs7lDwGm0jlX6DA1LbEn0yUCQLA==", + "integrity": "sha1-g0HPaoDjhK2aiHfMPXOYAoqVU/A=", "requires": { "sc-channel": "1.2.0" } @@ -459,12 +459,12 @@ "sc-uws": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/sc-uws/-/sc-uws-10.0.1.tgz", - "integrity": "sha512-sLk//KBp/4Ao/7cVqwU3DbOYRZZfru9Izkc03Hlmvs7aydFoGM6+tMhWDrrhjwTUM9tXdjZldrS7mv5yzxsRmA==" + "integrity": "sha1-Y57J+XVnuqotJCVe+Tb/Z2h102A=" }, "socketcluster-client": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-11.2.0.tgz", - "integrity": "sha512-6yRBmS5aGWwZzCxvIU5tfTvFRUcJmQ9Mfh9vi07TDda7+h5OQ77ZHFYqzfjVQvEjaZm5D1Ja/tgPd04MkpPIwA==", + "integrity": "sha1-Axl+TWh7VdkCrbb4VJKOcG+jLO8=", "dev": true, "requires": { "base-64": "0.1.0", @@ -491,7 +491,7 @@ "uuid": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + "integrity": "sha1-EsUou51Y0LkmXZovbw/ovhf/HxQ=" }, "wrappy": { "version": "1.0.2", @@ -502,7 +502,7 @@ "ws": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz", - "integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==", + "integrity": "sha1-HUNwRolxGsGUL9Lyg+OPglxLi5U=", "requires": { "async-limiter": "1.0.0" } diff --git a/package.json b/package.json index 24bc990..e8e5abe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "11.2.0", + "version": "11.2.1", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From f4c0bb063b3e580d39a03aa3fba62bc9870b9211 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 29 Apr 2018 19:17:44 +0200 Subject: [PATCH 032/265] Add authStateChange and authenticationStateChange events --- scserver.js | 22 ++++------ scsocket.js | 37 ++++++++++++++-- test/integration.js | 103 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 141 insertions(+), 21 deletions(-) diff --git a/scserver.js b/scserver.js index b615fc3..7c79413 100644 --- a/scserver.js +++ b/scserver.js @@ -324,6 +324,7 @@ SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, call var self = this; this.auth.verifyToken(signedAuthToken, this.verificationKey, this.defaultVerificationOptions, function (err, authToken) { + var oldState = scSocket.authState; if (authToken) { scSocket.signedAuthToken = signedAuthToken; scSocket.authToken = authToken; @@ -353,7 +354,7 @@ SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, call } // If an error is passed back from the authenticate middleware, it will be treated as a // server warning and not a socket error. - callback(middlewareError, isBadToken || false); + callback(middlewareError, isBadToken || false, oldState); }); } else { var errorData = self._processTokenError(err); @@ -366,7 +367,7 @@ SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, call self._emitBadAuthTokenError(scSocket, errorData.authError, signedAuthToken); } } - callback(errorData.authError, errorData.isBadToken); + callback(errorData.authError, errorData.isBadToken, oldState); } }); }; @@ -397,14 +398,13 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { this.emit('handshake', scSocket); scSocket.on('#authenticate', function (signedAuthToken, respond) { - self._processAuthToken(scSocket, signedAuthToken, function (err, isBadToken) { + self._processAuthToken(scSocket, signedAuthToken, function (err, isBadToken, oldState) { if (err) { if (isBadToken) { scSocket.deauthenticate(); } } else { - scSocket.emit('authenticate', scSocket.authToken); - self.emit('authentication', scSocket, scSocket.authToken); + scSocket.triggerAuthenticationEvents(oldState); } var authStatus = { isAuthenticated: !!scSocket.authToken, @@ -419,11 +419,7 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { }); scSocket.on('#removeAuthToken', function () { - var oldToken = scSocket.authToken; - scSocket.authToken = null; - scSocket.authState = scSocket.UNAUTHENTICATED; - scSocket.emit('deauthenticate', oldToken); - self.emit('deauthentication', scSocket, oldToken); + scSocket.deauthenticateSelf(); }); scSocket.on('#subscribe', function (channelOptions, res) { @@ -481,6 +477,7 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { scSocket.off('#subscribe'); scSocket.off('#unsubscribe'); scSocket.off('authenticate'); + scSocket.off('authStateChange'); scSocket.off('deauthenticate'); scSocket.off('_disconnect'); scSocket.off('_connectAbort'); @@ -533,7 +530,7 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { scSocket.disconnect(statusCode); return; } - self._processAuthToken(scSocket, signedAuthToken, function (err, isBadToken) { + self._processAuthToken(scSocket, signedAuthToken, function (err, isBadToken, oldState) { if (scSocket.state == scSocket.CLOSED) { return; } @@ -578,8 +575,7 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { self.emit('connection', scSocket, serverSocketStatus); if (clientSocketStatus.isAuthenticated) { - scSocket.emit('authenticate', scSocket.authToken); - self.emit('authentication', scSocket, scSocket.authToken); + scSocket.triggerAuthenticationEvents(oldState); } // Treat authentication failure as a 'soft' error respond(null, clientSocketStatus); diff --git a/scsocket.js b/scsocket.js index 2637e3b..4472cd0 100644 --- a/scsocket.js +++ b/scsocket.js @@ -26,6 +26,7 @@ var SCSocket = function (id, server, socket) { '_close': 1, 'message': 1, 'error': 1, + 'authStateChange': 1, 'authenticate': 1, 'deauthenticate': 1, 'badAuthToken': 1, @@ -40,6 +41,7 @@ var SCSocket = function (id, server, socket) { this.server = server; this.socket = socket; this.state = this.CONNECTING; + this.authState = this.UNAUTHENTICATED; this.request = this.socket.upgradeReq || {}; @@ -376,10 +378,25 @@ SCSocket.prototype.emit = function (event, data, callback, options) { } }; +SCSocket.prototype.triggerAuthenticationEvents = function (oldState) { + if (oldState != this.AUTHENTICATED) { + var stateChangeData = { + oldState: oldState, + newState: this.authState, + authToken: this.authToken + }; + this.emit('authStateChange', stateChangeData); + this.server.emit('authenticationStateChange', this, stateChangeData); + } + this.emit('authenticate', this.authToken); + this.server.emit('authentication', this, this.authToken); +}; + SCSocket.prototype.setAuthToken = function (data, options, callback) { var self = this; var authToken = cloneDeep(data); + var oldState = this.authState; this.authState = this.AUTHENTICATED; if (options == null) { @@ -439,23 +456,35 @@ SCSocket.prototype.setAuthToken = function (data, options, callback) { }); this.authToken = authToken; - this.emit('authenticate', this.authToken); - this.server.emit('authentication', this, this.authToken); + this.triggerAuthenticationEvents(oldState); }; SCSocket.prototype.getAuthToken = function () { return this.authToken; }; -SCSocket.prototype.deauthenticate = function (callback) { +SCSocket.prototype.deauthenticateSelf = function () { + var oldState = this.authState; var oldToken = this.authToken; this.authToken = null; this.authState = this.UNAUTHENTICATED; - this.emit('#removeAuthToken', null, callback); + if (oldState != this.UNAUTHENTICATED) { + var stateChangeData = { + oldState: oldState, + newState: this.authState + }; + this.emit('authStateChange', stateChangeData); + this.server.emit('authenticationStateChange', this, stateChangeData); + } this.emit('deauthenticate', oldToken); this.server.emit('deauthentication', this, oldToken); }; +SCSocket.prototype.deauthenticate = function (callback) { + this.deauthenticateSelf(); + this.emit('#removeAuthToken', null, callback); +}; + SCSocket.prototype.kickOut = function (channel, message, callback) { if (channel == null) { for (var i in this.channelSubscriptions) { diff --git a/test/integration.js b/test/integration.js index f0abfdd..b48ddac 100644 --- a/test/integration.js +++ b/test/integration.js @@ -209,15 +209,110 @@ describe('Integration tests', function () { }); it('Should allow switching between users', function (done) { + global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); + + var authenticateEvents = []; + var deauthenticateEvents = []; + var authenticationStateChangeEvents = []; + var authStateChangeEvents = []; + + server.on('authenticationStateChange', function (socket, stateChangeData) { + authenticationStateChangeEvents.push({ + socket: socket, + stateChangeData: stateChangeData + }); + }); + + server.on('connection', function (socket) { + socket.on('authenticate', function (authToken) { + authenticateEvents.push(authToken); + }); + socket.on('deauthenticate', function (oldAuthToken) { + deauthenticateEvents.push(oldAuthToken); + }); + socket.on('authStateChange', function (stateChangeData) { + authStateChangeEvents.push(stateChangeData); + }); + }); + + var clientSocketId; client = socketCluster.connect(clientOptions); client.once('connect', function (statusA) { + clientSocketId = client.id; client.emit('login', {username: 'alice'}); + }); - client.once('authTokenChange', function (signedToken) { - assert.equal(client.authState, 'authenticated'); - assert.notEqual(client.authToken, null); - assert.equal(client.authToken.username, 'alice'); + setTimeout(function () { + assert.equal(deauthenticateEvents.length, 0); + assert.equal(authenticateEvents.length, 2); + assert.equal(authenticateEvents[0].username, 'bob'); + assert.equal(authenticateEvents[1].username, 'alice'); + + assert.equal(authenticationStateChangeEvents.length, 1); + assert.notEqual(authenticationStateChangeEvents[0].socket, null); + assert.equal(authenticationStateChangeEvents[0].socket.id, clientSocketId); + assert.equal(authenticationStateChangeEvents[0].stateChangeData.oldState, 'unauthenticated'); + assert.equal(authenticationStateChangeEvents[0].stateChangeData.newState, 'authenticated'); + assert.notEqual(authenticationStateChangeEvents[0].stateChangeData.authToken, null); + assert.equal(authenticationStateChangeEvents[0].stateChangeData.authToken.username, 'bob'); + assert.equal(authStateChangeEvents.length, 1); + assert.equal(authStateChangeEvents[0].oldState, 'unauthenticated'); + assert.equal(authStateChangeEvents[0].newState, 'authenticated'); + assert.notEqual(authStateChangeEvents[0].authToken, null); + assert.equal(authStateChangeEvents[0].authToken.username, 'bob'); + + done(); + }, 100); + }); + + it('Should emit correct events/data when socket is deauthenticated', function (done) { + global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); + + var authenticationStateChangeEvents = []; + var authStateChangeEvents = []; + + server.on('authenticationStateChange', function (socket, stateChangeData) { + authenticationStateChangeEvents.push({ + socket: socket, + stateChangeData: stateChangeData + }); + }); + + client = socketCluster.connect(clientOptions); + client.once('connect', function (statusA) { + client.deauthenticate(); + }); + + server.on('connection', function (socket) { + var initialAuthToken = socket.authToken; + + socket.on('authStateChange', function (stateChangeData) { + authStateChangeEvents.push(stateChangeData); + }); + + socket.on('deauthenticate', function (oldToken) { + assert.equal(oldToken, initialAuthToken); + + assert.equal(authStateChangeEvents.length, 2); + assert.equal(authStateChangeEvents[0].oldState, 'unauthenticated'); + assert.equal(authStateChangeEvents[0].newState, 'authenticated'); + assert.notEqual(authStateChangeEvents[0].authToken, null); + assert.equal(authStateChangeEvents[0].authToken.username, 'bob'); + assert.equal(authStateChangeEvents[1].oldState, 'authenticated'); + assert.equal(authStateChangeEvents[1].newState, 'unauthenticated'); + assert.equal(authStateChangeEvents[1].authToken, null); + + assert.equal(authenticationStateChangeEvents.length, 2); + assert.notEqual(authenticationStateChangeEvents[0].stateChangeData, null); + assert.equal(authenticationStateChangeEvents[0].stateChangeData.oldState, 'unauthenticated'); + assert.equal(authenticationStateChangeEvents[0].stateChangeData.newState, 'authenticated'); + assert.notEqual(authenticationStateChangeEvents[0].stateChangeData.authToken, null); + assert.equal(authenticationStateChangeEvents[0].stateChangeData.authToken.username, 'bob'); + assert.notEqual(authenticationStateChangeEvents[1].stateChangeData, null); + assert.equal(authenticationStateChangeEvents[1].stateChangeData.oldState, 'authenticated'); + assert.equal(authenticationStateChangeEvents[1].stateChangeData.newState, 'unauthenticated'); + assert.equal(authenticationStateChangeEvents[1].stateChangeData.authToken, null); done(); }); }); From 2f2947a449bcb4a2c4ba19de85add30df62d6e7d Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 29 Apr 2018 19:19:51 +0200 Subject: [PATCH 033/265] v12.0.0 --- package-lock.json | 28 ++++++++++++++-------------- package.json | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index e49038c..6d711c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "11.2.1", + "version": "12.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -15,7 +15,7 @@ "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=" + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" }, "balanced-match": { "version": "1.0.0", @@ -42,7 +42,7 @@ "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { "balanced-match": "1.0.0", @@ -350,7 +350,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { "brace-expansion": "1.1.11" @@ -393,7 +393,7 @@ "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=" + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, "once": { "version": "1.4.0", @@ -419,7 +419,7 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, "sc-auth": { "version": "5.0.0", @@ -433,7 +433,7 @@ "sc-channel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", - "integrity": "sha1-2SCfOpHj+mlMZrARzlXErYwwh9k=", + "integrity": "sha512-M3gdq8PlKg0zWJSisWqAsMmTVxYRTpVRqw4CWAdKBgAfVKumFcTjoCV0hYu7lgUXccCtCD8Wk9VkkE+IXCxmZA==", "requires": { "component-emitter": "1.2.1" } @@ -441,17 +441,17 @@ "sc-errors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.0.tgz", - "integrity": "sha1-JUWY5W1EoCYBHNCdrNkNy4ewaI0=" + "integrity": "sha512-h+jRWx/xRJmkPFDd0IltoTl/QJ6hAr5Y+3ZVeBQRLuWZKe+dHdf2uVwFp2OYqlLQ7GHht4y9eXG2zOf2Ik6PTw==" }, "sc-formatter": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/sc-formatter/-/sc-formatter-3.0.2.tgz", - "integrity": "sha1-mr2xTnGHPOcVdxTTACR3u9szxOY=" + "integrity": "sha512-9PbqYBpCq+OoEeRQ3QfFIGE6qwjjBcd2j7UjgDlhnZbtSnuGgHdcRklPKYGuYFH82V/dwd+AIpu8XvA1zqTd+A==" }, "sc-simple-broker": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-2.1.1.tgz", - "integrity": "sha1-g0HPaoDjhK2aiHfMPXOYAoqVU/A=", + "integrity": "sha512-m6JM2dxP6G9mjEVC2VEDWt7SG/uBLerJs1U193WOd7pZKmbZhcVlhuw0IdJcs7lDwGm0jlX6DA1LbEn0yUCQLA==", "requires": { "sc-channel": "1.2.0" } @@ -459,12 +459,12 @@ "sc-uws": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/sc-uws/-/sc-uws-10.0.1.tgz", - "integrity": "sha1-Y57J+XVnuqotJCVe+Tb/Z2h102A=" + "integrity": "sha512-sLk//KBp/4Ao/7cVqwU3DbOYRZZfru9Izkc03Hlmvs7aydFoGM6+tMhWDrrhjwTUM9tXdjZldrS7mv5yzxsRmA==" }, "socketcluster-client": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-11.2.0.tgz", - "integrity": "sha1-Axl+TWh7VdkCrbb4VJKOcG+jLO8=", + "integrity": "sha512-6yRBmS5aGWwZzCxvIU5tfTvFRUcJmQ9Mfh9vi07TDda7+h5OQ77ZHFYqzfjVQvEjaZm5D1Ja/tgPd04MkpPIwA==", "dev": true, "requires": { "base-64": "0.1.0", @@ -491,7 +491,7 @@ "uuid": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha1-EsUou51Y0LkmXZovbw/ovhf/HxQ=" + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" }, "wrappy": { "version": "1.0.2", @@ -502,7 +502,7 @@ "ws": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz", - "integrity": "sha1-HUNwRolxGsGUL9Lyg+OPglxLi5U=", + "integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==", "requires": { "async-limiter": "1.0.0" } diff --git a/package.json b/package.json index e8e5abe..e819744 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "11.2.1", + "version": "12.0.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 8caeb9f0004c5a861c9b612830f8b55af0ea489a Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 2 May 2018 23:01:27 +0200 Subject: [PATCH 034/265] Rename SCSocket to SCServerSocket --- index.js | 4 +- scserver.js | 4 +- scsocket.js => scserversocket.js | 72 ++++++++++++++++---------------- 3 files changed, 40 insertions(+), 40 deletions(-) rename scsocket.js => scserversocket.js (84%) diff --git a/index.js b/index.js index f3a25d4..58fd69c 100644 --- a/index.js +++ b/index.js @@ -13,12 +13,12 @@ var http = require('http'); module.exports.SCServer = require('./scserver'); /** - * Expose SCSocket constructor. + * Expose SCServerSocket constructor. * * @api public */ -module.exports.SCSocket = require('./scsocket'); +module.exports.SCServerSocket = require('./scserversocket'); /** * Creates an http.Server exclusively used for WS upgrades. diff --git a/scserver.js b/scserver.js index 7c79413..6f85369 100644 --- a/scserver.js +++ b/scserver.js @@ -1,4 +1,4 @@ -var SCSocket = require('./scsocket'); +var SCServerSocket = require('./scserversocket'); var AuthEngine = require('sc-auth').AuthEngine; var formatter = require('sc-formatter'); var EventEmitter = require('events').EventEmitter; @@ -382,7 +382,7 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { var id = this.generateId(); - var scSocket = new SCSocket(id, this, wsSocket); + var scSocket = new SCServerSocket(id, this, wsSocket); scSocket.exchange = self.exchange; scSocket.on('error', function (err) { diff --git a/scsocket.js b/scserversocket.js similarity index 84% rename from scsocket.js rename to scserversocket.js index 4472cd0..ab478a5 100644 --- a/scsocket.js +++ b/scserversocket.js @@ -8,7 +8,7 @@ var SocketProtocolError = scErrors.SocketProtocolError; var TimeoutError = scErrors.TimeoutError; -var SCSocket = function (id, server, socket) { +var SCServerSocket = function (id, server, socket) { var self = this; Emitter.call(this); @@ -118,25 +118,25 @@ var SCSocket = function (id, server, socket) { }); }; -SCSocket.prototype = Object.create(Emitter.prototype); +SCServerSocket.prototype = Object.create(Emitter.prototype); -SCSocket.CONNECTING = SCSocket.prototype.CONNECTING = 'connecting'; -SCSocket.OPEN = SCSocket.prototype.OPEN = 'open'; -SCSocket.CLOSED = SCSocket.prototype.CLOSED = 'closed'; +SCServerSocket.CONNECTING = SCServerSocket.prototype.CONNECTING = 'connecting'; +SCServerSocket.OPEN = SCServerSocket.prototype.OPEN = 'open'; +SCServerSocket.CLOSED = SCServerSocket.prototype.CLOSED = 'closed'; -SCSocket.AUTHENTICATED = SCSocket.prototype.AUTHENTICATED = 'authenticated'; -SCSocket.UNAUTHENTICATED = SCSocket.prototype.UNAUTHENTICATED = 'unauthenticated'; +SCServerSocket.AUTHENTICATED = SCServerSocket.prototype.AUTHENTICATED = 'authenticated'; +SCServerSocket.UNAUTHENTICATED = SCServerSocket.prototype.UNAUTHENTICATED = 'unauthenticated'; -SCSocket.ignoreStatuses = scErrors.socketProtocolIgnoreStatuses; -SCSocket.errorStatuses = scErrors.socketProtocolErrorStatuses; +SCServerSocket.ignoreStatuses = scErrors.socketProtocolIgnoreStatuses; +SCServerSocket.errorStatuses = scErrors.socketProtocolErrorStatuses; -SCSocket.prototype._sendPing = function () { +SCServerSocket.prototype._sendPing = function () { if (this.state != this.CLOSED) { this.sendObject('#1'); } }; -SCSocket.prototype._handleEventObject = function (obj, message) { +SCServerSocket.prototype._handleEventObject = function (obj, message) { var self = this; if (obj && obj.event != null) { @@ -181,7 +181,7 @@ SCSocket.prototype._handleEventObject = function (obj, message) { } }; -SCSocket.prototype._resetPongTimeout = function () { +SCServerSocket.prototype._resetPongTimeout = function () { if (this.server.pingTimeoutDisabled) { return; } @@ -194,19 +194,19 @@ SCSocket.prototype._resetPongTimeout = function () { }, this.server.pingTimeout); }; -SCSocket.prototype._nextCallId = function () { +SCServerSocket.prototype._nextCallId = function () { return this._cid++; }; -SCSocket.prototype.getState = function () { +SCServerSocket.prototype.getState = function () { return this.state; }; -SCSocket.prototype.getBytesReceived = function () { +SCServerSocket.prototype.getBytesReceived = function () { return this.socket.bytesReceived; }; -SCSocket.prototype._onSCClose = function (code, data) { +SCServerSocket.prototype._onSCClose = function (code, data) { clearInterval(this._pingIntervalTicker); clearTimeout(this._pingTimeoutTicker); @@ -227,7 +227,7 @@ SCSocket.prototype._onSCClose = function (code, data) { Emitter.prototype.emit.call(this, '_close', code, data); Emitter.prototype.emit.call(this, 'close', code, data); - if (!SCSocket.ignoreStatuses[code]) { + if (!SCServerSocket.ignoreStatuses[code]) { var closeMessage; if (data) { var reasonString; @@ -244,13 +244,13 @@ SCSocket.prototype._onSCClose = function (code, data) { } else { closeMessage = 'Socket connection closed with status code ' + code; } - var err = new SocketProtocolError(SCSocket.errorStatuses[code] || closeMessage, code); + var err = new SocketProtocolError(SCServerSocket.errorStatuses[code] || closeMessage, code); Emitter.prototype.emit.call(this, 'error', err); } } }; -SCSocket.prototype.disconnect = function (code, data) { +SCServerSocket.prototype.disconnect = function (code, data) { code = code || 1000; if (typeof code != 'number') { @@ -269,11 +269,11 @@ SCSocket.prototype.disconnect = function (code, data) { } }; -SCSocket.prototype.terminate = function () { +SCServerSocket.prototype.terminate = function () { this.socket.terminate(); }; -SCSocket.prototype.send = function (data, options) { +SCServerSocket.prototype.send = function (data, options) { var self = this; this.socket.send(data, options, function (err) { @@ -283,15 +283,15 @@ SCSocket.prototype.send = function (data, options) { }); }; -SCSocket.prototype.decode = function (message) { +SCServerSocket.prototype.decode = function (message) { return this.server.codec.decode(message); }; -SCSocket.prototype.encode = function (object) { +SCServerSocket.prototype.encode = function (object) { return this.server.codec.encode(object); }; -SCSocket.prototype.sendObjectBatch = function (object) { +SCServerSocket.prototype.sendObjectBatch = function (object) { var self = this; this._batchSendList.push(object); @@ -316,7 +316,7 @@ SCSocket.prototype.sendObjectBatch = function (object) { }, this.server.options.pubSubBatchDuration || 0); }; -SCSocket.prototype.sendObjectSingle = function (object) { +SCServerSocket.prototype.sendObjectSingle = function (object) { var str; try { str = this.encode(object); @@ -328,7 +328,7 @@ SCSocket.prototype.sendObjectSingle = function (object) { } }; -SCSocket.prototype.sendObject = function (object, options) { +SCServerSocket.prototype.sendObject = function (object, options) { if (options && options.batch) { this.sendObjectBatch(object); } else { @@ -336,7 +336,7 @@ SCSocket.prototype.sendObject = function (object, options) { } }; -SCSocket.prototype.emit = function (event, data, callback, options) { +SCServerSocket.prototype.emit = function (event, data, callback, options) { var self = this; if (this._localEvents[event] == null) { @@ -378,7 +378,7 @@ SCSocket.prototype.emit = function (event, data, callback, options) { } }; -SCSocket.prototype.triggerAuthenticationEvents = function (oldState) { +SCServerSocket.prototype.triggerAuthenticationEvents = function (oldState) { if (oldState != this.AUTHENTICATED) { var stateChangeData = { oldState: oldState, @@ -392,7 +392,7 @@ SCSocket.prototype.triggerAuthenticationEvents = function (oldState) { this.server.emit('authentication', this, this.authToken); }; -SCSocket.prototype.setAuthToken = function (data, options, callback) { +SCServerSocket.prototype.setAuthToken = function (data, options, callback) { var self = this; var authToken = cloneDeep(data); @@ -459,11 +459,11 @@ SCSocket.prototype.setAuthToken = function (data, options, callback) { this.triggerAuthenticationEvents(oldState); }; -SCSocket.prototype.getAuthToken = function () { +SCServerSocket.prototype.getAuthToken = function () { return this.authToken; }; -SCSocket.prototype.deauthenticateSelf = function () { +SCServerSocket.prototype.deauthenticateSelf = function () { var oldState = this.authState; var oldToken = this.authToken; this.authToken = null; @@ -480,12 +480,12 @@ SCSocket.prototype.deauthenticateSelf = function () { this.server.emit('deauthentication', this, oldToken); }; -SCSocket.prototype.deauthenticate = function (callback) { +SCServerSocket.prototype.deauthenticate = function (callback) { this.deauthenticateSelf(); this.emit('#removeAuthToken', null, callback); }; -SCSocket.prototype.kickOut = function (channel, message, callback) { +SCServerSocket.prototype.kickOut = function (channel, message, callback) { if (channel == null) { for (var i in this.channelSubscriptions) { if (this.channelSubscriptions.hasOwnProperty(i)) { @@ -498,7 +498,7 @@ SCSocket.prototype.kickOut = function (channel, message, callback) { this.server.brokerEngine.unsubscribeSocket(this, channel, callback); }; -SCSocket.prototype.subscriptions = function () { +SCServerSocket.prototype.subscriptions = function () { var subs = []; for (var i in this.channelSubscriptions) { if (this.channelSubscriptions.hasOwnProperty(i)) { @@ -508,8 +508,8 @@ SCSocket.prototype.subscriptions = function () { return subs; }; -SCSocket.prototype.isSubscribed = function (channel) { +SCServerSocket.prototype.isSubscribed = function (channel) { return !!this.channelSubscriptions[channel]; }; -module.exports = SCSocket; +module.exports = SCServerSocket; From 6b65c1b21446ac951cc1ebd63bd25a169cddab15 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 2 May 2018 23:54:10 +0200 Subject: [PATCH 035/265] Emit error on socket if reserved event is emitted on socket --- scserver.js | 17 +++++++++-------- scserversocket.js | 14 +++++++++----- test/integration.js | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/scserver.js b/scserver.js index 6f85369..976b21c 100644 --- a/scserver.js +++ b/scserver.js @@ -2,6 +2,7 @@ var SCServerSocket = require('./scserversocket'); var AuthEngine = require('sc-auth').AuthEngine; var formatter = require('sc-formatter'); var EventEmitter = require('events').EventEmitter; +var Emitter = require('component-emitter'); var base64id = require('base64id'); var async = require('async'); var url = require('url'); @@ -248,7 +249,7 @@ SCServer.prototype._subscribeSocket = function (socket, channelOptions, callback delete socket.channelSubscriptions[channelName]; socket.channelSubscriptionsCount--; } else { - socket.emit('subscribe', channelName, channelOptions); + Emitter.prototype.emit.call(socket, 'subscribe', channelName, channelOptions); self.emit('subscription', socket, channelName, channelOptions); } callback && callback(err); @@ -283,7 +284,7 @@ SCServer.prototype._unsubscribeSocket = function (socket, channel) { this.brokerEngine.unsubscribeSocket(socket, channel); - socket.emit('unsubscribe', channel); + Emitter.prototype.emit.call(socket, 'unsubscribe', channel); this.emit('unsubscription', socket, channel); }; @@ -316,7 +317,7 @@ SCServer.prototype._emitBadAuthTokenError = function (scSocket, error, signedAut authError: error, signedAuthToken: signedAuthToken }; - scSocket.emit('badAuthToken', badAuthStatus); + Emitter.prototype.emit.call(scSocket, 'badAuthToken', badAuthStatus); this.emit('badSocketAuthToken', scSocket, badAuthStatus); }; @@ -362,7 +363,7 @@ SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, call // If the error is related to the JWT being badly formatted, then we will // treat the error as a socket error. if (err && signedAuthToken != null) { - scSocket.emit('error', errorData.authError); + Emitter.prototype.emit.call(scSocket, 'error', errorData.authError); if (errorData.isBadToken) { self._emitBadAuthTokenError(scSocket, errorData.authError, signedAuthToken); } @@ -437,7 +438,7 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { if (err) { var error = new BrokerError('Failed to subscribe socket to the ' + channelOptions.channel + ' channel - ' + err); res(error); - scSocket.emit('error', error); + Emitter.prototype.emit.call(scSocket, 'error', error); } else { if (channelOptions.batch) { res(undefined, undefined, {batch: true}); @@ -462,7 +463,7 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { } if (error) { res(error); - scSocket.emit('error', error); + Emitter.prototype.emit.call(scSocket, 'error', error); } else { res(); } @@ -568,8 +569,8 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { scSocket.state = scSocket.OPEN; - scSocket.emit('connect', serverSocketStatus); - scSocket.emit('_connect', serverSocketStatus); + Emitter.prototype.emit.call(scSocket, 'connect', serverSocketStatus); + Emitter.prototype.emit.call(scSocket, '_connect', serverSocketStatus); self.emit('_connection', scSocket, serverSocketStatus); self.emit('connection', scSocket, serverSocketStatus); diff --git a/scserversocket.js b/scserversocket.js index ab478a5..97c5a16 100644 --- a/scserversocket.js +++ b/scserversocket.js @@ -6,6 +6,7 @@ var scErrors = require('sc-errors'); var InvalidArgumentsError = scErrors.InvalidArgumentsError; var SocketProtocolError = scErrors.SocketProtocolError; var TimeoutError = scErrors.TimeoutError; +var InvalidActionError = scErrors.InvalidActionError; var SCServerSocket = function (id, server, socket) { @@ -373,8 +374,11 @@ SCServerSocket.prototype.emit = function (event, data, callback, options) { } } }); + } else if (event == 'error') { + Emitter.prototype.emit.call(this, event, data); } else { - Emitter.prototype.emit.apply(this, arguments); + var error = new InvalidActionError('The "' + event + '" event is reserved and cannot be emitted on a server socket'); + Emitter.prototype.emit.call(this, 'error', error); } }; @@ -385,10 +389,10 @@ SCServerSocket.prototype.triggerAuthenticationEvents = function (oldState) { newState: this.authState, authToken: this.authToken }; - this.emit('authStateChange', stateChangeData); + Emitter.prototype.emit.call(this, 'authStateChange', stateChangeData); this.server.emit('authenticationStateChange', this, stateChangeData); } - this.emit('authenticate', this.authToken); + Emitter.prototype.emit.call(this, 'authenticate', this.authToken); this.server.emit('authentication', this, this.authToken); }; @@ -473,10 +477,10 @@ SCServerSocket.prototype.deauthenticateSelf = function () { oldState: oldState, newState: this.authState }; - this.emit('authStateChange', stateChangeData); + Emitter.prototype.emit.call(this, 'authStateChange', stateChangeData); this.server.emit('authenticationStateChange', this, stateChangeData); } - this.emit('deauthenticate', oldToken); + Emitter.prototype.emit.call(this, 'deauthenticate', oldToken); this.server.emit('deauthentication', this, oldToken); }; diff --git a/test/integration.js b/test/integration.js index b48ddac..459e291 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1709,4 +1709,42 @@ describe('Integration tests', function () { }); }); }); + + describe('Errors', function () { + it('Should throw an error if reserved event is emitted on socket', function (done) { + server.on('connection', function (socket) { + var error; + socket.on('error', function (err) { + error = err; + }); + socket.emit('message', 123); + setTimeout(function () { + assert.notEqual(error, null); + assert.equal(error.name, 'InvalidActionError'); + done(); + }, 100); + }); + + client = socketCluster.connect(clientOptions); + }); + + it('Should allow emitting error event on socket', function (done) { + server.on('connection', function (socket) { + var error; + socket.on('error', function (err) { + error = err; + }); + var customError = new Error('This is a custom error'); + customError.name = 'CustomError'; + socket.emit('error', customError); + setTimeout(function () { + assert.notEqual(error, null); + assert.equal(error.name, 'CustomError'); + done(); + }, 100); + }); + + client = socketCluster.connect(clientOptions); + }); + }); }); From a66c571ee34a0c341fc5d21fafdc525cf54a3c09 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 2 May 2018 23:55:34 +0200 Subject: [PATCH 036/265] v13.0.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d711c8..f95a178 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "12.0.0", + "version": "13.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e819744..4b42f63 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "12.0.0", + "version": "13.0.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From a99d2a576036d9dee85c2d34475b9bdde1f81e5e Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 10 May 2018 18:03:15 +0200 Subject: [PATCH 037/265] Add support for multiple verify algorithms --- scserver.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scserver.js b/scserver.js index 976b21c..aae4216 100644 --- a/scserver.js +++ b/scserver.js @@ -130,13 +130,17 @@ var SCServer = function (options) { this.defaultVerificationOptions = { async: this.authVerifyAsync }; + if (opts.authVerifyAlgorithms != null) { + this.defaultVerificationOptions.algorithms = opts.authVerifyAlgorithms; + } else if (opts.authAlgorithm != null) { + this.defaultVerificationOptions.algorithms = [opts.authAlgorithm]; + } + this.defaultSignatureOptions = { expiresIn: opts.authDefaultExpiry, async: this.authSignAsync }; - if (opts.authAlgorithm != null) { - this.defaultVerificationOptions.algorithms = [opts.authAlgorithm]; this.defaultSignatureOptions.algorithm = opts.authAlgorithm; } From 39b36d39994cd36a765f1ed55b92932fe8f5f848 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 10 May 2018 18:56:12 +0200 Subject: [PATCH 038/265] Add destroy method on server socket to make it more consistent with client socket --- scserversocket.js | 6 +++++ test/integration.js | 59 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/scserversocket.js b/scserversocket.js index 97c5a16..06a98e8 100644 --- a/scserversocket.js +++ b/scserversocket.js @@ -43,6 +43,7 @@ var SCServerSocket = function (id, server, socket) { this.socket = socket; this.state = this.CONNECTING; this.authState = this.UNAUTHENTICATED; + this.active = true; this.request = this.socket.upgradeReq || {}; @@ -270,6 +271,11 @@ SCServerSocket.prototype.disconnect = function (code, data) { } }; +SCServerSocket.prototype.destroy = function (code, data) { + this.active = false; + this.disconnect(code, data); +}; + SCServerSocket.prototype.terminate = function () { this.socket.terminate(); }; diff --git a/test/integration.js b/test/integration.js index 459e291..723d5d1 100644 --- a/test/integration.js +++ b/test/integration.js @@ -617,7 +617,7 @@ describe('Integration tests', function () { }); }); - it('should remove client data from the server when client disconnects before authentication process finished', function (done) { + it('Should remove client data from the server when client disconnects before authentication process finished', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, @@ -658,6 +658,63 @@ describe('Integration tests', function () { }); }); + it('Server socket destroy should disconnect the socket', function (done) { + portNumber++; + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + + server.on('connection', function (socket) { + setTimeout(function () { + socket.destroy(1000, 'Custom reason'); + }, 100); + }); + server.on('ready', function () { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + client.on('disconnect', function (code, reason) { + assert.equal(code, 1000); + assert.equal(reason, 'Custom reason'); + assert.equal(server.clientsCount, 0); + assert.equal(server.pendingClientsCount, 0); + done(); + }); + }); + }); + + it('Server socket destroy should set the active property on the socket to false', function (done) { + portNumber++; + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + + var serverSocket; + + server.on('connection', function (socket) { + serverSocket = socket; + assert.equal(socket.active, true); + setTimeout(function () { + socket.destroy(); + }, 100); + }); + server.on('ready', function () { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + client.on('disconnect', function (code, reason) { + assert.equal(serverSocket.active, false); + done(); + }); + }); + }); + it('Client should not be able to subscribe to a channel before the handshake has completed', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { From 1d2eeff5c658c6791d2c2ff17e03eb95506751e3 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 10 May 2018 22:11:57 +0200 Subject: [PATCH 039/265] Pass socket as option to auth.verifyToken for more flexibility --- package.json | 2 +- scserver.js | 3 ++- test/integration.js | 55 +++++++++++++++++++++++++++++++++++---------- 3 files changed, 46 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 4b42f63..11085d6 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "base64id": "1.0.0", "component-emitter": "1.2.1", "lodash.clonedeep": "4.5.0", - "sc-auth": "^5.0.0", + "sc-auth": "^5.0.1", "sc-errors": "^1.4.0", "sc-formatter": "^3.0.2", "sc-simple-broker": "^2.1.1", diff --git a/scserver.js b/scserver.js index aae4216..b04f13b 100644 --- a/scserver.js +++ b/scserver.js @@ -328,7 +328,8 @@ SCServer.prototype._emitBadAuthTokenError = function (scSocket, error, signedAut SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, callback) { var self = this; - this.auth.verifyToken(signedAuthToken, this.verificationKey, this.defaultVerificationOptions, function (err, authToken) { + var verificationOptions = Object.assign({socket: scSocket}, this.defaultVerificationOptions); + this.auth.verifyToken(signedAuthToken, this.verificationKey, verificationOptions, function (err, authToken) { var oldState = scSocket.authState; if (authToken) { scSocket.signedAuthToken = signedAuthToken; diff --git a/test/integration.js b/test/integration.js index 723d5d1..af8749e 100644 --- a/test/integration.js +++ b/test/integration.js @@ -624,9 +624,9 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE }); server.setAuthEngine({ - verifyToken: function (signedAuthToken, verificationKey, defaultVerificationOptions, callback) { + verifyToken: function (signedAuthToken, verificationKey, verificationOptions, callback) { setTimeout(function () { - callback(null, {}) + callback(null, {}); }, 500) } }); @@ -722,9 +722,9 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE }); server.setAuthEngine({ - verifyToken: function (signedAuthToken, verificationKey, defaultVerificationOptions, callback) { + verifyToken: function (signedAuthToken, verificationKey, verificationOptions, callback) { setTimeout(function () { - callback(null, {}) + callback(null, {}); }, 500) } }); @@ -773,9 +773,9 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE }); server.setAuthEngine({ - verifyToken: function (signedAuthToken, verificationKey, defaultVerificationOptions, callback) { + verifyToken: function (signedAuthToken, verificationKey, verificationOptions, callback) { setTimeout(function () { - callback(null, {}) + callback(null, {}); }, 500) } }); @@ -843,9 +843,9 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE }); server.setAuthEngine({ - verifyToken: function (signedAuthToken, verificationKey, defaultVerificationOptions, callback) { + verifyToken: function (signedAuthToken, verificationKey, verificationOptions, callback) { setTimeout(function () { - callback(null, {}) + callback(null, {}); }, 10) } }); @@ -995,9 +995,9 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE }); server.setAuthEngine({ - verifyToken: function (signedAuthToken, verificationKey, defaultVerificationOptions, callback) { + verifyToken: function (signedAuthToken, verificationKey, verificationOptions, callback) { setTimeout(function () { - callback(null, {}) + callback(null, {}); }, 500) } }); @@ -1040,6 +1040,37 @@ describe('Integration tests', function () { }); }); + it('The verifyToken method of the authEngine receives correct params', function (done) { + global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); + + portNumber++; + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + server.setAuthEngine({ + verifyToken: function (signedAuthToken, verificationKey, verificationOptions, callback) { + setTimeout(function () { + assert.equal(signedAuthToken, validSignedAuthTokenBob); + assert.equal(verificationKey, serverOptions.authKey); + assert.notEqual(verificationOptions, null); + assert.notEqual(verificationOptions.socket, null); + assert.equal(typeof callback, 'function'); + callback(null, {}); + done(); + }, 500) + } + }); + server.on('connection', connectionHandler); + server.on('ready', function () { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + }); + }); + it('The close event should trigger when the socket loses the connection after the handshake', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { @@ -1047,9 +1078,9 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE }); server.setAuthEngine({ - verifyToken: function (signedAuthToken, verificationKey, defaultVerificationOptions, callback) { + verifyToken: function (signedAuthToken, verificationKey, verificationOptions, callback) { setTimeout(function () { - callback(null, {}) + callback(null, {}); }, 0) } }); From 050426be8658c5239a623e0d525eca1c8978f7a5 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 10 May 2018 22:32:13 +0200 Subject: [PATCH 040/265] v13.1.0 - Bump sc-uws --- package-lock.json | 163 +++++++++++++++++++--------------------------- package.json | 4 +- 2 files changed, 70 insertions(+), 97 deletions(-) diff --git a/package-lock.json b/package-lock.json index f95a178..60a47c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,19 @@ { "name": "socketcluster-server", - "version": "13.0.0", + "version": "13.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { "async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.3.0.tgz", + "version": "https://registry.npmjs.org/async/-/async-2.3.0.tgz", "integrity": "sha1-EBPRBRBH3TIP4k5JTVxm7K9hR9k=", "requires": { "lodash": "4.17.5" } }, "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + "version": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=" }, "balanced-match": { "version": "1.0.0", @@ -24,19 +22,16 @@ "dev": true }, "base-64": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "version": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=", "dev": true }, "base64id": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "version": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" }, "base64url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", + "version": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=" }, "brace-expansion": { @@ -56,13 +51,11 @@ "dev": true }, "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "version": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, "clone": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "version": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", "dev": true }, @@ -110,11 +103,10 @@ "dev": true }, "ecdsa-sig-formatter": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz", + "version": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz", "integrity": "sha1-S8kmJ07Dtau1AW5+HWCSGsJisqE=", "requires": { - "base64url": "2.0.0", + "base64url": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", "safe-buffer": "5.1.1" } }, @@ -189,48 +181,44 @@ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.2.0.tgz", "integrity": "sha512-1Wxh8ADP3cNyPl8tZ95WtraHXCAyXupgc0AhMHjU9er98BV+UcKsO7OJUjfhIu0Uba9A40n1oSx8dbJYrm+EoQ==", "requires": { - "jws": "3.1.4", - "lodash.includes": "4.3.0", - "lodash.isboolean": "3.0.3", - "lodash.isinteger": "4.0.4", - "lodash.isnumber": "3.0.3", - "lodash.isplainobject": "4.0.6", - "lodash.isstring": "4.0.1", - "lodash.once": "4.1.1", - "ms": "2.1.1", - "xtend": "4.0.1" + "jws": "https://registry.npmjs.org/jws/-/jws-3.1.4.tgz", + "lodash.includes": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "lodash.isboolean": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "lodash.isinteger": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "lodash.isnumber": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "lodash.isplainobject": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "lodash.isstring": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "lodash.once": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "ms": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "xtend": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" } }, "jwa": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.5.tgz", + "version": "https://registry.npmjs.org/jwa/-/jwa-1.1.5.tgz", "integrity": "sha1-oFUs4CIHQs1S4VN3SjKQXDDnVuU=", "requires": { - "base64url": "2.0.0", - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.9", + "base64url": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", + "buffer-equal-constant-time": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "ecdsa-sig-formatter": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz", "safe-buffer": "5.1.1" } }, "jws": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.4.tgz", + "version": "https://registry.npmjs.org/jws/-/jws-3.1.4.tgz", "integrity": "sha1-+ei5M46KhHJ31kRLFGT2GIDgUKI=", "requires": { - "base64url": "2.0.0", - "jwa": "1.1.5", + "base64url": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", + "jwa": "https://registry.npmjs.org/jwa/-/jwa-1.1.5.tgz", "safe-buffer": "5.1.1" } }, "linked-list": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz", + "version": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz", "integrity": "sha1-eYsP+X0bkqT9CEgPVa6k6dSdN78=", "dev": true }, "localStorage": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/localStorage/-/localStorage-1.0.3.tgz", + "version": "https://registry.npmjs.org/localStorage/-/localStorage-1.0.3.tgz", "integrity": "sha1-5riaV7t2ChVqOMyH4PJVD27UE9g=", "dev": true }, @@ -290,8 +278,7 @@ } }, "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "version": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" }, "lodash.isarguments": { @@ -307,28 +294,23 @@ "dev": true }, "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "version": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" }, "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "version": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" }, "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "version": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" }, "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "version": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" }, "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "version": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, "lodash.keys": { @@ -343,8 +325,7 @@ } }, "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "version": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" }, "minimatch": { @@ -391,9 +372,8 @@ } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=" }, "once": { "version": "1.4.0", @@ -411,8 +391,7 @@ "dev": true }, "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "version": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", "dev": true }, @@ -422,26 +401,24 @@ "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, "sc-auth": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/sc-auth/-/sc-auth-5.0.0.tgz", - "integrity": "sha1-ySdiDQDMuJ5oAs9l+c1+MB/Psus=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/sc-auth/-/sc-auth-5.0.1.tgz", + "integrity": "sha512-UgTdvJZS17wkLfNNwj8/uObfACUiGPc5Jl2VmTPpwipasj1kO1Sr64VVhCBUz38j+f/9rBCKlzxCTwljxgX95Q==", "requires": { "jsonwebtoken": "8.2.0", - "sc-errors": "1.4.0" + "sc-errors": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.0.tgz" } }, "sc-channel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", - "integrity": "sha512-M3gdq8PlKg0zWJSisWqAsMmTVxYRTpVRqw4CWAdKBgAfVKumFcTjoCV0hYu7lgUXccCtCD8Wk9VkkE+IXCxmZA==", + "version": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", + "integrity": "sha1-2SCfOpHj+mlMZrARzlXErYwwh9k=", "requires": { "component-emitter": "1.2.1" } }, "sc-errors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.0.tgz", - "integrity": "sha512-h+jRWx/xRJmkPFDd0IltoTl/QJ6hAr5Y+3ZVeBQRLuWZKe+dHdf2uVwFp2OYqlLQ7GHht4y9eXG2zOf2Ik6PTw==" + "version": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.0.tgz", + "integrity": "sha1-JUWY5W1EoCYBHNCdrNkNy4ewaI0=" }, "sc-formatter": { "version": "3.0.2", @@ -449,17 +426,16 @@ "integrity": "sha512-9PbqYBpCq+OoEeRQ3QfFIGE6qwjjBcd2j7UjgDlhnZbtSnuGgHdcRklPKYGuYFH82V/dwd+AIpu8XvA1zqTd+A==" }, "sc-simple-broker": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-2.1.1.tgz", - "integrity": "sha512-m6JM2dxP6G9mjEVC2VEDWt7SG/uBLerJs1U193WOd7pZKmbZhcVlhuw0IdJcs7lDwGm0jlX6DA1LbEn0yUCQLA==", + "version": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-2.1.1.tgz", + "integrity": "sha1-g0HPaoDjhK2aiHfMPXOYAoqVU/A=", "requires": { - "sc-channel": "1.2.0" + "sc-channel": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz" } }, "sc-uws": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/sc-uws/-/sc-uws-10.0.1.tgz", - "integrity": "sha512-sLk//KBp/4Ao/7cVqwU3DbOYRZZfru9Izkc03Hlmvs7aydFoGM6+tMhWDrrhjwTUM9tXdjZldrS7mv5yzxsRmA==" + "version": "10.148.0", + "resolved": "https://registry.npmjs.org/sc-uws/-/sc-uws-10.148.0.tgz", + "integrity": "sha1-OFZXma6WHlTM2kWUeihWyrSMj20=" }, "socketcluster-client": { "version": "11.2.0", @@ -467,16 +443,16 @@ "integrity": "sha512-6yRBmS5aGWwZzCxvIU5tfTvFRUcJmQ9Mfh9vi07TDda7+h5OQ77ZHFYqzfjVQvEjaZm5D1Ja/tgPd04MkpPIwA==", "dev": true, "requires": { - "base-64": "0.1.0", - "clone": "2.1.1", + "base-64": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "clone": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", "component-emitter": "1.2.1", - "linked-list": "0.1.0", - "querystring": "0.2.0", - "sc-channel": "1.2.0", - "sc-errors": "1.4.0", + "linked-list": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz", + "querystring": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "sc-channel": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", + "sc-errors": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.0.tgz", "sc-formatter": "3.0.2", - "uuid": "3.2.1", - "ws": "5.1.1" + "uuid": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "ws": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz" } }, "supports-color": { @@ -489,9 +465,8 @@ } }, "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + "version": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha1-EsUou51Y0LkmXZovbw/ovhf/HxQ=" }, "wrappy": { "version": "1.0.2", @@ -500,16 +475,14 @@ "dev": true }, "ws": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz", - "integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==", + "version": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz", + "integrity": "sha1-HUNwRolxGsGUL9Lyg+OPglxLi5U=", "requires": { - "async-limiter": "1.0.0" + "async-limiter": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz" } }, "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "version": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" } } diff --git a/package.json b/package.json index 11085d6..6d2095c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "13.0.0", + "version": "13.1.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { @@ -17,7 +17,7 @@ "sc-formatter": "^3.0.2", "sc-simple-broker": "^2.1.1", "uuid": "3.2.1", - "sc-uws": "^10.0.1", + "sc-uws": "~10.148.0", "ws": "5.1.1" }, "devDependencies": { From 9c32cd95c565bf2986bdcc9ca381edeced033dfa Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 10 May 2018 22:37:52 +0200 Subject: [PATCH 041/265] Move test case to authentication category --- test/integration.js | 62 ++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/test/integration.js b/test/integration.js index af8749e..0fc76c5 100644 --- a/test/integration.js +++ b/test/integration.js @@ -541,6 +541,37 @@ describe('Integration tests', function () { }); }); }); + + it('The verifyToken method of the authEngine receives correct params', function (done) { + global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); + + portNumber++; + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + server.setAuthEngine({ + verifyToken: function (signedAuthToken, verificationKey, verificationOptions, callback) { + setTimeout(function () { + assert.equal(signedAuthToken, validSignedAuthTokenBob); + assert.equal(verificationKey, serverOptions.authKey); + assert.notEqual(verificationOptions, null); + assert.notEqual(verificationOptions.socket, null); + assert.equal(typeof callback, 'function'); + callback(null, {}); + done(); + }, 500) + } + }); + server.on('connection', connectionHandler); + server.on('ready', function () { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + }); + }); }); describe('Event flow', function () { @@ -1040,37 +1071,6 @@ describe('Integration tests', function () { }); }); - it('The verifyToken method of the authEngine receives correct params', function (done) { - global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); - - portNumber++; - server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey, - wsEngine: WS_ENGINE - }); - server.setAuthEngine({ - verifyToken: function (signedAuthToken, verificationKey, verificationOptions, callback) { - setTimeout(function () { - assert.equal(signedAuthToken, validSignedAuthTokenBob); - assert.equal(verificationKey, serverOptions.authKey); - assert.notEqual(verificationOptions, null); - assert.notEqual(verificationOptions.socket, null); - assert.equal(typeof callback, 'function'); - callback(null, {}); - done(); - }, 500) - } - }); - server.on('connection', connectionHandler); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - }); - }); - it('The close event should trigger when the socket loses the connection after the handshake', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { From 1b76f533a21e95a88f8fb679633b5f4c05325080 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 10 May 2018 22:39:39 +0200 Subject: [PATCH 042/265] v13.1.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 60a47c9..f8c93e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "13.1.0", + "version": "13.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6d2095c..3ad278e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "13.1.0", + "version": "13.1.1", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 46b43428477086f1458e852cf1a71fca8d8b1d15 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 19 May 2018 11:03:18 +0200 Subject: [PATCH 043/265] Bump sc-simple-broker --- package-lock.json | 17 +++++++++-------- package.json | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index f8c93e8..0deb639 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ }, "async-limiter": { "version": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=" + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" }, "balanced-match": { "version": "1.0.0", @@ -373,7 +373,7 @@ }, "ms": { "version": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=" + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, "once": { "version": "1.4.0", @@ -411,14 +411,14 @@ }, "sc-channel": { "version": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", - "integrity": "sha1-2SCfOpHj+mlMZrARzlXErYwwh9k=", + "integrity": "sha512-M3gdq8PlKg0zWJSisWqAsMmTVxYRTpVRqw4CWAdKBgAfVKumFcTjoCV0hYu7lgUXccCtCD8Wk9VkkE+IXCxmZA==", "requires": { "component-emitter": "1.2.1" } }, "sc-errors": { "version": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.0.tgz", - "integrity": "sha1-JUWY5W1EoCYBHNCdrNkNy4ewaI0=" + "integrity": "sha512-h+jRWx/xRJmkPFDd0IltoTl/QJ6hAr5Y+3ZVeBQRLuWZKe+dHdf2uVwFp2OYqlLQ7GHht4y9eXG2zOf2Ik6PTw==" }, "sc-formatter": { "version": "3.0.2", @@ -426,8 +426,9 @@ "integrity": "sha512-9PbqYBpCq+OoEeRQ3QfFIGE6qwjjBcd2j7UjgDlhnZbtSnuGgHdcRklPKYGuYFH82V/dwd+AIpu8XvA1zqTd+A==" }, "sc-simple-broker": { - "version": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-2.1.1.tgz", - "integrity": "sha1-g0HPaoDjhK2aiHfMPXOYAoqVU/A=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-2.1.2.tgz", + "integrity": "sha512-8hbr47jLhrMecShZi6lunEeUPySkuLHlpg6G7g5jbBJQRrBiFiTuQdwk7KpMwAjLBh1qfaoku9Z+yWieOd5oLA==", "requires": { "sc-channel": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz" } @@ -466,7 +467,7 @@ }, "uuid": { "version": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha1-EsUou51Y0LkmXZovbw/ovhf/HxQ=" + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" }, "wrappy": { "version": "1.0.2", @@ -476,7 +477,7 @@ }, "ws": { "version": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz", - "integrity": "sha1-HUNwRolxGsGUL9Lyg+OPglxLi5U=", + "integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==", "requires": { "async-limiter": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz" } diff --git a/package.json b/package.json index 3ad278e..a8dde25 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "sc-auth": "^5.0.1", "sc-errors": "^1.4.0", "sc-formatter": "^3.0.2", - "sc-simple-broker": "^2.1.1", + "sc-simple-broker": "^2.1.2", "uuid": "3.2.1", "sc-uws": "~10.148.0", "ws": "5.1.1" From e1c10159931511e2298c56133845d7aca0aecccb Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 19 May 2018 11:04:10 +0200 Subject: [PATCH 044/265] v13.1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a8dde25..c90067a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "13.1.1", + "version": "13.1.2", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 5344585646ad866e508e98a406f3fd962ad15a77 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 30 Jun 2018 23:05:51 +0200 Subject: [PATCH 045/265] Bump mocha and sc-uws dependencies --- package-lock.json | 181 +++++++++++++--------------------------------- package.json | 4 +- 2 files changed, 54 insertions(+), 131 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0deb639..5fe1781 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "13.1.1", + "version": "13.1.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -45,9 +45,9 @@ } }, "browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, "buffer-equal-constant-time": { @@ -60,13 +60,10 @@ "dev": true }, "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "requires": { - "graceful-readlink": "1.0.1" - } + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true }, "component-emitter": { "version": "1.2.1", @@ -80,26 +77,26 @@ "dev": true }, "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { - "ms": "0.7.1" + "ms": "2.0.0" }, "dependencies": { "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true } } }, "diff": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", - "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, "ecdsa-sig-formatter": { @@ -123,9 +120,9 @@ "dev": true }, "glob": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.5.tgz", - "integrity": "sha1-tCAqaQmbu00pKnwblbZoK2fr3JU=", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { "fs.realpath": "1.0.0", @@ -136,22 +133,22 @@ "path-is-absolute": "1.0.1" } }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true - }, "growl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, "inflight": { @@ -170,12 +167,6 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, - "json3": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", - "dev": true - }, "jsonwebtoken": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.2.0.tgz", @@ -227,72 +218,15 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" }, - "lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "dev": true, - "requires": { - "lodash._basecopy": "3.0.1", - "lodash.keys": "3.1.2" - } - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basecreate": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", - "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" }, - "lodash.create": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", - "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", - "dev": true, - "requires": { - "lodash._baseassign": "3.2.0", - "lodash._basecreate": "3.0.3", - "lodash._isiterateecall": "3.0.9" - } - }, "lodash.includes": { "version": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, "lodash.isboolean": { "version": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" @@ -313,17 +247,6 @@ "version": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "3.9.1", - "lodash.isarguments": "3.1.0", - "lodash.isarray": "3.0.4" - } - }, "lodash.once": { "version": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" @@ -353,22 +276,22 @@ } }, "mocha": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.0.2.tgz", - "integrity": "sha1-Y6l/Phj00+ZZ1HphdnfQiYdFV/A=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", "dev": true, "requires": { - "browser-stdout": "1.3.0", - "commander": "2.9.0", - "debug": "2.2.0", - "diff": "1.4.0", + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", "escape-string-regexp": "1.0.5", - "glob": "7.0.5", - "growl": "1.9.2", - "json3": "3.3.2", - "lodash.create": "3.1.1", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "3.1.2" + "supports-color": "5.4.0" } }, "ms": { @@ -434,9 +357,9 @@ } }, "sc-uws": { - "version": "10.148.0", - "resolved": "https://registry.npmjs.org/sc-uws/-/sc-uws-10.148.0.tgz", - "integrity": "sha1-OFZXma6WHlTM2kWUeihWyrSMj20=" + "version": "10.148.1", + "resolved": "https://registry.npmjs.org/sc-uws/-/sc-uws-10.148.1.tgz", + "integrity": "sha512-frMMhWhapvVLknOjyZJ+T1GMPgE6KJp2rIcTNhVj+Hyj827m3rmAnI0aHZcWmlNlyPEr95PA9Sm9A5RuD0fD4g==" }, "socketcluster-client": { "version": "11.2.0", @@ -457,12 +380,12 @@ } }, "supports-color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", - "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "1.0.0" + "has-flag": "3.0.0" } }, "uuid": { diff --git a/package.json b/package.json index c90067a..ffa5ef9 100644 --- a/package.json +++ b/package.json @@ -17,12 +17,12 @@ "sc-formatter": "^3.0.2", "sc-simple-broker": "^2.1.2", "uuid": "3.2.1", - "sc-uws": "~10.148.0", + "sc-uws": "~10.148.1", "ws": "5.1.1" }, "devDependencies": { "localStorage": "^1.0.3", - "mocha": "3.0.2", + "mocha": "5.2.0", "socketcluster-client": "^11.2.0" }, "scripts": { From 47a29630b1ecb1f06cbc6ec8286069cfaa9090f8 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 30 Jun 2018 23:57:11 +0200 Subject: [PATCH 046/265] v13.1.3 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5fe1781..e2976dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "13.1.2", + "version": "13.1.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ffa5ef9..b3f2aeb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "13.1.2", + "version": "13.1.3", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 6c0ef1f44f033c7fcb2d6e791b7d33494a144e61 Mon Sep 17 00:00:00 2001 From: Mega Date: Sat, 7 Jul 2018 13:04:07 +0500 Subject: [PATCH 047/265] add MIT license --- LICENSE | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a5848a5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2013-2018 SocketCluster.io + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From 0a460658925042bd4f1fd186c38c18ff80f8cf39 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 13 Aug 2018 16:27:31 +0200 Subject: [PATCH 048/265] Emit handshake event after binding cleanupSocket handlers --- scserver.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scserver.js b/scserver.js index b04f13b..0deb0df 100644 --- a/scserver.js +++ b/scserver.js @@ -398,11 +398,6 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { self.pendingClients[id] = scSocket; self.pendingClientsCount++; - // Emit event to signal that a socket handshake has been initiated. - // The _handshake event is for internal use (including third-party plugins) - this.emit('_handshake', scSocket); - this.emit('handshake', scSocket); - scSocket.on('#authenticate', function (signedAuthToken, respond) { self._processAuthToken(scSocket, signedAuthToken, function (err, isBadToken, oldState) { if (err) { @@ -588,6 +583,11 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { }); }); }); + + // Emit event to signal that a socket handshake has been initiated. + // The _handshake event is for internal use (including third-party plugins) + this.emit('_handshake', scSocket); + this.emit('handshake', scSocket); }; SCServer.prototype.close = function () { From 0319d6ba6980444e5ee1357071d3d9965e82fef0 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 13 Aug 2018 17:43:18 +0200 Subject: [PATCH 049/265] v13.1.4 --- package-lock.json | 41 +++++++++++++++++++++++++---------------- package.json | 2 +- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index e2976dc..0642942 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,17 @@ { "name": "socketcluster-server", - "version": "13.1.3", + "version": "13.1.4", "lockfileVersion": 1, "requires": true, "dependencies": { "async": { - "version": "https://registry.npmjs.org/async/-/async-2.3.0.tgz", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.3.0.tgz", "integrity": "sha1-EBPRBRBH3TIP4k5JTVxm7K9hR9k=", "requires": { "lodash": "4.17.5" } }, - "async-limiter": { - "version": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -27,7 +24,8 @@ "dev": true }, "base64id": { - "version": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" }, "base64url": { @@ -209,7 +207,8 @@ "dev": true }, "localStorage": { - "version": "https://registry.npmjs.org/localStorage/-/localStorage-1.0.3.tgz", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/localStorage/-/localStorage-1.0.3.tgz", "integrity": "sha1-5riaV7t2ChVqOMyH4PJVD27UE9g=", "dev": true }, @@ -329,7 +328,7 @@ "integrity": "sha512-UgTdvJZS17wkLfNNwj8/uObfACUiGPc5Jl2VmTPpwipasj1kO1Sr64VVhCBUz38j+f/9rBCKlzxCTwljxgX95Q==", "requires": { "jsonwebtoken": "8.2.0", - "sc-errors": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.0.tgz" + "sc-errors": "1.4.0" } }, "sc-channel": { @@ -340,7 +339,8 @@ } }, "sc-errors": { - "version": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.0.tgz", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.0.tgz", "integrity": "sha512-h+jRWx/xRJmkPFDd0IltoTl/QJ6hAr5Y+3ZVeBQRLuWZKe+dHdf2uVwFp2OYqlLQ7GHht4y9eXG2zOf2Ik6PTw==" }, "sc-formatter": { @@ -373,10 +373,10 @@ "linked-list": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz", "querystring": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "sc-channel": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", - "sc-errors": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.0.tgz", + "sc-errors": "1.4.0", "sc-formatter": "3.0.2", - "uuid": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "ws": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz" + "uuid": "3.2.1", + "ws": "5.1.1" } }, "supports-color": { @@ -389,7 +389,8 @@ } }, "uuid": { - "version": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" }, "wrappy": { @@ -399,10 +400,18 @@ "dev": true }, "ws": { - "version": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz", "integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==", "requires": { - "async-limiter": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz" + "async-limiter": "1.0.0" + }, + "dependencies": { + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + } } }, "xtend": { diff --git a/package.json b/package.json index b3f2aeb..3dd703f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "13.1.3", + "version": "13.1.4", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 4b4975c7f2e5079034751b5dbadd87c68cc5f417 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 13 Aug 2018 19:12:58 +0200 Subject: [PATCH 050/265] Fix code formatting --- scserversocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scserversocket.js b/scserversocket.js index 06a98e8..1a40886 100644 --- a/scserversocket.js +++ b/scserversocket.js @@ -236,7 +236,7 @@ SCServerSocket.prototype._onSCClose = function (code, data) { if (typeof data == 'object') { try { reasonString = JSON.stringify(data); - } catch(error) { + } catch (error) { reasonString = data.toString(); } } else { From 39068ccf1cecc7d8e4f60737268b6c7a12b0a190 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 13 Aug 2018 19:14:01 +0200 Subject: [PATCH 051/265] v13.1.5 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0642942..0a2dc03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "13.1.4", + "version": "13.1.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 3dd703f..ac793ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "13.1.4", + "version": "13.1.5", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 98b4178a6cc6e945788cd2038f368b45b163f513 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 20 Aug 2018 23:02:22 +0200 Subject: [PATCH 052/265] Make sure that WS path always has a leading and a trailing slash --- scserver.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scserver.js b/scserver.js index 0deb0df..a4c2ac4 100644 --- a/scserver.js +++ b/scserver.js @@ -93,7 +93,9 @@ var SCServer = function (options) { this.brokerEngine = opts.brokerEngine; this.appName = opts.appName || ''; this.middlewareEmitWarnings = opts.middlewareEmitWarnings; - this._path = opts.path; + + // Make sure there is always a leading and a trailing slash in the WS path. + this._path = opts.path.replace(/\/?$/, '/').replace(/^\/?/, '/'); this.isReady = false; this.brokerEngine.once('ready', function () { From 3d022099c38b099a3e8a4e2205c7d37f4d8ebec2 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 20 Aug 2018 23:07:21 +0200 Subject: [PATCH 053/265] v13.2.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0a2dc03..d4e3877 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "13.1.5", + "version": "13.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ac793ad..6383a8a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "13.1.5", + "version": "13.2.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From f0784672e01d2bcd7e6babec426808b5acb84c00 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 21 Aug 2018 23:05:22 +0200 Subject: [PATCH 054/265] Allow passing back custom errors from broker --- scserver.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/scserver.js b/scserver.js index a4c2ac4..6826b37 100644 --- a/scserver.js +++ b/scserver.js @@ -205,7 +205,7 @@ SCServer.prototype.setCodecEngine = function (codecEngine) { }; SCServer.prototype._handleServerError = function (error) { - if (typeof error == 'string') { + if (typeof error === 'string') { error = new ServerProtocolError(error); } this.emit('error', error); @@ -237,7 +237,7 @@ SCServer.prototype._subscribeSocket = function (socket, channelOptions, callback var channelName = channelOptions.channel; - if (typeof channelName != 'string') { + if (typeof channelName !== 'string') { callback && callback('Socket ' + socket.id + ' provided an invalid channel name'); return; } @@ -276,7 +276,7 @@ SCServer.prototype._unsubscribeSocketFromAllChannels = function (socket) { }; SCServer.prototype._unsubscribeSocket = function (socket, channel) { - if (typeof channel != 'string') { + if (typeof channel !== 'string') { throw new InvalidActionError('Socket ' + socket.id + ' tried to unsubscribe from an invalid channel name'); } if (!socket.channelSubscriptions[channel]) { @@ -428,7 +428,7 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { scSocket.on('#subscribe', function (channelOptions, res) { if (!channelOptions) { channelOptions = {}; - } else if (typeof channelOptions == 'string') { + } else if (typeof channelOptions === 'string') { channelOptions = { channel: channelOptions }; @@ -675,7 +675,7 @@ SCServer.prototype.verifyHandshake = function (info, cb) { }; SCServer.prototype._isPrivateTransmittedEvent = function (event) { - return typeof event == 'string' && event.indexOf('#') == 0; + return typeof event === 'string' && event.indexOf('#') === 0; }; SCServer.prototype.verifyInboundEvent = function (socket, eventName, eventData, cb) { @@ -783,7 +783,6 @@ SCServer.prototype._passThroughMiddleware = function (options, cb) { } self.exchange.publish(request.channel, request.data, function (err) { if (err) { - err = new BrokerError(err); self.emit('warning', err); } cb(err, eventData, request.ackData); From 51e8eed5e124b34631e57819492cab914bdde4ff Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 21 Aug 2018 23:30:06 +0200 Subject: [PATCH 055/265] v14.0.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index d4e3877..2629c06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "13.2.0", + "version": "14.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6383a8a..e12817f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "13.2.0", + "version": "14.0.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 5e2ad9903426a10b7665edc32b8121365b384d02 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 31 Aug 2018 02:02:16 +0200 Subject: [PATCH 056/265] Bump socketcluster-client for tests --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e12817f..04b5b3f 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "devDependencies": { "localStorage": "^1.0.3", "mocha": "5.2.0", - "socketcluster-client": "^11.2.0" + "socketcluster-client": "^14.1.0" }, "scripts": { "test": "mocha --reporter spec --timeout 3000 --slow 3000" From 63135ac0811d18a03517b217d3ea0055a4516991 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 31 Aug 2018 02:06:21 +0200 Subject: [PATCH 057/265] v14.0.1 --- package-lock.json | 156 ++++++++++++++++++++++++++-------------------- package.json | 2 +- 2 files changed, 90 insertions(+), 68 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2629c06..31906cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "14.0.0", + "version": "14.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -18,20 +18,11 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, - "base-64": { - "version": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", - "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=", - "dev": true - }, "base64id": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" }, - "base64url": { - "version": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", - "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=" - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -49,14 +40,10 @@ "dev": true }, "buffer-equal-constant-time": { - "version": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, - "clone": { - "version": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", - "dev": true - }, "commander": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", @@ -98,10 +85,10 @@ "dev": true }, "ecdsa-sig-formatter": { - "version": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz", - "integrity": "sha1-S8kmJ07Dtau1AW5+HWCSGsJisqE=", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", + "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", "requires": { - "base64url": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", "safe-buffer": "5.1.1" } }, @@ -170,42 +157,37 @@ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.2.0.tgz", "integrity": "sha512-1Wxh8ADP3cNyPl8tZ95WtraHXCAyXupgc0AhMHjU9er98BV+UcKsO7OJUjfhIu0Uba9A40n1oSx8dbJYrm+EoQ==", "requires": { - "jws": "https://registry.npmjs.org/jws/-/jws-3.1.4.tgz", - "lodash.includes": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "lodash.isboolean": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "lodash.isinteger": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "lodash.isnumber": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "lodash.isplainobject": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "lodash.isstring": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "lodash.once": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "ms": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "xtend": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + "jws": "3.1.5", + "lodash.includes": "4.3.0", + "lodash.isboolean": "3.0.3", + "lodash.isinteger": "4.0.4", + "lodash.isnumber": "3.0.3", + "lodash.isplainobject": "4.0.6", + "lodash.isstring": "4.0.1", + "lodash.once": "4.1.1", + "ms": "2.1.1", + "xtend": "4.0.1" } }, "jwa": { - "version": "https://registry.npmjs.org/jwa/-/jwa-1.1.5.tgz", - "integrity": "sha1-oFUs4CIHQs1S4VN3SjKQXDDnVuU=", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", + "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", "requires": { - "base64url": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", - "buffer-equal-constant-time": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "ecdsa-sig-formatter": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz", + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.10", "safe-buffer": "5.1.1" } }, "jws": { - "version": "https://registry.npmjs.org/jws/-/jws-3.1.4.tgz", - "integrity": "sha1-+ei5M46KhHJ31kRLFGT2GIDgUKI=", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", + "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", "requires": { - "base64url": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", - "jwa": "https://registry.npmjs.org/jwa/-/jwa-1.1.5.tgz", + "jwa": "1.1.6", "safe-buffer": "5.1.1" } }, - "linked-list": { - "version": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz", - "integrity": "sha1-eYsP+X0bkqT9CEgPVa6k6dSdN78=", - "dev": true - }, "localStorage": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/localStorage/-/localStorage-1.0.3.tgz", @@ -223,31 +205,38 @@ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" }, "lodash.includes": { - "version": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" }, "lodash.isboolean": { - "version": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" }, "lodash.isinteger": { - "version": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" }, "lodash.isnumber": { - "version": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" }, "lodash.isplainobject": { - "version": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" }, "lodash.isstring": { - "version": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, "lodash.once": { - "version": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" }, "minimatch": { @@ -294,7 +283,8 @@ } }, "ms": { - "version": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, "once": { @@ -312,11 +302,6 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, - "querystring": { - "version": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", @@ -332,7 +317,8 @@ } }, "sc-channel": { - "version": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", "integrity": "sha512-M3gdq8PlKg0zWJSisWqAsMmTVxYRTpVRqw4CWAdKBgAfVKumFcTjoCV0hYu7lgUXccCtCD8Wk9VkkE+IXCxmZA==", "requires": { "component-emitter": "1.2.1" @@ -353,7 +339,7 @@ "resolved": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-2.1.2.tgz", "integrity": "sha512-8hbr47jLhrMecShZi6lunEeUPySkuLHlpg6G7g5jbBJQRrBiFiTuQdwk7KpMwAjLBh1qfaoku9Z+yWieOd5oLA==", "requires": { - "sc-channel": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz" + "sc-channel": "1.2.0" } }, "sc-uws": { @@ -362,21 +348,56 @@ "integrity": "sha512-frMMhWhapvVLknOjyZJ+T1GMPgE6KJp2rIcTNhVj+Hyj827m3rmAnI0aHZcWmlNlyPEr95PA9Sm9A5RuD0fD4g==" }, "socketcluster-client": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-11.2.0.tgz", - "integrity": "sha512-6yRBmS5aGWwZzCxvIU5tfTvFRUcJmQ9Mfh9vi07TDda7+h5OQ77ZHFYqzfjVQvEjaZm5D1Ja/tgPd04MkpPIwA==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-14.1.0.tgz", + "integrity": "sha512-Uzux62MQVK5ZmYu4I01Svsfiwf1c14i40+G1WlWTdFvVSOn0XZIX5J5FjiJEIRXe9HE/lwoVW0Gc8T2D5kfgBQ==", "dev": true, "requires": { - "base-64": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", - "clone": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "base-64": "0.1.0", + "clone": "2.1.1", "component-emitter": "1.2.1", - "linked-list": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz", - "querystring": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "sc-channel": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", + "linked-list": "0.1.0", + "querystring": "0.2.0", + "sc-channel": "1.2.0", "sc-errors": "1.4.0", "sc-formatter": "3.0.2", "uuid": "3.2.1", "ws": "5.1.1" + }, + "dependencies": { + "base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=", + "dev": true + }, + "clone": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "dev": true + }, + "linked-list": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz", + "integrity": "sha1-eYsP+X0bkqT9CEgPVa6k6dSdN78=", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "sc-channel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", + "integrity": "sha512-M3gdq8PlKg0zWJSisWqAsMmTVxYRTpVRqw4CWAdKBgAfVKumFcTjoCV0hYu7lgUXccCtCD8Wk9VkkE+IXCxmZA==", + "dev": true, + "requires": { + "component-emitter": "1.2.1" + } + } } }, "supports-color": { @@ -415,7 +436,8 @@ } }, "xtend": { - "version": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" } } diff --git a/package.json b/package.json index 04b5b3f..36bacee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "14.0.0", + "version": "14.0.1", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From a2936fc19287768a3d34eae850fef8bb130aae96 Mon Sep 17 00:00:00 2001 From: Akuukis Date: Wed, 19 Sep 2018 20:33:21 +0300 Subject: [PATCH 058/265] fix: compatibility with webpack dynamic import --- scserver.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/scserver.js b/scserver.js index 6826b37..e4c4b87 100644 --- a/scserver.js +++ b/scserver.js @@ -103,7 +103,27 @@ var SCServer = function (options) { EventEmitter.prototype.emit.call(self, 'ready'); }); - var wsEngine = require(opts.wsEngine); + // Provide explicit strings in order to make dynamic `require` compatible with Webpack. + var wsEngine = undefined; + try { + switch(opts.wsEngine) { + case('ws'): { + wsEngine = require('ws'); + break; + } + case('uws'): { + wsEngine = require('uws'); + break; + } + default: { + // This won't work in Webpack, but will work elsewhere. + wsEngine = require(opts.wsEngine); + break; + } + } + } catch(e) { + if(!e instanceof InvalidOptionsError) throw new InvalidOptionsError('wsEngine is not installed.') + } if (!wsEngine || !wsEngine.Server) { throw new InvalidOptionsError('The wsEngine option must be a path or module name which points ' + 'to a valid WebSocket engine module with a compatible interface'); From 7cb34cbac9479addb42b0635494e4a93a082e7ce Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 19 Sep 2018 23:47:04 +0200 Subject: [PATCH 059/265] Add signedAuthToken property to socket once token is signed after socket.setAuthToken, also emit a new authTokenSigned event --- scserversocket.js | 9 ++++++++- test/integration.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/scserversocket.js b/scserversocket.js index 1a40886..37ce769 100644 --- a/scserversocket.js +++ b/scserversocket.js @@ -28,6 +28,7 @@ var SCServerSocket = function (id, server, socket) { 'message': 1, 'error': 1, 'authStateChange': 1, + 'authTokenSigned': 1, 'authenticate': 1, 'deauthenticate': 1, 'badAuthToken': 1, @@ -451,6 +452,8 @@ SCServerSocket.prototype.setAuthToken = function (data, options, callback) { options.algorithm = defaultSignatureOptions.algorithm; } + this.authToken = authToken; + this.server.auth.signToken(authToken, this.server.signatureKey, options, function (err, signedToken) { if (err) { Emitter.prototype.emit.call(self, 'error', err); @@ -461,11 +464,14 @@ SCServerSocket.prototype.setAuthToken = function (data, options, callback) { var tokenData = { token: signedToken }; + if (self.authToken === authToken) { + self.signedAuthToken = signedToken; + Emitter.prototype.emit.call(self, 'authTokenSigned', signedToken); + } self.emit('#setAuthToken', tokenData, callback); } }); - this.authToken = authToken; this.triggerAuthenticationEvents(oldState); }; @@ -476,6 +482,7 @@ SCServerSocket.prototype.getAuthToken = function () { SCServerSocket.prototype.deauthenticateSelf = function () { var oldState = this.authState; var oldToken = this.authToken; + this.signedAuthToken = null; this.authToken = null; this.authState = this.UNAUTHENTICATED; if (oldState != this.UNAUTHENTICATED) { diff --git a/test/integration.js b/test/integration.js index 0fc76c5..537ba09 100644 --- a/test/integration.js +++ b/test/integration.js @@ -542,6 +542,50 @@ describe('Integration tests', function () { }); }); + it('Should trigger an authTokenSigned event and socket.signedAuthToken should be set after calling the socket.setAuthToken method', function (done) { + portNumber++; + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, + authSignAsync: true + }); + + var authTokenSignedEventEmitted = false; + + server.on('connection', function (socket) { + socket.on('authTokenSigned', function (signedAuthToken) { + authTokenSignedEventEmitted = true; + assert.notEqual(signedAuthToken, null); + assert.equal(signedAuthToken, socket.signedAuthToken); + }); + socket.on('login', function (userDetails, respond) { + if (allowedUsers[userDetails.username]) { + socket.setAuthToken(userDetails, {async: true}); + respond(); + } else { + var err = new Error('Failed to login'); + err.name = 'FailedLoginError'; + respond(err); + } + }); + }); + + server.on('ready', function () { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + client.once('connect', function (statusA) { + client.emit('login', {username: 'bob'}); + }); + setTimeout(function () { + assert.equal(authTokenSignedEventEmitted, true); + done(); + }, 100); + }); + }); + it('The verifyToken method of the authEngine receives correct params', function (done) { global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); From 6f69c63859f4220001fa773c9b2c662955317fe3 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 20 Sep 2018 00:56:05 +0200 Subject: [PATCH 060/265] v14.1.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 31906cf..baf540a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "14.0.1", + "version": "14.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 36bacee..e6a9bfd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "14.0.1", + "version": "14.1.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 77be9df7d62a8a6a17f768f6b8c5e363f9553513 Mon Sep 17 00:00:00 2001 From: Akuukis Date: Thu, 20 Sep 2018 11:40:11 +0300 Subject: [PATCH 061/265] feat: wsEngine accepts string or module --- scserver.js | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/scserver.js b/scserver.js index e4c4b87..ddb9ff7 100644 --- a/scserver.js +++ b/scserver.js @@ -103,27 +103,7 @@ var SCServer = function (options) { EventEmitter.prototype.emit.call(self, 'ready'); }); - // Provide explicit strings in order to make dynamic `require` compatible with Webpack. - var wsEngine = undefined; - try { - switch(opts.wsEngine) { - case('ws'): { - wsEngine = require('ws'); - break; - } - case('uws'): { - wsEngine = require('uws'); - break; - } - default: { - // This won't work in Webpack, but will work elsewhere. - wsEngine = require(opts.wsEngine); - break; - } - } - } catch(e) { - if(!e instanceof InvalidOptionsError) throw new InvalidOptionsError('wsEngine is not installed.') - } + var wsEngine = typeof opts.wsEngine === 'string' ? require(opts.wsEngine) : opts.wsEngine; if (!wsEngine || !wsEngine.Server) { throw new InvalidOptionsError('The wsEngine option must be a path or module name which points ' + 'to a valid WebSocket engine module with a compatible interface'); From a52297ad85ab4904b678005631a1affaa7801db5 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 20 Sep 2018 22:07:47 +0200 Subject: [PATCH 062/265] v14.2.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index baf540a..94de466 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "14.1.0", + "version": "14.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e6a9bfd..4273020 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "14.1.0", + "version": "14.2.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 089a992f39335f1b741fe50ee75692983d5c9dd6 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 21 Sep 2018 21:35:49 +0200 Subject: [PATCH 063/265] Remove channel from socket.channelSubscriptions when socket.kickOut is used --- scserversocket.js | 14 ++++--- test/integration.js | 93 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 5 deletions(-) diff --git a/scserversocket.js b/scserversocket.js index 37ce769..fba64b1 100644 --- a/scserversocket.js +++ b/scserversocket.js @@ -503,13 +503,17 @@ SCServerSocket.prototype.deauthenticate = function (callback) { }; SCServerSocket.prototype.kickOut = function (channel, message, callback) { + var self = this; + if (channel == null) { - for (var i in this.channelSubscriptions) { - if (this.channelSubscriptions.hasOwnProperty(i)) { - this.emit('#kickOut', {message: message, channel: i}); - } - } + Object.keys(this.channelSubscriptions).forEach(function (channelName) { + delete self.channelSubscriptions[channelName]; + self.channelSubscriptionsCount--; + self.emit('#kickOut', {message: message, channel: channelName}); + }); } else { + delete this.channelSubscriptions[channel]; + this.channelSubscriptionsCount--; this.emit('#kickOut', {message: message, channel: channel}); } this.server.brokerEngine.unsubscribeSocket(this, channel, callback); diff --git a/test/integration.js b/test/integration.js index 537ba09..2b1b1b3 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1492,6 +1492,99 @@ describe('Integration tests', function () { }, 200); }); }); + + it('Socket channelSubscriptions and channelSubscriptionsCount should update when socket.kickOut(channel) is called', function (done) { + portNumber++; + + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + + var errorList = []; + var serverSocket; + var wasKickOutCalled = false; + + server.on('connection', function (socket) { + serverSocket = socket; + socket.on('error', function (err) { + errorList.push(err); + }); + socket.on('subscribe', function (channelName) { + if (channelName === 'foo') { + setTimeout(function () { + wasKickOutCalled = true; + socket.kickOut('foo', 'Socket was kicked out of the channel'); + }, 50); + } + }); + }); + + server.on('ready', function () { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + + client.subscribe('foo'); + + setTimeout(function () { + assert.equal(errorList.length, 0); + assert.equal(wasKickOutCalled, true); + assert.equal(serverSocket.channelSubscriptionsCount, 0); + assert.equal(Object.keys(serverSocket.channelSubscriptions).length, 0); + done(); + }, 100); + }); + }); + + it('Socket channelSubscriptions and channelSubscriptionsCount should update when socket.kickOut() is called without arguments', function (done) { + portNumber++; + + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + + var errorList = []; + var serverSocket; + var wasKickOutCalled = false; + + server.on('connection', function (socket) { + serverSocket = socket; + socket.on('error', function (err) { + errorList.push(err); + }); + socket.on('subscribe', function (channelName) { + if (socket.channelSubscriptionsCount === 2) { + setTimeout(function () { + wasKickOutCalled = true; + socket.kickOut(); + }, 50); + } + }); + }); + + server.on('ready', function () { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + + client.subscribe('foo'); + client.subscribe('bar'); + + setTimeout(function () { + assert.equal(errorList.length, 0); + assert.equal(wasKickOutCalled, true); + assert.equal(serverSocket.channelSubscriptionsCount, 0); + assert.equal(Object.keys(serverSocket.channelSubscriptions).length, 0); + done(); + }, 100); + }); + }); }); describe('Middleware', function () { From dc95e7b4a044f1c30e3049839c78b58d88241e8f Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 21 Sep 2018 22:16:43 +0200 Subject: [PATCH 064/265] Improve test categories --- test/integration.js | 812 ++++++++++++++++++++++---------------------- 1 file changed, 410 insertions(+), 402 deletions(-) diff --git a/test/integration.js b/test/integration.js index 2b1b1b3..f8ea22c 100644 --- a/test/integration.js +++ b/test/integration.js @@ -152,7 +152,7 @@ describe('Integration tests', function () { }); }); - describe('Authentication', function () { + describe('Socket authentication', function () { it('Should not send back error if JWT is not provided in handshake', function (done) { client = socketCluster.connect(clientOptions); client.once('connect', function (status) { @@ -616,81 +616,6 @@ describe('Integration tests', function () { }); }); }); - }); - - describe('Event flow', function () { - it('Should support subscription batching', function (done) { - portNumber++; - server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey, - wsEngine: WS_ENGINE - }); - server.on('connection', function (socket) { - connectionHandler(socket); - var isFirstMessage = true; - socket.on('message', function (rawMessage) { - if (isFirstMessage) { - var data = JSON.parse(rawMessage); - // All 20 subscriptions should arrive as a single message. - assert.equal(data.length, 20); - isFirstMessage = false; - } - }); - }); - - var subscribeMiddlewareCounter = 0; - // Each subscription should pass through the middleware individually, even - // though they were sent as a batch/array. - server.addMiddleware(server.MIDDLEWARE_SUBSCRIBE, function (req, next) { - subscribeMiddlewareCounter++; - assert.equal(req.channel.indexOf('my-channel-'), 0); - if (req.channel == 'my-channel-10') { - assert.equal(JSON.stringify(req.data), JSON.stringify({foo: 123})); - } else if (req.channel == 'my-channel-12') { - // Block my-channel-12 - var err = new Error('You cannot subscribe to channel 12'); - err.name = 'UnauthorizedSubscribeError'; - next(err); - return; - } - next(); - }); - - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - var channelList = []; - for (var i = 0; i < 20; i++) { - var subscribeOptions = { - batch: true - }; - if (i == 10) { - subscribeOptions.data = {foo: 123}; - } - channelList.push( - client.subscribe('my-channel-' + i, subscribeOptions) - ); - } - channelList[12].on('subscribe', function (err) { - throw new Error('The my-channel-12 channel should have been blocked by MIDDLEWARE_SUBSCRIBE'); - }); - channelList[12].on('subscribeFail', function (err) { - assert.notEqual(err, null); - assert.equal(err.name, 'UnauthorizedSubscribeError'); - }); - channelList[19].watch(function (data) { - assert.equal(data, 'Hello!'); - assert.equal(subscribeMiddlewareCounter, 20); - done(); - }); - channelList[0].on('subscribe', function () { - client.publish('my-channel-19', 'Hello!'); - }); - }); - }); it('Should remove client data from the server when client disconnects before authentication process finished', function (done) { portNumber++; @@ -732,78 +657,55 @@ describe('Integration tests', function () { }, 1000); }); }); + }); - it('Server socket destroy should disconnect the socket', function (done) { + describe('Socket handshake', function () { + it('Exchange is attached to socket before the handshake event is triggered', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); - server.on('connection', function (socket) { - setTimeout(function () { - socket.destroy(1000, 'Custom reason'); - }, 100); - }); + server.on('connection', connectionHandler); + server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, port: portNumber, multiplex: false }); - client.on('disconnect', function (code, reason) { - assert.equal(code, 1000); - assert.equal(reason, 'Custom reason'); - assert.equal(server.clientsCount, 0); - assert.equal(server.pendingClientsCount, 0); - done(); - }); - }); - }); - - it('Server socket destroy should set the active property on the socket to false', function (done) { - portNumber++; - server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey, - wsEngine: WS_ENGINE - }); - var serverSocket; + server.once('handshake', function (socket) { + assert.notEqual(socket.exchange, null); + }); - server.on('connection', function (socket) { - serverSocket = socket; - assert.equal(socket.active, true); setTimeout(function () { - socket.destroy(); - }, 100); - }); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - client.on('disconnect', function (code, reason) { - assert.equal(serverSocket.active, false); done(); - }); + }, 300); }); }); + }); - it('Client should not be able to subscribe to a channel before the handshake has completed', function (done) { + describe('Socket connection', function () { + it('Server-side socket connect event and server connection event should trigger', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); - server.setAuthEngine({ - verifyToken: function (signedAuthToken, verificationKey, verificationOptions, callback) { - setTimeout(function () { - callback(null, {}); - }, 500) - } - }); + + var connectionEmitted = false; + var connectionStatus; + server.on('connection', connectionHandler); + server.once('connection', function (socket, status) { + connectionEmitted = true; + connectionStatus = status; + // Modify the status object and make sure that it doesn't get modified + // on the client. + status.foo = 123; + }); server.on('ready', function () { client = socketCluster.connect({ hostname: clientOptions.hostname, @@ -811,36 +713,65 @@ describe('Integration tests', function () { multiplex: false }); - var isSubscribed = false; - var error; + var connectEmitted = false; + var _connectEmitted = false; + var connectStatus; + var socketId; - server.on('subscription', function (socket, channel) { - isSubscribed = true; + server.once('handshake', function (socket) { + socket.once('connect', function (status) { + socketId = socket.id; + connectEmitted = true; + connectStatus = status; + }); + socket.once('_connect', function () { + _connectEmitted = true; + }); }); - // Hack to capture the error without relying on the standard client flow. - client.transport._callbackMap[2] = { - event: '#subscribe', - data: {"channel":"someChannel"}, - callback: function (err) { - error = err; - } - }; + var clientConnectEmitted = false; + var clientConnectStatus = false; - // Trick the server by sending a fake subscribe before the handshake is done. - client.transport.socket.on('open', function () { - client.send('{"event":"#subscribe","data":{"channel":"someChannel"},"cid":2}'); + client.once('connect', function (status) { + clientConnectEmitted = true; + clientConnectStatus = status; }); setTimeout(function () { - assert.equal(isSubscribed, false); - assert.notEqual(error, null); - assert.equal(error.name, 'InvalidActionError'); + assert.equal(connectEmitted, true); + assert.equal(_connectEmitted, true); + assert.equal(connectionEmitted, true); + assert.equal(clientConnectEmitted, true); + + assert.notEqual(connectionStatus, null); + assert.equal(connectionStatus.id, socketId); + assert.equal(connectionStatus.pingTimeout, server.pingTimeout); + assert.equal(connectionStatus.authError, null); + assert.equal(connectionStatus.isAuthenticated, false); + + assert.notEqual(connectStatus, null); + assert.equal(connectStatus.id, socketId); + assert.equal(connectStatus.pingTimeout, server.pingTimeout); + assert.equal(connectStatus.authError, null); + assert.equal(connectStatus.isAuthenticated, false); + + assert.notEqual(clientConnectStatus, null); + assert.equal(clientConnectStatus.id, socketId); + assert.equal(clientConnectStatus.pingTimeout, server.pingTimeout); + assert.equal(clientConnectStatus.authError, null); + assert.equal(clientConnectStatus.isAuthenticated, false); + assert.equal(clientConnectStatus.foo, null); + // Client socket status should be a clone of server socket status; not + // a reference to the same object. + assert.notEqual(clientConnectStatus.foo, connectStatus.foo); + done(); - }, 1000); + }, 300); }); }); + }); + describe('Socket disconnection', function () { it('Server-side socket disconnect event should not trigger if the socket did not complete the handshake; instead, it should trigger connectAbort', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { @@ -977,89 +908,7 @@ describe('Integration tests', function () { assert.equal(serverDisconnected, true); assert.equal(serverSocketAborted, false); done(); - }, 1000); - }); - }); - - it('Server-side socket connect event and server connection event should trigger', function (done) { - portNumber++; - server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey, - wsEngine: WS_ENGINE - }); - - var connectionEmitted = false; - var connectionStatus; - - server.on('connection', connectionHandler); - server.once('connection', function (socket, status) { - connectionEmitted = true; - connectionStatus = status; - // Modify the status object and make sure that it doesn't get modified - // on the client. - status.foo = 123; - }); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - - var connectEmitted = false; - var _connectEmitted = false; - var connectStatus; - var socketId; - - server.once('handshake', function (socket) { - socket.once('connect', function (status) { - socketId = socket.id; - connectEmitted = true; - connectStatus = status; - }); - socket.once('_connect', function () { - _connectEmitted = true; - }); - }); - - var clientConnectEmitted = false; - var clientConnectStatus = false; - - client.once('connect', function (status) { - clientConnectEmitted = true; - clientConnectStatus = status; - }); - - setTimeout(function () { - assert.equal(connectEmitted, true); - assert.equal(_connectEmitted, true); - assert.equal(connectionEmitted, true); - assert.equal(clientConnectEmitted, true); - - assert.notEqual(connectionStatus, null); - assert.equal(connectionStatus.id, socketId); - assert.equal(connectionStatus.pingTimeout, server.pingTimeout); - assert.equal(connectionStatus.authError, null); - assert.equal(connectionStatus.isAuthenticated, false); - - assert.notEqual(connectStatus, null); - assert.equal(connectStatus.id, socketId); - assert.equal(connectStatus.pingTimeout, server.pingTimeout); - assert.equal(connectStatus.authError, null); - assert.equal(connectStatus.isAuthenticated, false); - - assert.notEqual(clientConnectStatus, null); - assert.equal(clientConnectStatus.id, socketId); - assert.equal(clientConnectStatus.pingTimeout, server.pingTimeout); - assert.equal(clientConnectStatus.authError, null); - assert.equal(clientConnectStatus.isAuthenticated, false); - assert.equal(clientConnectStatus.foo, null); - // Client socket status should be a clone of server socket status; not - // a reference to the same object. - assert.notEqual(clientConnectStatus.foo, connectStatus.foo); - - done(); - }, 300); + }, 1000); }); }); @@ -1166,15 +1015,45 @@ describe('Integration tests', function () { }, 300); }); }); + }); - it('Exchange is attached to socket before the handshake event is triggered', function (done) { + describe('Socket pub/sub', function () { + it('Should support subscription batching', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + server.on('connection', function (socket) { + connectionHandler(socket); + var isFirstMessage = true; + socket.on('message', function (rawMessage) { + if (isFirstMessage) { + var data = JSON.parse(rawMessage); + // All 20 subscriptions should arrive as a single message. + assert.equal(data.length, 20); + isFirstMessage = false; + } + }); + }); - server.on('connection', connectionHandler); + var subscribeMiddlewareCounter = 0; + // Each subscription should pass through the middleware individually, even + // though they were sent as a batch/array. + server.addMiddleware(server.MIDDLEWARE_SUBSCRIBE, function (req, next) { + subscribeMiddlewareCounter++; + assert.equal(req.channel.indexOf('my-channel-'), 0); + if (req.channel == 'my-channel-10') { + assert.equal(JSON.stringify(req.data), JSON.stringify({foo: 123})); + } else if (req.channel == 'my-channel-12') { + // Block my-channel-12 + var err = new Error('You cannot subscribe to channel 12'); + err.name = 'UnauthorizedSubscribeError'; + next(err); + return; + } + next(); + }); server.on('ready', function () { client = socketCluster.connect({ @@ -1182,14 +1061,84 @@ describe('Integration tests', function () { port: portNumber, multiplex: false }); + var channelList = []; + for (var i = 0; i < 20; i++) { + var subscribeOptions = { + batch: true + }; + if (i == 10) { + subscribeOptions.data = {foo: 123}; + } + channelList.push( + client.subscribe('my-channel-' + i, subscribeOptions) + ); + } + channelList[12].on('subscribe', function (err) { + throw new Error('The my-channel-12 channel should have been blocked by MIDDLEWARE_SUBSCRIBE'); + }); + channelList[12].on('subscribeFail', function (err) { + assert.notEqual(err, null); + assert.equal(err.name, 'UnauthorizedSubscribeError'); + }); + channelList[19].watch(function (data) { + assert.equal(data, 'Hello!'); + assert.equal(subscribeMiddlewareCounter, 20); + done(); + }); + channelList[0].on('subscribe', function () { + client.publish('my-channel-19', 'Hello!'); + }); + }); + }); - server.once('handshake', function (socket) { - assert.notEqual(socket.exchange, null); + it('Client should not be able to subscribe to a channel before the handshake has completed', function (done) { + portNumber++; + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + server.setAuthEngine({ + verifyToken: function (signedAuthToken, verificationKey, verificationOptions, callback) { + setTimeout(function () { + callback(null, {}); + }, 500) + } + }); + server.on('connection', connectionHandler); + server.on('ready', function () { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + + var isSubscribed = false; + var error; + + server.on('subscription', function (socket, channel) { + isSubscribed = true; + }); + + // Hack to capture the error without relying on the standard client flow. + client.transport._callbackMap[2] = { + event: '#subscribe', + data: {"channel":"someChannel"}, + callback: function (err) { + error = err; + } + }; + + // Trick the server by sending a fake subscribe before the handshake is done. + client.transport.socket.on('open', function () { + client.send('{"event":"#subscribe","data":{"channel":"someChannel"},"cid":2}'); }); setTimeout(function () { + assert.equal(isSubscribed, false); + assert.notEqual(error, null); + assert.equal(error.name, 'InvalidActionError'); done(); - }, 300); + }, 1000); }); }); @@ -1510,79 +1459,266 @@ describe('Integration tests', function () { socket.on('error', function (err) { errorList.push(err); }); - socket.on('subscribe', function (channelName) { - if (channelName === 'foo') { - setTimeout(function () { - wasKickOutCalled = true; - socket.kickOut('foo', 'Socket was kicked out of the channel'); - }, 50); - } + socket.on('subscribe', function (channelName) { + if (channelName === 'foo') { + setTimeout(function () { + wasKickOutCalled = true; + socket.kickOut('foo', 'Socket was kicked out of the channel'); + }, 50); + } + }); + }); + + server.on('ready', function () { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + + client.subscribe('foo'); + + setTimeout(function () { + assert.equal(errorList.length, 0); + assert.equal(wasKickOutCalled, true); + assert.equal(serverSocket.channelSubscriptionsCount, 0); + assert.equal(Object.keys(serverSocket.channelSubscriptions).length, 0); + done(); + }, 100); + }); + }); + + it('Socket channelSubscriptions and channelSubscriptionsCount should update when socket.kickOut() is called without arguments', function (done) { + portNumber++; + + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + + var errorList = []; + var serverSocket; + var wasKickOutCalled = false; + + server.on('connection', function (socket) { + serverSocket = socket; + socket.on('error', function (err) { + errorList.push(err); + }); + socket.on('subscribe', function (channelName) { + if (socket.channelSubscriptionsCount === 2) { + setTimeout(function () { + wasKickOutCalled = true; + socket.kickOut(); + }, 50); + } + }); + }); + + server.on('ready', function () { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + + client.subscribe('foo'); + client.subscribe('bar'); + + setTimeout(function () { + assert.equal(errorList.length, 0); + assert.equal(wasKickOutCalled, true); + assert.equal(serverSocket.channelSubscriptionsCount, 0); + assert.equal(Object.keys(serverSocket.channelSubscriptions).length, 0); + done(); + }, 100); + }); + }); + }); + + describe('Socket destruction', function () { + it('Server socket destroy should disconnect the socket', function (done) { + portNumber++; + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + + server.on('connection', function (socket) { + setTimeout(function () { + socket.destroy(1000, 'Custom reason'); + }, 100); + }); + server.on('ready', function () { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + client.on('disconnect', function (code, reason) { + assert.equal(code, 1000); + assert.equal(reason, 'Custom reason'); + assert.equal(server.clientsCount, 0); + assert.equal(server.pendingClientsCount, 0); + done(); + }); + }); + }); + + it('Server socket destroy should set the active property on the socket to false', function (done) { + portNumber++; + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + + var serverSocket; + + server.on('connection', function (socket) { + serverSocket = socket; + assert.equal(socket.active, true); + setTimeout(function () { + socket.destroy(); + }, 100); + }); + server.on('ready', function () { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + client.on('disconnect', function (code, reason) { + assert.equal(serverSocket.active, false); + done(); + }); + }); + }); + }); + + describe('Socket Ping/pong', function () { + describe('When when pingTimeoutDisabled is not set (false)', function () { + beforeEach('Launch server with ping options before start', function (done) { + portNumber++; + // Intentionally make pingInterval higher than pingTimeout, that + // way the client will never receive a ping or send back a pong. + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, + pingInterval: 2000, + pingTimeout: 500 + }); + server.on('ready', function () { + done(); }); }); - server.on('ready', function () { + afterEach('Shut down server afterwards', function (done) { + destroyTestCase(function () { + server.close(); + done(); + }); + }); + + it('Should disconnect socket if server does not receive a pong from client before timeout', function (done) { client = socketCluster.connect({ hostname: clientOptions.hostname, port: portNumber, multiplex: false }); - client.subscribe('foo'); + var serverWarning = null; + server.on('warning', function (err) { + serverWarning = err; + }); + + var serverDisconnectionCode = null; + server.on('disconnection', function (socket, code) { + serverDisconnectionCode = code; + }); + + var clientError = null; + client.on('error', function (err) { + clientError = err; + }); + + var clientDisconnectCode = null; + client.on('disconnect', function (code) { + clientDisconnectCode = code; + }); setTimeout(function () { - assert.equal(errorList.length, 0); - assert.equal(wasKickOutCalled, true); - assert.equal(serverSocket.channelSubscriptionsCount, 0); - assert.equal(Object.keys(serverSocket.channelSubscriptions).length, 0); + assert.notEqual(clientError, null); + assert.equal(clientError.name, 'SocketProtocolError'); + assert.equal(clientDisconnectCode, 4000); + + assert.notEqual(serverWarning, null); + assert.equal(serverWarning.name, 'SocketProtocolError'); + assert.equal(serverDisconnectionCode, 4001); done(); - }, 100); + }, 1000); }); }); - it('Socket channelSubscriptions and channelSubscriptionsCount should update when socket.kickOut() is called without arguments', function (done) { - portNumber++; - - server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey, - wsEngine: WS_ENGINE + describe('When when pingTimeoutDisabled is true', function () { + beforeEach('Launch server with ping options before start', function (done) { + portNumber++; + // Intentionally make pingInterval higher than pingTimeout, that + // way the client will never receive a ping or send back a pong. + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, + pingInterval: 2000, + pingTimeout: 500, + pingTimeoutDisabled: true + }); + server.on('ready', function () { + done(); + }); }); - var errorList = []; - var serverSocket; - var wasKickOutCalled = false; - - server.on('connection', function (socket) { - serverSocket = socket; - socket.on('error', function (err) { - errorList.push(err); - }); - socket.on('subscribe', function (channelName) { - if (socket.channelSubscriptionsCount === 2) { - setTimeout(function () { - wasKickOutCalled = true; - socket.kickOut(); - }, 50); - } + afterEach('Shut down server afterwards', function (done) { + destroyTestCase(function () { + server.close(); + done(); }); }); - server.on('ready', function () { + it('Should not disconnect socket if server does not receive a pong from client before timeout', function (done) { client = socketCluster.connect({ hostname: clientOptions.hostname, port: portNumber, - multiplex: false + multiplex: false, + pingTimeoutDisabled: true }); - client.subscribe('foo'); - client.subscribe('bar'); + var serverWarning = null; + server.on('warning', function (err) { + serverWarning = err; + }); + + var serverDisconnectionCode = null; + server.on('disconnection', function (socket, code) { + serverDisconnectionCode = code; + }); + + var clientError = null; + client.on('error', function (err) { + clientError = err; + }); + + var clientDisconnectCode = null; + client.on('disconnect', function (code) { + clientDisconnectCode = code; + }); setTimeout(function () { - assert.equal(errorList.length, 0); - assert.equal(wasKickOutCalled, true); - assert.equal(serverSocket.channelSubscriptionsCount, 0); - assert.equal(Object.keys(serverSocket.channelSubscriptions).length, 0); + assert.equal(clientError, null); + assert.equal(clientDisconnectCode, null); + + assert.equal(serverWarning, null); + assert.equal(serverDisconnectionCode, null); done(); - }, 100); + }, 1000); }); }); }); @@ -1807,134 +1943,6 @@ describe('Integration tests', function () { }); }); - describe('Ping/pong', function () { - describe('When when pingTimeoutDisabled is not set (false)', function () { - beforeEach('Launch server with ping options before start', function (done) { - portNumber++; - // Intentionally make pingInterval higher than pingTimeout, that - // way the client will never receive a ping or send back a pong. - server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey, - wsEngine: WS_ENGINE, - pingInterval: 2000, - pingTimeout: 500 - }); - server.on('ready', function () { - done(); - }); - }); - - afterEach('Shut down server afterwards', function (done) { - destroyTestCase(function () { - server.close(); - done(); - }); - }); - - it('Should disconnect socket if server does not receive a pong from client before timeout', function (done) { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - - var serverWarning = null; - server.on('warning', function (err) { - serverWarning = err; - }); - - var serverDisconnectionCode = null; - server.on('disconnection', function (socket, code) { - serverDisconnectionCode = code; - }); - - var clientError = null; - client.on('error', function (err) { - clientError = err; - }); - - var clientDisconnectCode = null; - client.on('disconnect', function (code) { - clientDisconnectCode = code; - }); - - setTimeout(function () { - assert.notEqual(clientError, null); - assert.equal(clientError.name, 'SocketProtocolError'); - assert.equal(clientDisconnectCode, 4000); - - assert.notEqual(serverWarning, null); - assert.equal(serverWarning.name, 'SocketProtocolError'); - assert.equal(serverDisconnectionCode, 4001); - done(); - }, 1000); - }); - }); - - describe('When when pingTimeoutDisabled is true', function () { - beforeEach('Launch server with ping options before start', function (done) { - portNumber++; - // Intentionally make pingInterval higher than pingTimeout, that - // way the client will never receive a ping or send back a pong. - server = socketClusterServer.listen(portNumber, { - authKey: serverOptions.authKey, - wsEngine: WS_ENGINE, - pingInterval: 2000, - pingTimeout: 500, - pingTimeoutDisabled: true - }); - server.on('ready', function () { - done(); - }); - }); - - afterEach('Shut down server afterwards', function (done) { - destroyTestCase(function () { - server.close(); - done(); - }); - }); - - it('Should not disconnect socket if server does not receive a pong from client before timeout', function (done) { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false, - pingTimeoutDisabled: true - }); - - var serverWarning = null; - server.on('warning', function (err) { - serverWarning = err; - }); - - var serverDisconnectionCode = null; - server.on('disconnection', function (socket, code) { - serverDisconnectionCode = code; - }); - - var clientError = null; - client.on('error', function (err) { - clientError = err; - }); - - var clientDisconnectCode = null; - client.on('disconnect', function (code) { - clientDisconnectCode = code; - }); - - setTimeout(function () { - assert.equal(clientError, null); - assert.equal(clientDisconnectCode, null); - - assert.equal(serverWarning, null); - assert.equal(serverDisconnectionCode, null); - done(); - }, 1000); - }); - }); - }); - describe('Errors', function () { it('Should throw an error if reserved event is emitted on socket', function (done) { server.on('connection', function (socket) { From ab317be6c5aed2aa226614e2001f0142a78bbe87 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 21 Sep 2018 22:20:08 +0200 Subject: [PATCH 065/265] v14.2.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 94de466..7aa873d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "14.2.0", + "version": "14.2.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 4273020..410c6bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "14.2.0", + "version": "14.2.1", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 13af207809bc5090fffe963d5ede7c459ddc9fd8 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 23 Sep 2018 18:04:45 +0200 Subject: [PATCH 066/265] Use triple equal for primitive comparisons --- index.js | 2 +- scserver.js | 28 ++++++++++++++-------------- scserversocket.js | 27 +++++++++++++-------------- test/integration.js | 10 +++++----- 4 files changed, 33 insertions(+), 34 deletions(-) diff --git a/index.js b/index.js index 58fd69c..bd9cc3d 100644 --- a/index.js +++ b/index.js @@ -31,7 +31,7 @@ module.exports.SCServerSocket = require('./scserversocket'); */ module.exports.listen = function (port, options, fn) { - if ('function' == typeof options) { + if (typeof options === 'function') { fn = options; options = {}; } diff --git a/scserver.js b/scserver.js index ddb9ff7..6530e09 100644 --- a/scserver.js +++ b/scserver.js @@ -78,7 +78,7 @@ var SCServer = function (options) { this._middleware[this.MIDDLEWARE_AUTHENTICATE] = []; this.origins = opts.origins; - this._allowAllOrigins = this.origins.indexOf('*:*') != -1; + this._allowAllOrigins = this.origins.indexOf('*:*') !== -1; this.ackTimeout = opts.ackTimeout; this.handshakeTimeout = opts.handshakeTimeout; @@ -299,11 +299,11 @@ SCServer.prototype._processTokenError = function (err) { var isBadToken = true; if (err) { - if (err.name == 'TokenExpiredError') { + if (err.name === 'TokenExpiredError') { authError = new AuthTokenExpiredError(err.message, err.expiredAt); - } else if (err.name == 'JsonWebTokenError') { + } else if (err.name === 'JsonWebTokenError') { authError = new AuthTokenInvalidError(err.message); - } else if (err.name == 'NotBeforeError') { + } else if (err.name === 'NotBeforeError') { authError = new AuthTokenNotBeforeError(err.message, err.date); // In this case, the token is good; it's just not active yet. isBadToken = false; @@ -383,7 +383,7 @@ SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, call SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { var self = this; - if (this.options.wsEngine == 'ws') { + if (this.options.wsEngine === 'ws') { // Normalize ws module to match sc-uws module. wsSocket.upgradeReq = upgradeReq; } @@ -435,7 +435,7 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { } // This is an invalid state; it means the client tried to subscribe before // having completed the handshake. - if (scSocket.state == scSocket.OPEN) { + if (scSocket.state === scSocket.OPEN) { self._subscribeSocket(scSocket, channelOptions, function (err) { if (err) { var error = new BrokerError('Failed to subscribe socket to the ' + channelOptions.channel + ' channel - ' + err); @@ -500,10 +500,10 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { self._unsubscribeSocketFromAllChannels(scSocket); - if (type == 'disconnect') { + if (type === 'disconnect') { self.emit('_disconnection', scSocket, code, data); self.emit('disconnection', scSocket, code, data); - } else if (type == 'abort') { + } else if (type === 'abort') { self.emit('_connectionAbort', scSocket, code, data); self.emit('connectionAbort', scSocket, code, data); } @@ -534,7 +534,7 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { return; } self._processAuthToken(scSocket, signedAuthToken, function (err, isBadToken, oldState) { - if (scSocket.state == scSocket.CLOSED) { + if (scSocket.state === scSocket.CLOSED) { return; } @@ -617,7 +617,7 @@ SCServer.prototype.removeMiddleware = function (type, middleware) { var middlewareFunctions = this._middleware[type]; this._middleware[type] = middlewareFunctions.filter(function (fn) { - return fn != middleware; + return fn !== middleware; }); }; @@ -626,7 +626,7 @@ SCServer.prototype.verifyHandshake = function (info, cb) { var req = info.req; var origin = info.origin; - if (origin == 'null' || origin == null) { + if (origin === 'null' || origin == null) { origin = '*'; } var ok = false; @@ -720,7 +720,7 @@ SCServer.prototype._passThroughMiddleware = function (options, cb) { var event = options.event; if (this._isPrivateTransmittedEvent(event)) { - if (event == '#subscribe') { + if (event === '#subscribe') { var eventData = options.data || {}; request.channel = eventData.channel; request.waitForAuth = eventData.waitForAuth; @@ -752,7 +752,7 @@ SCServer.prototype._passThroughMiddleware = function (options, cb) { } ); } - } else if (event == '#publish') { + } else if (event === '#publish') { if (this.allowClientPublish) { var eventData = options.data || {}; request.channel = eventData.channel; @@ -894,7 +894,7 @@ SCServer.prototype.verifyOutboundEvent = function (socket, eventName, eventData, var callbackInvoked = false; - if (eventName == '#publish') { + if (eventName === '#publish') { var request = { socket: socket, channel: eventData.channel, diff --git a/scserversocket.js b/scserversocket.js index fba64b1..38e213b 100644 --- a/scserversocket.js +++ b/scserversocket.js @@ -49,7 +49,7 @@ var SCServerSocket = function (id, server, socket) { this.request = this.socket.upgradeReq || {}; var wsEngine = this.server.options.wsEngine; - if (wsEngine == 'sc-uws' || wsEngine == 'uws') { + if (wsEngine === 'sc-uws' || wsEngine === 'uws') { this.request.connection = this.socket._socket; } if (this.request.connection) { @@ -95,7 +95,7 @@ var SCServerSocket = function (id, server, socket) { try { obj = self.decode(message); } catch (err) { - if (err.name == 'Error') { + if (err.name === 'Error') { err.name = 'InvalidMessageError'; } Emitter.prototype.emit.call(self, 'error', err); @@ -103,7 +103,7 @@ var SCServerSocket = function (id, server, socket) { } // If pong - if (obj == '#2') { + if (obj === '#2') { var token = self.getAuthToken(); if (self.server.isAuthTokenExpired(token)) { self.deauthenticate(); @@ -134,14 +134,13 @@ SCServerSocket.ignoreStatuses = scErrors.socketProtocolIgnoreStatuses; SCServerSocket.errorStatuses = scErrors.socketProtocolErrorStatuses; SCServerSocket.prototype._sendPing = function () { - if (this.state != this.CLOSED) { + if (this.state !== this.CLOSED) { this.sendObject('#1'); } }; SCServerSocket.prototype._handleEventObject = function (obj, message) { var self = this; - if (obj && obj.event != null) { var eventName = obj.event; @@ -151,7 +150,7 @@ SCServerSocket.prototype._handleEventObject = function (obj, message) { if (err) { response.error(err, ackData); } else { - if (eventName == '#disconnect') { + if (eventName === '#disconnect') { var disconnectData = newEventData || {}; self._onSCClose(disconnectData.code, disconnectData.data); } else { @@ -213,11 +212,11 @@ SCServerSocket.prototype._onSCClose = function (code, data) { clearInterval(this._pingIntervalTicker); clearTimeout(this._pingTimeoutTicker); - if (this.state != this.CLOSED) { + if (this.state !== this.CLOSED) { var prevState = this.state; this.state = this.CLOSED; - if (prevState == this.CONNECTING) { + if (prevState === this.CONNECTING) { // Private connectAbort event for internal use only Emitter.prototype.emit.call(this, '_connectAbort', code, data); Emitter.prototype.emit.call(this, 'connectAbort', code, data); @@ -234,7 +233,7 @@ SCServerSocket.prototype._onSCClose = function (code, data) { var closeMessage; if (data) { var reasonString; - if (typeof data == 'object') { + if (typeof data === 'object') { try { reasonString = JSON.stringify(data); } catch (error) { @@ -256,12 +255,12 @@ SCServerSocket.prototype._onSCClose = function (code, data) { SCServerSocket.prototype.disconnect = function (code, data) { code = code || 1000; - if (typeof code != 'number') { + if (typeof code !== 'number') { var err = new InvalidArgumentsError('If specified, the code argument must be a number'); Emitter.prototype.emit.call(this, 'error', err); } - if (this.state != this.CLOSED) { + if (this.state !== this.CLOSED) { var packet = { code: code, data: data @@ -381,7 +380,7 @@ SCServerSocket.prototype.emit = function (event, data, callback, options) { } } }); - } else if (event == 'error') { + } else if (event === 'error') { Emitter.prototype.emit.call(this, event, data); } else { var error = new InvalidActionError('The "' + event + '" event is reserved and cannot be emitted on a server socket'); @@ -390,7 +389,7 @@ SCServerSocket.prototype.emit = function (event, data, callback, options) { }; SCServerSocket.prototype.triggerAuthenticationEvents = function (oldState) { - if (oldState != this.AUTHENTICATED) { + if (oldState !== this.AUTHENTICATED) { var stateChangeData = { oldState: oldState, newState: this.authState, @@ -485,7 +484,7 @@ SCServerSocket.prototype.deauthenticateSelf = function () { this.signedAuthToken = null; this.authToken = null; this.authState = this.UNAUTHENTICATED; - if (oldState != this.UNAUTHENTICATED) { + if (oldState !== this.UNAUTHENTICATED) { var stateChangeData = { oldState: oldState, newState: this.authState diff --git a/test/integration.js b/test/integration.js index f8ea22c..181b9c0 100644 --- a/test/integration.js +++ b/test/integration.js @@ -97,7 +97,7 @@ var destroyTestCase = function (next) { if (client) { client.on('error', function (err) {}); - if (client.state != client.CLOSED) { + if (client.state !== client.CLOSED) { client.once('close', function () { client.removeAllListeners('close'); client.removeAllListeners('connectAbort'); @@ -129,7 +129,7 @@ describe('Integration tests', function () { server.on('connection', connectionHandler); server.addMiddleware(server.MIDDLEWARE_AUTHENTICATE, function (req, next) { - if (req.authToken.username == 'alice') { + if (req.authToken.username === 'alice') { var err = new Error('Blocked by MIDDLEWARE_AUTHENTICATE'); err.name = 'AuthenticateMiddlewareError'; next(err); @@ -1043,9 +1043,9 @@ describe('Integration tests', function () { server.addMiddleware(server.MIDDLEWARE_SUBSCRIBE, function (req, next) { subscribeMiddlewareCounter++; assert.equal(req.channel.indexOf('my-channel-'), 0); - if (req.channel == 'my-channel-10') { + if (req.channel === 'my-channel-10') { assert.equal(JSON.stringify(req.data), JSON.stringify({foo: 123})); - } else if (req.channel == 'my-channel-12') { + } else if (req.channel === 'my-channel-12') { // Block my-channel-12 var err = new Error('You cannot subscribe to channel 12'); err.name = 'UnauthorizedSubscribeError'; @@ -1066,7 +1066,7 @@ describe('Integration tests', function () { var subscribeOptions = { batch: true }; - if (i == 10) { + if (i === 10) { subscribeOptions.data = {foo: 123}; } channelList.push( From 00edf3e0f0be6608b667cc0f53ece4ec1c71e42c Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 23 Sep 2018 18:05:32 +0200 Subject: [PATCH 067/265] v14.2.2 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7aa873d..2bc8cc0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "14.2.1", + "version": "14.2.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 410c6bb..8c49f3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "14.2.1", + "version": "14.2.2", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From f08db4dee452f96046337cc532842ebd6eeca646 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 23 Sep 2018 19:25:30 +0200 Subject: [PATCH 068/265] Bump dependencies --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 8c49f3f..f389219 100644 --- a/package.json +++ b/package.json @@ -12,18 +12,18 @@ "base64id": "1.0.0", "component-emitter": "1.2.1", "lodash.clonedeep": "4.5.0", - "sc-auth": "^5.0.1", - "sc-errors": "^1.4.0", + "sc-auth": "^5.0.2", + "sc-errors": "^1.4.1", "sc-formatter": "^3.0.2", - "sc-simple-broker": "^2.1.2", + "sc-simple-broker": "^2.1.3", "uuid": "3.2.1", - "sc-uws": "~10.148.1", + "sc-uws": "^10.148.2", "ws": "5.1.1" }, "devDependencies": { "localStorage": "^1.0.3", "mocha": "5.2.0", - "socketcluster-client": "^14.1.0" + "socketcluster-client": "^14.2.0" }, "scripts": { "test": "mocha --reporter spec --timeout 3000 --slow 3000" From 05da5a3444c0ae27093b3512c1eca6a95289fc7e Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 23 Sep 2018 19:29:33 +0200 Subject: [PATCH 069/265] v14.3.0 --- package-lock.json | 123 ++++++++++++++++++++-------------------------- package.json | 2 +- 2 files changed, 54 insertions(+), 71 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2bc8cc0..161ed9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "14.2.2", + "version": "14.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -18,6 +18,12 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=", + "dev": true + }, "base64id": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", @@ -44,6 +50,12 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, + "clone": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "dev": true + }, "commander": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", @@ -89,7 +101,7 @@ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "5.1.2" } }, "escape-string-regexp": { @@ -153,9 +165,9 @@ "dev": true }, "jsonwebtoken": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.2.0.tgz", - "integrity": "sha512-1Wxh8ADP3cNyPl8tZ95WtraHXCAyXupgc0AhMHjU9er98BV+UcKsO7OJUjfhIu0Uba9A40n1oSx8dbJYrm+EoQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz", + "integrity": "sha512-oge/hvlmeJCH+iIz1DwcO7vKPkNGJHhgkspk8OH3VKlw+mbi42WtD4ig1+VXRln765vxptAv+xT26Fd3cteqag==", "requires": { "jws": "3.1.5", "lodash.includes": "4.3.0", @@ -165,8 +177,7 @@ "lodash.isplainobject": "4.0.6", "lodash.isstring": "4.0.1", "lodash.once": "4.1.1", - "ms": "2.1.1", - "xtend": "4.0.1" + "ms": "2.1.1" } }, "jwa": { @@ -176,7 +187,7 @@ "requires": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.10", - "safe-buffer": "5.1.1" + "safe-buffer": "5.1.2" } }, "jws": { @@ -185,9 +196,15 @@ "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", "requires": { "jwa": "1.1.6", - "safe-buffer": "5.1.1" + "safe-buffer": "5.1.2" } }, + "linked-list": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz", + "integrity": "sha1-eYsP+X0bkqT9CEgPVa6k6dSdN78=", + "dev": true + }, "localStorage": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/localStorage/-/localStorage-1.0.3.tgz", @@ -302,18 +319,24 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "sc-auth": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/sc-auth/-/sc-auth-5.0.1.tgz", - "integrity": "sha512-UgTdvJZS17wkLfNNwj8/uObfACUiGPc5Jl2VmTPpwipasj1kO1Sr64VVhCBUz38j+f/9rBCKlzxCTwljxgX95Q==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/sc-auth/-/sc-auth-5.0.2.tgz", + "integrity": "sha512-Le3YBsFjzv5g6wIH6Y+vD+KFkK0HDXiaWy1Gm4nXtYebMQUyNYSf1cS83MtHrYzVEMlhYElRva1b0bvZ0hBqQw==", "requires": { - "jsonwebtoken": "8.2.0", - "sc-errors": "1.4.0" + "jsonwebtoken": "8.3.0", + "sc-errors": "1.4.1" } }, "sc-channel": { @@ -325,9 +348,9 @@ } }, "sc-errors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.0.tgz", - "integrity": "sha512-h+jRWx/xRJmkPFDd0IltoTl/QJ6hAr5Y+3ZVeBQRLuWZKe+dHdf2uVwFp2OYqlLQ7GHht4y9eXG2zOf2Ik6PTw==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.1.tgz", + "integrity": "sha512-dBn92iIonpChTxYLgKkIT/PCApvmYT6EPIbRvbQKTgY6tbEbIy8XVUv4pGyKwEK4nCmvX4TKXcN0iXC6tNW6rQ==" }, "sc-formatter": { "version": "3.0.2", @@ -335,22 +358,22 @@ "integrity": "sha512-9PbqYBpCq+OoEeRQ3QfFIGE6qwjjBcd2j7UjgDlhnZbtSnuGgHdcRklPKYGuYFH82V/dwd+AIpu8XvA1zqTd+A==" }, "sc-simple-broker": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-2.1.2.tgz", - "integrity": "sha512-8hbr47jLhrMecShZi6lunEeUPySkuLHlpg6G7g5jbBJQRrBiFiTuQdwk7KpMwAjLBh1qfaoku9Z+yWieOd5oLA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-2.1.3.tgz", + "integrity": "sha512-ldt0ybOS5fVZSMea5Z8qVu7lmDBTy0qO9BD6TseJjRuPx+g+stfSqmPAb0RsCsQUXRH8A1koCbwsuUnI9BOxvw==", "requires": { "sc-channel": "1.2.0" } }, "sc-uws": { - "version": "10.148.1", - "resolved": "https://registry.npmjs.org/sc-uws/-/sc-uws-10.148.1.tgz", - "integrity": "sha512-frMMhWhapvVLknOjyZJ+T1GMPgE6KJp2rIcTNhVj+Hyj827m3rmAnI0aHZcWmlNlyPEr95PA9Sm9A5RuD0fD4g==" + "version": "10.148.2", + "resolved": "https://registry.npmjs.org/sc-uws/-/sc-uws-10.148.2.tgz", + "integrity": "sha512-wGXiwsORev5O3OOewsAYi1WVyMeNFMQ4bw/Qg/6g0C0J9vsEs8xnxf19hovAAQrOq6sMVrcxCNa2k1rBiDsDzw==" }, "socketcluster-client": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-14.1.0.tgz", - "integrity": "sha512-Uzux62MQVK5ZmYu4I01Svsfiwf1c14i40+G1WlWTdFvVSOn0XZIX5J5FjiJEIRXe9HE/lwoVW0Gc8T2D5kfgBQ==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-14.2.0.tgz", + "integrity": "sha512-hibnSjupT+1JZlN608bxGwwaV7wqw36iBCSc0oXj7D+mv6DOR86+tSHWCPBaHWFkZK8ORW7v2BWSFuHSAzQzrw==", "dev": true, "requires": { "base-64": "0.1.0", @@ -359,45 +382,10 @@ "linked-list": "0.1.0", "querystring": "0.2.0", "sc-channel": "1.2.0", - "sc-errors": "1.4.0", + "sc-errors": "1.4.1", "sc-formatter": "3.0.2", "uuid": "3.2.1", "ws": "5.1.1" - }, - "dependencies": { - "base-64": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", - "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=", - "dev": true - }, - "clone": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", - "dev": true - }, - "linked-list": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz", - "integrity": "sha1-eYsP+X0bkqT9CEgPVa6k6dSdN78=", - "dev": true - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "sc-channel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", - "integrity": "sha512-M3gdq8PlKg0zWJSisWqAsMmTVxYRTpVRqw4CWAdKBgAfVKumFcTjoCV0hYu7lgUXccCtCD8Wk9VkkE+IXCxmZA==", - "dev": true, - "requires": { - "component-emitter": "1.2.1" - } - } } }, "supports-color": { @@ -434,11 +422,6 @@ "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" } } - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" } } } diff --git a/package.json b/package.json index f389219..2c7adf9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "14.2.2", + "version": "14.3.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 53094e4db590db0f208789f539927af850984aef Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 11 Oct 2018 00:21:08 +0200 Subject: [PATCH 070/265] Add support for custom disconnect status code in handshake middleware when using async function --- scserver.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scserver.js b/scserver.js index 6530e09..16d0f78 100644 --- a/scserver.js +++ b/scserver.js @@ -877,7 +877,10 @@ SCServer.prototype._passThroughHandshakeSCMiddleware = function (options, cb) { statusCode = 4008; } if (err) { - if (err === true) { + if (err.statusCode != null) { + statusCode = err.statusCode; + } + if (err === true || err.silent) { err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + self.MIDDLEWARE_HANDSHAKE_SC + ' middleware', self.MIDDLEWARE_HANDSHAKE_SC); } else if (self.middlewareEmitWarnings) { self.emit('warning', err); From 3666821514aa9c03836f3a6c9090ca51315958b9 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 11 Oct 2018 08:12:29 +0200 Subject: [PATCH 071/265] Allow all middleware lines to throw silent error --- scserver.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scserver.js b/scserver.js index 16d0f78..d51448e 100644 --- a/scserver.js +++ b/scserver.js @@ -653,7 +653,7 @@ SCServer.prototype.verifyHandshake = function (info, cb) { } else { callbackInvoked = true; if (err) { - if (err === true) { + if (err === true || err.silent) { err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + self.MIDDLEWARE_HANDSHAKE_WS + ' middleware', self.MIDDLEWARE_HANDSHAKE_WS); } else if (self.middlewareEmitWarnings) { self.emit('warning', err); @@ -738,7 +738,7 @@ SCServer.prototype._passThroughMiddleware = function (options, cb) { } else { callbackInvoked = true; if (err) { - if (err === true) { + if (err === true || err.silent) { err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + self.MIDDLEWARE_SUBSCRIBE + ' middleware', self.MIDDLEWARE_SUBSCRIBE); } else if (self.middlewareEmitWarnings) { self.emit('warning', err); @@ -768,7 +768,7 @@ SCServer.prototype._passThroughMiddleware = function (options, cb) { eventData.data = request.data; } if (err) { - if (err === true) { + if (err === true || err.silent) { err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + self.MIDDLEWARE_PUBLISH_IN + ' middleware', self.MIDDLEWARE_PUBLISH_IN); } else if (self.middlewareEmitWarnings) { self.emit('warning', err); @@ -811,7 +811,7 @@ SCServer.prototype._passThroughMiddleware = function (options, cb) { } else { callbackInvoked = true; if (err) { - if (err === true) { + if (err === true || err.silent) { err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + self.MIDDLEWARE_EMIT + ' middleware', self.MIDDLEWARE_EMIT); } else if (self.middlewareEmitWarnings) { self.emit('warning', err); @@ -844,7 +844,7 @@ SCServer.prototype._passThroughAuthenticateMiddleware = function (options, cb) { isBadToken = results[results.length - 1] || false; } if (err) { - if (err === true) { + if (err === true || err.silent) { err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + self.MIDDLEWARE_AUTHENTICATE + ' middleware', self.MIDDLEWARE_AUTHENTICATE); } else if (self.middlewareEmitWarnings) { self.emit('warning', err); @@ -913,7 +913,7 @@ SCServer.prototype.verifyOutboundEvent = function (socket, eventName, eventData, eventData.data = request.data; } if (err) { - if (err === true) { + if (err === true || err.silent) { err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + self.MIDDLEWARE_PUBLISH_OUT + ' middleware', self.MIDDLEWARE_PUBLISH_OUT); } else if (self.middlewareEmitWarnings) { self.emit('warning', err); From c3d9100b7ddc84f34e0b438cb90a322a57a0d3ee Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 11 Oct 2018 22:49:21 +0200 Subject: [PATCH 072/265] v14.3.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 161ed9e..5add926 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "14.3.0", + "version": "14.3.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2c7adf9..af0b4b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "14.3.0", + "version": "14.3.1", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From d460f03d61d05df581d148c17ae26626143165b8 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 21 Oct 2018 16:39:44 +0200 Subject: [PATCH 073/265] Use Promises instead of callbacks when interacting with the brokerEngine --- scserver.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/scserver.js b/scserver.js index d51448e..db48eae 100644 --- a/scserver.js +++ b/scserver.js @@ -250,14 +250,15 @@ SCServer.prototype._subscribeSocket = function (socket, channelOptions, callback socket.channelSubscriptionsCount++; } - this.brokerEngine.subscribeSocket(socket, channelName, function (err) { - if (err) { - delete socket.channelSubscriptions[channelName]; - socket.channelSubscriptionsCount--; - } else { - Emitter.prototype.emit.call(socket, 'subscribe', channelName, channelOptions); - self.emit('subscription', socket, channelName, channelOptions); - } + this.brokerEngine.subscribeSocket(socket, channelName) + .then(function () { + Emitter.prototype.emit.call(socket, 'subscribe', channelName, channelOptions); + self.emit('subscription', socket, channelName, channelOptions); + callback && callback(); + }) + .catch(function (err) { + delete socket.channelSubscriptions[channelName]; + socket.channelSubscriptionsCount--; callback && callback(err); }); }; From d2ff1596cc61a28047478d04e8337b13d3abdf66 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 29 Oct 2018 00:48:36 +0100 Subject: [PATCH 074/265] Use Promises instead of callbacks --- index.js | 6 +- scserver.js | 248 ++++++++++++++++++----------------- scserversocket.js | 325 +++++++++++++++++++++++----------------------- 3 files changed, 297 insertions(+), 282 deletions(-) diff --git a/index.js b/index.js index bd9cc3d..c648953 100644 --- a/index.js +++ b/index.js @@ -41,11 +41,11 @@ module.exports.listen = function (port, options, fn) { res.end('Not Implemented'); }); - var engine = module.exports.attach(server, options); - engine.httpServer = server; + var socketClusterServer = module.exports.attach(server, options); + socketClusterServer.httpServer = server; server.listen(port, fn); - return engine; + return socketClusterServer; }; /** diff --git a/scserver.js b/scserver.js index db48eae..8c1f052 100644 --- a/scserver.js +++ b/scserver.js @@ -49,13 +49,7 @@ var SCServer = function (options) { middlewareEmitWarnings: true }; - for (var i in options) { - if (options.hasOwnProperty(i)) { - opts[i] = options[i]; - } - } - - this.options = opts; + this.options = Object.assign(opts, options); this.MIDDLEWARE_HANDSHAKE_WS = 'handshakeWS'; this.MIDDLEWARE_HANDSHAKE_SC = 'handshakeSC'; @@ -98,9 +92,9 @@ var SCServer = function (options) { this._path = opts.path.replace(/\/?$/, '/').replace(/^\/?/, '/'); this.isReady = false; - this.brokerEngine.once('ready', function () { - self.isReady = true; - EventEmitter.prototype.emit.call(self, 'ready'); + this.brokerEngine.once('ready', () => { + this.isReady = true; + this.emit('ready'); }); var wsEngine = typeof opts.wsEngine === 'string' ? require(opts.wsEngine) : opts.wsEngine; @@ -222,8 +216,6 @@ SCServer.prototype._handleHandshakeTimeout = function (scSocket) { }; SCServer.prototype._subscribeSocket = function (socket, channelOptions, callback) { - var self = this; - if (!channelOptions) { callback && callback('Socket ' + socket.id + ' provided a malformated channel payload'); return; @@ -251,29 +243,28 @@ SCServer.prototype._subscribeSocket = function (socket, channelOptions, callback } this.brokerEngine.subscribeSocket(socket, channelName) - .then(function () { - Emitter.prototype.emit.call(socket, 'subscribe', channelName, channelOptions); - self.emit('subscription', socket, channelName, channelOptions); - callback && callback(); + .then(() => { + return null; }) - .catch(function (err) { - delete socket.channelSubscriptions[channelName]; - socket.channelSubscriptionsCount--; + .catch((err) => { + return err; + }) + .then((err) => { + if (err) { + delete socket.channelSubscriptions[channelName]; + socket.channelSubscriptionsCount--; + } else { + socket.emit('subscribe', channelName, channelOptions); + this.emit('subscription', socket, channelName, channelOptions); + } callback && callback(err); }); }; SCServer.prototype._unsubscribeSocketFromAllChannels = function (socket) { - var channels = []; - for (var channel in socket.channelSubscriptions) { - if (socket.channelSubscriptions.hasOwnProperty(channel)) { - channels.push(channel); - } - } - var len = channels.length; - for (var i = 0; i < len; i++) { - this._unsubscribeSocket(socket, channels[i]); - } + Object.keys(socket.channelSubscriptions).forEach((channelName) => { + this._unsubscribeSocket(socket, channelName); + }); }; SCServer.prototype._unsubscribeSocket = function (socket, channel) { @@ -291,7 +282,7 @@ SCServer.prototype._unsubscribeSocket = function (socket, channel) { this.brokerEngine.unsubscribeSocket(socket, channel); - Emitter.prototype.emit.call(socket, 'unsubscribe', channel); + socket.emit('unsubscribe', channel); this.emit('unsubscription', socket, channel); }; @@ -324,19 +315,21 @@ SCServer.prototype._emitBadAuthTokenError = function (scSocket, error, signedAut authError: error, signedAuthToken: signedAuthToken }; - Emitter.prototype.emit.call(scSocket, 'badAuthToken', badAuthStatus); + scSocket.emit('badAuthToken', badAuthStatus); this.emit('badSocketAuthToken', scSocket, badAuthStatus); }; SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, callback) { - var self = this; - var verificationOptions = Object.assign({socket: scSocket}, this.defaultVerificationOptions); - this.auth.verifyToken(signedAuthToken, this.verificationKey, verificationOptions, function (err, authToken) { + + var handleVerifyTokenResult = (result) => { + var err = result.error; + var token = result.token; + var oldState = scSocket.authState; - if (authToken) { + if (token) { scSocket.signedAuthToken = signedAuthToken; - scSocket.authToken = authToken; + scSocket.authToken = token; scSocket.authState = scSocket.AUTHENTICATED; } else { scSocket.signedAuthToken = null; @@ -349,16 +342,16 @@ SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, call // If there is an error but the token is good, then we will send back a 'quiet' error instead // (as part of the status object only). if (scSocket.authToken) { - self._passThroughAuthenticateMiddleware({ + this._passThroughAuthenticateMiddleware({ socket: scSocket, signedAuthToken: scSocket.signedAuthToken, authToken: scSocket.authToken - }, function (middlewareError, isBadToken) { + }, (middlewareError, isBadToken) => { if (middlewareError) { scSocket.authToken = null; scSocket.authState = scSocket.UNAUTHENTICATED; if (isBadToken) { - self._emitBadAuthTokenError(scSocket, middlewareError, signedAuthToken); + this._emitBadAuthTokenError(scSocket, middlewareError, signedAuthToken); } } // If an error is passed back from the authenticate middleware, it will be treated as a @@ -366,24 +359,42 @@ SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, call callback(middlewareError, isBadToken || false, oldState); }); } else { - var errorData = self._processTokenError(err); + var errorData = this._processTokenError(err); // If the error is related to the JWT being badly formatted, then we will // treat the error as a socket error. if (err && signedAuthToken != null) { - Emitter.prototype.emit.call(scSocket, 'error', errorData.authError); + scSocket.emit('error', errorData.authError); if (errorData.isBadToken) { - self._emitBadAuthTokenError(scSocket, errorData.authError, signedAuthToken); + this._emitBadAuthTokenError(scSocket, errorData.authError, signedAuthToken); } } callback(errorData.authError, errorData.isBadToken, oldState); } - }); + }; + + // TODO 2 Test + if (verificationOptions.async) { + this.auth.verifyToken(signedAuthToken, this.verificationKey, verificationOptions) + .then((token) => { + return {token: token}; + }) + .catch((err) => { + return {error: err}; + }) + .then(handleVerifyTokenResult); + } else { + var result = {}; + try { + result.token = this.auth.verifyToken(signedAuthToken, this.verificationKey, verificationOptions); + } catch (err) { + result.error = err; + } + handleVerifyTokenResult(result); + } }; SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { - var self = this; - if (this.options.wsEngine === 'ws') { // Normalize ws module to match sc-uws module. wsSocket.upgradeReq = upgradeReq; @@ -392,17 +403,17 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { var id = this.generateId(); var scSocket = new SCServerSocket(id, this, wsSocket); - scSocket.exchange = self.exchange; + scSocket.exchange = this.exchange; - scSocket.on('error', function (err) { - self._handleSocketError(err); + scSocket.on('error', (err) => { + this._handleSocketError(err); }); - self.pendingClients[id] = scSocket; - self.pendingClientsCount++; + this.pendingClients[id] = scSocket; + this.pendingClientsCount++; - scSocket.on('#authenticate', function (signedAuthToken, respond) { - self._processAuthToken(scSocket, signedAuthToken, function (err, isBadToken, oldState) { + scSocket.on('#authenticate', (signedAuthToken, respond) => { + this._processAuthToken(scSocket, signedAuthToken, (err, isBadToken, oldState) => { if (err) { if (isBadToken) { scSocket.deauthenticate(); @@ -422,11 +433,11 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { }); }); - scSocket.on('#removeAuthToken', function () { + scSocket.on('#removeAuthToken', () => { scSocket.deauthenticateSelf(); }); - scSocket.on('#subscribe', function (channelOptions, res) { + scSocket.on('#subscribe', (channelOptions, res) => { if (!channelOptions) { channelOptions = {}; } else if (typeof channelOptions === 'string') { @@ -437,11 +448,11 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { // This is an invalid state; it means the client tried to subscribe before // having completed the handshake. if (scSocket.state === scSocket.OPEN) { - self._subscribeSocket(scSocket, channelOptions, function (err) { + this._subscribeSocket(scSocket, channelOptions, (err) => { if (err) { var error = new BrokerError('Failed to subscribe socket to the ' + channelOptions.channel + ' channel - ' + err); res(error); - Emitter.prototype.emit.call(scSocket, 'error', error); + scSocket.emit('error', error); } else { if (channelOptions.batch) { res(undefined, undefined, {batch: true}); @@ -453,26 +464,26 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { } else { var error = new InvalidActionError('Cannot subscribe socket to a channel before it has completed the handshake'); res(error); - self.emit('warning', error); + this.emit('warning', error); } }); - scSocket.on('#unsubscribe', function (channel, res) { + scSocket.on('#unsubscribe', (channel, res) => { var error; try { - self._unsubscribeSocket(scSocket, channel); + this._unsubscribeSocket(scSocket, channel); } catch (err) { error = new BrokerError('Failed to unsubscribe socket from the ' + channel + ' channel - ' + err.message); } if (error) { res(error); - Emitter.prototype.emit.call(scSocket, 'error', error); + scSocket.emit('error', error); } else { res(); } }); - var cleanupSocket = function (type, code, data) { + var cleanupSocket = (type, code, data) => { clearTimeout(scSocket._handshakeTimeoutRef); scSocket.off('#handshake'); @@ -486,46 +497,46 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { scSocket.off('_disconnect'); scSocket.off('_connectAbort'); - var isClientFullyConnected = !!self.clients[id]; + var isClientFullyConnected = !!this.clients[id]; if (isClientFullyConnected) { - delete self.clients[id]; - self.clientsCount--; + delete this.clients[id]; + this.clientsCount--; } - var isClientPending = !!self.pendingClients[id]; + var isClientPending = !!this.pendingClients[id]; if (isClientPending) { - delete self.pendingClients[id]; - self.pendingClientsCount--; + delete this.pendingClients[id]; + this.pendingClientsCount--; } - self._unsubscribeSocketFromAllChannels(scSocket); + this._unsubscribeSocketFromAllChannels(scSocket); if (type === 'disconnect') { - self.emit('_disconnection', scSocket, code, data); - self.emit('disconnection', scSocket, code, data); + this.emit('_disconnection', scSocket, code, data); + this.emit('disconnection', scSocket, code, data); } else if (type === 'abort') { - self.emit('_connectionAbort', scSocket, code, data); - self.emit('connectionAbort', scSocket, code, data); + this.emit('_connectionAbort', scSocket, code, data); + this.emit('connectionAbort', scSocket, code, data); } - self.emit('_closure', scSocket, code, data); - self.emit('closure', scSocket, code, data); + this.emit('_closure', scSocket, code, data); + this.emit('closure', scSocket, code, data); }; scSocket.once('_disconnect', cleanupSocket.bind(scSocket, 'disconnect')); scSocket.once('_connectAbort', cleanupSocket.bind(scSocket, 'abort')); scSocket._handshakeTimeoutRef = setTimeout(this._handleHandshakeTimeout.bind(this, scSocket), this.handshakeTimeout); - scSocket.once('#handshake', function (data, respond) { + scSocket.once('#handshake', (data, respond) => { if (!data) { data = {}; } var signedAuthToken = data.authToken || null; clearTimeout(scSocket._handshakeTimeoutRef); - self._passThroughHandshakeSCMiddleware({ + this._passThroughHandshakeSCMiddleware({ socket: scSocket - }, function (err, statusCode) { + }, (err, statusCode) => { if (err) { var clientSocketErrorStatus = { code: statusCode @@ -534,18 +545,18 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { scSocket.disconnect(statusCode); return; } - self._processAuthToken(scSocket, signedAuthToken, function (err, isBadToken, oldState) { + this._processAuthToken(scSocket, signedAuthToken, (err, isBadToken, oldState) => { if (scSocket.state === scSocket.CLOSED) { return; } var clientSocketStatus = { id: scSocket.id, - pingTimeout: self.pingTimeout + pingTimeout: this.pingTimeout }; var serverSocketStatus = { id: scSocket.id, - pingTimeout: self.pingTimeout + pingTimeout: this.pingTimeout }; if (err) { @@ -563,20 +574,20 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { clientSocketStatus.isAuthenticated = !!scSocket.authToken; serverSocketStatus.isAuthenticated = clientSocketStatus.isAuthenticated; - if (self.pendingClients[id]) { - delete self.pendingClients[id]; - self.pendingClientsCount--; + if (this.pendingClients[id]) { + delete this.pendingClients[id]; + this.pendingClientsCount--; } - self.clients[id] = scSocket; - self.clientsCount++; + this.clients[id] = scSocket; + this.clientsCount++; scSocket.state = scSocket.OPEN; - Emitter.prototype.emit.call(scSocket, 'connect', serverSocketStatus); - Emitter.prototype.emit.call(scSocket, '_connect', serverSocketStatus); + scSocket.emit('connect', serverSocketStatus); + scSocket.emit('_connect', serverSocketStatus); - self.emit('_connection', scSocket, serverSocketStatus); - self.emit('connection', scSocket, serverSocketStatus); + this.emit('_connection', scSocket, serverSocketStatus); + this.emit('connection', scSocket, serverSocketStatus); if (clientSocketStatus.isAuthenticated) { scSocket.triggerAuthenticationEvents(oldState); @@ -623,8 +634,6 @@ SCServer.prototype.removeMiddleware = function (type, middleware) { }; SCServer.prototype.verifyHandshake = function (info, cb) { - var self = this; - var req = info.req; var origin = info.origin; if (origin === 'null' || origin == null) { @@ -648,16 +657,16 @@ SCServer.prototype.verifyHandshake = function (info, cb) { var handshakeMiddleware = this._middleware[this.MIDDLEWARE_HANDSHAKE_WS]; if (handshakeMiddleware.length) { var callbackInvoked = false; - async.applyEachSeries(handshakeMiddleware, req, function (err) { + async.applyEachSeries(handshakeMiddleware, req, (err) => { if (callbackInvoked) { - self.emit('warning', new InvalidActionError('Callback for ' + self.MIDDLEWARE_HANDSHAKE_WS + ' middleware was already invoked')); + this.emit('warning', new InvalidActionError('Callback for ' + this.MIDDLEWARE_HANDSHAKE_WS + ' middleware was already invoked')); } else { callbackInvoked = true; if (err) { if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + self.MIDDLEWARE_HANDSHAKE_WS + ' middleware', self.MIDDLEWARE_HANDSHAKE_WS); - } else if (self.middlewareEmitWarnings) { - self.emit('warning', err); + err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + this.MIDDLEWARE_HANDSHAKE_WS + ' middleware', this.MIDDLEWARE_HANDSHAKE_WS); + } else if (this.middlewareEmitWarnings) { + this.emit('warning', err); } cb(false, 401, err); } else { @@ -706,8 +715,6 @@ SCServer.prototype.isAuthTokenExpired = function (token) { }; SCServer.prototype._passThroughMiddleware = function (options, cb) { - var self = this; - var callbackInvoked = false; var request = { @@ -733,16 +740,16 @@ SCServer.prototype._passThroughMiddleware = function (options, cb) { cb(request.authTokenExpiredError, eventData); } else { async.applyEachSeries(this._middleware[this.MIDDLEWARE_SUBSCRIBE], request, - function (err) { + (err) => { if (callbackInvoked) { - self.emit('warning', new InvalidActionError('Callback for ' + self.MIDDLEWARE_SUBSCRIBE + ' middleware was already invoked')); + this.emit('warning', new InvalidActionError('Callback for ' + this.MIDDLEWARE_SUBSCRIBE + ' middleware was already invoked')); } else { callbackInvoked = true; if (err) { if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + self.MIDDLEWARE_SUBSCRIBE + ' middleware', self.MIDDLEWARE_SUBSCRIBE); - } else if (self.middlewareEmitWarnings) { - self.emit('warning', err); + err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + this.MIDDLEWARE_SUBSCRIBE + ' middleware', this.MIDDLEWARE_SUBSCRIBE); + } else if (this.middlewareEmitWarnings) { + this.emit('warning', err); } } if (request.data !== undefined) { @@ -760,9 +767,9 @@ SCServer.prototype._passThroughMiddleware = function (options, cb) { request.data = eventData.data; async.applyEachSeries(this._middleware[this.MIDDLEWARE_PUBLISH_IN], request, - function (err) { + (err) => { if (callbackInvoked) { - self.emit('warning', new InvalidActionError('Callback for ' + self.MIDDLEWARE_PUBLISH_IN + ' middleware was already invoked')); + this.emit('warning', new InvalidActionError('Callback for ' + this.MIDDLEWARE_PUBLISH_IN + ' middleware was already invoked')); } else { callbackInvoked = true; if (request.data !== undefined) { @@ -770,21 +777,28 @@ SCServer.prototype._passThroughMiddleware = function (options, cb) { } if (err) { if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + self.MIDDLEWARE_PUBLISH_IN + ' middleware', self.MIDDLEWARE_PUBLISH_IN); - } else if (self.middlewareEmitWarnings) { - self.emit('warning', err); + err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + this.MIDDLEWARE_PUBLISH_IN + ' middleware', this.MIDDLEWARE_PUBLISH_IN); + } else if (this.middlewareEmitWarnings) { + this.emit('warning', err); } cb(err, eventData, request.ackData); } else { if (typeof request.channel !== 'string') { err = new BrokerError('Socket ' + request.socket.id + ' tried to publish to an invalid ' + request.channel + ' channel'); - self.emit('warning', err); + this.emit('warning', err); cb(err, eventData, request.ackData); return; } - self.exchange.publish(request.channel, request.data, function (err) { + this.exchange.publish(request.channel, request.data) + .then(() => { + return null; + }) + .catch((err) => { + return err; + }) + .then((err) => { if (err) { - self.emit('warning', err); + this.emit('warning', err); } cb(err, eventData, request.ackData); }); @@ -794,7 +808,7 @@ SCServer.prototype._passThroughMiddleware = function (options, cb) { ); } else { var noPublishError = new InvalidActionError('Client publish feature is disabled'); - self.emit('warning', noPublishError); + this.emit('warning', noPublishError); cb(noPublishError, options.data); } } else { @@ -806,16 +820,16 @@ SCServer.prototype._passThroughMiddleware = function (options, cb) { request.data = options.data; async.applyEachSeries(this._middleware[this.MIDDLEWARE_EMIT], request, - function (err) { + (err) => { if (callbackInvoked) { - self.emit('warning', new InvalidActionError('Callback for ' + self.MIDDLEWARE_EMIT + ' middleware was already invoked')); + this.emit('warning', new InvalidActionError('Callback for ' + this.MIDDLEWARE_EMIT + ' middleware was already invoked')); } else { callbackInvoked = true; if (err) { if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + self.MIDDLEWARE_EMIT + ' middleware', self.MIDDLEWARE_EMIT); - } else if (self.middlewareEmitWarnings) { - self.emit('warning', err); + err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + this.MIDDLEWARE_EMIT + ' middleware', this.MIDDLEWARE_EMIT); + } else if (this.middlewareEmitWarnings) { + this.emit('warning', err); } } cb(err, request.data); diff --git a/scserversocket.js b/scserversocket.js index 38e213b..c031e3d 100644 --- a/scserversocket.js +++ b/scserversocket.js @@ -10,31 +10,8 @@ var InvalidActionError = scErrors.InvalidActionError; var SCServerSocket = function (id, server, socket) { - var self = this; - Emitter.call(this); - this._localEvents = { - 'subscribe': 1, - 'unsubscribe': 1, - 'connect': 1, - '_connect': 1, - 'disconnect': 1, - '_disconnect': 1, - 'connectAbort': 1, - '_connectAbort': 1, - 'close': 1, - '_close': 1, - 'message': 1, - 'error': 1, - 'authStateChange': 1, - 'authTokenSigned': 1, - 'authenticate': 1, - 'deauthenticate': 1, - 'badAuthToken': 1, - 'raw': 1 - }; - this._autoAckEvents = { '#publish': 1 }; @@ -72,12 +49,12 @@ var SCServerSocket = function (id, server, socket) { this.channelSubscriptions = {}; this.channelSubscriptionsCount = 0; - this.socket.on('error', function (err) { - Emitter.prototype.emit.call(self, 'error', err); + this.socket.on('error', (err) => { + this.emit('error', err); }); - this.socket.on('close', function (code, data) { - self._onSCClose(code, data); + this.socket.on('close', (code, data) => { + this._onSCClose(code, data); }); if (!this.server.pingTimeoutDisabled) { @@ -86,36 +63,36 @@ var SCServerSocket = function (id, server, socket) { this._resetPongTimeout(); // Receive incoming raw messages - this.socket.on('message', function (message, flags) { - self._resetPongTimeout(); + this.socket.on('message', (message, flags) => { + this._resetPongTimeout(); - Emitter.prototype.emit.call(self, 'message', message); + this.emit('message', message); var obj; try { - obj = self.decode(message); + obj = this.decode(message); } catch (err) { if (err.name === 'Error') { err.name = 'InvalidMessageError'; } - Emitter.prototype.emit.call(self, 'error', err); + this.emit('error', err); return; } // If pong if (obj === '#2') { - var token = self.getAuthToken(); - if (self.server.isAuthTokenExpired(token)) { - self.deauthenticate(); + var token = this.getAuthToken(); + if (this.server.isAuthTokenExpired(token)) { + this.deauthenticate(); } } else { if (Array.isArray(obj)) { var len = obj.length; for (var i = 0; i < len; i++) { - self._handleEventObject(obj[i], message); + this._handleEventObject(obj[i], message); } } else { - self._handleEventObject(obj, message); + this._handleEventObject(obj, message); } } }); @@ -140,46 +117,43 @@ SCServerSocket.prototype._sendPing = function () { }; SCServerSocket.prototype._handleEventObject = function (obj, message) { - var self = this; if (obj && obj.event != null) { var eventName = obj.event; - if (self._localEvents[eventName] == null) { - var response = new Response(self, obj.cid); - self.server.verifyInboundEvent(self, eventName, obj.data, function (err, newEventData, ackData) { - if (err) { - response.error(err, ackData); + var response = new Response(this, obj.cid); + this.server.verifyInboundEvent(this, eventName, obj.data, (err, newEventData, ackData) => { + if (err) { + response.error(err, ackData); + } else { + if (eventName === '#disconnect') { + var disconnectData = newEventData || {}; + this._onSCClose(disconnectData.code, disconnectData.data); } else { - if (eventName === '#disconnect') { - var disconnectData = newEventData || {}; - self._onSCClose(disconnectData.code, disconnectData.data); - } else { - if (self._autoAckEvents[eventName]) { - if (ackData !== undefined) { - response.end(ackData); - } else { - response.end(); - } - Emitter.prototype.emit.call(self, eventName, newEventData); + if (this._autoAckEvents[eventName]) { + if (ackData !== undefined) { + response.end(ackData); } else { - Emitter.prototype.emit.call(self, eventName, newEventData, response.callback.bind(response)); + response.end(); } + this.emit(eventName, newEventData); + } else { + this.emit(eventName, newEventData, response.callback.bind(response)); } } - }); - } + } + }); } else if (obj && obj.rid != null) { // If incoming message is a response to a previously sent message - var ret = self._callbackMap[obj.rid]; + var ret = this._callbackMap[obj.rid]; if (ret) { clearTimeout(ret.timeout); - delete self._callbackMap[obj.rid]; + delete this._callbackMap[obj.rid]; var rehydratedError = scErrors.hydrateError(obj.error); ret.callback(rehydratedError, obj.data); } } else { // The last remaining case is to treat the message as raw - Emitter.prototype.emit.call(self, 'raw', message); + this.emit('raw', message); } }; @@ -187,12 +161,10 @@ SCServerSocket.prototype._resetPongTimeout = function () { if (this.server.pingTimeoutDisabled) { return; } - var self = this; - clearTimeout(this._pingTimeoutTicker); - this._pingTimeoutTicker = setTimeout(function() { - self._onSCClose(4001); - self.socket.close(4001); + this._pingTimeoutTicker = setTimeout(() => { + this._onSCClose(4001); + this.socket.close(4001); }, this.server.pingTimeout); }; @@ -218,16 +190,16 @@ SCServerSocket.prototype._onSCClose = function (code, data) { if (prevState === this.CONNECTING) { // Private connectAbort event for internal use only - Emitter.prototype.emit.call(this, '_connectAbort', code, data); - Emitter.prototype.emit.call(this, 'connectAbort', code, data); + this.emit('_connectAbort', code, data); + this.emit('connectAbort', code, data); } else { // Private disconnect event for internal use only - Emitter.prototype.emit.call(this, '_disconnect', code, data); - Emitter.prototype.emit.call(this, 'disconnect', code, data); + this.emit('_disconnect', code, data); + this.emit('disconnect', code, data); } // Private close event for internal use only - Emitter.prototype.emit.call(this, '_close', code, data); - Emitter.prototype.emit.call(this, 'close', code, data); + this.emit('_close', code, data); + this.emit('close', code, data); if (!SCServerSocket.ignoreStatuses[code]) { var closeMessage; @@ -247,7 +219,7 @@ SCServerSocket.prototype._onSCClose = function (code, data) { closeMessage = 'Socket connection closed with status code ' + code; } var err = new SocketProtocolError(SCServerSocket.errorStatuses[code] || closeMessage, code); - Emitter.prototype.emit.call(this, 'error', err); + this.emit('error', err); } } }; @@ -257,7 +229,7 @@ SCServerSocket.prototype.disconnect = function (code, data) { if (typeof code !== 'number') { var err = new InvalidArgumentsError('If specified, the code argument must be a number'); - Emitter.prototype.emit.call(this, 'error', err); + this.emit('error', err); } if (this.state !== this.CLOSED) { @@ -265,7 +237,7 @@ SCServerSocket.prototype.disconnect = function (code, data) { code: code, data: data }; - this.emit('#disconnect', packet); + this.transmit('#disconnect', packet); this._onSCClose(code, data); this.socket.close(code); } @@ -281,11 +253,9 @@ SCServerSocket.prototype.terminate = function () { }; SCServerSocket.prototype.send = function (data, options) { - var self = this; - - this.socket.send(data, options, function (err) { + this.socket.send(data, options, (err) => { if (err) { - self._onSCClose(1006, err.toString()); + this._onSCClose(1006, err.toString()); } }); }; @@ -299,26 +269,24 @@ SCServerSocket.prototype.encode = function (object) { }; SCServerSocket.prototype.sendObjectBatch = function (object) { - var self = this; - this._batchSendList.push(object); if (this._batchTimeout) { return; } - this._batchTimeout = setTimeout(function () { - delete self._batchTimeout; - if (self._batchSendList.length) { + this._batchTimeout = setTimeout(() => { + delete this._batchTimeout; + if (this._batchSendList.length) { var str; try { - str = self.encode(self._batchSendList); + str = this.encode(this._batchSendList); } catch (err) { - Emitter.prototype.emit.call(self, 'error', err); + this.emit('error', err); } if (str != null) { - self.send(str); + this.send(str); } - self._batchSendList = []; + this._batchSendList = []; } }, this.server.options.pubSubBatchDuration || 0); }; @@ -328,7 +296,7 @@ SCServerSocket.prototype.sendObjectSingle = function (object) { try { str = this.encode(object); } catch (err) { - Emitter.prototype.emit.call(this, 'error', err); + this.emit('error', err); } if (str != null) { this.send(str); @@ -343,49 +311,66 @@ SCServerSocket.prototype.sendObject = function (object, options) { } }; -SCServerSocket.prototype.emit = function (event, data, callback, options) { - var self = this; +SCServerSocket.prototype.transmit = function (event, data, options) { + this.server.verifyOutboundEvent(this, event, data, options, (err, newData) => { + var eventObject = { + event: event + }; + if (newData !== undefined) { + eventObject.data = newData; + } + + if (!err) { + if (options && options.useCache && options.stringifiedData != null) { + // Optimized + this.send(options.stringifiedData); + } else { + this.sendObject(eventObject); + } + } + }); +}; - if (this._localEvents[event] == null) { - this.server.verifyOutboundEvent(this, event, data, options, function (err, newData) { +SCServerSocket.prototype.invoke = function (event, data, options) { + return new Promise((resolve, reject) => { + this.server.verifyOutboundEvent(this, event, data, options, (err, newData) => { + if (err) { + reject(err); + return; + } var eventObject = { - event: event + event: event, + cid: this._nextCallId() }; if (newData !== undefined) { eventObject.data = newData; } - if (err) { - if (callback) { - eventObject.cid = self._nextCallId(); - callback(err, eventObject); - } - } else { - if (callback) { - eventObject.cid = self._nextCallId(); - var timeout = setTimeout(function () { - var error = new TimeoutError("Event response for '" + event + "' timed out"); - - delete self._callbackMap[eventObject.cid]; - callback(error, eventObject); - }, self.server.ackTimeout); + var timeout = setTimeout(() => { + var error = new TimeoutError("Event response for '" + event + "' timed out"); + delete this._callbackMap[eventObject.cid]; + reject(error); + }, this.server.ackTimeout); + + this._callbackMap[eventObject.cid] = { + callback: (err, result) => { + if (err) { + reject(err); + return; + } + resolve(result); + }, + timeout: timeout + }; - self._callbackMap[eventObject.cid] = {callback: callback, timeout: timeout}; - } - if (options && options.useCache && options.stringifiedData != null) { - // Optimized - self.send(options.stringifiedData); - } else { - self.sendObject(eventObject); - } + if (options && options.useCache && options.stringifiedData != null) { + // Optimized + this.send(options.stringifiedData); + } else { + this.sendObject(eventObject); } }); - } else if (event === 'error') { - Emitter.prototype.emit.call(this, event, data); - } else { - var error = new InvalidActionError('The "' + event + '" event is reserved and cannot be emitted on a server socket'); - Emitter.prototype.emit.call(this, 'error', error); - } + }); }; SCServerSocket.prototype.triggerAuthenticationEvents = function (oldState) { @@ -395,16 +380,14 @@ SCServerSocket.prototype.triggerAuthenticationEvents = function (oldState) { newState: this.authState, authToken: this.authToken }; - Emitter.prototype.emit.call(this, 'authStateChange', stateChangeData); + this.emit('authStateChange', stateChangeData); this.server.emit('authenticationStateChange', this, stateChangeData); } - Emitter.prototype.emit.call(this, 'authenticate', this.authToken); + this.emit('authenticate', this.authToken); this.server.emit('authentication', this, this.authToken); }; -SCServerSocket.prototype.setAuthToken = function (data, options, callback) { - var self = this; - +SCServerSocket.prototype.setAuthToken = function (data, options) { var authToken = cloneDeep(data); var oldState = this.authState; this.authState = this.AUTHENTICATED; @@ -416,7 +399,7 @@ SCServerSocket.prototype.setAuthToken = function (data, options, callback) { if (options.algorithm != null) { delete options.algorithm; var err = new InvalidArgumentsError('Cannot change auth token algorithm at runtime - It must be specified as a config option on launch'); - Emitter.prototype.emit.call(this, 'error', err); + this.emit('error', err); } } @@ -453,25 +436,51 @@ SCServerSocket.prototype.setAuthToken = function (data, options, callback) { this.authToken = authToken; - this.server.auth.signToken(authToken, this.server.signatureKey, options, function (err, signedToken) { - if (err) { - Emitter.prototype.emit.call(self, 'error', err); - self._onSCClose(4002, err.toString()); - self.socket.close(4002); - callback && callback(err); - } else { - var tokenData = { - token: signedToken - }; - if (self.authToken === authToken) { - self.signedAuthToken = signedToken; - Emitter.prototype.emit.call(self, 'authTokenSigned', signedToken); - } - self.emit('#setAuthToken', tokenData, callback); + var handleSignTokenResult = (result) => { + if (result.error) { + this.emit('error', result.error); + this._onSCClose(4002, result.error.toString()); + this.socket.close(4002); + throw result.error; } - }); + var tokenData = { + token: result.signedToken + }; + if (this.authToken === authToken) { + this.signedAuthToken = result.signedToken; + this.emit('authTokenSigned', result.signedToken); + } + return this.invoke('#setAuthToken', tokenData); + }; + + var signTokenPromise; + + // TODO 2: Test + if (options.async) { + signTokenPromise = this.server.auth.signToken(authToken, this.server.signatureKey, options) + .then((signedToken) => { + return {signedToken: signedToken}; + }) + .catch((err) => { + return {error: err}; + }) + .then(handleSignTokenResult); + } else { + var result = {}; + try { + result.signedToken = this.server.auth.signToken(authToken, this.server.signatureKey, options); + } catch (err) { + result.error = err; + } + try { + signTokenPromise = handleSignTokenResult(result) + } catch (err) { + signTokenPromise = Promise.reject(err); + } + } this.triggerAuthenticationEvents(oldState); + return signTokenPromise; }; SCServerSocket.prototype.getAuthToken = function () { @@ -489,43 +498,35 @@ SCServerSocket.prototype.deauthenticateSelf = function () { oldState: oldState, newState: this.authState }; - Emitter.prototype.emit.call(this, 'authStateChange', stateChangeData); + this.emit('authStateChange', stateChangeData); this.server.emit('authenticationStateChange', this, stateChangeData); } - Emitter.prototype.emit.call(this, 'deauthenticate', oldToken); + this.emit('deauthenticate', oldToken); this.server.emit('deauthentication', this, oldToken); }; -SCServerSocket.prototype.deauthenticate = function (callback) { +SCServerSocket.prototype.deauthenticate = function () { this.deauthenticateSelf(); - this.emit('#removeAuthToken', null, callback); + return this.invoke('#removeAuthToken'); }; -SCServerSocket.prototype.kickOut = function (channel, message, callback) { - var self = this; - +SCServerSocket.prototype.kickOut = function (channel, message) { if (channel == null) { - Object.keys(this.channelSubscriptions).forEach(function (channelName) { - delete self.channelSubscriptions[channelName]; - self.channelSubscriptionsCount--; - self.emit('#kickOut', {message: message, channel: channelName}); + Object.keys(this.channelSubscriptions).forEach((channelName) => { + delete this.channelSubscriptions[channelName]; + this.channelSubscriptionsCount--; + this.emit('#kickOut', {message: message, channel: channelName}); }); } else { delete this.channelSubscriptions[channel]; this.channelSubscriptionsCount--; this.emit('#kickOut', {message: message, channel: channel}); } - this.server.brokerEngine.unsubscribeSocket(this, channel, callback); + return this.server.brokerEngine.unsubscribeSocket(this, channel); }; SCServerSocket.prototype.subscriptions = function () { - var subs = []; - for (var i in this.channelSubscriptions) { - if (this.channelSubscriptions.hasOwnProperty(i)) { - subs.push(i); - } - } - return subs; + return Object.keys(this.channelSubscriptions); }; SCServerSocket.prototype.isSubscribed = function (channel) { From 59d064a24d05db507396da2e212b5e2eddeafa1a Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 29 Oct 2018 22:07:37 +0100 Subject: [PATCH 075/265] Return promise from transmit function --- scserversocket.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scserversocket.js b/scserversocket.js index c031e3d..e5b475b 100644 --- a/scserversocket.js +++ b/scserversocket.js @@ -329,6 +329,7 @@ SCServerSocket.prototype.transmit = function (event, data, options) { } } }); + return Promise.resolve(); }; SCServerSocket.prototype.invoke = function (event, data, options) { From 1313c16012ad5285e18b1f8593deca52af95f9db Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 30 Oct 2018 00:15:59 +0100 Subject: [PATCH 076/265] Switch to Promises in integration tests --- test/integration.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/test/integration.js b/test/integration.js index 181b9c0..0c55e7d 100644 --- a/test/integration.js +++ b/test/integration.js @@ -164,7 +164,7 @@ describe('Integration tests', function () { it('Should be authenticated on connect if previous JWT token is present', function (done) { client = socketCluster.connect(clientOptions); client.once('connect', function (statusA) { - client.emit('login', {username: 'bob'}); + client.transmit('login', {username: 'bob'}); client.once('authenticate', function (state) { assert.equal(client.authState, 'authenticated'); @@ -189,9 +189,8 @@ describe('Integration tests', function () { client = socketCluster.connect(clientOptions); client.once('connect', function (statusA) { // Change the setAuthKey to invalidate the current token. - client.emit('setAuthKey', 'differentAuthKey', function (err) { - assert.equal(err == null, true); - + client.transmit('setAuthKey', 'differentAuthKey') + .then(function () { client.once('disconnect', function () { client.once('connect', function (statusB) { assert.equal(statusB.isAuthenticated, false); @@ -239,7 +238,7 @@ describe('Integration tests', function () { client = socketCluster.connect(clientOptions); client.once('connect', function (statusA) { clientSocketId = client.id; - client.emit('login', {username: 'alice'}); + client.transmit('login', {username: 'alice'}); }); setTimeout(function () { @@ -349,7 +348,8 @@ describe('Integration tests', function () { multiplex: false }); client.once('connect', function (statusA) { - client.emit('login', {username: 'bob'}, function (err) { + client.invoke('login', {username: 'bob'}) + .then(function () { assert.equal(client.authState, 'authenticated'); assert.notEqual(client.authToken, null); assert.equal(client.authToken.username, 'bob'); @@ -374,7 +374,7 @@ describe('Integration tests', function () { multiplex: false }); client.once('connect', function (statusA) { - client.emit('login', {username: 'bob'}); + client.transmit('login', {username: 'bob'}); client.on('authenticate', function (newSignedToken) { assert.equal(client.authState, 'authenticated'); assert.notEqual(client.authToken, null); @@ -400,7 +400,7 @@ describe('Integration tests', function () { multiplex: false }); client.once('connect', function (statusA) { - client.emit('login', {username: 'bob'}); + client.transmit('login', {username: 'bob'}); client.once('authenticate', function (newSignedToken) { client.once('disconnect', function () { client.once('connect', function (statusB) { @@ -441,7 +441,7 @@ describe('Integration tests', function () { assert.equal(dateDifference < 1000, true); done(); }); - client.emit('loginWithTenDayExpiry', {username: 'bob'}); + client.transmit('loginWithTenDayExpiry', {username: 'bob'}); }); }); }); @@ -470,7 +470,7 @@ describe('Integration tests', function () { assert.equal(dateDifference < 1000, true); done(); }); - client.emit('loginWithTenDayExp', {username: 'bob'}); + client.transmit('loginWithTenDayExp', {username: 'bob'}); }); }); }); @@ -499,7 +499,7 @@ describe('Integration tests', function () { assert.equal(dateDifference < 1000, true); done(); }); - client.emit('loginWithTenDayExpAndExpiry', {username: 'bob'}); + client.transmit('loginWithTenDayExpAndExpiry', {username: 'bob'}); }); }); }); @@ -532,7 +532,7 @@ describe('Integration tests', function () { assert.notEqual(err, null); assert.equal(err.name, 'SocketProtocolError'); }); - client.emit('loginWithIssAndIssuer', {username: 'bob'}); + client.transmit('loginWithIssAndIssuer', {username: 'bob'}); setTimeout(function () { server.removeAllListeners('warning'); assert.notEqual(warningMap['SocketProtocolError'], null); @@ -577,7 +577,7 @@ describe('Integration tests', function () { multiplex: false }); client.once('connect', function (statusA) { - client.emit('login', {username: 'bob'}); + client.transmit('login', {username: 'bob'}); }); setTimeout(function () { assert.equal(authTokenSignedEventEmitted, true); @@ -1376,7 +1376,7 @@ describe('Integration tests', function () { multiplex: false }); - client.emit('#unsubscribe', 'bar'); + client.transmit('#unsubscribe', 'bar'); setTimeout(function () { assert.equal(errorList.length, 1); @@ -1432,7 +1432,7 @@ describe('Integration tests', function () { }); fooChannel.on('subscribe', function () { - client.emit('#unsubscribe', 'foo'); + client.transmit('#unsubscribe', 'foo'); }); setTimeout(function () { @@ -1780,7 +1780,7 @@ describe('Integration tests', function () { multiplex: false }); - client.emit('login', {username: 'bob'}); + client.transmit('login', {username: 'bob'}); client.once('authenticate', function (state) { assert.equal(middlewareWasExecuted, true); done(); @@ -1944,13 +1944,13 @@ describe('Integration tests', function () { }); describe('Errors', function () { - it('Should throw an error if reserved event is emitted on socket', function (done) { + it('Should throw an error if reserved event is transmitted on socket', function (done) { server.on('connection', function (socket) { var error; socket.on('error', function (err) { error = err; }); - socket.emit('message', 123); + socket.transmit('message', 123); setTimeout(function () { assert.notEqual(error, null); assert.equal(error.name, 'InvalidActionError'); @@ -1961,7 +1961,7 @@ describe('Integration tests', function () { client = socketCluster.connect(clientOptions); }); - it('Should allow emitting error event on socket', function (done) { + it('Should allow transmitting error event on socket', function (done) { server.on('connection', function (socket) { var error; socket.on('error', function (err) { @@ -1969,7 +1969,7 @@ describe('Integration tests', function () { }); var customError = new Error('This is a custom error'); customError.name = 'CustomError'; - socket.emit('error', customError); + socket.transmit('error', customError); setTimeout(function () { assert.notEqual(error, null); assert.equal(error.name, 'CustomError'); From 171297e1c7bf12ac35561137dd162e02aa6d887d Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 31 Oct 2018 17:07:24 +0100 Subject: [PATCH 077/265] Fix auth signToken and verifyToken --- scserver.js | 25 ++++++++++++++++--------- scserversocket.js | 22 ++++++++++++++-------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/scserver.js b/scserver.js index 8c1f052..10e063d 100644 --- a/scserver.js +++ b/scserver.js @@ -373,9 +373,18 @@ SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, call } }; - // TODO 2 Test - if (verificationOptions.async) { - this.auth.verifyToken(signedAuthToken, this.verificationKey, verificationOptions) + var verifyTokenResult; + var verifyTokenError; + + // TODO 2: Test + try { + verifyTokenResult = this.auth.verifyToken(signedAuthToken, this.verificationKey, verificationOptions); + } catch (err) { + verifyTokenError = err; + } + + if (verifyTokenResult instanceof Promise) { // TODO 2: Check if there is a better way to check if Promise + verifyTokenResult .then((token) => { return {token: token}; }) @@ -384,12 +393,10 @@ SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, call }) .then(handleVerifyTokenResult); } else { - var result = {}; - try { - result.token = this.auth.verifyToken(signedAuthToken, this.verificationKey, verificationOptions); - } catch (err) { - result.error = err; - } + var result = { + token: verifyTokenResult, + error: verifyTokenError + }; handleVerifyTokenResult(result); } }; diff --git a/scserversocket.js b/scserversocket.js index e5b475b..c2cd59b 100644 --- a/scserversocket.js +++ b/scserversocket.js @@ -454,11 +454,19 @@ SCServerSocket.prototype.setAuthToken = function (data, options) { return this.invoke('#setAuthToken', tokenData); }; + var signTokenResult; + var signTokenError; var signTokenPromise; // TODO 2: Test - if (options.async) { - signTokenPromise = this.server.auth.signToken(authToken, this.server.signatureKey, options) + try { + signTokenResult = this.server.auth.signToken(authToken, this.server.signatureKey, options); + } catch (err) { + signTokenError = err; + } + + if (signTokenResult instanceof Promise) { + signTokenPromise = signTokenResult .then((signedToken) => { return {signedToken: signedToken}; }) @@ -467,12 +475,10 @@ SCServerSocket.prototype.setAuthToken = function (data, options) { }) .then(handleSignTokenResult); } else { - var result = {}; - try { - result.signedToken = this.server.auth.signToken(authToken, this.server.signatureKey, options); - } catch (err) { - result.error = err; - } + var result = { + signedToken: signTokenResult, + error: signTokenError + }; try { signTokenPromise = handleSignTokenResult(result) } catch (err) { From 44ffee4e4b06ad846bee217fe5bb3be568102e21 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 31 Oct 2018 17:37:26 -0400 Subject: [PATCH 078/265] More progress on Promises changes --- scserver.js | 10 +++---- test/integration.js | 73 ++++++++++++++++++++------------------------- 2 files changed, 37 insertions(+), 46 deletions(-) diff --git a/scserver.js b/scserver.js index 10e063d..eb4fef0 100644 --- a/scserver.js +++ b/scserver.js @@ -545,11 +545,11 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { socket: scSocket }, (err, statusCode) => { if (err) { - var clientSocketErrorStatus = { - code: statusCode - }; - respond(err, clientSocketErrorStatus); - scSocket.disconnect(statusCode); + if (err.statusCode == null) { + err.statusCode = statusCode; + } + respond(err); + scSocket.disconnect(err.statusCode); return; } this._processAuthToken(scSocket, signedAuthToken, (err, isBadToken, oldState) => { diff --git a/test/integration.js b/test/integration.js index 0c55e7d..b281985 100644 --- a/test/integration.js +++ b/test/integration.js @@ -26,6 +26,18 @@ var invalidSignedAuthToken = 'fakebGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.fakec2VybmFtZ var server, client; +var resolveAfterTimeout = function (duration, value) { + return new Promise((resolve, reject) => { + setTimeout(() => { + if (value === undefined) { + resolve(); + } else { + resolve(value); + } + }, duration); + }); +}; + var connectionHandler = function (socket) { socket.on('login', function (userDetails, respond) { if (allowedUsers[userDetails.username]) { @@ -333,7 +345,7 @@ describe('Integration tests', function () { }); }); - it('Token should be available inside login callback if token engine signing is synchronous', function (done) { + it('Token should be available after Promise resolves if token engine signing is synchronous', function (done) { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, @@ -595,16 +607,15 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE }); server.setAuthEngine({ - verifyToken: function (signedAuthToken, verificationKey, verificationOptions, callback) { - setTimeout(function () { + verifyToken: (signedAuthToken, verificationKey, verificationOptions) => { + setTimeout(() => { assert.equal(signedAuthToken, validSignedAuthTokenBob); assert.equal(verificationKey, serverOptions.authKey); assert.notEqual(verificationOptions, null); assert.notEqual(verificationOptions.socket, null); - assert.equal(typeof callback, 'function'); - callback(null, {}); done(); }, 500) + return Promise.resolve({}); } }); server.on('connection', connectionHandler); @@ -624,10 +635,8 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE }); server.setAuthEngine({ - verifyToken: function (signedAuthToken, verificationKey, verificationOptions, callback) { - setTimeout(function () { - callback(null, {}); - }, 500) + verifyToken: function (signedAuthToken, verificationKey, verificationOptions) { + return resolveAfterTimeout(500, {}); } }); server.on('connection', connectionHandler); @@ -779,10 +788,8 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE }); server.setAuthEngine({ - verifyToken: function (signedAuthToken, verificationKey, verificationOptions, callback) { - setTimeout(function () { - callback(null, {}); - }, 500) + verifyToken: function (signedAuthToken, verificationKey, verificationOptions) { + return resolveAfterTimeout(500, {}); } }); server.on('connection', connectionHandler); @@ -849,10 +856,8 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE }); server.setAuthEngine({ - verifyToken: function (signedAuthToken, verificationKey, verificationOptions, callback) { - setTimeout(function () { - callback(null, {}); - }, 10) + verifyToken: function (signedAuthToken, verificationKey, verificationOptions) { + return resolveAfterTimeout(10, {}); } }); server.on('connection', connectionHandler); @@ -919,10 +924,8 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE }); server.setAuthEngine({ - verifyToken: function (signedAuthToken, verificationKey, verificationOptions, callback) { - setTimeout(function () { - callback(null, {}); - }, 500) + verifyToken: function (signedAuthToken, verificationKey, verificationOptions) { + return resolveAfterTimeout(500, {}); } }); server.on('connection', connectionHandler); @@ -971,10 +974,8 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE }); server.setAuthEngine({ - verifyToken: function (signedAuthToken, verificationKey, verificationOptions, callback) { - setTimeout(function () { - callback(null, {}); - }, 0) + verifyToken: function (signedAuthToken, verificationKey, verificationOptions) { + return resolveAfterTimeout(0, {}); } }); server.on('connection', connectionHandler); @@ -1098,10 +1099,8 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE }); server.setAuthEngine({ - verifyToken: function (signedAuthToken, verificationKey, verificationOptions, callback) { - setTimeout(function () { - callback(null, {}); - }, 500) + verifyToken: function (signedAuthToken, verificationKey, verificationOptions) { + return resolveAfterTimeout(500, {}); } }); server.on('connection', connectionHandler); @@ -1299,12 +1298,8 @@ describe('Integration tests', function () { portNumber++; var customBrokerEngine = new SCSimpleBroker(); var defaultUnsubscribeSocket = customBrokerEngine.unsubscribeSocket; - customBrokerEngine.unsubscribeSocket = function (socket, channel, callback) { - defaultUnsubscribeSocket.call(this, socket, channel, function () { - setTimeout(function () { - callback && callback(); - }, 100); - }); + customBrokerEngine.unsubscribeSocket = function (socket, channel) { + return resolveAfterTimeout(100, defaultUnsubscribeSocket.call(this, socket, channel)); }; server = socketClusterServer.listen(portNumber, { @@ -1390,12 +1385,8 @@ describe('Integration tests', function () { portNumber++; var customBrokerEngine = new SCSimpleBroker(); var defaultUnsubscribeSocket = customBrokerEngine.unsubscribeSocket; - customBrokerEngine.unsubscribeSocket = function (socket, channel, callback) { - defaultUnsubscribeSocket.call(this, socket, channel, function () { - setTimeout(function () { - callback && callback(); - }, 300); - }); + customBrokerEngine.unsubscribeSocket = function (socket, channel) { + return resolveAfterTimeout(300, defaultUnsubscribeSocket.call(this, socket, channel)); }; server = socketClusterServer.listen(portNumber, { From a91906ea4249ca07b10f8f8cf552b8b41fd333f0 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 31 Oct 2018 20:04:40 -0400 Subject: [PATCH 079/265] Remove TODO --- scserver.js | 2 +- scserversocket.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scserver.js b/scserver.js index eb4fef0..3b6d3b6 100644 --- a/scserver.js +++ b/scserver.js @@ -383,7 +383,7 @@ SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, call verifyTokenError = err; } - if (verifyTokenResult instanceof Promise) { // TODO 2: Check if there is a better way to check if Promise + if (verifyTokenResult instanceof Promise) { verifyTokenResult .then((token) => { return {token: token}; diff --git a/scserversocket.js b/scserversocket.js index c2cd59b..878ca35 100644 --- a/scserversocket.js +++ b/scserversocket.js @@ -480,7 +480,7 @@ SCServerSocket.prototype.setAuthToken = function (data, options) { error: signTokenError }; try { - signTokenPromise = handleSignTokenResult(result) + signTokenPromise = handleSignTokenResult(result); } catch (err) { signTokenPromise = Promise.reject(err); } From 9d9ab614032c0d97aedaaffdd0c55b40487a7d3d Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 5 Nov 2018 02:57:23 -0300 Subject: [PATCH 080/265] Passing tests --- scserversocket.js | 4 +++- test/integration.js | 42 ++---------------------------------------- 2 files changed, 5 insertions(+), 41 deletions(-) diff --git a/scserversocket.js b/scserversocket.js index 878ca35..680e589 100644 --- a/scserversocket.js +++ b/scserversocket.js @@ -237,7 +237,9 @@ SCServerSocket.prototype.disconnect = function (code, data) { code: code, data: data }; - this.transmit('#disconnect', packet); + if (this.socket.readyState === this.socket.OPEN) { + this.transmit('#disconnect', packet); + } this._onSCClose(code, data); this.socket.close(code); } diff --git a/test/integration.js b/test/integration.js index b281985..9250f36 100644 --- a/test/integration.js +++ b/test/integration.js @@ -41,7 +41,7 @@ var resolveAfterTimeout = function (duration, value) { var connectionHandler = function (socket) { socket.on('login', function (userDetails, respond) { if (allowedUsers[userDetails.username]) { - socket.setAuthToken(userDetails); + socket.setAuthToken(userDetails); // TODO 2: Catch rejection? respond(); } else { var err = new Error('Failed to login'); @@ -90,7 +90,7 @@ var connectionHandler = function (socket) { userDetails.iss = 'foo'; socket.setAuthToken(userDetails, { issuer: 'bar' - }); + }).catch((err) => {}); respond(); } else { var err = new Error('Failed to login'); @@ -1933,42 +1933,4 @@ describe('Integration tests', function () { }); }); }); - - describe('Errors', function () { - it('Should throw an error if reserved event is transmitted on socket', function (done) { - server.on('connection', function (socket) { - var error; - socket.on('error', function (err) { - error = err; - }); - socket.transmit('message', 123); - setTimeout(function () { - assert.notEqual(error, null); - assert.equal(error.name, 'InvalidActionError'); - done(); - }, 100); - }); - - client = socketCluster.connect(clientOptions); - }); - - it('Should allow transmitting error event on socket', function (done) { - server.on('connection', function (socket) { - var error; - socket.on('error', function (err) { - error = err; - }); - var customError = new Error('This is a custom error'); - customError.name = 'CustomError'; - socket.transmit('error', customError); - setTimeout(function () { - assert.notEqual(error, null); - assert.equal(error.name, 'CustomError'); - done(); - }, 100); - }); - - client = socketCluster.connect(clientOptions); - }); - }); }); From 25749c2e660399514b56806bfa355e600884f864 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 6 Nov 2018 01:06:02 -0300 Subject: [PATCH 081/265] Add rejectOnFailedDelivery option to setAuthToken to reject Promise if auth token delivery fails --- scserver.js | 1 - scserversocket.js | 14 +++++-- test/integration.js | 98 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 108 insertions(+), 5 deletions(-) diff --git a/scserver.js b/scserver.js index 3b6d3b6..d2de788 100644 --- a/scserver.js +++ b/scserver.js @@ -376,7 +376,6 @@ SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, call var verifyTokenResult; var verifyTokenError; - // TODO 2: Test try { verifyTokenResult = this.auth.verifyToken(signedAuthToken, this.verificationKey, verificationOptions); } catch (err) { diff --git a/scserversocket.js b/scserversocket.js index 680e589..29c0675 100644 --- a/scserversocket.js +++ b/scserversocket.js @@ -7,6 +7,7 @@ var InvalidArgumentsError = scErrors.InvalidArgumentsError; var SocketProtocolError = scErrors.SocketProtocolError; var TimeoutError = scErrors.TimeoutError; var InvalidActionError = scErrors.InvalidActionError; +var AuthError = scErrors.AuthError; var SCServerSocket = function (id, server, socket) { @@ -407,7 +408,8 @@ SCServerSocket.prototype.setAuthToken = function (data, options) { } options.mutatePayload = true; - + var rejectOnFailedDelivery = options.rejectOnFailedDelivery; + delete options.rejectOnFailedDelivery; var defaultSignatureOptions = this.server.defaultSignatureOptions; // We cannot have the exp claim on the token and the expiresIn option @@ -453,14 +455,20 @@ SCServerSocket.prototype.setAuthToken = function (data, options) { this.signedAuthToken = result.signedToken; this.emit('authTokenSigned', result.signedToken); } - return this.invoke('#setAuthToken', tokenData); + return this.invoke('#setAuthToken', tokenData) + .catch((err) => { + var authError = new AuthError('Failed to deliver auth token to client - ' + err.message); + this.emit('error', authError); + if (rejectOnFailedDelivery) { + throw authError; + } + }); }; var signTokenResult; var signTokenError; var signTokenPromise; - // TODO 2: Test try { signTokenResult = this.server.auth.signToken(authToken, this.server.signatureKey, options); } catch (err) { diff --git a/test/integration.js b/test/integration.js index 9250f36..290c62d 100644 --- a/test/integration.js +++ b/test/integration.js @@ -41,7 +41,7 @@ var resolveAfterTimeout = function (duration, value) { var connectionHandler = function (socket) { socket.on('login', function (userDetails, respond) { if (allowedUsers[userDetails.username]) { - socket.setAuthToken(userDetails); // TODO 2: Catch rejection? + socket.setAuthToken(userDetails); respond(); } else { var err = new Error('Failed to login'); @@ -598,6 +598,102 @@ describe('Integration tests', function () { }); }); + it('Should reject Promise returned by socket.setAuthToken if token delivery fails and rejectOnFailedDelivery option is true', function (done) { + portNumber++; + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, + authSignAsync: true, + ackTimeout: 1000 + }); + + var socketErrors = []; + + server.on('connection', function (socket) { + socket.on('error', function (err) { + socketErrors.push(err); + }); + socket.on('login', function (userDetails, respond) { + if (allowedUsers[userDetails.username]) { + client.disconnect(); + socket.setAuthToken(userDetails, {rejectOnFailedDelivery: true}) + .catch((err) => { + assert.notEqual(err, null); + assert.equal(err.name, 'AuthError'); + assert.notEqual(socketErrors[0], null); + assert.equal(socketErrors[0].name, 'AuthError'); + done(); + }); + respond(); + } else { + var err = new Error('Failed to login'); + err.name = 'FailedLoginError'; + respond(err); + } + }); + }); + + server.on('ready', function () { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + client.once('connect', function (statusA) { + client.transmit('login', {username: 'bob'}); + }); + }); + }); + + it('Should not reject Promise returned by socket.setAuthToken if token delivery fails and rejectOnFailedDelivery option is not true', function (done) { + portNumber++; + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, + authSignAsync: true, + ackTimeout: 1000 + }); + + var socketErrors = []; + + server.on('connection', function (socket) { + socket.on('error', function (err) { + socketErrors.push(err); + }); + socket.on('login', function (userDetails, respond) { + if (allowedUsers[userDetails.username]) { + client.disconnect(); + socket.setAuthToken(userDetails) + .catch((err) => { + return err; + }) + .then((err) => { + assert.equal(err, null); + assert.notEqual(socketErrors[0], null); + assert.equal(socketErrors[0].name, 'AuthError'); + done(); + }); + respond(); + } else { + var err = new Error('Failed to login'); + err.name = 'FailedLoginError'; + respond(err); + } + }); + }); + + server.on('ready', function () { + client = socketCluster.connect({ + hostname: clientOptions.hostname, + port: portNumber, + multiplex: false + }); + client.once('connect', function (statusA) { + client.transmit('login', {username: 'bob'}); + }); + }); + }); + it('The verifyToken method of the authEngine receives correct params', function (done) { global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); From 6bbbabf8b27d1eba430c61741710aded334f80c6 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 6 Nov 2018 01:12:23 -0300 Subject: [PATCH 082/265] Use fat arrow functions --- scserver.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scserver.js b/scserver.js index d2de788..8f28f7d 100644 --- a/scserver.js +++ b/scserver.js @@ -634,7 +634,7 @@ SCServer.prototype.addMiddleware = function (type, middleware) { SCServer.prototype.removeMiddleware = function (type, middleware) { var middlewareFunctions = this._middleware[type]; - this._middleware[type] = middlewareFunctions.filter(function (fn) { + this._middleware[type] = middlewareFunctions.filter((fn) => { return fn !== middleware; }); }; @@ -855,7 +855,7 @@ SCServer.prototype._passThroughAuthenticateMiddleware = function (options, cb) { }; async.applyEachSeries(this._middleware[this.MIDDLEWARE_AUTHENTICATE], request, - function (err, results) { + (err, results) => { if (callbackInvoked) { self.emit('warning', new InvalidActionError('Callback for ' + self.MIDDLEWARE_AUTHENTICATE + ' middleware was already invoked')); } else { @@ -886,7 +886,7 @@ SCServer.prototype._passThroughHandshakeSCMiddleware = function (options, cb) { }; async.applyEachSeries(this._middleware[this.MIDDLEWARE_HANDSHAKE_SC], request, - function (err, results) { + (err, results) => { if (callbackInvoked) { self.emit('warning', new InvalidActionError('Callback for ' + self.MIDDLEWARE_HANDSHAKE_SC + ' middleware was already invoked')); } else { @@ -925,7 +925,7 @@ SCServer.prototype.verifyOutboundEvent = function (socket, eventName, eventData, data: eventData.data }; async.applyEachSeries(this._middleware[this.MIDDLEWARE_PUBLISH_OUT], request, - function (err) { + (err) => { if (callbackInvoked) { self.emit('warning', new InvalidActionError('Callback for ' + self.MIDDLEWARE_PUBLISH_OUT + ' middleware was already invoked')); } else { From 5d2fba0cc30a18b244dd809b9f50558f1501fb01 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 7 Nov 2018 22:43:43 -0300 Subject: [PATCH 083/265] Close SC socket asynchronously if underlying WebSocket closes --- scserversocket.js | 4 +++- test/integration.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scserversocket.js b/scserversocket.js index 29c0675..235074e 100644 --- a/scserversocket.js +++ b/scserversocket.js @@ -55,7 +55,9 @@ var SCServerSocket = function (id, server, socket) { }); this.socket.on('close', (code, data) => { - this._onSCClose(code, data); + setTimeout(() => { + this._onSCClose(code, data); + }, 0); }); if (!this.server.pingTimeoutDisabled) { diff --git a/test/integration.js b/test/integration.js index 290c62d..cd994fa 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1618,7 +1618,7 @@ describe('Integration tests', function () { assert.equal(serverSocket.channelSubscriptionsCount, 0); assert.equal(Object.keys(serverSocket.channelSubscriptions).length, 0); done(); - }, 100); + }, 200); }); }); }); From d2b37035308776ad8467025ec0393c2c2a9b58fa Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 7 Nov 2018 23:13:02 -0300 Subject: [PATCH 084/265] Remove #disconnect protocol event and use WebSocket close control frame instead to carry error code and reason --- scserversocket.js | 28 ++++++++-------------------- test/integration.js | 30 ++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/scserversocket.js b/scserversocket.js index 235074e..542ab48 100644 --- a/scserversocket.js +++ b/scserversocket.js @@ -128,20 +128,15 @@ SCServerSocket.prototype._handleEventObject = function (obj, message) { if (err) { response.error(err, ackData); } else { - if (eventName === '#disconnect') { - var disconnectData = newEventData || {}; - this._onSCClose(disconnectData.code, disconnectData.data); - } else { - if (this._autoAckEvents[eventName]) { - if (ackData !== undefined) { - response.end(ackData); - } else { - response.end(); - } - this.emit(eventName, newEventData); + if (this._autoAckEvents[eventName]) { + if (ackData !== undefined) { + response.end(ackData); } else { - this.emit(eventName, newEventData, response.callback.bind(response)); + response.end(); } + this.emit(eventName, newEventData); + } else { + this.emit(eventName, newEventData, response.callback.bind(response)); } } }); @@ -236,15 +231,8 @@ SCServerSocket.prototype.disconnect = function (code, data) { } if (this.state !== this.CLOSED) { - var packet = { - code: code, - data: data - }; - if (this.socket.readyState === this.socket.OPEN) { - this.transmit('#disconnect', packet); - } this._onSCClose(code, data); - this.socket.close(code); + this.socket.close(code, data); } }; diff --git a/test/integration.js b/test/integration.js index cd994fa..19ca58d 100644 --- a/test/integration.js +++ b/test/integration.js @@ -895,6 +895,7 @@ describe('Integration tests', function () { port: portNumber, multiplex: false }); + client.on('error', function () {}); var socketDisconnected = false; var socketDisconnectedBeforeConnect = false; @@ -915,8 +916,10 @@ describe('Integration tests', function () { } socketDisconnected = true; }); - socket.once('connectAbort', function () { + socket.once('connectAbort', function (code, reason) { clientSocketAborted = true; + assert.equal(code, 4444); + assert.equal(reason, 'Disconnect before handshake'); }); }); @@ -932,8 +935,9 @@ describe('Integration tests', function () { }); setTimeout(function () { - client.disconnect(); + client.disconnect(4444, 'Disconnect before handshake'); }, 100); + setTimeout(function () { assert.equal(socketDisconnected, false); assert.equal(socketDisconnectedBeforeConnect, false); @@ -963,6 +967,7 @@ describe('Integration tests', function () { port: portNumber, multiplex: false }); + client.on('error', function () {}); var socketDisconnected = false; var socketDisconnectedBeforeConnect = false; @@ -977,11 +982,13 @@ describe('Integration tests', function () { server.once('handshake', function (socket) { assert.equal(server.pendingClientsCount, 1); assert.notEqual(server.pendingClients[socket.id], null); - socket.once('disconnect', function () { + socket.once('disconnect', function (code, reason) { if (!connectionOnServer) { socketDisconnectedBeforeConnect = true; } socketDisconnected = true; + assert.equal(code, 4445); + assert.equal(reason, 'Disconnect after handshake'); }); socket.once('connectAbort', function () { clientSocketAborted = true; @@ -1000,8 +1007,9 @@ describe('Integration tests', function () { }); setTimeout(function () { - client.disconnect(); + client.disconnect(4445, 'Disconnect after handshake'); }, 200); + setTimeout(function () { assert.equal(socketDisconnectedBeforeConnect, false); assert.equal(socketDisconnected, true); @@ -1031,14 +1039,17 @@ describe('Integration tests', function () { port: portNumber, multiplex: false }); + client.on('error', function () {}); var serverSocketClosed = false; var serverSocketAborted = false; var serverClosure = false; server.on('handshake', function (socket) { - socket.once('close', function () { + socket.once('close', function (code, reason) { serverSocketClosed = true; + assert.equal(code, 4444); + assert.equal(reason, 'Disconnect before handshake'); }); }); @@ -1052,7 +1063,7 @@ describe('Integration tests', function () { }); setTimeout(function () { - client.disconnect(); + client.disconnect(4444, 'Disconnect before handshake'); }, 100); setTimeout(function () { assert.equal(serverSocketClosed, true); @@ -1081,14 +1092,17 @@ describe('Integration tests', function () { port: portNumber, multiplex: false }); + client.on('error', function () {}); var serverSocketClosed = false; var serverSocketDisconnected = false; var serverClosure = false; server.on('handshake', function (socket) { - socket.once('close', function () { + socket.once('close', function (code, reason) { serverSocketClosed = true; + assert.equal(code, 4445); + assert.equal(reason, 'Disconnect after handshake'); }); }); @@ -1102,7 +1116,7 @@ describe('Integration tests', function () { }); setTimeout(function () { - client.disconnect(); + client.disconnect(4445, 'Disconnect after handshake'); }, 100); setTimeout(function () { assert.equal(serverSocketClosed, true); From a83bfd11f0b585214914585e0a08e4f903c322c4 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 11 Nov 2018 01:09:40 -0500 Subject: [PATCH 085/265] Async/await version of SC --- index.js | 8 +- package.json | 3 +- response.js | 16 +- scserver.js | 1037 +++++++++------ scserversocket.js | 341 ++--- test/integration.js | 3040 +++++++++++++++++++++++-------------------- 6 files changed, 2451 insertions(+), 1994 deletions(-) diff --git a/index.js b/index.js index c648953..13188db 100644 --- a/index.js +++ b/index.js @@ -2,7 +2,7 @@ * Module dependencies. */ -var http = require('http'); +const http = require('http'); /** * Expose SCServer constructor. @@ -36,12 +36,12 @@ module.exports.listen = function (port, options, fn) { options = {}; } - var server = http.createServer(function (req, res) { + let server = http.createServer(function (req, res) { res.writeHead(501); res.end('Not Implemented'); }); - var socketClusterServer = module.exports.attach(server, options); + let socketClusterServer = module.exports.attach(server, options); socketClusterServer.httpServer = server; server.listen(port, fn); @@ -62,6 +62,6 @@ module.exports.attach = function (server, options) { options = {}; } options.httpServer = server; - var socketClusterServer = new module.exports.SCServer(options); + let socketClusterServer = new module.exports.SCServer(options); return socketClusterServer; }; diff --git a/package.json b/package.json index af0b4b9..c9b6d8f 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "async": "2.3.0", + "async-stream-emitter": "^1.1.0", "base64id": "1.0.0", "component-emitter": "1.2.1", "lodash.clonedeep": "4.5.0", @@ -16,8 +17,8 @@ "sc-errors": "^1.4.1", "sc-formatter": "^3.0.2", "sc-simple-broker": "^2.1.3", + "stream-demux": "^4.0.4", "uuid": "3.2.1", - "sc-uws": "^10.148.2", "ws": "5.1.1" }, "devDependencies": { diff --git a/response.js b/response.js index dad04cc..0e20a13 100644 --- a/response.js +++ b/response.js @@ -1,15 +1,15 @@ -var scErrors = require('sc-errors'); -var InvalidActionError = scErrors.InvalidActionError; +const scErrors = require('sc-errors'); +const InvalidActionError = scErrors.InvalidActionError; -var Response = function (socket, id) { +function Response(socket, id) { this.socket = socket; this.id = id; this.sent = false; -}; +} Response.prototype._respond = function (responseData, options) { if (this.sent) { - throw new InvalidActionError('Response ' + this.id + ' has already been sent'); + throw new InvalidActionError(`Response ${this.id} has already been sent`); } else { this.sent = true; this.socket.sendObject(responseData, options); @@ -18,7 +18,7 @@ Response.prototype._respond = function (responseData, options) { Response.prototype.end = function (data, options) { if (this.id) { - var responseData = { + let responseData = { rid: this.id }; if (data !== undefined) { @@ -30,9 +30,9 @@ Response.prototype.end = function (data, options) { Response.prototype.error = function (error, data, options) { if (this.id) { - var err = scErrors.dehydrateError(error); + let err = scErrors.dehydrateError(error); - var responseData = { + let responseData = { rid: this.id, error: err }; diff --git a/scserver.js b/scserver.js index 8f28f7d..b63d801 100644 --- a/scserver.js +++ b/scserver.js @@ -1,34 +1,31 @@ -var SCServerSocket = require('./scserversocket'); -var AuthEngine = require('sc-auth').AuthEngine; -var formatter = require('sc-formatter'); -var EventEmitter = require('events').EventEmitter; -var Emitter = require('component-emitter'); -var base64id = require('base64id'); -var async = require('async'); -var url = require('url'); -var crypto = require('crypto'); -var uuid = require('uuid'); -var SCSimpleBroker = require('sc-simple-broker').SCSimpleBroker; - -var scErrors = require('sc-errors'); -var AuthTokenExpiredError = scErrors.AuthTokenExpiredError; -var AuthTokenInvalidError = scErrors.AuthTokenInvalidError; -var AuthTokenNotBeforeError = scErrors.AuthTokenNotBeforeError; -var AuthTokenError = scErrors.AuthTokenError; -var SilentMiddlewareBlockedError = scErrors.SilentMiddlewareBlockedError; -var InvalidArgumentsError = scErrors.InvalidArgumentsError; -var InvalidOptionsError = scErrors.InvalidOptionsError; -var InvalidActionError = scErrors.InvalidActionError; -var BrokerError = scErrors.BrokerError; -var ServerProtocolError = scErrors.ServerProtocolError; - - -var SCServer = function (options) { - var self = this; - - EventEmitter.call(this); - - var opts = { +const SCServerSocket = require('./scserversocket'); +const AuthEngine = require('sc-auth').AuthEngine; +const formatter = require('sc-formatter'); +const base64id = require('base64id'); +const async = require('async'); +const url = require('url'); +const crypto = require('crypto'); +const uuid = require('uuid'); +const SCSimpleBroker = require('sc-simple-broker').SCSimpleBroker; +const AsyncStreamEmitter = require('async-stream-emitter'); + +const scErrors = require('sc-errors'); +const AuthTokenExpiredError = scErrors.AuthTokenExpiredError; +const AuthTokenInvalidError = scErrors.AuthTokenInvalidError; +const AuthTokenNotBeforeError = scErrors.AuthTokenNotBeforeError; +const AuthTokenError = scErrors.AuthTokenError; +const SilentMiddlewareBlockedError = scErrors.SilentMiddlewareBlockedError; +const InvalidArgumentsError = scErrors.InvalidArgumentsError; +const InvalidOptionsError = scErrors.InvalidOptionsError; +const InvalidActionError = scErrors.InvalidActionError; +const BrokerError = scErrors.BrokerError; +const ServerProtocolError = scErrors.ServerProtocolError; + + +function SCServer(options) { + AsyncStreamEmitter.call(this); + + let opts = { brokerEngine: new SCSimpleBroker(), wsEngine: 'ws', wsEngineServerOptions: {}, @@ -53,7 +50,8 @@ var SCServer = function (options) { this.MIDDLEWARE_HANDSHAKE_WS = 'handshakeWS'; this.MIDDLEWARE_HANDSHAKE_SC = 'handshakeSC'; - this.MIDDLEWARE_EMIT = 'emit'; + this.MIDDLEWARE_TRANSMIT = 'transmit'; + this.MIDDLEWARE_INVOKE = 'invoke'; this.MIDDLEWARE_SUBSCRIBE = 'subscribe'; this.MIDDLEWARE_PUBLISH_IN = 'publishIn'; this.MIDDLEWARE_PUBLISH_OUT = 'publishOut'; @@ -65,7 +63,8 @@ var SCServer = function (options) { this._middleware = {}; this._middleware[this.MIDDLEWARE_HANDSHAKE_WS] = []; this._middleware[this.MIDDLEWARE_HANDSHAKE_SC] = []; - this._middleware[this.MIDDLEWARE_EMIT] = []; + this._middleware[this.MIDDLEWARE_TRANSMIT] = []; + this._middleware[this.MIDDLEWARE_INVOKE] = []; this._middleware[this.MIDDLEWARE_SUBSCRIBE] = []; this._middleware[this.MIDDLEWARE_PUBLISH_IN] = []; this._middleware[this.MIDDLEWARE_PUBLISH_OUT] = []; @@ -90,25 +89,37 @@ var SCServer = function (options) { // Make sure there is always a leading and a trailing slash in the WS path. this._path = opts.path.replace(/\/?$/, '/').replace(/^\/?/, '/'); - this.isReady = false; - this.brokerEngine.once('ready', () => { + if (this.brokerEngine.isReady) { this.isReady = true; - this.emit('ready'); - }); + this.emit('ready', {}); + } else { + this.isReady = false; + (async () => { + await this.brokerEngine.listener('ready').once(); + this.isReady = true; + this.emit('ready', {}); + })(); + } - var wsEngine = typeof opts.wsEngine === 'string' ? require(opts.wsEngine) : opts.wsEngine; + let wsEngine = typeof opts.wsEngine === 'string' ? require(opts.wsEngine) : opts.wsEngine; if (!wsEngine || !wsEngine.Server) { - throw new InvalidOptionsError('The wsEngine option must be a path or module name which points ' + - 'to a valid WebSocket engine module with a compatible interface'); + throw new InvalidOptionsError( + 'The wsEngine option must be a path or module name which points ' + + 'to a valid WebSocket engine module with a compatible interface' + ); } - var WSServer = wsEngine.Server; + let WSServer = wsEngine.Server; if (opts.authPrivateKey != null || opts.authPublicKey != null) { if (opts.authPrivateKey == null) { - throw new InvalidOptionsError('The authPrivateKey option must be specified if authPublicKey is specified'); + throw new InvalidOptionsError( + 'The authPrivateKey option must be specified if authPublicKey is specified' + ); } else if (opts.authPublicKey == null) { - throw new InvalidOptionsError('The authPublicKey option must be specified if authPrivateKey is specified'); + throw new InvalidOptionsError( + 'The authPublicKey option must be specified if authPrivateKey is specified' + ); } this.signatureKey = opts.authPrivateKey; this.verificationKey = opts.authPublicKey; @@ -162,7 +173,7 @@ var SCServer = function (options) { this.exchange = this.brokerEngine.exchange(); - var wsServerOptions = opts.wsEngineServerOptions || {}; + let wsServerOptions = opts.wsEngineServerOptions || {}; wsServerOptions.server = this.httpServer; wsServerOptions.verifyClient = this.verifyHandshake.bind(this); @@ -186,9 +197,9 @@ var SCServer = function (options) { this.wsServer.on('error', this._handleServerError.bind(this)); this.wsServer.on('connection', this._handleSocketConnection.bind(this)); -}; +} -SCServer.prototype = Object.create(EventEmitter.prototype); +SCServer.prototype = Object.create(AsyncStreamEmitter.prototype); SCServer.prototype.setAuthEngine = function (authEngine) { this.auth = authEngine; @@ -198,40 +209,47 @@ SCServer.prototype.setCodecEngine = function (codecEngine) { this.codec = codecEngine; }; +SCServer.prototype.emitError = function (error) { + this.emit('error', {error}); +}; + +SCServer.prototype.emitWarning = function (warning) { + this.emit('warning', {warning}); +}; + SCServer.prototype._handleServerError = function (error) { if (typeof error === 'string') { error = new ServerProtocolError(error); } - this.emit('error', error); + this.emitError(error); }; -SCServer.prototype._handleSocketError = function (error) { - // We don't want to crash the entire worker on socket error - // so we emit it as a warning instead. - this.emit('warning', error); +SCServer.prototype._handleSocketErrors = async function (socket) { + // A socket error will show up as a warning on the server. + for await (let event of socket.listener('error')) { + this.emitWarning(event.error); + } }; SCServer.prototype._handleHandshakeTimeout = function (scSocket) { scSocket.disconnect(4005); }; -SCServer.prototype._subscribeSocket = function (socket, channelOptions, callback) { +SCServer.prototype._subscribeSocket = async function (socket, channelOptions) { if (!channelOptions) { - callback && callback('Socket ' + socket.id + ' provided a malformated channel payload'); - return; + throw new InvalidActionError(`Socket ${socket.id} provided a malformated channel payload`); } if (this.socketChannelLimit && socket.channelSubscriptionsCount >= this.socketChannelLimit) { - callback && callback('Socket ' + socket.id + ' tried to exceed the channel subscription limit of ' + - this.socketChannelLimit); - return; + throw new InvalidActionError( + `Socket ${socket.id} tried to exceed the channel subscription limit of ${this.socketChannelLimit}` + ); } - var channelName = channelOptions.channel; + let channelName = channelOptions.channel; if (typeof channelName !== 'string') { - callback && callback('Socket ' + socket.id + ' provided an invalid channel name'); - return; + throw new InvalidActionError(`Socket ${socket.id} provided an invalid channel name`); } if (socket.channelSubscriptionsCount == null) { @@ -242,22 +260,21 @@ SCServer.prototype._subscribeSocket = function (socket, channelOptions, callback socket.channelSubscriptionsCount++; } - this.brokerEngine.subscribeSocket(socket, channelName) - .then(() => { - return null; - }) - .catch((err) => { - return err; - }) - .then((err) => { - if (err) { - delete socket.channelSubscriptions[channelName]; - socket.channelSubscriptionsCount--; - } else { - socket.emit('subscribe', channelName, channelOptions); - this.emit('subscription', socket, channelName, channelOptions); - } - callback && callback(err); + try { + await this.brokerEngine.subscribeSocket(socket, channelName); + } catch (err) { + delete socket.channelSubscriptions[channelName]; + socket.channelSubscriptionsCount--; + throw err; + } + socket.emit('subscribe', { + channel: channelName, + subscribeOptions: channelOptions + }); + this.emit('subscription', { + socket, + channel: channelName, + subscribeOptions: channelOptions }); }; @@ -269,10 +286,14 @@ SCServer.prototype._unsubscribeSocketFromAllChannels = function (socket) { SCServer.prototype._unsubscribeSocket = function (socket, channel) { if (typeof channel !== 'string') { - throw new InvalidActionError('Socket ' + socket.id + ' tried to unsubscribe from an invalid channel name'); + throw new InvalidActionError( + `Socket ${socket.id} tried to unsubscribe from an invalid channel name` + ); } if (!socket.channelSubscriptions[channel]) { - throw new InvalidActionError('Socket ' + socket.id + ' tried to unsubscribe from a channel which it is not subscribed to'); + throw new InvalidActionError( + `Socket ${socket.id} tried to unsubscribe from a channel which it is not subscribed to` + ); } delete socket.channelSubscriptions[channel]; @@ -282,13 +303,13 @@ SCServer.prototype._unsubscribeSocket = function (socket, channel) { this.brokerEngine.unsubscribeSocket(socket, channel); - socket.emit('unsubscribe', channel); - this.emit('unsubscription', socket, channel); + socket.emit('unsubscribe', {channel}); + this.emit('unsubscription', {socket, channel}); }; SCServer.prototype._processTokenError = function (err) { - var authError = null; - var isBadToken = true; + let authError = null; + let isBadToken = true; if (err) { if (err.name === 'TokenExpiredError') { @@ -311,22 +332,29 @@ SCServer.prototype._processTokenError = function (err) { }; SCServer.prototype._emitBadAuthTokenError = function (scSocket, error, signedAuthToken) { - var badAuthStatus = { + let badAuthStatus = { authError: error, signedAuthToken: signedAuthToken }; - scSocket.emit('badAuthToken', badAuthStatus); - this.emit('badSocketAuthToken', scSocket, badAuthStatus); + scSocket.emit('badAuthToken', { + authError: error, + signedAuthToken: signedAuthToken + }); + this.emit('badSocketAuthToken', { + socket: scSocket, + authError: error, + signedAuthToken: signedAuthToken + }); }; SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, callback) { - var verificationOptions = Object.assign({socket: scSocket}, this.defaultVerificationOptions); + let verificationOptions = Object.assign({socket: scSocket}, this.defaultVerificationOptions); - var handleVerifyTokenResult = (result) => { - var err = result.error; - var token = result.token; + let handleVerifyTokenResult = (result) => { + let err = result.error; + let token = result.token; - var oldState = scSocket.authState; + let oldAuthState = scSocket.authState; if (token) { scSocket.signedAuthToken = signedAuthToken; scSocket.authToken = token; @@ -356,25 +384,25 @@ SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, call } // If an error is passed back from the authenticate middleware, it will be treated as a // server warning and not a socket error. - callback(middlewareError, isBadToken || false, oldState); + callback(middlewareError, isBadToken || false, oldAuthState); }); } else { - var errorData = this._processTokenError(err); + let errorData = this._processTokenError(err); // If the error is related to the JWT being badly formatted, then we will // treat the error as a socket error. if (err && signedAuthToken != null) { - scSocket.emit('error', errorData.authError); + scSocket.emitError(errorData.authError); if (errorData.isBadToken) { this._emitBadAuthTokenError(scSocket, errorData.authError, signedAuthToken); } } - callback(errorData.authError, errorData.isBadToken, oldState); + callback(errorData.authError, errorData.isBadToken, oldAuthState); } }; - var verifyTokenResult; - var verifyTokenError; + let verifyTokenResult; + let verifyTokenError; try { verifyTokenResult = this.auth.verifyToken(signedAuthToken, this.verificationKey, verificationOptions); @@ -383,16 +411,17 @@ SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, call } if (verifyTokenResult instanceof Promise) { - verifyTokenResult - .then((token) => { - return {token: token}; - }) - .catch((err) => { - return {error: err}; - }) - .then(handleVerifyTokenResult); + (async () => { + let result = {}; + try { + result.token = await verifyTokenResult; + } catch (err) { + result.error = err; + } + handleVerifyTokenResult(result); + })(); } else { - var result = { + let result = { token: verifyTokenResult, error: verifyTokenError }; @@ -401,218 +430,265 @@ SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, call }; SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { - if (this.options.wsEngine === 'ws') { - // Normalize ws module to match sc-uws module. + if (!wsSocket.upgradeReq) { + // Normalize ws modules to match. wsSocket.upgradeReq = upgradeReq; } - var id = this.generateId(); + let id = this.generateId(); - var scSocket = new SCServerSocket(id, this, wsSocket); + let scSocket = new SCServerSocket(id, this, wsSocket); scSocket.exchange = this.exchange; - scSocket.on('error', (err) => { - this._handleSocketError(err); - }); + this._handleSocketErrors(scSocket); this.pendingClients[id] = scSocket; this.pendingClientsCount++; - scSocket.on('#authenticate', (signedAuthToken, respond) => { - this._processAuthToken(scSocket, signedAuthToken, (err, isBadToken, oldState) => { - if (err) { - if (isBadToken) { - scSocket.deauthenticate(); - } - } else { - scSocket.triggerAuthenticationEvents(oldState); - } - var authStatus = { - isAuthenticated: !!scSocket.authToken, - authError: scErrors.dehydrateError(err) - }; - if (err && isBadToken) { - respond(err, authStatus); - } else { - respond(null, authStatus); - } - }); - }); - - scSocket.on('#removeAuthToken', () => { - scSocket.deauthenticateSelf(); - }); + let handleSocketAuthenticate = async () => { + for await (let rpc of scSocket.procedure('#authenticate')) { + let signedAuthToken = rpc.data; - scSocket.on('#subscribe', (channelOptions, res) => { - if (!channelOptions) { - channelOptions = {}; - } else if (typeof channelOptions === 'string') { - channelOptions = { - channel: channelOptions - }; - } - // This is an invalid state; it means the client tried to subscribe before - // having completed the handshake. - if (scSocket.state === scSocket.OPEN) { - this._subscribeSocket(scSocket, channelOptions, (err) => { + this._processAuthToken(scSocket, signedAuthToken, (err, isBadToken, oldAuthState) => { if (err) { - var error = new BrokerError('Failed to subscribe socket to the ' + channelOptions.channel + ' channel - ' + err); - res(error); - scSocket.emit('error', error); - } else { - if (channelOptions.batch) { - res(undefined, undefined, {batch: true}); - } else { - res(); + if (isBadToken) { + scSocket.deauthenticate(); } + } else { + scSocket.triggerAuthenticationEvents(oldAuthState); + } + if (err && isBadToken) { + rpc.error(err); + } else { + let authStatus = { + isAuthenticated: !!scSocket.authToken, + authError: scErrors.dehydrateError(err) + }; + rpc.end(authStatus); } }); - } else { - var error = new InvalidActionError('Cannot subscribe socket to a channel before it has completed the handshake'); - res(error); - this.emit('warning', error); } - }); + }; + handleSocketAuthenticate(); - scSocket.on('#unsubscribe', (channel, res) => { - var error; - try { - this._unsubscribeSocket(scSocket, channel); - } catch (err) { - error = new BrokerError('Failed to unsubscribe socket from the ' + channel + ' channel - ' + err.message); + let handleSocketRemoveAuthToken = async () => { + for await (let data of scSocket.receiver('#removeAuthToken')) { + scSocket.deauthenticateSelf(); } - if (error) { - res(error); - scSocket.emit('error', error); - } else { - res(); + }; + handleSocketRemoveAuthToken(); + + let handleSocketSubscribe = async () => { + for await (let rpc of scSocket.procedure('#subscribe')) { + let channelOptions = rpc.data; + + if (!channelOptions) { + channelOptions = {}; + } else if (typeof channelOptions === 'string') { + channelOptions = { + channel: channelOptions + }; + } + + (async () => { + if (scSocket.state === scSocket.OPEN) { + try { + await this._subscribeSocket(scSocket, channelOptions); + } catch (err) { + let error = new BrokerError(`Failed to subscribe socket to the ${channelOptions.channel} channel - ${err}`); + rpc.error(error); + scSocket.emitError(error); + return; + } + if (channelOptions.batch) { + rpc.end(undefined, {batch: true}); + return; + } + rpc.end(); + return; + } + // This is an invalid state; it means the client tried to subscribe before + // having completed the handshake. + let error = new InvalidActionError('Cannot subscribe socket to a channel before it has completed the handshake'); + rpc.error(error); + this.emitWarning(error); + })(); } - }); + }; + handleSocketSubscribe(); + + let handleSocketUnsubscribe = async () => { + for await (let rpc of scSocket.procedure('#unsubscribe')) { + let channel = rpc.data; + let error; + try { + this._unsubscribeSocket(scSocket, channel); + } catch (err) { + error = new BrokerError( + `Failed to unsubscribe socket from the ${channel} channel - ${err}` + ); + } + if (error) { + rpc.error(error); + scSocket.emitError(error); + } else { + rpc.end(); + } + } + }; + handleSocketUnsubscribe(); - var cleanupSocket = (type, code, data) => { + let cleanupSocket = (type, code, reason) => { clearTimeout(scSocket._handshakeTimeoutRef); - scSocket.off('#handshake'); - scSocket.off('#authenticate'); - scSocket.off('#removeAuthToken'); - scSocket.off('#subscribe'); - scSocket.off('#unsubscribe'); - scSocket.off('authenticate'); - scSocket.off('authStateChange'); - scSocket.off('deauthenticate'); - scSocket.off('_disconnect'); - scSocket.off('_connectAbort'); + scSocket.closeProcedure('#handshake'); + scSocket.closeProcedure('#authenticate'); + scSocket.closeProcedure('#subscribe'); + scSocket.closeProcedure('#unsubscribe'); + scSocket.closeReceiver('#removeAuthToken'); + scSocket.closeListener('authenticate'); + scSocket.closeListener('authStateChange'); + scSocket.closeListener('deauthenticate'); - var isClientFullyConnected = !!this.clients[id]; + let isClientFullyConnected = !!this.clients[id]; if (isClientFullyConnected) { delete this.clients[id]; this.clientsCount--; } - var isClientPending = !!this.pendingClients[id]; + let isClientPending = !!this.pendingClients[id]; if (isClientPending) { delete this.pendingClients[id]; this.pendingClientsCount--; } - this._unsubscribeSocketFromAllChannels(scSocket); - if (type === 'disconnect') { - this.emit('_disconnection', scSocket, code, data); - this.emit('disconnection', scSocket, code, data); + this.emit('disconnection', { + socket: scSocket, + code, + reason + }); } else if (type === 'abort') { - this.emit('_connectionAbort', scSocket, code, data); - this.emit('connectionAbort', scSocket, code, data); + this.emit('connectionAbort', { + socket: scSocket, + code, + reason + }); } - this.emit('_closure', scSocket, code, data); - this.emit('closure', scSocket, code, data); + this.emit('closure', { + socket: scSocket, + code, + reason + }); + + this._unsubscribeSocketFromAllChannels(scSocket); }; - scSocket.once('_disconnect', cleanupSocket.bind(scSocket, 'disconnect')); - scSocket.once('_connectAbort', cleanupSocket.bind(scSocket, 'abort')); + let handleSocketDisconnect = async () => { + let event = await scSocket.listener('disconnect').once(); + cleanupSocket('disconnect', event.code, event.data); + }; + handleSocketDisconnect(); - scSocket._handshakeTimeoutRef = setTimeout(this._handleHandshakeTimeout.bind(this, scSocket), this.handshakeTimeout); - scSocket.once('#handshake', (data, respond) => { - if (!data) { - data = {}; - } - var signedAuthToken = data.authToken || null; - clearTimeout(scSocket._handshakeTimeoutRef); + let handleSocketAbort = async () => { + let event = await scSocket.listener('connectAbort').once(); + cleanupSocket('abort', event.code, event.data); + }; + handleSocketAbort(); - this._passThroughHandshakeSCMiddleware({ - socket: scSocket - }, (err, statusCode) => { - if (err) { - if (err.statusCode == null) { - err.statusCode = statusCode; - } - respond(err); - scSocket.disconnect(err.statusCode); - return; - } - this._processAuthToken(scSocket, signedAuthToken, (err, isBadToken, oldState) => { - if (scSocket.state === scSocket.CLOSED) { - return; - } + scSocket._handshakeTimeoutRef = setTimeout(this._handleHandshakeTimeout.bind(this, scSocket), this.handshakeTimeout); - var clientSocketStatus = { - id: scSocket.id, - pingTimeout: this.pingTimeout - }; - var serverSocketStatus = { - id: scSocket.id, - pingTimeout: this.pingTimeout - }; + let handleSocketHandshake = async () => { + for await (let rpc of scSocket.procedure('#handshake')) { + let data = rpc.data || {}; + let signedAuthToken = data.authToken || null; + clearTimeout(scSocket._handshakeTimeoutRef); + this._passThroughHandshakeSCMiddleware({ + socket: scSocket + }, (err, statusCode) => { if (err) { - if (signedAuthToken != null) { - // Because the token is optional as part of the handshake, we don't count - // it as an error if the token wasn't provided. - clientSocketStatus.authError = scErrors.dehydrateError(err); - serverSocketStatus.authError = err; - - if (isBadToken) { - scSocket.deauthenticate(); - } + if (err.statusCode == null) { + err.statusCode = statusCode; } + rpc.error(err); + scSocket.disconnect(err.statusCode); + return; } - clientSocketStatus.isAuthenticated = !!scSocket.authToken; - serverSocketStatus.isAuthenticated = clientSocketStatus.isAuthenticated; + this._processAuthToken(scSocket, signedAuthToken, (err, isBadToken, oldAuthState) => { + if (scSocket.state === scSocket.CLOSED) { + return; + } - if (this.pendingClients[id]) { - delete this.pendingClients[id]; - this.pendingClientsCount--; - } - this.clients[id] = scSocket; - this.clientsCount++; + let clientSocketStatus = { + id: scSocket.id, + pingTimeout: this.pingTimeout + }; + let serverSocketStatus = { + id: scSocket.id, + pingTimeout: this.pingTimeout + }; - scSocket.state = scSocket.OPEN; + if (err) { + if (signedAuthToken != null) { + // Because the token is optional as part of the handshake, we don't count + // it as an error if the token wasn't provided. + clientSocketStatus.authError = scErrors.dehydrateError(err); + serverSocketStatus.authError = err; + + if (isBadToken) { + scSocket.deauthenticate(); + } + } + } + clientSocketStatus.isAuthenticated = !!scSocket.authToken; + serverSocketStatus.isAuthenticated = clientSocketStatus.isAuthenticated; - scSocket.emit('connect', serverSocketStatus); - scSocket.emit('_connect', serverSocketStatus); + if (this.pendingClients[id]) { + delete this.pendingClients[id]; + this.pendingClientsCount--; + } + this.clients[id] = scSocket; + this.clientsCount++; + + scSocket.state = scSocket.OPEN; + + if (clientSocketStatus.isAuthenticated) { + // Needs to be executed after the connection event to allow + // consumers to be setup from inside the connection loop. + (async () => { + await this.listener('connection').once(); + scSocket.triggerAuthenticationEvents(oldAuthState); + })(); + } - this.emit('_connection', scSocket, serverSocketStatus); - this.emit('connection', scSocket, serverSocketStatus); + scSocket.emit('connect', serverSocketStatus); + this.emit('connection', {socket: scSocket, ...serverSocketStatus}); - if (clientSocketStatus.isAuthenticated) { - scSocket.triggerAuthenticationEvents(oldState); - } - // Treat authentication failure as a 'soft' error - respond(null, clientSocketStatus); + // Treat authentication failure as a 'soft' error + rpc.end(clientSocketStatus); + }); }); - }); - }); + } + }; + handleSocketHandshake(); // Emit event to signal that a socket handshake has been initiated. - // The _handshake event is for internal use (including third-party plugins) - this.emit('_handshake', scSocket); - this.emit('handshake', scSocket); + this.emit('handshake', {socket: scSocket}); }; SCServer.prototype.close = function () { this.isReady = false; - this.wsServer.close.apply(this.wsServer, arguments); + return new Promise((resolve, reject) => { + this.wsServer.close((err) => { + if (err) { + reject(err); + return; + } + resolve(); + }); + }); }; SCServer.prototype.getPath = function () { @@ -632,26 +708,26 @@ SCServer.prototype.addMiddleware = function (type, middleware) { }; SCServer.prototype.removeMiddleware = function (type, middleware) { - var middlewareFunctions = this._middleware[type]; + let middlewareFunctions = this._middleware[type]; this._middleware[type] = middlewareFunctions.filter((fn) => { return fn !== middleware; }); }; -SCServer.prototype.verifyHandshake = function (info, cb) { - var req = info.req; - var origin = info.origin; +SCServer.prototype.verifyHandshake = function (info, callback) { + let req = info.req; + let origin = info.origin; if (origin === 'null' || origin == null) { origin = '*'; } - var ok = false; + let ok = false; if (this._allowAllOrigins) { ok = true; } else { try { - var parts = url.parse(origin); + let parts = url.parse(origin); parts.port = parts.port || 80; ok = ~this.origins.indexOf(parts.hostname + ':' + parts.port) || ~this.origins.indexOf(parts.hostname + ':*') || @@ -660,196 +736,293 @@ SCServer.prototype.verifyHandshake = function (info, cb) { } if (ok) { - var handshakeMiddleware = this._middleware[this.MIDDLEWARE_HANDSHAKE_WS]; + let handshakeMiddleware = this._middleware[this.MIDDLEWARE_HANDSHAKE_WS]; if (handshakeMiddleware.length) { - var callbackInvoked = false; + let callbackInvoked = false; async.applyEachSeries(handshakeMiddleware, req, (err) => { if (callbackInvoked) { - this.emit('warning', new InvalidActionError('Callback for ' + this.MIDDLEWARE_HANDSHAKE_WS + ' middleware was already invoked')); + this.emitWarning( + new InvalidActionError( + `Callback for ${this.MIDDLEWARE_HANDSHAKE_WS} middleware was already invoked` + ) + ); } else { callbackInvoked = true; if (err) { if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + this.MIDDLEWARE_HANDSHAKE_WS + ' middleware', this.MIDDLEWARE_HANDSHAKE_WS); + err = new SilentMiddlewareBlockedError( + `Action was silently blocked by ${this.MIDDLEWARE_HANDSHAKE_WS} middleware`, + this.MIDDLEWARE_HANDSHAKE_WS + ); } else if (this.middlewareEmitWarnings) { - this.emit('warning', err); + this.emitWarning(err); } - cb(false, 401, err); + callback(false, 401, typeof err === 'string' ? err : err.message); } else { - cb(true); + callback(true); } } }); } else { - cb(true); + callback(true); } } else { - var err = new ServerProtocolError('Failed to authorize socket handshake - Invalid origin: ' + origin); - this.emit('warning', err); - cb(false, 403, err); + let err = new ServerProtocolError( + `Failed to authorize socket handshake - Invalid origin: ${origin}` + ); + this.emitWarning(err); + callback(false, 403, err.message); } }; -SCServer.prototype._isPrivateTransmittedEvent = function (event) { +SCServer.prototype._isReservedRemoteEvent = function (event) { return typeof event === 'string' && event.indexOf('#') === 0; }; -SCServer.prototype.verifyInboundEvent = function (socket, eventName, eventData, cb) { - var request = { - socket: socket, - event: eventName, - data: eventData - }; - - var token = socket.getAuthToken(); +SCServer.prototype.verifyInboundRemoteEvent = function (requestOptions, callback) { + let socket = requestOptions.socket; + let token = socket.getAuthToken(); if (this.isAuthTokenExpired(token)) { - request.authTokenExpiredError = new AuthTokenExpiredError('The socket auth token has expired', token.exp); + requestOptions.authTokenExpiredError = new AuthTokenExpiredError( + 'The socket auth token has expired', + token.exp + ); socket.deauthenticate(); } - this._passThroughMiddleware(request, cb); + this._passThroughMiddleware(requestOptions, callback); }; SCServer.prototype.isAuthTokenExpired = function (token) { if (token && token.exp != null) { - var currentTime = Date.now(); - var expiryMilliseconds = token.exp * 1000; + let currentTime = Date.now(); + let expiryMilliseconds = token.exp * 1000; return currentTime > expiryMilliseconds; } return false; }; -SCServer.prototype._passThroughMiddleware = function (options, cb) { - var callbackInvoked = false; +SCServer.prototype._processPublishAction = function (options, request, callback) { + let callbackInvoked = false; - var request = { - socket: options.socket - }; + if (this.allowClientPublish) { + let eventData = options.data || {}; + request.channel = eventData.channel; + request.data = eventData.data; - if (options.authTokenExpiredError != null) { - request.authTokenExpiredError = options.authTokenExpiredError; - } - - var event = options.event; - - if (this._isPrivateTransmittedEvent(event)) { - if (event === '#subscribe') { - var eventData = options.data || {}; - request.channel = eventData.channel; - request.waitForAuth = eventData.waitForAuth; - request.data = eventData.data; - - if (request.waitForAuth && request.authTokenExpiredError) { - // If the channel has the waitForAuth flag set, then we will handle the expiry quietly - // and we won't pass this request through the subscribe middleware. - cb(request.authTokenExpiredError, eventData); - } else { - async.applyEachSeries(this._middleware[this.MIDDLEWARE_SUBSCRIBE], request, - (err) => { - if (callbackInvoked) { - this.emit('warning', new InvalidActionError('Callback for ' + this.MIDDLEWARE_SUBSCRIBE + ' middleware was already invoked')); - } else { - callbackInvoked = true; - if (err) { - if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + this.MIDDLEWARE_SUBSCRIBE + ' middleware', this.MIDDLEWARE_SUBSCRIBE); - } else if (this.middlewareEmitWarnings) { - this.emit('warning', err); - } - } - if (request.data !== undefined) { - eventData.data = request.data; - } - cb(err, eventData); - } + async.applyEachSeries(this._middleware[this.MIDDLEWARE_PUBLISH_IN], request, + (err) => { + if (callbackInvoked) { + this.emitWarning( + new InvalidActionError( + `Callback for ${this.MIDDLEWARE_PUBLISH_IN} middleware was already invoked` + ) + ); + } else { + callbackInvoked = true; + if (request.data !== undefined) { + eventData.data = request.data; } - ); - } - } else if (event === '#publish') { - if (this.allowClientPublish) { - var eventData = options.data || {}; - request.channel = eventData.channel; - request.data = eventData.data; - - async.applyEachSeries(this._middleware[this.MIDDLEWARE_PUBLISH_IN], request, - (err) => { - if (callbackInvoked) { - this.emit('warning', new InvalidActionError('Callback for ' + this.MIDDLEWARE_PUBLISH_IN + ' middleware was already invoked')); - } else { - callbackInvoked = true; - if (request.data !== undefined) { - eventData.data = request.data; - } - if (err) { - if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + this.MIDDLEWARE_PUBLISH_IN + ' middleware', this.MIDDLEWARE_PUBLISH_IN); - } else if (this.middlewareEmitWarnings) { - this.emit('warning', err); - } - cb(err, eventData, request.ackData); - } else { - if (typeof request.channel !== 'string') { - err = new BrokerError('Socket ' + request.socket.id + ' tried to publish to an invalid ' + request.channel + ' channel'); - this.emit('warning', err); - cb(err, eventData, request.ackData); - return; - } - this.exchange.publish(request.channel, request.data) - .then(() => { - return null; - }) - .catch((err) => { - return err; - }) - .then((err) => { - if (err) { - this.emit('warning', err); - } - cb(err, eventData, request.ackData); - }); - } + if (err) { + if (err === true || err.silent) { + err = new SilentMiddlewareBlockedError( + `Action was silently blocked by ${this.MIDDLEWARE_PUBLISH_IN} middleware`, + this.MIDDLEWARE_PUBLISH_IN + ); + } else if (this.middlewareEmitWarnings) { + this.emitWarning(err); + } + callback(err, eventData, request.ackData); + } else { + if (typeof request.channel !== 'string') { + err = new BrokerError( + `Socket ${request.socket.id} tried to publish to an invalid ${request.channel} channel` + ); + this.emitWarning(err); + callback(err, eventData, request.ackData); + return; } + (async () => { + let error; + try { + await this.exchange.publish(request.channel, request.data); + } catch (err) { + error = err; + this.emitWarning(error); + } + callback(error, eventData, request.ackData); + })(); } - ); - } else { - var noPublishError = new InvalidActionError('Client publish feature is disabled'); - this.emit('warning', noPublishError); - cb(noPublishError, options.data); + } } - } else { - // Do not allow blocking other reserved events or it could interfere with SC behaviour - cb(null, options.data); - } + ); } else { - request.event = event; - request.data = options.data; + let noPublishError = new InvalidActionError('Client publish feature is disabled'); + this.emitWarning(noPublishError); + callback(noPublishError); + } +}; + +SCServer.prototype._processSubscribeAction = function (options, request, callback) { + let callbackInvoked = false; - async.applyEachSeries(this._middleware[this.MIDDLEWARE_EMIT], request, + let eventData = options.data || {}; + request.channel = eventData.channel; + request.waitForAuth = eventData.waitForAuth; + request.data = eventData.data; + + if (request.waitForAuth && request.authTokenExpiredError) { + // If the channel has the waitForAuth flag set, then we will handle the expiry quietly + // and we won't pass this request through the subscribe middleware. + callback(request.authTokenExpiredError, eventData); + } else { + async.applyEachSeries(this._middleware[this.MIDDLEWARE_SUBSCRIBE], request, (err) => { if (callbackInvoked) { - this.emit('warning', new InvalidActionError('Callback for ' + this.MIDDLEWARE_EMIT + ' middleware was already invoked')); + this.emitWarning( + new InvalidActionError( + `Callback for ${this.MIDDLEWARE_SUBSCRIBE} middleware was already invoked` + ) + ); } else { callbackInvoked = true; if (err) { if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + this.MIDDLEWARE_EMIT + ' middleware', this.MIDDLEWARE_EMIT); + err = new SilentMiddlewareBlockedError( + `Action was silently blocked by ${this.MIDDLEWARE_SUBSCRIBE} middleware`, + this.MIDDLEWARE_SUBSCRIBE + ); } else if (this.middlewareEmitWarnings) { - this.emit('warning', err); + this.emitWarning(err); } } - cb(err, request.data); + if (request.data !== undefined) { + eventData.data = request.data; + } + callback(err, eventData); } } ); } }; -SCServer.prototype._passThroughAuthenticateMiddleware = function (options, cb) { - var self = this; - var callbackInvoked = false; +SCServer.prototype._processTransmitAction = function (options, request, callback) { + let callbackInvoked = false; + + request.event = options.event; + request.data = options.data; - var request = { + async.applyEachSeries(this._middleware[this.MIDDLEWARE_TRANSMIT], request, + (err) => { + if (callbackInvoked) { + this.emitWarning( + new InvalidActionError( + `Callback for ${this.MIDDLEWARE_TRANSMIT} middleware was already invoked` + ) + ); + } else { + callbackInvoked = true; + if (err) { + if (err === true || err.silent) { + err = new SilentMiddlewareBlockedError( + `Action was silently blocked by ${this.MIDDLEWARE_TRANSMIT} middleware`, + this.MIDDLEWARE_TRANSMIT + ); + } else if (this.middlewareEmitWarnings) { + this.emitWarning(err); + } + } + callback(err, request.data); + } + } + ); +}; + +SCServer.prototype._processInvokeAction = function (options, request, callback) { + let callbackInvoked = false; + + request.event = options.event; + request.data = options.data; + + async.applyEachSeries(this._middleware[this.MIDDLEWARE_INVOKE], request, + (err) => { + if (callbackInvoked) { + this.emitWarning( + new InvalidActionError( + `Callback for ${this.MIDDLEWARE_INVOKE} middleware was already invoked` + ) + ); + } else { + callbackInvoked = true; + if (err) { + if (err === true || err.silent) { + err = new SilentMiddlewareBlockedError( + `Action was silently blocked by ${this.MIDDLEWARE_INVOKE} middleware`, + this.MIDDLEWARE_INVOKE + ); + } else if (this.middlewareEmitWarnings) { + this.emitWarning(err); + } + } + callback(err, request.data); + } + } + ); +}; + +SCServer.prototype._passThroughMiddleware = function (options, callback) { + let request = { + socket: options.socket + }; + + if (options.authTokenExpiredError != null) { + request.authTokenExpiredError = options.authTokenExpiredError; + } + + let event = options.event; + + if (options.cid == null) { + // If transmit. + if (this._isReservedRemoteEvent(event)) { + if (event === '#publish') { + this._processPublishAction(options, request, callback); + } else if (event === '#removeAuthToken') { + callback(null, options.data); + } else { + let error = new InvalidActionError(`The reserved transmitted event ${event} is not supported`); + callback(error); + } + } else { + this._processTransmitAction(options, request, callback); + } + } else { + // If invoke/RPC. + if (this._isReservedRemoteEvent(event)) { + if (event === '#subscribe') { + this._processSubscribeAction(options, request, callback); + } else if (event === '#publish') { + this._processPublishAction(options, request, callback); + } else if ( + event === '#handshake' || + event === '#authenticate' || + event === '#unsubscribe' + ) { + callback(null, options.data); + } else { + let error = new InvalidActionError(`The reserved invoked event ${event} is not supported`); + callback(error); + } + } else { + this._processInvokeAction(options, request, callback); + } + } +}; + +SCServer.prototype._passThroughAuthenticateMiddleware = function (options, callback) { + let callbackInvoked = false; + + let request = { socket: options.socket, authToken: options.authToken }; @@ -857,41 +1030,51 @@ SCServer.prototype._passThroughAuthenticateMiddleware = function (options, cb) { async.applyEachSeries(this._middleware[this.MIDDLEWARE_AUTHENTICATE], request, (err, results) => { if (callbackInvoked) { - self.emit('warning', new InvalidActionError('Callback for ' + self.MIDDLEWARE_AUTHENTICATE + ' middleware was already invoked')); + this.emitWarning( + new InvalidActionError( + `Callback for ${this.MIDDLEWARE_AUTHENTICATE} middleware was already invoked` + ) + ); } else { callbackInvoked = true; - var isBadToken = false; + let isBadToken = false; if (results.length) { isBadToken = results[results.length - 1] || false; } if (err) { if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + self.MIDDLEWARE_AUTHENTICATE + ' middleware', self.MIDDLEWARE_AUTHENTICATE); - } else if (self.middlewareEmitWarnings) { - self.emit('warning', err); + err = new SilentMiddlewareBlockedError( + `Action was silently blocked by ${this.MIDDLEWARE_AUTHENTICATE} middleware`, + this.MIDDLEWARE_AUTHENTICATE + ); + } else if (this.middlewareEmitWarnings) { + this.emitWarning(err); } } - cb(err, isBadToken); + callback(err, isBadToken); } } ); }; -SCServer.prototype._passThroughHandshakeSCMiddleware = function (options, cb) { - var self = this; - var callbackInvoked = false; +SCServer.prototype._passThroughHandshakeSCMiddleware = function (options, callback) { + let callbackInvoked = false; - var request = { + let request = { socket: options.socket }; async.applyEachSeries(this._middleware[this.MIDDLEWARE_HANDSHAKE_SC], request, (err, results) => { if (callbackInvoked) { - self.emit('warning', new InvalidActionError('Callback for ' + self.MIDDLEWARE_HANDSHAKE_SC + ' middleware was already invoked')); + this.emitWarning( + new InvalidActionError( + `Callback for ${this.MIDDLEWARE_HANDSHAKE_SC} middleware was already invoked` + ) + ); } else { callbackInvoked = true; - var statusCode; + let statusCode; if (results.length) { statusCode = results[results.length - 1] || 4008; } else { @@ -902,24 +1085,25 @@ SCServer.prototype._passThroughHandshakeSCMiddleware = function (options, cb) { statusCode = err.statusCode; } if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + self.MIDDLEWARE_HANDSHAKE_SC + ' middleware', self.MIDDLEWARE_HANDSHAKE_SC); - } else if (self.middlewareEmitWarnings) { - self.emit('warning', err); + err = new SilentMiddlewareBlockedError( + `Action was silently blocked by ${this.MIDDLEWARE_HANDSHAKE_SC} middleware`, + this.MIDDLEWARE_HANDSHAKE_SC + ); + } else if (this.middlewareEmitWarnings) { + this.emitWarning(err); } } - cb(err, statusCode); + callback(err, statusCode); } } ); }; -SCServer.prototype.verifyOutboundEvent = function (socket, eventName, eventData, options, cb) { - var self = this; - - var callbackInvoked = false; +SCServer.prototype.verifyOutboundEvent = function (socket, eventName, eventData, options, callback) { + let callbackInvoked = false; if (eventName === '#publish') { - var request = { + let request = { socket: socket, channel: eventData.channel, data: eventData.data @@ -927,7 +1111,11 @@ SCServer.prototype.verifyOutboundEvent = function (socket, eventName, eventData, async.applyEachSeries(this._middleware[this.MIDDLEWARE_PUBLISH_OUT], request, (err) => { if (callbackInvoked) { - self.emit('warning', new InvalidActionError('Callback for ' + self.MIDDLEWARE_PUBLISH_OUT + ' middleware was already invoked')); + this.emitWarning( + new InvalidActionError( + `Callback for ${this.MIDDLEWARE_PUBLISH_OUT} middleware was already invoked` + ) + ); } else { callbackInvoked = true; if (request.data !== undefined) { @@ -935,22 +1123,25 @@ SCServer.prototype.verifyOutboundEvent = function (socket, eventName, eventData, } if (err) { if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError('Action was silently blocked by ' + self.MIDDLEWARE_PUBLISH_OUT + ' middleware', self.MIDDLEWARE_PUBLISH_OUT); - } else if (self.middlewareEmitWarnings) { - self.emit('warning', err); + err = new SilentMiddlewareBlockedError( + `Action was silently blocked by ${this.MIDDLEWARE_PUBLISH_OUT} middleware`, + this.MIDDLEWARE_PUBLISH_OUT + ); + } else if (this.middlewareEmitWarnings) { + this.emitWarning(err); } - cb(err, eventData); + callback(err, eventData); } else { if (options && request.useCache) { options.useCache = true; } - cb(null, eventData); + callback(null, eventData); } } } ); } else { - cb(null, eventData); + callback(null, eventData); } }; diff --git a/scserversocket.js b/scserversocket.js index 542ab48..ba0841c 100644 --- a/scserversocket.js +++ b/scserversocket.js @@ -1,19 +1,20 @@ -var cloneDeep = require('lodash.clonedeep'); -var Emitter = require('component-emitter'); -var Response = require('./response').Response; +const cloneDeep = require('lodash.clonedeep'); +const StreamDemux = require('stream-demux'); +const AsyncStreamEmitter = require('async-stream-emitter'); +const Response = require('./response').Response; -var scErrors = require('sc-errors'); -var InvalidArgumentsError = scErrors.InvalidArgumentsError; -var SocketProtocolError = scErrors.SocketProtocolError; -var TimeoutError = scErrors.TimeoutError; -var InvalidActionError = scErrors.InvalidActionError; -var AuthError = scErrors.AuthError; +const scErrors = require('sc-errors'); +const InvalidArgumentsError = scErrors.InvalidArgumentsError; +const SocketProtocolError = scErrors.SocketProtocolError; +const TimeoutError = scErrors.TimeoutError; +const InvalidActionError = scErrors.InvalidActionError; +const AuthError = scErrors.AuthError; -var SCServerSocket = function (id, server, socket) { - Emitter.call(this); +function SCServerSocket(id, server, socket) { + AsyncStreamEmitter.call(this); - this._autoAckEvents = { + this._autoAckRPCs = { '#publish': 1 }; @@ -24,12 +25,11 @@ var SCServerSocket = function (id, server, socket) { this.authState = this.UNAUTHENTICATED; this.active = true; + this._receiverDemux = new StreamDemux(); + this._procedureDemux = new StreamDemux(); + this.request = this.socket.upgradeReq || {}; - var wsEngine = this.server.options.wsEngine; - if (wsEngine === 'sc-uws' || wsEngine === 'uws') { - this.request.connection = this.socket._socket; - } if (this.request.connection) { this.remoteAddress = this.request.connection.remoteAddress; this.remoteFamily = this.request.connection.remoteFamily; @@ -51,13 +51,11 @@ var SCServerSocket = function (id, server, socket) { this.channelSubscriptionsCount = 0; this.socket.on('error', (err) => { - this.emit('error', err); + this.emitError(err); }); this.socket.on('close', (code, data) => { - setTimeout(() => { - this._onSCClose(code, data); - }, 0); + this._onSCClose(code, data); }); if (!this.server.pingTimeoutDisabled) { @@ -69,39 +67,39 @@ var SCServerSocket = function (id, server, socket) { this.socket.on('message', (message, flags) => { this._resetPongTimeout(); - this.emit('message', message); + this.emit('message', {message}); - var obj; + let obj; try { obj = this.decode(message); } catch (err) { if (err.name === 'Error') { err.name = 'InvalidMessageError'; } - this.emit('error', err); + this.emitError(err); return; } // If pong if (obj === '#2') { - var token = this.getAuthToken(); + let token = this.getAuthToken(); if (this.server.isAuthTokenExpired(token)) { this.deauthenticate(); } } else { if (Array.isArray(obj)) { - var len = obj.length; - for (var i = 0; i < len; i++) { - this._handleEventObject(obj[i], message); + let len = obj.length; + for (let i = 0; i < len; i++) { + this._handleRemoteEventObject(obj[i], message); } } else { - this._handleEventObject(obj, message); + this._handleRemoteEventObject(obj, message); } } }); -}; +} -SCServerSocket.prototype = Object.create(Emitter.prototype); +SCServerSocket.prototype = Object.create(AsyncStreamEmitter.prototype); SCServerSocket.CONNECTING = SCServerSocket.prototype.CONNECTING = 'connecting'; SCServerSocket.OPEN = SCServerSocket.prototype.OPEN = 'open'; @@ -113,45 +111,83 @@ SCServerSocket.UNAUTHENTICATED = SCServerSocket.prototype.UNAUTHENTICATED = 'una SCServerSocket.ignoreStatuses = scErrors.socketProtocolIgnoreStatuses; SCServerSocket.errorStatuses = scErrors.socketProtocolErrorStatuses; +SCServerSocket.prototype.receiver = function (receiverName) { + return this._receiverDemux.stream(receiverName); +}; + +SCServerSocket.prototype.closeReceiver = function (receiverName) { + this._receiverDemux.close(receiverName); +}; + +SCServerSocket.prototype.procedure = function (procedureName) { + return this._procedureDemux.stream(procedureName); +}; + +SCServerSocket.prototype.closeProcedure = function (procedureName) { + this._procedureDemux.close(procedureName); +}; + SCServerSocket.prototype._sendPing = function () { if (this.state !== this.CLOSED) { this.sendObject('#1'); } }; -SCServerSocket.prototype._handleEventObject = function (obj, message) { +SCServerSocket.prototype._handleRemoteEventObject = function (obj, message) { if (obj && obj.event != null) { - var eventName = obj.event; + let eventName = obj.event; - var response = new Response(this, obj.cid); - this.server.verifyInboundEvent(this, eventName, obj.data, (err, newEventData, ackData) => { - if (err) { - response.error(err, ackData); - } else { - if (this._autoAckEvents[eventName]) { - if (ackData !== undefined) { - response.end(ackData); + let requestOptions = { + socket: this, + event: eventName, + data: obj.data, + }; + + if (obj.cid == null) { + this.server.verifyInboundRemoteEvent(requestOptions, (err, newEventData) => { + if (!err) { + this._receiverDemux.write(eventName, newEventData); + } + }); + } else { + requestOptions.cid = obj.cid; + let response = new Response(this, requestOptions.cid); + this.server.verifyInboundRemoteEvent(requestOptions, (err, newEventData, ackData) => { + if (err) { + response.error(err); + } else { + if (this._autoAckRPCs[eventName]) { + if (ackData !== undefined) { + response.end(ackData); + } else { + response.end(); + } } else { - response.end(); + this._procedureDemux.write(eventName, { + data: newEventData, + end: (data) => { + response.end(data); + }, + error: (err) => { + response.error(err); + } + }); } - this.emit(eventName, newEventData); - } else { - this.emit(eventName, newEventData, response.callback.bind(response)); } - } - }); + }); + } } else if (obj && obj.rid != null) { // If incoming message is a response to a previously sent message - var ret = this._callbackMap[obj.rid]; + let ret = this._callbackMap[obj.rid]; if (ret) { clearTimeout(ret.timeout); delete this._callbackMap[obj.rid]; - var rehydratedError = scErrors.hydrateError(obj.error); + let rehydratedError = scErrors.hydrateError(obj.error); ret.callback(rehydratedError, obj.data); } } else { // The last remaining case is to treat the message as raw - this.emit('raw', message); + this.emit('raw', {message}); } }; @@ -178,46 +214,46 @@ SCServerSocket.prototype.getBytesReceived = function () { return this.socket.bytesReceived; }; -SCServerSocket.prototype._onSCClose = function (code, data) { +SCServerSocket.prototype.emitError = function (error) { + this.emit('error', { + error + }); +}; + +SCServerSocket.prototype._onSCClose = function (code, reason) { clearInterval(this._pingIntervalTicker); clearTimeout(this._pingTimeoutTicker); if (this.state !== this.CLOSED) { - var prevState = this.state; + let prevState = this.state; this.state = this.CLOSED; if (prevState === this.CONNECTING) { - // Private connectAbort event for internal use only - this.emit('_connectAbort', code, data); - this.emit('connectAbort', code, data); + this.emit('connectAbort', {code, reason}); } else { - // Private disconnect event for internal use only - this.emit('_disconnect', code, data); - this.emit('disconnect', code, data); + this.emit('disconnect', {code, reason}); } - // Private close event for internal use only - this.emit('_close', code, data); - this.emit('close', code, data); + this.emit('close', {code, reason}); if (!SCServerSocket.ignoreStatuses[code]) { - var closeMessage; - if (data) { - var reasonString; - if (typeof data === 'object') { + let closeMessage; + if (reason) { + let reasonString; + if (typeof reason === 'object') { try { - reasonString = JSON.stringify(data); + reasonString = JSON.stringify(reason); } catch (error) { - reasonString = data.toString(); + reasonString = reason.toString(); } } else { - reasonString = data; + reasonString = reason; } - closeMessage = 'Socket connection closed with status code ' + code + ' and reason: ' + reasonString; + closeMessage = `Socket connection closed with status code ${code} and reason: ${reasonString}`; } else { - closeMessage = 'Socket connection closed with status code ' + code; + closeMessage = `Socket connection closed with status code ${code}`; } - var err = new SocketProtocolError(SCServerSocket.errorStatuses[code] || closeMessage, code); - this.emit('error', err); + let err = new SocketProtocolError(SCServerSocket.errorStatuses[code] || closeMessage, code); + this.emitError(err); } } }; @@ -226,8 +262,8 @@ SCServerSocket.prototype.disconnect = function (code, data) { code = code || 1000; if (typeof code !== 'number') { - var err = new InvalidArgumentsError('If specified, the code argument must be a number'); - this.emit('error', err); + let err = new InvalidArgumentsError('If specified, the code argument must be a number'); + this.emitError(err); } if (this.state !== this.CLOSED) { @@ -270,11 +306,11 @@ SCServerSocket.prototype.sendObjectBatch = function (object) { this._batchTimeout = setTimeout(() => { delete this._batchTimeout; if (this._batchSendList.length) { - var str; + let str; try { str = this.encode(this._batchSendList); } catch (err) { - this.emit('error', err); + this.emitError(err); } if (str != null) { this.send(str); @@ -285,11 +321,11 @@ SCServerSocket.prototype.sendObjectBatch = function (object) { }; SCServerSocket.prototype.sendObjectSingle = function (object) { - var str; + let str; try { str = this.encode(object); } catch (err) { - this.emit('error', err); + this.emitError(err); } if (str != null) { this.send(str); @@ -306,7 +342,7 @@ SCServerSocket.prototype.sendObject = function (object, options) { SCServerSocket.prototype.transmit = function (event, data, options) { this.server.verifyOutboundEvent(this, event, data, options, (err, newData) => { - var eventObject = { + let eventObject = { event: event }; if (newData !== undefined) { @@ -332,7 +368,7 @@ SCServerSocket.prototype.invoke = function (event, data, options) { reject(err); return; } - var eventObject = { + let eventObject = { event: event, cid: this._nextCallId() }; @@ -340,8 +376,8 @@ SCServerSocket.prototype.invoke = function (event, data, options) { eventObject.data = newData; } - var timeout = setTimeout(() => { - var error = new TimeoutError("Event response for '" + event + "' timed out"); + let timeout = setTimeout(() => { + let error = new TimeoutError(`Event response for "${event}" timed out`); delete this._callbackMap[eventObject.cid]; reject(error); }, this.server.ackTimeout); @@ -367,23 +403,29 @@ SCServerSocket.prototype.invoke = function (event, data, options) { }); }; -SCServerSocket.prototype.triggerAuthenticationEvents = function (oldState) { - if (oldState !== this.AUTHENTICATED) { - var stateChangeData = { - oldState: oldState, - newState: this.authState, +SCServerSocket.prototype.triggerAuthenticationEvents = function (oldAuthState) { + if (oldAuthState !== this.AUTHENTICATED) { + let stateChangeData = { + oldAuthState, + newAuthState: this.authState, authToken: this.authToken }; this.emit('authStateChange', stateChangeData); - this.server.emit('authenticationStateChange', this, stateChangeData); + this.server.emit('authenticationStateChange', { + socket: this, + ...stateChangeData + }); } - this.emit('authenticate', this.authToken); - this.server.emit('authentication', this, this.authToken); + this.emit('authenticate', {authToken: this.authToken}); + this.server.emit('authentication', { + socket: this, + authToken: this.authToken + }); }; -SCServerSocket.prototype.setAuthToken = function (data, options) { - var authToken = cloneDeep(data); - var oldState = this.authState; +SCServerSocket.prototype.setAuthToken = async function (data, options) { + let authToken = cloneDeep(data); + let oldAuthState = this.authState; this.authState = this.AUTHENTICATED; if (options == null) { @@ -392,19 +434,21 @@ SCServerSocket.prototype.setAuthToken = function (data, options) { options = cloneDeep(options); if (options.algorithm != null) { delete options.algorithm; - var err = new InvalidArgumentsError('Cannot change auth token algorithm at runtime - It must be specified as a config option on launch'); - this.emit('error', err); + let err = new InvalidArgumentsError( + 'Cannot change auth token algorithm at runtime - It must be specified as a config option on launch' + ); + this.emitError(err); } } options.mutatePayload = true; - var rejectOnFailedDelivery = options.rejectOnFailedDelivery; + let rejectOnFailedDelivery = options.rejectOnFailedDelivery; delete options.rejectOnFailedDelivery; - var defaultSignatureOptions = this.server.defaultSignatureOptions; + let defaultSignatureOptions = this.server.defaultSignatureOptions; // We cannot have the exp claim on the token and the expiresIn option // set at the same time or else auth.signToken will throw an error. - var expiresIn; + let expiresIn; if (options.expiresIn == null) { expiresIn = defaultSignatureOptions.expiresIn; } else { @@ -431,63 +475,56 @@ SCServerSocket.prototype.setAuthToken = function (data, options) { this.authToken = authToken; - var handleSignTokenResult = (result) => { - if (result.error) { - this.emit('error', result.error); - this._onSCClose(4002, result.error.toString()); - this.socket.close(4002); - throw result.error; - } - var tokenData = { - token: result.signedToken + let handleAuthTokenSignFail = (error) => { + this.emitError(error); + this._onSCClose(4002, error.toString()); + this.socket.close(4002); + throw error; + }; + + let sendAuthTokenToClient = async (signedToken) => { + let tokenData = { + token: signedToken }; - if (this.authToken === authToken) { - this.signedAuthToken = result.signedToken; - this.emit('authTokenSigned', result.signedToken); + try { + return await this.invoke('#setAuthToken', tokenData); + } catch (err) { + throw new AuthError(`Failed to deliver auth token to client - ${err}`); } - return this.invoke('#setAuthToken', tokenData) - .catch((err) => { - var authError = new AuthError('Failed to deliver auth token to client - ' + err.message); - this.emit('error', authError); - if (rejectOnFailedDelivery) { - throw authError; - } - }); }; - var signTokenResult; - var signTokenError; - var signTokenPromise; + let signTokenResult; try { signTokenResult = this.server.auth.signToken(authToken, this.server.signatureKey, options); } catch (err) { - signTokenError = err; + handleAuthTokenSignFail(err); } + let signedAuthToken; if (signTokenResult instanceof Promise) { - signTokenPromise = signTokenResult - .then((signedToken) => { - return {signedToken: signedToken}; - }) - .catch((err) => { - return {error: err}; - }) - .then(handleSignTokenResult); - } else { - var result = { - signedToken: signTokenResult, - error: signTokenError - }; try { - signTokenPromise = handleSignTokenResult(result); + signedAuthToken = await signTokenResult; } catch (err) { - signTokenPromise = Promise.reject(err); + handleAuthTokenSignFail(err); } + } else { + signedAuthToken = signTokenResult; + } + if (this.authToken === authToken) { + this.signedAuthToken = signedAuthToken; + this.emit('authTokenSigned', {signedAuthToken}); } - this.triggerAuthenticationEvents(oldState); - return signTokenPromise; + this.triggerAuthenticationEvents(oldAuthState); + try { + await sendAuthTokenToClient(signedAuthToken); + } catch (err) { + this.emitError(err); + if (rejectOnFailedDelivery) { + throw err; + } + } }; SCServerSocket.prototype.getAuthToken = function () { @@ -495,21 +532,27 @@ SCServerSocket.prototype.getAuthToken = function () { }; SCServerSocket.prototype.deauthenticateSelf = function () { - var oldState = this.authState; - var oldToken = this.authToken; + let oldAuthState = this.authState; + let oldAuthToken = this.authToken; this.signedAuthToken = null; this.authToken = null; this.authState = this.UNAUTHENTICATED; - if (oldState !== this.UNAUTHENTICATED) { - var stateChangeData = { - oldState: oldState, - newState: this.authState + if (oldAuthState !== this.UNAUTHENTICATED) { + let stateChangeData = { + oldAuthState, + newAuthState: this.authState }; this.emit('authStateChange', stateChangeData); - this.server.emit('authenticationStateChange', this, stateChangeData); + this.server.emit('authenticationStateChange', { + socket: this, + ...stateChangeData + }); } - this.emit('deauthenticate', oldToken); - this.server.emit('deauthentication', this, oldToken); + this.emit('deauthenticate', {oldAuthToken}); + this.server.emit('deauthentication', { + socket: this, + oldAuthToken + }); }; SCServerSocket.prototype.deauthenticate = function () { @@ -522,12 +565,12 @@ SCServerSocket.prototype.kickOut = function (channel, message) { Object.keys(this.channelSubscriptions).forEach((channelName) => { delete this.channelSubscriptions[channelName]; this.channelSubscriptionsCount--; - this.emit('#kickOut', {message: message, channel: channelName}); + this.transmit('#kickOut', {message: message, channel: channelName}); }); } else { delete this.channelSubscriptions[channel]; this.channelSubscriptionsCount--; - this.emit('#kickOut', {message: message, channel: channel}); + this.transmit('#kickOut', {message: message, channel: channel}); } return this.server.brokerEngine.unsubscribeSocket(this, channel); }; diff --git a/test/integration.js b/test/integration.js index 19ca58d..6fef418 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1,135 +1,143 @@ -var assert = require('assert'); -var socketClusterServer = require('../'); -var socketCluster = require('socketcluster-client'); -var localStorage = require('localStorage'); -var SCSimpleBroker = require('sc-simple-broker').SCSimpleBroker; +const assert = require('assert'); +const socketClusterServer = require('../'); +const socketClusterClient = require('socketcluster-client'); +const localStorage = require('localStorage'); +const SCSimpleBroker = require('sc-simple-broker').SCSimpleBroker; // Add to the global scope like in browser. global.localStorage = localStorage; -var portNumber = 8008; +let portNumber = 8008; -var clientOptions; -var serverOptions; +let clientOptions; +let serverOptions; -var allowedUsers = { +let allowedUsers = { bob: true, alice: true }; -var TEN_DAYS_IN_SECONDS = 60 * 60 * 24 * 10; -var WS_ENGINE = 'ws'; +const TEN_DAYS_IN_SECONDS = 60 * 60 * 24 * 10; +const WS_ENGINE = 'ws'; -var validSignedAuthTokenBob = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImJvYiIsImV4cCI6MzE2Mzc1ODk3OTA4MDMxMCwiaWF0IjoxNTAyNzQ3NzQ2fQ.dSZOfsImq4AvCu-Or3Fcmo7JNv1hrV3WqxaiSKkTtAo'; -var validSignedAuthTokenAlice = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNTE4NzI4MjU5LCJleHAiOjMxNjM3NTg5NzkwODAzMTB9.XxbzPPnnXrJfZrS0FJwb_EAhIu2VY5i7rGyUThtNLh4'; -var invalidSignedAuthToken = 'fakebGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.fakec2VybmFtZSI6ImJvYiIsImlhdCI6MTUwMjYyNTIxMywiZXhwIjoxNTAyNzExNjEzfQ.fakemYcOOjM9bzmS4UYRvlWSk_lm3WGHvclmFjLbyOk'; +let validSignedAuthTokenBob = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImJvYiIsImV4cCI6MzE2Mzc1ODk3OTA4MDMxMCwiaWF0IjoxNTAyNzQ3NzQ2fQ.dSZOfsImq4AvCu-Or3Fcmo7JNv1hrV3WqxaiSKkTtAo'; +let validSignedAuthTokenAlice = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNTE4NzI4MjU5LCJleHAiOjMxNjM3NTg5NzkwODAzMTB9.XxbzPPnnXrJfZrS0FJwb_EAhIu2VY5i7rGyUThtNLh4'; +let invalidSignedAuthToken = 'fakebGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.fakec2VybmFtZSI6ImJvYiIsImlhdCI6MTUwMjYyNTIxMywiZXhwIjoxNTAyNzExNjEzfQ.fakemYcOOjM9bzmS4UYRvlWSk_lm3WGHvclmFjLbyOk'; -var server, client; +let server, client; -var resolveAfterTimeout = function (duration, value) { - return new Promise((resolve, reject) => { +function wait(duration) { + return new Promise((resolve) => { setTimeout(() => { - if (value === undefined) { - resolve(); - } else { - resolve(value); - } + resolve(); }, duration); }); +} + +async function resolveAfterTimeout(duration, value) { + await wait(duration); + return value; }; -var connectionHandler = function (socket) { - socket.on('login', function (userDetails, respond) { - if (allowedUsers[userDetails.username]) { - socket.setAuthToken(userDetails); - respond(); - } else { - var err = new Error('Failed to login'); - err.name = 'FailedLoginError'; - respond(err); +function connectionHandler(socket) { + (async () => { + for await (let rpc of socket.procedure('login')) { + if (allowedUsers[rpc.data.username]) { + socket.setAuthToken(rpc.data); + rpc.end(); + } else { + let err = new Error('Failed to login'); + err.name = 'FailedLoginError'; + rpc.error(err); + } } - }); - socket.on('loginWithTenDayExpiry', function (userDetails, respond) { - if (allowedUsers[userDetails.username]) { - socket.setAuthToken(userDetails, { - expiresIn: TEN_DAYS_IN_SECONDS - }); - respond(); - } else { - var err = new Error('Failed to login'); - err.name = 'FailedLoginError'; - respond(err); + })(); + + (async () => { + for await (let rpc of socket.procedure('loginWithTenDayExpiry')) { + if (allowedUsers[rpc.data.username]) { + socket.setAuthToken(rpc.data, { + expiresIn: TEN_DAYS_IN_SECONDS + }); + rpc.end(); + } else { + let err = new Error('Failed to login'); + err.name = 'FailedLoginError'; + rpc.error(err); + } } - }); - socket.on('loginWithTenDayExp', function (userDetails, respond) { - if (allowedUsers[userDetails.username]) { - userDetails.exp = Math.round(Date.now() / 1000) + TEN_DAYS_IN_SECONDS; - socket.setAuthToken(userDetails); - respond(); - } else { - var err = new Error('Failed to login'); - err.name = 'FailedLoginError'; - respond(err); + })(); + + (async () => { + for await (let rpc of socket.procedure('loginWithTenDayExp')) { + if (allowedUsers[rpc.data.username]) { + rpc.data.exp = Math.round(Date.now() / 1000) + TEN_DAYS_IN_SECONDS; + socket.setAuthToken(rpc.data); + rpc.end(); + } else { + let err = new Error('Failed to login'); + err.name = 'FailedLoginError'; + rpc.error(err); + } } - }); - socket.on('loginWithTenDayExpAndExpiry', function (userDetails, respond) { - if (allowedUsers[userDetails.username]) { - userDetails.exp = Math.round(Date.now() / 1000) + TEN_DAYS_IN_SECONDS; - socket.setAuthToken(userDetails, { - expiresIn: TEN_DAYS_IN_SECONDS * 100 // 1000 days - }); - respond(); - } else { - var err = new Error('Failed to login'); - err.name = 'FailedLoginError'; - respond(err); + })(); + + (async () => { + for await (let rpc of socket.procedure('loginWithTenDayExpAndExpiry')) { + if (allowedUsers[rpc.data.username]) { + rpc.data.exp = Math.round(Date.now() / 1000) + TEN_DAYS_IN_SECONDS; + socket.setAuthToken(rpc.data, { + expiresIn: TEN_DAYS_IN_SECONDS * 100 // 1000 days + }); + rpc.end(); + } else { + let err = new Error('Failed to login'); + err.name = 'FailedLoginError'; + rpc.error(err); + } } - }); - socket.on('loginWithIssAndIssuer', function (userDetails, respond) { - if (allowedUsers[userDetails.username]) { - userDetails.iss = 'foo'; - socket.setAuthToken(userDetails, { - issuer: 'bar' - }).catch((err) => {}); - respond(); - } else { - var err = new Error('Failed to login'); - err.name = 'FailedLoginError'; - respond(err); + })(); + + (async () => { + for await (let rpc of socket.procedure('loginWithIssAndIssuer')) { + if (allowedUsers[rpc.data.username]) { + rpc.data.iss = 'foo'; + try { + await socket.setAuthToken(rpc.data, { + issuer: 'bar' + }); + } catch (err) {} + rpc.end(); + } else { + let err = new Error('Failed to login'); + err.name = 'FailedLoginError'; + rpc.error(err); + } } - }); - socket.on('setAuthKey', function (newAuthKey, respond) { - server.signatureKey = newAuthKey; - server.verificationKey = newAuthKey; - respond(); - }); + })(); + + (async () => { + for await (let rpc of socket.procedure('setAuthKey')) { + server.signatureKey = rpc.data; + server.verificationKey = rpc.data; + rpc.end(); + } + })(); }; -var destroyTestCase = function (next) { +function destroyTestCase() { if (client) { - client.on('error', function (err) {}); - if (client.state !== client.CLOSED) { - client.once('close', function () { - client.removeAllListeners('close'); - client.removeAllListeners('connectAbort'); - client.removeAllListeners('disconnect'); - next(); - }); + client.closeAllListeners(); client.disconnect(); - } else { - next(); } - } else { - next(); } }; describe('Integration tests', function () { - beforeEach('Run the server before start', function (done) { + beforeEach('Run the server before start', async function () { clientOptions = { hostname: '127.0.0.1', - multiplex: false, port: portNumber }; serverOptions = { @@ -138,423 +146,464 @@ describe('Integration tests', function () { }; server = socketClusterServer.listen(portNumber, serverOptions); - server.on('connection', connectionHandler); - server.addMiddleware(server.MIDDLEWARE_AUTHENTICATE, function (req, next) { + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + + server.addMiddleware(server.MIDDLEWARE_AUTHENTICATE, async function (req) { if (req.authToken.username === 'alice') { - var err = new Error('Blocked by MIDDLEWARE_AUTHENTICATE'); + let err = new Error('Blocked by MIDDLEWARE_AUTHENTICATE'); err.name = 'AuthenticateMiddlewareError'; - next(err); - } else { - next(); + throw err; } }); - server.on('ready', function () { - done(); - }); + await server.listener('ready').once(); }); - afterEach('Shut down client after each test', function (done) { - server.close(); + afterEach('Close server after each test', async function () { portNumber++; - destroyTestCase(function () { - global.localStorage.removeItem('socketCluster.authToken'); - done(); - }); + destroyTestCase(); + server.close(); + global.localStorage.removeItem('socketCluster.authToken'); }); describe('Socket authentication', function () { - it('Should not send back error if JWT is not provided in handshake', function (done) { - client = socketCluster.connect(clientOptions); - client.once('connect', function (status) { - assert.equal(status.authError === undefined, true); - done(); - }); + it('Should not send back error if JWT is not provided in handshake', async function () { + client = socketClusterClient.create(clientOptions); + let event = await client.listener('connect').once(); + assert.equal(event.authError === undefined, true); }); - it('Should be authenticated on connect if previous JWT token is present', function (done) { - client = socketCluster.connect(clientOptions); - client.once('connect', function (statusA) { - client.transmit('login', {username: 'bob'}); - client.once('authenticate', function (state) { - assert.equal(client.authState, 'authenticated'); - - client.once('disconnect', function () { - client.once('connect', function (statusB) { - assert.equal(statusB.isAuthenticated, true); - assert.equal(statusB.authError === undefined, true); - done(); - }); - - client.connect(); - }); - - client.disconnect(); - }); - }); + it('Should be authenticated on connect if previous JWT token is present', async function () { + client = socketClusterClient.create(clientOptions); + await client.listener('connect').once(); + client.invoke('login', {username: 'bob'}); + await client.listener('authenticate').once(); + assert.equal(client.authState, 'authenticated'); + client.disconnect(); + client.connect(); + let event = await client.listener('connect').once(); + assert.equal(event.isAuthenticated, true); + assert.equal(event.authError === undefined, true); }); - it('Should send back error if JWT is invalid during handshake', function (done) { + it('Should send back error if JWT is invalid during handshake', async function () { global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); - client = socketCluster.connect(clientOptions); - client.once('connect', function (statusA) { - // Change the setAuthKey to invalidate the current token. - client.transmit('setAuthKey', 'differentAuthKey') - .then(function () { - client.once('disconnect', function () { - client.once('connect', function (statusB) { - assert.equal(statusB.isAuthenticated, false); - assert.notEqual(statusB.authError, null); - assert.equal(statusB.authError.name, 'AuthTokenInvalidError'); - done(); - }); - - client.connect(); - }); + client = socketClusterClient.create(clientOptions); - client.disconnect(); - }); - }); + await client.listener('connect').once(); + // Change the setAuthKey to invalidate the current token. + await client.invoke('setAuthKey', 'differentAuthKey'); + client.disconnect(); + client.connect(); + let event = await client.listener('connect').once(); + assert.equal(event.isAuthenticated, false); + assert.notEqual(event.authError, null); + assert.equal(event.authError.name, 'AuthTokenInvalidError'); }); - it('Should allow switching between users', function (done) { + it('Should allow switching between users', async function () { global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); - var authenticateEvents = []; - var deauthenticateEvents = []; - var authenticationStateChangeEvents = []; - var authStateChangeEvents = []; - - server.on('authenticationStateChange', function (socket, stateChangeData) { - authenticationStateChangeEvents.push({ - socket: socket, - stateChangeData: stateChangeData - }); - }); - - server.on('connection', function (socket) { - socket.on('authenticate', function (authToken) { - authenticateEvents.push(authToken); - }); - socket.on('deauthenticate', function (oldAuthToken) { - deauthenticateEvents.push(oldAuthToken); - }); - socket.on('authStateChange', function (stateChangeData) { - authStateChangeEvents.push(stateChangeData); - }); - }); + let authenticateEvents = []; + let deauthenticateEvents = []; + let authenticationStateChangeEvents = []; + let authStateChangeEvents = []; - var clientSocketId; - client = socketCluster.connect(clientOptions); - client.once('connect', function (statusA) { - clientSocketId = client.id; - client.transmit('login', {username: 'alice'}); - }); - - setTimeout(function () { - assert.equal(deauthenticateEvents.length, 0); - assert.equal(authenticateEvents.length, 2); - assert.equal(authenticateEvents[0].username, 'bob'); - assert.equal(authenticateEvents[1].username, 'alice'); - - assert.equal(authenticationStateChangeEvents.length, 1); - assert.notEqual(authenticationStateChangeEvents[0].socket, null); - assert.equal(authenticationStateChangeEvents[0].socket.id, clientSocketId); - assert.equal(authenticationStateChangeEvents[0].stateChangeData.oldState, 'unauthenticated'); - assert.equal(authenticationStateChangeEvents[0].stateChangeData.newState, 'authenticated'); - assert.notEqual(authenticationStateChangeEvents[0].stateChangeData.authToken, null); - assert.equal(authenticationStateChangeEvents[0].stateChangeData.authToken.username, 'bob'); - - assert.equal(authStateChangeEvents.length, 1); - assert.equal(authStateChangeEvents[0].oldState, 'unauthenticated'); - assert.equal(authStateChangeEvents[0].newState, 'authenticated'); - assert.notEqual(authStateChangeEvents[0].authToken, null); - assert.equal(authStateChangeEvents[0].authToken.username, 'bob'); + (async () => { + for await (let stateChangePacket of server.listener('authenticationStateChange')) { + authenticationStateChangeEvents.push(stateChangePacket); + } + })(); - done(); - }, 100); + (async () => { + for await (let {socket} of server.listener('connection')) { + (async () => { + for await (let {authToken} of socket.listener('authenticate')) { + authenticateEvents.push(authToken); + } + })(); + (async () => { + for await (let {oldAuthToken} of socket.listener('deauthenticate')) { + deauthenticateEvents.push(oldAuthToken); + } + })(); + (async () => { + for await (let stateChangeData of socket.listener('authStateChange')) { + authStateChangeEvents.push(stateChangeData); + } + })(); + } + })(); + + let clientSocketId; + client = socketClusterClient.create(clientOptions); + await client.listener('connect').once(); + clientSocketId = client.id; + client.invoke('login', {username: 'alice'}); + + await wait(100); + + assert.equal(deauthenticateEvents.length, 0); + assert.equal(authenticateEvents.length, 2); + assert.equal(authenticateEvents[0].username, 'bob'); + assert.equal(authenticateEvents[1].username, 'alice'); + + assert.equal(authenticationStateChangeEvents.length, 1); + assert.notEqual(authenticationStateChangeEvents[0].socket, null); + assert.equal(authenticationStateChangeEvents[0].socket.id, clientSocketId); + assert.equal(authenticationStateChangeEvents[0].oldAuthState, 'unauthenticated'); + assert.equal(authenticationStateChangeEvents[0].newAuthState, 'authenticated'); + assert.notEqual(authenticationStateChangeEvents[0].authToken, null); + assert.equal(authenticationStateChangeEvents[0].authToken.username, 'bob'); + + assert.equal(authStateChangeEvents.length, 1); + assert.equal(authStateChangeEvents[0].oldAuthState, 'unauthenticated'); + assert.equal(authStateChangeEvents[0].newAuthState, 'authenticated'); + assert.notEqual(authStateChangeEvents[0].authToken, null); + assert.equal(authStateChangeEvents[0].authToken.username, 'bob'); }); - it('Should emit correct events/data when socket is deauthenticated', function (done) { + it('Should emit correct events/data when socket is deauthenticated', async function () { global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); - var authenticationStateChangeEvents = []; - var authStateChangeEvents = []; + let authenticationStateChangeEvents = []; + let authStateChangeEvents = []; - server.on('authenticationStateChange', function (socket, stateChangeData) { - authenticationStateChangeEvents.push({ - socket: socket, - stateChangeData: stateChangeData - }); - }); + (async () => { + for await (let stateChangePacket of server.listener('authenticationStateChange')) { + authenticationStateChangeEvents.push(stateChangePacket); + } + })(); - client = socketCluster.connect(clientOptions); - client.once('connect', function (statusA) { - client.deauthenticate(); - }); + client = socketClusterClient.create(clientOptions); - server.on('connection', function (socket) { - var initialAuthToken = socket.authToken; + (async () => { + for await (let event of client.listener('connect')) { + client.deauthenticate(); + } + })(); - socket.on('authStateChange', function (stateChangeData) { - authStateChangeEvents.push(stateChangeData); - }); + let {socket} = await server.listener('connection').once(); + let initialAuthToken = socket.authToken; - socket.on('deauthenticate', function (oldToken) { - assert.equal(oldToken, initialAuthToken); - - assert.equal(authStateChangeEvents.length, 2); - assert.equal(authStateChangeEvents[0].oldState, 'unauthenticated'); - assert.equal(authStateChangeEvents[0].newState, 'authenticated'); - assert.notEqual(authStateChangeEvents[0].authToken, null); - assert.equal(authStateChangeEvents[0].authToken.username, 'bob'); - assert.equal(authStateChangeEvents[1].oldState, 'authenticated'); - assert.equal(authStateChangeEvents[1].newState, 'unauthenticated'); - assert.equal(authStateChangeEvents[1].authToken, null); - - assert.equal(authenticationStateChangeEvents.length, 2); - assert.notEqual(authenticationStateChangeEvents[0].stateChangeData, null); - assert.equal(authenticationStateChangeEvents[0].stateChangeData.oldState, 'unauthenticated'); - assert.equal(authenticationStateChangeEvents[0].stateChangeData.newState, 'authenticated'); - assert.notEqual(authenticationStateChangeEvents[0].stateChangeData.authToken, null); - assert.equal(authenticationStateChangeEvents[0].stateChangeData.authToken.username, 'bob'); - assert.notEqual(authenticationStateChangeEvents[1].stateChangeData, null); - assert.equal(authenticationStateChangeEvents[1].stateChangeData.oldState, 'authenticated'); - assert.equal(authenticationStateChangeEvents[1].stateChangeData.newState, 'unauthenticated'); - assert.equal(authenticationStateChangeEvents[1].stateChangeData.authToken, null); - done(); - }); - }); + (async () => { + for await (let stateChangeData of socket.listener('authStateChange')) { + authStateChangeEvents.push(stateChangeData); + } + })(); + + let {oldAuthToken} = await socket.listener('deauthenticate').once(); + assert.equal(oldAuthToken, initialAuthToken); + + assert.equal(authStateChangeEvents.length, 2); + assert.equal(authStateChangeEvents[0].oldAuthState, 'unauthenticated'); + assert.equal(authStateChangeEvents[0].newAuthState, 'authenticated'); + assert.notEqual(authStateChangeEvents[0].authToken, null); + assert.equal(authStateChangeEvents[0].authToken.username, 'bob'); + assert.equal(authStateChangeEvents[1].oldAuthState, 'authenticated'); + assert.equal(authStateChangeEvents[1].newAuthState, 'unauthenticated'); + assert.equal(authStateChangeEvents[1].authToken, null); + + assert.equal(authenticationStateChangeEvents.length, 2); + assert.notEqual(authenticationStateChangeEvents[0], null); + assert.equal(authenticationStateChangeEvents[0].oldAuthState, 'unauthenticated'); + assert.equal(authenticationStateChangeEvents[0].newAuthState, 'authenticated'); + assert.notEqual(authenticationStateChangeEvents[0].authToken, null); + assert.equal(authenticationStateChangeEvents[0].authToken.username, 'bob'); + assert.notEqual(authenticationStateChangeEvents[1], null); + assert.equal(authenticationStateChangeEvents[1].oldAuthState, 'authenticated'); + assert.equal(authenticationStateChangeEvents[1].newAuthState, 'unauthenticated'); + assert.equal(authenticationStateChangeEvents[1].authToken, null); }); - it('Should not authenticate the client if MIDDLEWARE_AUTHENTICATE blocks the authentication', function (done) { + it('Should not authenticate the client if MIDDLEWARE_AUTHENTICATE blocks the authentication', async function () { global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenAlice); - client = socketCluster.connect(clientOptions); + client = socketClusterClient.create(clientOptions); // The previous test authenticated us as 'alice', so that token will be passed to the server as // part of the handshake. - client.once('connect', function (statusB) { - // Any token containing the username 'alice' should be blocked by the MIDDLEWARE_AUTHENTICATE middleware. - // This will only affects token-based authentication, not the credentials-based login event. - assert.equal(statusB.isAuthenticated, false); - assert.notEqual(statusB.authError, null); - assert.equal(statusB.authError.name, 'AuthenticateMiddlewareError'); - done(); - }); + + let event = await client.listener('connect').once(); + // Any token containing the username 'alice' should be blocked by the MIDDLEWARE_AUTHENTICATE middleware. + // This will only affects token-based authentication, not the credentials-based login event. + assert.equal(event.isAuthenticated, false); + assert.notEqual(event.authError, null); + assert.equal(event.authError.name, 'AuthenticateMiddlewareError'); }); - it('Token should be available after Promise resolves if token engine signing is synchronous', function (done) { + it('Token should be available after Promise resolves if token engine signing is synchronous', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authSignAsync: false }); - server.on('connection', connectionHandler); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - client.once('connect', function (statusA) { - client.invoke('login', {username: 'bob'}) - .then(function () { - assert.equal(client.authState, 'authenticated'); - assert.notEqual(client.authToken, null); - assert.equal(client.authToken.username, 'bob'); - done(); - }); - }); + + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + + await server.listener('ready').once(); + + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); + + await client.listener('connect').once(); + + client.invoke('login', {username: 'bob'}); + await client.listener('authenticate').once(); + + assert.equal(client.authState, 'authenticated'); + assert.notEqual(client.authToken, null); + assert.equal(client.authToken.username, 'bob'); }); - it('If token engine signing is asynchronous, authentication can be captured using the authenticate event', function (done) { + it('If token engine signing is asynchronous, authentication can be captured using the authenticate event', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authSignAsync: true }); - server.on('connection', connectionHandler); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - client.once('connect', function (statusA) { - client.transmit('login', {username: 'bob'}); - client.on('authenticate', function (newSignedToken) { - assert.equal(client.authState, 'authenticated'); - assert.notEqual(client.authToken, null); - assert.equal(client.authToken.username, 'bob'); - done(); - }); - }); + + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + + await server.listener('ready').once(); + + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); + + await client.listener('connect').once(); + + client.invoke('login', {username: 'bob'}); + await client.listener('authenticate').once(); + + assert.equal(client.authState, 'authenticated'); + assert.notEqual(client.authToken, null); + assert.equal(client.authToken.username, 'bob'); }); - it('Should still work if token verification is asynchronous', function (done) { + it('Should still work if token verification is asynchronous', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authVerifyAsync: false }); - server.on('connection', connectionHandler); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - client.once('connect', function (statusA) { - client.transmit('login', {username: 'bob'}); - client.once('authenticate', function (newSignedToken) { - client.once('disconnect', function () { - client.once('connect', function (statusB) { - assert.equal(statusB.isAuthenticated, true); - assert.notEqual(client.authToken, null); - assert.equal(client.authToken.username, 'bob'); - done(); - }); - client.connect(); - }); - client.disconnect(); - }); - }); + + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + + await server.listener('ready').once(); + + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); + + await client.listener('connect').once(); + + client.invoke('login', {username: 'bob'}); + + await client.listener('authenticate').once(); + + client.disconnect(); + client.connect(); + + let event = await client.listener('connect').once(); + + assert.equal(event.isAuthenticated, true); + assert.notEqual(client.authToken, null); + assert.equal(client.authToken.username, 'bob'); }); - it('Should set the correct expiry when using expiresIn option when creating a JWT with socket.setAuthToken', function (done) { + it('Should set the correct expiry when using expiresIn option when creating a JWT with socket.setAuthToken', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authVerifyAsync: false }); - server.on('connection', connectionHandler); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - client.once('connect', function (statusA) { - client.once('authenticate', function (newSignedToken) { - assert.notEqual(client.authToken, null); - assert.notEqual(client.authToken.exp, null); - var dateMillisecondsInTenDays = Date.now() + TEN_DAYS_IN_SECONDS * 1000; - var dateDifference = Math.abs(dateMillisecondsInTenDays - client.authToken.exp * 1000); - // Expiry must be accurate within 1000 milliseconds. - assert.equal(dateDifference < 1000, true); - done(); - }); - client.transmit('loginWithTenDayExpiry', {username: 'bob'}); - }); + + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + + await server.listener('ready').once(); + + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); + + await client.listener('connect').once(); + client.invoke('loginWithTenDayExpiry', {username: 'bob'}); + await client.listener('authenticate').once(); + + assert.notEqual(client.authToken, null); + assert.notEqual(client.authToken.exp, null); + let dateMillisecondsInTenDays = Date.now() + TEN_DAYS_IN_SECONDS * 1000; + let dateDifference = Math.abs(dateMillisecondsInTenDays - client.authToken.exp * 1000); + // Expiry must be accurate within 1000 milliseconds. + assert.equal(dateDifference < 1000, true); }); - it('Should set the correct expiry when adding exp claim when creating a JWT with socket.setAuthToken', function (done) { + it('Should set the correct expiry when adding exp claim when creating a JWT with socket.setAuthToken', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authVerifyAsync: false }); - server.on('connection', connectionHandler); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - client.once('connect', function (statusA) { - client.once('authenticate', function (newSignedToken) { - assert.notEqual(client.authToken, null); - assert.notEqual(client.authToken.exp, null); - var dateMillisecondsInTenDays = Date.now() + TEN_DAYS_IN_SECONDS * 1000; - var dateDifference = Math.abs(dateMillisecondsInTenDays - client.authToken.exp * 1000); - // Expiry must be accurate within 1000 milliseconds. - assert.equal(dateDifference < 1000, true); - done(); - }); - client.transmit('loginWithTenDayExp', {username: 'bob'}); - }); + + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + + await server.listener('ready').once(); + + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); + + await client.listener('connect').once(); + client.invoke('loginWithTenDayExp', {username: 'bob'}); + await client.listener('authenticate').once(); + + assert.notEqual(client.authToken, null); + assert.notEqual(client.authToken.exp, null); + let dateMillisecondsInTenDays = Date.now() + TEN_DAYS_IN_SECONDS * 1000; + let dateDifference = Math.abs(dateMillisecondsInTenDays - client.authToken.exp * 1000); + // Expiry must be accurate within 1000 milliseconds. + assert.equal(dateDifference < 1000, true); }); - it('The exp claim should have priority over expiresIn option when using socket.setAuthToken', function (done) { + it('The exp claim should have priority over expiresIn option when using socket.setAuthToken', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authVerifyAsync: false }); - server.on('connection', connectionHandler); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - client.once('connect', function (statusA) { - client.once('authenticate', function (newSignedToken) { - assert.notEqual(client.authToken, null); - assert.notEqual(client.authToken.exp, null); - var dateMillisecondsInTenDays = Date.now() + TEN_DAYS_IN_SECONDS * 1000; - var dateDifference = Math.abs(dateMillisecondsInTenDays - client.authToken.exp * 1000); - // Expiry must be accurate within 1000 milliseconds. - assert.equal(dateDifference < 1000, true); - done(); - }); - client.transmit('loginWithTenDayExpAndExpiry', {username: 'bob'}); - }); + + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + + await server.listener('ready').once(); + + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); + + await client.listener('connect').once(); + client.invoke('loginWithTenDayExpAndExpiry', {username: 'bob'}); + await client.listener('authenticate').once(); + + assert.notEqual(client.authToken, null); + assert.notEqual(client.authToken.exp, null); + let dateMillisecondsInTenDays = Date.now() + TEN_DAYS_IN_SECONDS * 1000; + let dateDifference = Math.abs(dateMillisecondsInTenDays - client.authToken.exp * 1000); + // Expiry must be accurate within 1000 milliseconds. + assert.equal(dateDifference < 1000, true); }); - it('Should send back error if socket.setAuthToken tries to set both iss claim and issuer option', function (done) { + it('Should send back error if socket.setAuthToken tries to set both iss claim and issuer option', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authVerifyAsync: false }); - var warningMap = {}; + let warningMap = {}; - server.on('connection', connectionHandler); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - client.once('connect', function (statusA) { - client.once('authenticate', function (newSignedToken) { - throw new Error('Should not pass authentication because the signature should fail'); - }); - server.on('warning', function (warning) { - assert.notEqual(warning, null); - warningMap[warning.name] = warning; - }); - client.once('error', function (err) { - assert.notEqual(err, null); - assert.equal(err.name, 'SocketProtocolError'); - }); - client.transmit('loginWithIssAndIssuer', {username: 'bob'}); - setTimeout(function () { - server.removeAllListeners('warning'); - assert.notEqual(warningMap['SocketProtocolError'], null); - done(); - }, 1000); - }); + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + + await server.listener('ready').once(); + + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); + + await client.listener('connect').once(); + + (async () => { + await client.listener('authenticate').once(); + throw new Error('Should not pass authentication because the signature should fail'); + })(); + + (async () => { + for await (let {warning} of server.listener('warning')) { + assert.notEqual(warning, null); + warningMap[warning.name] = warning; + } + })(); + + (async () => { + for await (let {error} of server.listener('error')) { + assert.notEqual(error, null); + assert.equal(error.name, 'SocketProtocolError'); + } + })(); + + let closePackets = []; + + (async () => { + let event = await client.listener('close').once(); + closePackets.push(event); + })(); + + let error; + try { + await client.invoke('loginWithIssAndIssuer', {username: 'bob'}); + } catch (err) { + error = err; + } + + assert.notEqual(error, null); + assert.equal(error.name, 'BadConnectionError'); + + await wait(1000); + + assert.equal(closePackets.length, 1); + assert.equal(closePackets[0].code, 4002); + server.closeListener('warning'); + assert.notEqual(warningMap['SocketProtocolError'], null); }); - it('Should trigger an authTokenSigned event and socket.signedAuthToken should be set after calling the socket.setAuthToken method', function (done) { + it('Should trigger an authTokenSigned event and socket.signedAuthToken should be set after calling the socket.setAuthToken method', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, @@ -562,43 +611,48 @@ describe('Integration tests', function () { authSignAsync: true }); - var authTokenSignedEventEmitted = false; + let authTokenSignedEventEmitted = false; - server.on('connection', function (socket) { - socket.on('authTokenSigned', function (signedAuthToken) { - authTokenSignedEventEmitted = true; - assert.notEqual(signedAuthToken, null); - assert.equal(signedAuthToken, socket.signedAuthToken); - }); - socket.on('login', function (userDetails, respond) { - if (allowedUsers[userDetails.username]) { - socket.setAuthToken(userDetails, {async: true}); - respond(); - } else { - var err = new Error('Failed to login'); - err.name = 'FailedLoginError'; - respond(err); - } - }); - }); + (async () => { + for await (let {socket} of server.listener('connection')) { + (async () => { + for await (let {signedAuthToken} of socket.listener('authTokenSigned')) { + authTokenSignedEventEmitted = true; + assert.notEqual(signedAuthToken, null); + assert.equal(signedAuthToken, socket.signedAuthToken); + } + })(); + + (async () => { + for await (let req of socket.procedure('login')) { + if (allowedUsers[req.data.username]) { + socket.setAuthToken(req.data, {async: true}); + req.end(); + } else { + let err = new Error('Failed to login'); + err.name = 'FailedLoginError'; + req.error(err); + } + } + })(); + } + })(); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - client.once('connect', function (statusA) { - client.transmit('login', {username: 'bob'}); - }); - setTimeout(function () { - assert.equal(authTokenSignedEventEmitted, true); - done(); - }, 100); + await server.listener('ready').once(); + + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); + + await client.listener('connect').once(); + await client.invoke('login', {username: 'bob'}); + await client.listener('authenticate').once(); + + assert.equal(authTokenSignedEventEmitted, true); }); - it('Should reject Promise returned by socket.setAuthToken if token delivery fails and rejectOnFailedDelivery option is true', function (done) { + it('Should reject Promise returned by socket.setAuthToken if token delivery fails and rejectOnFailedDelivery option is true', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, @@ -607,45 +661,49 @@ describe('Integration tests', function () { ackTimeout: 1000 }); - var socketErrors = []; + let socketErrors = []; - server.on('connection', function (socket) { - socket.on('error', function (err) { - socketErrors.push(err); - }); - socket.on('login', function (userDetails, respond) { - if (allowedUsers[userDetails.username]) { - client.disconnect(); - socket.setAuthToken(userDetails, {rejectOnFailedDelivery: true}) - .catch((err) => { - assert.notEqual(err, null); - assert.equal(err.name, 'AuthError'); - assert.notEqual(socketErrors[0], null); - assert.equal(socketErrors[0].name, 'AuthError'); - done(); - }); - respond(); - } else { - var err = new Error('Failed to login'); - err.name = 'FailedLoginError'; - respond(err); - } - }); - }); - - server.on('ready', function () { - client = socketCluster.connect({ + (async () => { + await server.listener('ready').once(); + client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - client.once('connect', function (statusA) { - client.transmit('login', {username: 'bob'}); + port: portNumber }); - }); + await client.listener('connect').once(); + client.invoke('login', {username: 'bob'}); + })(); + + let {socket} = await server.listener('connection').once(); + + (async () => { + for await (let {error} of socket.listener('error')) { + socketErrors.push(error); + } + })(); + + let req = await socket.procedure('login').once(); + if (allowedUsers[req.data.username]) { + req.end(); + socket.disconnect(); + let error; + try { + await socket.setAuthToken(req.data, {rejectOnFailedDelivery: true}); + } catch (err) { + error = err; + } + assert.notEqual(error, null); + assert.equal(error.name, 'AuthError'); + await wait(0); + assert.notEqual(socketErrors[0], null); + assert.equal(socketErrors[0].name, 'AuthError'); + } else { + let err = new Error('Failed to login'); + err.name = 'FailedLoginError'; + req.error(err); + } }); - it('Should not reject Promise returned by socket.setAuthToken if token delivery fails and rejectOnFailedDelivery option is not true', function (done) { + it('Should not reject Promise returned by socket.setAuthToken if token delivery fails and rejectOnFailedDelivery option is not true', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, @@ -654,47 +712,48 @@ describe('Integration tests', function () { ackTimeout: 1000 }); - var socketErrors = []; - - server.on('connection', function (socket) { - socket.on('error', function (err) { - socketErrors.push(err); - }); - socket.on('login', function (userDetails, respond) { - if (allowedUsers[userDetails.username]) { - client.disconnect(); - socket.setAuthToken(userDetails) - .catch((err) => { - return err; - }) - .then((err) => { - assert.equal(err, null); - assert.notEqual(socketErrors[0], null); - assert.equal(socketErrors[0].name, 'AuthError'); - done(); - }); - respond(); - } else { - var err = new Error('Failed to login'); - err.name = 'FailedLoginError'; - respond(err); - } - }); - }); + let socketErrors = []; - server.on('ready', function () { - client = socketCluster.connect({ + (async () => { + await server.listener('ready').once(); + client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: portNumber, - multiplex: false + port: portNumber }); - client.once('connect', function (statusA) { - client.transmit('login', {username: 'bob'}); - }); - }); + await client.listener('connect').once(); + client.invoke('login', {username: 'bob'}); + })(); + + let {socket} = await server.listener('connection').once(); + + (async () => { + for await (let {error} of socket.listener('error')) { + socketErrors.push(error); + } + })(); + + let req = await socket.procedure('login').once(); + if (allowedUsers[req.data.username]) { + req.end(); + socket.disconnect(); + let error; + try { + await socket.setAuthToken(req.data); + } catch (err) { + error = err; + } + assert.equal(error, null); + await wait(0); + assert.notEqual(socketErrors[0], null); + assert.equal(socketErrors[0].name, 'AuthError'); + } else { + let err = new Error('Failed to login'); + err.name = 'FailedLoginError'; + req.error(err); + } }); - it('The verifyToken method of the authEngine receives correct params', function (done) { + it('The verifyToken method of the authEngine receives correct params', async function () { global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); portNumber++; @@ -702,29 +761,37 @@ describe('Integration tests', function () { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); - server.setAuthEngine({ - verifyToken: (signedAuthToken, verificationKey, verificationOptions) => { - setTimeout(() => { + + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + + (async () => { + await server.listener('ready').once(); + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber + }); + })(); + + return new Promise((resolve) => { + server.setAuthEngine({ + verifyToken: async (signedAuthToken, verificationKey, verificationOptions) => { + await wait(500); assert.equal(signedAuthToken, validSignedAuthTokenBob); assert.equal(verificationKey, serverOptions.authKey); assert.notEqual(verificationOptions, null); assert.notEqual(verificationOptions.socket, null); - done(); - }, 500) - return Promise.resolve({}); - } - }); - server.on('connection', connectionHandler); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false + resolve(); + return Promise.resolve({}); + } }); }); }); - it('Should remove client data from the server when client disconnects before authentication process finished', function (done) { + it('Should remove client data from the server when client disconnects before authentication process finished', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, @@ -735,149 +802,154 @@ describe('Integration tests', function () { return resolveAfterTimeout(500, {}); } }); - server.on('connection', connectionHandler); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - var serverSocket; - server.on('handshake', function (socket) { - serverSocket = socket; - }); - setTimeout(function () { - assert.equal(server.clientsCount, 0); - assert.equal(server.pendingClientsCount, 1); - assert.notEqual(serverSocket, null); - assert.equal(Object.keys(server.pendingClients)[0], serverSocket.id); - client.disconnect(); - }, 100); - setTimeout(function () { - assert.equal(Object.keys(server.clients).length, 0); - assert.equal(server.clientsCount, 0); - assert.equal(server.pendingClientsCount, 0); - assert.equal(JSON.stringify(server.pendingClients), '{}'); - done(); - }, 1000); + + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + + await server.listener('ready').once(); + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); + + let serverSocket; + (async () => { + for await (let {socket} of server.listener('handshake')) { + serverSocket = socket; + } + })(); + + await wait(100); + assert.equal(server.clientsCount, 0); + assert.equal(server.pendingClientsCount, 1); + assert.notEqual(serverSocket, null); + assert.equal(Object.keys(server.pendingClients)[0], serverSocket.id); + client.disconnect(); + + await wait(1000); + assert.equal(Object.keys(server.clients).length, 0); + assert.equal(server.clientsCount, 0); + assert.equal(server.pendingClientsCount, 0); + assert.equal(JSON.stringify(server.pendingClients), '{}'); }); }); describe('Socket handshake', function () { - it('Exchange is attached to socket before the handshake event is triggered', function (done) { + it('Exchange is attached to socket before the handshake event is triggered', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); - server.on('connection', connectionHandler); - - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); - server.once('handshake', function (socket) { - assert.notEqual(socket.exchange, null); - }); + await server.listener('ready').once(); - setTimeout(function () { - done(); - }, 300); + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); + + let {socket} = await server.listener('handshake').once(); + assert.notEqual(socket.exchange, null); }); }); describe('Socket connection', function () { - it('Server-side socket connect event and server connection event should trigger', function (done) { + it('Server-side socket connect event and server connection event should trigger', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); - var connectionEmitted = false; - var connectionStatus; + let connectionEmitted = false; + let connectionEvent; + + (async () => { + for await (let event of server.listener('connection')) { + connectionEvent = event; + connectionHandler(event.socket); + connectionEmitted = true; + } + })(); + + await server.listener('ready').once(); - server.on('connection', connectionHandler); - server.once('connection', function (socket, status) { - connectionEmitted = true; - connectionStatus = status; - // Modify the status object and make sure that it doesn't get modified - // on the client. - status.foo = 123; + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - var connectEmitted = false; - var _connectEmitted = false; - var connectStatus; - var socketId; + let connectEmitted = false; + let connectStatus; + let socketId; - server.once('handshake', function (socket) { - socket.once('connect', function (status) { - socketId = socket.id; - connectEmitted = true; - connectStatus = status; - }); - socket.once('_connect', function () { - _connectEmitted = true; - }); - }); + (async () => { + for await (let {socket} of server.listener('handshake')) { + (async () => { + for await (let serverSocketStatus of socket.listener('connect')) { + socketId = socket.id; + connectEmitted = true; + connectStatus = serverSocketStatus; + // This is to check that mutating the status on the server + // doesn't affect the status sent to the client. + serverSocketStatus.foo = 123; + } + })(); + } + })(); - var clientConnectEmitted = false; - var clientConnectStatus = false; + let clientConnectEmitted = false; + let clientConnectStatus = false; - client.once('connect', function (status) { + (async () => { + for await (let event of client.listener('connect')) { clientConnectEmitted = true; - clientConnectStatus = status; - }); - - setTimeout(function () { - assert.equal(connectEmitted, true); - assert.equal(_connectEmitted, true); - assert.equal(connectionEmitted, true); - assert.equal(clientConnectEmitted, true); - - assert.notEqual(connectionStatus, null); - assert.equal(connectionStatus.id, socketId); - assert.equal(connectionStatus.pingTimeout, server.pingTimeout); - assert.equal(connectionStatus.authError, null); - assert.equal(connectionStatus.isAuthenticated, false); - - assert.notEqual(connectStatus, null); - assert.equal(connectStatus.id, socketId); - assert.equal(connectStatus.pingTimeout, server.pingTimeout); - assert.equal(connectStatus.authError, null); - assert.equal(connectStatus.isAuthenticated, false); - - assert.notEqual(clientConnectStatus, null); - assert.equal(clientConnectStatus.id, socketId); - assert.equal(clientConnectStatus.pingTimeout, server.pingTimeout); - assert.equal(clientConnectStatus.authError, null); - assert.equal(clientConnectStatus.isAuthenticated, false); - assert.equal(clientConnectStatus.foo, null); - // Client socket status should be a clone of server socket status; not - // a reference to the same object. - assert.notEqual(clientConnectStatus.foo, connectStatus.foo); - - done(); - }, 300); - }); + clientConnectStatus = event; + } + })(); + + await wait(300); + + assert.equal(connectEmitted, true); + assert.equal(connectionEmitted, true); + assert.equal(clientConnectEmitted, true); + + assert.notEqual(connectionEvent, null); + assert.equal(connectionEvent.id, socketId); + assert.equal(connectionEvent.pingTimeout, server.pingTimeout); + assert.equal(connectionEvent.authError, null); + assert.equal(connectionEvent.isAuthenticated, false); + + assert.notEqual(connectStatus, null); + assert.equal(connectStatus.id, socketId); + assert.equal(connectStatus.pingTimeout, server.pingTimeout); + assert.equal(connectStatus.authError, null); + assert.equal(connectStatus.isAuthenticated, false); + + assert.notEqual(clientConnectStatus, null); + assert.equal(clientConnectStatus.id, socketId); + assert.equal(clientConnectStatus.pingTimeout, server.pingTimeout); + assert.equal(clientConnectStatus.authError, null); + assert.equal(clientConnectStatus.isAuthenticated, false); + assert.equal(clientConnectStatus.foo, null); + // Client socket status should be a clone of server socket status; not + // a reference to the same object. + assert.notEqual(clientConnectStatus.foo, connectStatus.foo); }); }); describe('Socket disconnection', function () { - it('Server-side socket disconnect event should not trigger if the socket did not complete the handshake; instead, it should trigger connectAbort', function (done) { + it('Server-side socket disconnect event should not trigger if the socket did not complete the handshake; instead, it should trigger connectAbort', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, @@ -888,68 +960,73 @@ describe('Integration tests', function () { return resolveAfterTimeout(500, {}); } }); - server.on('connection', connectionHandler); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - client.on('error', function () {}); - - var socketDisconnected = false; - var socketDisconnectedBeforeConnect = false; - var clientSocketAborted = false; - var connectionOnServer = false; + let connectionOnServer = false; - server.once('connection', function () { + (async () => { + for await (let {socket} of server.listener('connection')) { connectionOnServer = true; - }); - - server.once('handshake', function (socket) { - assert.equal(server.pendingClientsCount, 1); - assert.notEqual(server.pendingClients[socket.id], null); - socket.once('disconnect', function () { - if (!connectionOnServer) { - socketDisconnectedBeforeConnect = true; - } - socketDisconnected = true; - }); - socket.once('connectAbort', function (code, reason) { - clientSocketAborted = true; - assert.equal(code, 4444); - assert.equal(reason, 'Disconnect before handshake'); - }); - }); + connectionHandler(socket); + } + })(); - var serverDisconnected = false; - var serverSocketAborted = false; + await server.listener('ready').once(); - server.once('disconnection', function () { - serverDisconnected = true; - }); + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber + }); - server.once('connectionAbort', function () { - serverSocketAborted = true; - }); + let socketDisconnected = false; + let socketDisconnectedBeforeConnect = false; + let clientSocketAborted = false; - setTimeout(function () { - client.disconnect(4444, 'Disconnect before handshake'); - }, 100); + (async () => { + let {socket} = await server.listener('handshake').once(); + assert.equal(server.pendingClientsCount, 1); + assert.notEqual(server.pendingClients[socket.id], null); - setTimeout(function () { - assert.equal(socketDisconnected, false); - assert.equal(socketDisconnectedBeforeConnect, false); - assert.equal(clientSocketAborted, true); - assert.equal(serverSocketAborted, true); - assert.equal(serverDisconnected, false); - done(); - }, 1000); - }); + (async () => { + await socket.listener('disconnect').once(); + if (!connectionOnServer) { + socketDisconnectedBeforeConnect = true; + } + socketDisconnected = true; + })(); + + (async () => { + let event = await socket.listener('connectAbort').once(); + clientSocketAborted = true; + assert.equal(event.code, 4444); + assert.equal(event.reason, 'Disconnect before handshake'); + })(); + })(); + + let serverDisconnected = false; + let serverSocketAborted = false; + + (async () => { + await server.listener('disconnection').once(); + serverDisconnected = true; + })(); + + (async () => { + await server.listener('connectionAbort').once(); + serverSocketAborted = true; + })(); + + await wait(100); + client.disconnect(4444, 'Disconnect before handshake'); + + await wait(1000); + assert.equal(socketDisconnected, false); + assert.equal(socketDisconnectedBeforeConnect, false); + assert.equal(clientSocketAborted, true); + assert.equal(serverSocketAborted, true); + assert.equal(serverDisconnected, false); }); - it('Server-side socket disconnect event should trigger if the socket completed the handshake (not connectAbort)', function (done) { + it('Server-side socket disconnect event should trigger if the socket completed the handshake (not connectAbort)', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, @@ -960,68 +1037,74 @@ describe('Integration tests', function () { return resolveAfterTimeout(10, {}); } }); - server.on('connection', connectionHandler); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - client.on('error', function () {}); - - var socketDisconnected = false; - var socketDisconnectedBeforeConnect = false; - var clientSocketAborted = false; - var connectionOnServer = false; + let connectionOnServer = false; - server.once('connection', function () { + (async () => { + for await (let {socket} of server.listener('connection')) { connectionOnServer = true; - }); - - server.once('handshake', function (socket) { - assert.equal(server.pendingClientsCount, 1); - assert.notEqual(server.pendingClients[socket.id], null); - socket.once('disconnect', function (code, reason) { - if (!connectionOnServer) { - socketDisconnectedBeforeConnect = true; - } - socketDisconnected = true; - assert.equal(code, 4445); - assert.equal(reason, 'Disconnect after handshake'); - }); - socket.once('connectAbort', function () { - clientSocketAborted = true; - }); - }); + connectionHandler(socket); + } + })(); - var serverDisconnected = false; - var serverSocketAborted = false; + await server.listener('ready').once(); - server.once('disconnection', function () { - serverDisconnected = true; - }); + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber + }); - server.once('connectionAbort', function () { - serverSocketAborted = true; - }); + let socketDisconnected = false; + let socketDisconnectedBeforeConnect = false; + let clientSocketAborted = false; - setTimeout(function () { - client.disconnect(4445, 'Disconnect after handshake'); - }, 200); + (async () => { + let {socket} = await server.listener('handshake').once(); + assert.equal(server.pendingClientsCount, 1); + assert.notEqual(server.pendingClients[socket.id], null); - setTimeout(function () { - assert.equal(socketDisconnectedBeforeConnect, false); - assert.equal(socketDisconnected, true); - assert.equal(clientSocketAborted, false); - assert.equal(serverDisconnected, true); - assert.equal(serverSocketAborted, false); - done(); - }, 1000); - }); + (async () => { + let event = await socket.listener('disconnect').once(); + if (!connectionOnServer) { + socketDisconnectedBeforeConnect = true; + } + socketDisconnected = true; + assert.equal(event.code, 4445); + assert.equal(event.reason, 'Disconnect after handshake'); + })(); + + (async () => { + let event = await socket.listener('connectAbort').once(); + clientSocketAborted = true; + })(); + })(); + + let serverDisconnected = false; + let serverSocketAborted = false; + + (async () => { + await server.listener('disconnection').once(); + serverDisconnected = true; + })(); + + (async () => { + await server.listener('connectionAbort').once(); + serverSocketAborted = true; + })(); + + await wait(200); + client.disconnect(4445, 'Disconnect after handshake'); + + await wait(1000); + + assert.equal(socketDisconnectedBeforeConnect, false); + assert.equal(socketDisconnected, true); + assert.equal(clientSocketAborted, false); + assert.equal(serverDisconnected, true); + assert.equal(serverSocketAborted, false); }); - it('The close event should trigger when the socket loses the connection before the handshake', function (done) { + it('The close event should trigger when the socket loses the connection before the handshake', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, @@ -1032,49 +1115,57 @@ describe('Integration tests', function () { return resolveAfterTimeout(500, {}); } }); - server.on('connection', connectionHandler); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - client.on('error', function () {}); - var serverSocketClosed = false; - var serverSocketAborted = false; - var serverClosure = false; + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionOnServer = true; + connectionHandler(socket); + } + })(); - server.on('handshake', function (socket) { - socket.once('close', function (code, reason) { - serverSocketClosed = true; - assert.equal(code, 4444); - assert.equal(reason, 'Disconnect before handshake'); - }); - }); + await server.listener('ready').once(); - server.once('connectionAbort', function () { + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber + }); + + let serverSocketClosed = false; + let serverSocketAborted = false; + let serverClosure = false; + + (async () => { + for await (let {socket} of server.listener('handshake')) { + let event = await socket.listener('close').once(); + serverSocketClosed = true; + assert.equal(event.code, 4444); + assert.equal(event.reason, 'Disconnect before handshake'); + } + })(); + + (async () => { + for await (let event of server.listener('connectionAbort')) { serverSocketAborted = true; - }); + } + })(); - server.on('closure', function (socket) { - assert.equal(socket.state, socket.CLOSED); + (async () => { + for await (let event of server.listener('closure')) { + assert.equal(event.socket.state, event.socket.CLOSED); serverClosure = true; - }); + } + })(); - setTimeout(function () { - client.disconnect(4444, 'Disconnect before handshake'); - }, 100); - setTimeout(function () { - assert.equal(serverSocketClosed, true); - assert.equal(serverSocketAborted, true); - assert.equal(serverClosure, true); - done(); - }, 1000); - }); + await wait(100); + client.disconnect(4444, 'Disconnect before handshake'); + + await wait(1000); + assert.equal(serverSocketClosed, true); + assert.equal(serverSocketAborted, true); + assert.equal(serverClosure, true); }); - it('The close event should trigger when the socket loses the connection after the handshake', function (done) { + it('The close event should trigger when the socket loses the connection after the handshake', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, @@ -1085,70 +1176,85 @@ describe('Integration tests', function () { return resolveAfterTimeout(0, {}); } }); - server.on('connection', connectionHandler); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - client.on('error', function () {}); - var serverSocketClosed = false; - var serverSocketDisconnected = false; - var serverClosure = false; + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionOnServer = true; + connectionHandler(socket); + } + })(); - server.on('handshake', function (socket) { - socket.once('close', function (code, reason) { - serverSocketClosed = true; - assert.equal(code, 4445); - assert.equal(reason, 'Disconnect after handshake'); - }); - }); + await server.listener('ready').once(); - server.once('disconnection', function () { + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber + }); + + let serverSocketClosed = false; + let serverSocketDisconnected = false; + let serverClosure = false; + + (async () => { + for await (let {socket} of server.listener('handshake')) { + let event = await socket.listener('close').once(); + serverSocketClosed = true; + assert.equal(event.code, 4445); + assert.equal(event.reason, 'Disconnect after handshake'); + } + })(); + + (async () => { + for await (let event of server.listener('disconnection')) { serverSocketDisconnected = true; - }); + } + })(); - server.on('closure', function (socket) { - assert.equal(socket.state, socket.CLOSED); + (async () => { + for await (let event of server.listener('closure')) { + assert.equal(event.socket.state, event.socket.CLOSED); serverClosure = true; - }); + } + })(); - setTimeout(function () { - client.disconnect(4445, 'Disconnect after handshake'); - }, 100); - setTimeout(function () { - assert.equal(serverSocketClosed, true); - assert.equal(serverSocketDisconnected, true); - assert.equal(serverClosure, true); - done(); - }, 300); - }); + await wait(100); + client.disconnect(4445, 'Disconnect after handshake'); + + await wait(1000); + assert.equal(serverSocketClosed, true); + assert.equal(serverSocketDisconnected, true); + assert.equal(serverClosure, true); }); }); describe('Socket pub/sub', function () { - it('Should support subscription batching', function (done) { + it('Should support subscription batching', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); - server.on('connection', function (socket) { - connectionHandler(socket); - var isFirstMessage = true; - socket.on('message', function (rawMessage) { - if (isFirstMessage) { - var data = JSON.parse(rawMessage); - // All 20 subscriptions should arrive as a single message. - assert.equal(data.length, 20); - isFirstMessage = false; - } - }); - }); - var subscribeMiddlewareCounter = 0; + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + let isFirstMessage = true; + + (async () => { + for await (let {message} of socket.listener('message')) { + if (isFirstMessage) { + let data = JSON.parse(message); + // All 20 subscriptions should arrive as a single message. + assert.equal(data.length, 20); + isFirstMessage = false; + } + } + })(); + } + })(); + + let subscribeMiddlewareCounter = 0; + // Each subscription should pass through the middleware individually, even // though they were sent as a batch/array. server.addMiddleware(server.MIDDLEWARE_SUBSCRIBE, function (req, next) { @@ -1158,7 +1264,7 @@ describe('Integration tests', function () { assert.equal(JSON.stringify(req.data), JSON.stringify({foo: 123})); } else if (req.channel === 'my-channel-12') { // Block my-channel-12 - var err = new Error('You cannot subscribe to channel 12'); + let err = new Error('You cannot subscribe to channel 12'); err.name = 'UnauthorizedSubscribeError'; next(err); return; @@ -1166,169 +1272,190 @@ describe('Integration tests', function () { next(); }); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - var channelList = []; - for (var i = 0; i < 20; i++) { - var subscribeOptions = { - batch: true - }; - if (i === 10) { - subscribeOptions.data = {foo: 123}; - } - channelList.push( - client.subscribe('my-channel-' + i, subscribeOptions) - ); + await server.listener('ready').once(); + + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber + }); + + let channelList = []; + for (let i = 0; i < 20; i++) { + let subscribeOptions = { + batch: true + }; + if (i === 10) { + subscribeOptions.data = {foo: 123}; } - channelList[12].on('subscribe', function (err) { + channelList.push( + client.subscribe('my-channel-' + i, subscribeOptions) + ); + } + + (async () => { + for await (let event of channelList[12].listener('subscribe')) { throw new Error('The my-channel-12 channel should have been blocked by MIDDLEWARE_SUBSCRIBE'); - }); - channelList[12].on('subscribeFail', function (err) { - assert.notEqual(err, null); - assert.equal(err.name, 'UnauthorizedSubscribeError'); - }); - channelList[19].watch(function (data) { - assert.equal(data, 'Hello!'); - assert.equal(subscribeMiddlewareCounter, 20); - done(); - }); - channelList[0].on('subscribe', function () { + } + })(); + + (async () => { + for await (let event of channelList[12].listener('subscribeFail')) { + assert.notEqual(event.error, null); + assert.equal(event.error.name, 'UnauthorizedSubscribeError'); + } + })(); + + (async () => { + for await (let event of channelList[0].listener('subscribe')) { client.publish('my-channel-19', 'Hello!'); - }); - }); + } + })(); + + for await (let data of channelList[19]) { + assert.equal(data, 'Hello!'); + assert.equal(subscribeMiddlewareCounter, 20); + break; + } }); - it('Client should not be able to subscribe to a channel before the handshake has completed', function (done) { + it('Client should not be able to subscribe to a channel before the handshake has completed', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + server.setAuthEngine({ verifyToken: function (signedAuthToken, verificationKey, verificationOptions) { return resolveAfterTimeout(500, {}); } }); - server.on('connection', connectionHandler); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - var isSubscribed = false; - var error; + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); - server.on('subscription', function (socket, channel) { - isSubscribed = true; - }); + await server.listener('ready').once(); - // Hack to capture the error without relying on the standard client flow. - client.transport._callbackMap[2] = { - event: '#subscribe', - data: {"channel":"someChannel"}, - callback: function (err) { - error = err; - } - }; + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber + }); - // Trick the server by sending a fake subscribe before the handshake is done. - client.transport.socket.on('open', function () { - client.send('{"event":"#subscribe","data":{"channel":"someChannel"},"cid":2}'); - }); + let isSubscribed = false; + let error; - setTimeout(function () { - assert.equal(isSubscribed, false); - assert.notEqual(error, null); - assert.equal(error.name, 'InvalidActionError'); - done(); - }, 1000); + (async () => { + for await (let event of server.listener('subscription')) { + isSubscribed = true; + } + })(); + + // Hack to capture the error without relying on the standard client flow. + client.transport._callbackMap[2] = { + event: '#subscribe', + data: {"channel":"someChannel"}, + callback: function (err) { + error = err; + } + }; + + // Trick the server by sending a fake subscribe before the handshake is done. + client.transport.socket.on('open', function () { + client.send('{"event":"#subscribe","data":{"channel":"someChannel"},"cid":2}'); }); + + await wait(1000); + assert.equal(isSubscribed, false); + assert.notEqual(error, null); + assert.equal(error.name, 'InvalidActionError'); }); - it('Server should be able to handle invalid #subscribe and #unsubscribe and #publish packets without crashing', function (done) { + it('Server should be able to handle invalid #subscribe and #unsubscribe and #publish events without crashing', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); - server.on('connection', connectionHandler); + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); + await server.listener('ready').once(); - var nullInChannelArrayError; - var objectAsChannelNameError; - var nullChannelNameError; - var nullUnsubscribeError; - - var undefinedPublishError; - var objectAsChannelNamePublishError; - var nullPublishError; - - // Hacks to capture the errors without relying on the standard client flow. - client.transport._callbackMap[2] = { - event: '#subscribe', - data: [null], - callback: function (err) { - nullInChannelArrayError = err; - } - }; - client.transport._callbackMap[3] = { - event: '#subscribe', - data: {"channel": {"hello": 123}}, - callback: function (err) { - objectAsChannelNameError = err; - } - }; - client.transport._callbackMap[4] = { - event: '#subscribe', - data: null, - callback: function (err) { - nullChannelNameError = err; - } - }; - client.transport._callbackMap[5] = { - event: '#unsubscribe', - data: [null], - callback: function (err) { - nullUnsubscribeError = err; - } - }; - client.transport._callbackMap[6] = { - event: '#publish', - data: null, - callback: function (err) { - undefinedPublishError = err; - } - }; - client.transport._callbackMap[7] = { - event: '#publish', - data: {"channel": {"hello": 123}}, - callback: function (err) { - objectAsChannelNamePublishError = err; - } - }; - client.transport._callbackMap[8] = { - event: '#publish', - data: {"channel": null}, - callback: function (err) { - nullPublishError = err; - } - }; + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber + }); + + let nullInChannelArrayError; + let objectAsChannelNameError; + let nullChannelNameError; + let nullUnsubscribeError; + + let undefinedPublishError; + let objectAsChannelNamePublishError; + let nullPublishError; + + // Hacks to capture the errors without relying on the standard client flow. + client.transport._callbackMap[2] = { + event: '#subscribe', + data: [null], + callback: function (err) { + nullInChannelArrayError = err; + } + }; + client.transport._callbackMap[3] = { + event: '#subscribe', + data: {"channel": {"hello": 123}}, + callback: function (err) { + objectAsChannelNameError = err; + } + }; + client.transport._callbackMap[4] = { + event: '#subscribe', + data: null, + callback: function (err) { + nullChannelNameError = err; + } + }; + client.transport._callbackMap[5] = { + event: '#unsubscribe', + data: [null], + callback: function (err) { + nullUnsubscribeError = err; + } + }; + client.transport._callbackMap[6] = { + event: '#publish', + data: null, + callback: function (err) { + undefinedPublishError = err; + } + }; + client.transport._callbackMap[7] = { + event: '#publish', + data: {"channel": {"hello": 123}}, + callback: function (err) { + objectAsChannelNamePublishError = err; + } + }; + client.transport._callbackMap[8] = { + event: '#publish', + data: {"channel": null}, + callback: function (err) { + nullPublishError = err; + } + }; - // Trick the server by sending a fake subscribe before the handshake is done. - client.on('connect', function () { + (async () => { + for await (let event of client.listener('connect')) { + // Trick the server by sending a fake subscribe before the handshake is done. client.send('{"event":"#subscribe","data":[null],"cid":2}'); client.send('{"event":"#subscribe","data":{"channel":{"hello":123}},"cid":3}'); client.send('{"event":"#subscribe","data":null,"cid":4}'); @@ -1336,78 +1463,164 @@ describe('Integration tests', function () { client.send('{"event":"#publish","data":null,"cid":6}'); client.send('{"event":"#publish","data":{"channel":{"hello":123}},"cid":7}'); client.send('{"event":"#publish","data":{"channel":null},"cid":8}'); - }); + } + })(); - setTimeout(function () { - assert.notEqual(nullInChannelArrayError, null); - // console.log('nullInChannelArrayError:', nullInChannelArrayError); - assert.notEqual(objectAsChannelNameError, null); - // console.log('objectAsChannelNameError:', objectAsChannelNameError); - assert.notEqual(nullChannelNameError, null); - // console.log('nullChannelNameError:', nullChannelNameError); - assert.notEqual(nullUnsubscribeError, null); - // console.log('nullUnsubscribeError:', nullUnsubscribeError); - assert.notEqual(undefinedPublishError, null); - // console.log('undefinedPublishError:', undefinedPublishError); - assert.notEqual(objectAsChannelNamePublishError, null); - // console.log('objectAsChannelNamePublishError:', objectAsChannelNamePublishError); - assert.notEqual(nullPublishError, null); - // console.log('nullPublishError:', nullPublishError); - - done(); - }, 300); - }); + await wait(300); + + assert.notEqual(nullInChannelArrayError, null); + assert.notEqual(objectAsChannelNameError, null); + assert.notEqual(nullChannelNameError, null); + assert.notEqual(nullUnsubscribeError, null); + assert.notEqual(undefinedPublishError, null); + assert.notEqual(objectAsChannelNamePublishError, null); + assert.notEqual(nullPublishError, null); }); - it('When default SCSimpleBroker broker engine is used, unsubscribe event should trigger before disconnect event', function (done) { + it('When default SCSimpleBroker broker engine is used, disconnect event should trigger before unsubscribe event', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); - var eventList = []; + let eventList = []; - server.on('connection', function (socket) { - socket.on('unsubscribe', function (channel) { - eventList.push({ - type: 'unsubscribe', - channel: channel - }); + (async () => { + await server.listener('ready').once(); + + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); - socket.on('disconnect', function (code, reason) { + + await client.subscribe('foo').listener('subscribe').once(); + await wait(200); + client.disconnect(); + })(); + + let {socket} = await server.listener('connection').once(); + + (async () => { + for await (let event of socket.listener('unsubscribe')) { eventList.push({ - type: 'disconnect', - code: code, - reason: reason + type: 'unsubscribe', + channel: event.channel }); - assert.equal(eventList[0].type, 'unsubscribe'); - assert.equal(eventList[0].channel, 'foo'); - assert.equal(eventList[1].type, 'disconnect'); + } + })(); - done(); - }); + let disconnectPacket = await socket.listener('disconnect').once(); + eventList.push({ + type: 'disconnect', + code: disconnectPacket.code, + reason: disconnectPacket.data }); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); + await wait(0); + assert.equal(eventList[0].type, 'disconnect'); + assert.equal(eventList[1].type, 'unsubscribe'); + assert.equal(eventList[1].channel, 'foo'); + }); - client.subscribe('foo').on('subscribe', function () { - setTimeout(function () { - client.disconnect(); - }, 200); - }); + it('When default SCSimpleBroker broker engine is used, scServer.exchange should support consuming data from a channel', async function () { + portNumber++; + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + + await server.listener('ready').once(); + + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber + }); + + (async () => { + await client.listener('connect').once(); + + client.publish('foo', 'hi1'); + await wait(10); + client.publish('foo', 'hi2'); + })(); + + let receivedSubscribedData = []; + let receivedChannelData = []; + + (async () => { + let subscription = server.exchange.subscribe('foo'); + for await (let data of subscription) { + receivedSubscribedData.push(data); + } + })(); + + let channel = server.exchange.channel('foo'); + for await (let data of channel) { + receivedChannelData.push(data); + if (receivedChannelData.length > 1) { + break; + } + } + + assert.equal(server.exchange.isSubscribed('foo'), true); + assert.equal(server.exchange.subscriptions().join(','), 'foo'); + + assert.equal(receivedSubscribedData[0], 'hi1'); + assert.equal(receivedSubscribedData[1], 'hi2'); + assert.equal(receivedChannelData[0], 'hi1'); + assert.equal(receivedChannelData[1], 'hi2'); + }); + + it('When default SCSimpleBroker broker engine is used, scServer.exchange should support publishing data to a channel', async function () { + portNumber++; + server = socketClusterServer.listen(portNumber, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + + await server.listener('ready').once(); + + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); + + (async () => { + await client.listener('subscribe').once(); + server.exchange.publish('bar', 'hello1'); + await wait(10); + server.exchange.publish('bar', 'hello2'); + })(); + + let receivedSubscribedData = []; + let receivedChannelData = []; + + (async () => { + let subscription = client.subscribe('bar'); + for await (let data of subscription) { + receivedSubscribedData.push(data); + } + })(); + + let channel = client.channel('bar'); + for await (let data of channel) { + receivedChannelData.push(data); + if (receivedChannelData.length > 1) { + break; + } + } + + assert.equal(receivedSubscribedData[0], 'hello1'); + assert.equal(receivedSubscribedData[1], 'hello2'); + assert.equal(receivedChannelData[0], 'hello1'); + assert.equal(receivedChannelData[1], 'hello2'); }); - it('When disconnecting a socket, the unsubscribe event should trigger before disconnect event (accounting for delayed unsubscribe by brokerEngine)', function (done) { + it('When disconnecting a socket, the unsubscribe event should trigger after the disconnect event', async function () { portNumber++; - var customBrokerEngine = new SCSimpleBroker(); - var defaultUnsubscribeSocket = customBrokerEngine.unsubscribeSocket; + let customBrokerEngine = new SCSimpleBroker(); + let defaultUnsubscribeSocket = customBrokerEngine.unsubscribeSocket; customBrokerEngine.unsubscribeSocket = function (socket, channel) { return resolveAfterTimeout(100, defaultUnsubscribeSocket.call(this, socket, channel)); }; @@ -1418,47 +1631,49 @@ describe('Integration tests', function () { brokerEngine: customBrokerEngine }); - var eventList = []; + let eventList = []; - server.on('connection', function (socket) { - socket.on('unsubscribe', function (channel) { - eventList.push({ - type: 'unsubscribe', - channel: channel - }); + (async () => { + await server.listener('ready').once(); + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); - socket.on('disconnect', function (code, reason) { + + for await (let event of client.subscribe('foo').listener('subscribe')) { + (async () => { + await wait(200); + client.disconnect(); + })(); + } + })(); + + let {socket} = await server.listener('connection').once(); + + (async () => { + for await (let event of socket.listener('unsubscribe')) { eventList.push({ - type: 'disconnect', - code: code, - reason: reason + type: 'unsubscribe', + channel: event.channel }); + } + })(); - assert.equal(eventList[0].type, 'unsubscribe'); - assert.equal(eventList[0].channel, 'foo'); - assert.equal(eventList[1].type, 'disconnect'); + let event = await socket.listener('disconnect').once(); - done(); - }); + eventList.push({ + type: 'disconnect', + code: event.code, + reason: event.reason }); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - client.on('error', function () {}); - - client.subscribe('foo').on('subscribe', function () { - setTimeout(function () { - client.disconnect(); - }, 200); - }); - }); + await wait(0); + assert.equal(eventList[0].type, 'disconnect'); + assert.equal(eventList[1].type, 'unsubscribe'); + assert.equal(eventList[1].channel, 'foo'); }); - it('Socket should emit an error when trying to unsubscribe to a channel which it is not subscribed to', function (done) { + it('Socket should emit an error when trying to unsubscribe to a channel which it is not subscribed to', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { @@ -1466,35 +1681,43 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE }); - var errorList = []; - - server.on('connection', function (socket) { - socket.on('error', function (err) { - errorList.push(err); - }); - }); + let errorList = []; - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); + (async () => { + for await (let {socket} of server.listener('connection')) { + (async () => { + for await (let {error} of socket.listener('error')) { + errorList.push(error); + } + })(); + } + })(); - client.transmit('#unsubscribe', 'bar'); + await server.listener('ready').once(); - setTimeout(function () { - assert.equal(errorList.length, 1); - assert.equal(errorList[0].name, 'BrokerError'); - done(); - }, 100); + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); + + let error; + try { + await client.invoke('#unsubscribe', 'bar'); + } catch (err) { + error = err; + } + assert.notEqual(error, null); + assert.equal(error.name, 'BrokerError'); + + await wait(100); + assert.equal(errorList.length, 1); + assert.equal(errorList[0].name, 'BrokerError'); }); - it('Socket should not receive messages from a channel which it has only just unsubscribed from (accounting for delayed unsubscribe by brokerEngine)', function (done) { + it('Socket should not receive messages from a channel which it has only just unsubscribed from (accounting for delayed unsubscribe by brokerEngine)', async function () { portNumber++; - var customBrokerEngine = new SCSimpleBroker(); - var defaultUnsubscribeSocket = customBrokerEngine.unsubscribeSocket; + let customBrokerEngine = new SCSimpleBroker(); + let defaultUnsubscribeSocket = customBrokerEngine.unsubscribeSocket; customBrokerEngine.unsubscribeSocket = function (socket, channel) { return resolveAfterTimeout(300, defaultUnsubscribeSocket.call(this, socket, channel)); }; @@ -1505,45 +1728,50 @@ describe('Integration tests', function () { brokerEngine: customBrokerEngine }); - server.on('connection', function (socket) { - socket.on('unsubscribe', function (channelName) { - if (channelName === 'foo') { - server.exchange.publish('foo', 'hello'); - } - }); - }); + (async () => { + for await (let {socket} of server.listener('connection')) { + (async () => { + for await (let event of socket.listener('unsubscribe')) { + if (event.channel === 'foo') { + server.exchange.publish('foo', 'hello'); + } + } + })(); + } + })(); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - // Stub the isSubscribed method so that it always returns true. - // That way the client will always invoke watchers whenever - // it receives a #publish event. - client.isSubscribed = function () { return true; }; + await server.listener('ready').once(); + + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber + }); + // Stub the isSubscribed method so that it always returns true. + // That way the client will always invoke watchers whenever + // it receives a #publish event. + client.isSubscribed = function () { return true; }; - var messageList = []; + let messageList = []; - var fooChannel = client.subscribe('foo'); + let fooChannel = client.subscribe('foo'); - client.watch('foo', function (data) { + (async () => { + for await (let data of fooChannel) { messageList.push(data); - }); + } + })(); - fooChannel.on('subscribe', function () { - client.transmit('#unsubscribe', 'foo'); - }); + (async () => { + for await (let event of fooChannel.listener('subscribe')) { + client.invoke('#unsubscribe', 'foo'); + } + })(); - setTimeout(function () { - assert.equal(messageList.length, 0); - done(); - }, 200); - }); + await wait(200); + assert.equal(messageList.length, 0); }); - it('Socket channelSubscriptions and channelSubscriptionsCount should update when socket.kickOut(channel) is called', function (done) { + it('Socket channelSubscriptions and channelSubscriptionsCount should update when socket.kickOut(channel) is called', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { @@ -1551,45 +1779,49 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE }); - var errorList = []; - var serverSocket; - var wasKickOutCalled = false; + let errorList = []; + let serverSocket; + let wasKickOutCalled = false; - server.on('connection', function (socket) { - serverSocket = socket; - socket.on('error', function (err) { - errorList.push(err); - }); - socket.on('subscribe', function (channelName) { - if (channelName === 'foo') { - setTimeout(function () { - wasKickOutCalled = true; - socket.kickOut('foo', 'Socket was kicked out of the channel'); - }, 50); - } - }); - }); + (async () => { + for await (let {socket} of server.listener('connection')) { + serverSocket = socket; - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); + (async () => { + for await (let {error} of socket.listener('error')) { + errorList.push(error); + } + })(); + + (async () => { + for await (let event of socket.listener('subscribe')) { + if (event.channel === 'foo') { + await wait(50); + wasKickOutCalled = true; + socket.kickOut('foo', 'Socket was kicked out of the channel'); + } + } + })(); + } + })(); - client.subscribe('foo'); + await server.listener('ready').once(); - setTimeout(function () { - assert.equal(errorList.length, 0); - assert.equal(wasKickOutCalled, true); - assert.equal(serverSocket.channelSubscriptionsCount, 0); - assert.equal(Object.keys(serverSocket.channelSubscriptions).length, 0); - done(); - }, 100); + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); + + client.subscribe('foo'); + + await wait(100); + assert.equal(errorList.length, 0); + assert.equal(wasKickOutCalled, true); + assert.equal(serverSocket.channelSubscriptionsCount, 0); + assert.equal(Object.keys(serverSocket.channelSubscriptions).length, 0); }); - it('Socket channelSubscriptions and channelSubscriptionsCount should update when socket.kickOut() is called without arguments', function (done) { + it('Socket channelSubscriptions and channelSubscriptionsCount should update when socket.kickOut() is called without arguments', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { @@ -1597,108 +1829,111 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE }); - var errorList = []; - var serverSocket; - var wasKickOutCalled = false; + let errorList = []; + let serverSocket; + let wasKickOutCalled = false; - server.on('connection', function (socket) { - serverSocket = socket; - socket.on('error', function (err) { - errorList.push(err); - }); - socket.on('subscribe', function (channelName) { - if (socket.channelSubscriptionsCount === 2) { - setTimeout(function () { - wasKickOutCalled = true; - socket.kickOut(); - }, 50); - } - }); - }); + (async () => { + for await (let {socket} of server.listener('connection')) { + serverSocket = socket; - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); + (async () => { + for await (let {error} of socket.listener('error')) { + errorList.push(error); + } + })(); + + (async () => { + for await (let event of socket.listener('subscribe')) { + if (socket.channelSubscriptionsCount === 2) { + await wait(50); + wasKickOutCalled = true; + socket.kickOut(); + } + } + })(); + } + })(); - client.subscribe('foo'); - client.subscribe('bar'); + await server.listener('ready').once(); - setTimeout(function () { - assert.equal(errorList.length, 0); - assert.equal(wasKickOutCalled, true); - assert.equal(serverSocket.channelSubscriptionsCount, 0); - assert.equal(Object.keys(serverSocket.channelSubscriptions).length, 0); - done(); - }, 200); + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); + + client.subscribe('foo'); + client.subscribe('bar'); + + await wait(200); + assert.equal(errorList.length, 0); + assert.equal(wasKickOutCalled, true); + assert.equal(serverSocket.channelSubscriptionsCount, 0); + assert.equal(Object.keys(serverSocket.channelSubscriptions).length, 0); }); }); describe('Socket destruction', function () { - it('Server socket destroy should disconnect the socket', function (done) { + it('Server socket destroy should disconnect the socket', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); - server.on('connection', function (socket) { - setTimeout(function () { + (async () => { + for await (let {socket} of server.listener('connection')) { + await wait(100); socket.destroy(1000, 'Custom reason'); - }, 100); - }); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - client.on('disconnect', function (code, reason) { - assert.equal(code, 1000); - assert.equal(reason, 'Custom reason'); - assert.equal(server.clientsCount, 0); - assert.equal(server.pendingClientsCount, 0); - done(); - }); + } + })(); + + await server.listener('ready').once(); + + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); + + let {code, reason} = await client.listener('disconnect').once(); + assert.equal(code, 1000); + assert.equal(reason, 'Custom reason'); + assert.equal(server.clientsCount, 0); + assert.equal(server.pendingClientsCount, 0); }); - it('Server socket destroy should set the active property on the socket to false', function (done) { + it('Server socket destroy should set the active property on the socket to false', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); - var serverSocket; + let serverSocket; - server.on('connection', function (socket) { - serverSocket = socket; - assert.equal(socket.active, true); - setTimeout(function () { + (async () => { + for await (let {socket} of server.listener('connection')) { + serverSocket = socket; + assert.equal(socket.active, true); + await wait(100); socket.destroy(); - }, 100); - }); - server.on('ready', function () { - client = socketCluster.connect({ - hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - client.on('disconnect', function (code, reason) { - assert.equal(serverSocket.active, false); - done(); - }); + } + })(); + + await server.listener('ready').once(); + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: portNumber }); + + await client.listener('disconnect').once(); + assert.equal(serverSocket.active, false); }); }); describe('Socket Ping/pong', function () { - describe('When when pingTimeoutDisabled is not set (false)', function () { - beforeEach('Launch server with ping options before start', function (done) { + describe('When when pingTimeoutDisabled is not set', function () { + beforeEach('Launch server with ping options before start', async function () { portNumber++; // Intentionally make pingInterval higher than pingTimeout, that // way the client will never receive a ping or send back a pong. @@ -1708,60 +1943,62 @@ describe('Integration tests', function () { pingInterval: 2000, pingTimeout: 500 }); - server.on('ready', function () { - done(); - }); + + await server.listener('ready').once(); }); - afterEach('Shut down server afterwards', function (done) { - destroyTestCase(function () { - server.close(); - done(); - }); + afterEach('Shut down server afterwards', async function () { + destroyTestCase(); + server.close(); }); - it('Should disconnect socket if server does not receive a pong from client before timeout', function (done) { - client = socketCluster.connect({ + it('Should disconnect socket if server does not receive a pong from client before timeout', async function () { + client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: portNumber, - multiplex: false + port: portNumber }); - var serverWarning = null; - server.on('warning', function (err) { - serverWarning = err; - }); + let serverWarning = null; + (async () => { + for await (let {warning} of server.listener('warning')) { + serverWarning = warning; + } + })(); - var serverDisconnectionCode = null; - server.on('disconnection', function (socket, code) { - serverDisconnectionCode = code; - }); + let serverDisconnectionCode = null; + (async () => { + for await (let event of server.listener('disconnection')) { + serverDisconnectionCode = event.code; + } + })(); - var clientError = null; - client.on('error', function (err) { - clientError = err; - }); + let clientError = null; + (async () => { + for await (let {error} of client.listener('error')) { + clientError = error; + } + })(); - var clientDisconnectCode = null; - client.on('disconnect', function (code) { - clientDisconnectCode = code; - }); + let clientDisconnectCode = null; + (async () => { + for await (let event of client.listener('disconnect')) { + clientDisconnectCode = event.code; + } + })(); - setTimeout(function () { - assert.notEqual(clientError, null); - assert.equal(clientError.name, 'SocketProtocolError'); - assert.equal(clientDisconnectCode, 4000); + await wait(1000); + assert.notEqual(clientError, null); + assert.equal(clientError.name, 'SocketProtocolError'); + assert.equal(clientDisconnectCode, 4001); - assert.notEqual(serverWarning, null); - assert.equal(serverWarning.name, 'SocketProtocolError'); - assert.equal(serverDisconnectionCode, 4001); - done(); - }, 1000); + assert.notEqual(serverWarning, null); + assert.equal(serverWarning.name, 'SocketProtocolError'); + assert.equal(serverDisconnectionCode, 4001); }); }); describe('When when pingTimeoutDisabled is true', function () { - beforeEach('Launch server with ping options before start', function (done) { + beforeEach('Launch server with ping options before start', async function () { portNumber++; // Intentionally make pingInterval higher than pingTimeout, that // way the client will never receive a ping or send back a pong. @@ -1772,274 +2009,259 @@ describe('Integration tests', function () { pingTimeout: 500, pingTimeoutDisabled: true }); - server.on('ready', function () { - done(); - }); + + await server.listener('ready').once(); }); - afterEach('Shut down server afterwards', function (done) { - destroyTestCase(function () { - server.close(); - done(); - }); + afterEach('Shut down server afterwards', async function () { + destroyTestCase(); + server.close(); }); - it('Should not disconnect socket if server does not receive a pong from client before timeout', function (done) { - client = socketCluster.connect({ + it('Should not disconnect socket if server does not receive a pong from client before timeout', async function () { + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: portNumber, - multiplex: false, pingTimeoutDisabled: true }); - var serverWarning = null; - server.on('warning', function (err) { - serverWarning = err; - }); + let serverWarning = null; + (async () => { + for await (let {warning} of server.listener('warning')) { + serverWarning = warning; + } + })(); - var serverDisconnectionCode = null; - server.on('disconnection', function (socket, code) { - serverDisconnectionCode = code; - }); + let serverDisconnectionCode = null; + (async () => { + for await (let event of server.listener('disconnection')) { + serverDisconnectionCode = event.code; + } + })(); - var clientError = null; - client.on('error', function (err) { - clientError = err; - }); + let clientError = null; + (async () => { + for await (let {error} of client.listener('error')) { + clientError = error; + } + })(); - var clientDisconnectCode = null; - client.on('disconnect', function (code) { - clientDisconnectCode = code; - }); + let clientDisconnectCode = null; + (async () => { + for await (let event of client.listener('disconnect')) { + clientDisconnectCode = event.code; + } + })(); - setTimeout(function () { - assert.equal(clientError, null); - assert.equal(clientDisconnectCode, null); + await wait(1000); + assert.equal(clientError, null); + assert.equal(clientDisconnectCode, null); - assert.equal(serverWarning, null); - assert.equal(serverDisconnectionCode, null); - done(); - }, 1000); + assert.equal(serverWarning, null); + assert.equal(serverDisconnectionCode, null); }); }); }); describe('Middleware', function () { - var middlewareFunction; - var middlewareWasExecuted = false; + let middlewareFunction; + let middlewareWasExecuted = false; - beforeEach('Launch server without middleware before start', function (done) { + beforeEach('Launch server without middleware before start', async function () { portNumber++; server = socketClusterServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); - server.on('ready', function () { - done(); - }); + await server.listener('ready').once(); }); - afterEach('Shut down server afterwards', function (done) { - destroyTestCase(function () { - server.close(); - done(); - }); + afterEach('Shut down server afterwards', async function () { + destroyTestCase(); + server.close(); }); describe('MIDDLEWARE_AUTHENTICATE', function () { - it('Should not run authenticate middleware if JWT token does not exist', function (done) { - middlewareFunction = function (req, next) { + it('Should not run authenticate middleware if JWT token does not exist', async function () { + middlewareFunction = async function (req) { middlewareWasExecuted = true; - next(); }; server.addMiddleware(server.MIDDLEWARE_AUTHENTICATE, middlewareFunction); - client = socketCluster.connect({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: portNumber, - multiplex: false + port: portNumber }); - client.once('connect', function () { - assert.notEqual(middlewareWasExecuted, true); - done(); - }); + await client.listener('connect').once(); + assert.notEqual(middlewareWasExecuted, true); }); - it('Should run authenticate middleware if JWT token exists', function (done) { + it('Should run authenticate middleware if JWT token exists', async function () { global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); - middlewareFunction = function (req, next) { + middlewareFunction = async function (req) { middlewareWasExecuted = true; - next(); }; server.addMiddleware(server.MIDDLEWARE_AUTHENTICATE, middlewareFunction); - client = socketCluster.connect({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: portNumber, - multiplex: false + port: portNumber }); - client.transmit('login', {username: 'bob'}); - client.once('authenticate', function (state) { - assert.equal(middlewareWasExecuted, true); - done(); - }); + (async () => { + try { + await client.invoke('login', {username: 'bob'}); + } catch (err) {} + })(); + + await client.listener('authenticate').once(); + assert.equal(middlewareWasExecuted, true); }); }); describe('MIDDLEWARE_HANDSHAKE_SC', function () { - it('Should trigger correct events if MIDDLEWARE_HANDSHAKE_SC blocks with an error', function (done) { - var middlewareWasExecuted = false; - var serverWarnings = []; - var clientErrors = []; - var abortStatus; - - middlewareFunction = function (req, next) { - setTimeout(function () { - middlewareWasExecuted = true; - var err = new Error('SC handshake failed because the server was too lazy'); - err.name = 'TooLazyHandshakeError'; - next(err); - }, 100); + it('Should trigger correct events if MIDDLEWARE_HANDSHAKE_SC blocks with an error', async function () { + let middlewareWasExecuted = false; + let serverWarnings = []; + let clientErrors = []; + let abortStatus; + + middlewareFunction = async function (req) { + await wait(100); + middlewareWasExecuted = true; + let err = new Error('SC handshake failed because the server was too lazy'); + err.name = 'TooLazyHandshakeError'; + throw err; }; server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_SC, middlewareFunction); - server.on('warning', function (err) { - serverWarnings.push(err); - }); + (async () => { + for await (let {warning} of server.listener('warning')) { + serverWarnings.push(warning); + } + })(); - client = socketCluster.connect({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: portNumber, - multiplex: false - }); - - client.on('error', function (err) { - clientErrors.push(err); + port: portNumber }); - client.once('connectAbort', function (status, reason) { - abortStatus = status; - }); - - setTimeout(function () { - assert.equal(middlewareWasExecuted, true); - assert.notEqual(clientErrors[0], null); - assert.equal(clientErrors[0].name, 'TooLazyHandshakeError'); - assert.notEqual(clientErrors[1], null); - assert.equal(clientErrors[1].name, 'SocketProtocolError'); - assert.notEqual(serverWarnings[0], null); - assert.equal(serverWarnings[0].name, 'TooLazyHandshakeError'); - assert.notEqual(abortStatus, null); - done(); - }, 200); - }); - - it('Should send back default 4008 status code if MIDDLEWARE_HANDSHAKE_SC blocks without providing a status code', function (done) { - var middlewareWasExecuted = false; - var abortStatus; - var abortReason; - - middlewareFunction = function (req, next) { - setTimeout(function () { - middlewareWasExecuted = true; - var err = new Error('SC handshake failed because the server was too lazy'); - err.name = 'TooLazyHandshakeError'; - next(err); - }, 100); + (async () => { + for await (let {error} of client.listener('error')) { + clientErrors.push(error); + } + })(); + + (async () => { + let event = await client.listener('connectAbort').once(); + abortStatus = event.code; + })(); + + await wait(200); + assert.equal(middlewareWasExecuted, true); + assert.notEqual(clientErrors[0], null); + assert.equal(clientErrors[0].name, 'TooLazyHandshakeError'); + assert.notEqual(clientErrors[1], null); + assert.equal(clientErrors[1].name, 'SocketProtocolError'); + assert.notEqual(serverWarnings[0], null); + assert.equal(serverWarnings[0].name, 'TooLazyHandshakeError'); + assert.notEqual(abortStatus, null); + }); + + it('Should send back default 4008 status code if MIDDLEWARE_HANDSHAKE_SC blocks without providing a status code', async function () { + let middlewareWasExecuted = false; + let abortStatus; + let abortReason; + + middlewareFunction = async function (req) { + await wait(100); + middlewareWasExecuted = true; + let err = new Error('SC handshake failed because the server was too lazy'); + err.name = 'TooLazyHandshakeError'; + throw err; }; server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_SC, middlewareFunction); - client = socketCluster.connect({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: portNumber, - multiplex: false + port: portNumber }); - client.on('error', function () {}); - client.once('connectAbort', function (status, reason) { - abortStatus = status; - abortReason = reason; - }); + (async () => { + let event = await client.listener('connectAbort').once(); + abortStatus = event.code; + abortReason = event.reason; + })(); + + await wait(200); + assert.equal(middlewareWasExecuted, true); + assert.equal(abortStatus, 4008); + assert.equal(abortReason, 'TooLazyHandshakeError: SC handshake failed because the server was too lazy'); + }); - setTimeout(function () { - assert.equal(middlewareWasExecuted, true); - assert.equal(abortStatus, 4008); - assert.equal(abortReason, 'TooLazyHandshakeError: SC handshake failed because the server was too lazy'); - done(); - }, 200); - }); - - it('Should send back custom status code if MIDDLEWARE_HANDSHAKE_SC blocks by providing a status code', function (done) { - var middlewareWasExecuted = false; - var abortStatus; - var abortReason; - - middlewareFunction = function (req, next) { - setTimeout(function () { - middlewareWasExecuted = true; - var err = new Error('SC handshake failed because of invalid query auth parameters'); - err.name = 'InvalidAuthQueryHandshakeError'; - - // Pass custom 4501 status code as the second argument to the next() function. - // We will treat this code as a fatal authentication failure on the front end. - // A status code of 4500 or higher means that the client shouldn't try to reconnect. - next(err, 4501); - }, 100); + it('Should send back custom status code if MIDDLEWARE_HANDSHAKE_SC blocks by providing a status code', async function () { + let middlewareWasExecuted = false; + let abortStatus; + let abortReason; + + middlewareFunction = async function (req) { + await wait(100); + middlewareWasExecuted = true; + let err = new Error('SC handshake failed because of invalid query auth parameters'); + err.name = 'InvalidAuthQueryHandshakeError'; + // Set custom 4501 status code as a property of the error. + // We will treat this code as a fatal authentication failure on the front end. + // A status code of 4500 or higher means that the client shouldn't try to reconnect. + err.statusCode = 4501; + throw err; }; server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_SC, middlewareFunction); - client = socketCluster.connect({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: portNumber, - multiplex: false + port: portNumber }); - client.on('error', function () {}); - client.once('connectAbort', function (status, reason) { - abortStatus = status; - abortReason = reason; - }); + (async () => { + let event = await client.listener('connectAbort').once(); + abortStatus = event.code; + abortReason = event.reason; + })(); - setTimeout(function () { - assert.equal(middlewareWasExecuted, true); - assert.equal(abortStatus, 4501); - assert.equal(abortReason, 'InvalidAuthQueryHandshakeError: SC handshake failed because of invalid query auth parameters'); - done(); - }, 200); + await wait(200); + assert.equal(middlewareWasExecuted, true); + assert.equal(abortStatus, 4501); + assert.equal(abortReason, 'InvalidAuthQueryHandshakeError: SC handshake failed because of invalid query auth parameters'); }); - it('Should connect with a delay if next() is called after a timeout inside the middleware function', function (done) { - var createConnectionTime = null; - var connectEventTime = null; - var abortStatus; - var abortReason; + it('Should connect with a delay if next() is called after a timeout inside the middleware function', async function () { + let createConnectionTime = null; + let connectEventTime = null; + let abortStatus; + let abortReason; - middlewareFunction = function (req, next) { - setTimeout(function () { - next(); - }, 500); + middlewareFunction = async function (req) { + await wait(500); }; server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_SC, middlewareFunction); createConnectionTime = Date.now(); - client = socketCluster.connect({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: portNumber, - multiplex: false + port: portNumber }); - client.once('connectAbort', function (status, reason) { - abortStatus = status; - abortReason = reason; - }); - client.once('connect', function () { - connectEventTime = Date.now(); - assert.equal(connectEventTime - createConnectionTime > 400, true); - done(); - }); + (async () => { + let event = await client.listener('connectAbort').once(); + abortStatus = event.code; + abortReason = event.reason; + })(); + + await client.listener('connect').once(); + connectEventTime = Date.now(); + assert.equal(connectEventTime - createConnectionTime > 400, true); }); }); }); From 39481ed0da0a3d44996edc7dff162506b044ef92 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 27 Dec 2018 01:50:34 +0100 Subject: [PATCH 086/265] Bump dependencies --- package.json | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index c9b6d8f..c9a1426 100644 --- a/package.json +++ b/package.json @@ -8,18 +8,17 @@ "url": "git://github.com/SocketCluster/socketcluster-server.git" }, "dependencies": { - "async": "2.3.0", + "async": "2.6.1", "async-stream-emitter": "^1.1.0", "base64id": "1.0.0", - "component-emitter": "1.2.1", "lodash.clonedeep": "4.5.0", - "sc-auth": "^5.0.2", - "sc-errors": "^1.4.1", + "sc-auth": "^6.0.0", + "sc-errors": "^2.0.0", "sc-formatter": "^3.0.2", - "sc-simple-broker": "^2.1.3", + "sc-simple-broker": "^3.0.0", "stream-demux": "^4.0.4", "uuid": "3.2.1", - "ws": "5.1.1" + "ws": "6.1.2" }, "devDependencies": { "localStorage": "^1.0.3", From c1f6c759acf28bc88a16163ddc0f2c9b4f77c7f0 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 27 Dec 2018 02:36:20 +0100 Subject: [PATCH 087/265] WIP --- README.md | 4 +- package-lock.json | 157 +++++++++++++++++++++++++------------------- package.json | 2 +- test/integration.js | 92 +++++++++++++------------- 4 files changed, 138 insertions(+), 117 deletions(-) diff --git a/README.md b/README.md index af87ff3..b9b94c8 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The repository for the full-featured framework is here: https://github.com/Socke ## Setting up -You will need to install ```socketcluster-server``` and ```socketcluster-client``` (https://github.com/SocketCluster/socketcluster-client) separately. +You will need to install ```socketcluster-server``` and ```asyngular-client``` (https://github.com/SocketCluster/asyngular-client) separately. To install this module: ```npm install socketcluster-server``` @@ -54,7 +54,7 @@ scServer.on('connection', function (socket) { httpServer.listen(8000); ``` -Note that the full SocketCluster framework (https://github.com/SocketCluster/socketcluster) uses this module behind the scenes so the API is exactly the same and it works with the socketcluster-client out of the box. +Note that the full SocketCluster framework (https://github.com/SocketCluster/socketcluster) uses this module behind the scenes so the API is exactly the same and it works with the asyngular-client out of the box. The main difference with using socketcluster-server is that you won't get features like: - Automatic scalability across multiple CPU cores. diff --git a/package-lock.json b/package-lock.json index 5add926..b1225cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,11 +5,48 @@ "requires": true, "dependencies": { "async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.3.0.tgz", - "integrity": "sha1-EBPRBRBH3TIP4k5JTVxm7K9hR9k=", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", "requires": { - "lodash": "4.17.5" + "lodash": "4.17.11" + } + }, + "async-iterable-stream": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/async-iterable-stream/-/async-iterable-stream-3.0.1.tgz", + "integrity": "sha512-Y4/wTlwUsp3+S/Aiw4KOCh3s6t/ES1kU5erhMuUuvSVvhOTey3vxohks0KoeCslT5TtRg7MvpK6NVcCbT7r3CA==" + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, + "async-stream-emitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/async-stream-emitter/-/async-stream-emitter-1.1.0.tgz", + "integrity": "sha512-oteYCw7qY0LJeSemRx/bqirqhEaZMInMN730rbZ3dhM3E0huT4QAm8CstlYUPD+nEKgkH6HplZbkC8Scv6MbcQ==", + "requires": { + "stream-demux": "4.0.4" + } + }, + "asyngular-client": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/asyngular-client/-/asyngular-client-1.0.1.tgz", + "integrity": "sha512-POsF6Z+8ZSJlrHwkwLMfFWonGZnact7ITlEl9PMSj0N1Vpebrf/2kpxoDBiwT4CNtHM7r4G06/Zirj4O8Kvv6Q==", + "dev": true, + "requires": { + "async-stream-emitter": "1.1.0", + "base-64": "0.1.0", + "clone": "2.1.1", + "linked-list": "0.1.0", + "querystring": "0.2.0", + "sc-channel": "2.0.0", + "sc-errors": "2.0.0", + "sc-formatter": "3.0.2", + "stream-demux": "4.0.4", + "uuid": "3.2.1", + "ws": "6.1.2" } }, "balanced-match": { @@ -58,15 +95,10 @@ }, "commander": { "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz", "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -165,9 +197,9 @@ "dev": true }, "jsonwebtoken": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz", - "integrity": "sha512-oge/hvlmeJCH+iIz1DwcO7vKPkNGJHhgkspk8OH3VKlw+mbi42WtD4ig1+VXRln765vxptAv+xT26Fd3cteqag==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.4.0.tgz", + "integrity": "sha512-coyXjRTCy0pw5WYBpMvWOMN+Kjaik2MwTUIq9cna/W7NpO9E+iYbumZONAz3hcr+tXFJECoQVrtmIoC3Oz0gvg==", "requires": { "jws": "3.1.5", "lodash.includes": "4.3.0", @@ -206,15 +238,15 @@ "dev": true }, "localStorage": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/localStorage/-/localStorage-1.0.3.tgz", - "integrity": "sha1-5riaV7t2ChVqOMyH4PJVD27UE9g=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/localStorage/-/localStorage-1.0.4.tgz", + "integrity": "sha512-r35zrihcDiX+dqWlJSeIwS9nrF95OQTgqMFm3FB2D/+XgdmZtcutZOb7t0xXkhOEM8a9kpuu7cc28g1g36I5DQ==", "dev": true }, "lodash": { - "version": "4.17.5", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, "lodash.clonedeep": { "version": "4.5.0", @@ -267,13 +299,13 @@ }, "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -315,7 +347,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, @@ -331,26 +363,26 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "sc-auth": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/sc-auth/-/sc-auth-5.0.2.tgz", - "integrity": "sha512-Le3YBsFjzv5g6wIH6Y+vD+KFkK0HDXiaWy1Gm4nXtYebMQUyNYSf1cS83MtHrYzVEMlhYElRva1b0bvZ0hBqQw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/sc-auth/-/sc-auth-6.0.0.tgz", + "integrity": "sha512-JSqG9CBrOIt3HRbpWlRfVOxwISqw9/CiqENfOsnm0Jf3WUZBmJarL/yP+NFks1D0hSuHa0OYW1qluEhYqrjaQw==", "requires": { - "jsonwebtoken": "8.3.0", - "sc-errors": "1.4.1" + "jsonwebtoken": "8.4.0", + "sc-errors": "2.0.0" } }, "sc-channel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", - "integrity": "sha512-M3gdq8PlKg0zWJSisWqAsMmTVxYRTpVRqw4CWAdKBgAfVKumFcTjoCV0hYu7lgUXccCtCD8Wk9VkkE+IXCxmZA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-2.0.0.tgz", + "integrity": "sha512-XFAic96C3rbG736D/nRiOvhkDXi+K8GGVrGtdo1shggRfQXKHj2K1ajXOIDA8HxT91G6NdnWsgsokfE3YHw9DA==", "requires": { - "component-emitter": "1.2.1" + "async-iterable-stream": "3.0.1" } }, "sc-errors": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.1.tgz", - "integrity": "sha512-dBn92iIonpChTxYLgKkIT/PCApvmYT6EPIbRvbQKTgY6tbEbIy8XVUv4pGyKwEK4nCmvX4TKXcN0iXC6tNW6rQ==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-2.0.0.tgz", + "integrity": "sha512-zLIg4GskHvkBM7gpKl7JrdU1FXVYsYCavsUeTILFIi/YsuOHLN9OTlFcMp6otb+ebpNEnpcDJI395YXZPif+fw==" }, "sc-formatter": { "version": "3.0.2", @@ -358,34 +390,22 @@ "integrity": "sha512-9PbqYBpCq+OoEeRQ3QfFIGE6qwjjBcd2j7UjgDlhnZbtSnuGgHdcRklPKYGuYFH82V/dwd+AIpu8XvA1zqTd+A==" }, "sc-simple-broker": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-2.1.3.tgz", - "integrity": "sha512-ldt0ybOS5fVZSMea5Z8qVu7lmDBTy0qO9BD6TseJjRuPx+g+stfSqmPAb0RsCsQUXRH8A1koCbwsuUnI9BOxvw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-3.0.0.tgz", + "integrity": "sha512-337+Te7nXrFK2mgLLYzmTK47WMhqSstfy+0bwTfU66opCg19ipmbeXrAJnGZXN6OukMV3zzLpQt60s6r1QA0Sg==", "requires": { - "sc-channel": "1.2.0" + "async-stream-emitter": "1.1.0", + "sc-channel": "2.0.0", + "stream-demux": "4.0.4" } }, - "sc-uws": { - "version": "10.148.2", - "resolved": "https://registry.npmjs.org/sc-uws/-/sc-uws-10.148.2.tgz", - "integrity": "sha512-wGXiwsORev5O3OOewsAYi1WVyMeNFMQ4bw/Qg/6g0C0J9vsEs8xnxf19hovAAQrOq6sMVrcxCNa2k1rBiDsDzw==" - }, - "socketcluster-client": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-14.2.0.tgz", - "integrity": "sha512-hibnSjupT+1JZlN608bxGwwaV7wqw36iBCSc0oXj7D+mv6DOR86+tSHWCPBaHWFkZK8ORW7v2BWSFuHSAzQzrw==", - "dev": true, + "stream-demux": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stream-demux/-/stream-demux-4.0.4.tgz", + "integrity": "sha512-aSB1jS+0tPCnijI9BqEXbV9xC0JqkXnO869JIU4bC2isYMZizUdWm9g7NV0U9D4yaZ+K+HYtgI9XxAebOpkZjw==", "requires": { - "base-64": "0.1.0", - "clone": "2.1.1", - "component-emitter": "1.2.1", - "linked-list": "0.1.0", - "querystring": "0.2.0", - "sc-channel": "1.2.0", - "sc-errors": "1.4.1", - "sc-formatter": "3.0.2", - "uuid": "3.2.1", - "ws": "5.1.1" + "async-iterable-stream": "3.0.1", + "writable-async-iterable-stream": "4.0.1" } }, "supports-color": { @@ -408,19 +428,20 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "writable-async-iterable-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/writable-async-iterable-stream/-/writable-async-iterable-stream-4.0.1.tgz", + "integrity": "sha512-zhEBUFIQRtVi2zNyVRWBmFuNBPTYHqtyv8cOPtqDxU+h3IGre6vPpyCW60Es+uJ51EZ1+s7IjpJdvJ0rHMjbVw==", + "requires": { + "async-iterable-stream": "3.0.1" + } + }, "ws": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz", - "integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.2.tgz", + "integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==", "requires": { "async-limiter": "1.0.0" - }, - "dependencies": { - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" - } } } } diff --git a/package.json b/package.json index c9a1426..da03d58 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "devDependencies": { "localStorage": "^1.0.3", "mocha": "5.2.0", - "socketcluster-client": "^14.2.0" + "asyngular-client": "^1.0.1" }, "scripts": { "test": "mocha --reporter spec --timeout 3000 --slow 3000" diff --git a/test/integration.js b/test/integration.js index 6fef418..a6051f2 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1,6 +1,6 @@ const assert = require('assert'); const socketClusterServer = require('../'); -const socketClusterClient = require('socketcluster-client'); +const asyngularClient = require('asyngular-client'); const localStorage = require('localStorage'); const SCSimpleBroker = require('sc-simple-broker').SCSimpleBroker; @@ -173,13 +173,13 @@ describe('Integration tests', function () { describe('Socket authentication', function () { it('Should not send back error if JWT is not provided in handshake', async function () { - client = socketClusterClient.create(clientOptions); + client = asyngularClient.create(clientOptions); let event = await client.listener('connect').once(); assert.equal(event.authError === undefined, true); }); it('Should be authenticated on connect if previous JWT token is present', async function () { - client = socketClusterClient.create(clientOptions); + client = asyngularClient.create(clientOptions); await client.listener('connect').once(); client.invoke('login', {username: 'bob'}); await client.listener('authenticate').once(); @@ -194,7 +194,7 @@ describe('Integration tests', function () { it('Should send back error if JWT is invalid during handshake', async function () { global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); - client = socketClusterClient.create(clientOptions); + client = asyngularClient.create(clientOptions); await client.listener('connect').once(); // Change the setAuthKey to invalidate the current token. @@ -242,7 +242,7 @@ describe('Integration tests', function () { })(); let clientSocketId; - client = socketClusterClient.create(clientOptions); + client = asyngularClient.create(clientOptions); await client.listener('connect').once(); clientSocketId = client.id; client.invoke('login', {username: 'alice'}); @@ -281,7 +281,7 @@ describe('Integration tests', function () { } })(); - client = socketClusterClient.create(clientOptions); + client = asyngularClient.create(clientOptions); (async () => { for await (let event of client.listener('connect')) { @@ -325,7 +325,7 @@ describe('Integration tests', function () { it('Should not authenticate the client if MIDDLEWARE_AUTHENTICATE blocks the authentication', async function () { global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenAlice); - client = socketClusterClient.create(clientOptions); + client = asyngularClient.create(clientOptions); // The previous test authenticated us as 'alice', so that token will be passed to the server as // part of the handshake. @@ -353,7 +353,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -384,7 +384,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -415,7 +415,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -452,7 +452,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -485,7 +485,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -518,7 +518,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -552,7 +552,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -640,7 +640,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -665,7 +665,7 @@ describe('Integration tests', function () { (async () => { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -716,7 +716,7 @@ describe('Integration tests', function () { (async () => { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -770,7 +770,7 @@ describe('Integration tests', function () { (async () => { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -810,7 +810,7 @@ describe('Integration tests', function () { })(); await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -853,7 +853,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -884,7 +884,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -972,7 +972,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -1049,7 +1049,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -1125,7 +1125,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -1186,7 +1186,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -1274,7 +1274,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -1339,7 +1339,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -1388,7 +1388,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -1489,7 +1489,7 @@ describe('Integration tests', function () { (async () => { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -1532,7 +1532,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -1581,7 +1581,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -1635,7 +1635,7 @@ describe('Integration tests', function () { (async () => { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -1695,7 +1695,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -1742,7 +1742,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -1807,7 +1807,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -1857,7 +1857,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -1890,7 +1890,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -1921,7 +1921,7 @@ describe('Integration tests', function () { })(); await server.listener('ready').once(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -1953,7 +1953,7 @@ describe('Integration tests', function () { }); it('Should disconnect socket if server does not receive a pong from client before timeout', async function () { - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -2019,7 +2019,7 @@ describe('Integration tests', function () { }); it('Should not disconnect socket if server does not receive a pong from client before timeout', async function () { - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber, pingTimeoutDisabled: true @@ -2088,7 +2088,7 @@ describe('Integration tests', function () { }; server.addMiddleware(server.MIDDLEWARE_AUTHENTICATE, middlewareFunction); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -2105,7 +2105,7 @@ describe('Integration tests', function () { }; server.addMiddleware(server.MIDDLEWARE_AUTHENTICATE, middlewareFunction); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -2143,7 +2143,7 @@ describe('Integration tests', function () { } })(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -2184,7 +2184,7 @@ describe('Integration tests', function () { }; server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_SC, middlewareFunction); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -2219,7 +2219,7 @@ describe('Integration tests', function () { }; server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_SC, middlewareFunction); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); @@ -2248,7 +2248,7 @@ describe('Integration tests', function () { server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_SC, middlewareFunction); createConnectionTime = Date.now(); - client = socketClusterClient.create({ + client = asyngularClient.create({ hostname: clientOptions.hostname, port: portNumber }); From 2179b8581db8106117832c4aed2cca0e12f3d2b2 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 27 Dec 2018 03:02:45 +0100 Subject: [PATCH 088/265] Rename --- README.md | 67 ++++---------------- scserver.js => agserver.js | 72 ++++++++++----------- scserversocket.js => agserversocket.js | 86 +++++++++++++------------- index.js | 22 +++---- package-lock.json | 8 +-- package.json | 13 ++-- test/integration.js | 72 ++++++++++----------- 7 files changed, 149 insertions(+), 191 deletions(-) rename scserver.js => agserver.js (94%) rename scserversocket.js => agserversocket.js (84%) diff --git a/README.md b/README.md index b9b94c8..f4561cd 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,28 @@ -# socketcluster-server -Minimal server module for SocketCluster +# asyngular-server +Minimal server module for Asyngular -This is a stand-alone server module for SocketCluster. This module offers the most flexibility when creating a SocketCluster service but requires the most work to setup. -The repository for the full-featured framework is here: https://github.com/SocketCluster/socketcluster +This is a stand-alone server module for Asyngular. This module offers the most flexibility when creating a Asyngular service but requires the most work to setup. +The repository for the full-featured framework is here: https://github.com/SocketCluster/asyngular ## Setting up -You will need to install ```socketcluster-server``` and ```asyngular-client``` (https://github.com/SocketCluster/asyngular-client) separately. +You will need to install ```asyngular-server``` and ```asyngular-client``` (https://github.com/SocketCluster/asyngular-client) separately. To install this module: -```npm install socketcluster-server``` +```npm install asyngular-server``` -## Using with basic http(s) module (example) - -You need to attach it to an existing Node.js http or https server (example): -```js -var http = require('http'); -var socketClusterServer = require('socketcluster-server'); - -var httpServer = http.createServer(); -var scServer = socketClusterServer.attach(httpServer); - -scServer.on('connection', function (socket) { - // ... Handle new socket connections here -}); - -httpServer.listen(8000); -``` - -## Using with Express (example) - -```js -var http = require('http'); -var socketClusterServer = require('socketcluster-server'); -var serveStatic = require('serve-static'); -var path = require('path'); -var app = require('express')(); - -app.use(serveStatic(path.resolve(__dirname, 'public'))); - -var httpServer = http.createServer(); - -// Attach express to our httpServer -httpServer.on('request', app); - -// Attach socketcluster-server to our httpServer -var scServer = socketClusterServer.attach(httpServer); - -scServer.on('connection', function (socket) { - // ... Handle new socket connections here -}); - -httpServer.listen(8000); -``` - -Note that the full SocketCluster framework (https://github.com/SocketCluster/socketcluster) uses this module behind the scenes so the API is exactly the same and it works with the asyngular-client out of the box. -The main difference with using socketcluster-server is that you won't get features like: +Note that the full Asyngular framework (https://github.com/SocketCluster/asyngular) uses this module behind the scenes so the API is exactly the same and it works with the asyngular-client out of the box. +The main difference with using asyngular-server is that you won't get features like: - Automatic scalability across multiple CPU cores. - Resilience; you are responsible for respawning the process if it crashes. - Convenience; It requires more work up front to get working (not good for beginners). -- Pub/sub channels won't scale across multiple socketcluster-server processes/hosts by default.\* +- Pub/sub channels won't scale across multiple asyngular-server processes/hosts by default.\* -\* Note that the ```socketClusterServer.attach(httpServer, options);``` takes an optional options argument which can have a ```brokerEngine``` property - By default, socketcluster-server -uses ```sc-simple-broker``` which is a basic single-process in-memory broker. If you want to add your own brokerEngine (for example to scale your socketcluster-servers across multiple cores/hosts), then you might want to look at how sc-simple-broker was implemented. +\* Note that the ```asyngularServer.attach(httpServer, options);``` takes an optional options argument which can have a ```brokerEngine``` property - By default, asyngular-server +uses ```sc-simple-broker``` which is a basic single-process in-memory broker. If you want to add your own brokerEngine (for example to scale your asyngular-servers across multiple cores/hosts), then you might want to look at how sc-simple-broker was implemented. -The full SocketCluster framework uses a different broker engine: ```sc-broker-cluster```(https://github.com/SocketCluster/sc-broker-cluster) - This is a more complex brokerEngine - It allows messages to be brokered between +The full Asyngular framework uses a different broker engine: ```sc-broker-cluster```(https://github.com/SocketCluster/sc-broker-cluster) - This is a more complex brokerEngine - It allows messages to be brokered between multiple processes and can be synchronized with remote hosts too so you can get both horizontal and vertical scalability. The main benefit of this module is that it gives you maximum flexibility. You just need to attach it to a Node.js http server so you can use it alongside pretty much any framework. diff --git a/scserver.js b/agserver.js similarity index 94% rename from scserver.js rename to agserver.js index b63d801..b29b775 100644 --- a/scserver.js +++ b/agserver.js @@ -1,4 +1,4 @@ -const SCServerSocket = require('./scserversocket'); +const AGServerSocket = require('./agserversocket'); const AuthEngine = require('sc-auth').AuthEngine; const formatter = require('sc-formatter'); const base64id = require('base64id'); @@ -22,7 +22,7 @@ const BrokerError = scErrors.BrokerError; const ServerProtocolError = scErrors.ServerProtocolError; -function SCServer(options) { +function AGServer(options) { AsyncStreamEmitter.call(this); let opts = { @@ -199,43 +199,43 @@ function SCServer(options) { this.wsServer.on('connection', this._handleSocketConnection.bind(this)); } -SCServer.prototype = Object.create(AsyncStreamEmitter.prototype); +AGServer.prototype = Object.create(AsyncStreamEmitter.prototype); -SCServer.prototype.setAuthEngine = function (authEngine) { +AGServer.prototype.setAuthEngine = function (authEngine) { this.auth = authEngine; }; -SCServer.prototype.setCodecEngine = function (codecEngine) { +AGServer.prototype.setCodecEngine = function (codecEngine) { this.codec = codecEngine; }; -SCServer.prototype.emitError = function (error) { +AGServer.prototype.emitError = function (error) { this.emit('error', {error}); }; -SCServer.prototype.emitWarning = function (warning) { +AGServer.prototype.emitWarning = function (warning) { this.emit('warning', {warning}); }; -SCServer.prototype._handleServerError = function (error) { +AGServer.prototype._handleServerError = function (error) { if (typeof error === 'string') { error = new ServerProtocolError(error); } this.emitError(error); }; -SCServer.prototype._handleSocketErrors = async function (socket) { +AGServer.prototype._handleSocketErrors = async function (socket) { // A socket error will show up as a warning on the server. for await (let event of socket.listener('error')) { this.emitWarning(event.error); } }; -SCServer.prototype._handleHandshakeTimeout = function (scSocket) { +AGServer.prototype._handleHandshakeTimeout = function (scSocket) { scSocket.disconnect(4005); }; -SCServer.prototype._subscribeSocket = async function (socket, channelOptions) { +AGServer.prototype._subscribeSocket = async function (socket, channelOptions) { if (!channelOptions) { throw new InvalidActionError(`Socket ${socket.id} provided a malformated channel payload`); } @@ -278,13 +278,13 @@ SCServer.prototype._subscribeSocket = async function (socket, channelOptions) { }); }; -SCServer.prototype._unsubscribeSocketFromAllChannels = function (socket) { +AGServer.prototype._unsubscribeSocketFromAllChannels = function (socket) { Object.keys(socket.channelSubscriptions).forEach((channelName) => { this._unsubscribeSocket(socket, channelName); }); }; -SCServer.prototype._unsubscribeSocket = function (socket, channel) { +AGServer.prototype._unsubscribeSocket = function (socket, channel) { if (typeof channel !== 'string') { throw new InvalidActionError( `Socket ${socket.id} tried to unsubscribe from an invalid channel name` @@ -307,7 +307,7 @@ SCServer.prototype._unsubscribeSocket = function (socket, channel) { this.emit('unsubscription', {socket, channel}); }; -SCServer.prototype._processTokenError = function (err) { +AGServer.prototype._processTokenError = function (err) { let authError = null; let isBadToken = true; @@ -331,7 +331,7 @@ SCServer.prototype._processTokenError = function (err) { }; }; -SCServer.prototype._emitBadAuthTokenError = function (scSocket, error, signedAuthToken) { +AGServer.prototype._emitBadAuthTokenError = function (scSocket, error, signedAuthToken) { let badAuthStatus = { authError: error, signedAuthToken: signedAuthToken @@ -347,7 +347,7 @@ SCServer.prototype._emitBadAuthTokenError = function (scSocket, error, signedAut }); }; -SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, callback) { +AGServer.prototype._processAuthToken = function (scSocket, signedAuthToken, callback) { let verificationOptions = Object.assign({socket: scSocket}, this.defaultVerificationOptions); let handleVerifyTokenResult = (result) => { @@ -429,7 +429,7 @@ SCServer.prototype._processAuthToken = function (scSocket, signedAuthToken, call } }; -SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { +AGServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { if (!wsSocket.upgradeReq) { // Normalize ws modules to match. wsSocket.upgradeReq = upgradeReq; @@ -437,7 +437,7 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { let id = this.generateId(); - let scSocket = new SCServerSocket(id, this, wsSocket); + let scSocket = new AGServerSocket(id, this, wsSocket); scSocket.exchange = this.exchange; this._handleSocketErrors(scSocket); @@ -678,7 +678,7 @@ SCServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { this.emit('handshake', {socket: scSocket}); }; -SCServer.prototype.close = function () { +AGServer.prototype.close = function () { this.isReady = false; return new Promise((resolve, reject) => { this.wsServer.close((err) => { @@ -691,15 +691,15 @@ SCServer.prototype.close = function () { }); }; -SCServer.prototype.getPath = function () { +AGServer.prototype.getPath = function () { return this._path; }; -SCServer.prototype.generateId = function () { +AGServer.prototype.generateId = function () { return base64id.generateId(); }; -SCServer.prototype.addMiddleware = function (type, middleware) { +AGServer.prototype.addMiddleware = function (type, middleware) { if (!this._middleware[type]) { throw new InvalidArgumentsError(`Middleware type "${type}" is not supported`); // Read more: https://socketcluster.io/#!/docs/middleware-and-authorization @@ -707,7 +707,7 @@ SCServer.prototype.addMiddleware = function (type, middleware) { this._middleware[type].push(middleware); }; -SCServer.prototype.removeMiddleware = function (type, middleware) { +AGServer.prototype.removeMiddleware = function (type, middleware) { let middlewareFunctions = this._middleware[type]; this._middleware[type] = middlewareFunctions.filter((fn) => { @@ -715,7 +715,7 @@ SCServer.prototype.removeMiddleware = function (type, middleware) { }); }; -SCServer.prototype.verifyHandshake = function (info, callback) { +AGServer.prototype.verifyHandshake = function (info, callback) { let req = info.req; let origin = info.origin; if (origin === 'null' || origin == null) { @@ -775,11 +775,11 @@ SCServer.prototype.verifyHandshake = function (info, callback) { } }; -SCServer.prototype._isReservedRemoteEvent = function (event) { +AGServer.prototype._isReservedRemoteEvent = function (event) { return typeof event === 'string' && event.indexOf('#') === 0; }; -SCServer.prototype.verifyInboundRemoteEvent = function (requestOptions, callback) { +AGServer.prototype.verifyInboundRemoteEvent = function (requestOptions, callback) { let socket = requestOptions.socket; let token = socket.getAuthToken(); if (this.isAuthTokenExpired(token)) { @@ -794,7 +794,7 @@ SCServer.prototype.verifyInboundRemoteEvent = function (requestOptions, callback this._passThroughMiddleware(requestOptions, callback); }; -SCServer.prototype.isAuthTokenExpired = function (token) { +AGServer.prototype.isAuthTokenExpired = function (token) { if (token && token.exp != null) { let currentTime = Date.now(); let expiryMilliseconds = token.exp * 1000; @@ -803,7 +803,7 @@ SCServer.prototype.isAuthTokenExpired = function (token) { return false; }; -SCServer.prototype._processPublishAction = function (options, request, callback) { +AGServer.prototype._processPublishAction = function (options, request, callback) { let callbackInvoked = false; if (this.allowClientPublish) { @@ -864,7 +864,7 @@ SCServer.prototype._processPublishAction = function (options, request, callback) } }; -SCServer.prototype._processSubscribeAction = function (options, request, callback) { +AGServer.prototype._processSubscribeAction = function (options, request, callback) { let callbackInvoked = false; let eventData = options.data || {}; @@ -907,7 +907,7 @@ SCServer.prototype._processSubscribeAction = function (options, request, callbac } }; -SCServer.prototype._processTransmitAction = function (options, request, callback) { +AGServer.prototype._processTransmitAction = function (options, request, callback) { let callbackInvoked = false; request.event = options.event; @@ -939,7 +939,7 @@ SCServer.prototype._processTransmitAction = function (options, request, callback ); }; -SCServer.prototype._processInvokeAction = function (options, request, callback) { +AGServer.prototype._processInvokeAction = function (options, request, callback) { let callbackInvoked = false; request.event = options.event; @@ -971,7 +971,7 @@ SCServer.prototype._processInvokeAction = function (options, request, callback) ); }; -SCServer.prototype._passThroughMiddleware = function (options, callback) { +AGServer.prototype._passThroughMiddleware = function (options, callback) { let request = { socket: options.socket }; @@ -1019,7 +1019,7 @@ SCServer.prototype._passThroughMiddleware = function (options, callback) { } }; -SCServer.prototype._passThroughAuthenticateMiddleware = function (options, callback) { +AGServer.prototype._passThroughAuthenticateMiddleware = function (options, callback) { let callbackInvoked = false; let request = { @@ -1057,7 +1057,7 @@ SCServer.prototype._passThroughAuthenticateMiddleware = function (options, callb ); }; -SCServer.prototype._passThroughHandshakeSCMiddleware = function (options, callback) { +AGServer.prototype._passThroughHandshakeSCMiddleware = function (options, callback) { let callbackInvoked = false; let request = { @@ -1099,7 +1099,7 @@ SCServer.prototype._passThroughHandshakeSCMiddleware = function (options, callba ); }; -SCServer.prototype.verifyOutboundEvent = function (socket, eventName, eventData, options, callback) { +AGServer.prototype.verifyOutboundEvent = function (socket, eventName, eventData, options, callback) { let callbackInvoked = false; if (eventName === '#publish') { @@ -1145,4 +1145,4 @@ SCServer.prototype.verifyOutboundEvent = function (socket, eventName, eventData, } }; -module.exports = SCServer; +module.exports = AGServer; diff --git a/scserversocket.js b/agserversocket.js similarity index 84% rename from scserversocket.js rename to agserversocket.js index ba0841c..1b298a4 100644 --- a/scserversocket.js +++ b/agserversocket.js @@ -11,7 +11,7 @@ const InvalidActionError = scErrors.InvalidActionError; const AuthError = scErrors.AuthError; -function SCServerSocket(id, server, socket) { +function AGServerSocket(id, server, socket) { AsyncStreamEmitter.call(this); this._autoAckRPCs = { @@ -99,41 +99,41 @@ function SCServerSocket(id, server, socket) { }); } -SCServerSocket.prototype = Object.create(AsyncStreamEmitter.prototype); +AGServerSocket.prototype = Object.create(AsyncStreamEmitter.prototype); -SCServerSocket.CONNECTING = SCServerSocket.prototype.CONNECTING = 'connecting'; -SCServerSocket.OPEN = SCServerSocket.prototype.OPEN = 'open'; -SCServerSocket.CLOSED = SCServerSocket.prototype.CLOSED = 'closed'; +AGServerSocket.CONNECTING = AGServerSocket.prototype.CONNECTING = 'connecting'; +AGServerSocket.OPEN = AGServerSocket.prototype.OPEN = 'open'; +AGServerSocket.CLOSED = AGServerSocket.prototype.CLOSED = 'closed'; -SCServerSocket.AUTHENTICATED = SCServerSocket.prototype.AUTHENTICATED = 'authenticated'; -SCServerSocket.UNAUTHENTICATED = SCServerSocket.prototype.UNAUTHENTICATED = 'unauthenticated'; +AGServerSocket.AUTHENTICATED = AGServerSocket.prototype.AUTHENTICATED = 'authenticated'; +AGServerSocket.UNAUTHENTICATED = AGServerSocket.prototype.UNAUTHENTICATED = 'unauthenticated'; -SCServerSocket.ignoreStatuses = scErrors.socketProtocolIgnoreStatuses; -SCServerSocket.errorStatuses = scErrors.socketProtocolErrorStatuses; +AGServerSocket.ignoreStatuses = scErrors.socketProtocolIgnoreStatuses; +AGServerSocket.errorStatuses = scErrors.socketProtocolErrorStatuses; -SCServerSocket.prototype.receiver = function (receiverName) { +AGServerSocket.prototype.receiver = function (receiverName) { return this._receiverDemux.stream(receiverName); }; -SCServerSocket.prototype.closeReceiver = function (receiverName) { +AGServerSocket.prototype.closeReceiver = function (receiverName) { this._receiverDemux.close(receiverName); }; -SCServerSocket.prototype.procedure = function (procedureName) { +AGServerSocket.prototype.procedure = function (procedureName) { return this._procedureDemux.stream(procedureName); }; -SCServerSocket.prototype.closeProcedure = function (procedureName) { +AGServerSocket.prototype.closeProcedure = function (procedureName) { this._procedureDemux.close(procedureName); }; -SCServerSocket.prototype._sendPing = function () { +AGServerSocket.prototype._sendPing = function () { if (this.state !== this.CLOSED) { this.sendObject('#1'); } }; -SCServerSocket.prototype._handleRemoteEventObject = function (obj, message) { +AGServerSocket.prototype._handleRemoteEventObject = function (obj, message) { if (obj && obj.event != null) { let eventName = obj.event; @@ -191,7 +191,7 @@ SCServerSocket.prototype._handleRemoteEventObject = function (obj, message) { } }; -SCServerSocket.prototype._resetPongTimeout = function () { +AGServerSocket.prototype._resetPongTimeout = function () { if (this.server.pingTimeoutDisabled) { return; } @@ -202,25 +202,25 @@ SCServerSocket.prototype._resetPongTimeout = function () { }, this.server.pingTimeout); }; -SCServerSocket.prototype._nextCallId = function () { +AGServerSocket.prototype._nextCallId = function () { return this._cid++; }; -SCServerSocket.prototype.getState = function () { +AGServerSocket.prototype.getState = function () { return this.state; }; -SCServerSocket.prototype.getBytesReceived = function () { +AGServerSocket.prototype.getBytesReceived = function () { return this.socket.bytesReceived; }; -SCServerSocket.prototype.emitError = function (error) { +AGServerSocket.prototype.emitError = function (error) { this.emit('error', { error }); }; -SCServerSocket.prototype._onSCClose = function (code, reason) { +AGServerSocket.prototype._onSCClose = function (code, reason) { clearInterval(this._pingIntervalTicker); clearTimeout(this._pingTimeoutTicker); @@ -235,7 +235,7 @@ SCServerSocket.prototype._onSCClose = function (code, reason) { } this.emit('close', {code, reason}); - if (!SCServerSocket.ignoreStatuses[code]) { + if (!AGServerSocket.ignoreStatuses[code]) { let closeMessage; if (reason) { let reasonString; @@ -252,13 +252,13 @@ SCServerSocket.prototype._onSCClose = function (code, reason) { } else { closeMessage = `Socket connection closed with status code ${code}`; } - let err = new SocketProtocolError(SCServerSocket.errorStatuses[code] || closeMessage, code); + let err = new SocketProtocolError(AGServerSocket.errorStatuses[code] || closeMessage, code); this.emitError(err); } } }; -SCServerSocket.prototype.disconnect = function (code, data) { +AGServerSocket.prototype.disconnect = function (code, data) { code = code || 1000; if (typeof code !== 'number') { @@ -272,16 +272,16 @@ SCServerSocket.prototype.disconnect = function (code, data) { } }; -SCServerSocket.prototype.destroy = function (code, data) { +AGServerSocket.prototype.destroy = function (code, data) { this.active = false; this.disconnect(code, data); }; -SCServerSocket.prototype.terminate = function () { +AGServerSocket.prototype.terminate = function () { this.socket.terminate(); }; -SCServerSocket.prototype.send = function (data, options) { +AGServerSocket.prototype.send = function (data, options) { this.socket.send(data, options, (err) => { if (err) { this._onSCClose(1006, err.toString()); @@ -289,15 +289,15 @@ SCServerSocket.prototype.send = function (data, options) { }); }; -SCServerSocket.prototype.decode = function (message) { +AGServerSocket.prototype.decode = function (message) { return this.server.codec.decode(message); }; -SCServerSocket.prototype.encode = function (object) { +AGServerSocket.prototype.encode = function (object) { return this.server.codec.encode(object); }; -SCServerSocket.prototype.sendObjectBatch = function (object) { +AGServerSocket.prototype.sendObjectBatch = function (object) { this._batchSendList.push(object); if (this._batchTimeout) { return; @@ -320,7 +320,7 @@ SCServerSocket.prototype.sendObjectBatch = function (object) { }, this.server.options.pubSubBatchDuration || 0); }; -SCServerSocket.prototype.sendObjectSingle = function (object) { +AGServerSocket.prototype.sendObjectSingle = function (object) { let str; try { str = this.encode(object); @@ -332,7 +332,7 @@ SCServerSocket.prototype.sendObjectSingle = function (object) { } }; -SCServerSocket.prototype.sendObject = function (object, options) { +AGServerSocket.prototype.sendObject = function (object, options) { if (options && options.batch) { this.sendObjectBatch(object); } else { @@ -340,7 +340,7 @@ SCServerSocket.prototype.sendObject = function (object, options) { } }; -SCServerSocket.prototype.transmit = function (event, data, options) { +AGServerSocket.prototype.transmit = function (event, data, options) { this.server.verifyOutboundEvent(this, event, data, options, (err, newData) => { let eventObject = { event: event @@ -361,7 +361,7 @@ SCServerSocket.prototype.transmit = function (event, data, options) { return Promise.resolve(); }; -SCServerSocket.prototype.invoke = function (event, data, options) { +AGServerSocket.prototype.invoke = function (event, data, options) { return new Promise((resolve, reject) => { this.server.verifyOutboundEvent(this, event, data, options, (err, newData) => { if (err) { @@ -403,7 +403,7 @@ SCServerSocket.prototype.invoke = function (event, data, options) { }); }; -SCServerSocket.prototype.triggerAuthenticationEvents = function (oldAuthState) { +AGServerSocket.prototype.triggerAuthenticationEvents = function (oldAuthState) { if (oldAuthState !== this.AUTHENTICATED) { let stateChangeData = { oldAuthState, @@ -423,7 +423,7 @@ SCServerSocket.prototype.triggerAuthenticationEvents = function (oldAuthState) { }); }; -SCServerSocket.prototype.setAuthToken = async function (data, options) { +AGServerSocket.prototype.setAuthToken = async function (data, options) { let authToken = cloneDeep(data); let oldAuthState = this.authState; this.authState = this.AUTHENTICATED; @@ -527,11 +527,11 @@ SCServerSocket.prototype.setAuthToken = async function (data, options) { } }; -SCServerSocket.prototype.getAuthToken = function () { +AGServerSocket.prototype.getAuthToken = function () { return this.authToken; }; -SCServerSocket.prototype.deauthenticateSelf = function () { +AGServerSocket.prototype.deauthenticateSelf = function () { let oldAuthState = this.authState; let oldAuthToken = this.authToken; this.signedAuthToken = null; @@ -555,12 +555,12 @@ SCServerSocket.prototype.deauthenticateSelf = function () { }); }; -SCServerSocket.prototype.deauthenticate = function () { +AGServerSocket.prototype.deauthenticate = function () { this.deauthenticateSelf(); return this.invoke('#removeAuthToken'); }; -SCServerSocket.prototype.kickOut = function (channel, message) { +AGServerSocket.prototype.kickOut = function (channel, message) { if (channel == null) { Object.keys(this.channelSubscriptions).forEach((channelName) => { delete this.channelSubscriptions[channelName]; @@ -575,12 +575,12 @@ SCServerSocket.prototype.kickOut = function (channel, message) { return this.server.brokerEngine.unsubscribeSocket(this, channel); }; -SCServerSocket.prototype.subscriptions = function () { +AGServerSocket.prototype.subscriptions = function () { return Object.keys(this.channelSubscriptions); }; -SCServerSocket.prototype.isSubscribed = function (channel) { +AGServerSocket.prototype.isSubscribed = function (channel) { return !!this.channelSubscriptions[channel]; }; -module.exports = SCServerSocket; +module.exports = AGServerSocket; diff --git a/index.js b/index.js index 13188db..b988502 100644 --- a/index.js +++ b/index.js @@ -5,20 +5,20 @@ const http = require('http'); /** - * Expose SCServer constructor. + * Expose AGServer constructor. * * @api public */ -module.exports.SCServer = require('./scserver'); +module.exports.AGServer = require('./agserver'); /** - * Expose SCServerSocket constructor. + * Expose AGServerSocket constructor. * * @api public */ -module.exports.SCServerSocket = require('./scserversocket'); +module.exports.AGServerSocket = require('./agserversocket'); /** * Creates an http.Server exclusively used for WS upgrades. @@ -26,7 +26,7 @@ module.exports.SCServerSocket = require('./scserversocket'); * @param {Number} port * @param {Function} callback * @param {Object} options - * @return {SCServer} websocket cluster server + * @return {AGServer} websocket cluster server * @api public */ @@ -41,11 +41,11 @@ module.exports.listen = function (port, options, fn) { res.end('Not Implemented'); }); - let socketClusterServer = module.exports.attach(server, options); - socketClusterServer.httpServer = server; + let asyngularServer = module.exports.attach(server, options); + asyngularServer.httpServer = server; server.listen(port, fn); - return socketClusterServer; + return asyngularServer; }; /** @@ -53,7 +53,7 @@ module.exports.listen = function (port, options, fn) { * * @param {http.Server} server * @param {Object} options - * @return {SCServer} websocket cluster server + * @return {AGServer} websocket cluster server * @api public */ @@ -62,6 +62,6 @@ module.exports.attach = function (server, options) { options = {}; } options.httpServer = server; - let socketClusterServer = new module.exports.SCServer(options); - return socketClusterServer; + let asyngularServer = new module.exports.AGServer(options); + return asyngularServer; }; diff --git a/package-lock.json b/package-lock.json index b1225cd..b180f94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "socketcluster-server", + "name": "asyngular-server", "version": "14.3.1", "lockfileVersion": 1, "requires": true, @@ -31,9 +31,9 @@ } }, "asyngular-client": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/asyngular-client/-/asyngular-client-1.0.1.tgz", - "integrity": "sha512-POsF6Z+8ZSJlrHwkwLMfFWonGZnact7ITlEl9PMSj0N1Vpebrf/2kpxoDBiwT4CNtHM7r4G06/Zirj4O8Kvv6Q==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/asyngular-client/-/asyngular-client-1.0.2.tgz", + "integrity": "sha512-wpYIUnzlSdnf5OBthy2fSn1jIfjs/mqeMG6C8glSMdpFi+92hKlaXKT6MgwGhTtItMe5SbN2ctXEnHKnzjXpPw==", "dev": true, "requires": { "async-stream-emitter": "1.1.0", diff --git a/package.json b/package.json index da03d58..ae2feef 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { - "name": "socketcluster-server", + "name": "asyngular-server", "version": "14.3.1", - "description": "Server module for SocketCluster", + "description": "Server module for Asyngular", "main": "index.js", "repository": { "type": "git", - "url": "git://github.com/SocketCluster/socketcluster-server.git" + "url": "git://github.com/SocketCluster/asyngular-server.git" }, "dependencies": { "async": "2.6.1", @@ -21,9 +21,9 @@ "ws": "6.1.2" }, "devDependencies": { + "asyngular-client": "^1.0.2", "localStorage": "^1.0.3", - "mocha": "5.2.0", - "asyngular-client": "^1.0.1" + "mocha": "5.2.0" }, "scripts": { "test": "mocha --reporter spec --timeout 3000 --slow 3000" @@ -31,7 +31,8 @@ "keywords": [ "websocket", "realtime", - "socketcluster" + "socketcluster", + "asyngular" ], "author": "Jonathan Gros-Dubois ", "license": "MIT" diff --git a/test/integration.js b/test/integration.js index a6051f2..d2da5f5 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1,5 +1,5 @@ const assert = require('assert'); -const socketClusterServer = require('../'); +const asyngularServer = require('../'); const asyngularClient = require('asyngular-client'); const localStorage = require('localStorage'); const SCSimpleBroker = require('sc-simple-broker').SCSimpleBroker; @@ -145,7 +145,7 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE }; - server = socketClusterServer.listen(portNumber, serverOptions); + server = asyngularServer.listen(portNumber, serverOptions); (async () => { for await (let {socket} of server.listener('connection')) { @@ -339,7 +339,7 @@ describe('Integration tests', function () { it('Token should be available after Promise resolves if token engine signing is synchronous', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authSignAsync: false @@ -370,7 +370,7 @@ describe('Integration tests', function () { it('If token engine signing is asynchronous, authentication can be captured using the authenticate event', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authSignAsync: true @@ -401,7 +401,7 @@ describe('Integration tests', function () { it('Should still work if token verification is asynchronous', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authVerifyAsync: false @@ -438,7 +438,7 @@ describe('Integration tests', function () { it('Should set the correct expiry when using expiresIn option when creating a JWT with socket.setAuthToken', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authVerifyAsync: false @@ -471,7 +471,7 @@ describe('Integration tests', function () { it('Should set the correct expiry when adding exp claim when creating a JWT with socket.setAuthToken', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authVerifyAsync: false @@ -504,7 +504,7 @@ describe('Integration tests', function () { it('The exp claim should have priority over expiresIn option when using socket.setAuthToken', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authVerifyAsync: false @@ -537,7 +537,7 @@ describe('Integration tests', function () { it('Should send back error if socket.setAuthToken tries to set both iss claim and issuer option', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authVerifyAsync: false @@ -605,7 +605,7 @@ describe('Integration tests', function () { it('Should trigger an authTokenSigned event and socket.signedAuthToken should be set after calling the socket.setAuthToken method', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authSignAsync: true @@ -654,7 +654,7 @@ describe('Integration tests', function () { it('Should reject Promise returned by socket.setAuthToken if token delivery fails and rejectOnFailedDelivery option is true', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authSignAsync: true, @@ -705,7 +705,7 @@ describe('Integration tests', function () { it('Should not reject Promise returned by socket.setAuthToken if token delivery fails and rejectOnFailedDelivery option is not true', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authSignAsync: true, @@ -757,7 +757,7 @@ describe('Integration tests', function () { global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -793,7 +793,7 @@ describe('Integration tests', function () { it('Should remove client data from the server when client disconnects before authentication process finished', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -840,7 +840,7 @@ describe('Integration tests', function () { describe('Socket handshake', function () { it('Exchange is attached to socket before the handshake event is triggered', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -866,7 +866,7 @@ describe('Integration tests', function () { describe('Socket connection', function () { it('Server-side socket connect event and server connection event should trigger', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -951,7 +951,7 @@ describe('Integration tests', function () { describe('Socket disconnection', function () { it('Server-side socket disconnect event should not trigger if the socket did not complete the handshake; instead, it should trigger connectAbort', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1028,7 +1028,7 @@ describe('Integration tests', function () { it('Server-side socket disconnect event should trigger if the socket completed the handshake (not connectAbort)', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1106,7 +1106,7 @@ describe('Integration tests', function () { it('The close event should trigger when the socket loses the connection before the handshake', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1167,7 +1167,7 @@ describe('Integration tests', function () { it('The close event should trigger when the socket loses the connection after the handshake', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1230,7 +1230,7 @@ describe('Integration tests', function () { describe('Socket pub/sub', function () { it('Should support subscription batching', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1320,7 +1320,7 @@ describe('Integration tests', function () { it('Client should not be able to subscribe to a channel before the handshake has completed', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1375,7 +1375,7 @@ describe('Integration tests', function () { it('Server should be able to handle invalid #subscribe and #unsubscribe and #publish events without crashing', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1479,7 +1479,7 @@ describe('Integration tests', function () { it('When default SCSimpleBroker broker engine is used, disconnect event should trigger before unsubscribe event', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1525,7 +1525,7 @@ describe('Integration tests', function () { it('When default SCSimpleBroker broker engine is used, scServer.exchange should support consuming data from a channel', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1574,7 +1574,7 @@ describe('Integration tests', function () { it('When default SCSimpleBroker broker engine is used, scServer.exchange should support publishing data to a channel', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1625,7 +1625,7 @@ describe('Integration tests', function () { return resolveAfterTimeout(100, defaultUnsubscribeSocket.call(this, socket, channel)); }; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, brokerEngine: customBrokerEngine @@ -1676,7 +1676,7 @@ describe('Integration tests', function () { it('Socket should emit an error when trying to unsubscribe to a channel which it is not subscribed to', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1722,7 +1722,7 @@ describe('Integration tests', function () { return resolveAfterTimeout(300, defaultUnsubscribeSocket.call(this, socket, channel)); }; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, brokerEngine: customBrokerEngine @@ -1774,7 +1774,7 @@ describe('Integration tests', function () { it('Socket channelSubscriptions and channelSubscriptionsCount should update when socket.kickOut(channel) is called', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1824,7 +1824,7 @@ describe('Integration tests', function () { it('Socket channelSubscriptions and channelSubscriptionsCount should update when socket.kickOut() is called without arguments', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1876,7 +1876,7 @@ describe('Integration tests', function () { describe('Socket destruction', function () { it('Server socket destroy should disconnect the socket', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1904,7 +1904,7 @@ describe('Integration tests', function () { it('Server socket destroy should set the active property on the socket to false', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1937,7 +1937,7 @@ describe('Integration tests', function () { portNumber++; // Intentionally make pingInterval higher than pingTimeout, that // way the client will never receive a ping or send back a pong. - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, pingInterval: 2000, @@ -2002,7 +2002,7 @@ describe('Integration tests', function () { portNumber++; // Intentionally make pingInterval higher than pingTimeout, that // way the client will never receive a ping or send back a pong. - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, pingInterval: 2000, @@ -2069,7 +2069,7 @@ describe('Integration tests', function () { beforeEach('Launch server without middleware before start', async function () { portNumber++; - server = socketClusterServer.listen(portNumber, { + server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); From 3a32c1dd7c1b79947782365e0b2bf46e6e715e4a Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 27 Dec 2018 03:03:55 +0100 Subject: [PATCH 089/265] v1.0.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index b180f94..a4f1b11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "14.3.1", + "version": "1.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ae2feef..62f8c58 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "14.3.1", + "version": "1.0.1", "description": "Server module for Asyngular", "main": "index.js", "repository": { From 61927a6ad1d18a0b7d4663c21e6446559b93a116 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 27 Dec 2018 03:13:15 +0100 Subject: [PATCH 090/265] More rename --- package-lock.json | 6 +++--- package.json | 2 +- test/integration.js | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index a4f1b11..75b54cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,9 +31,9 @@ } }, "asyngular-client": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/asyngular-client/-/asyngular-client-1.0.2.tgz", - "integrity": "sha512-wpYIUnzlSdnf5OBthy2fSn1jIfjs/mqeMG6C8glSMdpFi+92hKlaXKT6MgwGhTtItMe5SbN2ctXEnHKnzjXpPw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/asyngular-client/-/asyngular-client-1.0.3.tgz", + "integrity": "sha512-BpmKxeayDykzzruzOzIeUCJ5GttpvC0a8s9fIe4P16khTCHXSaeR7NEYF7g5GxA5D+yIBb+FolSXQcsWFn5gJQ==", "dev": true, "requires": { "async-stream-emitter": "1.1.0", diff --git a/package.json b/package.json index 62f8c58..5198345 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "ws": "6.1.2" }, "devDependencies": { - "asyngular-client": "^1.0.2", + "asyngular-client": "^1.0.3", "localStorage": "^1.0.3", "mocha": "5.2.0" }, diff --git a/test/integration.js b/test/integration.js index d2da5f5..ccb941e 100644 --- a/test/integration.js +++ b/test/integration.js @@ -168,7 +168,7 @@ describe('Integration tests', function () { portNumber++; destroyTestCase(); server.close(); - global.localStorage.removeItem('socketCluster.authToken'); + global.localStorage.removeItem('asyngular.authToken'); }); describe('Socket authentication', function () { @@ -192,7 +192,7 @@ describe('Integration tests', function () { }); it('Should send back error if JWT is invalid during handshake', async function () { - global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); + global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); client = asyngularClient.create(clientOptions); @@ -208,7 +208,7 @@ describe('Integration tests', function () { }); it('Should allow switching between users', async function () { - global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); + global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); let authenticateEvents = []; let deauthenticateEvents = []; @@ -270,7 +270,7 @@ describe('Integration tests', function () { }); it('Should emit correct events/data when socket is deauthenticated', async function () { - global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); + global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); let authenticationStateChangeEvents = []; let authStateChangeEvents = []; @@ -323,7 +323,7 @@ describe('Integration tests', function () { }); it('Should not authenticate the client if MIDDLEWARE_AUTHENTICATE blocks the authentication', async function () { - global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenAlice); + global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenAlice); client = asyngularClient.create(clientOptions); // The previous test authenticated us as 'alice', so that token will be passed to the server as @@ -754,7 +754,7 @@ describe('Integration tests', function () { }); it('The verifyToken method of the authEngine receives correct params', async function () { - global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); + global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); portNumber++; server = asyngularServer.listen(portNumber, { @@ -2098,7 +2098,7 @@ describe('Integration tests', function () { }); it('Should run authenticate middleware if JWT token exists', async function () { - global.localStorage.setItem('socketCluster.authToken', validSignedAuthTokenBob); + global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); middlewareFunction = async function (req) { middlewareWasExecuted = true; From 3369386c93e8c0160d7353dba9c560cd8fe7fb7b Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 27 Dec 2018 03:13:54 +0100 Subject: [PATCH 091/265] v1.0.2 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 75b54cc..5b65534 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.0.1", + "version": "1.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 5198345..73c798d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.0.1", + "version": "1.0.2", "description": "Server module for Asyngular", "main": "index.js", "repository": { From 15ce443521ea71dc26ac68aa6feb6889f9104450 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 27 Dec 2018 22:15:55 +0100 Subject: [PATCH 092/265] Fix name in test --- test/integration.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration.js b/test/integration.js index ccb941e..5ab2018 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1523,7 +1523,7 @@ describe('Integration tests', function () { assert.equal(eventList[1].channel, 'foo'); }); - it('When default SCSimpleBroker broker engine is used, scServer.exchange should support consuming data from a channel', async function () { + it('When default SCSimpleBroker broker engine is used, agServer.exchange should support consuming data from a channel', async function () { portNumber++; server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, @@ -1572,7 +1572,7 @@ describe('Integration tests', function () { assert.equal(receivedChannelData[1], 'hi2'); }); - it('When default SCSimpleBroker broker engine is used, scServer.exchange should support publishing data to a channel', async function () { + it('When default SCSimpleBroker broker engine is used, agServer.exchange should support publishing data to a channel', async function () { portNumber++; server = asyngularServer.listen(portNumber, { authKey: serverOptions.authKey, From 7e77d018bae9c8b220beac39a966030bae51805f Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 27 Dec 2018 23:09:53 +0100 Subject: [PATCH 093/265] Rename function in test --- test/integration.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/integration.js b/test/integration.js index 5ab2018..e60b46e 100644 --- a/test/integration.js +++ b/test/integration.js @@ -125,7 +125,7 @@ function connectionHandler(socket) { })(); }; -function destroyTestCase() { +function destroyClients() { if (client) { if (client.state !== client.CLOSED) { client.closeAllListeners(); @@ -166,7 +166,7 @@ describe('Integration tests', function () { afterEach('Close server after each test', async function () { portNumber++; - destroyTestCase(); + destroyClients(); server.close(); global.localStorage.removeItem('asyngular.authToken'); }); @@ -1948,7 +1948,7 @@ describe('Integration tests', function () { }); afterEach('Shut down server afterwards', async function () { - destroyTestCase(); + destroyClients(); server.close(); }); @@ -2014,7 +2014,7 @@ describe('Integration tests', function () { }); afterEach('Shut down server afterwards', async function () { - destroyTestCase(); + destroyClients(); server.close(); }); @@ -2077,7 +2077,7 @@ describe('Integration tests', function () { }); afterEach('Shut down server afterwards', async function () { - destroyTestCase(); + destroyClients(); server.close(); }); From a178f42fd4627337566cbf3341cda69307da17d2 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 27 Dec 2018 23:34:03 +0100 Subject: [PATCH 094/265] destroy method is no longer needed --- agserversocket.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/agserversocket.js b/agserversocket.js index 1b298a4..385f666 100644 --- a/agserversocket.js +++ b/agserversocket.js @@ -23,7 +23,6 @@ function AGServerSocket(id, server, socket) { this.socket = socket; this.state = this.CONNECTING; this.authState = this.UNAUTHENTICATED; - this.active = true; this._receiverDemux = new StreamDemux(); this._procedureDemux = new StreamDemux(); @@ -272,11 +271,6 @@ AGServerSocket.prototype.disconnect = function (code, data) { } }; -AGServerSocket.prototype.destroy = function (code, data) { - this.active = false; - this.disconnect(code, data); -}; - AGServerSocket.prototype.terminate = function () { this.socket.terminate(); }; From 31217268cdd9a6651c512be06495f7f91f318048 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 27 Dec 2018 23:34:20 +0100 Subject: [PATCH 095/265] Fix tests so that they exit correctly --- test/integration.js | 151 ++++++++++++-------------------------------- 1 file changed, 39 insertions(+), 112 deletions(-) diff --git a/test/integration.js b/test/integration.js index e60b46e..d0ba58c 100644 --- a/test/integration.js +++ b/test/integration.js @@ -125,53 +125,51 @@ function connectionHandler(socket) { })(); }; -function destroyClients() { - if (client) { - if (client.state !== client.CLOSED) { +describe('Integration tests', function () { + afterEach('Close server after each test', async function () { + portNumber++; + if (client) { client.closeAllListeners(); client.disconnect(); } - } -}; + if (server) { + server.closeAllListeners(); + server.close(); + server.httpServer.close(); + } + global.localStorage.removeItem('asyngular.authToken'); + }); -describe('Integration tests', function () { - beforeEach('Run the server before start', async function () { - clientOptions = { - hostname: '127.0.0.1', - port: portNumber - }; - serverOptions = { - authKey: 'testkey', - wsEngine: WS_ENGINE - }; - - server = asyngularServer.listen(portNumber, serverOptions); - - (async () => { - for await (let {socket} of server.listener('connection')) { - connectionHandler(socket); - } - })(); + describe('Client authentication', function () { + beforeEach('Run the server before start', async function () { + clientOptions = { + hostname: '127.0.0.1', + port: portNumber + }; + serverOptions = { + authKey: 'testkey', + wsEngine: WS_ENGINE + }; - server.addMiddleware(server.MIDDLEWARE_AUTHENTICATE, async function (req) { - if (req.authToken.username === 'alice') { - let err = new Error('Blocked by MIDDLEWARE_AUTHENTICATE'); - err.name = 'AuthenticateMiddlewareError'; - throw err; - } - }); + server = asyngularServer.listen(portNumber, serverOptions); - await server.listener('ready').once(); - }); + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); - afterEach('Close server after each test', async function () { - portNumber++; - destroyClients(); - server.close(); - global.localStorage.removeItem('asyngular.authToken'); - }); + server.addMiddleware(server.MIDDLEWARE_AUTHENTICATE, async function (req) { + if (req.authToken.username === 'alice') { + let err = new Error('Blocked by MIDDLEWARE_AUTHENTICATE'); + err.name = 'AuthenticateMiddlewareError'; + throw err; + } + }); + + await server.listener('ready').once(); + }); - describe('Socket authentication', function () { it('Should not send back error if JWT is not provided in handshake', async function () { client = asyngularClient.create(clientOptions); let event = await client.listener('connect').once(); @@ -336,7 +334,9 @@ describe('Integration tests', function () { assert.notEqual(event.authError, null); assert.equal(event.authError.name, 'AuthenticateMiddlewareError'); }); + }); + describe('Server authentication', function () { it('Token should be available after Promise resolves if token engine signing is synchronous', async function () { portNumber++; server = asyngularServer.listen(portNumber, { @@ -1873,64 +1873,6 @@ describe('Integration tests', function () { }); }); - describe('Socket destruction', function () { - it('Server socket destroy should disconnect the socket', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { - authKey: serverOptions.authKey, - wsEngine: WS_ENGINE - }); - - (async () => { - for await (let {socket} of server.listener('connection')) { - await wait(100); - socket.destroy(1000, 'Custom reason'); - } - })(); - - await server.listener('ready').once(); - - client = asyngularClient.create({ - hostname: clientOptions.hostname, - port: portNumber - }); - - let {code, reason} = await client.listener('disconnect').once(); - assert.equal(code, 1000); - assert.equal(reason, 'Custom reason'); - assert.equal(server.clientsCount, 0); - assert.equal(server.pendingClientsCount, 0); - }); - - it('Server socket destroy should set the active property on the socket to false', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { - authKey: serverOptions.authKey, - wsEngine: WS_ENGINE - }); - - let serverSocket; - - (async () => { - for await (let {socket} of server.listener('connection')) { - serverSocket = socket; - assert.equal(socket.active, true); - await wait(100); - socket.destroy(); - } - })(); - - await server.listener('ready').once(); - client = asyngularClient.create({ - hostname: clientOptions.hostname, - port: portNumber - }); - - await client.listener('disconnect').once(); - assert.equal(serverSocket.active, false); - }); - }); - describe('Socket Ping/pong', function () { describe('When when pingTimeoutDisabled is not set', function () { beforeEach('Launch server with ping options before start', async function () { @@ -1947,11 +1889,6 @@ describe('Integration tests', function () { await server.listener('ready').once(); }); - afterEach('Shut down server afterwards', async function () { - destroyClients(); - server.close(); - }); - it('Should disconnect socket if server does not receive a pong from client before timeout', async function () { client = asyngularClient.create({ hostname: clientOptions.hostname, @@ -2013,11 +1950,6 @@ describe('Integration tests', function () { await server.listener('ready').once(); }); - afterEach('Shut down server afterwards', async function () { - destroyClients(); - server.close(); - }); - it('Should not disconnect socket if server does not receive a pong from client before timeout', async function () { client = asyngularClient.create({ hostname: clientOptions.hostname, @@ -2076,11 +2008,6 @@ describe('Integration tests', function () { await server.listener('ready').once(); }); - afterEach('Shut down server afterwards', async function () { - destroyClients(); - server.close(); - }); - describe('MIDDLEWARE_AUTHENTICATE', function () { it('Should not run authenticate middleware if JWT token does not exist', async function () { middlewareFunction = async function (req) { From f23094e057135264b6f1180d7c98dbd307130baf Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 27 Dec 2018 23:38:22 +0100 Subject: [PATCH 096/265] Use a single port number for all test cases --- test/integration.js | 183 +++++++++++++++++++------------------------- 1 file changed, 78 insertions(+), 105 deletions(-) diff --git a/test/integration.js b/test/integration.js index d0ba58c..9b1b050 100644 --- a/test/integration.js +++ b/test/integration.js @@ -7,7 +7,6 @@ const SCSimpleBroker = require('sc-simple-broker').SCSimpleBroker; // Add to the global scope like in browser. global.localStorage = localStorage; -let portNumber = 8008; let clientOptions; let serverOptions; @@ -17,6 +16,7 @@ let allowedUsers = { alice: true }; +const PORT_NUMBER = 8008; const TEN_DAYS_IN_SECONDS = 60 * 60 * 24 * 10; const WS_ENGINE = 'ws'; @@ -127,7 +127,6 @@ function connectionHandler(socket) { describe('Integration tests', function () { afterEach('Close server after each test', async function () { - portNumber++; if (client) { client.closeAllListeners(); client.disconnect(); @@ -144,14 +143,14 @@ describe('Integration tests', function () { beforeEach('Run the server before start', async function () { clientOptions = { hostname: '127.0.0.1', - port: portNumber + port: PORT_NUMBER }; serverOptions = { authKey: 'testkey', wsEngine: WS_ENGINE }; - server = asyngularServer.listen(portNumber, serverOptions); + server = asyngularServer.listen(PORT_NUMBER, serverOptions); (async () => { for await (let {socket} of server.listener('connection')) { @@ -338,8 +337,7 @@ describe('Integration tests', function () { describe('Server authentication', function () { it('Token should be available after Promise resolves if token engine signing is synchronous', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authSignAsync: false @@ -355,7 +353,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); await client.listener('connect').once(); @@ -369,8 +367,7 @@ describe('Integration tests', function () { }); it('If token engine signing is asynchronous, authentication can be captured using the authenticate event', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authSignAsync: true @@ -386,7 +383,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); await client.listener('connect').once(); @@ -400,8 +397,7 @@ describe('Integration tests', function () { }); it('Should still work if token verification is asynchronous', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authVerifyAsync: false @@ -417,7 +413,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); await client.listener('connect').once(); @@ -437,8 +433,7 @@ describe('Integration tests', function () { }); it('Should set the correct expiry when using expiresIn option when creating a JWT with socket.setAuthToken', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authVerifyAsync: false @@ -454,7 +449,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); await client.listener('connect').once(); @@ -470,8 +465,7 @@ describe('Integration tests', function () { }); it('Should set the correct expiry when adding exp claim when creating a JWT with socket.setAuthToken', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authVerifyAsync: false @@ -487,7 +481,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); await client.listener('connect').once(); @@ -503,8 +497,7 @@ describe('Integration tests', function () { }); it('The exp claim should have priority over expiresIn option when using socket.setAuthToken', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authVerifyAsync: false @@ -520,7 +513,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); await client.listener('connect').once(); @@ -536,8 +529,7 @@ describe('Integration tests', function () { }); it('Should send back error if socket.setAuthToken tries to set both iss claim and issuer option', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authVerifyAsync: false @@ -554,7 +546,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); await client.listener('connect').once(); @@ -604,8 +596,7 @@ describe('Integration tests', function () { }); it('Should trigger an authTokenSigned event and socket.signedAuthToken should be set after calling the socket.setAuthToken method', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authSignAsync: true @@ -642,7 +633,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); await client.listener('connect').once(); @@ -653,8 +644,7 @@ describe('Integration tests', function () { }); it('Should reject Promise returned by socket.setAuthToken if token delivery fails and rejectOnFailedDelivery option is true', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authSignAsync: true, @@ -667,7 +657,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); await client.listener('connect').once(); client.invoke('login', {username: 'bob'}); @@ -704,8 +694,7 @@ describe('Integration tests', function () { }); it('Should not reject Promise returned by socket.setAuthToken if token delivery fails and rejectOnFailedDelivery option is not true', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, authSignAsync: true, @@ -718,7 +707,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); await client.listener('connect').once(); client.invoke('login', {username: 'bob'}); @@ -756,8 +745,7 @@ describe('Integration tests', function () { it('The verifyToken method of the authEngine receives correct params', async function () { global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -772,7 +760,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); })(); @@ -792,8 +780,7 @@ describe('Integration tests', function () { }); it('Should remove client data from the server when client disconnects before authentication process finished', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -812,7 +799,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); let serverSocket; @@ -839,8 +826,7 @@ describe('Integration tests', function () { describe('Socket handshake', function () { it('Exchange is attached to socket before the handshake event is triggered', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -855,7 +841,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); let {socket} = await server.listener('handshake').once(); @@ -865,8 +851,7 @@ describe('Integration tests', function () { describe('Socket connection', function () { it('Server-side socket connect event and server connection event should trigger', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -886,7 +871,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); let connectEmitted = false; @@ -950,8 +935,7 @@ describe('Integration tests', function () { describe('Socket disconnection', function () { it('Server-side socket disconnect event should not trigger if the socket did not complete the handshake; instead, it should trigger connectAbort', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -974,7 +958,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); let socketDisconnected = false; @@ -1027,8 +1011,7 @@ describe('Integration tests', function () { }); it('Server-side socket disconnect event should trigger if the socket completed the handshake (not connectAbort)', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1051,7 +1034,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); let socketDisconnected = false; @@ -1105,8 +1088,7 @@ describe('Integration tests', function () { }); it('The close event should trigger when the socket loses the connection before the handshake', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1127,7 +1109,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); let serverSocketClosed = false; @@ -1166,8 +1148,7 @@ describe('Integration tests', function () { }); it('The close event should trigger when the socket loses the connection after the handshake', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1188,7 +1169,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); let serverSocketClosed = false; @@ -1227,10 +1208,15 @@ describe('Integration tests', function () { }); }); + describe('Socket invoke', function () { + it ('Should support invoking a remote procedure on the server', async function () { + + }); + }); + describe('Socket pub/sub', function () { it('Should support subscription batching', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1276,7 +1262,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); let channelList = []; @@ -1319,8 +1305,7 @@ describe('Integration tests', function () { }); it('Client should not be able to subscribe to a channel before the handshake has completed', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1341,7 +1326,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); let isSubscribed = false; @@ -1374,8 +1359,7 @@ describe('Integration tests', function () { }); it('Server should be able to handle invalid #subscribe and #unsubscribe and #publish events without crashing', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1390,7 +1374,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); let nullInChannelArrayError; @@ -1478,8 +1462,7 @@ describe('Integration tests', function () { }); it('When default SCSimpleBroker broker engine is used, disconnect event should trigger before unsubscribe event', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1491,7 +1474,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); await client.subscribe('foo').listener('subscribe').once(); @@ -1524,8 +1507,7 @@ describe('Integration tests', function () { }); it('When default SCSimpleBroker broker engine is used, agServer.exchange should support consuming data from a channel', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1534,7 +1516,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); (async () => { @@ -1573,8 +1555,7 @@ describe('Integration tests', function () { }); it('When default SCSimpleBroker broker engine is used, agServer.exchange should support publishing data to a channel', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1583,7 +1564,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); (async () => { @@ -1618,14 +1599,13 @@ describe('Integration tests', function () { }); it('When disconnecting a socket, the unsubscribe event should trigger after the disconnect event', async function () { - portNumber++; let customBrokerEngine = new SCSimpleBroker(); let defaultUnsubscribeSocket = customBrokerEngine.unsubscribeSocket; customBrokerEngine.unsubscribeSocket = function (socket, channel) { return resolveAfterTimeout(100, defaultUnsubscribeSocket.call(this, socket, channel)); }; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, brokerEngine: customBrokerEngine @@ -1637,7 +1617,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); for await (let event of client.subscribe('foo').listener('subscribe')) { @@ -1674,9 +1654,8 @@ describe('Integration tests', function () { }); it('Socket should emit an error when trying to unsubscribe to a channel which it is not subscribed to', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1697,7 +1676,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); let error; @@ -1715,14 +1694,13 @@ describe('Integration tests', function () { }); it('Socket should not receive messages from a channel which it has only just unsubscribed from (accounting for delayed unsubscribe by brokerEngine)', async function () { - portNumber++; let customBrokerEngine = new SCSimpleBroker(); let defaultUnsubscribeSocket = customBrokerEngine.unsubscribeSocket; customBrokerEngine.unsubscribeSocket = function (socket, channel) { return resolveAfterTimeout(300, defaultUnsubscribeSocket.call(this, socket, channel)); }; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, brokerEngine: customBrokerEngine @@ -1744,7 +1722,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); // Stub the isSubscribed method so that it always returns true. // That way the client will always invoke watchers whenever @@ -1772,9 +1750,8 @@ describe('Integration tests', function () { }); it('Socket channelSubscriptions and channelSubscriptionsCount should update when socket.kickOut(channel) is called', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1809,7 +1786,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); client.subscribe('foo'); @@ -1822,9 +1799,8 @@ describe('Integration tests', function () { }); it('Socket channelSubscriptions and channelSubscriptionsCount should update when socket.kickOut() is called without arguments', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1859,7 +1835,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); client.subscribe('foo'); @@ -1876,10 +1852,9 @@ describe('Integration tests', function () { describe('Socket Ping/pong', function () { describe('When when pingTimeoutDisabled is not set', function () { beforeEach('Launch server with ping options before start', async function () { - portNumber++; // Intentionally make pingInterval higher than pingTimeout, that // way the client will never receive a ping or send back a pong. - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, pingInterval: 2000, @@ -1892,7 +1867,7 @@ describe('Integration tests', function () { it('Should disconnect socket if server does not receive a pong from client before timeout', async function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); let serverWarning = null; @@ -1936,10 +1911,9 @@ describe('Integration tests', function () { describe('When when pingTimeoutDisabled is true', function () { beforeEach('Launch server with ping options before start', async function () { - portNumber++; // Intentionally make pingInterval higher than pingTimeout, that // way the client will never receive a ping or send back a pong. - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, pingInterval: 2000, @@ -1953,7 +1927,7 @@ describe('Integration tests', function () { it('Should not disconnect socket if server does not receive a pong from client before timeout', async function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber, + port: PORT_NUMBER, pingTimeoutDisabled: true }); @@ -2000,8 +1974,7 @@ describe('Integration tests', function () { let middlewareWasExecuted = false; beforeEach('Launch server without middleware before start', async function () { - portNumber++; - server = asyngularServer.listen(portNumber, { + server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -2017,7 +1990,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); await client.listener('connect').once(); @@ -2034,7 +2007,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); (async () => { @@ -2072,7 +2045,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); (async () => { @@ -2113,7 +2086,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); (async () => { @@ -2148,7 +2121,7 @@ describe('Integration tests', function () { client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); (async () => { @@ -2177,7 +2150,7 @@ describe('Integration tests', function () { createConnectionTime = Date.now(); client = asyngularClient.create({ hostname: clientOptions.hostname, - port: portNumber + port: PORT_NUMBER }); (async () => { From 105d0d59b2d9d5de8fa7fc49ecb84789e7d7a2ba Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 27 Dec 2018 23:52:12 +0100 Subject: [PATCH 097/265] Add test cases related to RPC and transmit --- test/integration.js | 66 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/test/integration.js b/test/integration.js index 9b1b050..6e1e8ec 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1208,9 +1208,73 @@ describe('Integration tests', function () { }); }); - describe('Socket invoke', function () { + describe('Socket RPC invoke', function () { it ('Should support invoking a remote procedure on the server', async function () { + server = asyngularServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + + (async () => { + for await (let {socket} of server.listener('connection')) { + (async () => { + for await (let req of socket.procedure('customProc')) { + if (req.data.bad) { + let error = new Error('Server failed to execute the procedure'); + error.name = 'BadCustomError'; + req.error(error); + } else { + req.end('Success'); + } + } + })(); + } + })(); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + let result = await client.invoke('customProc', {good: true}); + assert.equal(result, 'Success'); + + let error; + try { + result = await client.invoke('customProc', {bad: true}); + } catch (err) { + error = err; + } + assert.notEqual(error, null); + assert.equal(error.name, 'BadCustomError'); + }); + }); + + describe('Socket transmit', function () { + it ('Should support receiving remote transmitted data on the server', async function () { + server = asyngularServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + + (async () => { + await wait(10); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + client.transmit('customRemoteEvent', 'This is data'); + })(); + + for await (let {socket} of server.listener('connection')) { + for await (let data of socket.receiver('customRemoteEvent')) { + assert.equal(data, 'This is data'); + break; + } + break; + } }); }); From 61cb193dfc762a10a47b9d7d2607d36f49dda17b Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 28 Dec 2018 00:07:41 +0100 Subject: [PATCH 098/265] Improve README --- README.md | 57 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index f4561cd..df0c913 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,57 @@ # asyngular-server Minimal server module for Asyngular -This is a stand-alone server module for Asyngular. This module offers the most flexibility when creating a Asyngular service but requires the most work to setup. -The repository for the full-featured framework is here: https://github.com/SocketCluster/asyngular +This is a stand-alone server module for Asyngular (SocketCluster with full async/await support). +Note that Asyngular's protocol is backwards compatible with the SocketCluster protocol. ## Setting up -You will need to install ```asyngular-server``` and ```asyngular-client``` (https://github.com/SocketCluster/asyngular-client) separately. +You will need to install both ```asyngular-server``` and ```asyngular-client``` (https://github.com/SocketCluster/asyngular-client). To install this module: ```npm install asyngular-server``` -Note that the full Asyngular framework (https://github.com/SocketCluster/asyngular) uses this module behind the scenes so the API is exactly the same and it works with the asyngular-client out of the box. -The main difference with using asyngular-server is that you won't get features like: +## Usage -- Automatic scalability across multiple CPU cores. -- Resilience; you are responsible for respawning the process if it crashes. -- Convenience; It requires more work up front to get working (not good for beginners). -- Pub/sub channels won't scale across multiple asyngular-server processes/hosts by default.\* +You need to attach it to an existing Node.js http or https server (example): +```js +var http = require('http'); +var asyngularServer = require('socketcluster-server'); -\* Note that the ```asyngularServer.attach(httpServer, options);``` takes an optional options argument which can have a ```brokerEngine``` property - By default, asyngular-server -uses ```sc-simple-broker``` which is a basic single-process in-memory broker. If you want to add your own brokerEngine (for example to scale your asyngular-servers across multiple cores/hosts), then you might want to look at how sc-simple-broker was implemented. +var httpServer = http.createServer(); +var agServer = asyngularServer.attach(httpServer); + +(async () => { + // Handle new inbound sockets. + for await (let {socket} of server.listener('connection')) { + + (async () => { + // Set up a loop to handle and respond to RPCs for a procedure. + for await (let req of socket.procedure('customProc')) { + if (req.data.bad) { + let error = new Error('Server failed to execute the procedure'); + error.name = 'BadCustomError'; + req.error(error); + } else { + req.end('Success'); + } + } + })(); + + (async () => { + // Set up a loop to handle remote transmitted events. + for await (let data of socket.receiver('customRemoteEvent')) { + // ... + } + })(); -The full Asyngular framework uses a different broker engine: ```sc-broker-cluster```(https://github.com/SocketCluster/sc-broker-cluster) - This is a more complex brokerEngine - It allows messages to be brokered between -multiple processes and can be synchronized with remote hosts too so you can get both horizontal and vertical scalability. + } +})(); -The main benefit of this module is that it gives you maximum flexibility. You just need to attach it to a Node.js http server so you can use it alongside pretty much any framework. +httpServer.listen(8000); +``` + +For more detailed examples of how to use Asyngular, see `test/integration.js`. + +\* Note that the ```asyngularServer.attach(httpServer, options);``` takes an optional options argument which can have a ```brokerEngine``` property - By default, asyngular-server +uses ```sc-simple-broker``` which is a basic single-process in-memory broker. If you want to add your own brokerEngine (for example to scale your asyngular-servers across multiple cores/hosts), then you might want to look at how sc-simple-broker was implemented. From 26936173dff4306ad10b14711623a26ca32bbc5f Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 28 Dec 2018 00:08:15 +0100 Subject: [PATCH 099/265] v1.0.3 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5b65534..7bbc0bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.0.2", + "version": "1.0.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 73c798d..e73ad4c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.0.2", + "version": "1.0.3", "description": "Server module for Asyngular", "main": "index.js", "repository": { From 1019a1d5300f4fead92d190d9f67b57cc0fea631 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 28 Dec 2018 00:11:41 +0100 Subject: [PATCH 100/265] Additional README info --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index df0c913..5eaed15 100644 --- a/README.md +++ b/README.md @@ -53,5 +53,12 @@ httpServer.listen(8000); For more detailed examples of how to use Asyngular, see `test/integration.js`. +## Running the integration tests + +- Clone this repo: `git clone git@github.com:SocketCluster/asyngular-server.git` +- Navigate to project directory: `cd asyngular-server` +- Install all dependencies: `npm install` +- Run the tests `npm test` + \* Note that the ```asyngularServer.attach(httpServer, options);``` takes an optional options argument which can have a ```brokerEngine``` property - By default, asyngular-server uses ```sc-simple-broker``` which is a basic single-process in-memory broker. If you want to add your own brokerEngine (for example to scale your asyngular-servers across multiple cores/hosts), then you might want to look at how sc-simple-broker was implemented. From db2e935b11b3fba83af68407bcd396b187c8bef5 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 28 Dec 2018 00:13:56 +0100 Subject: [PATCH 101/265] README fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5eaed15..e689ae3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Minimal server module for Asyngular This is a stand-alone server module for Asyngular (SocketCluster with full async/await support). -Note that Asyngular's protocol is backwards compatible with the SocketCluster protocol. +Asyngular's protocol is backwards compatible with the SocketCluster protocol. ## Setting up From b0700ef4c317ca147ea0f75c8e203733f75eb7a9 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 28 Dec 2018 00:16:24 +0100 Subject: [PATCH 102/265] Correct README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e689ae3..e0fb633 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ var agServer = asyngularServer.attach(httpServer); (async () => { // Handle new inbound sockets. - for await (let {socket} of server.listener('connection')) { + for await (let {socket} of agServer.listener('connection')) { (async () => { // Set up a loop to handle and respond to RPCs for a procedure. From c42fe949bcf126060400345429d61bce73b0b67a Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 28 Dec 2018 00:17:42 +0100 Subject: [PATCH 103/265] README update --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e0fb633..f2ea035 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ httpServer.listen(8000); ``` For more detailed examples of how to use Asyngular, see `test/integration.js`. +Also, see tests from the `asyngular-client` module. ## Running the integration tests From 0c0cb60b11e3a0c4c3f2a163eb15b28c5c855497 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 28 Dec 2018 00:18:48 +0100 Subject: [PATCH 104/265] Minor fix to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2ea035..e5a9ca7 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ httpServer.listen(8000); For more detailed examples of how to use Asyngular, see `test/integration.js`. Also, see tests from the `asyngular-client` module. -## Running the integration tests +## Running the tests - Clone this repo: `git clone git@github.com:SocketCluster/asyngular-server.git` - Navigate to project directory: `cd asyngular-server` From 205f6a366e39e7b1e326a8d39602a0135fa9a66d Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 28 Dec 2018 00:19:14 +0100 Subject: [PATCH 105/265] README fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e5a9ca7..5edfb38 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Also, see tests from the `asyngular-client` module. - Clone this repo: `git clone git@github.com:SocketCluster/asyngular-server.git` - Navigate to project directory: `cd asyngular-server` - Install all dependencies: `npm install` -- Run the tests `npm test` +- Run the tests: `npm test` \* Note that the ```asyngularServer.attach(httpServer, options);``` takes an optional options argument which can have a ```brokerEngine``` property - By default, asyngular-server uses ```sc-simple-broker``` which is a basic single-process in-memory broker. If you want to add your own brokerEngine (for example to scale your asyngular-servers across multiple cores/hosts), then you might want to look at how sc-simple-broker was implemented. From 86d4be534ae7a12a393eb83b4cc34b3087caab03 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 28 Dec 2018 00:24:27 +0100 Subject: [PATCH 106/265] Fix README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5edfb38..336749e 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ To install this module: You need to attach it to an existing Node.js http or https server (example): ```js var http = require('http'); -var asyngularServer = require('socketcluster-server'); +var asyngularServer = require('asyngular-server'); var httpServer = http.createServer(); var agServer = asyngularServer.attach(httpServer); From 2cf9034952549afb38d6c7873ebdb9900929208e Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 28 Dec 2018 00:25:01 +0100 Subject: [PATCH 107/265] v1.0.4 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7bbc0bc..7458334 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.0.3", + "version": "1.0.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e73ad4c..304506e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.0.3", + "version": "1.0.4", "description": "Server module for Asyngular", "main": "index.js", "repository": { From 600f36aed8429d7d25e3ce1f4ee49bdc112c22ec Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 28 Dec 2018 00:35:09 +0100 Subject: [PATCH 108/265] Consistent line spacing --- agserver.js | 1 - agserversocket.js | 1 - 2 files changed, 2 deletions(-) diff --git a/agserver.js b/agserver.js index b29b775..aa907ab 100644 --- a/agserver.js +++ b/agserver.js @@ -21,7 +21,6 @@ const InvalidActionError = scErrors.InvalidActionError; const BrokerError = scErrors.BrokerError; const ServerProtocolError = scErrors.ServerProtocolError; - function AGServer(options) { AsyncStreamEmitter.call(this); diff --git a/agserversocket.js b/agserversocket.js index 385f666..ccfa875 100644 --- a/agserversocket.js +++ b/agserversocket.js @@ -10,7 +10,6 @@ const TimeoutError = scErrors.TimeoutError; const InvalidActionError = scErrors.InvalidActionError; const AuthError = scErrors.AuthError; - function AGServerSocket(id, server, socket) { AsyncStreamEmitter.call(this); From 31f79a855baa1c973c367b3371f78e27d002ec69 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 28 Dec 2018 10:16:20 +0100 Subject: [PATCH 109/265] Update README --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 336749e..0eb644f 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,9 @@ httpServer.listen(8000); For more detailed examples of how to use Asyngular, see `test/integration.js`. Also, see tests from the `asyngular-client` module. +Asyngular can work without the `for-await-of` loop; a `while` loop with `await` statements can be used instead. +See https://github.com/SocketCluster/stream-demux#usage + ## Running the tests - Clone this repo: `git clone git@github.com:SocketCluster/asyngular-server.git` @@ -61,5 +64,8 @@ Also, see tests from the `asyngular-client` module. - Install all dependencies: `npm install` - Run the tests: `npm test` -\* Note that the ```asyngularServer.attach(httpServer, options);``` takes an optional options argument which can have a ```brokerEngine``` property - By default, asyngular-server -uses ```sc-simple-broker``` which is a basic single-process in-memory broker. If you want to add your own brokerEngine (for example to scale your asyngular-servers across multiple cores/hosts), then you might want to look at how sc-simple-broker was implemented. +## Benefits of async `Iterable` over `EventEmitter` + +- **More readable**: Code is written sequentially from top to bottom. Avoids event handler callback hell. It's also much easier to write and read complex integration test scenarios. +- **More manageable**: No need to remember to unbind listeners with `removeEventListener(...)`; just `break` out of the `for-await-of` loop to stop consuming. This also encourages a more declarative style of coding. +- **Safer**: Each kind of async operation can be declared to run sequentially without missing any events. On the other hand, with `EventEmitter`, the listener function for the same event cannot be prevented from running multiple times in parallel; this can cause unintended side effects. From bd7bac04d6d2b7da185c9d68b501337082bffe84 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 28 Dec 2018 10:32:19 +0100 Subject: [PATCH 110/265] Add license to README --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 0eb644f..aa06a82 100644 --- a/README.md +++ b/README.md @@ -69,3 +69,15 @@ See https://github.com/SocketCluster/stream-demux#usage - **More readable**: Code is written sequentially from top to bottom. Avoids event handler callback hell. It's also much easier to write and read complex integration test scenarios. - **More manageable**: No need to remember to unbind listeners with `removeEventListener(...)`; just `break` out of the `for-await-of` loop to stop consuming. This also encourages a more declarative style of coding. - **Safer**: Each kind of async operation can be declared to run sequentially without missing any events. On the other hand, with `EventEmitter`, the listener function for the same event cannot be prevented from running multiple times in parallel; this can cause unintended side effects. + +## License + +(The MIT License) + +Copyright (c) 2013-2019 SocketCluster.io + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From eb3a43ded25b4ee57286169fdb18cbd348571c60 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 28 Dec 2018 17:25:05 +0100 Subject: [PATCH 111/265] Await close in test --- test/integration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration.js b/test/integration.js index 6e1e8ec..99e04fb 100644 --- a/test/integration.js +++ b/test/integration.js @@ -133,8 +133,8 @@ describe('Integration tests', function () { } if (server) { server.closeAllListeners(); - server.close(); server.httpServer.close(); + await server.close(); } global.localStorage.removeItem('asyngular.authToken'); }); From d27c1f5c52fed86a3229c69accf5a3a3cac6e2da Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 28 Dec 2018 17:32:32 +0100 Subject: [PATCH 112/265] Improve test desc --- test/integration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration.js b/test/integration.js index 99e04fb..e13d85a 100644 --- a/test/integration.js +++ b/test/integration.js @@ -126,7 +126,7 @@ function connectionHandler(socket) { }; describe('Integration tests', function () { - afterEach('Close server after each test', async function () { + afterEach('Close server and client after each test', async function () { if (client) { client.closeAllListeners(); client.disconnect(); From 1ebf7868919af1b573b1eb7b9df771a5b8f495de Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 28 Dec 2018 17:43:03 +0100 Subject: [PATCH 113/265] README update --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aa06a82..5f2e66a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,9 @@ Asyngular's protocol is backwards compatible with the SocketCluster protocol. You will need to install both ```asyngular-server``` and ```asyngular-client``` (https://github.com/SocketCluster/asyngular-client). To install this module: -```npm install asyngular-server``` +```bash +npm install asyngular-server +``` ## Usage From a07a5cd72123e76b806fac173f4d081735a9dfc2 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 28 Dec 2018 17:46:32 +0100 Subject: [PATCH 114/265] Improve README --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5f2e66a..789bc84 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,11 @@ npm install asyngular-server You need to attach it to an existing Node.js http or https server (example): ```js -var http = require('http'); -var asyngularServer = require('asyngular-server'); +const http = require('http'); +const asyngularServer = require('asyngular-server'); -var httpServer = http.createServer(); -var agServer = asyngularServer.attach(httpServer); +let httpServer = http.createServer(); +let agServer = asyngularServer.attach(httpServer); (async () => { // Handle new inbound sockets. From e8bf71a782a124004e4340b6c9d160c03585d5e7 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 28 Dec 2018 18:16:48 +0100 Subject: [PATCH 115/265] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 789bc84..53a0c71 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# asyngular-server +# Asyngular server Minimal server module for Asyngular This is a stand-alone server module for Asyngular (SocketCluster with full async/await support). From d9e62101dee0e6679ac50bc717a14d2ca95080d4 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 29 Dec 2018 00:09:07 +0100 Subject: [PATCH 116/265] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 53a0c71..35f7a65 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ See https://github.com/SocketCluster/stream-demux#usage ## Benefits of async `Iterable` over `EventEmitter` - **More readable**: Code is written sequentially from top to bottom. Avoids event handler callback hell. It's also much easier to write and read complex integration test scenarios. -- **More manageable**: No need to remember to unbind listeners with `removeEventListener(...)`; just `break` out of the `for-await-of` loop to stop consuming. This also encourages a more declarative style of coding. +- **More manageable**: No need to remember to unbind listeners with `removeListener(...)`; just `break` out of the `for-await-of` loop to stop consuming. This also encourages a more declarative style of coding. - **Safer**: Each kind of async operation can be declared to run sequentially without missing any events. On the other hand, with `EventEmitter`, the listener function for the same event cannot be prevented from running multiple times in parallel; this can cause unintended side effects. ## License From baa90ad9b11bdbc5ef928d599e468987e14a37db Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 29 Dec 2018 00:10:56 +0100 Subject: [PATCH 117/265] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 35f7a65..019d9e1 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ See https://github.com/SocketCluster/stream-demux#usage - **More readable**: Code is written sequentially from top to bottom. Avoids event handler callback hell. It's also much easier to write and read complex integration test scenarios. - **More manageable**: No need to remember to unbind listeners with `removeListener(...)`; just `break` out of the `for-await-of` loop to stop consuming. This also encourages a more declarative style of coding. -- **Safer**: Each kind of async operation can be declared to run sequentially without missing any events. On the other hand, with `EventEmitter`, the listener function for the same event cannot be prevented from running multiple times in parallel; this can cause unintended side effects. +- **Safer**: Each kind of async operation can run sequentially without missing any events. On the other hand, with `EventEmitter`, the listener function for the same event can run multiple times in parallel; this can cause unintended side effects. ## License From 2b26a7c16d8e1e75c04c21782f84e9ee2c3d0316 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 29 Dec 2018 00:12:12 +0100 Subject: [PATCH 118/265] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 019d9e1..16d40dc 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ See https://github.com/SocketCluster/stream-demux#usage - **More readable**: Code is written sequentially from top to bottom. Avoids event handler callback hell. It's also much easier to write and read complex integration test scenarios. - **More manageable**: No need to remember to unbind listeners with `removeListener(...)`; just `break` out of the `for-await-of` loop to stop consuming. This also encourages a more declarative style of coding. -- **Safer**: Each kind of async operation can run sequentially without missing any events. On the other hand, with `EventEmitter`, the listener function for the same event can run multiple times in parallel; this can cause unintended side effects. +- **Safer**: Each event can be processed sequentially without missing any events. On the other hand, with `EventEmitter`, the listener function for the same event can run multiple times in parallel; this can cause unintended side effects. ## License From d99018d3250a402fe04fd72279b0a54702f24fb7 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 29 Dec 2018 00:12:50 +0100 Subject: [PATCH 119/265] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 16d40dc..bdc51dc 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ See https://github.com/SocketCluster/stream-demux#usage - **More readable**: Code is written sequentially from top to bottom. Avoids event handler callback hell. It's also much easier to write and read complex integration test scenarios. - **More manageable**: No need to remember to unbind listeners with `removeListener(...)`; just `break` out of the `for-await-of` loop to stop consuming. This also encourages a more declarative style of coding. -- **Safer**: Each event can be processed sequentially without missing any events. On the other hand, with `EventEmitter`, the listener function for the same event can run multiple times in parallel; this can cause unintended side effects. +- **Safer**: Each event can be processed sequentially without missing any events. On the other hand, with `EventEmitter`, the listener function for the same event cannot be prevented from running multiple times in parallel; this can cause unintended side effects. ## License From 6599092c4e7d918bf2e31aa24b515ce25e6a4471 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 29 Dec 2018 00:14:33 +0100 Subject: [PATCH 120/265] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bdc51dc..82c782a 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ See https://github.com/SocketCluster/stream-demux#usage ## Benefits of async `Iterable` over `EventEmitter` -- **More readable**: Code is written sequentially from top to bottom. Avoids event handler callback hell. It's also much easier to write and read complex integration test scenarios. +- **More readable**: Code is written sequentially from top to bottom. It avoids event handler callback hell. It's also much easier to write and read complex integration test scenarios. - **More manageable**: No need to remember to unbind listeners with `removeListener(...)`; just `break` out of the `for-await-of` loop to stop consuming. This also encourages a more declarative style of coding. - **Safer**: Each event can be processed sequentially without missing any events. On the other hand, with `EventEmitter`, the listener function for the same event cannot be prevented from running multiple times in parallel; this can cause unintended side effects. From 6fdd5fda01735dde9c848a7dfed6638eb7b7765b Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 29 Dec 2018 00:27:37 +0100 Subject: [PATCH 121/265] Update README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 82c782a..61ef946 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,9 @@ See https://github.com/SocketCluster/stream-demux#usage ## Benefits of async `Iterable` over `EventEmitter` - **More readable**: Code is written sequentially from top to bottom. It avoids event handler callback hell. It's also much easier to write and read complex integration test scenarios. +- **More succinct**: Event streams can be easily chained, filtered and combined using a declarative syntax (e.g. using async generators). - **More manageable**: No need to remember to unbind listeners with `removeListener(...)`; just `break` out of the `for-await-of` loop to stop consuming. This also encourages a more declarative style of coding. -- **Safer**: Each event can be processed sequentially without missing any events. On the other hand, with `EventEmitter`, the listener function for the same event cannot be prevented from running multiple times in parallel; this can cause unintended side effects. +- **Less error-prone**: Each event can be processed sequentially without missing any events. On the other hand, with `EventEmitter`, the listener function for the same event cannot be prevented from running multiple times in parallel; this can cause unintended side effects. ## License From 83cc04668e3f7342775f40e5d5e775d617c80068 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 29 Dec 2018 11:23:22 +0100 Subject: [PATCH 122/265] v1.0.5 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7458334..fd452c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.0.4", + "version": "1.0.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 304506e..7a9eca2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.0.4", + "version": "1.0.5", "description": "Server module for Asyngular", "main": "index.js", "repository": { From efb1a06cb21026f7d01dccad5e14e1042f1f021e Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 4 Jan 2019 18:32:37 +0100 Subject: [PATCH 123/265] v1.0.6 --- package-lock.json | 8 ++++---- package.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index fd452c5..f5a24a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.0.5", + "version": "1.0.6", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -390,9 +390,9 @@ "integrity": "sha512-9PbqYBpCq+OoEeRQ3QfFIGE6qwjjBcd2j7UjgDlhnZbtSnuGgHdcRklPKYGuYFH82V/dwd+AIpu8XvA1zqTd+A==" }, "sc-simple-broker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-3.0.0.tgz", - "integrity": "sha512-337+Te7nXrFK2mgLLYzmTK47WMhqSstfy+0bwTfU66opCg19ipmbeXrAJnGZXN6OukMV3zzLpQt60s6r1QA0Sg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-3.1.0.tgz", + "integrity": "sha512-TJMImj76QWRyCq/RiAdfv1lovKwHb0EQAlg6yfb57K6tQ26GvFhkBxDOClcetzdhe3irQ/3Qp5obAMDgNoL9ew==", "requires": { "async-stream-emitter": "1.1.0", "sc-channel": "2.0.0", diff --git a/package.json b/package.json index 7a9eca2..77ad3e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.0.5", + "version": "1.0.6", "description": "Server module for Asyngular", "main": "index.js", "repository": { @@ -15,7 +15,7 @@ "sc-auth": "^6.0.0", "sc-errors": "^2.0.0", "sc-formatter": "^3.0.2", - "sc-simple-broker": "^3.0.0", + "sc-simple-broker": "^3.1.0", "stream-demux": "^4.0.4", "uuid": "3.2.1", "ws": "6.1.2" From 11fdc2aa072f44656f456a42b599128121b3aee1 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 4 Jan 2019 18:45:49 +0100 Subject: [PATCH 124/265] v1.0.7 --- package-lock.json | 8 ++++---- package.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index f5a24a7..e8b3fe4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.0.6", + "version": "1.0.7", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -390,9 +390,9 @@ "integrity": "sha512-9PbqYBpCq+OoEeRQ3QfFIGE6qwjjBcd2j7UjgDlhnZbtSnuGgHdcRklPKYGuYFH82V/dwd+AIpu8XvA1zqTd+A==" }, "sc-simple-broker": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-3.1.0.tgz", - "integrity": "sha512-TJMImj76QWRyCq/RiAdfv1lovKwHb0EQAlg6yfb57K6tQ26GvFhkBxDOClcetzdhe3irQ/3Qp5obAMDgNoL9ew==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-3.2.1.tgz", + "integrity": "sha512-b3BOF835oce+NzSjMQRclQO0nqb+fmhxSi+tZy3I113KbN2WHhSkBexW8vB6G1bayBxTe0nJFfKlMsy82be/EQ==", "requires": { "async-stream-emitter": "1.1.0", "sc-channel": "2.0.0", diff --git a/package.json b/package.json index 77ad3e5..3663f21 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.0.6", + "version": "1.0.7", "description": "Server module for Asyngular", "main": "index.js", "repository": { @@ -15,7 +15,7 @@ "sc-auth": "^6.0.0", "sc-errors": "^2.0.0", "sc-formatter": "^3.0.2", - "sc-simple-broker": "^3.1.0", + "sc-simple-broker": "^3.2.1", "stream-demux": "^4.0.4", "uuid": "3.2.1", "ws": "6.1.2" From 3de8b115a92af6aeac3f1873f70abee586c2adac Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 6 Jan 2019 22:25:19 +0100 Subject: [PATCH 125/265] Rename SC to AG --- agserver.js | 16 ++++++++-------- agserversocket.js | 12 ++++++------ test/integration.js | 26 +++++++++++++------------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/agserver.js b/agserver.js index aa907ab..5fd3a61 100644 --- a/agserver.js +++ b/agserver.js @@ -48,7 +48,7 @@ function AGServer(options) { this.options = Object.assign(opts, options); this.MIDDLEWARE_HANDSHAKE_WS = 'handshakeWS'; - this.MIDDLEWARE_HANDSHAKE_SC = 'handshakeSC'; + this.MIDDLEWARE_HANDSHAKE_AG = 'handshakeAG'; this.MIDDLEWARE_TRANSMIT = 'transmit'; this.MIDDLEWARE_INVOKE = 'invoke'; this.MIDDLEWARE_SUBSCRIBE = 'subscribe'; @@ -61,7 +61,7 @@ function AGServer(options) { this._middleware = {}; this._middleware[this.MIDDLEWARE_HANDSHAKE_WS] = []; - this._middleware[this.MIDDLEWARE_HANDSHAKE_SC] = []; + this._middleware[this.MIDDLEWARE_HANDSHAKE_AG] = []; this._middleware[this.MIDDLEWARE_TRANSMIT] = []; this._middleware[this.MIDDLEWARE_INVOKE] = []; this._middleware[this.MIDDLEWARE_SUBSCRIBE] = []; @@ -604,7 +604,7 @@ AGServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { let signedAuthToken = data.authToken || null; clearTimeout(scSocket._handshakeTimeoutRef); - this._passThroughHandshakeSCMiddleware({ + this._passThroughHandshakeAGMiddleware({ socket: scSocket }, (err, statusCode) => { if (err) { @@ -1056,19 +1056,19 @@ AGServer.prototype._passThroughAuthenticateMiddleware = function (options, callb ); }; -AGServer.prototype._passThroughHandshakeSCMiddleware = function (options, callback) { +AGServer.prototype._passThroughHandshakeAGMiddleware = function (options, callback) { let callbackInvoked = false; let request = { socket: options.socket }; - async.applyEachSeries(this._middleware[this.MIDDLEWARE_HANDSHAKE_SC], request, + async.applyEachSeries(this._middleware[this.MIDDLEWARE_HANDSHAKE_AG], request, (err, results) => { if (callbackInvoked) { this.emitWarning( new InvalidActionError( - `Callback for ${this.MIDDLEWARE_HANDSHAKE_SC} middleware was already invoked` + `Callback for ${this.MIDDLEWARE_HANDSHAKE_AG} middleware was already invoked` ) ); } else { @@ -1085,8 +1085,8 @@ AGServer.prototype._passThroughHandshakeSCMiddleware = function (options, callba } if (err === true || err.silent) { err = new SilentMiddlewareBlockedError( - `Action was silently blocked by ${this.MIDDLEWARE_HANDSHAKE_SC} middleware`, - this.MIDDLEWARE_HANDSHAKE_SC + `Action was silently blocked by ${this.MIDDLEWARE_HANDSHAKE_AG} middleware`, + this.MIDDLEWARE_HANDSHAKE_AG ); } else if (this.middlewareEmitWarnings) { this.emitWarning(err); diff --git a/agserversocket.js b/agserversocket.js index ccfa875..52077b8 100644 --- a/agserversocket.js +++ b/agserversocket.js @@ -53,7 +53,7 @@ function AGServerSocket(id, server, socket) { }); this.socket.on('close', (code, data) => { - this._onSCClose(code, data); + this._onClose(code, data); }); if (!this.server.pingTimeoutDisabled) { @@ -195,7 +195,7 @@ AGServerSocket.prototype._resetPongTimeout = function () { } clearTimeout(this._pingTimeoutTicker); this._pingTimeoutTicker = setTimeout(() => { - this._onSCClose(4001); + this._onClose(4001); this.socket.close(4001); }, this.server.pingTimeout); }; @@ -218,7 +218,7 @@ AGServerSocket.prototype.emitError = function (error) { }); }; -AGServerSocket.prototype._onSCClose = function (code, reason) { +AGServerSocket.prototype._onClose = function (code, reason) { clearInterval(this._pingIntervalTicker); clearTimeout(this._pingTimeoutTicker); @@ -265,7 +265,7 @@ AGServerSocket.prototype.disconnect = function (code, data) { } if (this.state !== this.CLOSED) { - this._onSCClose(code, data); + this._onClose(code, data); this.socket.close(code, data); } }; @@ -277,7 +277,7 @@ AGServerSocket.prototype.terminate = function () { AGServerSocket.prototype.send = function (data, options) { this.socket.send(data, options, (err) => { if (err) { - this._onSCClose(1006, err.toString()); + this._onClose(1006, err.toString()); } }); }; @@ -470,7 +470,7 @@ AGServerSocket.prototype.setAuthToken = async function (data, options) { let handleAuthTokenSignFail = (error) => { this.emitError(error); - this._onSCClose(4002, error.toString()); + this._onClose(4002, error.toString()); this.socket.close(4002); throw error; }; diff --git a/test/integration.js b/test/integration.js index e13d85a..f92967c 100644 --- a/test/integration.js +++ b/test/integration.js @@ -2085,8 +2085,8 @@ describe('Integration tests', function () { }); }); - describe('MIDDLEWARE_HANDSHAKE_SC', function () { - it('Should trigger correct events if MIDDLEWARE_HANDSHAKE_SC blocks with an error', async function () { + describe('MIDDLEWARE_HANDSHAKE_AG', function () { + it('Should trigger correct events if MIDDLEWARE_HANDSHAKE_AG blocks with an error', async function () { let middlewareWasExecuted = false; let serverWarnings = []; let clientErrors = []; @@ -2095,11 +2095,11 @@ describe('Integration tests', function () { middlewareFunction = async function (req) { await wait(100); middlewareWasExecuted = true; - let err = new Error('SC handshake failed because the server was too lazy'); + let err = new Error('AG handshake failed because the server was too lazy'); err.name = 'TooLazyHandshakeError'; throw err; }; - server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_SC, middlewareFunction); + server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_AG, middlewareFunction); (async () => { for await (let {warning} of server.listener('warning')) { @@ -2134,7 +2134,7 @@ describe('Integration tests', function () { assert.notEqual(abortStatus, null); }); - it('Should send back default 4008 status code if MIDDLEWARE_HANDSHAKE_SC blocks without providing a status code', async function () { + it('Should send back default 4008 status code if MIDDLEWARE_HANDSHAKE_AG blocks without providing a status code', async function () { let middlewareWasExecuted = false; let abortStatus; let abortReason; @@ -2142,11 +2142,11 @@ describe('Integration tests', function () { middlewareFunction = async function (req) { await wait(100); middlewareWasExecuted = true; - let err = new Error('SC handshake failed because the server was too lazy'); + let err = new Error('AG handshake failed because the server was too lazy'); err.name = 'TooLazyHandshakeError'; throw err; }; - server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_SC, middlewareFunction); + server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_AG, middlewareFunction); client = asyngularClient.create({ hostname: clientOptions.hostname, @@ -2162,10 +2162,10 @@ describe('Integration tests', function () { await wait(200); assert.equal(middlewareWasExecuted, true); assert.equal(abortStatus, 4008); - assert.equal(abortReason, 'TooLazyHandshakeError: SC handshake failed because the server was too lazy'); + assert.equal(abortReason, 'TooLazyHandshakeError: AG handshake failed because the server was too lazy'); }); - it('Should send back custom status code if MIDDLEWARE_HANDSHAKE_SC blocks by providing a status code', async function () { + it('Should send back custom status code if MIDDLEWARE_HANDSHAKE_AG blocks by providing a status code', async function () { let middlewareWasExecuted = false; let abortStatus; let abortReason; @@ -2173,7 +2173,7 @@ describe('Integration tests', function () { middlewareFunction = async function (req) { await wait(100); middlewareWasExecuted = true; - let err = new Error('SC handshake failed because of invalid query auth parameters'); + let err = new Error('AG handshake failed because of invalid query auth parameters'); err.name = 'InvalidAuthQueryHandshakeError'; // Set custom 4501 status code as a property of the error. // We will treat this code as a fatal authentication failure on the front end. @@ -2181,7 +2181,7 @@ describe('Integration tests', function () { err.statusCode = 4501; throw err; }; - server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_SC, middlewareFunction); + server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_AG, middlewareFunction); client = asyngularClient.create({ hostname: clientOptions.hostname, @@ -2197,7 +2197,7 @@ describe('Integration tests', function () { await wait(200); assert.equal(middlewareWasExecuted, true); assert.equal(abortStatus, 4501); - assert.equal(abortReason, 'InvalidAuthQueryHandshakeError: SC handshake failed because of invalid query auth parameters'); + assert.equal(abortReason, 'InvalidAuthQueryHandshakeError: AG handshake failed because of invalid query auth parameters'); }); it('Should connect with a delay if next() is called after a timeout inside the middleware function', async function () { @@ -2209,7 +2209,7 @@ describe('Integration tests', function () { middlewareFunction = async function (req) { await wait(500); }; - server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_SC, middlewareFunction); + server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_AG, middlewareFunction); createConnectionTime = Date.now(); client = asyngularClient.create({ From 21229e3e5f638d79ef0997e923524f7bea2728cf Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 6 Jan 2019 22:25:56 +0100 Subject: [PATCH 126/265] v1.1.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e8b3fe4..5f76af0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.0.7", + "version": "1.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 3663f21..89a75ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.0.7", + "version": "1.1.0", "description": "Server module for Asyngular", "main": "index.js", "repository": { From f170e1eccb58ee166fb513962b6299e503a19911 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 6 Jan 2019 23:33:31 +0100 Subject: [PATCH 127/265] Bump dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 89a75ef..ba6ac93 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "ws": "6.1.2" }, "devDependencies": { - "asyngular-client": "^1.0.3", + "asyngular-client": "^1.1.3", "localStorage": "^1.0.3", "mocha": "5.2.0" }, From 5651861a3a10fde69f84739690f0535cb294f47c Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 6 Jan 2019 23:34:10 +0100 Subject: [PATCH 128/265] v1.1.1 --- package-lock.json | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5f76af0..2655c05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.1.0", + "version": "1.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -31,9 +31,9 @@ } }, "asyngular-client": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/asyngular-client/-/asyngular-client-1.0.3.tgz", - "integrity": "sha512-BpmKxeayDykzzruzOzIeUCJ5GttpvC0a8s9fIe4P16khTCHXSaeR7NEYF7g5GxA5D+yIBb+FolSXQcsWFn5gJQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/asyngular-client/-/asyngular-client-1.1.3.tgz", + "integrity": "sha512-15Q6wVdJ/vlpjSo9VYNvtxGjRiOetKcBSqkJaINXiULk3/lP6E80gjuaKLdiIyfN5mhnJtALYOXTMFKCxI39uw==", "dev": true, "requires": { "async-stream-emitter": "1.1.0", @@ -95,7 +95,7 @@ }, "commander": { "version": "2.15.1", - "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true }, @@ -299,13 +299,13 @@ }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -347,7 +347,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, diff --git a/package.json b/package.json index ba6ac93..b3431ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.1.0", + "version": "1.1.1", "description": "Server module for Asyngular", "main": "index.js", "repository": { From 4cf4f567843eef6f600ed3ae9d71f403699060e6 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 7 Jan 2019 01:59:06 +0100 Subject: [PATCH 129/265] v1.1.2 --- package-lock.json | 448 ---------------------------------------------- package.json | 4 +- 2 files changed, 2 insertions(+), 450 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 2655c05..0000000 --- a/package-lock.json +++ /dev/null @@ -1,448 +0,0 @@ -{ - "name": "asyngular-server", - "version": "1.1.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", - "requires": { - "lodash": "4.17.11" - } - }, - "async-iterable-stream": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/async-iterable-stream/-/async-iterable-stream-3.0.1.tgz", - "integrity": "sha512-Y4/wTlwUsp3+S/Aiw4KOCh3s6t/ES1kU5erhMuUuvSVvhOTey3vxohks0KoeCslT5TtRg7MvpK6NVcCbT7r3CA==" - }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" - }, - "async-stream-emitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/async-stream-emitter/-/async-stream-emitter-1.1.0.tgz", - "integrity": "sha512-oteYCw7qY0LJeSemRx/bqirqhEaZMInMN730rbZ3dhM3E0huT4QAm8CstlYUPD+nEKgkH6HplZbkC8Scv6MbcQ==", - "requires": { - "stream-demux": "4.0.4" - } - }, - "asyngular-client": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/asyngular-client/-/asyngular-client-1.1.3.tgz", - "integrity": "sha512-15Q6wVdJ/vlpjSo9VYNvtxGjRiOetKcBSqkJaINXiULk3/lP6E80gjuaKLdiIyfN5mhnJtALYOXTMFKCxI39uw==", - "dev": true, - "requires": { - "async-stream-emitter": "1.1.0", - "base-64": "0.1.0", - "clone": "2.1.1", - "linked-list": "0.1.0", - "querystring": "0.2.0", - "sc-channel": "2.0.0", - "sc-errors": "2.0.0", - "sc-formatter": "3.0.2", - "stream-demux": "4.0.4", - "uuid": "3.2.1", - "ws": "6.1.2" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base-64": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", - "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=", - "dev": true - }, - "base64id": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", - "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" - }, - "clone": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", - "dev": true - }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "ecdsa-sig-formatter": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", - "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "jsonwebtoken": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.4.0.tgz", - "integrity": "sha512-coyXjRTCy0pw5WYBpMvWOMN+Kjaik2MwTUIq9cna/W7NpO9E+iYbumZONAz3hcr+tXFJECoQVrtmIoC3Oz0gvg==", - "requires": { - "jws": "3.1.5", - "lodash.includes": "4.3.0", - "lodash.isboolean": "3.0.3", - "lodash.isinteger": "4.0.4", - "lodash.isnumber": "3.0.3", - "lodash.isplainobject": "4.0.6", - "lodash.isstring": "4.0.1", - "lodash.once": "4.1.1", - "ms": "2.1.1" - } - }, - "jwa": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", - "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.10", - "safe-buffer": "5.1.2" - } - }, - "jws": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", - "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", - "requires": { - "jwa": "1.1.6", - "safe-buffer": "5.1.2" - } - }, - "linked-list": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz", - "integrity": "sha1-eYsP+X0bkqT9CEgPVa6k6dSdN78=", - "dev": true - }, - "localStorage": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/localStorage/-/localStorage-1.0.4.tgz", - "integrity": "sha512-r35zrihcDiX+dqWlJSeIwS9nrF95OQTgqMFm3FB2D/+XgdmZtcutZOb7t0xXkhOEM8a9kpuu7cc28g1g36I5DQ==", - "dev": true - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" - }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "dev": true, - "requires": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "sc-auth": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/sc-auth/-/sc-auth-6.0.0.tgz", - "integrity": "sha512-JSqG9CBrOIt3HRbpWlRfVOxwISqw9/CiqENfOsnm0Jf3WUZBmJarL/yP+NFks1D0hSuHa0OYW1qluEhYqrjaQw==", - "requires": { - "jsonwebtoken": "8.4.0", - "sc-errors": "2.0.0" - } - }, - "sc-channel": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-2.0.0.tgz", - "integrity": "sha512-XFAic96C3rbG736D/nRiOvhkDXi+K8GGVrGtdo1shggRfQXKHj2K1ajXOIDA8HxT91G6NdnWsgsokfE3YHw9DA==", - "requires": { - "async-iterable-stream": "3.0.1" - } - }, - "sc-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-2.0.0.tgz", - "integrity": "sha512-zLIg4GskHvkBM7gpKl7JrdU1FXVYsYCavsUeTILFIi/YsuOHLN9OTlFcMp6otb+ebpNEnpcDJI395YXZPif+fw==" - }, - "sc-formatter": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/sc-formatter/-/sc-formatter-3.0.2.tgz", - "integrity": "sha512-9PbqYBpCq+OoEeRQ3QfFIGE6qwjjBcd2j7UjgDlhnZbtSnuGgHdcRklPKYGuYFH82V/dwd+AIpu8XvA1zqTd+A==" - }, - "sc-simple-broker": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-3.2.1.tgz", - "integrity": "sha512-b3BOF835oce+NzSjMQRclQO0nqb+fmhxSi+tZy3I113KbN2WHhSkBexW8vB6G1bayBxTe0nJFfKlMsy82be/EQ==", - "requires": { - "async-stream-emitter": "1.1.0", - "sc-channel": "2.0.0", - "stream-demux": "4.0.4" - } - }, - "stream-demux": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stream-demux/-/stream-demux-4.0.4.tgz", - "integrity": "sha512-aSB1jS+0tPCnijI9BqEXbV9xC0JqkXnO869JIU4bC2isYMZizUdWm9g7NV0U9D4yaZ+K+HYtgI9XxAebOpkZjw==", - "requires": { - "async-iterable-stream": "3.0.1", - "writable-async-iterable-stream": "4.0.1" - } - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - }, - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "writable-async-iterable-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/writable-async-iterable-stream/-/writable-async-iterable-stream-4.0.1.tgz", - "integrity": "sha512-zhEBUFIQRtVi2zNyVRWBmFuNBPTYHqtyv8cOPtqDxU+h3IGre6vPpyCW60Es+uJ51EZ1+s7IjpJdvJ0rHMjbVw==", - "requires": { - "async-iterable-stream": "3.0.1" - } - }, - "ws": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.2.tgz", - "integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==", - "requires": { - "async-limiter": "1.0.0" - } - } - } -} diff --git a/package.json b/package.json index b3431ec..1e1f40a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.1.1", + "version": "1.1.2", "description": "Server module for Asyngular", "main": "index.js", "repository": { @@ -15,7 +15,7 @@ "sc-auth": "^6.0.0", "sc-errors": "^2.0.0", "sc-formatter": "^3.0.2", - "sc-simple-broker": "^3.2.1", + "sc-simple-broker": "^3.2.2", "stream-demux": "^4.0.4", "uuid": "3.2.1", "ws": "6.1.2" From 87fea5506cb6b9df24374b0a8947f3829891d0cc Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 8 Jan 2019 21:36:33 +0100 Subject: [PATCH 130/265] Fix error code in test --- test/integration.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/integration.js b/test/integration.js index f92967c..5d633bb 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1717,8 +1717,7 @@ describe('Integration tests', function () { assert.equal(eventList[1].channel, 'foo'); }); - it('Socket should emit an error when trying to unsubscribe to a channel which it is not subscribed to', async function () { - + it('Socket should emit an error when trying to unsubscribe from a channel which it is not subscribed to', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE @@ -1965,7 +1964,7 @@ describe('Integration tests', function () { await wait(1000); assert.notEqual(clientError, null); assert.equal(clientError.name, 'SocketProtocolError'); - assert.equal(clientDisconnectCode, 4001); + assert.equal(clientDisconnectCode, 4000); assert.notEqual(serverWarning, null); assert.equal(serverWarning.name, 'SocketProtocolError'); From 64c1d50946abcdfff2630ce54d34405e38e7b9ee Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 8 Jan 2019 21:37:03 +0100 Subject: [PATCH 131/265] v1.1.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1e1f40a..6fd306d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.1.2", + "version": "1.1.3", "description": "Server module for Asyngular", "main": "index.js", "repository": { From c0d4f6fcc7bb89d69c8f3829d21eab1bfaa126ff Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 8 Jan 2019 23:05:07 +0100 Subject: [PATCH 132/265] Punctuation fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 61ef946..22bdac6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Asyngular server -Minimal server module for Asyngular +Minimal server module for Asyngular. This is a stand-alone server module for Asyngular (SocketCluster with full async/await support). Asyngular's protocol is backwards compatible with the SocketCluster protocol. From fd4c536cc402848eb191fcaa090045d100c1733b Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 10 Jan 2019 20:00:54 +0100 Subject: [PATCH 133/265] New middleware format to allow keeping all async messages in-order --- action.js | 28 ++ agserver.js | 971 ++++++++++++-------------------------------- agserversocket.js | 496 +++++++++++++++------- package.json | 6 +- request.js | 38 ++ response.js | 55 --- test/integration.js | 596 ++++++++++++++------------- 7 files changed, 993 insertions(+), 1197 deletions(-) create mode 100644 action.js create mode 100644 request.js delete mode 100644 response.js diff --git a/action.js b/action.js new file mode 100644 index 0000000..05400c7 --- /dev/null +++ b/action.js @@ -0,0 +1,28 @@ +const scErrors = require('sc-errors'); +const InvalidActionError = scErrors.InvalidActionError; + +function Action() { + this.outcome = null; + this.promise = new Promise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + }); + + this.allow = (packet) => { + if (this.outcome) { + throw new InvalidActionError(`Action ${this.type} has already been ${this.outcome}; cannot allow`); + } + this.outcome = 'allowed'; + this._resolve(packet); + }; + + this.block = (error) => { + if (this.outcome) { + throw new InvalidActionError(`Action ${this.type} has already been ${this.outcome}; cannot block`); + } + this.outcome = 'blocked'; + this._reject(error); + }; +} + +module.exports = Action; diff --git a/agserver.js b/agserver.js index 5fd3a61..bd3141e 100644 --- a/agserver.js +++ b/agserver.js @@ -1,19 +1,16 @@ const AGServerSocket = require('./agserversocket'); -const AuthEngine = require('sc-auth').AuthEngine; +const AuthEngine = require('ag-auth'); const formatter = require('sc-formatter'); const base64id = require('base64id'); -const async = require('async'); const url = require('url'); const crypto = require('crypto'); const uuid = require('uuid'); -const SCSimpleBroker = require('sc-simple-broker').SCSimpleBroker; +const AGSimpleBroker = require('ag-simple-broker'); const AsyncStreamEmitter = require('async-stream-emitter'); +const WritableAsyncIterableStream = require('writable-async-iterable-stream'); +const Action = require('./action'); const scErrors = require('sc-errors'); -const AuthTokenExpiredError = scErrors.AuthTokenExpiredError; -const AuthTokenInvalidError = scErrors.AuthTokenInvalidError; -const AuthTokenNotBeforeError = scErrors.AuthTokenNotBeforeError; -const AuthTokenError = scErrors.AuthTokenError; const SilentMiddlewareBlockedError = scErrors.SilentMiddlewareBlockedError; const InvalidArgumentsError = scErrors.InvalidArgumentsError; const InvalidOptionsError = scErrors.InvalidOptionsError; @@ -21,11 +18,13 @@ const InvalidActionError = scErrors.InvalidActionError; const BrokerError = scErrors.BrokerError; const ServerProtocolError = scErrors.ServerProtocolError; +const HANDSHAKE_REJECTION_STATUS_CODE = 4008; + function AGServer(options) { AsyncStreamEmitter.call(this); let opts = { - brokerEngine: new SCSimpleBroker(), + brokerEngine: new AGSimpleBroker(), wsEngine: 'ws', wsEngineServerOptions: {}, maxPayload: null, @@ -39,35 +38,13 @@ function AGServer(options) { appName: uuid.v4(), path: '/socketcluster/', authDefaultExpiry: 86400, - authSignAsync: false, - authVerifyAsync: true, pubSubBatchDuration: null, - middlewareEmitWarnings: true + middlewareEmitFailures: true }; this.options = Object.assign(opts, options); - this.MIDDLEWARE_HANDSHAKE_WS = 'handshakeWS'; - this.MIDDLEWARE_HANDSHAKE_AG = 'handshakeAG'; - this.MIDDLEWARE_TRANSMIT = 'transmit'; - this.MIDDLEWARE_INVOKE = 'invoke'; - this.MIDDLEWARE_SUBSCRIBE = 'subscribe'; - this.MIDDLEWARE_PUBLISH_IN = 'publishIn'; - this.MIDDLEWARE_PUBLISH_OUT = 'publishOut'; - this.MIDDLEWARE_AUTHENTICATE = 'authenticate'; - - // Deprecated - this.MIDDLEWARE_PUBLISH = this.MIDDLEWARE_PUBLISH_IN; - this._middleware = {}; - this._middleware[this.MIDDLEWARE_HANDSHAKE_WS] = []; - this._middleware[this.MIDDLEWARE_HANDSHAKE_AG] = []; - this._middleware[this.MIDDLEWARE_TRANSMIT] = []; - this._middleware[this.MIDDLEWARE_INVOKE] = []; - this._middleware[this.MIDDLEWARE_SUBSCRIBE] = []; - this._middleware[this.MIDDLEWARE_PUBLISH_IN] = []; - this._middleware[this.MIDDLEWARE_PUBLISH_OUT] = []; - this._middleware[this.MIDDLEWARE_AUTHENTICATE] = []; this.origins = opts.origins; this._allowAllOrigins = this.origins.indexOf('*:*') !== -1; @@ -84,11 +61,17 @@ function AGServer(options) { this.brokerEngine = opts.brokerEngine; this.appName = opts.appName || ''; - this.middlewareEmitWarnings = opts.middlewareEmitWarnings; + this.middlewareEmitFailures = opts.middlewareEmitFailures; // Make sure there is always a leading and a trailing slash in the WS path. this._path = opts.path.replace(/\/?$/, '/').replace(/^\/?/, '/'); + (async () => { + for await (let {error} of this.brokerEngine.listener('error')) { + this.emitWarning(error); + } + })(); + if (this.brokerEngine.isReady) { this.isReady = true; this.emit('ready', {}); @@ -130,12 +113,7 @@ function AGServer(options) { this.verificationKey = opts.authKey; } - this.authVerifyAsync = opts.authVerifyAsync; - this.authSignAsync = opts.authSignAsync; - - this.defaultVerificationOptions = { - async: this.authVerifyAsync - }; + this.defaultVerificationOptions = {}; if (opts.authVerifyAlgorithms != null) { this.defaultVerificationOptions.algorithms = opts.authVerifyAlgorithms; } else if (opts.authAlgorithm != null) { @@ -143,8 +121,7 @@ function AGServer(options) { } this.defaultSignatureOptions = { - expiresIn: opts.authDefaultExpiry, - async: this.authSignAsync + expiresIn: opts.authDefaultExpiry }; if (opts.authAlgorithm != null) { this.defaultSignatureOptions.algorithm = opts.authAlgorithm; @@ -163,6 +140,8 @@ function AGServer(options) { // Default codec engine this.codec = formatter; } + this.brokerEngine.setCodecEngine(this.codec); + this.exchange = this.brokerEngine.exchange(); this.clients = {}; this.clientsCount = 0; @@ -170,8 +149,6 @@ function AGServer(options) { this.pendingClients = {}; this.pendingClientsCount = 0; - this.exchange = this.brokerEngine.exchange(); - let wsServerOptions = opts.wsEngineServerOptions || {}; wsServerOptions.server = this.httpServer; wsServerOptions.verifyClient = this.verifyHandshake.bind(this); @@ -200,12 +177,29 @@ function AGServer(options) { AGServer.prototype = Object.create(AsyncStreamEmitter.prototype); +AGServer.prototype.SYMBOL_MIDDLEWARE_INBOUND_STREAM = AGServer.SYMBOL_MIDDLEWARE_INBOUND_STREAM = Symbol('inboundStream'); +AGServer.prototype.SYMBOL_MIDDLEWARE_OUTBOUND_STREAM = AGServer.SYMBOL_MIDDLEWARE_OUTBOUND_STREAM = Symbol('outboundStream'); + +AGServer.prototype.MIDDLEWARE_INBOUND = AGServer.MIDDLEWARE_INBOUND = 'inbound'; +AGServer.prototype.MIDDLEWARE_OUTBOUND = AGServer.MIDDLEWARE_OUTBOUND = 'outbound'; + +AGServer.prototype.ACTION_HANDSHAKE_WS = AGServer.ACTION_HANDSHAKE_WS = 'handshakeWS'; +AGServer.prototype.ACTION_HANDSHAKE_AG = AGServer.ACTION_HANDSHAKE_AG = 'handshakeAG'; + +AGServer.prototype.ACTION_TRANSMIT = AGServer.ACTION_TRANSMIT = 'transmit'; +AGServer.prototype.ACTION_INVOKE = AGServer.ACTION_INVOKE = 'invoke'; +AGServer.prototype.ACTION_SUBSCRIBE = AGServer.ACTION_SUBSCRIBE = 'subscribe'; +AGServer.prototype.ACTION_PUBLISH_IN = AGServer.ACTION_PUBLISH_IN = 'publishIn'; +AGServer.prototype.ACTION_PUBLISH_OUT = AGServer.ACTION_PUBLISH_OUT = 'publishOut'; +AGServer.prototype.ACTION_AUTHENTICATE = AGServer.ACTION_AUTHENTICATE = 'authenticate'; + AGServer.prototype.setAuthEngine = function (authEngine) { this.auth = authEngine; }; AGServer.prototype.setCodecEngine = function (codecEngine) { this.codec = codecEngine; + this.brokerEngine.setCodecEngine(codecEngine); }; AGServer.prototype.emitError = function (error) { @@ -223,19 +217,12 @@ AGServer.prototype._handleServerError = function (error) { this.emitError(error); }; -AGServer.prototype._handleSocketErrors = async function (socket) { - // A socket error will show up as a warning on the server. - for await (let event of socket.listener('error')) { - this.emitWarning(event.error); - } -}; - -AGServer.prototype._handleHandshakeTimeout = function (scSocket) { - scSocket.disconnect(4005); +AGServer.prototype._handleHandshakeTimeout = function (agSocket) { + agSocket.disconnect(4005); }; -AGServer.prototype._subscribeSocket = async function (socket, channelOptions) { - if (!channelOptions) { +AGServer.prototype._subscribeSocket = async function (socket, channelName, subscriptionOptions) { + if (channelName === undefined || !subscriptionOptions) { throw new InvalidActionError(`Socket ${socket.id} provided a malformated channel payload`); } @@ -245,8 +232,6 @@ AGServer.prototype._subscribeSocket = async function (socket, channelOptions) { ); } - let channelName = channelOptions.channel; - if (typeof channelName !== 'string') { throw new InvalidActionError(`Socket ${socket.id} provided an invalid channel name`); } @@ -268,12 +253,12 @@ AGServer.prototype._subscribeSocket = async function (socket, channelOptions) { } socket.emit('subscribe', { channel: channelName, - subscribeOptions: channelOptions + subscriptionOptions }); this.emit('subscription', { socket, channel: channelName, - subscribeOptions: channelOptions + subscriptionOptions }); }; @@ -306,204 +291,86 @@ AGServer.prototype._unsubscribeSocket = function (socket, channel) { this.emit('unsubscription', {socket, channel}); }; -AGServer.prototype._processTokenError = function (err) { - let authError = null; - let isBadToken = true; - - if (err) { - if (err.name === 'TokenExpiredError') { - authError = new AuthTokenExpiredError(err.message, err.expiredAt); - } else if (err.name === 'JsonWebTokenError') { - authError = new AuthTokenInvalidError(err.message); - } else if (err.name === 'NotBeforeError') { - authError = new AuthTokenNotBeforeError(err.message, err.date); - // In this case, the token is good; it's just not active yet. - isBadToken = false; - } else { - authError = new AuthTokenError(err.message); - } - } - - return { - authError: authError, - isBadToken: isBadToken - }; -}; - -AGServer.prototype._emitBadAuthTokenError = function (scSocket, error, signedAuthToken) { - let badAuthStatus = { - authError: error, - signedAuthToken: signedAuthToken - }; - scSocket.emit('badAuthToken', { - authError: error, - signedAuthToken: signedAuthToken - }); - this.emit('badSocketAuthToken', { - socket: scSocket, - authError: error, - signedAuthToken: signedAuthToken - }); -}; - -AGServer.prototype._processAuthToken = function (scSocket, signedAuthToken, callback) { - let verificationOptions = Object.assign({socket: scSocket}, this.defaultVerificationOptions); - - let handleVerifyTokenResult = (result) => { - let err = result.error; - let token = result.token; - - let oldAuthState = scSocket.authState; - if (token) { - scSocket.signedAuthToken = signedAuthToken; - scSocket.authToken = token; - scSocket.authState = scSocket.AUTHENTICATED; - } else { - scSocket.signedAuthToken = null; - scSocket.authToken = null; - scSocket.authState = scSocket.UNAUTHENTICATED; - } - - // If the socket is authenticated, pass it through the MIDDLEWARE_AUTHENTICATE middleware. - // If the token is bad, we will tell the client to remove it. - // If there is an error but the token is good, then we will send back a 'quiet' error instead - // (as part of the status object only). - if (scSocket.authToken) { - this._passThroughAuthenticateMiddleware({ - socket: scSocket, - signedAuthToken: scSocket.signedAuthToken, - authToken: scSocket.authToken - }, (middlewareError, isBadToken) => { - if (middlewareError) { - scSocket.authToken = null; - scSocket.authState = scSocket.UNAUTHENTICATED; - if (isBadToken) { - this._emitBadAuthTokenError(scSocket, middlewareError, signedAuthToken); - } - } - // If an error is passed back from the authenticate middleware, it will be treated as a - // server warning and not a socket error. - callback(middlewareError, isBadToken || false, oldAuthState); - }); - } else { - let errorData = this._processTokenError(err); - - // If the error is related to the JWT being badly formatted, then we will - // treat the error as a socket error. - if (err && signedAuthToken != null) { - scSocket.emitError(errorData.authError); - if (errorData.isBadToken) { - this._emitBadAuthTokenError(scSocket, errorData.authError, signedAuthToken); - } - } - callback(errorData.authError, errorData.isBadToken, oldAuthState); - } - }; - - let verifyTokenResult; - let verifyTokenError; - - try { - verifyTokenResult = this.auth.verifyToken(signedAuthToken, this.verificationKey, verificationOptions); - } catch (err) { - verifyTokenError = err; - } - - if (verifyTokenResult instanceof Promise) { - (async () => { - let result = {}; - try { - result.token = await verifyTokenResult; - } catch (err) { - result.error = err; - } - handleVerifyTokenResult(result); - })(); - } else { - let result = { - token: verifyTokenResult, - error: verifyTokenError - }; - handleVerifyTokenResult(result); - } -}; - AGServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { if (!wsSocket.upgradeReq) { // Normalize ws modules to match. wsSocket.upgradeReq = upgradeReq; } - let id = this.generateId(); + let socketId = this.generateId(); - let scSocket = new AGServerSocket(id, this, wsSocket); - scSocket.exchange = this.exchange; + let agSocket = new AGServerSocket(socketId, this, wsSocket); + agSocket.exchange = this.exchange; - this._handleSocketErrors(scSocket); + let socketOutboundMiddleware = this._middleware[this.MIDDLEWARE_OUTBOUND]; + if (socketOutboundMiddleware) { + socketOutboundMiddleware(agSocket._middlewareOutboundStream); + } - this.pendingClients[id] = scSocket; + this.pendingClients[socketId] = agSocket; this.pendingClientsCount++; let handleSocketAuthenticate = async () => { - for await (let rpc of scSocket.procedure('#authenticate')) { + for await (let rpc of agSocket.procedure('#authenticate')) { let signedAuthToken = rpc.data; + let oldAuthState = agSocket.authState; + try { + await agSocket._processAuthToken(signedAuthToken); + } catch (error) { + if (error.isBadToken) { + agSocket.deauthenticate(); + rpc.error(error); - this._processAuthToken(scSocket, signedAuthToken, (err, isBadToken, oldAuthState) => { - if (err) { - if (isBadToken) { - scSocket.deauthenticate(); - } - } else { - scSocket.triggerAuthenticationEvents(oldAuthState); - } - if (err && isBadToken) { - rpc.error(err); - } else { - let authStatus = { - isAuthenticated: !!scSocket.authToken, - authError: scErrors.dehydrateError(err) - }; - rpc.end(authStatus); + return; } + + rpc.end({ + isAuthenticated: !!agSocket.authToken, + authError: signedAuthToken == null ? null : scErrors.dehydrateError(error) + }); + + return; + } + agSocket.triggerAuthenticationEvents(oldAuthState); + rpc.end({ + isAuthenticated: !!agSocket.authToken, + authError: null }); } }; handleSocketAuthenticate(); let handleSocketRemoveAuthToken = async () => { - for await (let data of scSocket.receiver('#removeAuthToken')) { - scSocket.deauthenticateSelf(); + for await (let data of agSocket.receiver('#removeAuthToken')) { + agSocket.deauthenticateSelf(); } }; handleSocketRemoveAuthToken(); let handleSocketSubscribe = async () => { - for await (let rpc of scSocket.procedure('#subscribe')) { - let channelOptions = rpc.data; - - if (!channelOptions) { - channelOptions = {}; - } else if (typeof channelOptions === 'string') { - channelOptions = { - channel: channelOptions - }; - } + for await (let rpc of agSocket.procedure('#subscribe')) { + let subscriptionOptions = Object.assign({}, rpc.data); + let channelName = subscriptionOptions.channel; + delete subscriptionOptions.channel; (async () => { - if (scSocket.state === scSocket.OPEN) { + if (agSocket.state === agSocket.OPEN) { try { - await this._subscribeSocket(scSocket, channelOptions); + await this._subscribeSocket(agSocket, channelName, subscriptionOptions); } catch (err) { - let error = new BrokerError(`Failed to subscribe socket to the ${channelOptions.channel} channel - ${err}`); + let error = new BrokerError(`Failed to subscribe socket to the ${channelName} channel - ${err}`); rpc.error(error); - scSocket.emitError(error); + agSocket.emitError(error); + return; } - if (channelOptions.batch) { + if (subscriptionOptions.batch) { rpc.end(undefined, {batch: true}); + return; } rpc.end(); + return; } // This is an invalid state; it means the client tried to subscribe before @@ -517,11 +384,11 @@ AGServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { handleSocketSubscribe(); let handleSocketUnsubscribe = async () => { - for await (let rpc of scSocket.procedure('#unsubscribe')) { + for await (let rpc of agSocket.procedure('#unsubscribe')) { let channel = rpc.data; let error; try { - this._unsubscribeSocket(scSocket, channel); + this._unsubscribeSocket(agSocket, channel); } catch (err) { error = new BrokerError( `Failed to unsubscribe socket from the ${channel} channel - ${err}` @@ -529,7 +396,7 @@ AGServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { } if (error) { rpc.error(error); - scSocket.emitError(error); + agSocket.emitError(error); } else { rpc.end(); } @@ -538,143 +405,148 @@ AGServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { handleSocketUnsubscribe(); let cleanupSocket = (type, code, reason) => { - clearTimeout(scSocket._handshakeTimeoutRef); + clearTimeout(agSocket._handshakeTimeoutRef); - scSocket.closeProcedure('#handshake'); - scSocket.closeProcedure('#authenticate'); - scSocket.closeProcedure('#subscribe'); - scSocket.closeProcedure('#unsubscribe'); - scSocket.closeReceiver('#removeAuthToken'); - scSocket.closeListener('authenticate'); - scSocket.closeListener('authStateChange'); - scSocket.closeListener('deauthenticate'); + agSocket.closeProcedure('#handshake'); + agSocket.closeProcedure('#authenticate'); + agSocket.closeProcedure('#subscribe'); + agSocket.closeProcedure('#unsubscribe'); + agSocket.closeReceiver('#removeAuthToken'); + agSocket.closeListener('authenticate'); + agSocket.closeListener('authStateChange'); + agSocket.closeListener('deauthenticate'); + agSocket._middlewareOutboundStream.close(); + agSocket._middlewareInboundStream.close(); - let isClientFullyConnected = !!this.clients[id]; + let isClientFullyConnected = !!this.clients[socketId]; if (isClientFullyConnected) { - delete this.clients[id]; + delete this.clients[socketId]; this.clientsCount--; } - let isClientPending = !!this.pendingClients[id]; + let isClientPending = !!this.pendingClients[socketId]; if (isClientPending) { - delete this.pendingClients[id]; + delete this.pendingClients[socketId]; this.pendingClientsCount--; } if (type === 'disconnect') { this.emit('disconnection', { - socket: scSocket, + socket: agSocket, code, reason }); } else if (type === 'abort') { this.emit('connectionAbort', { - socket: scSocket, + socket: agSocket, code, reason }); } this.emit('closure', { - socket: scSocket, + socket: agSocket, code, reason }); - this._unsubscribeSocketFromAllChannels(scSocket); + this._unsubscribeSocketFromAllChannels(agSocket); }; let handleSocketDisconnect = async () => { - let event = await scSocket.listener('disconnect').once(); + let event = await agSocket.listener('disconnect').once(); cleanupSocket('disconnect', event.code, event.data); }; handleSocketDisconnect(); let handleSocketAbort = async () => { - let event = await scSocket.listener('connectAbort').once(); + let event = await agSocket.listener('connectAbort').once(); cleanupSocket('abort', event.code, event.data); }; handleSocketAbort(); - scSocket._handshakeTimeoutRef = setTimeout(this._handleHandshakeTimeout.bind(this, scSocket), this.handshakeTimeout); + agSocket._handshakeTimeoutRef = setTimeout(this._handleHandshakeTimeout.bind(this, agSocket), this.handshakeTimeout); let handleSocketHandshake = async () => { - for await (let rpc of scSocket.procedure('#handshake')) { + for await (let rpc of agSocket.procedure('#handshake')) { let data = rpc.data || {}; let signedAuthToken = data.authToken || null; - clearTimeout(scSocket._handshakeTimeoutRef); - - this._passThroughHandshakeAGMiddleware({ - socket: scSocket - }, (err, statusCode) => { - if (err) { - if (err.statusCode == null) { - err.statusCode = statusCode; - } - rpc.error(err); - scSocket.disconnect(err.statusCode); - return; + clearTimeout(agSocket._handshakeTimeoutRef); + + let action = new Action(); + action.type = this.ACTION_HANDSHAKE_AG; + action.socket = agSocket; + + try { + await this._processMiddlewareAction(agSocket._middlewareInboundStream, action); + } catch (error) { + if (error.statusCode == null) { + error.statusCode = HANDSHAKE_REJECTION_STATUS_CODE; } - this._processAuthToken(scSocket, signedAuthToken, (err, isBadToken, oldAuthState) => { - if (scSocket.state === scSocket.CLOSED) { - return; - } + rpc.error(error); + agSocket.disconnect(error.statusCode); + return; + } - let clientSocketStatus = { - id: scSocket.id, - pingTimeout: this.pingTimeout - }; - let serverSocketStatus = { - id: scSocket.id, - pingTimeout: this.pingTimeout - }; - - if (err) { - if (signedAuthToken != null) { - // Because the token is optional as part of the handshake, we don't count - // it as an error if the token wasn't provided. - clientSocketStatus.authError = scErrors.dehydrateError(err); - serverSocketStatus.authError = err; - - if (isBadToken) { - scSocket.deauthenticate(); - } - } - } - clientSocketStatus.isAuthenticated = !!scSocket.authToken; - serverSocketStatus.isAuthenticated = clientSocketStatus.isAuthenticated; + let clientSocketStatus = { + id: socketId, + pingTimeout: this.pingTimeout + }; + let serverSocketStatus = { + id: socketId, + pingTimeout: this.pingTimeout + }; - if (this.pendingClients[id]) { - delete this.pendingClients[id]; - this.pendingClientsCount--; - } - this.clients[id] = scSocket; - this.clientsCount++; - - scSocket.state = scSocket.OPEN; - - if (clientSocketStatus.isAuthenticated) { - // Needs to be executed after the connection event to allow - // consumers to be setup from inside the connection loop. - (async () => { - await this.listener('connection').once(); - scSocket.triggerAuthenticationEvents(oldAuthState); - })(); + let oldAuthState = agSocket.authState; + try { + await agSocket._processAuthToken(signedAuthToken); + if (agSocket.state === agSocket.CLOSED) { + return; + } + } catch (error) { + if (signedAuthToken != null) { + // Because the token is optional as part of the handshake, we don't count + // it as an error if the token wasn't provided. + clientSocketStatus.authError = scErrors.dehydrateError(error); + serverSocketStatus.authError = error; + + if (error.isBadToken) { + agSocket.deauthenticate(); } + } + } + clientSocketStatus.isAuthenticated = !!agSocket.authToken; + serverSocketStatus.isAuthenticated = clientSocketStatus.isAuthenticated; + + if (this.pendingClients[socketId]) { + delete this.pendingClients[socketId]; + this.pendingClientsCount--; + } + this.clients[socketId] = agSocket; + this.clientsCount++; + + agSocket.state = agSocket.OPEN; + + if (clientSocketStatus.isAuthenticated) { + // Needs to be executed after the connection event to allow + // consumers to be setup from inside the connection loop. + (async () => { + await this.listener('connection').once(); + agSocket.triggerAuthenticationEvents(oldAuthState); + })(); + } - scSocket.emit('connect', serverSocketStatus); - this.emit('connection', {socket: scSocket, ...serverSocketStatus}); + agSocket.emit('connect', serverSocketStatus); + this.emit('connection', {socket: agSocket, ...serverSocketStatus}); - // Treat authentication failure as a 'soft' error - rpc.end(clientSocketStatus); - }); - }); + // Treat authentication failure as a 'soft' error + rpc.end(clientSocketStatus); } }; handleSocketHandshake(); // Emit event to signal that a socket handshake has been initiated. - this.emit('handshake', {socket: scSocket}); + this.emit('handshake', {socket: agSocket}); }; AGServer.prototype.close = function () { @@ -698,23 +570,71 @@ AGServer.prototype.generateId = function () { return base64id.generateId(); }; -AGServer.prototype.addMiddleware = function (type, middleware) { - if (!this._middleware[type]) { - throw new InvalidArgumentsError(`Middleware type "${type}" is not supported`); - // Read more: https://socketcluster.io/#!/docs/middleware-and-authorization +AGServer.prototype.setMiddleware = function (type, middleware) { + if ( + type !== this.MIDDLEWARE_INBOUND && + type !== this.MIDDLEWARE_OUTBOUND + ) { + throw new InvalidArgumentsError( + `Middleware type "${type}" is not supported` + ); } - this._middleware[type].push(middleware); + if (this._middleware[type]) { + throw new InvalidActionError(`Middleware type "${type}" has already been set`); + } + this._middleware[type] = middleware; }; -AGServer.prototype.removeMiddleware = function (type, middleware) { - let middlewareFunctions = this._middleware[type]; +AGServer.prototype.removeMiddleware = function (type) { + delete this._middleware[type]; +}; - this._middleware[type] = middlewareFunctions.filter((fn) => { - return fn !== middleware; - }); +AGServer.prototype.hasMiddleware = function (type) { + return !!this._middleware[type]; +}; + +AGServer.prototype._processMiddlewareAction = async function (middlewareStream, action, socket) { + if (!this.hasMiddleware(middlewareStream.type)) { + return {data: action.data, options: null}; + } + middlewareStream.write(action); + + let newData; + let options = null; + try { + let result = await action.promise; + if (result) { + newData = result.data; + options = result.options; + } + } catch (error) { + let clientError; + if (error.silent) { + clientError = new SilentMiddlewareBlockedError( + `Action was blocked by ${action.name} middleware`, + action.name + ); + } else { + clientError = error; + } + if (this.middlewareEmitFailures) { + if (socket) { + socket.emitError(error); + } else { + this.emitWarning(error); + } + } + throw clientError; + } + + if (newData === undefined) { + newData = action.data; + } + + return {data: newData, options}; }; -AGServer.prototype.verifyHandshake = function (info, callback) { +AGServer.prototype.verifyHandshake = async function (info, callback) { let req = info.req; let origin = info.origin; if (origin === 'null' || origin == null) { @@ -735,413 +655,32 @@ AGServer.prototype.verifyHandshake = function (info, callback) { } if (ok) { - let handshakeMiddleware = this._middleware[this.MIDDLEWARE_HANDSHAKE_WS]; - if (handshakeMiddleware.length) { - let callbackInvoked = false; - async.applyEachSeries(handshakeMiddleware, req, (err) => { - if (callbackInvoked) { - this.emitWarning( - new InvalidActionError( - `Callback for ${this.MIDDLEWARE_HANDSHAKE_WS} middleware was already invoked` - ) - ); - } else { - callbackInvoked = true; - if (err) { - if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError( - `Action was silently blocked by ${this.MIDDLEWARE_HANDSHAKE_WS} middleware`, - this.MIDDLEWARE_HANDSHAKE_WS - ); - } else if (this.middlewareEmitWarnings) { - this.emitWarning(err); - } - callback(false, 401, typeof err === 'string' ? err : err.message); - } else { - callback(true); - } - } - }); - } else { - callback(true); - } - } else { - let err = new ServerProtocolError( - `Failed to authorize socket handshake - Invalid origin: ${origin}` - ); - this.emitWarning(err); - callback(false, 403, err.message); - } -}; - -AGServer.prototype._isReservedRemoteEvent = function (event) { - return typeof event === 'string' && event.indexOf('#') === 0; -}; - -AGServer.prototype.verifyInboundRemoteEvent = function (requestOptions, callback) { - let socket = requestOptions.socket; - let token = socket.getAuthToken(); - if (this.isAuthTokenExpired(token)) { - requestOptions.authTokenExpiredError = new AuthTokenExpiredError( - 'The socket auth token has expired', - token.exp - ); - - socket.deauthenticate(); - } - - this._passThroughMiddleware(requestOptions, callback); -}; - -AGServer.prototype.isAuthTokenExpired = function (token) { - if (token && token.exp != null) { - let currentTime = Date.now(); - let expiryMilliseconds = token.exp * 1000; - return currentTime > expiryMilliseconds; - } - return false; -}; - -AGServer.prototype._processPublishAction = function (options, request, callback) { - let callbackInvoked = false; - - if (this.allowClientPublish) { - let eventData = options.data || {}; - request.channel = eventData.channel; - request.data = eventData.data; - - async.applyEachSeries(this._middleware[this.MIDDLEWARE_PUBLISH_IN], request, - (err) => { - if (callbackInvoked) { - this.emitWarning( - new InvalidActionError( - `Callback for ${this.MIDDLEWARE_PUBLISH_IN} middleware was already invoked` - ) - ); - } else { - callbackInvoked = true; - if (request.data !== undefined) { - eventData.data = request.data; - } - if (err) { - if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError( - `Action was silently blocked by ${this.MIDDLEWARE_PUBLISH_IN} middleware`, - this.MIDDLEWARE_PUBLISH_IN - ); - } else if (this.middlewareEmitWarnings) { - this.emitWarning(err); - } - callback(err, eventData, request.ackData); - } else { - if (typeof request.channel !== 'string') { - err = new BrokerError( - `Socket ${request.socket.id} tried to publish to an invalid ${request.channel} channel` - ); - this.emitWarning(err); - callback(err, eventData, request.ackData); - return; - } - (async () => { - let error; - try { - await this.exchange.publish(request.channel, request.data); - } catch (err) { - error = err; - this.emitWarning(error); - } - callback(error, eventData, request.ackData); - })(); - } - } - } - ); - } else { - let noPublishError = new InvalidActionError('Client publish feature is disabled'); - this.emitWarning(noPublishError); - callback(noPublishError); - } -}; - -AGServer.prototype._processSubscribeAction = function (options, request, callback) { - let callbackInvoked = false; - - let eventData = options.data || {}; - request.channel = eventData.channel; - request.waitForAuth = eventData.waitForAuth; - request.data = eventData.data; - - if (request.waitForAuth && request.authTokenExpiredError) { - // If the channel has the waitForAuth flag set, then we will handle the expiry quietly - // and we won't pass this request through the subscribe middleware. - callback(request.authTokenExpiredError, eventData); - } else { - async.applyEachSeries(this._middleware[this.MIDDLEWARE_SUBSCRIBE], request, - (err) => { - if (callbackInvoked) { - this.emitWarning( - new InvalidActionError( - `Callback for ${this.MIDDLEWARE_SUBSCRIBE} middleware was already invoked` - ) - ); - } else { - callbackInvoked = true; - if (err) { - if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError( - `Action was silently blocked by ${this.MIDDLEWARE_SUBSCRIBE} middleware`, - this.MIDDLEWARE_SUBSCRIBE - ); - } else if (this.middlewareEmitWarnings) { - this.emitWarning(err); - } - } - if (request.data !== undefined) { - eventData.data = request.data; - } - callback(err, eventData); - } - } - ); - } -}; - -AGServer.prototype._processTransmitAction = function (options, request, callback) { - let callbackInvoked = false; - - request.event = options.event; - request.data = options.data; - - async.applyEachSeries(this._middleware[this.MIDDLEWARE_TRANSMIT], request, - (err) => { - if (callbackInvoked) { - this.emitWarning( - new InvalidActionError( - `Callback for ${this.MIDDLEWARE_TRANSMIT} middleware was already invoked` - ) - ); - } else { - callbackInvoked = true; - if (err) { - if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError( - `Action was silently blocked by ${this.MIDDLEWARE_TRANSMIT} middleware`, - this.MIDDLEWARE_TRANSMIT - ); - } else if (this.middlewareEmitWarnings) { - this.emitWarning(err); - } - } - callback(err, request.data); - } - } - ); -}; - -AGServer.prototype._processInvokeAction = function (options, request, callback) { - let callbackInvoked = false; - - request.event = options.event; - request.data = options.data; + let middlewareInboundStream = new WritableAsyncIterableStream(); + middlewareInboundStream.type = this.MIDDLEWARE_INBOUND; + req[this.SYMBOL_MIDDLEWARE_INBOUND_STREAM] = middlewareInboundStream; - async.applyEachSeries(this._middleware[this.MIDDLEWARE_INVOKE], request, - (err) => { - if (callbackInvoked) { - this.emitWarning( - new InvalidActionError( - `Callback for ${this.MIDDLEWARE_INVOKE} middleware was already invoked` - ) - ); - } else { - callbackInvoked = true; - if (err) { - if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError( - `Action was silently blocked by ${this.MIDDLEWARE_INVOKE} middleware`, - this.MIDDLEWARE_INVOKE - ); - } else if (this.middlewareEmitWarnings) { - this.emitWarning(err); - } - } - callback(err, request.data); - } + let serverInboundMiddleware = this._middleware[this.MIDDLEWARE_INBOUND]; + if (serverInboundMiddleware) { + serverInboundMiddleware(middlewareInboundStream); } - ); -}; + let action = new Action(); + action.type = this.ACTION_HANDSHAKE_WS; + action.request = req; -AGServer.prototype._passThroughMiddleware = function (options, callback) { - let request = { - socket: options.socket - }; - - if (options.authTokenExpiredError != null) { - request.authTokenExpiredError = options.authTokenExpiredError; - } - - let event = options.event; - - if (options.cid == null) { - // If transmit. - if (this._isReservedRemoteEvent(event)) { - if (event === '#publish') { - this._processPublishAction(options, request, callback); - } else if (event === '#removeAuthToken') { - callback(null, options.data); - } else { - let error = new InvalidActionError(`The reserved transmitted event ${event} is not supported`); - callback(error); - } - } else { - this._processTransmitAction(options, request, callback); - } - } else { - // If invoke/RPC. - if (this._isReservedRemoteEvent(event)) { - if (event === '#subscribe') { - this._processSubscribeAction(options, request, callback); - } else if (event === '#publish') { - this._processPublishAction(options, request, callback); - } else if ( - event === '#handshake' || - event === '#authenticate' || - event === '#unsubscribe' - ) { - callback(null, options.data); - } else { - let error = new InvalidActionError(`The reserved invoked event ${event} is not supported`); - callback(error); - } - } else { - this._processInvokeAction(options, request, callback); + try { + await this._processMiddlewareAction(middlewareInboundStream, action); + } catch (error) { + callback(false, 401, typeof error === 'string' ? error : error.message); + return; } + callback(true); + return; } -}; - -AGServer.prototype._passThroughAuthenticateMiddleware = function (options, callback) { - let callbackInvoked = false; - - let request = { - socket: options.socket, - authToken: options.authToken - }; - - async.applyEachSeries(this._middleware[this.MIDDLEWARE_AUTHENTICATE], request, - (err, results) => { - if (callbackInvoked) { - this.emitWarning( - new InvalidActionError( - `Callback for ${this.MIDDLEWARE_AUTHENTICATE} middleware was already invoked` - ) - ); - } else { - callbackInvoked = true; - let isBadToken = false; - if (results.length) { - isBadToken = results[results.length - 1] || false; - } - if (err) { - if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError( - `Action was silently blocked by ${this.MIDDLEWARE_AUTHENTICATE} middleware`, - this.MIDDLEWARE_AUTHENTICATE - ); - } else if (this.middlewareEmitWarnings) { - this.emitWarning(err); - } - } - callback(err, isBadToken); - } - } - ); -}; - -AGServer.prototype._passThroughHandshakeAGMiddleware = function (options, callback) { - let callbackInvoked = false; - - let request = { - socket: options.socket - }; - - async.applyEachSeries(this._middleware[this.MIDDLEWARE_HANDSHAKE_AG], request, - (err, results) => { - if (callbackInvoked) { - this.emitWarning( - new InvalidActionError( - `Callback for ${this.MIDDLEWARE_HANDSHAKE_AG} middleware was already invoked` - ) - ); - } else { - callbackInvoked = true; - let statusCode; - if (results.length) { - statusCode = results[results.length - 1] || 4008; - } else { - statusCode = 4008; - } - if (err) { - if (err.statusCode != null) { - statusCode = err.statusCode; - } - if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError( - `Action was silently blocked by ${this.MIDDLEWARE_HANDSHAKE_AG} middleware`, - this.MIDDLEWARE_HANDSHAKE_AG - ); - } else if (this.middlewareEmitWarnings) { - this.emitWarning(err); - } - } - callback(err, statusCode); - } - } + let error = new ServerProtocolError( + `Failed to authorize socket handshake - Invalid origin: ${origin}` ); -}; - -AGServer.prototype.verifyOutboundEvent = function (socket, eventName, eventData, options, callback) { - let callbackInvoked = false; - - if (eventName === '#publish') { - let request = { - socket: socket, - channel: eventData.channel, - data: eventData.data - }; - async.applyEachSeries(this._middleware[this.MIDDLEWARE_PUBLISH_OUT], request, - (err) => { - if (callbackInvoked) { - this.emitWarning( - new InvalidActionError( - `Callback for ${this.MIDDLEWARE_PUBLISH_OUT} middleware was already invoked` - ) - ); - } else { - callbackInvoked = true; - if (request.data !== undefined) { - eventData.data = request.data; - } - if (err) { - if (err === true || err.silent) { - err = new SilentMiddlewareBlockedError( - `Action was silently blocked by ${this.MIDDLEWARE_PUBLISH_OUT} middleware`, - this.MIDDLEWARE_PUBLISH_OUT - ); - } else if (this.middlewareEmitWarnings) { - this.emitWarning(err); - } - callback(err, eventData); - } else { - if (options && request.useCache) { - options.useCache = true; - } - callback(null, eventData); - } - } - } - ); - } else { - callback(null, eventData); - } + this.emitWarning(error); + callback(false, 403, error.message); }; module.exports = AGServer; diff --git a/agserversocket.js b/agserversocket.js index 52077b8..5058b3d 100644 --- a/agserversocket.js +++ b/agserversocket.js @@ -1,7 +1,9 @@ const cloneDeep = require('lodash.clonedeep'); +const WritableAsyncIterableStream = require('writable-async-iterable-stream'); const StreamDemux = require('stream-demux'); const AsyncStreamEmitter = require('async-stream-emitter'); -const Response = require('./response').Response; +const Action = require('./action'); +const Request = require('./request'); const scErrors = require('sc-errors'); const InvalidArgumentsError = scErrors.InvalidArgumentsError; @@ -9,14 +11,15 @@ const SocketProtocolError = scErrors.SocketProtocolError; const TimeoutError = scErrors.TimeoutError; const InvalidActionError = scErrors.InvalidActionError; const AuthError = scErrors.AuthError; +const AuthTokenExpiredError = scErrors.AuthTokenExpiredError; +const AuthTokenInvalidError = scErrors.AuthTokenInvalidError; +const AuthTokenNotBeforeError = scErrors.AuthTokenNotBeforeError; +const AuthTokenError = scErrors.AuthTokenError; +const SilentMiddlewareBlockedError = scErrors.SilentMiddlewareBlockedError; function AGServerSocket(id, server, socket) { AsyncStreamEmitter.call(this); - this._autoAckRPCs = { - '#publish': 1 - }; - this.id = id; this.server = server; this.socket = socket; @@ -26,7 +29,12 @@ function AGServerSocket(id, server, socket) { this._receiverDemux = new StreamDemux(); this._procedureDemux = new StreamDemux(); - this.request = this.socket.upgradeReq || {}; + this.request = this.socket.upgradeReq; + + // TODO 2: Add MIDDLEWARE_INBOUND_RAW for applying middleware on raw messages. + this._middlewareInboundStream = this.request[this.server.SYMBOL_MIDDLEWARE_INBOUND_STREAM]; + this._middlewareOutboundStream = new WritableAsyncIterableStream(); + this._middlewareOutboundStream.type = this.server.MIDDLEWARE_OUTBOUND; if (this.request.connection) { this.remoteAddress = this.request.connection.remoteAddress; @@ -67,9 +75,9 @@ function AGServerSocket(id, server, socket) { this.emit('message', {message}); - let obj; + let packet; try { - obj = this.decode(message); + packet = this.decode(message); } catch (err) { if (err.name === 'Error') { err.name = 'InvalidMessageError'; @@ -79,19 +87,19 @@ function AGServerSocket(id, server, socket) { } // If pong - if (obj === '#2') { + if (packet === '#2') { let token = this.getAuthToken(); - if (this.server.isAuthTokenExpired(token)) { + if (this.isAuthTokenExpired(token)) { this.deauthenticate(); } } else { - if (Array.isArray(obj)) { - let len = obj.length; + if (Array.isArray(packet)) { + let len = packet.length; for (let i = 0; i < len; i++) { - this._handleRemoteEventObject(obj[i], message); + this._processInboundPacket(packet[i], message); } } else { - this._handleRemoteEventObject(obj, message); + this._processInboundPacket(packet, message); } } }); @@ -131,62 +139,154 @@ AGServerSocket.prototype._sendPing = function () { } }; -AGServerSocket.prototype._handleRemoteEventObject = function (obj, message) { - if (obj && obj.event != null) { - let eventName = obj.event; +AGServerSocket.prototype._processInboundPacket = async function (packet, message) { + if (packet && packet.event != null) { + let eventName = packet.event; + let isRPC = packet.cid != null; - let requestOptions = { - socket: this, - event: eventName, - data: obj.data, - }; + if (eventName === '#handshake' || eventName === '#authenticate') { + // Let AGServer handle these events. + let request = new Request(this, packet.cid, eventName, packet.data); + this._procedureDemux.write(eventName, request); + + return; + } + if (eventName === '#removeAuthToken') { + this._receiverDemux.write(eventName, packet.data); + + return; + } + + let tokenExpiredError = this._processAuthTokenExpiry(); + + let action = new Action(); + action.socket = this; + + let isPublish = eventName === '#publish'; + let isSubscribe = eventName === '#subscribe'; + + if (isPublish) { + if (!this.server.allowClientPublish) { + let error = new InvalidActionError('Client publish feature is disabled'); + this.emitError(error); - if (obj.cid == null) { - this.server.verifyInboundRemoteEvent(requestOptions, (err, newEventData) => { - if (!err) { - this._receiverDemux.write(eventName, newEventData); + if (isRPC) { + let request = new Request(this, packet.cid, eventName, packet.data); + request.error(error); } - }); + return; + } + action.type = this.server.ACTION_PUBLISH_IN; + if (packet.data) { + action.channel = packet.data.channel; + action.data = packet.data.data; + } + } else if (isSubscribe) { + action.type = this.server.ACTION_SUBSCRIBE; + if (packet.data) { + action.channel = packet.data.channel; + action.data = packet.data.data; + } + } else if (eventName === '#unsubscribe') { + // Let AGServer handle this event. + let request = new Request(this, packet.cid, eventName, packet.data); + this._procedureDemux.write(eventName, request); + + return; } else { - requestOptions.cid = obj.cid; - let response = new Response(this, requestOptions.cid); - this.server.verifyInboundRemoteEvent(requestOptions, (err, newEventData, ackData) => { - if (err) { - response.error(err); - } else { - if (this._autoAckRPCs[eventName]) { - if (ackData !== undefined) { - response.end(ackData); - } else { - response.end(); - } - } else { - this._procedureDemux.write(eventName, { - data: newEventData, - end: (data) => { - response.end(data); - }, - error: (err) => { - response.error(err); - } - }); - } + if (isRPC) { + action.type = this.server.ACTION_INVOKE; + action.procedure = packet.event; + if (packet.data !== undefined) { + action.data = packet.data; + } + } else { + action.type = this.server.ACTION_TRANSMIT; + action.receiver = packet.event; + if (packet.data !== undefined) { + action.data = packet.data; + } + } + } + + let newData; + + if (tokenExpiredError) { + action.authTokenExpiredError = tokenExpiredError; + } + + if (isRPC) { + let request = new Request(this, packet.cid, eventName, packet.data); + try { + let {data} = await this.server._processMiddlewareAction(this._middlewareInboundStream, action, this); + newData = data; + } catch (error) { + request.error(error); + + return; + } + + if (isPublish || isSubscribe) { + request.data = request.data || {}; + request.data.data = newData; + } else { + request.data = newData; + } + if (isPublish) { + let publishPacket = request.data || {}; + + if (typeof publishPacket.channel !== 'string') { + let error = new InvalidActionError(`Socket ${this.id} tried to publish to an invalid "${publishPacket.channel}" channel`); + this.emitError(error); + request.error(error); + + return; + } + + try { + await this.server.exchange.publish(publishPacket.channel, publishPacket.data); + } catch (error) { + this.emitError(error); + request.error(error); + + return; } - }); + request.end(); + + return; + } + + this._procedureDemux.write(eventName, request); + + return; + } + + try { + let {data} = await this.server._processMiddlewareAction(this._middlewareInboundStream, action, this); + newData = data; + } catch (error) { + + return; } - } else if (obj && obj.rid != null) { + + this._receiverDemux.write(eventName, newData); + + return; + } + + if (packet && packet.rid != null) { // If incoming message is a response to a previously sent message - let ret = this._callbackMap[obj.rid]; + let ret = this._callbackMap[packet.rid]; if (ret) { clearTimeout(ret.timeout); - delete this._callbackMap[obj.rid]; - let rehydratedError = scErrors.hydrateError(obj.error); - ret.callback(rehydratedError, obj.data); + delete this._callbackMap[packet.rid]; + let rehydratedError = scErrors.hydrateError(packet.error); + ret.callback(rehydratedError, packet.data); } - } else { - // The last remaining case is to treat the message as raw - this.emit('raw', {message}); + return; } + // The last remaining case is to treat the message as raw + this.emit('raw', {message}); }; AGServerSocket.prototype._resetPongTimeout = function () { @@ -213,9 +313,8 @@ AGServerSocket.prototype.getBytesReceived = function () { }; AGServerSocket.prototype.emitError = function (error) { - this.emit('error', { - error - }); + this.emit('error', {error}); + this.server.emitWarning(error); }; AGServerSocket.prototype._onClose = function (code, reason) { @@ -333,66 +432,85 @@ AGServerSocket.prototype.sendObject = function (object, options) { } }; -AGServerSocket.prototype.transmit = function (event, data, options) { - this.server.verifyOutboundEvent(this, event, data, options, (err, newData) => { +AGServerSocket.prototype.transmit = async function (event, data, options) { + let newData; + let useCache = options ? options.useCache : false; + let packet = {event, data}; + let isPublish = event === '#publish'; + if (isPublish) { + let action = new Action(); + action.type = this.server.ACTION_PUBLISH_OUT; + action.socket = this; + + if (data !== undefined) { + action.channel = data.channel; + action.data = data.data; + } + useCache = !this.server.hasMiddleware(this._middlewareOutboundStream.type); + + try { + let {data, options} = await this.server._processMiddlewareAction(this._middlewareOutboundStream, action, this); + newData = data; + useCache = options == null ? useCache : options.useCache; + } catch (error) { + + return; + } + } else { + newData = packet.data; + } + + if (options && useCache && options.stringifiedData != null) { + // Optimized + this.send(options.stringifiedData); + } else { let eventObject = { - event: event + event }; - if (newData !== undefined) { + if (isPublish) { + eventObject.data = data || {}; + eventObject.data.data = newData; + } else { eventObject.data = newData; } - if (!err) { - if (options && options.useCache && options.stringifiedData != null) { - // Optimized - this.send(options.stringifiedData); - } else { - this.sendObject(eventObject); - } - } - }); - return Promise.resolve(); + this.sendObject(eventObject); + } }; AGServerSocket.prototype.invoke = function (event, data, options) { return new Promise((resolve, reject) => { - this.server.verifyOutboundEvent(this, event, data, options, (err, newData) => { - if (err) { - reject(err); - return; - } - let eventObject = { - event: event, - cid: this._nextCallId() - }; - if (newData !== undefined) { - eventObject.data = newData; - } + let eventObject = { + event, + cid: this._nextCallId() + }; + if (data !== undefined) { + eventObject.data = data; + } - let timeout = setTimeout(() => { - let error = new TimeoutError(`Event response for "${event}" timed out`); - delete this._callbackMap[eventObject.cid]; - reject(error); - }, this.server.ackTimeout); - - this._callbackMap[eventObject.cid] = { - callback: (err, result) => { - if (err) { - reject(err); - return; - } - resolve(result); - }, - timeout: timeout - }; - - if (options && options.useCache && options.stringifiedData != null) { - // Optimized - this.send(options.stringifiedData); - } else { - this.sendObject(eventObject); - } - }); + let timeout = setTimeout(() => { + let error = new TimeoutError(`Event response for "${event}" timed out`); + delete this._callbackMap[eventObject.cid]; + reject(error); + }, this.server.ackTimeout); + + this._callbackMap[eventObject.cid] = { + callback: (err, result) => { + if (err) { + reject(err); + return; + } + resolve(result); + }, + timeout + }; + + if (options && options.useCache && options.stringifiedData != null) { + // Optimized + this.send(options.stringifiedData); + } else { + this.sendObject(eventObject); + } }); }; @@ -457,10 +575,6 @@ AGServerSocket.prototype.setAuthToken = async function (data, options) { options.expiresIn = expiresIn; } - // Always use the default sync/async signing mode since it cannot be changed at runtime. - if (defaultSignatureOptions.async != null) { - options.async = defaultSignatureOptions.async; - } // Always use the default algorithm since it cannot be changed at runtime. if (defaultSignatureOptions.algorithm != null) { options.algorithm = defaultSignatureOptions.algorithm; @@ -468,13 +582,6 @@ AGServerSocket.prototype.setAuthToken = async function (data, options) { this.authToken = authToken; - let handleAuthTokenSignFail = (error) => { - this.emitError(error); - this._onClose(4002, error.toString()); - this.socket.close(4002); - throw error; - }; - let sendAuthTokenToClient = async (signedToken) => { let tokenData = { token: signedToken @@ -486,24 +593,17 @@ AGServerSocket.prototype.setAuthToken = async function (data, options) { } }; - let signTokenResult; + let signedAuthToken; try { - signTokenResult = this.server.auth.signToken(authToken, this.server.signatureKey, options); - } catch (err) { - handleAuthTokenSignFail(err); + signedAuthToken = await this.server.auth.signToken(authToken, this.server.signatureKey, options); + } catch (error) { + this.emitError(error); + this._onClose(4002, error.toString()); + this.socket.close(4002); + throw error; } - let signedAuthToken; - if (signTokenResult instanceof Promise) { - try { - signedAuthToken = await signTokenResult; - } catch (err) { - handleAuthTokenSignFail(err); - } - } else { - signedAuthToken = signTokenResult; - } if (this.authToken === authToken) { this.signedAuthToken = signedAuthToken; this.emit('authTokenSigned', {signedAuthToken}); @@ -554,17 +654,9 @@ AGServerSocket.prototype.deauthenticate = function () { }; AGServerSocket.prototype.kickOut = function (channel, message) { - if (channel == null) { - Object.keys(this.channelSubscriptions).forEach((channelName) => { - delete this.channelSubscriptions[channelName]; - this.channelSubscriptionsCount--; - this.transmit('#kickOut', {message: message, channel: channelName}); - }); - } else { - delete this.channelSubscriptions[channel]; - this.channelSubscriptionsCount--; - this.transmit('#kickOut', {message: message, channel: channel}); - } + delete this.channelSubscriptions[channel]; + this.channelSubscriptionsCount--; + this.transmit('#kickOut', {channel, message}); return this.server.brokerEngine.unsubscribeSocket(this, channel); }; @@ -576,4 +668,112 @@ AGServerSocket.prototype.isSubscribed = function (channel) { return !!this.channelSubscriptions[channel]; }; +AGServerSocket.prototype._processAuthTokenExpiry = function () { + let token = this.getAuthToken(); + if (this.isAuthTokenExpired(token)) { + this.deauthenticate(); + + return new AuthTokenExpiredError( + 'The socket auth token has expired', + token.exp + ); + } + return null; +}; + +AGServerSocket.prototype.isAuthTokenExpired = function (token) { + if (token && token.exp != null) { + let currentTime = Date.now(); + let expiryMilliseconds = token.exp * 1000; + return currentTime > expiryMilliseconds; + } + return false; +}; + +AGServerSocket.prototype._processTokenError = function (err) { + if (err) { + if (err.name === 'TokenExpiredError') { + let authError = new AuthTokenExpiredError(err.message, err.expiredAt); + authError.isBadToken = true; + return authError; + } + if (err.name === 'JsonWebTokenError') { + let authError = new AuthTokenInvalidError(err.message); + authError.isBadToken = true; + return authError; + } + if (err.name === 'NotBeforeError') { + let authError = new AuthTokenNotBeforeError(err.message, err.date); + // In this case, the token is good; it's just not active yet. + authError.isBadToken = false; + return authError; + } + let authError = new AuthTokenError(err.message); + authError.isBadToken = true; + return authError; + } + return null; +}; + +AGServerSocket.prototype._emitBadAuthTokenError = function (error, signedAuthToken) { + this.emit('badAuthToken', { + authError: error, + signedAuthToken + }); + this.server.emit('badSocketAuthToken', { + socket: this, + authError: error, + signedAuthToken + }); +}; + +AGServerSocket.prototype._processAuthToken = async function (signedAuthToken) { + let verificationOptions = Object.assign({}, this.server.defaultVerificationOptions, { + socket: this + }); + let authToken; + + try { + authToken = await this.server.auth.verifyToken(signedAuthToken, this.server.verificationKey, verificationOptions); + } catch (error) { + this.signedAuthToken = null; + this.authToken = null; + this.authState = this.UNAUTHENTICATED; + + let authTokenError = this._processTokenError(error); + + // If the error is related to the JWT being badly formatted, then we will + // treat the error as a socket error. + if (error && signedAuthToken != null) { + this.emitError(authTokenError); + if (authTokenError.isBadToken) { + this._emitBadAuthTokenError(authTokenError, signedAuthToken); + } + } + throw authTokenError; + } + + this.signedAuthToken = signedAuthToken; + this.authToken = authToken; + this.authState = this.AUTHENTICATED; + + let action = new Action(); + action.type = this.server.ACTION_AUTHENTICATE; + action.socket = this; + action.signedAuthToken = this.signedAuthToken; + action.authToken = this.authToken; + + try { + await this.server._processMiddlewareAction(this._middlewareInboundStream, action, this); + } catch (error) { + this.authToken = null; + this.authState = this.UNAUTHENTICATED; + + if (error.isBadToken) { + this._emitBadAuthTokenError(error, signedAuthToken); + } + throw error; + } +}; + module.exports = AGServerSocket; diff --git a/package.json b/package.json index 6fd306d..4526b3c 100644 --- a/package.json +++ b/package.json @@ -8,16 +8,16 @@ "url": "git://github.com/SocketCluster/asyngular-server.git" }, "dependencies": { - "async": "2.6.1", + "ag-auth": "^1.0.0", + "ag-simple-broker": "^1.1.0", "async-stream-emitter": "^1.1.0", "base64id": "1.0.0", "lodash.clonedeep": "4.5.0", - "sc-auth": "^6.0.0", "sc-errors": "^2.0.0", "sc-formatter": "^3.0.2", - "sc-simple-broker": "^3.2.2", "stream-demux": "^4.0.4", "uuid": "3.2.1", + "writable-async-iterable-stream": "^4.0.1", "ws": "6.1.2" }, "devDependencies": { diff --git a/request.js b/request.js new file mode 100644 index 0000000..55199ab --- /dev/null +++ b/request.js @@ -0,0 +1,38 @@ +const scErrors = require('sc-errors'); +const InvalidActionError = scErrors.InvalidActionError; + +function Request(socket, id, procedureName, data) { + this.socket = socket; + this.id = id; + this.procedure = procedureName; + this.data = data; + this.sent = false; + + this._respond = (responseData, options) => { + if (this.sent) { + throw new InvalidActionError(`Response to request ${this.id} has already been sent`); + } + this.sent = true; + this.socket.sendObject(responseData, options); + }; + + this.end = (data, options) => { + let responseData = { + rid: this.id + }; + if (data !== undefined) { + responseData.data = data; + } + this._respond(responseData, options); + }; + + this.error = (error, options) => { + let responseData = { + rid: this.id, + error: scErrors.dehydrateError(error) + }; + this._respond(responseData, options); + }; +} + +module.exports = Request; diff --git a/response.js b/response.js deleted file mode 100644 index 0e20a13..0000000 --- a/response.js +++ /dev/null @@ -1,55 +0,0 @@ -const scErrors = require('sc-errors'); -const InvalidActionError = scErrors.InvalidActionError; - -function Response(socket, id) { - this.socket = socket; - this.id = id; - this.sent = false; -} - -Response.prototype._respond = function (responseData, options) { - if (this.sent) { - throw new InvalidActionError(`Response ${this.id} has already been sent`); - } else { - this.sent = true; - this.socket.sendObject(responseData, options); - } -}; - -Response.prototype.end = function (data, options) { - if (this.id) { - let responseData = { - rid: this.id - }; - if (data !== undefined) { - responseData.data = data; - } - this._respond(responseData, options); - } -}; - -Response.prototype.error = function (error, data, options) { - if (this.id) { - let err = scErrors.dehydrateError(error); - - let responseData = { - rid: this.id, - error: err - }; - if (data !== undefined) { - responseData.data = data; - } - - this._respond(responseData, options); - } -}; - -Response.prototype.callback = function (error, data, options) { - if (error) { - this.error(error, data, options); - } else { - this.end(data, options); - } -}; - -module.exports.Response = Response; diff --git a/test/integration.js b/test/integration.js index 5d633bb..8c12dbb 100644 --- a/test/integration.js +++ b/test/integration.js @@ -2,12 +2,11 @@ const assert = require('assert'); const asyngularServer = require('../'); const asyngularClient = require('asyngular-client'); const localStorage = require('localStorage'); -const SCSimpleBroker = require('sc-simple-broker').SCSimpleBroker; +const AGSimpleBroker = require('ag-simple-broker'); // Add to the global scope like in browser. global.localStorage = localStorage; - let clientOptions; let serverOptions; @@ -17,8 +16,11 @@ let allowedUsers = { }; const PORT_NUMBER = 8008; -const TEN_DAYS_IN_SECONDS = 60 * 60 * 24 * 10; const WS_ENGINE = 'ws'; +const LOG_WARNINGS = false; +const LOG_ERRORS = false; + +const TEN_DAYS_IN_SECONDS = 60 * 60 * 24 * 10; let validSignedAuthTokenBob = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImJvYiIsImV4cCI6MzE2Mzc1ODk3OTA4MDMxMCwiaWF0IjoxNTAyNzQ3NzQ2fQ.dSZOfsImq4AvCu-Or3Fcmo7JNv1hrV3WqxaiSKkTtAo'; let validSignedAuthTokenAlice = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNTE4NzI4MjU5LCJleHAiOjMxNjM3NTg5NzkwODAzMTB9.XxbzPPnnXrJfZrS0FJwb_EAhIu2VY5i7rGyUThtNLh4'; @@ -42,7 +44,7 @@ async function resolveAfterTimeout(duration, value) { function connectionHandler(socket) { (async () => { for await (let rpc of socket.procedure('login')) { - if (allowedUsers[rpc.data.username]) { + if (rpc.data && allowedUsers[rpc.data.username]) { socket.setAuthToken(rpc.data); rpc.end(); } else { @@ -125,7 +127,35 @@ function connectionHandler(socket) { })(); }; +function bindFailureHandlers(server) { + if (LOG_ERRORS) { + (async () => { + for await (let {error} of server.listener('error')) { + console.error('ERROR', error); + } + })(); + } + if (LOG_WARNINGS) { + (async () => { + for await (let {warning} of server.listener('warning')) { + console.warn('WARNING', warning); + } + })(); + } +} + describe('Integration tests', function () { + beforeEach('Prepare options', async function () { + clientOptions = { + hostname: '127.0.0.1', + port: PORT_NUMBER + }; + serverOptions = { + authKey: 'testkey', + wsEngine: WS_ENGINE + }; + }); + afterEach('Close server and client after each test', async function () { if (client) { client.closeAllListeners(); @@ -141,16 +171,23 @@ describe('Integration tests', function () { describe('Client authentication', function () { beforeEach('Run the server before start', async function () { - clientOptions = { - hostname: '127.0.0.1', - port: PORT_NUMBER - }; - serverOptions = { - authKey: 'testkey', - wsEngine: WS_ENGINE - }; - server = asyngularServer.listen(PORT_NUMBER, serverOptions); + bindFailureHandlers(server); + + server.setMiddleware(server.MIDDLEWARE_INBOUND, async (middlewareStream) => { + for await (let action of middlewareStream) { + if ( + action.type === server.ACTION_AUTHENTICATE && + (!action.authToken || action.authToken.username === 'alice') + ) { + let err = new Error('Blocked by MIDDLEWARE_INBOUND'); + err.name = 'AuthenticateMiddlewareError'; + action.block(err); + continue; + } + action.allow(); + } + }); (async () => { for await (let {socket} of server.listener('connection')) { @@ -158,14 +195,6 @@ describe('Integration tests', function () { } })(); - server.addMiddleware(server.MIDDLEWARE_AUTHENTICATE, async function (req) { - if (req.authToken.username === 'alice') { - let err = new Error('Blocked by MIDDLEWARE_AUTHENTICATE'); - err.name = 'AuthenticateMiddlewareError'; - throw err; - } - }); - await server.listener('ready').once(); }); @@ -319,15 +348,14 @@ describe('Integration tests', function () { assert.equal(authenticationStateChangeEvents[1].authToken, null); }); - it('Should not authenticate the client if MIDDLEWARE_AUTHENTICATE blocks the authentication', async function () { + it('Should not authenticate the client if MIDDLEWARE_INBOUND blocks the authentication', async function () { global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenAlice); client = asyngularClient.create(clientOptions); // The previous test authenticated us as 'alice', so that token will be passed to the server as // part of the handshake. - let event = await client.listener('connect').once(); - // Any token containing the username 'alice' should be blocked by the MIDDLEWARE_AUTHENTICATE middleware. + // Any token containing the username 'alice' should be blocked by the MIDDLEWARE_INBOUND middleware. // This will only affects token-based authentication, not the credentials-based login event. assert.equal(event.isAuthenticated, false); assert.notEqual(event.authError, null); @@ -336,12 +364,12 @@ describe('Integration tests', function () { }); describe('Server authentication', function () { - it('Token should be available after Promise resolves if token engine signing is synchronous', async function () { + it('Token should be available after the authenticate listener resolves', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, - wsEngine: WS_ENGINE, - authSignAsync: false + wsEngine: WS_ENGINE }); + bindFailureHandlers(server); (async () => { for await (let {socket} of server.listener('connection')) { @@ -366,12 +394,12 @@ describe('Integration tests', function () { assert.equal(client.authToken.username, 'bob'); }); - it('If token engine signing is asynchronous, authentication can be captured using the authenticate event', async function () { + it('Authentication can be captured using the authenticate listener', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, - wsEngine: WS_ENGINE, - authSignAsync: true + wsEngine: WS_ENGINE }); + bindFailureHandlers(server); (async () => { for await (let {socket} of server.listener('connection')) { @@ -396,12 +424,12 @@ describe('Integration tests', function () { assert.equal(client.authToken.username, 'bob'); }); - it('Should still work if token verification is asynchronous', async function () { + it('Previously authenticated client should still be authenticated after reconnecting', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, - wsEngine: WS_ENGINE, - authVerifyAsync: false + wsEngine: WS_ENGINE }); + bindFailureHandlers(server); (async () => { for await (let {socket} of server.listener('connection')) { @@ -435,9 +463,9 @@ describe('Integration tests', function () { it('Should set the correct expiry when using expiresIn option when creating a JWT with socket.setAuthToken', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, - wsEngine: WS_ENGINE, - authVerifyAsync: false + wsEngine: WS_ENGINE }); + bindFailureHandlers(server); (async () => { for await (let {socket} of server.listener('connection')) { @@ -467,9 +495,9 @@ describe('Integration tests', function () { it('Should set the correct expiry when adding exp claim when creating a JWT with socket.setAuthToken', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, - wsEngine: WS_ENGINE, - authVerifyAsync: false + wsEngine: WS_ENGINE }); + bindFailureHandlers(server); (async () => { for await (let {socket} of server.listener('connection')) { @@ -499,9 +527,9 @@ describe('Integration tests', function () { it('The exp claim should have priority over expiresIn option when using socket.setAuthToken', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, - wsEngine: WS_ENGINE, - authVerifyAsync: false + wsEngine: WS_ENGINE }); + bindFailureHandlers(server); (async () => { for await (let {socket} of server.listener('connection')) { @@ -531,9 +559,10 @@ describe('Integration tests', function () { it('Should send back error if socket.setAuthToken tries to set both iss claim and issuer option', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, - wsEngine: WS_ENGINE, - authVerifyAsync: false + wsEngine: WS_ENGINE }); + bindFailureHandlers(server); + let warningMap = {}; (async () => { @@ -598,9 +627,9 @@ describe('Integration tests', function () { it('Should trigger an authTokenSigned event and socket.signedAuthToken should be set after calling the socket.setAuthToken method', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, - wsEngine: WS_ENGINE, - authSignAsync: true + wsEngine: WS_ENGINE }); + bindFailureHandlers(server); let authTokenSignedEventEmitted = false; @@ -617,7 +646,7 @@ describe('Integration tests', function () { (async () => { for await (let req of socket.procedure('login')) { if (allowedUsers[req.data.username]) { - socket.setAuthToken(req.data, {async: true}); + socket.setAuthToken(req.data); req.end(); } else { let err = new Error('Failed to login'); @@ -643,13 +672,13 @@ describe('Integration tests', function () { assert.equal(authTokenSignedEventEmitted, true); }); - it('Should reject Promise returned by socket.setAuthToken if token delivery fails and rejectOnFailedDelivery option is true', async function () { + it('The socket.setAuthToken call should reject if token delivery fails and rejectOnFailedDelivery option is true', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, - authSignAsync: true, ackTimeout: 1000 }); + bindFailureHandlers(server); let socketErrors = []; @@ -693,13 +722,13 @@ describe('Integration tests', function () { } }); - it('Should not reject Promise returned by socket.setAuthToken if token delivery fails and rejectOnFailedDelivery option is not true', async function () { + it('The socket.setAuthToken call should not reject if token delivery fails and rejectOnFailedDelivery option is not true', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, - authSignAsync: true, ackTimeout: 1000 }); + bindFailureHandlers(server); let socketErrors = []; @@ -749,6 +778,7 @@ describe('Integration tests', function () { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); (async () => { for await (let {socket} of server.listener('connection')) { @@ -784,6 +814,8 @@ describe('Integration tests', function () { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); + server.setAuthEngine({ verifyToken: function (signedAuthToken, verificationKey, verificationOptions) { return resolveAfterTimeout(500, {}); @@ -830,6 +862,7 @@ describe('Integration tests', function () { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); (async () => { for await (let {socket} of server.listener('connection')) { @@ -855,6 +888,7 @@ describe('Integration tests', function () { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); let connectionEmitted = false; let connectionEvent; @@ -939,6 +973,8 @@ describe('Integration tests', function () { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); + server.setAuthEngine({ verifyToken: function (signedAuthToken, verificationKey, verificationOptions) { return resolveAfterTimeout(500, {}); @@ -1015,6 +1051,8 @@ describe('Integration tests', function () { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); + server.setAuthEngine({ verifyToken: function (signedAuthToken, verificationKey, verificationOptions) { return resolveAfterTimeout(10, {}); @@ -1092,6 +1130,8 @@ describe('Integration tests', function () { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); + server.setAuthEngine({ verifyToken: function (signedAuthToken, verificationKey, verificationOptions) { return resolveAfterTimeout(500, {}); @@ -1152,6 +1192,8 @@ describe('Integration tests', function () { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); + server.setAuthEngine({ verifyToken: function (signedAuthToken, verificationKey, verificationOptions) { return resolveAfterTimeout(0, {}); @@ -1214,6 +1256,7 @@ describe('Integration tests', function () { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); (async () => { for await (let {socket} of server.listener('connection')) { @@ -1256,6 +1299,7 @@ describe('Integration tests', function () { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); (async () => { await wait(10); @@ -1284,6 +1328,7 @@ describe('Integration tests', function () { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); (async () => { for await (let {socket} of server.listener('connection')) { @@ -1307,19 +1352,23 @@ describe('Integration tests', function () { // Each subscription should pass through the middleware individually, even // though they were sent as a batch/array. - server.addMiddleware(server.MIDDLEWARE_SUBSCRIBE, function (req, next) { - subscribeMiddlewareCounter++; - assert.equal(req.channel.indexOf('my-channel-'), 0); - if (req.channel === 'my-channel-10') { - assert.equal(JSON.stringify(req.data), JSON.stringify({foo: 123})); - } else if (req.channel === 'my-channel-12') { - // Block my-channel-12 - let err = new Error('You cannot subscribe to channel 12'); - err.name = 'UnauthorizedSubscribeError'; - next(err); - return; + server.setMiddleware(server.MIDDLEWARE_INBOUND, async function (middlewareStream) { + for await (let action of middlewareStream) { + if (action.type === server.ACTION_SUBSCRIBE) { + subscribeMiddlewareCounter++; + assert.equal(action.channel.indexOf('my-channel-'), 0); + if (action.channel === 'my-channel-10') { + assert.equal(JSON.stringify(action.data), JSON.stringify({foo: 123})); + } else if (action.channel === 'my-channel-12') { + // Block my-channel-12 + let err = new Error('You cannot subscribe to channel 12'); + err.name = 'UnauthorizedSubscribeError'; + action.block(err); + continue; + } + } + action.allow(); } - next(); }); await server.listener('ready').once(); @@ -1331,14 +1380,14 @@ describe('Integration tests', function () { let channelList = []; for (let i = 0; i < 20; i++) { - let subscribeOptions = { + let subscriptionOptions = { batch: true }; if (i === 10) { - subscribeOptions.data = {foo: 123}; + subscriptionOptions.data = {foo: 123}; } channelList.push( - client.subscribe('my-channel-' + i, subscribeOptions) + client.subscribe('my-channel-' + i, subscriptionOptions) ); } @@ -1356,7 +1405,7 @@ describe('Integration tests', function () { })(); (async () => { - for await (let event of channelList[0].listener('subscribe')) { + for await (let event of channelList[19].listener('subscribe')) { client.publish('my-channel-19', 'Hello!'); } })(); @@ -1373,6 +1422,7 @@ describe('Integration tests', function () { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); server.setAuthEngine({ verifyToken: function (signedAuthToken, verificationKey, verificationOptions) { @@ -1427,6 +1477,7 @@ describe('Integration tests', function () { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); (async () => { for await (let {socket} of server.listener('connection')) { @@ -1525,11 +1576,12 @@ describe('Integration tests', function () { assert.notEqual(nullPublishError, null); }); - it('When default SCSimpleBroker broker engine is used, disconnect event should trigger before unsubscribe event', async function () { + it('When default AGSimpleBroker broker engine is used, disconnect event should trigger before unsubscribe event', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); let eventList = []; @@ -1570,11 +1622,12 @@ describe('Integration tests', function () { assert.equal(eventList[1].channel, 'foo'); }); - it('When default SCSimpleBroker broker engine is used, agServer.exchange should support consuming data from a channel', async function () { + it('When default AGSimpleBroker broker engine is used, agServer.exchange should support consuming data from a channel', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); await server.listener('ready').once(); @@ -1618,11 +1671,12 @@ describe('Integration tests', function () { assert.equal(receivedChannelData[1], 'hi2'); }); - it('When default SCSimpleBroker broker engine is used, agServer.exchange should support publishing data to a channel', async function () { + it('When default AGSimpleBroker broker engine is used, agServer.exchange should support publishing data to a channel', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); await server.listener('ready').once(); @@ -1663,7 +1717,7 @@ describe('Integration tests', function () { }); it('When disconnecting a socket, the unsubscribe event should trigger after the disconnect event', async function () { - let customBrokerEngine = new SCSimpleBroker(); + let customBrokerEngine = new AGSimpleBroker(); let defaultUnsubscribeSocket = customBrokerEngine.unsubscribeSocket; customBrokerEngine.unsubscribeSocket = function (socket, channel) { return resolveAfterTimeout(100, defaultUnsubscribeSocket.call(this, socket, channel)); @@ -1674,6 +1728,7 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE, brokerEngine: customBrokerEngine }); + bindFailureHandlers(server); let eventList = []; @@ -1722,6 +1777,7 @@ describe('Integration tests', function () { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); let errorList = []; @@ -1757,7 +1813,7 @@ describe('Integration tests', function () { }); it('Socket should not receive messages from a channel which it has only just unsubscribed from (accounting for delayed unsubscribe by brokerEngine)', async function () { - let customBrokerEngine = new SCSimpleBroker(); + let customBrokerEngine = new AGSimpleBroker(); let defaultUnsubscribeSocket = customBrokerEngine.unsubscribeSocket; customBrokerEngine.unsubscribeSocket = function (socket, channel) { return resolveAfterTimeout(300, defaultUnsubscribeSocket.call(this, socket, channel)); @@ -1768,6 +1824,7 @@ describe('Integration tests', function () { wsEngine: WS_ENGINE, brokerEngine: customBrokerEngine }); + bindFailureHandlers(server); (async () => { for await (let {socket} of server.listener('connection')) { @@ -1813,11 +1870,11 @@ describe('Integration tests', function () { }); it('Socket channelSubscriptions and channelSubscriptionsCount should update when socket.kickOut(channel) is called', async function () { - server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); let errorList = []; let serverSocket; @@ -1860,56 +1917,6 @@ describe('Integration tests', function () { assert.equal(serverSocket.channelSubscriptionsCount, 0); assert.equal(Object.keys(serverSocket.channelSubscriptions).length, 0); }); - - it('Socket channelSubscriptions and channelSubscriptionsCount should update when socket.kickOut() is called without arguments', async function () { - - server = asyngularServer.listen(PORT_NUMBER, { - authKey: serverOptions.authKey, - wsEngine: WS_ENGINE - }); - - let errorList = []; - let serverSocket; - let wasKickOutCalled = false; - - (async () => { - for await (let {socket} of server.listener('connection')) { - serverSocket = socket; - - (async () => { - for await (let {error} of socket.listener('error')) { - errorList.push(error); - } - })(); - - (async () => { - for await (let event of socket.listener('subscribe')) { - if (socket.channelSubscriptionsCount === 2) { - await wait(50); - wasKickOutCalled = true; - socket.kickOut(); - } - } - })(); - } - })(); - - await server.listener('ready').once(); - - client = asyngularClient.create({ - hostname: clientOptions.hostname, - port: PORT_NUMBER - }); - - client.subscribe('foo'); - client.subscribe('bar'); - - await wait(200); - assert.equal(errorList.length, 0); - assert.equal(wasKickOutCalled, true); - assert.equal(serverSocket.channelSubscriptionsCount, 0); - assert.equal(Object.keys(serverSocket.channelSubscriptions).length, 0); - }); }); describe('Socket Ping/pong', function () { @@ -1923,6 +1930,7 @@ describe('Integration tests', function () { pingInterval: 2000, pingTimeout: 500 }); + bindFailureHandlers(server); await server.listener('ready').once(); }); @@ -1964,11 +1972,11 @@ describe('Integration tests', function () { await wait(1000); assert.notEqual(clientError, null); assert.equal(clientError.name, 'SocketProtocolError'); - assert.equal(clientDisconnectCode, 4000); + assert.equal(clientDisconnectCode === 4000 || clientDisconnectCode === 4001, true); assert.notEqual(serverWarning, null); assert.equal(serverWarning.name, 'SocketProtocolError'); - assert.equal(serverDisconnectionCode, 4001); + assert.equal(clientDisconnectCode === 4000 || clientDisconnectCode === 4001, true); }); }); @@ -1983,6 +1991,7 @@ describe('Integration tests', function () { pingTimeout: 500, pingTimeoutDisabled: true }); + bindFailureHandlers(server); await server.listener('ready').once(); }); @@ -2041,190 +2050,227 @@ describe('Integration tests', function () { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); + bindFailureHandlers(server); + await server.listener('ready').once(); }); - describe('MIDDLEWARE_AUTHENTICATE', function () { - it('Should not run authenticate middleware if JWT token does not exist', async function () { - middlewareFunction = async function (req) { - middlewareWasExecuted = true; - }; - server.addMiddleware(server.MIDDLEWARE_AUTHENTICATE, middlewareFunction); + describe('MIDDLEWARE_INBOUND', function () { + describe('ACTION_AUTHENTICATE', function () { + it('Should not run ACTION_AUTHENTICATE middleware action if JWT token does not exist', async function () { + middlewareFunction = async function (middlewareStream) { + for await (let {type, allow} of middlewareStream) { + if (type === server.ACTION_AUTHENTICATE) { + middlewareWasExecuted = true; + } + allow(); + } + }; + server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - client = asyngularClient.create({ - hostname: clientOptions.hostname, - port: PORT_NUMBER + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + await client.listener('connect').once(); + assert.notEqual(middlewareWasExecuted, true); }); - await client.listener('connect').once(); - assert.notEqual(middlewareWasExecuted, true); - }); + it('Should run ACTION_AUTHENTICATE middleware action if JWT token exists', async function () { + global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); + + middlewareFunction = async function (middlewareStream) { + for await (let {type, allow} of middlewareStream) { + if (type === server.ACTION_AUTHENTICATE) { + middlewareWasExecuted = true; + } + allow(); + } + }; + server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - it('Should run authenticate middleware if JWT token exists', async function () { - global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); - middlewareFunction = async function (req) { - middlewareWasExecuted = true; - }; - server.addMiddleware(server.MIDDLEWARE_AUTHENTICATE, middlewareFunction); + (async () => { + try { + await client.invoke('login', {username: 'bob'}); + } catch (err) {} + })(); - client = asyngularClient.create({ - hostname: clientOptions.hostname, - port: PORT_NUMBER + await client.listener('authenticate').once(); + assert.equal(middlewareWasExecuted, true); }); + }); - (async () => { - try { - await client.invoke('login', {username: 'bob'}); - } catch (err) {} - })(); + describe('ACTION_HANDSHAKE_AG', function () { + it('Should trigger correct events if MIDDLEWARE_HANDSHAKE_AG blocks with an error', async function () { + let middlewareWasExecuted = false; + let serverWarnings = []; + let clientErrors = []; + let abortStatus; + + middlewareFunction = async function (middlewareStream) { + for await (let {type, allow, block} of middlewareStream) { + if (type === server.ACTION_HANDSHAKE_AG) { + await wait(100); + middlewareWasExecuted = true; + let err = new Error('AG handshake failed because the server was too lazy'); + err.name = 'TooLazyHandshakeError'; + block(err); + continue; + } + allow(); + } + }; + server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - await client.listener('authenticate').once(); - assert.equal(middlewareWasExecuted, true); - }); - }); + (async () => { + for await (let {warning} of server.listener('warning')) { + serverWarnings.push(warning); + } + })(); - describe('MIDDLEWARE_HANDSHAKE_AG', function () { - it('Should trigger correct events if MIDDLEWARE_HANDSHAKE_AG blocks with an error', async function () { - let middlewareWasExecuted = false; - let serverWarnings = []; - let clientErrors = []; - let abortStatus; - - middlewareFunction = async function (req) { - await wait(100); - middlewareWasExecuted = true; - let err = new Error('AG handshake failed because the server was too lazy'); - err.name = 'TooLazyHandshakeError'; - throw err; - }; - server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_AG, middlewareFunction); + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); - (async () => { - for await (let {warning} of server.listener('warning')) { - serverWarnings.push(warning); - } - })(); + (async () => { + for await (let {error} of client.listener('error')) { + clientErrors.push(error); + } + })(); - client = asyngularClient.create({ - hostname: clientOptions.hostname, - port: PORT_NUMBER + (async () => { + let event = await client.listener('connectAbort').once(); + abortStatus = event.code; + })(); + + await wait(200); + assert.equal(middlewareWasExecuted, true); + assert.notEqual(clientErrors[0], null); + assert.equal(clientErrors[0].name, 'TooLazyHandshakeError'); + assert.notEqual(clientErrors[1], null); + assert.equal(clientErrors[1].name, 'SocketProtocolError'); + assert.notEqual(serverWarnings[0], null); + assert.equal(serverWarnings[0].name, 'TooLazyHandshakeError'); + assert.notEqual(abortStatus, null); }); - (async () => { - for await (let {error} of client.listener('error')) { - clientErrors.push(error); - } - })(); + it('Should send back default 4008 status code if MIDDLEWARE_HANDSHAKE_AG blocks without providing a status code', async function () { + let middlewareWasExecuted = false; + let abortStatus; + let abortReason; + + middlewareFunction = async function (middlewareStream) { + for await (let {type, allow, block} of middlewareStream) { + if (type === server.ACTION_HANDSHAKE_AG) { + await wait(100); + middlewareWasExecuted = true; + let err = new Error('AG handshake failed because the server was too lazy'); + err.name = 'TooLazyHandshakeError'; + block(err); + continue; + } + allow(); + } + }; + server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - (async () => { - let event = await client.listener('connectAbort').once(); - abortStatus = event.code; - })(); + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); - await wait(200); - assert.equal(middlewareWasExecuted, true); - assert.notEqual(clientErrors[0], null); - assert.equal(clientErrors[0].name, 'TooLazyHandshakeError'); - assert.notEqual(clientErrors[1], null); - assert.equal(clientErrors[1].name, 'SocketProtocolError'); - assert.notEqual(serverWarnings[0], null); - assert.equal(serverWarnings[0].name, 'TooLazyHandshakeError'); - assert.notEqual(abortStatus, null); - }); - - it('Should send back default 4008 status code if MIDDLEWARE_HANDSHAKE_AG blocks without providing a status code', async function () { - let middlewareWasExecuted = false; - let abortStatus; - let abortReason; - - middlewareFunction = async function (req) { - await wait(100); - middlewareWasExecuted = true; - let err = new Error('AG handshake failed because the server was too lazy'); - err.name = 'TooLazyHandshakeError'; - throw err; - }; - server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_AG, middlewareFunction); + (async () => { + let event = await client.listener('connectAbort').once(); + abortStatus = event.code; + abortReason = event.reason; + })(); - client = asyngularClient.create({ - hostname: clientOptions.hostname, - port: PORT_NUMBER + await wait(200); + assert.equal(middlewareWasExecuted, true); + assert.equal(abortStatus, 4008); + assert.equal(abortReason, 'TooLazyHandshakeError: AG handshake failed because the server was too lazy'); }); - (async () => { - let event = await client.listener('connectAbort').once(); - abortStatus = event.code; - abortReason = event.reason; - })(); + it('Should send back custom status code if MIDDLEWARE_HANDSHAKE_AG blocks by providing a status code', async function () { + let middlewareWasExecuted = false; + let abortStatus; + let abortReason; + + middlewareFunction = async function (middlewareStream) { + for await (let {type, allow, block} of middlewareStream) { + if (type === server.ACTION_HANDSHAKE_AG) { + await wait(100); + middlewareWasExecuted = true; + let err = new Error('AG handshake failed because of invalid query auth parameters'); + err.name = 'InvalidAuthQueryHandshakeError'; + // Set custom 4501 status code as a property of the error. + // We will treat this code as a fatal authentication failure on the front end. + // A status code of 4500 or higher means that the client shouldn't try to reconnect. + err.statusCode = 4501; + block(err); + continue; + } + allow(); + } + }; + server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - await wait(200); - assert.equal(middlewareWasExecuted, true); - assert.equal(abortStatus, 4008); - assert.equal(abortReason, 'TooLazyHandshakeError: AG handshake failed because the server was too lazy'); - }); - - it('Should send back custom status code if MIDDLEWARE_HANDSHAKE_AG blocks by providing a status code', async function () { - let middlewareWasExecuted = false; - let abortStatus; - let abortReason; - - middlewareFunction = async function (req) { - await wait(100); - middlewareWasExecuted = true; - let err = new Error('AG handshake failed because of invalid query auth parameters'); - err.name = 'InvalidAuthQueryHandshakeError'; - // Set custom 4501 status code as a property of the error. - // We will treat this code as a fatal authentication failure on the front end. - // A status code of 4500 or higher means that the client shouldn't try to reconnect. - err.statusCode = 4501; - throw err; - }; - server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_AG, middlewareFunction); + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); - client = asyngularClient.create({ - hostname: clientOptions.hostname, - port: PORT_NUMBER + (async () => { + let event = await client.listener('connectAbort').once(); + abortStatus = event.code; + abortReason = event.reason; + })(); + + await wait(200); + assert.equal(middlewareWasExecuted, true); + assert.equal(abortStatus, 4501); + assert.equal(abortReason, 'InvalidAuthQueryHandshakeError: AG handshake failed because of invalid query auth parameters'); }); - (async () => { - let event = await client.listener('connectAbort').once(); - abortStatus = event.code; - abortReason = event.reason; - })(); + it('Should connect with a delay if next() is called after a timeout inside the middleware function', async function () { + let createConnectionTime = null; + let connectEventTime = null; + let abortStatus; + let abortReason; - await wait(200); - assert.equal(middlewareWasExecuted, true); - assert.equal(abortStatus, 4501); - assert.equal(abortReason, 'InvalidAuthQueryHandshakeError: AG handshake failed because of invalid query auth parameters'); - }); + middlewareFunction = async function (middlewareStream) { + for await (let {type, allow} of middlewareStream) { + if (type === server.ACTION_HANDSHAKE_AG) { + await wait(500); + } + allow(); + } + }; + server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - it('Should connect with a delay if next() is called after a timeout inside the middleware function', async function () { - let createConnectionTime = null; - let connectEventTime = null; - let abortStatus; - let abortReason; + createConnectionTime = Date.now(); + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); - middlewareFunction = async function (req) { - await wait(500); - }; - server.addMiddleware(server.MIDDLEWARE_HANDSHAKE_AG, middlewareFunction); + (async () => { + let event = await client.listener('connectAbort').once(); + abortStatus = event.code; + abortReason = event.reason; + })(); - createConnectionTime = Date.now(); - client = asyngularClient.create({ - hostname: clientOptions.hostname, - port: PORT_NUMBER + await client.listener('connect').once(); + connectEventTime = Date.now(); + assert.equal(connectEventTime - createConnectionTime > 400, true); }); - - (async () => { - let event = await client.listener('connectAbort').once(); - abortStatus = event.code; - abortReason = event.reason; - })(); - - await client.listener('connect').once(); - connectEventTime = Date.now(); - assert.equal(connectEventTime - createConnectionTime > 400, true); }); }); }); From 198ec11ecc33c7444236f8dd7c45a7fe5ea2779c Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 13 Jan 2019 17:54:21 +0100 Subject: [PATCH 134/265] v2.0.0 --- package-lock.json | 444 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 +- 2 files changed, 446 insertions(+), 2 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0a2f713 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,444 @@ +{ + "name": "asyngular-server", + "version": "2.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ag-auth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ag-auth/-/ag-auth-1.0.1.tgz", + "integrity": "sha512-zlDUFLNWOrWuQstP785+UXL+WMhRvpw/66BThpKMpqSZ15dcl8dbic5VYC7an0bgoBgCLNNiCxtz0+bJxT1BeQ==", + "requires": { + "jsonwebtoken": "8.4.0", + "sc-errors": "2.0.0" + } + }, + "ag-channel": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ag-channel/-/ag-channel-1.0.0.tgz", + "integrity": "sha512-4tKcXiJG7O/nwjWIyH+86fXVjgBno1NqzvjS1ltMuraZBUzmL4yI5YyCE0T7+cCATpHpXPV6NDTrHQ3duXiOlA==", + "requires": { + "async-iterable-stream": "3.0.1" + } + }, + "ag-simple-broker": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ag-simple-broker/-/ag-simple-broker-1.1.0.tgz", + "integrity": "sha512-vqyszNQlNcZruABWO1dPazKUIkJFAex8TMujbhxtb7UEmBiri8tji3TTSv8IhMIDYcqCfJfUpu4yDbJMVbXSPA==", + "requires": { + "ag-channel": "1.0.0", + "async-stream-emitter": "1.1.0", + "stream-demux": "4.0.4" + } + }, + "async-iterable-stream": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/async-iterable-stream/-/async-iterable-stream-3.0.1.tgz", + "integrity": "sha512-Y4/wTlwUsp3+S/Aiw4KOCh3s6t/ES1kU5erhMuUuvSVvhOTey3vxohks0KoeCslT5TtRg7MvpK6NVcCbT7r3CA==" + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, + "async-stream-emitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/async-stream-emitter/-/async-stream-emitter-1.1.0.tgz", + "integrity": "sha512-oteYCw7qY0LJeSemRx/bqirqhEaZMInMN730rbZ3dhM3E0huT4QAm8CstlYUPD+nEKgkH6HplZbkC8Scv6MbcQ==", + "requires": { + "stream-demux": "4.0.4" + } + }, + "asyngular-client": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/asyngular-client/-/asyngular-client-1.1.3.tgz", + "integrity": "sha512-15Q6wVdJ/vlpjSo9VYNvtxGjRiOetKcBSqkJaINXiULk3/lP6E80gjuaKLdiIyfN5mhnJtALYOXTMFKCxI39uw==", + "dev": true, + "requires": { + "async-stream-emitter": "1.1.0", + "base-64": "0.1.0", + "clone": "2.1.1", + "linked-list": "0.1.0", + "querystring": "0.2.0", + "sc-channel": "2.0.0", + "sc-errors": "2.0.0", + "sc-formatter": "3.0.2", + "stream-demux": "4.0.4", + "uuid": "3.2.1", + "ws": "6.1.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=", + "dev": true + }, + "base64id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "clone": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "dev": true + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "ecdsa-sig-formatter": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", + "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "jsonwebtoken": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.4.0.tgz", + "integrity": "sha512-coyXjRTCy0pw5WYBpMvWOMN+Kjaik2MwTUIq9cna/W7NpO9E+iYbumZONAz3hcr+tXFJECoQVrtmIoC3Oz0gvg==", + "requires": { + "jws": "3.1.5", + "lodash.includes": "4.3.0", + "lodash.isboolean": "3.0.3", + "lodash.isinteger": "4.0.4", + "lodash.isnumber": "3.0.3", + "lodash.isplainobject": "4.0.6", + "lodash.isstring": "4.0.1", + "lodash.once": "4.1.1", + "ms": "2.1.1" + } + }, + "jwa": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", + "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.10", + "safe-buffer": "5.1.2" + } + }, + "jws": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", + "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", + "requires": { + "jwa": "1.1.6", + "safe-buffer": "5.1.2" + } + }, + "linked-list": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz", + "integrity": "sha1-eYsP+X0bkqT9CEgPVa6k6dSdN78=", + "dev": true + }, + "localStorage": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/localStorage/-/localStorage-1.0.4.tgz", + "integrity": "sha512-r35zrihcDiX+dqWlJSeIwS9nrF95OQTgqMFm3FB2D/+XgdmZtcutZOb7t0xXkhOEM8a9kpuu7cc28g1g36I5DQ==", + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "sc-channel": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-2.0.0.tgz", + "integrity": "sha512-XFAic96C3rbG736D/nRiOvhkDXi+K8GGVrGtdo1shggRfQXKHj2K1ajXOIDA8HxT91G6NdnWsgsokfE3YHw9DA==", + "dev": true, + "requires": { + "async-iterable-stream": "3.0.1" + } + }, + "sc-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-2.0.0.tgz", + "integrity": "sha512-zLIg4GskHvkBM7gpKl7JrdU1FXVYsYCavsUeTILFIi/YsuOHLN9OTlFcMp6otb+ebpNEnpcDJI395YXZPif+fw==" + }, + "sc-formatter": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sc-formatter/-/sc-formatter-3.0.2.tgz", + "integrity": "sha512-9PbqYBpCq+OoEeRQ3QfFIGE6qwjjBcd2j7UjgDlhnZbtSnuGgHdcRklPKYGuYFH82V/dwd+AIpu8XvA1zqTd+A==" + }, + "stream-demux": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stream-demux/-/stream-demux-4.0.4.tgz", + "integrity": "sha512-aSB1jS+0tPCnijI9BqEXbV9xC0JqkXnO869JIU4bC2isYMZizUdWm9g7NV0U9D4yaZ+K+HYtgI9XxAebOpkZjw==", + "requires": { + "async-iterable-stream": "3.0.1", + "writable-async-iterable-stream": "4.0.1" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "writable-async-iterable-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/writable-async-iterable-stream/-/writable-async-iterable-stream-4.0.1.tgz", + "integrity": "sha512-zhEBUFIQRtVi2zNyVRWBmFuNBPTYHqtyv8cOPtqDxU+h3IGre6vPpyCW60Es+uJ51EZ1+s7IjpJdvJ0rHMjbVw==", + "requires": { + "async-iterable-stream": "3.0.1" + } + }, + "ws": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.2.tgz", + "integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==", + "requires": { + "async-limiter": "1.0.0" + } + } + } +} diff --git a/package.json b/package.json index 4526b3c..051d28c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "1.1.3", + "version": "2.0.0", "description": "Server module for Asyngular", "main": "index.js", "repository": { @@ -8,7 +8,7 @@ "url": "git://github.com/SocketCluster/asyngular-server.git" }, "dependencies": { - "ag-auth": "^1.0.0", + "ag-auth": "^1.0.1", "ag-simple-broker": "^1.1.0", "async-stream-emitter": "^1.1.0", "base64id": "1.0.0", From f40bc3701b01901e465a477edc6205c95ab7d548 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 13 Jan 2019 21:59:40 +0100 Subject: [PATCH 135/265] Add separate handshake middleware --- agserver.js | 83 +++++++++++++++++++++----------- agserversocket.js | 52 ++++++++++++++++++-- test/integration.js | 112 ++++++++++++++++++++++---------------------- 3 files changed, 160 insertions(+), 87 deletions(-) diff --git a/agserver.js b/agserver.js index bd3141e..92b9a65 100644 --- a/agserver.js +++ b/agserver.js @@ -177,15 +177,18 @@ function AGServer(options) { AGServer.prototype = Object.create(AsyncStreamEmitter.prototype); -AGServer.prototype.SYMBOL_MIDDLEWARE_INBOUND_STREAM = AGServer.SYMBOL_MIDDLEWARE_INBOUND_STREAM = Symbol('inboundStream'); -AGServer.prototype.SYMBOL_MIDDLEWARE_OUTBOUND_STREAM = AGServer.SYMBOL_MIDDLEWARE_OUTBOUND_STREAM = Symbol('outboundStream'); +AGServer.prototype.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM = AGServer.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM = Symbol('handshakeStream'); +AGServer.prototype.MIDDLEWARE_HANDSHAKE = AGServer.MIDDLEWARE_HANDSHAKE = 'handshake'; +AGServer.prototype.MIDDLEWARE_INBOUND_RAW = AGServer.MIDDLEWARE_INBOUND_RAW = 'inboundRaw'; AGServer.prototype.MIDDLEWARE_INBOUND = AGServer.MIDDLEWARE_INBOUND = 'inbound'; AGServer.prototype.MIDDLEWARE_OUTBOUND = AGServer.MIDDLEWARE_OUTBOUND = 'outbound'; AGServer.prototype.ACTION_HANDSHAKE_WS = AGServer.ACTION_HANDSHAKE_WS = 'handshakeWS'; AGServer.prototype.ACTION_HANDSHAKE_AG = AGServer.ACTION_HANDSHAKE_AG = 'handshakeAG'; +AGServer.prototype.ACTION_MESSAGE = AGServer.ACTION_MESSAGE = 'message'; + AGServer.prototype.ACTION_TRANSMIT = AGServer.ACTION_TRANSMIT = 'transmit'; AGServer.prototype.ACTION_INVOKE = AGServer.ACTION_INVOKE = 'invoke'; AGServer.prototype.ACTION_SUBSCRIBE = AGServer.ACTION_SUBSCRIBE = 'subscribe'; @@ -218,6 +221,7 @@ AGServer.prototype._handleServerError = function (error) { }; AGServer.prototype._handleHandshakeTimeout = function (agSocket) { + let middlewareHandshakeStream = agSocket.request[this.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; agSocket.disconnect(4005); }; @@ -302,9 +306,19 @@ AGServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { let agSocket = new AGServerSocket(socketId, this, wsSocket); agSocket.exchange = this.exchange; - let socketOutboundMiddleware = this._middleware[this.MIDDLEWARE_OUTBOUND]; - if (socketOutboundMiddleware) { - socketOutboundMiddleware(agSocket._middlewareOutboundStream); + let inboundRawMiddleware = this._middleware[this.MIDDLEWARE_INBOUND_RAW]; + if (inboundRawMiddleware) { + inboundRawMiddleware(agSocket._middlewareInboundRawStream); + } + + let inboundMiddleware = this._middleware[this.MIDDLEWARE_INBOUND]; + if (inboundMiddleware) { + inboundMiddleware(agSocket._middlewareInboundStream); + } + + let outboundMiddleware = this._middleware[this.MIDDLEWARE_OUTBOUND]; + if (outboundMiddleware) { + outboundMiddleware(agSocket._middlewareOutboundStream); } this.pendingClients[socketId] = agSocket; @@ -412,11 +426,12 @@ AGServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { agSocket.closeProcedure('#subscribe'); agSocket.closeProcedure('#unsubscribe'); agSocket.closeReceiver('#removeAuthToken'); - agSocket.closeListener('authenticate'); - agSocket.closeListener('authStateChange'); - agSocket.closeListener('deauthenticate'); - agSocket._middlewareOutboundStream.close(); + + let middlewareHandshakeStream = agSocket.request[this.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; + middlewareHandshakeStream.close(); + agSocket._middlewareInboundRawStream.close(); agSocket._middlewareInboundStream.close(); + agSocket._middlewareOutboundStream.close(); let isClientFullyConnected = !!this.clients[socketId]; @@ -475,10 +490,13 @@ AGServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { let action = new Action(); action.type = this.ACTION_HANDSHAKE_AG; + action.request = agSocket.request; action.socket = agSocket; + let middlewareHandshakeStream = agSocket.request[this.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; + try { - await this._processMiddlewareAction(agSocket._middlewareInboundStream, action); + await this._processMiddlewareAction(middlewareHandshakeStream, action); } catch (error) { if (error.statusCode == null) { error.statusCode = HANDSHAKE_REJECTION_STATUS_CODE; @@ -541,6 +559,8 @@ AGServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { // Treat authentication failure as a 'soft' error rpc.end(clientSocketStatus); + + middlewareHandshakeStream.close(); } }; handleSocketHandshake(); @@ -572,6 +592,8 @@ AGServer.prototype.generateId = function () { AGServer.prototype.setMiddleware = function (type, middleware) { if ( + type !== this.MIDDLEWARE_HANDSHAKE && + type !== this.MIDDLEWARE_INBOUND_RAW && type !== this.MIDDLEWARE_INBOUND && type !== this.MIDDLEWARE_OUTBOUND ) { @@ -654,32 +676,39 @@ AGServer.prototype.verifyHandshake = async function (info, callback) { } catch (e) {} } - if (ok) { - let middlewareInboundStream = new WritableAsyncIterableStream(); - middlewareInboundStream.type = this.MIDDLEWARE_INBOUND; - req[this.SYMBOL_MIDDLEWARE_INBOUND_STREAM] = middlewareInboundStream; + let middlewareHandshakeStream = new WritableAsyncIterableStream(); + middlewareHandshakeStream.type = this.MIDDLEWARE_HANDSHAKE; - let serverInboundMiddleware = this._middleware[this.MIDDLEWARE_INBOUND]; - if (serverInboundMiddleware) { - serverInboundMiddleware(middlewareInboundStream); - } - let action = new Action(); - action.type = this.ACTION_HANDSHAKE_WS; - action.request = req; + req[this.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM] = middlewareHandshakeStream; - try { - await this._processMiddlewareAction(middlewareInboundStream, action); - } catch (error) { - callback(false, 401, typeof error === 'string' ? error : error.message); - return; - } + let handshakeMiddleware = this._middleware[this.MIDDLEWARE_HANDSHAKE]; + if (handshakeMiddleware) { + handshakeMiddleware(middlewareHandshakeStream); + } + + let action = new Action(); + action.type = this.ACTION_HANDSHAKE_WS; + action.request = req; + + try { + await this._processMiddlewareAction(middlewareHandshakeStream, action); + } catch (error) { + middlewareHandshakeStream.close(); + callback(false, 401, typeof error === 'string' ? error : error.message); + return; + } + + if (ok) { callback(true); return; } + let error = new ServerProtocolError( `Failed to authorize socket handshake - Invalid origin: ${origin}` ); this.emitWarning(error); + + middlewareHandshakeStream.close(); callback(false, 403, error.message); }; diff --git a/agserversocket.js b/agserversocket.js index 5058b3d..b6b41aa 100644 --- a/agserversocket.js +++ b/agserversocket.js @@ -9,6 +9,7 @@ const scErrors = require('sc-errors'); const InvalidArgumentsError = scErrors.InvalidArgumentsError; const SocketProtocolError = scErrors.SocketProtocolError; const TimeoutError = scErrors.TimeoutError; +const BadConnectionError = scErrors.BadConnectionError; const InvalidActionError = scErrors.InvalidActionError; const AuthError = scErrors.AuthError; const AuthTokenExpiredError = scErrors.AuthTokenExpiredError; @@ -31,8 +32,12 @@ function AGServerSocket(id, server, socket) { this.request = this.socket.upgradeReq; - // TODO 2: Add MIDDLEWARE_INBOUND_RAW for applying middleware on raw messages. - this._middlewareInboundStream = this.request[this.server.SYMBOL_MIDDLEWARE_INBOUND_STREAM]; + this._middlewareInboundRawStream = new WritableAsyncIterableStream(); + this._middlewareInboundRawStream.type = this.server.MIDDLEWARE_INBOUND_RAW; + + this._middlewareInboundStream = new WritableAsyncIterableStream(); + this._middlewareInboundStream.type = this.server.MIDDLEWARE_INBOUND; + this._middlewareOutboundStream = new WritableAsyncIterableStream(); this._middlewareOutboundStream.type = this.server.MIDDLEWARE_OUTBOUND; @@ -70,9 +75,24 @@ function AGServerSocket(id, server, socket) { this._resetPongTimeout(); // Receive incoming raw messages - this.socket.on('message', (message, flags) => { + this.socket.on('message', async (message, flags) => { this._resetPongTimeout(); + if (this.server.hasMiddleware(this.server.MIDDLEWARE_INBOUND_RAW)) { + let action = new Action(); + action.socket = this; + action.type = this.server.ACTION_MESSAGE; + action.data = message; + + try { + let {data} = await this.server._processMiddlewareAction(this._middlewareInboundRawStream, action, this); + message = data; + } catch (error) { + + return; + } + } + this.emit('message', {message}); let packet; @@ -317,17 +337,38 @@ AGServerSocket.prototype.emitError = function (error) { this.server.emitWarning(error); }; +AGServerSocket.prototype._abortAllPendingEventsDueToBadConnection = function (failureType) { + Object.keys(this._callbackMap || {}).forEach((i) => { + let eventObject = this._callbackMap[i]; + delete this._callbackMap[i]; + + clearTimeout(eventObject.timeout); + delete eventObject.timeout; + + let errorMessage = `Event "${eventObject.event}" was aborted due to a bad connection`; + let badConnectionError = new BadConnectionError(errorMessage, failureType); + + let callback = eventObject.callback; + delete eventObject.callback; + + callback.call(eventObject, badConnectionError, eventObject); + }); +}; + AGServerSocket.prototype._onClose = function (code, reason) { clearInterval(this._pingIntervalTicker); clearTimeout(this._pingTimeoutTicker); - if (this.state !== this.CLOSED) { + if (this.state === this.CLOSED) { + this._abortAllPendingEventsDueToBadConnection('connectAbort'); + } else { let prevState = this.state; this.state = this.CLOSED; - if (prevState === this.CONNECTING) { + this._abortAllPendingEventsDueToBadConnection('connectAbort'); this.emit('connectAbort', {code, reason}); } else { + this._abortAllPendingEventsDueToBadConnection('disconnect'); this.emit('disconnect', {code, reason}); } this.emit('close', {code, reason}); @@ -495,6 +536,7 @@ AGServerSocket.prototype.invoke = function (event, data, options) { }, this.server.ackTimeout); this._callbackMap[eventObject.cid] = { + event, callback: (err, result) => { if (err) { reject(err); diff --git a/test/integration.js b/test/integration.js index 8c12dbb..b9ae16e 100644 --- a/test/integration.js +++ b/test/integration.js @@ -2055,57 +2055,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); }); - describe('MIDDLEWARE_INBOUND', function () { - describe('ACTION_AUTHENTICATE', function () { - it('Should not run ACTION_AUTHENTICATE middleware action if JWT token does not exist', async function () { - middlewareFunction = async function (middlewareStream) { - for await (let {type, allow} of middlewareStream) { - if (type === server.ACTION_AUTHENTICATE) { - middlewareWasExecuted = true; - } - allow(); - } - }; - server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - - client = asyngularClient.create({ - hostname: clientOptions.hostname, - port: PORT_NUMBER - }); - - await client.listener('connect').once(); - assert.notEqual(middlewareWasExecuted, true); - }); - - it('Should run ACTION_AUTHENTICATE middleware action if JWT token exists', async function () { - global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); - - middlewareFunction = async function (middlewareStream) { - for await (let {type, allow} of middlewareStream) { - if (type === server.ACTION_AUTHENTICATE) { - middlewareWasExecuted = true; - } - allow(); - } - }; - server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - - client = asyngularClient.create({ - hostname: clientOptions.hostname, - port: PORT_NUMBER - }); - - (async () => { - try { - await client.invoke('login', {username: 'bob'}); - } catch (err) {} - })(); - - await client.listener('authenticate').once(); - assert.equal(middlewareWasExecuted, true); - }); - }); - + describe('MIDDLEWARE_HANDSHAKE', function () { describe('ACTION_HANDSHAKE_AG', function () { it('Should trigger correct events if MIDDLEWARE_HANDSHAKE_AG blocks with an error', async function () { let middlewareWasExecuted = false; @@ -2126,7 +2076,7 @@ describe('Integration tests', function () { allow(); } }; - server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); + server.setMiddleware(server.MIDDLEWARE_HANDSHAKE, middlewareFunction); (async () => { for await (let {warning} of server.listener('warning')) { @@ -2179,7 +2129,7 @@ describe('Integration tests', function () { allow(); } }; - server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); + server.setMiddleware(server.MIDDLEWARE_HANDSHAKE, middlewareFunction); client = asyngularClient.create({ hostname: clientOptions.hostname, @@ -2220,7 +2170,7 @@ describe('Integration tests', function () { allow(); } }; - server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); + server.setMiddleware(server.MIDDLEWARE_HANDSHAKE, middlewareFunction); client = asyngularClient.create({ hostname: clientOptions.hostname, @@ -2253,7 +2203,7 @@ describe('Integration tests', function () { allow(); } }; - server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); + server.setMiddleware(server.MIDDLEWARE_HANDSHAKE, middlewareFunction); createConnectionTime = Date.now(); client = asyngularClient.create({ @@ -2273,5 +2223,57 @@ describe('Integration tests', function () { }); }); }); + + describe('MIDDLEWARE_INBOUND', function () { + describe('ACTION_AUTHENTICATE', function () { + it('Should not run ACTION_AUTHENTICATE middleware action if JWT token does not exist', async function () { + middlewareFunction = async function (middlewareStream) { + for await (let {type, allow} of middlewareStream) { + if (type === server.ACTION_AUTHENTICATE) { + middlewareWasExecuted = true; + } + allow(); + } + }; + server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + await client.listener('connect').once(); + assert.notEqual(middlewareWasExecuted, true); + }); + + it('Should run ACTION_AUTHENTICATE middleware action if JWT token exists', async function () { + global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); + + middlewareFunction = async function (middlewareStream) { + for await (let {type, allow} of middlewareStream) { + if (type === server.ACTION_AUTHENTICATE) { + middlewareWasExecuted = true; + } + allow(); + } + }; + server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + (async () => { + try { + await client.invoke('login', {username: 'bob'}); + } catch (err) {} + })(); + + await client.listener('authenticate').once(); + assert.equal(middlewareWasExecuted, true); + }); + }); + }); }); }); From f7868c5a29caf1dd534dd29806a6c4341f81552f Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 13 Jan 2019 22:00:05 +0100 Subject: [PATCH 136/265] v2.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 051d28c..5030a2d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "2.0.0", + "version": "2.1.0", "description": "Server module for Asyngular", "main": "index.js", "repository": { From 37961a6e3e0d6ccbc581b5ff71bd31ab9ef2083b Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 13 Jan 2019 22:06:20 +0100 Subject: [PATCH 137/265] v2.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5030a2d..18e8999 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "ws": "6.1.2" }, "devDependencies": { - "asyngular-client": "^1.1.3", + "asyngular-client": "^2.0.0", "localStorage": "^1.0.3", "mocha": "5.2.0" }, From 3738a727d0eb0e5d01ab3d2b7e3e64a786b703b8 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 13 Jan 2019 22:07:20 +0100 Subject: [PATCH 138/265] v2.1.1 --- package-lock.json | 19 +++++-------------- package.json | 2 +- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0a2f713..b81bec2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "2.0.0", + "version": "2.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -50,17 +50,17 @@ } }, "asyngular-client": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/asyngular-client/-/asyngular-client-1.1.3.tgz", - "integrity": "sha512-15Q6wVdJ/vlpjSo9VYNvtxGjRiOetKcBSqkJaINXiULk3/lP6E80gjuaKLdiIyfN5mhnJtALYOXTMFKCxI39uw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/asyngular-client/-/asyngular-client-2.0.0.tgz", + "integrity": "sha512-b09uuZDCV/EhhKetS36rB8IMbLPdowtc0TEikhTfdqNfavZn9k095ReNI6iTfwV62gfoB3joosFbp/INsq93AQ==", "dev": true, "requires": { + "ag-channel": "1.0.0", "async-stream-emitter": "1.1.0", "base-64": "0.1.0", "clone": "2.1.1", "linked-list": "0.1.0", "querystring": "0.2.0", - "sc-channel": "2.0.0", "sc-errors": "2.0.0", "sc-formatter": "3.0.2", "stream-demux": "4.0.4", @@ -376,15 +376,6 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "sc-channel": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-2.0.0.tgz", - "integrity": "sha512-XFAic96C3rbG736D/nRiOvhkDXi+K8GGVrGtdo1shggRfQXKHj2K1ajXOIDA8HxT91G6NdnWsgsokfE3YHw9DA==", - "dev": true, - "requires": { - "async-iterable-stream": "3.0.1" - } - }, "sc-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-2.0.0.tgz", diff --git a/package.json b/package.json index 18e8999..f569c36 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "2.1.0", + "version": "2.1.1", "description": "Server module for Asyngular", "main": "index.js", "repository": { From 6722b1c82f75db7acd97c785958e70ecd25423b1 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 15 Jan 2019 19:13:53 +0100 Subject: [PATCH 139/265] Add support for publish message which does not expect a response --- agserversocket.js | 63 ++++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/agserversocket.js b/agserversocket.js index b6b41aa..a3a552c 100644 --- a/agserversocket.js +++ b/agserversocket.js @@ -159,6 +159,20 @@ AGServerSocket.prototype._sendPing = function () { } }; +AGServerSocket.prototype._processInboundPublishPacket = async function (packet) { + if (typeof packet.channel !== 'string') { + let error = new InvalidActionError(`Socket ${this.id} tried to publish to an invalid "${publishPacket.channel}" channel`); + this.emitError(error); + throw error; + } + try { + await this.server.exchange.publish(packet.channel, packet.data); + } catch (error) { + this.emitError(error); + throw error; + } +}; + AGServerSocket.prototype._processInboundPacket = async function (packet, message) { if (packet && packet.event != null) { let eventName = packet.event; @@ -177,11 +191,15 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message return; } - let tokenExpiredError = this._processAuthTokenExpiry(); let action = new Action(); action.socket = this; + let tokenExpiredError = this._processAuthTokenExpiry(); + if (tokenExpiredError) { + action.authTokenExpiredError = tokenExpiredError; + } + let isPublish = eventName === '#publish'; let isSubscribe = eventName === '#subscribe'; @@ -231,10 +249,6 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message let newData; - if (tokenExpiredError) { - action.authTokenExpiredError = tokenExpiredError; - } - if (isRPC) { let request = new Request(this, packet.cid, eventName, packet.data); try { @@ -246,34 +260,25 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message return; } - if (isPublish || isSubscribe) { - request.data = request.data || {}; + if (isSubscribe) { + if (!request.data) { + request.data = {}; + } request.data.data = newData; - } else { - request.data = newData; - } - if (isPublish) { - let publishPacket = request.data || {}; - - if (typeof publishPacket.channel !== 'string') { - let error = new InvalidActionError(`Socket ${this.id} tried to publish to an invalid "${publishPacket.channel}" channel`); - this.emitError(error); - request.error(error); - - return; + } else if (isPublish) { + if (!request.data) { + request.data = {}; } - + request.data.data = newData; try { - await this.server.exchange.publish(publishPacket.channel, publishPacket.data); + await this._processInboundPublishPacket(request.data || {}); } catch (error) { - this.emitError(error); request.error(error); - return; } request.end(); - - return; + } else { + request.data = newData; } this._procedureDemux.write(eventName, request); @@ -289,6 +294,14 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message return; } + if (isPublish) { + try { + await this._processInboundPublishPacket(packet.data || {}); + } catch (error) { + return; + } + } + this._receiverDemux.write(eventName, newData); return; From b7998c5cdb254818fc11fa7411cfb7a0b0d96335 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 15 Jan 2019 19:18:43 +0100 Subject: [PATCH 140/265] Use arrow function instead of regular function for callback --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index b988502..c0bd42e 100644 --- a/index.js +++ b/index.js @@ -36,7 +36,7 @@ module.exports.listen = function (port, options, fn) { options = {}; } - let server = http.createServer(function (req, res) { + let server = http.createServer((req, res) => { res.writeHead(501); res.end('Not Implemented'); }); From e64f0f3c47047cea726ba1ac0195d3f4c1090b55 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 16 Jan 2019 00:04:52 +0100 Subject: [PATCH 141/265] Rename publish to transmitPublish and invokePublish --- action.js | 12 ++++++++++++ agserver.js | 16 ++-------------- agserversocket.js | 18 +++++++++--------- package.json | 6 +++--- test/integration.js | 43 ++++++++++++++++++++++--------------------- 5 files changed, 48 insertions(+), 47 deletions(-) diff --git a/action.js b/action.js index 05400c7..7808fb5 100644 --- a/action.js +++ b/action.js @@ -25,4 +25,16 @@ function Action() { }; } +Action.prototype.HANDSHAKE_WS = Action.HANDSHAKE_WS = 'handshakeWS'; +Action.prototype.HANDSHAKE_AG = Action.HANDSHAKE_AG = 'handshakeAG'; + +Action.prototype.MESSAGE = Action.MESSAGE = 'message'; + +Action.prototype.TRANSMIT = Action.TRANSMIT = 'transmit'; +Action.prototype.INVOKE = Action.INVOKE = 'invoke'; +Action.prototype.SUBSCRIBE = Action.SUBSCRIBE = 'subscribe'; +Action.prototype.PUBLISH_IN = Action.PUBLISH_IN = 'publishIn'; +Action.prototype.PUBLISH_OUT = Action.PUBLISH_OUT = 'publishOut'; +Action.prototype.AUTHENTICATE = Action.AUTHENTICATE = 'authenticate'; + module.exports = Action; diff --git a/agserver.js b/agserver.js index 92b9a65..1d9aeaa 100644 --- a/agserver.js +++ b/agserver.js @@ -184,18 +184,6 @@ AGServer.prototype.MIDDLEWARE_INBOUND_RAW = AGServer.MIDDLEWARE_INBOUND_RAW = 'i AGServer.prototype.MIDDLEWARE_INBOUND = AGServer.MIDDLEWARE_INBOUND = 'inbound'; AGServer.prototype.MIDDLEWARE_OUTBOUND = AGServer.MIDDLEWARE_OUTBOUND = 'outbound'; -AGServer.prototype.ACTION_HANDSHAKE_WS = AGServer.ACTION_HANDSHAKE_WS = 'handshakeWS'; -AGServer.prototype.ACTION_HANDSHAKE_AG = AGServer.ACTION_HANDSHAKE_AG = 'handshakeAG'; - -AGServer.prototype.ACTION_MESSAGE = AGServer.ACTION_MESSAGE = 'message'; - -AGServer.prototype.ACTION_TRANSMIT = AGServer.ACTION_TRANSMIT = 'transmit'; -AGServer.prototype.ACTION_INVOKE = AGServer.ACTION_INVOKE = 'invoke'; -AGServer.prototype.ACTION_SUBSCRIBE = AGServer.ACTION_SUBSCRIBE = 'subscribe'; -AGServer.prototype.ACTION_PUBLISH_IN = AGServer.ACTION_PUBLISH_IN = 'publishIn'; -AGServer.prototype.ACTION_PUBLISH_OUT = AGServer.ACTION_PUBLISH_OUT = 'publishOut'; -AGServer.prototype.ACTION_AUTHENTICATE = AGServer.ACTION_AUTHENTICATE = 'authenticate'; - AGServer.prototype.setAuthEngine = function (authEngine) { this.auth = authEngine; }; @@ -489,7 +477,7 @@ AGServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { clearTimeout(agSocket._handshakeTimeoutRef); let action = new Action(); - action.type = this.ACTION_HANDSHAKE_AG; + action.type = Action.HANDSHAKE_AG; action.request = agSocket.request; action.socket = agSocket; @@ -687,7 +675,7 @@ AGServer.prototype.verifyHandshake = async function (info, callback) { } let action = new Action(); - action.type = this.ACTION_HANDSHAKE_WS; + action.type = Action.HANDSHAKE_WS; action.request = req; try { diff --git a/agserversocket.js b/agserversocket.js index a3a552c..efd588d 100644 --- a/agserversocket.js +++ b/agserversocket.js @@ -81,7 +81,7 @@ function AGServerSocket(id, server, socket) { if (this.server.hasMiddleware(this.server.MIDDLEWARE_INBOUND_RAW)) { let action = new Action(); action.socket = this; - action.type = this.server.ACTION_MESSAGE; + action.type = Action.MESSAGE; action.data = message; try { @@ -166,7 +166,7 @@ AGServerSocket.prototype._processInboundPublishPacket = async function (packet) throw error; } try { - await this.server.exchange.publish(packet.channel, packet.data); + await this.server.exchange.invokePublish(packet.channel, packet.data); } catch (error) { this.emitError(error); throw error; @@ -214,13 +214,13 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message } return; } - action.type = this.server.ACTION_PUBLISH_IN; + action.type = Action.PUBLISH_IN; if (packet.data) { action.channel = packet.data.channel; action.data = packet.data.data; } } else if (isSubscribe) { - action.type = this.server.ACTION_SUBSCRIBE; + action.type = Action.SUBSCRIBE; if (packet.data) { action.channel = packet.data.channel; action.data = packet.data.data; @@ -233,13 +233,13 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message return; } else { if (isRPC) { - action.type = this.server.ACTION_INVOKE; + action.type = Action.INVOKE; action.procedure = packet.event; if (packet.data !== undefined) { action.data = packet.data; } } else { - action.type = this.server.ACTION_TRANSMIT; + action.type = Action.TRANSMIT; action.receiver = packet.event; if (packet.data !== undefined) { action.data = packet.data; @@ -493,7 +493,7 @@ AGServerSocket.prototype.transmit = async function (event, data, options) { let isPublish = event === '#publish'; if (isPublish) { let action = new Action(); - action.type = this.server.ACTION_PUBLISH_OUT; + action.type = Action.PUBLISH_OUT; action.socket = this; if (data !== undefined) { @@ -532,7 +532,7 @@ AGServerSocket.prototype.transmit = async function (event, data, options) { } }; -AGServerSocket.prototype.invoke = function (event, data, options) { +AGServerSocket.prototype.invoke = async function (event, data, options) { return new Promise((resolve, reject) => { let eventObject = { event, @@ -813,7 +813,7 @@ AGServerSocket.prototype._processAuthToken = async function (signedAuthToken) { this.authState = this.AUTHENTICATED; let action = new Action(); - action.type = this.server.ACTION_AUTHENTICATE; + action.type = Action.AUTHENTICATE; action.socket = this; action.signedAuthToken = this.signedAuthToken; action.authToken = this.authToken; diff --git a/package.json b/package.json index f569c36..8bb773e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "2.1.1", + "version": "3.0.0", "description": "Server module for Asyngular", "main": "index.js", "repository": { @@ -9,7 +9,7 @@ }, "dependencies": { "ag-auth": "^1.0.1", - "ag-simple-broker": "^1.1.0", + "ag-simple-broker": "^2.0.1", "async-stream-emitter": "^1.1.0", "base64id": "1.0.0", "lodash.clonedeep": "4.5.0", @@ -21,7 +21,7 @@ "ws": "6.1.2" }, "devDependencies": { - "asyngular-client": "^2.0.0", + "asyngular-client": "^3.0.0", "localStorage": "^1.0.3", "mocha": "5.2.0" }, diff --git a/test/integration.js b/test/integration.js index b9ae16e..9797130 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1,5 +1,6 @@ const assert = require('assert'); const asyngularServer = require('../'); +const Action = require('../action'); const asyngularClient = require('asyngular-client'); const localStorage = require('localStorage'); const AGSimpleBroker = require('ag-simple-broker'); @@ -177,7 +178,7 @@ describe('Integration tests', function () { server.setMiddleware(server.MIDDLEWARE_INBOUND, async (middlewareStream) => { for await (let action of middlewareStream) { if ( - action.type === server.ACTION_AUTHENTICATE && + action.type === Action.AUTHENTICATE && (!action.authToken || action.authToken.username === 'alice') ) { let err = new Error('Blocked by MIDDLEWARE_INBOUND'); @@ -1354,7 +1355,7 @@ describe('Integration tests', function () { // though they were sent as a batch/array. server.setMiddleware(server.MIDDLEWARE_INBOUND, async function (middlewareStream) { for await (let action of middlewareStream) { - if (action.type === server.ACTION_SUBSCRIBE) { + if (action.type === Action.SUBSCRIBE) { subscribeMiddlewareCounter++; assert.equal(action.channel.indexOf('my-channel-'), 0); if (action.channel === 'my-channel-10') { @@ -1406,7 +1407,7 @@ describe('Integration tests', function () { (async () => { for await (let event of channelList[19].listener('subscribe')) { - client.publish('my-channel-19', 'Hello!'); + client.transmitPublish('my-channel-19', 'Hello!'); } })(); @@ -1639,9 +1640,9 @@ describe('Integration tests', function () { (async () => { await client.listener('connect').once(); - client.publish('foo', 'hi1'); + client.transmitPublish('foo', 'hi1'); await wait(10); - client.publish('foo', 'hi2'); + client.transmitPublish('foo', 'hi2'); })(); let receivedSubscribedData = []; @@ -1687,9 +1688,9 @@ describe('Integration tests', function () { (async () => { await client.listener('subscribe').once(); - server.exchange.publish('bar', 'hello1'); + server.exchange.transmitPublish('bar', 'hello1'); await wait(10); - server.exchange.publish('bar', 'hello2'); + server.exchange.transmitPublish('bar', 'hello2'); })(); let receivedSubscribedData = []; @@ -1831,7 +1832,7 @@ describe('Integration tests', function () { (async () => { for await (let event of socket.listener('unsubscribe')) { if (event.channel === 'foo') { - server.exchange.publish('foo', 'hello'); + server.exchange.transmitPublish('foo', 'hello'); } } })(); @@ -2056,8 +2057,8 @@ describe('Integration tests', function () { }); describe('MIDDLEWARE_HANDSHAKE', function () { - describe('ACTION_HANDSHAKE_AG', function () { - it('Should trigger correct events if MIDDLEWARE_HANDSHAKE_AG blocks with an error', async function () { + describe('HANDSHAKE_AG action', function () { + it('Should trigger correct events if MIDDLEWARE_HANDSHAKE blocks with an error', async function () { let middlewareWasExecuted = false; let serverWarnings = []; let clientErrors = []; @@ -2065,7 +2066,7 @@ describe('Integration tests', function () { middlewareFunction = async function (middlewareStream) { for await (let {type, allow, block} of middlewareStream) { - if (type === server.ACTION_HANDSHAKE_AG) { + if (type === Action.HANDSHAKE_AG) { await wait(100); middlewareWasExecuted = true; let err = new Error('AG handshake failed because the server was too lazy'); @@ -2111,14 +2112,14 @@ describe('Integration tests', function () { assert.notEqual(abortStatus, null); }); - it('Should send back default 4008 status code if MIDDLEWARE_HANDSHAKE_AG blocks without providing a status code', async function () { + it('Should send back default 4008 status code if MIDDLEWARE_HANDSHAKE blocks without providing a status code', async function () { let middlewareWasExecuted = false; let abortStatus; let abortReason; middlewareFunction = async function (middlewareStream) { for await (let {type, allow, block} of middlewareStream) { - if (type === server.ACTION_HANDSHAKE_AG) { + if (type === Action.HANDSHAKE_AG) { await wait(100); middlewareWasExecuted = true; let err = new Error('AG handshake failed because the server was too lazy'); @@ -2148,14 +2149,14 @@ describe('Integration tests', function () { assert.equal(abortReason, 'TooLazyHandshakeError: AG handshake failed because the server was too lazy'); }); - it('Should send back custom status code if MIDDLEWARE_HANDSHAKE_AG blocks by providing a status code', async function () { + it('Should send back custom status code if MIDDLEWARE_HANDSHAKE blocks by providing a status code', async function () { let middlewareWasExecuted = false; let abortStatus; let abortReason; middlewareFunction = async function (middlewareStream) { for await (let {type, allow, block} of middlewareStream) { - if (type === server.ACTION_HANDSHAKE_AG) { + if (type === Action.HANDSHAKE_AG) { await wait(100); middlewareWasExecuted = true; let err = new Error('AG handshake failed because of invalid query auth parameters'); @@ -2197,7 +2198,7 @@ describe('Integration tests', function () { middlewareFunction = async function (middlewareStream) { for await (let {type, allow} of middlewareStream) { - if (type === server.ACTION_HANDSHAKE_AG) { + if (type === Action.HANDSHAKE_AG) { await wait(500); } allow(); @@ -2225,11 +2226,11 @@ describe('Integration tests', function () { }); describe('MIDDLEWARE_INBOUND', function () { - describe('ACTION_AUTHENTICATE', function () { - it('Should not run ACTION_AUTHENTICATE middleware action if JWT token does not exist', async function () { + describe('AUTHENTICATE action', function () { + it('Should not run AUTHENTICATE action in middleware if JWT token does not exist', async function () { middlewareFunction = async function (middlewareStream) { for await (let {type, allow} of middlewareStream) { - if (type === server.ACTION_AUTHENTICATE) { + if (type === Action.AUTHENTICATE) { middlewareWasExecuted = true; } allow(); @@ -2246,12 +2247,12 @@ describe('Integration tests', function () { assert.notEqual(middlewareWasExecuted, true); }); - it('Should run ACTION_AUTHENTICATE middleware action if JWT token exists', async function () { + it('Should run AUTHENTICATE action in middleware if JWT token exists', async function () { global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); middlewareFunction = async function (middlewareStream) { for await (let {type, allow} of middlewareStream) { - if (type === server.ACTION_AUTHENTICATE) { + if (type === Action.AUTHENTICATE) { middlewareWasExecuted = true; } allow(); From 7c6df60590c2d3eced481ed0b298cbd5d1fae8d2 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 16 Jan 2019 00:09:25 +0100 Subject: [PATCH 142/265] v3.0.0 --- .gitignore | 1 + package-lock.json | 27 +++++++++++++++++++-------- package.json | 2 +- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index c2658d7..d570088 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules/ + diff --git a/package-lock.json b/package-lock.json index b81bec2..ccdfa61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "2.1.1", + "version": "3.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -14,19 +14,19 @@ } }, "ag-channel": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ag-channel/-/ag-channel-1.0.0.tgz", - "integrity": "sha512-4tKcXiJG7O/nwjWIyH+86fXVjgBno1NqzvjS1ltMuraZBUzmL4yI5YyCE0T7+cCATpHpXPV6NDTrHQ3duXiOlA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ag-channel/-/ag-channel-2.0.0.tgz", + "integrity": "sha512-EJCkxaxdm1ZZ7JoMGqkGKcQusnSXDW0NL1WP9mznLEeUG2KjfSuxojvnIRElvtJb8WCFyV9MBnLyTHm+Z+qGcg==", "requires": { "async-iterable-stream": "3.0.1" } }, "ag-simple-broker": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ag-simple-broker/-/ag-simple-broker-1.1.0.tgz", - "integrity": "sha512-vqyszNQlNcZruABWO1dPazKUIkJFAex8TMujbhxtb7UEmBiri8tji3TTSv8IhMIDYcqCfJfUpu4yDbJMVbXSPA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ag-simple-broker/-/ag-simple-broker-2.0.1.tgz", + "integrity": "sha512-fY1yWJagNMMoyViaiXZ8ePu/CVm+sn684zqlvkqUzmn8HZKzqqIIAVj8qMG1mPkwabKEIRpmeayadZpBHE9PvQ==", "requires": { - "ag-channel": "1.0.0", + "ag-channel": "2.0.0", "async-stream-emitter": "1.1.0", "stream-demux": "4.0.4" } @@ -66,6 +66,17 @@ "stream-demux": "4.0.4", "uuid": "3.2.1", "ws": "6.1.2" + }, + "dependencies": { + "ag-channel": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ag-channel/-/ag-channel-1.0.0.tgz", + "integrity": "sha512-4tKcXiJG7O/nwjWIyH+86fXVjgBno1NqzvjS1ltMuraZBUzmL4yI5YyCE0T7+cCATpHpXPV6NDTrHQ3duXiOlA==", + "dev": true, + "requires": { + "async-iterable-stream": "3.0.1" + } + } } }, "balanced-match": { diff --git a/package.json b/package.json index 8bb773e..f9eb40c 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "ws": "6.1.2" }, "devDependencies": { - "asyngular-client": "^3.0.0", + "asyngular-client": "^2.0.0", "localStorage": "^1.0.3", "mocha": "5.2.0" }, From 6f581828fa5002b7c0bb2a49b75590d5bf0d5e9b Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 16 Jan 2019 00:17:21 +0100 Subject: [PATCH 143/265] v3.0.1 --- package-lock.json | 21 +++++---------------- package.json | 4 ++-- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index ccdfa61..3268d07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "3.0.0", + "version": "3.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -50,12 +50,12 @@ } }, "asyngular-client": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/asyngular-client/-/asyngular-client-2.0.0.tgz", - "integrity": "sha512-b09uuZDCV/EhhKetS36rB8IMbLPdowtc0TEikhTfdqNfavZn9k095ReNI6iTfwV62gfoB3joosFbp/INsq93AQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/asyngular-client/-/asyngular-client-3.0.0.tgz", + "integrity": "sha512-JxbD9FcK1D3m62z/N6ykoCcJ58LKbgzOAbDM1FkubulYi9DmZJwLTgyC1XWGu+ac2vWUEhxWn6ErZ8g1QPC67w==", "dev": true, "requires": { - "ag-channel": "1.0.0", + "ag-channel": "2.0.0", "async-stream-emitter": "1.1.0", "base-64": "0.1.0", "clone": "2.1.1", @@ -66,17 +66,6 @@ "stream-demux": "4.0.4", "uuid": "3.2.1", "ws": "6.1.2" - }, - "dependencies": { - "ag-channel": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ag-channel/-/ag-channel-1.0.0.tgz", - "integrity": "sha512-4tKcXiJG7O/nwjWIyH+86fXVjgBno1NqzvjS1ltMuraZBUzmL4yI5YyCE0T7+cCATpHpXPV6NDTrHQ3duXiOlA==", - "dev": true, - "requires": { - "async-iterable-stream": "3.0.1" - } - } } }, "balanced-match": { diff --git a/package.json b/package.json index f9eb40c..721673d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "3.0.0", + "version": "3.0.1", "description": "Server module for Asyngular", "main": "index.js", "repository": { @@ -21,7 +21,7 @@ "ws": "6.1.2" }, "devDependencies": { - "asyngular-client": "^2.0.0", + "asyngular-client": "^3.0.0", "localStorage": "^1.0.3", "mocha": "5.2.0" }, From 6e259d29043d75bfd4d52ce80b1f532471998cb7 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 16 Jan 2019 00:25:20 +0100 Subject: [PATCH 144/265] v3.0.2 --- package-lock.json | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3268d07..8d5d4e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "3.0.1", + "version": "3.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 721673d..fd6457c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "3.0.1", + "version": "3.0.2", "description": "Server module for Asyngular", "main": "index.js", "repository": { @@ -21,7 +21,7 @@ "ws": "6.1.2" }, "devDependencies": { - "asyngular-client": "^3.0.0", + "asyngular-client": "^3.0.2", "localStorage": "^1.0.3", "mocha": "5.2.0" }, From bde97d3e1978565d3d7d8df1fcb1415055d9612c Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 17 Jan 2019 21:12:52 +0100 Subject: [PATCH 145/265] Bump dependencies --- .gitignore | 1 + package-lock.json | 435 ---------------------------------------------- package.json | 6 +- 3 files changed, 4 insertions(+), 438 deletions(-) delete mode 100644 package-lock.json diff --git a/.gitignore b/.gitignore index d570088..b8ffe08 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ +package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 8d5d4e4..0000000 --- a/package-lock.json +++ /dev/null @@ -1,435 +0,0 @@ -{ - "name": "asyngular-server", - "version": "3.0.2", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "ag-auth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ag-auth/-/ag-auth-1.0.1.tgz", - "integrity": "sha512-zlDUFLNWOrWuQstP785+UXL+WMhRvpw/66BThpKMpqSZ15dcl8dbic5VYC7an0bgoBgCLNNiCxtz0+bJxT1BeQ==", - "requires": { - "jsonwebtoken": "8.4.0", - "sc-errors": "2.0.0" - } - }, - "ag-channel": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ag-channel/-/ag-channel-2.0.0.tgz", - "integrity": "sha512-EJCkxaxdm1ZZ7JoMGqkGKcQusnSXDW0NL1WP9mznLEeUG2KjfSuxojvnIRElvtJb8WCFyV9MBnLyTHm+Z+qGcg==", - "requires": { - "async-iterable-stream": "3.0.1" - } - }, - "ag-simple-broker": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ag-simple-broker/-/ag-simple-broker-2.0.1.tgz", - "integrity": "sha512-fY1yWJagNMMoyViaiXZ8ePu/CVm+sn684zqlvkqUzmn8HZKzqqIIAVj8qMG1mPkwabKEIRpmeayadZpBHE9PvQ==", - "requires": { - "ag-channel": "2.0.0", - "async-stream-emitter": "1.1.0", - "stream-demux": "4.0.4" - } - }, - "async-iterable-stream": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/async-iterable-stream/-/async-iterable-stream-3.0.1.tgz", - "integrity": "sha512-Y4/wTlwUsp3+S/Aiw4KOCh3s6t/ES1kU5erhMuUuvSVvhOTey3vxohks0KoeCslT5TtRg7MvpK6NVcCbT7r3CA==" - }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" - }, - "async-stream-emitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/async-stream-emitter/-/async-stream-emitter-1.1.0.tgz", - "integrity": "sha512-oteYCw7qY0LJeSemRx/bqirqhEaZMInMN730rbZ3dhM3E0huT4QAm8CstlYUPD+nEKgkH6HplZbkC8Scv6MbcQ==", - "requires": { - "stream-demux": "4.0.4" - } - }, - "asyngular-client": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/asyngular-client/-/asyngular-client-3.0.0.tgz", - "integrity": "sha512-JxbD9FcK1D3m62z/N6ykoCcJ58LKbgzOAbDM1FkubulYi9DmZJwLTgyC1XWGu+ac2vWUEhxWn6ErZ8g1QPC67w==", - "dev": true, - "requires": { - "ag-channel": "2.0.0", - "async-stream-emitter": "1.1.0", - "base-64": "0.1.0", - "clone": "2.1.1", - "linked-list": "0.1.0", - "querystring": "0.2.0", - "sc-errors": "2.0.0", - "sc-formatter": "3.0.2", - "stream-demux": "4.0.4", - "uuid": "3.2.1", - "ws": "6.1.2" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base-64": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", - "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=", - "dev": true - }, - "base64id": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", - "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" - }, - "clone": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", - "dev": true - }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "ecdsa-sig-formatter": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", - "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "jsonwebtoken": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.4.0.tgz", - "integrity": "sha512-coyXjRTCy0pw5WYBpMvWOMN+Kjaik2MwTUIq9cna/W7NpO9E+iYbumZONAz3hcr+tXFJECoQVrtmIoC3Oz0gvg==", - "requires": { - "jws": "3.1.5", - "lodash.includes": "4.3.0", - "lodash.isboolean": "3.0.3", - "lodash.isinteger": "4.0.4", - "lodash.isnumber": "3.0.3", - "lodash.isplainobject": "4.0.6", - "lodash.isstring": "4.0.1", - "lodash.once": "4.1.1", - "ms": "2.1.1" - } - }, - "jwa": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", - "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.10", - "safe-buffer": "5.1.2" - } - }, - "jws": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", - "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", - "requires": { - "jwa": "1.1.6", - "safe-buffer": "5.1.2" - } - }, - "linked-list": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz", - "integrity": "sha1-eYsP+X0bkqT9CEgPVa6k6dSdN78=", - "dev": true - }, - "localStorage": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/localStorage/-/localStorage-1.0.4.tgz", - "integrity": "sha512-r35zrihcDiX+dqWlJSeIwS9nrF95OQTgqMFm3FB2D/+XgdmZtcutZOb7t0xXkhOEM8a9kpuu7cc28g1g36I5DQ==", - "dev": true - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" - }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "dev": true, - "requires": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "sc-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-2.0.0.tgz", - "integrity": "sha512-zLIg4GskHvkBM7gpKl7JrdU1FXVYsYCavsUeTILFIi/YsuOHLN9OTlFcMp6otb+ebpNEnpcDJI395YXZPif+fw==" - }, - "sc-formatter": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/sc-formatter/-/sc-formatter-3.0.2.tgz", - "integrity": "sha512-9PbqYBpCq+OoEeRQ3QfFIGE6qwjjBcd2j7UjgDlhnZbtSnuGgHdcRklPKYGuYFH82V/dwd+AIpu8XvA1zqTd+A==" - }, - "stream-demux": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stream-demux/-/stream-demux-4.0.4.tgz", - "integrity": "sha512-aSB1jS+0tPCnijI9BqEXbV9xC0JqkXnO869JIU4bC2isYMZizUdWm9g7NV0U9D4yaZ+K+HYtgI9XxAebOpkZjw==", - "requires": { - "async-iterable-stream": "3.0.1", - "writable-async-iterable-stream": "4.0.1" - } - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - }, - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "writable-async-iterable-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/writable-async-iterable-stream/-/writable-async-iterable-stream-4.0.1.tgz", - "integrity": "sha512-zhEBUFIQRtVi2zNyVRWBmFuNBPTYHqtyv8cOPtqDxU+h3IGre6vPpyCW60Es+uJ51EZ1+s7IjpJdvJ0rHMjbVw==", - "requires": { - "async-iterable-stream": "3.0.1" - } - }, - "ws": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.2.tgz", - "integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==", - "requires": { - "async-limiter": "1.0.0" - } - } - } -} diff --git a/package.json b/package.json index fd6457c..f0f173f 100644 --- a/package.json +++ b/package.json @@ -10,14 +10,14 @@ "dependencies": { "ag-auth": "^1.0.1", "ag-simple-broker": "^2.0.1", - "async-stream-emitter": "^1.1.0", + "async-stream-emitter": "^2.0.0", "base64id": "1.0.0", "lodash.clonedeep": "4.5.0", "sc-errors": "^2.0.0", "sc-formatter": "^3.0.2", - "stream-demux": "^4.0.4", + "stream-demux": "^5.1.0", "uuid": "3.2.1", - "writable-async-iterable-stream": "^4.0.1", + "writable-async-iterable-stream": "^5.1.0", "ws": "6.1.2" }, "devDependencies": { From 4ee047b84c44cf7e438f52726d90222174422d09 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 17 Jan 2019 21:13:17 +0100 Subject: [PATCH 146/265] v3.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f0f173f..b0b0505 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "3.0.2", + "version": "3.1.0", "description": "Server module for Asyngular", "main": "index.js", "repository": { From 106d2e89f8904fc0463d51fd38182a2d8ace801d Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 17 Jan 2019 21:24:55 +0100 Subject: [PATCH 147/265] Improve README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 22bdac6..5ff9f34 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,8 @@ See https://github.com/SocketCluster/stream-demux#usage - **More readable**: Code is written sequentially from top to bottom. It avoids event handler callback hell. It's also much easier to write and read complex integration test scenarios. - **More succinct**: Event streams can be easily chained, filtered and combined using a declarative syntax (e.g. using async generators). -- **More manageable**: No need to remember to unbind listeners with `removeListener(...)`; just `break` out of the `for-await-of` loop to stop consuming. This also encourages a more declarative style of coding. -- **Less error-prone**: Each event can be processed sequentially without missing any events. On the other hand, with `EventEmitter`, the listener function for the same event cannot be prevented from running multiple times in parallel; this can cause unintended side effects. +- **More manageable**: No need to remember to unbind listeners with `removeListener(...)`; just `break` out of the `for-await-of` loop to stop consuming. This also encourages a more declarative style of coding which reduces the likelihood of memory leaks and unintended side effects. +- **Less error-prone**: Each event/RPC/message can be processed sequentially in the same order that they were sent without missing any data; even if asynchronous calls are made inside middleware or listeners. On the other hand, with `EventEmitter`, the listener function for the same event cannot be prevented from running multiple times in parallel; also, asynchronous calls within middleware and listeners can affect the final order of actions; all this can cause unintended side effects. ## License From 811238b71db9e566877cdac8d75831e315cb33c1 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 17 Jan 2019 21:32:30 +0100 Subject: [PATCH 148/265] v3.2.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b0b0505..46e0edb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "3.1.0", + "version": "3.2.0", "description": "Server module for Asyngular", "main": "index.js", "repository": { @@ -9,7 +9,7 @@ }, "dependencies": { "ag-auth": "^1.0.1", - "ag-simple-broker": "^2.0.1", + "ag-simple-broker": "^3.0.0", "async-stream-emitter": "^2.0.0", "base64id": "1.0.0", "lodash.clonedeep": "4.5.0", From a575227ebb3a6b9f21c8e120561c6bfdd627984d Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 20 Jan 2019 11:56:49 +0100 Subject: [PATCH 149/265] Rename Request to AGrequest and Action to AGAction --- action.js | 40 ---------------------------------------- agaction.js | 40 ++++++++++++++++++++++++++++++++++++++++ agserver.js | 12 ++++++------ agserversocket.js | 34 +++++++++++++++++----------------- package.json | 1 + request.js | 38 -------------------------------------- test/integration.js | 18 +++++++++--------- 7 files changed, 73 insertions(+), 110 deletions(-) delete mode 100644 action.js create mode 100644 agaction.js delete mode 100644 request.js diff --git a/action.js b/action.js deleted file mode 100644 index 7808fb5..0000000 --- a/action.js +++ /dev/null @@ -1,40 +0,0 @@ -const scErrors = require('sc-errors'); -const InvalidActionError = scErrors.InvalidActionError; - -function Action() { - this.outcome = null; - this.promise = new Promise((resolve, reject) => { - this._resolve = resolve; - this._reject = reject; - }); - - this.allow = (packet) => { - if (this.outcome) { - throw new InvalidActionError(`Action ${this.type} has already been ${this.outcome}; cannot allow`); - } - this.outcome = 'allowed'; - this._resolve(packet); - }; - - this.block = (error) => { - if (this.outcome) { - throw new InvalidActionError(`Action ${this.type} has already been ${this.outcome}; cannot block`); - } - this.outcome = 'blocked'; - this._reject(error); - }; -} - -Action.prototype.HANDSHAKE_WS = Action.HANDSHAKE_WS = 'handshakeWS'; -Action.prototype.HANDSHAKE_AG = Action.HANDSHAKE_AG = 'handshakeAG'; - -Action.prototype.MESSAGE = Action.MESSAGE = 'message'; - -Action.prototype.TRANSMIT = Action.TRANSMIT = 'transmit'; -Action.prototype.INVOKE = Action.INVOKE = 'invoke'; -Action.prototype.SUBSCRIBE = Action.SUBSCRIBE = 'subscribe'; -Action.prototype.PUBLISH_IN = Action.PUBLISH_IN = 'publishIn'; -Action.prototype.PUBLISH_OUT = Action.PUBLISH_OUT = 'publishOut'; -Action.prototype.AUTHENTICATE = Action.AUTHENTICATE = 'authenticate'; - -module.exports = Action; diff --git a/agaction.js b/agaction.js new file mode 100644 index 0000000..ed5416e --- /dev/null +++ b/agaction.js @@ -0,0 +1,40 @@ +const scErrors = require('sc-errors'); +const InvalidActionError = scErrors.InvalidActionError; + +function AGAction() { + this.outcome = null; + this.promise = new Promise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + }); + + this.allow = (packet) => { + if (this.outcome) { + throw new InvalidActionError(`AGAction ${this.type} has already been ${this.outcome}; cannot allow`); + } + this.outcome = 'allowed'; + this._resolve(packet); + }; + + this.block = (error) => { + if (this.outcome) { + throw new InvalidActionError(`AGAction ${this.type} has already been ${this.outcome}; cannot block`); + } + this.outcome = 'blocked'; + this._reject(error); + }; +} + +AGAction.prototype.HANDSHAKE_WS = AGAction.HANDSHAKE_WS = 'handshakeWS'; +AGAction.prototype.HANDSHAKE_AG = AGAction.HANDSHAKE_AG = 'handshakeAG'; + +AGAction.prototype.MESSAGE = AGAction.MESSAGE = 'message'; + +AGAction.prototype.TRANSMIT = AGAction.TRANSMIT = 'transmit'; +AGAction.prototype.INVOKE = AGAction.INVOKE = 'invoke'; +AGAction.prototype.SUBSCRIBE = AGAction.SUBSCRIBE = 'subscribe'; +AGAction.prototype.PUBLISH_IN = AGAction.PUBLISH_IN = 'publishIn'; +AGAction.prototype.PUBLISH_OUT = AGAction.PUBLISH_OUT = 'publishOut'; +AGAction.prototype.AUTHENTICATE = AGAction.AUTHENTICATE = 'authenticate'; + +module.exports = AGAction; diff --git a/agserver.js b/agserver.js index 1d9aeaa..afd5786 100644 --- a/agserver.js +++ b/agserver.js @@ -8,7 +8,7 @@ const uuid = require('uuid'); const AGSimpleBroker = require('ag-simple-broker'); const AsyncStreamEmitter = require('async-stream-emitter'); const WritableAsyncIterableStream = require('writable-async-iterable-stream'); -const Action = require('./action'); +const AGAction = require('./agaction'); const scErrors = require('sc-errors'); const SilentMiddlewareBlockedError = scErrors.SilentMiddlewareBlockedError; @@ -476,8 +476,8 @@ AGServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { let signedAuthToken = data.authToken || null; clearTimeout(agSocket._handshakeTimeoutRef); - let action = new Action(); - action.type = Action.HANDSHAKE_AG; + let action = new AGAction(); + action.type = AGAction.HANDSHAKE_AG; action.request = agSocket.request; action.socket = agSocket; @@ -621,7 +621,7 @@ AGServer.prototype._processMiddlewareAction = async function (middlewareStream, let clientError; if (error.silent) { clientError = new SilentMiddlewareBlockedError( - `Action was blocked by ${action.name} middleware`, + `AGAction was blocked by ${action.name} middleware`, action.name ); } else { @@ -674,8 +674,8 @@ AGServer.prototype.verifyHandshake = async function (info, callback) { handshakeMiddleware(middlewareHandshakeStream); } - let action = new Action(); - action.type = Action.HANDSHAKE_WS; + let action = new AGAction(); + action.type = AGAction.HANDSHAKE_WS; action.request = req; try { diff --git a/agserversocket.js b/agserversocket.js index efd588d..03e289a 100644 --- a/agserversocket.js +++ b/agserversocket.js @@ -2,8 +2,8 @@ const cloneDeep = require('lodash.clonedeep'); const WritableAsyncIterableStream = require('writable-async-iterable-stream'); const StreamDemux = require('stream-demux'); const AsyncStreamEmitter = require('async-stream-emitter'); -const Action = require('./action'); -const Request = require('./request'); +const AGAction = require('./agaction'); +const AGRequest = require('ag-request'); const scErrors = require('sc-errors'); const InvalidArgumentsError = scErrors.InvalidArgumentsError; @@ -79,9 +79,9 @@ function AGServerSocket(id, server, socket) { this._resetPongTimeout(); if (this.server.hasMiddleware(this.server.MIDDLEWARE_INBOUND_RAW)) { - let action = new Action(); + let action = new AGAction(); action.socket = this; - action.type = Action.MESSAGE; + action.type = AGAction.MESSAGE; action.data = message; try { @@ -180,7 +180,7 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message if (eventName === '#handshake' || eventName === '#authenticate') { // Let AGServer handle these events. - let request = new Request(this, packet.cid, eventName, packet.data); + let request = new AGRequest(this, packet.cid, eventName, packet.data); this._procedureDemux.write(eventName, request); return; @@ -192,7 +192,7 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message } - let action = new Action(); + let action = new AGAction(); action.socket = this; let tokenExpiredError = this._processAuthTokenExpiry(); @@ -209,37 +209,37 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message this.emitError(error); if (isRPC) { - let request = new Request(this, packet.cid, eventName, packet.data); + let request = new AGRequest(this, packet.cid, eventName, packet.data); request.error(error); } return; } - action.type = Action.PUBLISH_IN; + action.type = AGAction.PUBLISH_IN; if (packet.data) { action.channel = packet.data.channel; action.data = packet.data.data; } } else if (isSubscribe) { - action.type = Action.SUBSCRIBE; + action.type = AGAction.SUBSCRIBE; if (packet.data) { action.channel = packet.data.channel; action.data = packet.data.data; } } else if (eventName === '#unsubscribe') { // Let AGServer handle this event. - let request = new Request(this, packet.cid, eventName, packet.data); + let request = new AGRequest(this, packet.cid, eventName, packet.data); this._procedureDemux.write(eventName, request); return; } else { if (isRPC) { - action.type = Action.INVOKE; + action.type = AGAction.INVOKE; action.procedure = packet.event; if (packet.data !== undefined) { action.data = packet.data; } } else { - action.type = Action.TRANSMIT; + action.type = AGAction.TRANSMIT; action.receiver = packet.event; if (packet.data !== undefined) { action.data = packet.data; @@ -250,7 +250,7 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message let newData; if (isRPC) { - let request = new Request(this, packet.cid, eventName, packet.data); + let request = new AGRequest(this, packet.cid, eventName, packet.data); try { let {data} = await this.server._processMiddlewareAction(this._middlewareInboundStream, action, this); newData = data; @@ -492,8 +492,8 @@ AGServerSocket.prototype.transmit = async function (event, data, options) { let packet = {event, data}; let isPublish = event === '#publish'; if (isPublish) { - let action = new Action(); - action.type = Action.PUBLISH_OUT; + let action = new AGAction(); + action.type = AGAction.PUBLISH_OUT; action.socket = this; if (data !== undefined) { @@ -812,8 +812,8 @@ AGServerSocket.prototype._processAuthToken = async function (signedAuthToken) { this.authToken = authToken; this.authState = this.AUTHENTICATED; - let action = new Action(); - action.type = Action.AUTHENTICATE; + let action = new AGAction(); + action.type = AGAction.AUTHENTICATE; action.socket = this; action.signedAuthToken = this.signedAuthToken; action.authToken = this.authToken; diff --git a/package.json b/package.json index 46e0edb..03fef78 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "ag-auth": "^1.0.1", + "ag-request": "^1.0.0", "ag-simple-broker": "^3.0.0", "async-stream-emitter": "^2.0.0", "base64id": "1.0.0", diff --git a/request.js b/request.js deleted file mode 100644 index 55199ab..0000000 --- a/request.js +++ /dev/null @@ -1,38 +0,0 @@ -const scErrors = require('sc-errors'); -const InvalidActionError = scErrors.InvalidActionError; - -function Request(socket, id, procedureName, data) { - this.socket = socket; - this.id = id; - this.procedure = procedureName; - this.data = data; - this.sent = false; - - this._respond = (responseData, options) => { - if (this.sent) { - throw new InvalidActionError(`Response to request ${this.id} has already been sent`); - } - this.sent = true; - this.socket.sendObject(responseData, options); - }; - - this.end = (data, options) => { - let responseData = { - rid: this.id - }; - if (data !== undefined) { - responseData.data = data; - } - this._respond(responseData, options); - }; - - this.error = (error, options) => { - let responseData = { - rid: this.id, - error: scErrors.dehydrateError(error) - }; - this._respond(responseData, options); - }; -} - -module.exports = Request; diff --git a/test/integration.js b/test/integration.js index 9797130..1ed9f43 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1,6 +1,6 @@ const assert = require('assert'); const asyngularServer = require('../'); -const Action = require('../action'); +const AGAction = require('../agaction'); const asyngularClient = require('asyngular-client'); const localStorage = require('localStorage'); const AGSimpleBroker = require('ag-simple-broker'); @@ -178,7 +178,7 @@ describe('Integration tests', function () { server.setMiddleware(server.MIDDLEWARE_INBOUND, async (middlewareStream) => { for await (let action of middlewareStream) { if ( - action.type === Action.AUTHENTICATE && + action.type === AGAction.AUTHENTICATE && (!action.authToken || action.authToken.username === 'alice') ) { let err = new Error('Blocked by MIDDLEWARE_INBOUND'); @@ -1355,7 +1355,7 @@ describe('Integration tests', function () { // though they were sent as a batch/array. server.setMiddleware(server.MIDDLEWARE_INBOUND, async function (middlewareStream) { for await (let action of middlewareStream) { - if (action.type === Action.SUBSCRIBE) { + if (action.type === AGAction.SUBSCRIBE) { subscribeMiddlewareCounter++; assert.equal(action.channel.indexOf('my-channel-'), 0); if (action.channel === 'my-channel-10') { @@ -2066,7 +2066,7 @@ describe('Integration tests', function () { middlewareFunction = async function (middlewareStream) { for await (let {type, allow, block} of middlewareStream) { - if (type === Action.HANDSHAKE_AG) { + if (type === AGAction.HANDSHAKE_AG) { await wait(100); middlewareWasExecuted = true; let err = new Error('AG handshake failed because the server was too lazy'); @@ -2119,7 +2119,7 @@ describe('Integration tests', function () { middlewareFunction = async function (middlewareStream) { for await (let {type, allow, block} of middlewareStream) { - if (type === Action.HANDSHAKE_AG) { + if (type === AGAction.HANDSHAKE_AG) { await wait(100); middlewareWasExecuted = true; let err = new Error('AG handshake failed because the server was too lazy'); @@ -2156,7 +2156,7 @@ describe('Integration tests', function () { middlewareFunction = async function (middlewareStream) { for await (let {type, allow, block} of middlewareStream) { - if (type === Action.HANDSHAKE_AG) { + if (type === AGAction.HANDSHAKE_AG) { await wait(100); middlewareWasExecuted = true; let err = new Error('AG handshake failed because of invalid query auth parameters'); @@ -2198,7 +2198,7 @@ describe('Integration tests', function () { middlewareFunction = async function (middlewareStream) { for await (let {type, allow} of middlewareStream) { - if (type === Action.HANDSHAKE_AG) { + if (type === AGAction.HANDSHAKE_AG) { await wait(500); } allow(); @@ -2230,7 +2230,7 @@ describe('Integration tests', function () { it('Should not run AUTHENTICATE action in middleware if JWT token does not exist', async function () { middlewareFunction = async function (middlewareStream) { for await (let {type, allow} of middlewareStream) { - if (type === Action.AUTHENTICATE) { + if (type === AGAction.AUTHENTICATE) { middlewareWasExecuted = true; } allow(); @@ -2252,7 +2252,7 @@ describe('Integration tests', function () { middlewareFunction = async function (middlewareStream) { for await (let {type, allow} of middlewareStream) { - if (type === Action.AUTHENTICATE) { + if (type === AGAction.AUTHENTICATE) { middlewareWasExecuted = true; } allow(); From 13cb89f4b829dda8845bebf856d674b627f01960 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 20 Jan 2019 11:59:03 +0100 Subject: [PATCH 150/265] v3.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 03fef78..fc3eab8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "3.2.0", + "version": "3.2.1", "description": "Server module for Asyngular", "main": "index.js", "repository": { From 2107bea63358fcce3321d8431c9e5428a39dd5f0 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 20 Jan 2019 12:14:15 +0100 Subject: [PATCH 151/265] v3.2.2 - Rename files --- agaction.js => action.js | 0 index.js | 4 ++-- package.json | 2 +- agserver.js => server.js | 4 ++-- agserversocket.js => serversocket.js | 2 +- test/integration.js | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) rename agaction.js => action.js (100%) rename agserver.js => server.js (99%) rename agserversocket.js => serversocket.js (99%) diff --git a/agaction.js b/action.js similarity index 100% rename from agaction.js rename to action.js diff --git a/index.js b/index.js index c0bd42e..537f514 100644 --- a/index.js +++ b/index.js @@ -10,7 +10,7 @@ const http = require('http'); * @api public */ -module.exports.AGServer = require('./agserver'); +module.exports.AGServer = require('./server'); /** * Expose AGServerSocket constructor. @@ -18,7 +18,7 @@ module.exports.AGServer = require('./agserver'); * @api public */ -module.exports.AGServerSocket = require('./agserversocket'); +module.exports.AGServerSocket = require('./serversocket'); /** * Creates an http.Server exclusively used for WS upgrades. diff --git a/package.json b/package.json index fc3eab8..156d190 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "3.2.1", + "version": "3.2.2", "description": "Server module for Asyngular", "main": "index.js", "repository": { diff --git a/agserver.js b/server.js similarity index 99% rename from agserver.js rename to server.js index afd5786..4a966bd 100644 --- a/agserver.js +++ b/server.js @@ -1,4 +1,4 @@ -const AGServerSocket = require('./agserversocket'); +const AGServerSocket = require('./serversocket'); const AuthEngine = require('ag-auth'); const formatter = require('sc-formatter'); const base64id = require('base64id'); @@ -8,7 +8,7 @@ const uuid = require('uuid'); const AGSimpleBroker = require('ag-simple-broker'); const AsyncStreamEmitter = require('async-stream-emitter'); const WritableAsyncIterableStream = require('writable-async-iterable-stream'); -const AGAction = require('./agaction'); +const AGAction = require('./action'); const scErrors = require('sc-errors'); const SilentMiddlewareBlockedError = scErrors.SilentMiddlewareBlockedError; diff --git a/agserversocket.js b/serversocket.js similarity index 99% rename from agserversocket.js rename to serversocket.js index 03e289a..173b21f 100644 --- a/agserversocket.js +++ b/serversocket.js @@ -2,7 +2,7 @@ const cloneDeep = require('lodash.clonedeep'); const WritableAsyncIterableStream = require('writable-async-iterable-stream'); const StreamDemux = require('stream-demux'); const AsyncStreamEmitter = require('async-stream-emitter'); -const AGAction = require('./agaction'); +const AGAction = require('./action'); const AGRequest = require('ag-request'); const scErrors = require('sc-errors'); diff --git a/test/integration.js b/test/integration.js index 1ed9f43..10d9d9e 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1,6 +1,6 @@ const assert = require('assert'); const asyngularServer = require('../'); -const AGAction = require('../agaction'); +const AGAction = require('../action'); const asyngularClient = require('asyngular-client'); const localStorage = require('localStorage'); const AGSimpleBroker = require('ag-simple-broker'); From 67181e98337f3200ada2286518101ad08de0b616 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 20 Jan 2019 16:16:59 +0100 Subject: [PATCH 152/265] Change order of properties --- serversocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serversocket.js b/serversocket.js index 173b21f..8c3d44b 100644 --- a/serversocket.js +++ b/serversocket.js @@ -80,8 +80,8 @@ function AGServerSocket(id, server, socket) { if (this.server.hasMiddleware(this.server.MIDDLEWARE_INBOUND_RAW)) { let action = new AGAction(); - action.socket = this; action.type = AGAction.MESSAGE; + action.socket = this; action.data = message; try { From d9f5df6e59439a7cb034b7f54bfcbfb877ace8b7 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 20 Jan 2019 16:21:54 +0100 Subject: [PATCH 153/265] Always put action socket before type for consistency --- server.js | 4 ++-- serversocket.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server.js b/server.js index 4a966bd..29e6c6d 100644 --- a/server.js +++ b/server.js @@ -477,9 +477,9 @@ AGServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { clearTimeout(agSocket._handshakeTimeoutRef); let action = new AGAction(); - action.type = AGAction.HANDSHAKE_AG; action.request = agSocket.request; action.socket = agSocket; + action.type = AGAction.HANDSHAKE_AG; let middlewareHandshakeStream = agSocket.request[this.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; @@ -675,8 +675,8 @@ AGServer.prototype.verifyHandshake = async function (info, callback) { } let action = new AGAction(); - action.type = AGAction.HANDSHAKE_WS; action.request = req; + action.type = AGAction.HANDSHAKE_WS; try { await this._processMiddlewareAction(middlewareHandshakeStream, action); diff --git a/serversocket.js b/serversocket.js index 8c3d44b..c994f03 100644 --- a/serversocket.js +++ b/serversocket.js @@ -80,8 +80,8 @@ function AGServerSocket(id, server, socket) { if (this.server.hasMiddleware(this.server.MIDDLEWARE_INBOUND_RAW)) { let action = new AGAction(); - action.type = AGAction.MESSAGE; action.socket = this; + action.type = AGAction.MESSAGE; action.data = message; try { @@ -493,8 +493,8 @@ AGServerSocket.prototype.transmit = async function (event, data, options) { let isPublish = event === '#publish'; if (isPublish) { let action = new AGAction(); - action.type = AGAction.PUBLISH_OUT; action.socket = this; + action.type = AGAction.PUBLISH_OUT; if (data !== undefined) { action.channel = data.channel; @@ -813,8 +813,8 @@ AGServerSocket.prototype._processAuthToken = async function (signedAuthToken) { this.authState = this.AUTHENTICATED; let action = new AGAction(); - action.type = AGAction.AUTHENTICATE; action.socket = this; + action.type = AGAction.AUTHENTICATE; action.signedAuthToken = this.signedAuthToken; action.authToken = this.authToken; From da183ee6de9f82f822901271fb25c8af464aeef8 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 22 Jan 2019 21:47:19 +0100 Subject: [PATCH 154/265] Use empty string as default ping/pong mechanism --- README.md | 11 ++++++++++ package.json | 1 - server.js | 9 ++++---- serversocket.js | 58 +++++++++++++++++++++++++++++++------------------ 4 files changed, 52 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 5ff9f34..d794ae5 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,17 @@ Also, see tests from the `asyngular-client` module. Asyngular can work without the `for-await-of` loop; a `while` loop with `await` statements can be used instead. See https://github.com/SocketCluster/stream-demux#usage +## Compatibility mode + +For compatibility with existing SocketCluster clients, set the `protocolVersion` to `1` and make sure that the `path` matches your old client path: + +```js +let agServer = asyngularServer.attach(httpServer, { + protocolVersion: 1, + path: '/socketcluster/' +}); +``` + ## Running the tests - Clone this repo: `git clone git@github.com:SocketCluster/asyngular-server.git` diff --git a/package.json b/package.json index 156d190..62aeedc 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "sc-errors": "^2.0.0", "sc-formatter": "^3.0.2", "stream-demux": "^5.1.0", - "uuid": "3.2.1", "writable-async-iterable-stream": "^5.1.0", "ws": "6.1.2" }, diff --git a/server.js b/server.js index 29e6c6d..6cf8745 100644 --- a/server.js +++ b/server.js @@ -4,7 +4,6 @@ const formatter = require('sc-formatter'); const base64id = require('base64id'); const url = require('url'); const crypto = require('crypto'); -const uuid = require('uuid'); const AGSimpleBroker = require('ag-simple-broker'); const AsyncStreamEmitter = require('async-stream-emitter'); const WritableAsyncIterableStream = require('writable-async-iterable-stream'); @@ -35,8 +34,8 @@ function AGServer(options) { pingTimeoutDisabled: false, pingInterval: 8000, origins: '*:*', - appName: uuid.v4(), - path: '/socketcluster/', + path: '/asyngular/', + protocolVersion: 2, authDefaultExpiry: 86400, pubSubBatchDuration: null, middlewareEmitFailures: true @@ -58,9 +57,9 @@ function AGServer(options) { this.perMessageDeflate = opts.perMessageDeflate; this.httpServer = opts.httpServer; this.socketChannelLimit = opts.socketChannelLimit; + this.protocolVersion = opts.protocolVersion; this.brokerEngine = opts.brokerEngine; - this.appName = opts.appName || ''; this.middlewareEmitFailures = opts.middlewareEmitFailures; // Make sure there is always a leading and a trailing slash in the WS path. @@ -291,7 +290,7 @@ AGServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { let socketId = this.generateId(); - let agSocket = new AGServerSocket(socketId, this, wsSocket); + let agSocket = new AGServerSocket(socketId, this, wsSocket, this.protocolVersion); agSocket.exchange = this.exchange; let inboundRawMiddleware = this._middleware[this.MIDDLEWARE_INBOUND_RAW]; diff --git a/serversocket.js b/serversocket.js index c994f03..7338800 100644 --- a/serversocket.js +++ b/serversocket.js @@ -18,7 +18,7 @@ const AuthTokenNotBeforeError = scErrors.AuthTokenNotBeforeError; const AuthTokenError = scErrors.AuthTokenError; const SilentMiddlewareBlockedError = scErrors.SilentMiddlewareBlockedError; -function AGServerSocket(id, server, socket) { +function AGServerSocket(id, server, socket, protocolVersion) { AsyncStreamEmitter.call(this); this.id = id; @@ -26,6 +26,7 @@ function AGServerSocket(id, server, socket) { this.socket = socket; this.state = this.CONNECTING; this.authState = this.UNAUTHENTICATED; + this.protocolVersion = protocolVersion; this._receiverDemux = new StreamDemux(); this._procedureDemux = new StreamDemux(); @@ -69,6 +70,23 @@ function AGServerSocket(id, server, socket) { this._onClose(code, data); }); + let pongMessage; + if (this.protocolVersion === 1) { + pongMessage = '#2'; + this._sendPing = () => { + if (this.state !== this.CLOSED) { + this.sendObject('#1'); + } + }; + } else { + pongMessage = ''; + this._sendPing = () => { + if (this.state !== this.CLOSED) { + this.send(''); + } + }; + } + if (!this.server.pingTimeoutDisabled) { this._pingIntervalTicker = setInterval(this._sendPing.bind(this), this.server.pingInterval); } @@ -76,7 +94,11 @@ function AGServerSocket(id, server, socket) { // Receive incoming raw messages this.socket.on('message', async (message, flags) => { - this._resetPongTimeout(); + let isPong = message === pongMessage; + + if (isPong) { + this._resetPongTimeout(); + } if (this.server.hasMiddleware(this.server.MIDDLEWARE_INBOUND_RAW)) { let action = new AGAction(); @@ -95,6 +117,14 @@ function AGServerSocket(id, server, socket) { this.emit('message', {message}); + if (isPong) { + let token = this.getAuthToken(); + if (this.isAuthTokenExpired(token)) { + this.deauthenticate(); + } + return; + } + let packet; try { packet = this.decode(message); @@ -106,21 +136,13 @@ function AGServerSocket(id, server, socket) { return; } - // If pong - if (packet === '#2') { - let token = this.getAuthToken(); - if (this.isAuthTokenExpired(token)) { - this.deauthenticate(); + if (Array.isArray(packet)) { + let len = packet.length; + for (let i = 0; i < len; i++) { + this._processInboundPacket(packet[i], message); } } else { - if (Array.isArray(packet)) { - let len = packet.length; - for (let i = 0; i < len; i++) { - this._processInboundPacket(packet[i], message); - } - } else { - this._processInboundPacket(packet, message); - } + this._processInboundPacket(packet, message); } }); } @@ -153,12 +175,6 @@ AGServerSocket.prototype.closeProcedure = function (procedureName) { this._procedureDemux.close(procedureName); }; -AGServerSocket.prototype._sendPing = function () { - if (this.state !== this.CLOSED) { - this.sendObject('#1'); - } -}; - AGServerSocket.prototype._processInboundPublishPacket = async function (packet) { if (typeof packet.channel !== 'string') { let error = new InvalidActionError(`Socket ${this.id} tried to publish to an invalid "${publishPacket.channel}" channel`); From af8a54c653270f398ff698f71ee5f1e74cfdfb18 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 22 Jan 2019 22:01:46 +0100 Subject: [PATCH 155/265] v4.0.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 62aeedc..5cf3c00 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "3.2.2", + "version": "4.0.0", "description": "Server module for Asyngular", "main": "index.js", "repository": { @@ -21,7 +21,7 @@ "ws": "6.1.2" }, "devDependencies": { - "asyngular-client": "^3.0.2", + "asyngular-client": "^4.0.0", "localStorage": "^1.0.3", "mocha": "5.2.0" }, From 0c161be0453cee2fdaee4f7da416d5001dafbd74 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 24 Jan 2019 22:57:54 +0100 Subject: [PATCH 156/265] Fix bug with origin over https --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index 6cf8745..dd80959 100644 --- a/server.js +++ b/server.js @@ -656,7 +656,7 @@ AGServer.prototype.verifyHandshake = async function (info, callback) { } else { try { let parts = url.parse(origin); - parts.port = parts.port || 80; + parts.port = parts.port || (parts.protocol === 'https:' ? 443 : 80); ok = ~this.origins.indexOf(parts.hostname + ':' + parts.port) || ~this.origins.indexOf(parts.hostname + ':*') || ~this.origins.indexOf('*:' + parts.port); From 2a274a3b42b4e5cb68607e96d4e09be3accc0217 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 24 Jan 2019 22:58:22 +0100 Subject: [PATCH 157/265] v4.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5cf3c00..715c0d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "4.0.0", + "version": "4.0.1", "description": "Server module for Asyngular", "main": "index.js", "repository": { From 4c046329c45131368a6595dee05a332110bfc6ee Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 28 Jan 2019 19:07:57 +0100 Subject: [PATCH 158/265] Fix issue with publish and subscribe falling out of order --- server.js | 328 +-------------------------------- serversocket.js | 433 ++++++++++++++++++++++++++++++++++++++------ test/integration.js | 271 ++++++++++++++++++++++++++- 3 files changed, 642 insertions(+), 390 deletions(-) diff --git a/server.js b/server.js index dd80959..3f81139 100644 --- a/server.js +++ b/server.js @@ -14,11 +14,8 @@ const SilentMiddlewareBlockedError = scErrors.SilentMiddlewareBlockedError; const InvalidArgumentsError = scErrors.InvalidArgumentsError; const InvalidOptionsError = scErrors.InvalidOptionsError; const InvalidActionError = scErrors.InvalidActionError; -const BrokerError = scErrors.BrokerError; const ServerProtocolError = scErrors.ServerProtocolError; -const HANDSHAKE_REJECTION_STATUS_CODE = 4008; - function AGServer(options) { AsyncStreamEmitter.call(this); @@ -207,81 +204,6 @@ AGServer.prototype._handleServerError = function (error) { this.emitError(error); }; -AGServer.prototype._handleHandshakeTimeout = function (agSocket) { - let middlewareHandshakeStream = agSocket.request[this.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; - agSocket.disconnect(4005); -}; - -AGServer.prototype._subscribeSocket = async function (socket, channelName, subscriptionOptions) { - if (channelName === undefined || !subscriptionOptions) { - throw new InvalidActionError(`Socket ${socket.id} provided a malformated channel payload`); - } - - if (this.socketChannelLimit && socket.channelSubscriptionsCount >= this.socketChannelLimit) { - throw new InvalidActionError( - `Socket ${socket.id} tried to exceed the channel subscription limit of ${this.socketChannelLimit}` - ); - } - - if (typeof channelName !== 'string') { - throw new InvalidActionError(`Socket ${socket.id} provided an invalid channel name`); - } - - if (socket.channelSubscriptionsCount == null) { - socket.channelSubscriptionsCount = 0; - } - if (socket.channelSubscriptions[channelName] == null) { - socket.channelSubscriptions[channelName] = true; - socket.channelSubscriptionsCount++; - } - - try { - await this.brokerEngine.subscribeSocket(socket, channelName); - } catch (err) { - delete socket.channelSubscriptions[channelName]; - socket.channelSubscriptionsCount--; - throw err; - } - socket.emit('subscribe', { - channel: channelName, - subscriptionOptions - }); - this.emit('subscription', { - socket, - channel: channelName, - subscriptionOptions - }); -}; - -AGServer.prototype._unsubscribeSocketFromAllChannels = function (socket) { - Object.keys(socket.channelSubscriptions).forEach((channelName) => { - this._unsubscribeSocket(socket, channelName); - }); -}; - -AGServer.prototype._unsubscribeSocket = function (socket, channel) { - if (typeof channel !== 'string') { - throw new InvalidActionError( - `Socket ${socket.id} tried to unsubscribe from an invalid channel name` - ); - } - if (!socket.channelSubscriptions[channel]) { - throw new InvalidActionError( - `Socket ${socket.id} tried to unsubscribe from a channel which it is not subscribed to` - ); - } - - delete socket.channelSubscriptions[channel]; - if (socket.channelSubscriptionsCount != null) { - socket.channelSubscriptionsCount--; - } - - this.brokerEngine.unsubscribeSocket(socket, channel); - - socket.emit('unsubscribe', {channel}); - this.emit('unsubscription', {socket, channel}); -}; - AGServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { if (!wsSocket.upgradeReq) { // Normalize ws modules to match. @@ -295,263 +217,19 @@ AGServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { let inboundRawMiddleware = this._middleware[this.MIDDLEWARE_INBOUND_RAW]; if (inboundRawMiddleware) { - inboundRawMiddleware(agSocket._middlewareInboundRawStream); + inboundRawMiddleware(agSocket.middlewareInboundRawStream); } let inboundMiddleware = this._middleware[this.MIDDLEWARE_INBOUND]; if (inboundMiddleware) { - inboundMiddleware(agSocket._middlewareInboundStream); + inboundMiddleware(agSocket.middlewareInboundStream); } let outboundMiddleware = this._middleware[this.MIDDLEWARE_OUTBOUND]; if (outboundMiddleware) { - outboundMiddleware(agSocket._middlewareOutboundStream); + outboundMiddleware(agSocket.middlewareOutboundStream); } - this.pendingClients[socketId] = agSocket; - this.pendingClientsCount++; - - let handleSocketAuthenticate = async () => { - for await (let rpc of agSocket.procedure('#authenticate')) { - let signedAuthToken = rpc.data; - let oldAuthState = agSocket.authState; - try { - await agSocket._processAuthToken(signedAuthToken); - } catch (error) { - if (error.isBadToken) { - agSocket.deauthenticate(); - rpc.error(error); - - return; - } - - rpc.end({ - isAuthenticated: !!agSocket.authToken, - authError: signedAuthToken == null ? null : scErrors.dehydrateError(error) - }); - - return; - } - agSocket.triggerAuthenticationEvents(oldAuthState); - rpc.end({ - isAuthenticated: !!agSocket.authToken, - authError: null - }); - } - }; - handleSocketAuthenticate(); - - let handleSocketRemoveAuthToken = async () => { - for await (let data of agSocket.receiver('#removeAuthToken')) { - agSocket.deauthenticateSelf(); - } - }; - handleSocketRemoveAuthToken(); - - let handleSocketSubscribe = async () => { - for await (let rpc of agSocket.procedure('#subscribe')) { - let subscriptionOptions = Object.assign({}, rpc.data); - let channelName = subscriptionOptions.channel; - delete subscriptionOptions.channel; - - (async () => { - if (agSocket.state === agSocket.OPEN) { - try { - await this._subscribeSocket(agSocket, channelName, subscriptionOptions); - } catch (err) { - let error = new BrokerError(`Failed to subscribe socket to the ${channelName} channel - ${err}`); - rpc.error(error); - agSocket.emitError(error); - - return; - } - if (subscriptionOptions.batch) { - rpc.end(undefined, {batch: true}); - - return; - } - rpc.end(); - - return; - } - // This is an invalid state; it means the client tried to subscribe before - // having completed the handshake. - let error = new InvalidActionError('Cannot subscribe socket to a channel before it has completed the handshake'); - rpc.error(error); - this.emitWarning(error); - })(); - } - }; - handleSocketSubscribe(); - - let handleSocketUnsubscribe = async () => { - for await (let rpc of agSocket.procedure('#unsubscribe')) { - let channel = rpc.data; - let error; - try { - this._unsubscribeSocket(agSocket, channel); - } catch (err) { - error = new BrokerError( - `Failed to unsubscribe socket from the ${channel} channel - ${err}` - ); - } - if (error) { - rpc.error(error); - agSocket.emitError(error); - } else { - rpc.end(); - } - } - }; - handleSocketUnsubscribe(); - - let cleanupSocket = (type, code, reason) => { - clearTimeout(agSocket._handshakeTimeoutRef); - - agSocket.closeProcedure('#handshake'); - agSocket.closeProcedure('#authenticate'); - agSocket.closeProcedure('#subscribe'); - agSocket.closeProcedure('#unsubscribe'); - agSocket.closeReceiver('#removeAuthToken'); - - let middlewareHandshakeStream = agSocket.request[this.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; - middlewareHandshakeStream.close(); - agSocket._middlewareInboundRawStream.close(); - agSocket._middlewareInboundStream.close(); - agSocket._middlewareOutboundStream.close(); - - let isClientFullyConnected = !!this.clients[socketId]; - - if (isClientFullyConnected) { - delete this.clients[socketId]; - this.clientsCount--; - } - - let isClientPending = !!this.pendingClients[socketId]; - if (isClientPending) { - delete this.pendingClients[socketId]; - this.pendingClientsCount--; - } - - if (type === 'disconnect') { - this.emit('disconnection', { - socket: agSocket, - code, - reason - }); - } else if (type === 'abort') { - this.emit('connectionAbort', { - socket: agSocket, - code, - reason - }); - } - this.emit('closure', { - socket: agSocket, - code, - reason - }); - - this._unsubscribeSocketFromAllChannels(agSocket); - }; - - let handleSocketDisconnect = async () => { - let event = await agSocket.listener('disconnect').once(); - cleanupSocket('disconnect', event.code, event.data); - }; - handleSocketDisconnect(); - - let handleSocketAbort = async () => { - let event = await agSocket.listener('connectAbort').once(); - cleanupSocket('abort', event.code, event.data); - }; - handleSocketAbort(); - - agSocket._handshakeTimeoutRef = setTimeout(this._handleHandshakeTimeout.bind(this, agSocket), this.handshakeTimeout); - - let handleSocketHandshake = async () => { - for await (let rpc of agSocket.procedure('#handshake')) { - let data = rpc.data || {}; - let signedAuthToken = data.authToken || null; - clearTimeout(agSocket._handshakeTimeoutRef); - - let action = new AGAction(); - action.request = agSocket.request; - action.socket = agSocket; - action.type = AGAction.HANDSHAKE_AG; - - let middlewareHandshakeStream = agSocket.request[this.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; - - try { - await this._processMiddlewareAction(middlewareHandshakeStream, action); - } catch (error) { - if (error.statusCode == null) { - error.statusCode = HANDSHAKE_REJECTION_STATUS_CODE; - } - rpc.error(error); - agSocket.disconnect(error.statusCode); - return; - } - - let clientSocketStatus = { - id: socketId, - pingTimeout: this.pingTimeout - }; - let serverSocketStatus = { - id: socketId, - pingTimeout: this.pingTimeout - }; - - let oldAuthState = agSocket.authState; - try { - await agSocket._processAuthToken(signedAuthToken); - if (agSocket.state === agSocket.CLOSED) { - return; - } - } catch (error) { - if (signedAuthToken != null) { - // Because the token is optional as part of the handshake, we don't count - // it as an error if the token wasn't provided. - clientSocketStatus.authError = scErrors.dehydrateError(error); - serverSocketStatus.authError = error; - - if (error.isBadToken) { - agSocket.deauthenticate(); - } - } - } - clientSocketStatus.isAuthenticated = !!agSocket.authToken; - serverSocketStatus.isAuthenticated = clientSocketStatus.isAuthenticated; - - if (this.pendingClients[socketId]) { - delete this.pendingClients[socketId]; - this.pendingClientsCount--; - } - this.clients[socketId] = agSocket; - this.clientsCount++; - - agSocket.state = agSocket.OPEN; - - if (clientSocketStatus.isAuthenticated) { - // Needs to be executed after the connection event to allow - // consumers to be setup from inside the connection loop. - (async () => { - await this.listener('connection').once(); - agSocket.triggerAuthenticationEvents(oldAuthState); - })(); - } - - agSocket.emit('connect', serverSocketStatus); - this.emit('connection', {socket: agSocket, ...serverSocketStatus}); - - // Treat authentication failure as a 'soft' error - rpc.end(clientSocketStatus); - - middlewareHandshakeStream.close(); - } - }; - handleSocketHandshake(); - // Emit event to signal that a socket handshake has been initiated. this.emit('handshake', {socket: agSocket}); }; diff --git a/serversocket.js b/serversocket.js index 7338800..c7ef2d1 100644 --- a/serversocket.js +++ b/serversocket.js @@ -16,7 +16,9 @@ const AuthTokenExpiredError = scErrors.AuthTokenExpiredError; const AuthTokenInvalidError = scErrors.AuthTokenInvalidError; const AuthTokenNotBeforeError = scErrors.AuthTokenNotBeforeError; const AuthTokenError = scErrors.AuthTokenError; -const SilentMiddlewareBlockedError = scErrors.SilentMiddlewareBlockedError; +const BrokerError = scErrors.BrokerError; + +const HANDSHAKE_REJECTION_STATUS_CODE = 4008; function AGServerSocket(id, server, socket, protocolVersion) { AsyncStreamEmitter.call(this); @@ -33,14 +35,16 @@ function AGServerSocket(id, server, socket, protocolVersion) { this.request = this.socket.upgradeReq; - this._middlewareInboundRawStream = new WritableAsyncIterableStream(); - this._middlewareInboundRawStream.type = this.server.MIDDLEWARE_INBOUND_RAW; + this._rawInboundMessageStream = new WritableAsyncIterableStream(); + + this.middlewareInboundRawStream = new WritableAsyncIterableStream(); + this.middlewareInboundRawStream.type = this.server.MIDDLEWARE_INBOUND_RAW; - this._middlewareInboundStream = new WritableAsyncIterableStream(); - this._middlewareInboundStream.type = this.server.MIDDLEWARE_INBOUND; + this.middlewareInboundStream = new WritableAsyncIterableStream(); + this.middlewareInboundStream.type = this.server.MIDDLEWARE_INBOUND; - this._middlewareOutboundStream = new WritableAsyncIterableStream(); - this._middlewareOutboundStream.type = this.server.MIDDLEWARE_OUTBOUND; + this.middlewareOutboundStream = new WritableAsyncIterableStream(); + this.middlewareOutboundStream.type = this.server.MIDDLEWARE_OUTBOUND; if (this.request.connection) { this.remoteAddress = this.request.connection.remoteAddress; @@ -88,10 +92,21 @@ function AGServerSocket(id, server, socket, protocolVersion) { } if (!this.server.pingTimeoutDisabled) { - this._pingIntervalTicker = setInterval(this._sendPing.bind(this), this.server.pingInterval); + this._pingIntervalTicker = setInterval(() => { + this._sendPing(); + }, this.server.pingInterval); } this._resetPongTimeout(); + this._handshakeTimeoutRef = setTimeout(() => { + this._handleHandshakeTimeout(); + }, this.server.handshakeTimeout); + + this.server.pendingClients[this.id] = this; + this.server.pendingClientsCount++; + + this._handleRawInboundMessageStream(this._rawInboundMessageStream, pongMessage); + // Receive incoming raw messages this.socket.on('message', async (message, flags) => { let isPong = message === pongMessage; @@ -107,7 +122,7 @@ function AGServerSocket(id, server, socket, protocolVersion) { action.data = message; try { - let {data} = await this.server._processMiddlewareAction(this._middlewareInboundRawStream, action, this); + let {data} = await this.server._processMiddlewareAction(this.middlewareInboundRawStream, action, this); message = data; } catch (error) { @@ -115,14 +130,33 @@ function AGServerSocket(id, server, socket, protocolVersion) { } } + this._rawInboundMessageStream.write(message); this.emit('message', {message}); + }); +} + +AGServerSocket.prototype = Object.create(AsyncStreamEmitter.prototype); + +AGServerSocket.CONNECTING = AGServerSocket.prototype.CONNECTING = 'connecting'; +AGServerSocket.OPEN = AGServerSocket.prototype.OPEN = 'open'; +AGServerSocket.CLOSED = AGServerSocket.prototype.CLOSED = 'closed'; + +AGServerSocket.AUTHENTICATED = AGServerSocket.prototype.AUTHENTICATED = 'authenticated'; +AGServerSocket.UNAUTHENTICATED = AGServerSocket.prototype.UNAUTHENTICATED = 'unauthenticated'; + +AGServerSocket.ignoreStatuses = scErrors.socketProtocolIgnoreStatuses; +AGServerSocket.errorStatuses = scErrors.socketProtocolErrorStatuses; + +AGServerSocket.prototype._handleRawInboundMessageStream = async function (messageStream, pongMessage) { + for await (let message of messageStream) { + let isPong = message === pongMessage; if (isPong) { let token = this.getAuthToken(); if (this.isAuthTokenExpired(token)) { this.deauthenticate(); } - return; + continue; } let packet; @@ -133,31 +167,19 @@ function AGServerSocket(id, server, socket, protocolVersion) { err.name = 'InvalidMessageError'; } this.emitError(err); - return; + continue; } if (Array.isArray(packet)) { let len = packet.length; for (let i = 0; i < len; i++) { - this._processInboundPacket(packet[i], message); + await this._processInboundPacket(packet[i], message); } } else { - this._processInboundPacket(packet, message); + await this._processInboundPacket(packet, message); } - }); -} - -AGServerSocket.prototype = Object.create(AsyncStreamEmitter.prototype); - -AGServerSocket.CONNECTING = AGServerSocket.prototype.CONNECTING = 'connecting'; -AGServerSocket.OPEN = AGServerSocket.prototype.OPEN = 'open'; -AGServerSocket.CLOSED = AGServerSocket.prototype.CLOSED = 'closed'; - -AGServerSocket.AUTHENTICATED = AGServerSocket.prototype.AUTHENTICATED = 'authenticated'; -AGServerSocket.UNAUTHENTICATED = AGServerSocket.prototype.UNAUTHENTICATED = 'unauthenticated'; - -AGServerSocket.ignoreStatuses = scErrors.socketProtocolIgnoreStatuses; -AGServerSocket.errorStatuses = scErrors.socketProtocolErrorStatuses; + } +}; AGServerSocket.prototype.receiver = function (receiverName) { return this._receiverDemux.stream(receiverName); @@ -175,39 +197,305 @@ AGServerSocket.prototype.closeProcedure = function (procedureName) { this._procedureDemux.close(procedureName); }; +AGServerSocket.prototype._handleHandshakeTimeout = function () { + let middlewareHandshakeStream = this.request[this.server.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; + this.disconnect(4005); +}; + +AGServerSocket.prototype._processHandshakeRequest = async function (request) { + let data = request.data || {}; + let signedAuthToken = data.authToken || null; + clearTimeout(this._handshakeTimeoutRef); + + let action = new AGAction(); + action.request = this.request; + action.socket = this; + action.type = AGAction.HANDSHAKE_AG; + + let middlewareHandshakeStream = this.request[this.server.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; + + try { + await this.server._processMiddlewareAction(middlewareHandshakeStream, action); + } catch (error) { + if (error.statusCode == null) { + error.statusCode = HANDSHAKE_REJECTION_STATUS_CODE; + } + request.error(error); + this.disconnect(error.statusCode); + return; + } + + let clientSocketStatus = { + id: this.id, + pingTimeout: this.server.pingTimeout + }; + let serverSocketStatus = { + id: this.id, + pingTimeout: this.server.pingTimeout + }; + + let oldAuthState = this.authState; + try { + await this._processAuthToken(signedAuthToken); + if (this.state === this.CLOSED) { + return; + } + } catch (error) { + if (signedAuthToken != null) { + // Because the token is optional as part of the handshake, we don't count + // it as an error if the token wasn't provided. + clientSocketStatus.authError = scErrors.dehydrateError(error); + serverSocketStatus.authError = error; + + if (error.isBadToken) { + this.deauthenticate(); + } + } + } + clientSocketStatus.isAuthenticated = !!this.authToken; + serverSocketStatus.isAuthenticated = clientSocketStatus.isAuthenticated; + + if (this.server.pendingClients[this.id]) { + delete this.server.pendingClients[this.id]; + this.server.pendingClientsCount--; + } + this.server.clients[this.id] = this; + this.server.clientsCount++; + + this.state = this.OPEN; + + if (clientSocketStatus.isAuthenticated) { + // Needs to be executed after the connection event to allow + // consumers to be setup from inside the connection loop. + (async () => { + await this.listener('connect').once(); + this.triggerAuthenticationEvents(oldAuthState); + })(); + } + + this.emit('connect', serverSocketStatus); + this.server.emit('connection', {socket: this, ...serverSocketStatus}); + + // Treat authentication failure as a 'soft' error + request.end(clientSocketStatus); + + middlewareHandshakeStream.close(); +}; + +AGServerSocket.prototype._processAuthenticateRequest = async function (request) { + let signedAuthToken = request.data; + let oldAuthState = this.authState; + try { + await this._processAuthToken(signedAuthToken); + } catch (error) { + if (error.isBadToken) { + this.deauthenticate(); + request.error(error); + + return; + } + + request.end({ + isAuthenticated: !!this.authToken, + authError: signedAuthToken == null ? null : scErrors.dehydrateError(error) + }); + + return; + } + this.triggerAuthenticationEvents(oldAuthState); + request.end({ + isAuthenticated: !!this.authToken, + authError: null + }); +}; + +AGServerSocket.prototype._subscribeSocket = async function (channelName, subscriptionOptions) { + if (channelName === undefined || !subscriptionOptions) { + throw new InvalidActionError(`Socket ${this.id} provided a malformated channel payload`); + } + + if (this.server.socketChannelLimit && this.channelSubscriptionsCount >= this.server.socketChannelLimit) { + throw new InvalidActionError( + `Socket ${this.id} tried to exceed the channel subscription limit of ${this.server.socketChannelLimit}` + ); + } + + if (typeof channelName !== 'string') { + throw new InvalidActionError(`Socket ${this.id} provided an invalid channel name`); + } + + if (this.channelSubscriptionsCount == null) { + this.channelSubscriptionsCount = 0; + } + if (this.channelSubscriptions[channelName] == null) { + this.channelSubscriptions[channelName] = true; + this.channelSubscriptionsCount++; + } + + try { + await this.server.brokerEngine.subscribeSocket(this, channelName); + } catch (err) { + delete this.channelSubscriptions[channelName]; + this.channelSubscriptionsCount--; + throw err; + } + this.emit('subscribe', { + channel: channelName, + subscriptionOptions + }); + this.server.emit('subscription', { + socket: this, + channel: channelName, + subscriptionOptions + }); +}; + +AGServerSocket.prototype._processSubscribeRequest = async function (request) { + let subscriptionOptions = Object.assign({}, request.data); + let channelName = subscriptionOptions.channel; + delete subscriptionOptions.channel; + + if (this.state === this.OPEN) { + try { + await this._subscribeSocket(channelName, subscriptionOptions); + } catch (err) { + let error = new BrokerError(`Failed to subscribe socket to the ${channelName} channel - ${err}`); + this.emitError(error); + request.error(error); + + return; + } + if (subscriptionOptions.batch) { + request.end(undefined, {batch: true}); + + return; + } + request.end(); + + return; + } + // This is an invalid state; it means the client tried to subscribe before + // having completed the handshake. + let error = new InvalidActionError('Cannot subscribe socket to a channel before it has completed the handshake'); + this.emitError(error); + request.error(error); +}; + +AGServerSocket.prototype._unsubscribeFromAllChannels = function () { + Object.keys(this.channelSubscriptions).forEach((channelName) => { + this._unsubscribe(channelName); + }); +}; + +AGServerSocket.prototype._unsubscribe = function (channel) { + if (typeof channel !== 'string') { + throw new InvalidActionError( + `Socket ${this.id} tried to unsubscribe from an invalid channel name` + ); + } + if (!this.channelSubscriptions[channel]) { + throw new InvalidActionError( + `Socket ${this.id} tried to unsubscribe from a channel which it is not subscribed to` + ); + } + + delete this.channelSubscriptions[channel]; + if (this.channelSubscriptionsCount != null) { + this.channelSubscriptionsCount--; + } + + this.server.brokerEngine.unsubscribeSocket(this, channel); + + this.emit('unsubscribe', {channel}); + this.server.emit('unsubscription', {socket: this, channel}); +}; + +AGServerSocket.prototype._processUnsubscribePacket = async function (packet) { + let channel = packet.data; + try { + this._unsubscribe(channel); + } catch (err) { + let error = new BrokerError( + `Failed to unsubscribe socket from the ${channel} channel - ${err}` + ); + this.emitError(error); + } +}; + +AGServerSocket.prototype._processUnsubscribeRequest = async function (request) { + let channel = request.data; + try { + this._unsubscribe(channel); + } catch (err) { + let error = new BrokerError( + `Failed to unsubscribe socket from the ${channel} channel - ${err}` + ); + this.emitError(error); + request.error(error); + return; + } + request.end(); +}; + AGServerSocket.prototype._processInboundPublishPacket = async function (packet) { - if (typeof packet.channel !== 'string') { - let error = new InvalidActionError(`Socket ${this.id} tried to publish to an invalid "${publishPacket.channel}" channel`); + let data = packet.data || {}; + if (typeof data.channel !== 'string') { + let error = new InvalidActionError(`Socket ${this.id} tried to invoke publish to an invalid "${data.channel}" channel`); this.emitError(error); - throw error; + return; } try { - await this.server.exchange.invokePublish(packet.channel, packet.data); + await this.server.exchange.invokePublish(data.channel, data.data); } catch (error) { this.emitError(error); - throw error; } }; +AGServerSocket.prototype._processInboundPublishRequest = async function (request) { + let data = request.data || {}; + if (typeof data.channel !== 'string') { + let error = new InvalidActionError(`Socket ${this.id} tried to transmit publish to an invalid "${data.channel}" channel`); + this.emitError(error); + request.error(error); + return; + } + try { + await this.server.exchange.invokePublish(data.channel, data.data); + } catch (error) { + this.emitError(error); + request.error(error); + return; + } + request.end(); +}; + AGServerSocket.prototype._processInboundPacket = async function (packet, message) { if (packet && packet.event != null) { let eventName = packet.event; let isRPC = packet.cid != null; - if (eventName === '#handshake' || eventName === '#authenticate') { + if (eventName === '#handshake') { + let request = new AGRequest(this, packet.cid, eventName, packet.data); + await this._processHandshakeRequest(request); + this._procedureDemux.write(eventName, request); + + return; + } + if (eventName === '#authenticate') { // Let AGServer handle these events. let request = new AGRequest(this, packet.cid, eventName, packet.data); + await this._processAuthenticateRequest(request); this._procedureDemux.write(eventName, request); return; } if (eventName === '#removeAuthToken') { + this.deauthenticateSelf(); this._receiverDemux.write(eventName, packet.data); return; } - let action = new AGAction(); action.socket = this; @@ -218,6 +506,7 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message let isPublish = eventName === '#publish'; let isSubscribe = eventName === '#subscribe'; + let isUnsubscribe = eventName === '#unsubscribe'; if (isPublish) { if (!this.server.allowClientPublish) { @@ -241,11 +530,15 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message action.channel = packet.data.channel; action.data = packet.data.data; } - } else if (eventName === '#unsubscribe') { - // Let AGServer handle this event. - let request = new AGRequest(this, packet.cid, eventName, packet.data); - this._procedureDemux.write(eventName, request); - + } else if (isUnsubscribe) { + if (isRPC) { + let request = new AGRequest(this, packet.cid, eventName, packet.data); + await this._processUnsubscribeRequest(request); + this._procedureDemux.write(eventName, request); + return; + } + await this._processUnsubscribePacket(packet); + this._receiverDemux.write(eventName, packet.data); return; } else { if (isRPC) { @@ -268,7 +561,7 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message if (isRPC) { let request = new AGRequest(this, packet.cid, eventName, packet.data); try { - let {data} = await this.server._processMiddlewareAction(this._middlewareInboundStream, action, this); + let {data} = await this.server._processMiddlewareAction(this.middlewareInboundStream, action, this); newData = data; } catch (error) { request.error(error); @@ -281,18 +574,13 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message request.data = {}; } request.data.data = newData; + await this._processSubscribeRequest(request); } else if (isPublish) { if (!request.data) { request.data = {}; } request.data.data = newData; - try { - await this._processInboundPublishPacket(request.data || {}); - } catch (error) { - request.error(error); - return; - } - request.end(); + await this._processInboundPublishRequest(request); } else { request.data = newData; } @@ -303,7 +591,7 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message } try { - let {data} = await this.server._processMiddlewareAction(this._middlewareInboundStream, action, this); + let {data} = await this.server._processMiddlewareAction(this.middlewareInboundStream, action, this); newData = data; } catch (error) { @@ -311,11 +599,7 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message } if (isPublish) { - try { - await this._processInboundPublishPacket(packet.data || {}); - } catch (error) { - return; - } + await this._processInboundPublishPacket(packet); } this._receiverDemux.write(eventName, newData); @@ -396,11 +680,48 @@ AGServerSocket.prototype._onClose = function (code, reason) { if (prevState === this.CONNECTING) { this._abortAllPendingEventsDueToBadConnection('connectAbort'); this.emit('connectAbort', {code, reason}); + this.server.emit('connectionAbort', { + socket: this, + code, + reason + }); } else { this._abortAllPendingEventsDueToBadConnection('disconnect'); this.emit('disconnect', {code, reason}); + this.server.emit('disconnection', { + socket: this, + code, + reason + }); } this.emit('close', {code, reason}); + this.server.emit('closure', { + socket: this, + code, + reason + }); + this._unsubscribeFromAllChannels(); + + clearTimeout(this._handshakeTimeoutRef); + let isClientFullyConnected = !!this.server.clients[this.id]; + + if (isClientFullyConnected) { + delete this.server.clients[this.id]; + this.server.clientsCount--; + } + + let isClientPending = !!this.server.pendingClients[this.id]; + if (isClientPending) { + delete this.server.pendingClients[this.id]; + this.server.pendingClientsCount--; + } + + let middlewareHandshakeStream = this.request[this.server.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; + middlewareHandshakeStream.close(); + this.middlewareInboundRawStream.close(); + this.middlewareInboundStream.close(); + this.middlewareOutboundStream.close(); + this._rawInboundMessageStream.close(); if (!AGServerSocket.ignoreStatuses[code]) { let closeMessage; @@ -516,10 +837,10 @@ AGServerSocket.prototype.transmit = async function (event, data, options) { action.channel = data.channel; action.data = data.data; } - useCache = !this.server.hasMiddleware(this._middlewareOutboundStream.type); + useCache = !this.server.hasMiddleware(this.middlewareOutboundStream.type); try { - let {data, options} = await this.server._processMiddlewareAction(this._middlewareOutboundStream, action, this); + let {data, options} = await this.server._processMiddlewareAction(this.middlewareOutboundStream, action, this); newData = data; useCache = options == null ? useCache : options.useCache; } catch (error) { @@ -835,7 +1156,7 @@ AGServerSocket.prototype._processAuthToken = async function (signedAuthToken) { action.authToken = this.authToken; try { - await this.server._processMiddlewareAction(this._middlewareInboundStream, action, this); + await this.server._processMiddlewareAction(this.middlewareInboundStream, action, this); } catch (error) { this.authToken = null; this.authState = this.UNAUTHENTICATED; diff --git a/test/integration.js b/test/integration.js index 10d9d9e..bbdb7c0 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1324,6 +1324,81 @@ describe('Integration tests', function () { }); describe('Socket pub/sub', function () { + it('Should maintain order of publish and subscribe', async function () { + server = asyngularServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + bindFailureHandlers(server); + + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + + await server.listener('ready').once(); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + await client.listener('connect').once(); + + let receivedMessages = []; + + (async () => { + for await (let data of client.subscribe('foo')) { + receivedMessages.push(data); + } + })(); + + await client.invokePublish('foo', 123); + + assert.equal(client.state, client.OPEN); + await wait(100); + assert.equal(receivedMessages.length, 1); + }); + + it('Should maintain order of publish and subscribe when client starts out as disconnected', async function () { + server = asyngularServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + bindFailureHandlers(server); + + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + + await server.listener('ready').once(); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER, + autoConnect: false + }); + + assert.equal(client.state, client.CLOSED); + + let receivedMessages = []; + + (async () => { + for await (let data of client.subscribe('foo')) { + receivedMessages.push(data); + } + })(); + + client.invokePublish('foo', 123); + + await wait(100); + assert.equal(client.state, client.OPEN); + assert.equal(receivedMessages.length, 1); + }); + it('Should support subscription batching', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, @@ -2043,9 +2118,6 @@ describe('Integration tests', function () { }); describe('Middleware', function () { - let middlewareFunction; - let middlewareWasExecuted = false; - beforeEach('Launch server without middleware before start', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, @@ -2064,7 +2136,7 @@ describe('Integration tests', function () { let clientErrors = []; let abortStatus; - middlewareFunction = async function (middlewareStream) { + let middlewareFunction = async function (middlewareStream) { for await (let {type, allow, block} of middlewareStream) { if (type === AGAction.HANDSHAKE_AG) { await wait(100); @@ -2117,7 +2189,7 @@ describe('Integration tests', function () { let abortStatus; let abortReason; - middlewareFunction = async function (middlewareStream) { + let middlewareFunction = async function (middlewareStream) { for await (let {type, allow, block} of middlewareStream) { if (type === AGAction.HANDSHAKE_AG) { await wait(100); @@ -2154,7 +2226,7 @@ describe('Integration tests', function () { let abortStatus; let abortReason; - middlewareFunction = async function (middlewareStream) { + let middlewareFunction = async function (middlewareStream) { for await (let {type, allow, block} of middlewareStream) { if (type === AGAction.HANDSHAKE_AG) { await wait(100); @@ -2196,7 +2268,7 @@ describe('Integration tests', function () { let abortStatus; let abortReason; - middlewareFunction = async function (middlewareStream) { + let middlewareFunction = async function (middlewareStream) { for await (let {type, allow} of middlewareStream) { if (type === AGAction.HANDSHAKE_AG) { await wait(500); @@ -2228,7 +2300,8 @@ describe('Integration tests', function () { describe('MIDDLEWARE_INBOUND', function () { describe('AUTHENTICATE action', function () { it('Should not run AUTHENTICATE action in middleware if JWT token does not exist', async function () { - middlewareFunction = async function (middlewareStream) { + let middlewareWasExecuted = false; + let middlewareFunction = async function (middlewareStream) { for await (let {type, allow} of middlewareStream) { if (type === AGAction.AUTHENTICATE) { middlewareWasExecuted = true; @@ -2249,8 +2322,9 @@ describe('Integration tests', function () { it('Should run AUTHENTICATE action in middleware if JWT token exists', async function () { global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); + let middlewareWasExecuted = false; - middlewareFunction = async function (middlewareStream) { + let middlewareFunction = async function (middlewareStream) { for await (let {type, allow} of middlewareStream) { if (type === AGAction.AUTHENTICATE) { middlewareWasExecuted = true; @@ -2275,6 +2349,185 @@ describe('Integration tests', function () { assert.equal(middlewareWasExecuted, true); }); }); + + describe('PUBLISH_IN action', function () { + it('Should run PUBLISH_IN action in middleware if client publishes to a channel', async function () { + let middlewareWasExecuted = false; + let middlewareAction = null; + + let middlewareFunction = async function (middlewareStream) { + for await (let action of middlewareStream) { + if (action.type === AGAction.PUBLISH_IN) { + middlewareWasExecuted = true; + middlewareAction = action; + } + action.allow(); + } + }; + server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + await client.invokePublish('hello', 'world'); + + assert.equal(middlewareWasExecuted, true); + assert.notEqual(middlewareAction, null); + assert.equal(middlewareAction.channel, 'hello'); + assert.equal(middlewareAction.data, 'world'); + }); + + it('Should be able to delay and block publish using PUBLISH_IN middleware', async function () { + let middlewareWasExecuted = false; + + let middlewareFunction = async function (middlewareStream) { + for await (let action of middlewareStream) { + if (action.type === AGAction.PUBLISH_IN) { + middlewareWasExecuted = true; + let error = new Error('Blocked by middleware'); + error.name = 'BlockedError'; + await wait(50); + action.block(error); + continue; + } + action.allow(); + } + }; + server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + let helloChannel = client.subscribe('hello'); + await helloChannel.listener('subscribe').once(); + + let receivedMessages = []; + (async () => { + for await (let data of helloChannel) { + receivedMessages.push(data); + } + })(); + + let error; + try { + await client.invokePublish('hello', 'world'); + } catch (err) { + error = err; + } + await wait(100); + + assert.equal(middlewareWasExecuted, true); + assert.notEqual(error, null); + assert.equal(error.name, 'BlockedError'); + assert.equal(receivedMessages.length, 0); + }); + }); + + describe('SUBSCRIBE action', function () { + it('Should run SUBSCRIBE action in middleware if client subscribes to a channel', async function () { + let middlewareWasExecuted = false; + let middlewareAction = null; + + let middlewareFunction = async function (middlewareStream) { + for await (let action of middlewareStream) { + if (action.type === AGAction.SUBSCRIBE) { + middlewareWasExecuted = true; + middlewareAction = action; + } + action.allow(); + } + }; + server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + await client.subscribe('hello').listener('subscribe').once(); + + assert.equal(middlewareWasExecuted, true); + assert.notEqual(middlewareAction, null); + assert.equal(middlewareAction.channel, 'hello'); + }); + + it('Should maintain pub/sub order if SUBSCRIBE action is delayed in middleware even if client starts out in disconnected state', async function () { + let middlewareActions = []; + + let middlewareFunction = async function (middlewareStream) { + for await (let action of middlewareStream) { + middlewareActions.push(action); + if (action.type === AGAction.SUBSCRIBE) { + await wait(100); + action.allow(); + continue; + } + action.allow(); + } + }; + server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER, + autoConnect: false + }); + + let receivedMessage; + + let fooChannel = client.subscribe('foo'); + client.transmitPublish('foo', 'bar'); + + for await (let data of fooChannel) { + receivedMessage = data; + break; + } + + assert.equal(receivedMessage, 'bar'); + assert.equal(middlewareActions.length, 2); + assert.equal(middlewareActions[0].type, AGAction.SUBSCRIBE); + assert.equal(middlewareActions[0].channel, 'foo'); + assert.equal(middlewareActions[1].type, AGAction.PUBLISH_IN); + assert.equal(middlewareActions[1].channel, 'foo'); + }); + }); + }); + + describe('MIDDLEWARE_OUTBOUND', function () { + describe('PUBLISH_OUT action', function () { + it('Should run PUBLISH_OUT action in middleware if client publishes to a channel', async function () { + let middlewareWasExecuted = false; + let middlewareAction = null; + + let middlewareFunction = async function (middlewareStream) { + for await (let action of middlewareStream) { + if (action.type === AGAction.PUBLISH_OUT) { + middlewareWasExecuted = true; + middlewareAction = action; + } + action.allow(); + } + }; + server.setMiddleware(server.MIDDLEWARE_OUTBOUND, middlewareFunction); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + await client.subscribe('hello').listener('subscribe').once(); + await client.invokePublish('hello', 123); + + assert.equal(middlewareWasExecuted, true); + assert.notEqual(middlewareAction, null); + assert.equal(middlewareAction.channel, 'hello'); + assert.equal(middlewareAction.data, 123); + }); + }); }); }); }); From ecc34fffb4a0b8cf47ffa70e2b63a84a899bb622 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 28 Jan 2019 19:50:15 +0100 Subject: [PATCH 159/265] Add middleware tests to check thaht delaying one client does not affect others --- test/integration.js | 164 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/test/integration.js b/test/integration.js index bbdb7c0..967ac08 100644 --- a/test/integration.js +++ b/test/integration.js @@ -2129,6 +2129,59 @@ describe('Integration tests', function () { }); describe('MIDDLEWARE_HANDSHAKE', function () { + describe('HANDSHAKE_WS action', function () { + it('Delaying handshake for one client should not affect other clients', async function () { + let middlewareFunction = async function (middlewareStream) { + for await (let action of middlewareStream) { + if (action.type === AGAction.HANDSHAKE_WS) { + if (action.request.url.indexOf('?delayMe=true') !== -1) { + // Long delay. + await wait(5000); + action.allow(); + continue; + } + } + action.allow(); + } + }; + server.setMiddleware(server.MIDDLEWARE_HANDSHAKE, middlewareFunction); + + let clientA = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + let clientB = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER, + query: { + delayMe: true + } + }); + + let clientAIsConnected = false; + let clientBIsConnected = false; + + (async () => { + await clientA.listener('connect').once(); + clientAIsConnected = true; + })(); + + (async () => { + await clientB.listener('connect').once(); + clientBIsConnected = true; + })(); + + await wait(100); + + assert.equal(clientAIsConnected, true); + assert.equal(clientBIsConnected, false); + + clientA.disconnect(); + clientB.disconnect(); + }); + }); + describe('HANDSHAKE_AG action', function () { it('Should trigger correct events if MIDDLEWARE_HANDSHAKE blocks with an error', async function () { let middlewareWasExecuted = false; @@ -2294,6 +2347,57 @@ describe('Integration tests', function () { connectEventTime = Date.now(); assert.equal(connectEventTime - createConnectionTime > 400, true); }); + + it('Delaying handshake for one client should not affect other clients', async function () { + let middlewareFunction = async function (middlewareStream) { + for await (let action of middlewareStream) { + if (action.type === AGAction.HANDSHAKE_AG) { + if (action.socket.request.url.indexOf('?delayMe=true') !== -1) { + // Long delay. + await wait(5000); + action.allow(); + continue; + } + } + action.allow(); + } + }; + server.setMiddleware(server.MIDDLEWARE_HANDSHAKE, middlewareFunction); + + let clientA = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + let clientB = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER, + query: { + delayMe: true + } + }); + + let clientAIsConnected = false; + let clientBIsConnected = false; + + (async () => { + await clientA.listener('connect').once(); + clientAIsConnected = true; + })(); + + (async () => { + await clientB.listener('connect').once(); + clientBIsConnected = true; + })(); + + await wait(100); + + assert.equal(clientAIsConnected, true); + assert.equal(clientBIsConnected, false); + + clientA.disconnect(); + clientB.disconnect(); + }); }); }); @@ -2425,6 +2529,66 @@ describe('Integration tests', function () { assert.equal(error.name, 'BlockedError'); assert.equal(receivedMessages.length, 0); }); + + it('Delaying PUBLISH_IN action for one client should not affect other clients', async function () { + let middlewareFunction = async function (middlewareStream) { + for await (let action of middlewareStream) { + if (action.type === AGAction.PUBLISH_IN) { + if (action.socket.request.url.indexOf('?delayMe=true') !== -1) { + // Long delay. + await wait(5000); + action.allow(); + continue; + } + } + action.allow(); + } + }; + server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); + + let clientA = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + let clientB = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER, + query: { + delayMe: true + } + }); + + let clientC = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + await clientC.listener('connect').once(); + + let receivedMessages = []; + (async () => { + for await (let data of clientC.subscribe('foo')) { + receivedMessages.push(data); + } + })(); + + clientA.transmitPublish('foo', 'a1'); + clientA.transmitPublish('foo', 'a2'); + + clientB.transmitPublish('foo', 'b1'); + clientB.transmitPublish('foo', 'b2'); + + await wait(100); + + assert.equal(receivedMessages.length, 2); + assert.equal(receivedMessages[0], 'a1'); + assert.equal(receivedMessages[1], 'a2'); + + clientA.disconnect(); + clientB.disconnect(); + clientC.disconnect(); + }); }); describe('SUBSCRIBE action', function () { From 7ae4e4a2f0ddf5246cd1d0a503ff4e20b6dfa8a2 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 28 Jan 2019 19:58:01 +0100 Subject: [PATCH 160/265] Do not enforce leading/trailing slashes anymore --- server.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server.js b/server.js index 3f81139..d485980 100644 --- a/server.js +++ b/server.js @@ -59,8 +59,7 @@ function AGServer(options) { this.brokerEngine = opts.brokerEngine; this.middlewareEmitFailures = opts.middlewareEmitFailures; - // Make sure there is always a leading and a trailing slash in the WS path. - this._path = opts.path.replace(/\/?$/, '/').replace(/^\/?/, '/'); + this._path = opts.path; (async () => { for await (let {error} of this.brokerEngine.listener('error')) { From 771275728888e32f239d36e4308b039e750957ec Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 28 Jan 2019 22:47:07 +0100 Subject: [PATCH 161/265] Add support for inboundBackpressure measurement --- serversocket.js | 16 +++++++++- test/integration.js | 72 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/serversocket.js b/serversocket.js index c7ef2d1..e613d7c 100644 --- a/serversocket.js +++ b/serversocket.js @@ -35,6 +35,9 @@ function AGServerSocket(id, server, socket, protocolVersion) { this.request = this.socket.upgradeReq; + this.inboundReceivedMessageCount = 0; + this.inboundProcessedMessageCount = 0; + this._rawInboundMessageStream = new WritableAsyncIterableStream(); this.middlewareInboundRawStream = new WritableAsyncIterableStream(); @@ -109,6 +112,8 @@ function AGServerSocket(id, server, socket, protocolVersion) { // Receive incoming raw messages this.socket.on('message', async (message, flags) => { + this.inboundReceivedMessageCount++; + let isPong = message === pongMessage; if (isPong) { @@ -125,7 +130,7 @@ function AGServerSocket(id, server, socket, protocolVersion) { let {data} = await this.server._processMiddlewareAction(this.middlewareInboundRawStream, action, this); message = data; } catch (error) { - + this.inboundProcessedMessageCount++; return; } } @@ -147,8 +152,15 @@ AGServerSocket.UNAUTHENTICATED = AGServerSocket.prototype.UNAUTHENTICATED = 'una AGServerSocket.ignoreStatuses = scErrors.socketProtocolIgnoreStatuses; AGServerSocket.errorStatuses = scErrors.socketProtocolErrorStatuses; +Object.defineProperty(AGServerSocket.prototype, 'inboundBackpressure', { + get: function () { + return this.inboundReceivedMessageCount - this.inboundProcessedMessageCount; + } +}); + AGServerSocket.prototype._handleRawInboundMessageStream = async function (messageStream, pongMessage) { for await (let message of messageStream) { + this.inboundProcessedMessageCount++; let isPong = message === pongMessage; if (isPong) { @@ -815,6 +827,7 @@ AGServerSocket.prototype.sendObjectSingle = function (object) { } }; +// TODO 2: Refactor batch functionality. AGServerSocket.prototype.sendObject = function (object, options) { if (options && options.batch) { this.sendObjectBatch(object); @@ -823,6 +836,7 @@ AGServerSocket.prototype.sendObject = function (object, options) { } }; +// TODO 2: Refactor transmit and invoke using a stream and calculate outboundBackpressure. AGServerSocket.prototype.transmit = async function (event, data, options) { let newData; let useCache = options ? options.useCache : false; diff --git a/test/integration.js b/test/integration.js index 967ac08..1e95922 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1323,6 +1323,78 @@ describe('Integration tests', function () { }); }); + describe('Socket backpressure', function () { + it('Should be able to get the message inboundBackpressure on a socket object', async function () { + server = asyngularServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + bindFailureHandlers(server); + + let backpressureHistory = []; + + server.setMiddleware(server.MIDDLEWARE_INBOUND_RAW, async (middlewareStream) => { + for await (let action of middlewareStream) { + backpressureHistory.push(action.socket.inboundBackpressure); + action.allow(); + } + }); + + let pause = true; + let messageCount = 0; + + server.setMiddleware(server.MIDDLEWARE_INBOUND, async (middlewareStream) => { + for await (let action of middlewareStream) { + messageCount++; + if (!pause) { + action.allow(); + continue; + } + // Do not allow any messages until inboundBackpressure reaches 10. + while (true) { + if (action.socket.inboundBackpressure >= 10) { + pause = false; + break; + } + await wait(2); + } + action.allow(); + } + }); + + await server.listener('ready').once(); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + await client.listener('connect').once(); + for (let i = 0; i < 20; i++) { + await wait(20); + client.transmitPublish('foo', 123); + } + + while (true) { + await wait(10); + if (messageCount >= 20) { + break; + } + } + + // There should be 1 handshake and 20 publishes. + assert.equal(backpressureHistory.length, 21); + // The first entry is for the handshake which we are not blocking; + // so it should not add any backpressure. + assert.equal(backpressureHistory[0], 1); + assert.equal(backpressureHistory[1], 1); + assert.equal(backpressureHistory[6] > 3, true); + assert.equal(backpressureHistory[9] > 7, true); + assert.equal(backpressureHistory[15], 1); + assert.equal(backpressureHistory[20], 1); + }); + }); + describe('Socket pub/sub', function () { it('Should maintain order of publish and subscribe', async function () { server = asyngularServer.listen(PORT_NUMBER, { From 963da4e204dc3d12f199f99525fdf5e2290f9598 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 30 Jan 2019 20:50:48 +0100 Subject: [PATCH 162/265] Changes to message batching --- server.js | 4 +- serversocket.js | 130 +++++++++++++++++------- test/integration.js | 242 +++++++++++++++++++++++++++----------------- 3 files changed, 245 insertions(+), 131 deletions(-) diff --git a/server.js b/server.js index d485980..817aaea 100644 --- a/server.js +++ b/server.js @@ -34,7 +34,9 @@ function AGServer(options) { path: '/asyngular/', protocolVersion: 2, authDefaultExpiry: 86400, - pubSubBatchDuration: null, + batchOnHandshake: false, + batchOnHandshakeDuration: 400, + batchInterval: 50, middlewareEmitFailures: true }; diff --git a/serversocket.js b/serversocket.js index e613d7c..93b12b7 100644 --- a/serversocket.js +++ b/serversocket.js @@ -62,9 +62,16 @@ function AGServerSocket(id, server, socket, protocolVersion) { this.forwardedForAddress = this.request.forwardedForAddress; } + this.isBufferingBatch = false; + this.isBatching = false; + this.batchOnHandshake = this.server.options.batchOnHandshake; + this.batchOnHandshakeDuration = this.server.options.batchOnHandshakeDuration; + this.batchInterval = this.server.options.batchInterval; + this._batchBuffer = []; + + this._batchingIntervalId = null; this._cid = 1; this._callbackMap = {}; - this._batchSendList = []; this.channelSubscriptions = {}; this.channelSubscriptionsCount = 0; @@ -158,6 +165,15 @@ Object.defineProperty(AGServerSocket.prototype, 'inboundBackpressure', { } }); +AGServerSocket.prototype._startBatchOnHandshake = function () { + this._startBatching(); + setTimeout(() => { + if (!this.isBatching) { + this._stopBatching(); + } + }, this.batchOnHandshakeDuration); +}; + AGServerSocket.prototype._handleRawInboundMessageStream = async function (messageStream, pongMessage) { for await (let message of messageStream) { this.inboundProcessedMessageCount++; @@ -285,12 +301,16 @@ AGServerSocket.prototype._processHandshakeRequest = async function (request) { })(); } - this.emit('connect', serverSocketStatus); - this.server.emit('connection', {socket: this, ...serverSocketStatus}); - // Treat authentication failure as a 'soft' error request.end(clientSocketStatus); + if (this.batchOnHandshake) { + this._startBatchOnHandshake(); + } + + this.emit('connect', serverSocketStatus); + this.server.emit('connection', {socket: this, ...serverSocketStatus}); + middlewareHandshakeStream.close(); }; @@ -377,11 +397,7 @@ AGServerSocket.prototype._processSubscribeRequest = async function (request) { return; } - if (subscriptionOptions.batch) { - request.end(undefined, {batch: true}); - return; - } request.end(); return; @@ -684,6 +700,8 @@ AGServerSocket.prototype._onClose = function (code, reason) { clearInterval(this._pingIntervalTicker); clearTimeout(this._pingTimeoutTicker); + this._cancelBatching(); + if (this.state === this.CLOSED) { this._abortAllPendingEventsDueToBadConnection('connectAbort'); } else { @@ -792,47 +810,87 @@ AGServerSocket.prototype.encode = function (object) { return this.server.codec.encode(object); }; -AGServerSocket.prototype.sendObjectBatch = function (object) { - this._batchSendList.push(object); - if (this._batchTimeout) { +AGServerSocket.prototype.startBatch = function () { + this.isBufferingBatch = true; + this._batchBuffer = []; +}; + +AGServerSocket.prototype.flushBatch = function () { + this.isBufferingBatch = false; + if (!this._batchBuffer.length) { return; } + let serializedBatch = this.serializeObject(this._batchBuffer); + this._batchBuffer = []; + this.send(serializedBatch); +}; - this._batchTimeout = setTimeout(() => { - delete this._batchTimeout; - if (this._batchSendList.length) { - let str; - try { - str = this.encode(this._batchSendList); - } catch (err) { - this.emitError(err); - } - if (str != null) { - this.send(str); - } - this._batchSendList = []; - } - }, this.server.options.pubSubBatchDuration || 0); +AGServerSocket.prototype.cancelBatch = function () { + this.isBufferingBatch = false; + this._batchBuffer = []; }; -AGServerSocket.prototype.sendObjectSingle = function (object) { +AGServerSocket.prototype._startBatching = function () { + if (this._batchingIntervalId != null) { + return; + } + this.startBatch(); + this._batchingIntervalId = setInterval(() => { + this.flushBatch(); + this.startBatch(); + }, this.batchInterval); +}; + +AGServerSocket.prototype.startBatching = function () { + this.isBatching = true; + this._startBatching(); +}; + +AGServerSocket.prototype._stopBatching = function () { + if (this._batchingIntervalId != null) { + clearInterval(this._batchingIntervalId); + } + this._batchingIntervalId = null; + this.flushBatch(); +}; + +AGServerSocket.prototype.stopBatching = function () { + this.isBatching = false; + this._stopBatching(); +}; + +AGServerSocket.prototype._cancelBatching = function () { + if (this._batchingIntervalId != null) { + clearInterval(this._batchingIntervalId); + } + this._batchingIntervalId = null; + this.cancelBatch(); +}; + +AGServerSocket.prototype.cancelBatching = function () { + this.isBatching = false; + this._cancelBatching(); +}; + +AGServerSocket.prototype.serializeObject = function (object) { let str; try { str = this.encode(object); } catch (err) { this.emitError(err); + return null; } - if (str != null) { - this.send(str); - } + return str; }; -// TODO 2: Refactor batch functionality. -AGServerSocket.prototype.sendObject = function (object, options) { - if (options && options.batch) { - this.sendObjectBatch(object); - } else { - this.sendObjectSingle(object); +AGServerSocket.prototype.sendObject = function (object) { + if (this.isBufferingBatch) { + this._batchBuffer.push(object); + return; + } + let str = this.serializeObject(object); + if (str != null) { + this.send(str); } }; diff --git a/test/integration.js b/test/integration.js index 1e95922..d56459f 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1471,100 +1471,6 @@ describe('Integration tests', function () { assert.equal(receivedMessages.length, 1); }); - it('Should support subscription batching', async function () { - server = asyngularServer.listen(PORT_NUMBER, { - authKey: serverOptions.authKey, - wsEngine: WS_ENGINE - }); - bindFailureHandlers(server); - - (async () => { - for await (let {socket} of server.listener('connection')) { - connectionHandler(socket); - let isFirstMessage = true; - - (async () => { - for await (let {message} of socket.listener('message')) { - if (isFirstMessage) { - let data = JSON.parse(message); - // All 20 subscriptions should arrive as a single message. - assert.equal(data.length, 20); - isFirstMessage = false; - } - } - })(); - } - })(); - - let subscribeMiddlewareCounter = 0; - - // Each subscription should pass through the middleware individually, even - // though they were sent as a batch/array. - server.setMiddleware(server.MIDDLEWARE_INBOUND, async function (middlewareStream) { - for await (let action of middlewareStream) { - if (action.type === AGAction.SUBSCRIBE) { - subscribeMiddlewareCounter++; - assert.equal(action.channel.indexOf('my-channel-'), 0); - if (action.channel === 'my-channel-10') { - assert.equal(JSON.stringify(action.data), JSON.stringify({foo: 123})); - } else if (action.channel === 'my-channel-12') { - // Block my-channel-12 - let err = new Error('You cannot subscribe to channel 12'); - err.name = 'UnauthorizedSubscribeError'; - action.block(err); - continue; - } - } - action.allow(); - } - }); - - await server.listener('ready').once(); - - client = asyngularClient.create({ - hostname: clientOptions.hostname, - port: PORT_NUMBER - }); - - let channelList = []; - for (let i = 0; i < 20; i++) { - let subscriptionOptions = { - batch: true - }; - if (i === 10) { - subscriptionOptions.data = {foo: 123}; - } - channelList.push( - client.subscribe('my-channel-' + i, subscriptionOptions) - ); - } - - (async () => { - for await (let event of channelList[12].listener('subscribe')) { - throw new Error('The my-channel-12 channel should have been blocked by MIDDLEWARE_SUBSCRIBE'); - } - })(); - - (async () => { - for await (let event of channelList[12].listener('subscribeFail')) { - assert.notEqual(event.error, null); - assert.equal(event.error.name, 'UnauthorizedSubscribeError'); - } - })(); - - (async () => { - for await (let event of channelList[19].listener('subscribe')) { - client.transmitPublish('my-channel-19', 'Hello!'); - } - })(); - - for await (let data of channelList[19]) { - assert.equal(data, 'Hello!'); - assert.equal(subscribeMiddlewareCounter, 20); - break; - } - }); - it('Client should not be able to subscribe to a channel before the handshake has completed', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, @@ -2067,6 +1973,154 @@ describe('Integration tests', function () { }); }); + describe('Batching', function () { + it('Should batch messages sent through sockets after the handshake when the batchOnHandshake option is true', async function () { + server = asyngularServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, + batchOnHandshake: true, + batchOnHandshakeDuration: 400, + batchInterval: 50 + }); + bindFailureHandlers(server); + + let receivedServerMessages = []; + + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + + (async () => { + for await (let {message} of socket.listener('message')) { + receivedServerMessages.push(message); + } + })(); + } + })(); + + let subscribeMiddlewareCounter = 0; + + // Each subscription should pass through the middleware individually, even + // though they were sent as a batch/array. + server.setMiddleware(server.MIDDLEWARE_INBOUND, async function (middlewareStream) { + for await (let action of middlewareStream) { + if (action.type === AGAction.SUBSCRIBE) { + subscribeMiddlewareCounter++; + assert.equal(action.channel.indexOf('my-channel-'), 0); + if (action.channel === 'my-channel-10') { + assert.equal(JSON.stringify(action.data), JSON.stringify({foo: 123})); + } else if (action.channel === 'my-channel-12') { + // Block my-channel-12 + let err = new Error('You cannot subscribe to channel 12'); + err.name = 'UnauthorizedSubscribeError'; + action.block(err); + continue; + } + } + action.allow(); + } + }); + + await server.listener('ready').once(); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER, + batchOnHandshake: true, + batchOnHandshakeDuration: 100, + batchInterval: 50 + }); + + let receivedClientMessages = []; + (async () => { + for await (let {message} of client.listener('message')) { + receivedClientMessages.push(message); + } + })(); + + let channelList = []; + for (let i = 0; i < 20; i++) { + let subscriptionOptions = {}; + if (i === 10) { + subscriptionOptions.data = {foo: 123}; + } + channelList.push( + client.subscribe('my-channel-' + i, subscriptionOptions) + ); + } + + (async () => { + for await (let event of channelList[12].listener('subscribe')) { + throw new Error('The my-channel-12 channel should have been blocked by MIDDLEWARE_SUBSCRIBE'); + } + })(); + + (async () => { + for await (let event of channelList[12].listener('subscribeFail')) { + assert.notEqual(event.error, null); + assert.equal(event.error.name, 'UnauthorizedSubscribeError'); + } + })(); + + (async () => { + for await (let event of channelList[19].listener('subscribe')) { + client.transmitPublish('my-channel-19', 'Hello!'); + } + })(); + + for await (let data of channelList[19]) { + assert.equal(data, 'Hello!'); + assert.equal(subscribeMiddlewareCounter, 20); + break; + } + + assert.notEqual(receivedServerMessages[0], null); + // All 20 subscriptions should arrive as a single message. + assert.equal(JSON.parse(receivedServerMessages[0]).length, 20); + + assert.equal(Array.isArray(JSON.parse(receivedClientMessages[0])), false); + assert.equal(JSON.parse(receivedClientMessages[1]).length, 20); + }); + + it('The batchOnHandshake option should not break the order of subscribe and publish', async function () { + server = asyngularServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, + batchOnHandshake: true, + batchOnHandshakeDuration: 400, + batchInterval: 50 + }); + bindFailureHandlers(server); + + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + + await server.listener('ready').once(); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER, + autoConnect: false, + batchOnHandshake: true, + batchOnHandshakeDuration: 100, + batchInterval: 50 + }); + + let receivedMessage; + + let fooChannel = client.subscribe('foo'); + client.transmitPublish('foo', 'bar'); + + for await (let data of fooChannel) { + receivedMessage = data; + break; + } + }); + }); + describe('Socket Ping/pong', function () { describe('When when pingTimeoutDisabled is not set', function () { beforeEach('Launch server with ping options before start', async function () { From 7382d754755553019063abd8fb3455cd07e614ca Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 31 Jan 2019 21:52:58 +0100 Subject: [PATCH 163/265] Fix batching for outbound publish --- package.json | 8 ++++---- server.js | 3 ++- serversocket.js | 9 +++++++-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 715c0d9..0ebde68 100644 --- a/package.json +++ b/package.json @@ -12,18 +12,18 @@ "ag-request": "^1.0.0", "ag-simple-broker": "^3.0.0", "async-stream-emitter": "^2.0.0", - "base64id": "1.0.0", - "lodash.clonedeep": "4.5.0", + "base64id": "^1.0.0", + "lodash.clonedeep": "^4.5.0", "sc-errors": "^2.0.0", "sc-formatter": "^3.0.2", "stream-demux": "^5.1.0", "writable-async-iterable-stream": "^5.1.0", - "ws": "6.1.2" + "ws": "^6.1.2" }, "devDependencies": { "asyngular-client": "^4.0.0", "localStorage": "^1.0.3", - "mocha": "5.2.0" + "mocha": "^5.2.0" }, "scripts": { "test": "mocha --reporter spec --timeout 3000 --slow 3000" diff --git a/server.js b/server.js index 817aaea..b33d721 100644 --- a/server.js +++ b/server.js @@ -37,7 +37,8 @@ function AGServer(options) { batchOnHandshake: false, batchOnHandshakeDuration: 400, batchInterval: 50, - middlewareEmitFailures: true + middlewareEmitFailures: true, + cloneData: false }; this.options = Object.assign(opts, options); diff --git a/serversocket.js b/serversocket.js index 93b12b7..91404c2 100644 --- a/serversocket.js +++ b/serversocket.js @@ -38,6 +38,8 @@ function AGServerSocket(id, server, socket, protocolVersion) { this.inboundReceivedMessageCount = 0; this.inboundProcessedMessageCount = 0; + this.cloneData = this.server.options.cloneData; + this._rawInboundMessageStream = new WritableAsyncIterableStream(); this.middlewareInboundRawStream = new WritableAsyncIterableStream(); @@ -885,6 +887,9 @@ AGServerSocket.prototype.serializeObject = function (object) { AGServerSocket.prototype.sendObject = function (object) { if (this.isBufferingBatch) { + if (this.cloneData) { + object = cloneDeep(object); + } this._batchBuffer.push(object); return; } @@ -923,7 +928,7 @@ AGServerSocket.prototype.transmit = async function (event, data, options) { newData = packet.data; } - if (options && useCache && options.stringifiedData != null) { + if (options && useCache && options.stringifiedData != null && !this.isBufferingBatch) { // Optimized this.send(options.stringifiedData); } else { @@ -969,7 +974,7 @@ AGServerSocket.prototype.invoke = async function (event, data, options) { timeout }; - if (options && options.useCache && options.stringifiedData != null) { + if (options && options.useCache && options.stringifiedData != null && !this.isBufferingBatch) { // Optimized this.send(options.stringifiedData); } else { From 0e8787cd7769c4b789e006b8d8c9bea0c5803732 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 1 Feb 2019 01:13:04 +0100 Subject: [PATCH 164/265] Add outbound backpressure --- serversocket.js | 110 ++++++++++++++++++++++++++++++++++---------- test/integration.js | 91 +++++++++++++++++++++++------------- 2 files changed, 145 insertions(+), 56 deletions(-) diff --git a/serversocket.js b/serversocket.js index 91404c2..d159b21 100644 --- a/serversocket.js +++ b/serversocket.js @@ -38,9 +38,13 @@ function AGServerSocket(id, server, socket, protocolVersion) { this.inboundReceivedMessageCount = 0; this.inboundProcessedMessageCount = 0; + this.outboundPreparedMessageCount = 0; + this.outboundSentMessageCount = 0; + this.cloneData = this.server.options.cloneData; this._rawInboundMessageStream = new WritableAsyncIterableStream(); + this._outboundPacketStream = new WritableAsyncIterableStream(); this.middlewareInboundRawStream = new WritableAsyncIterableStream(); this.middlewareInboundRawStream.type = this.server.MIDDLEWARE_INBOUND_RAW; @@ -91,7 +95,7 @@ function AGServerSocket(id, server, socket, protocolVersion) { pongMessage = '#2'; this._sendPing = () => { if (this.state !== this.CLOSED) { - this.sendObject('#1'); + this.send('#1'); } }; } else { @@ -117,7 +121,8 @@ function AGServerSocket(id, server, socket, protocolVersion) { this.server.pendingClients[this.id] = this; this.server.pendingClientsCount++; - this._handleRawInboundMessageStream(this._rawInboundMessageStream, pongMessage); + this._handleRawInboundMessageStream(pongMessage); + this._handleOutboundPacketStream(); // Receive incoming raw messages this.socket.on('message', async (message, flags) => { @@ -167,6 +172,12 @@ Object.defineProperty(AGServerSocket.prototype, 'inboundBackpressure', { } }); +Object.defineProperty(AGServerSocket.prototype, 'outboundBackpressure', { + get: function () { + return this.outboundPreparedMessageCount - this.outboundSentMessageCount; + } +}); + AGServerSocket.prototype._startBatchOnHandshake = function () { this._startBatching(); setTimeout(() => { @@ -176,8 +187,25 @@ AGServerSocket.prototype._startBatchOnHandshake = function () { }, this.batchOnHandshakeDuration); }; -AGServerSocket.prototype._handleRawInboundMessageStream = async function (messageStream, pongMessage) { - for await (let message of messageStream) { +AGServerSocket.prototype.receiver = function (receiverName) { + return this._receiverDemux.stream(receiverName); +}; + +AGServerSocket.prototype.closeReceiver = function (receiverName) { + this._receiverDemux.close(receiverName); +}; + +AGServerSocket.prototype.procedure = function (procedureName) { + return this._procedureDemux.stream(procedureName); +}; + +AGServerSocket.prototype.closeProcedure = function (procedureName) { + this._procedureDemux.close(procedureName); +}; + + +AGServerSocket.prototype._handleRawInboundMessageStream = async function (pongMessage) { + for await (let message of this._rawInboundMessageStream) { this.inboundProcessedMessageCount++; let isPong = message === pongMessage; @@ -211,22 +239,6 @@ AGServerSocket.prototype._handleRawInboundMessageStream = async function (messag } }; -AGServerSocket.prototype.receiver = function (receiverName) { - return this._receiverDemux.stream(receiverName); -}; - -AGServerSocket.prototype.closeReceiver = function (receiverName) { - this._receiverDemux.close(receiverName); -}; - -AGServerSocket.prototype.procedure = function (procedureName) { - return this._procedureDemux.stream(procedureName); -}; - -AGServerSocket.prototype.closeProcedure = function (procedureName) { - this._procedureDemux.close(procedureName); -}; - AGServerSocket.prototype._handleHandshakeTimeout = function () { let middlewareHandshakeStream = this.request[this.server.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; this.disconnect(4005); @@ -754,6 +766,7 @@ AGServerSocket.prototype._onClose = function (code, reason) { this.middlewareInboundStream.close(); this.middlewareOutboundStream.close(); this._rawInboundMessageStream.close(); + this._outboundPacketStream.close(); if (!AGServerSocket.ignoreStatuses[code]) { let closeMessage; @@ -887,9 +900,6 @@ AGServerSocket.prototype.serializeObject = function (object) { AGServerSocket.prototype.sendObject = function (object) { if (this.isBufferingBatch) { - if (this.cloneData) { - object = cloneDeep(object); - } this._batchBuffer.push(object); return; } @@ -899,8 +909,58 @@ AGServerSocket.prototype.sendObject = function (object) { } }; -// TODO 2: Refactor transmit and invoke using a stream and calculate outboundBackpressure. +AGServerSocket.prototype._handleOutboundPacketStream = async function () { + for await (let packet of this._outboundPacketStream) { + if (packet.resolve) { + // Invoke has no middleware, so there is no need to await here. + (async () => { + let result; + try { + result = await this._invoke(packet.event, packet.data, packet.options); + } catch (error) { + packet.reject(error); + return; + } + packet.resolve(result); + })(); + + this.outboundSentMessageCount++; + continue; + } + await this._transmit(packet.event, packet.data, packet.options); + this.outboundSentMessageCount++; + } +}; + AGServerSocket.prototype.transmit = async function (event, data, options) { + if (this.cloneData) { + data = cloneDeep(data); + } + this.outboundPreparedMessageCount++; + this._outboundPacketStream.write({ + event, + data, + options + }); +}; + +AGServerSocket.prototype.invoke = async function (event, data, options) { + if (this.cloneData) { + data = cloneDeep(data); + } + this.outboundPreparedMessageCount++; + return new Promise((resolve, reject) => { + this._outboundPacketStream.write({ + event, + data, + options, + resolve, + reject + }); + }); +}; + +AGServerSocket.prototype._transmit = async function (event, data, options) { let newData; let useCache = options ? options.useCache : false; let packet = {event, data}; @@ -946,7 +1006,7 @@ AGServerSocket.prototype.transmit = async function (event, data, options) { } }; -AGServerSocket.prototype.invoke = async function (event, data, options) { +AGServerSocket.prototype._invoke = async function (event, data, options) { return new Promise((resolve, reject) => { let eventObject = { event, diff --git a/test/integration.js b/test/integration.js index d56459f..9089458 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1340,23 +1340,10 @@ describe('Integration tests', function () { } }); - let pause = true; - let messageCount = 0; - server.setMiddleware(server.MIDDLEWARE_INBOUND, async (middlewareStream) => { for await (let action of middlewareStream) { - messageCount++; - if (!pause) { - action.allow(); - continue; - } - // Do not allow any messages until inboundBackpressure reaches 10. - while (true) { - if (action.socket.inboundBackpressure >= 10) { - pause = false; - break; - } - await wait(2); + if (action.data === 5) { + await wait(100); } action.allow(); } @@ -1371,27 +1358,69 @@ describe('Integration tests', function () { await client.listener('connect').once(); for (let i = 0; i < 20; i++) { - await wait(20); - client.transmitPublish('foo', 123); - } - - while (true) { await wait(10); - if (messageCount >= 20) { - break; - } + client.transmitPublish('foo', i); } - // There should be 1 handshake and 20 publishes. + await wait(400); + + // Backpressure should go up and come back down. assert.equal(backpressureHistory.length, 21); - // The first entry is for the handshake which we are not blocking; - // so it should not add any backpressure. assert.equal(backpressureHistory[0], 1); - assert.equal(backpressureHistory[1], 1); - assert.equal(backpressureHistory[6] > 3, true); - assert.equal(backpressureHistory[9] > 7, true); - assert.equal(backpressureHistory[15], 1); - assert.equal(backpressureHistory[20], 1); + assert.equal(backpressureHistory[12] > 4, true); + assert.equal(backpressureHistory[14] > 6, true); + assert.equal(backpressureHistory[19], 1); + }); + + it('Should be able to get the message outboundBackpressure on a socket object', async function () { + server = asyngularServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + bindFailureHandlers(server); + + let backpressureHistory = []; + + (async () => { + for await (let {socket} of server.listener('connection')) { + (async () => { + await socket.listener('subscribe').once(); + + for (let i = 0; i < 20; i++) { + await wait(10); + server.exchange.transmitPublish('foo', i); + backpressureHistory.push(socket.outboundBackpressure); + } + })(); + } + })(); + + server.setMiddleware(server.MIDDLEWARE_OUTBOUND, async (middlewareStream) => { + for await (let action of middlewareStream) { + if (action.data === 5) { + await wait(100); + } + action.allow(); + } + }); + + await server.listener('ready').once(); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + await client.subscribe('foo').listener('subscribe').once(); + + await wait(400); + + // Backpressure should go up and come back down. + assert.equal(backpressureHistory.length, 20); + assert.equal(backpressureHistory[0], 1); + assert.equal(backpressureHistory[13] > 7, true); + assert.equal(backpressureHistory[14] > 8, true); + assert.equal(backpressureHistory[19], 1); }); }); From a7f27ad1c93b0d0759ae8876f61521694f2e8fef Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 1 Feb 2019 09:17:11 +0100 Subject: [PATCH 165/265] Fix deauthenticate after adding outbound stream --- serversocket.js | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/serversocket.js b/serversocket.js index d159b21..3b6e8ba 100644 --- a/serversocket.js +++ b/serversocket.js @@ -1,3 +1,5 @@ +// TODO 2: Allow disconnecting out of band (disconnect message should not wait to be processed in stream sequence). + const cloneDeep = require('lodash.clonedeep'); const WritableAsyncIterableStream = require('writable-async-iterable-stream'); const StreamDemux = require('stream-demux'); @@ -220,11 +222,11 @@ AGServerSocket.prototype._handleRawInboundMessageStream = async function (pongMe let packet; try { packet = this.decode(message); - } catch (err) { - if (err.name === 'Error') { - err.name = 'InvalidMessageError'; + } catch (error) { + if (error.name === 'Error') { + error.name = 'InvalidMessageError'; } - this.emitError(err); + this.emitError(error); continue; } @@ -380,10 +382,10 @@ AGServerSocket.prototype._subscribeSocket = async function (channelName, subscri try { await this.server.brokerEngine.subscribeSocket(this, channelName); - } catch (err) { + } catch (error) { delete this.channelSubscriptions[channelName]; this.channelSubscriptionsCount--; - throw err; + throw error; } this.emit('subscribe', { channel: channelName, @@ -891,8 +893,8 @@ AGServerSocket.prototype.serializeObject = function (object) { let str; try { str = this.encode(object); - } catch (err) { - this.emitError(err); + } catch (error) { + this.emitError(error); return null; } return str; @@ -933,6 +935,10 @@ AGServerSocket.prototype._handleOutboundPacketStream = async function () { }; AGServerSocket.prototype.transmit = async function (event, data, options) { + if (this.state != this.OPEN) { + let errorMessage = `Socket transmit "${event}" was aborted due to a bad connection`; + throw new BadConnectionError(errorMessage, 'connectAbort'); + } if (this.cloneData) { data = cloneDeep(data); } @@ -944,7 +950,12 @@ AGServerSocket.prototype.transmit = async function (event, data, options) { }); }; +// TODO 2: Should invoke timeout start counting immediately or wait until after stream processes the message? AGServerSocket.prototype.invoke = async function (event, data, options) { + if (this.state != this.OPEN) { + let errorMessage = `Socket invoke "${event}" was aborted due to a bad connection`; + throw new BadConnectionError(errorMessage, 'connectAbort'); + } if (this.cloneData) { data = cloneDeep(data); } @@ -1141,10 +1152,10 @@ AGServerSocket.prototype.setAuthToken = async function (data, options) { this.triggerAuthenticationEvents(oldAuthState); try { await sendAuthTokenToClient(signedAuthToken); - } catch (err) { - this.emitError(err); + } catch (error) { + this.emitError(error); if (rejectOnFailedDelivery) { - throw err; + throw error; } } }; @@ -1177,9 +1188,16 @@ AGServerSocket.prototype.deauthenticateSelf = function () { }); }; -AGServerSocket.prototype.deauthenticate = function () { +AGServerSocket.prototype.deauthenticate = async function (options) { this.deauthenticateSelf(); - return this.invoke('#removeAuthToken'); + try { + await this.invoke('#removeAuthToken'); + } catch (error) { + this.emitError(error); + if (options && options.rejectOnFailedDelivery) { + throw error; + } + } }; AGServerSocket.prototype.kickOut = function (channel, message) { From bb477b8e3a3052809487c1e3a25548112d22cfd1 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 1 Feb 2019 23:46:54 +0100 Subject: [PATCH 166/265] Add support for setting and removing auth token via either transmit or invoke --- serversocket.js | 53 ++++++++++++++++++++++----------------------- test/integration.js | 32 +++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 29 deletions(-) diff --git a/serversocket.js b/serversocket.js index 3b6e8ba..a56eb2d 100644 --- a/serversocket.js +++ b/serversocket.js @@ -935,9 +935,8 @@ AGServerSocket.prototype._handleOutboundPacketStream = async function () { }; AGServerSocket.prototype.transmit = async function (event, data, options) { - if (this.state != this.OPEN) { - let errorMessage = `Socket transmit "${event}" was aborted due to a bad connection`; - throw new BadConnectionError(errorMessage, 'connectAbort'); + if (this.state !== this.OPEN) { + return; } if (this.cloneData) { data = cloneDeep(data); @@ -950,9 +949,8 @@ AGServerSocket.prototype.transmit = async function (event, data, options) { }); }; -// TODO 2: Should invoke timeout start counting immediately or wait until after stream processes the message? AGServerSocket.prototype.invoke = async function (event, data, options) { - if (this.state != this.OPEN) { + if (this.state !== this.OPEN) { let errorMessage = `Socket invoke "${event}" was aborted due to a bad connection`; throw new BadConnectionError(errorMessage, 'connectAbort'); } @@ -1122,17 +1120,6 @@ AGServerSocket.prototype.setAuthToken = async function (data, options) { this.authToken = authToken; - let sendAuthTokenToClient = async (signedToken) => { - let tokenData = { - token: signedToken - }; - try { - return await this.invoke('#setAuthToken', tokenData); - } catch (err) { - throw new AuthError(`Failed to deliver auth token to client - ${err}`); - } - }; - let signedAuthToken; try { @@ -1150,14 +1137,22 @@ AGServerSocket.prototype.setAuthToken = async function (data, options) { } this.triggerAuthenticationEvents(oldAuthState); - try { - await sendAuthTokenToClient(signedAuthToken); - } catch (error) { - this.emitError(error); - if (rejectOnFailedDelivery) { + + let tokenData = { + token: signedAuthToken + }; + + if (rejectOnFailedDelivery) { + try { + await this.invoke('#setAuthToken', tokenData); + } catch (err) { + let error = new AuthError(`Failed to deliver auth token to client - ${err}`); + this.emitError(error); throw error; } + return; } + this.transmit('#setAuthToken', tokenData); }; AGServerSocket.prototype.getAuthToken = function () { @@ -1190,14 +1185,18 @@ AGServerSocket.prototype.deauthenticateSelf = function () { AGServerSocket.prototype.deauthenticate = async function (options) { this.deauthenticateSelf(); - try { - await this.invoke('#removeAuthToken'); - } catch (error) { - this.emitError(error); - if (options && options.rejectOnFailedDelivery) { - throw error; + if (options && options.rejectOnFailedDelivery) { + try { + await this.invoke('#removeAuthToken'); + } catch (error) { + this.emitError(error); + if (options && options.rejectOnFailedDelivery) { + throw error; + } } + return; } + this.transmit('#removeAuthToken'); }; AGServerSocket.prototype.kickOut = function (channel, message) { diff --git a/test/integration.js b/test/integration.js index 9089458..ab3e1d5 100644 --- a/test/integration.js +++ b/test/integration.js @@ -349,6 +349,35 @@ describe('Integration tests', function () { assert.equal(authenticationStateChangeEvents[1].authToken, null); }); + it('Should throw error if server socket deauthenticate is called after client disconnected and rejectOnFailedDelivery is true', async function () { + global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); + + client = asyngularClient.create(clientOptions); + + let {socket} = await server.listener('connection').once(); + + client.disconnect(); + let error; + try { + await socket.deauthenticate({rejectOnFailedDelivery: true}); + } catch (err) { + error = err; + } + assert.notEqual(error, null); + assert.equal(error.name, 'BadConnectionError'); + }); + + it('Should not throw error if server socket deauthenticate is called after client disconnected and rejectOnFailedDelivery is not true', async function () { + global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); + + client = asyngularClient.create(clientOptions); + + let {socket} = await server.listener('connection').once(); + + client.disconnect(); + socket.deauthenticate(); + }); + it('Should not authenticate the client if MIDDLEWARE_INBOUND blocks the authentication', async function () { global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenAlice); @@ -763,8 +792,7 @@ describe('Integration tests', function () { } assert.equal(error, null); await wait(0); - assert.notEqual(socketErrors[0], null); - assert.equal(socketErrors[0].name, 'AuthError'); + assert.equal(socketErrors[0], null); } else { let err = new Error('Failed to login'); err.name = 'FailedLoginError'; From d86311ae8f28bfec132e02f697851206efc3a86c Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 2 Feb 2019 19:34:35 +0100 Subject: [PATCH 167/265] Improve error handling related to sending data through a disconnected AGServerSocket --- serversocket.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/serversocket.js b/serversocket.js index a56eb2d..3672aa2 100644 --- a/serversocket.js +++ b/serversocket.js @@ -812,9 +812,10 @@ AGServerSocket.prototype.terminate = function () { }; AGServerSocket.prototype.send = function (data, options) { - this.socket.send(data, options, (err) => { - if (err) { - this._onClose(1006, err.toString()); + this.socket.send(data, options, (error) => { + if (error) { + this.emitError(error); + this._onClose(1006, error.toString()); } }); }; @@ -936,6 +937,11 @@ AGServerSocket.prototype._handleOutboundPacketStream = async function () { AGServerSocket.prototype.transmit = async function (event, data, options) { if (this.state !== this.OPEN) { + let error = new BadConnectionError( + `Socket transmit "${event}" was aborted due to a bad connection`, + 'connectAbort' + ); + this.emitError(error); return; } if (this.cloneData) { @@ -951,8 +957,12 @@ AGServerSocket.prototype.transmit = async function (event, data, options) { AGServerSocket.prototype.invoke = async function (event, data, options) { if (this.state !== this.OPEN) { - let errorMessage = `Socket invoke "${event}" was aborted due to a bad connection`; - throw new BadConnectionError(errorMessage, 'connectAbort'); + let error = new BadConnectionError( + `Socket invoke "${event}" was aborted due to a bad connection`, + 'connectAbort' + ); + this.emitError(error); + throw error; } if (this.cloneData) { data = cloneDeep(data); From 173b9f41d8fb38b119ee31e48b83612d70464f93 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 2 Feb 2019 19:35:40 +0100 Subject: [PATCH 168/265] Add tests related to disconnection when there is backpressure --- test/integration.js | 103 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 2 deletions(-) diff --git a/test/integration.js b/test/integration.js index ab3e1d5..f065c58 100644 --- a/test/integration.js +++ b/test/integration.js @@ -744,7 +744,9 @@ describe('Integration tests', function () { assert.equal(error.name, 'AuthError'); await wait(0); assert.notEqual(socketErrors[0], null); - assert.equal(socketErrors[0].name, 'AuthError'); + assert.equal(socketErrors[0].name, 'BadConnectionError'); + assert.notEqual(socketErrors[1], null); + assert.equal(socketErrors[1].name, 'AuthError'); } else { let err = new Error('Failed to login'); err.name = 'FailedLoginError'; @@ -792,7 +794,8 @@ describe('Integration tests', function () { } assert.equal(error, null); await wait(0); - assert.equal(socketErrors[0], null); + assert.notEqual(socketErrors[0], null); + assert.equal(socketErrors[0].name, 'BadConnectionError'); } else { let err = new Error('Failed to login'); err.name = 'FailedLoginError'; @@ -1277,6 +1280,102 @@ describe('Integration tests', function () { assert.equal(serverSocketDisconnected, true); assert.equal(serverClosure, true); }); + + it('Disconnection should support socket message backpressure', async function () { + server = asyngularServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + bindFailureHandlers(server); + + let serverWarnings = []; + (async () => { + for await (let {warning} of server.listener('warning')) { + serverWarnings.push(warning); + } + })(); + + await server.listener('ready').once(); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + let currentRequestData = null; + let requestDataAtTimeOfDisconnect = null; + + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionOnServer = true; + connectionHandler(socket); + + (async () => { + await socket.listener('disconnect').once(); + requestDataAtTimeOfDisconnect = currentRequestData; + })(); + + (async () => { + for await (let request of socket.procedure('foo')) { + currentRequestData = request.data; + await wait(10); + (async () => { + try { + await socket.invoke('bla', request.data); + } catch (err) {} + })(); + socket.transmit('hi', request.data); + request.end('bar'); + if (request.data === 10) { + client.disconnect(); + } + } + })(); + } + })(); + + for (let i = 0; i < 30; i++) { + (async () => { + let result; + try { + result = await client.invoke('foo', i); + } catch (error) { + return; + } + })(); + } + + await wait(200); + + // Expect a server warning (socket error) if a response was sent on a disconnected socket. + assert.equal( + serverWarnings.some((warning) => { + return warning.message.match(/WebSocket is not open/g); + }), + true + ); + + // Expect a server warning (socket error) if transmit was called on a disconnected socket. + assert.equal( + serverWarnings.some((warning) => { + return warning.name === 'BadConnectionError' && warning.message.match(/Socket transmit "hi" was aborted/g); + }), + true + ); + + // Expect a server warning (socket error) if invoke was called on a disconnected socket. + assert.equal( + serverWarnings.some((warning) => { + return warning.name === 'BadConnectionError' && warning.message.match(/Socket invoke "bla" was aborted/g); + }), + true + ); + + // Check that the disconnect event on the back end socket triggers as soon as possible (out-of-band) and not at the end of the stream. + // Any value less than 30 indicates that the 'disconnect' event was triggerred out-of-band. + // Since the client disconnect() call is executed on the 11th message, we can assume that the 'disconnect' event will trigger sooner. + assert.equal(requestDataAtTimeOfDisconnect < 15, true); + }); }); describe('Socket RPC invoke', function () { From 2bc6b2061fe55b1941a33de86a45506efb2452d4 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 2 Feb 2019 19:55:47 +0100 Subject: [PATCH 169/265] v5.0.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0ebde68..c22a976 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "4.0.1", + "version": "5.0.0", "description": "Server module for Asyngular", "main": "index.js", "repository": { @@ -21,7 +21,7 @@ "ws": "^6.1.2" }, "devDependencies": { - "asyngular-client": "^4.0.0", + "asyngular-client": "^5.0.1", "localStorage": "^1.0.3", "mocha": "^5.2.0" }, From 5974cfb5dbce505343bf1a54b5d5c6f4fa19d21b Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 2 Feb 2019 20:34:43 +0100 Subject: [PATCH 170/265] Remove comment which is no longer needed --- serversocket.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/serversocket.js b/serversocket.js index 3672aa2..45b4fdf 100644 --- a/serversocket.js +++ b/serversocket.js @@ -1,5 +1,3 @@ -// TODO 2: Allow disconnecting out of band (disconnect message should not wait to be processed in stream sequence). - const cloneDeep = require('lodash.clonedeep'); const WritableAsyncIterableStream = require('writable-async-iterable-stream'); const StreamDemux = require('stream-demux'); From 21227643b2262708718ad077eee683c90e249af9 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 2 Feb 2019 20:35:13 +0100 Subject: [PATCH 171/265] v5.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c22a976..649f15b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "5.0.0", + "version": "5.0.1", "description": "Server module for Asyngular", "main": "index.js", "repository": { From b47811d0fe36c8c2ce65a100c28fd3d31d421691 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 21 Feb 2019 23:51:12 +0100 Subject: [PATCH 172/265] Add support for consumable streams --- package.json | 10 +++++----- server.js | 4 ++-- serversocket.js | 28 ++++++++++++---------------- test/integration.js | 8 ++++---- 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 649f15b..d82aa9d 100644 --- a/package.json +++ b/package.json @@ -10,18 +10,18 @@ "dependencies": { "ag-auth": "^1.0.1", "ag-request": "^1.0.0", - "ag-simple-broker": "^3.0.0", - "async-stream-emitter": "^2.0.0", + "ag-simple-broker": "^3.1.0", + "async-stream-emitter": "^2.1.0", "base64id": "^1.0.0", "lodash.clonedeep": "^4.5.0", "sc-errors": "^2.0.0", "sc-formatter": "^3.0.2", - "stream-demux": "^5.1.0", - "writable-async-iterable-stream": "^5.1.0", + "stream-demux": "^6.1.0", + "writable-consumable-stream": "^1.1.1", "ws": "^6.1.2" }, "devDependencies": { - "asyngular-client": "^5.0.1", + "asyngular-client": "^5.1.0", "localStorage": "^1.0.3", "mocha": "^5.2.0" }, diff --git a/server.js b/server.js index b33d721..fb742ea 100644 --- a/server.js +++ b/server.js @@ -6,7 +6,7 @@ const url = require('url'); const crypto = require('crypto'); const AGSimpleBroker = require('ag-simple-broker'); const AsyncStreamEmitter = require('async-stream-emitter'); -const WritableAsyncIterableStream = require('writable-async-iterable-stream'); +const WritableConsumableStream = require('writable-consumable-stream'); const AGAction = require('./action'); const scErrors = require('sc-errors'); @@ -343,7 +343,7 @@ AGServer.prototype.verifyHandshake = async function (info, callback) { } catch (e) {} } - let middlewareHandshakeStream = new WritableAsyncIterableStream(); + let middlewareHandshakeStream = new WritableConsumableStream(); middlewareHandshakeStream.type = this.MIDDLEWARE_HANDSHAKE; req[this.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM] = middlewareHandshakeStream; diff --git a/serversocket.js b/serversocket.js index 45b4fdf..0aadb03 100644 --- a/serversocket.js +++ b/serversocket.js @@ -1,5 +1,5 @@ const cloneDeep = require('lodash.clonedeep'); -const WritableAsyncIterableStream = require('writable-async-iterable-stream'); +const WritableConsumableStream = require('writable-consumable-stream'); const StreamDemux = require('stream-demux'); const AsyncStreamEmitter = require('async-stream-emitter'); const AGAction = require('./action'); @@ -43,16 +43,16 @@ function AGServerSocket(id, server, socket, protocolVersion) { this.cloneData = this.server.options.cloneData; - this._rawInboundMessageStream = new WritableAsyncIterableStream(); - this._outboundPacketStream = new WritableAsyncIterableStream(); + this._rawInboundMessageStream = new WritableConsumableStream(); + this._outboundPacketStream = new WritableConsumableStream(); - this.middlewareInboundRawStream = new WritableAsyncIterableStream(); + this.middlewareInboundRawStream = new WritableConsumableStream(); this.middlewareInboundRawStream.type = this.server.MIDDLEWARE_INBOUND_RAW; - this.middlewareInboundStream = new WritableAsyncIterableStream(); + this.middlewareInboundStream = new WritableConsumableStream(); this.middlewareInboundStream.type = this.server.MIDDLEWARE_INBOUND; - this.middlewareOutboundStream = new WritableAsyncIterableStream(); + this.middlewareOutboundStream = new WritableConsumableStream(); this.middlewareOutboundStream.type = this.server.MIDDLEWARE_OUTBOUND; if (this.request.connection) { @@ -166,17 +166,13 @@ AGServerSocket.UNAUTHENTICATED = AGServerSocket.prototype.UNAUTHENTICATED = 'una AGServerSocket.ignoreStatuses = scErrors.socketProtocolIgnoreStatuses; AGServerSocket.errorStatuses = scErrors.socketProtocolErrorStatuses; -Object.defineProperty(AGServerSocket.prototype, 'inboundBackpressure', { - get: function () { - return this.inboundReceivedMessageCount - this.inboundProcessedMessageCount; - } -}); +AGServerSocket.prototype.getInboundBackpressure = function () { + return this.inboundReceivedMessageCount - this.inboundProcessedMessageCount; +}; -Object.defineProperty(AGServerSocket.prototype, 'outboundBackpressure', { - get: function () { - return this.outboundPreparedMessageCount - this.outboundSentMessageCount; - } -}); +AGServerSocket.prototype.getOutboundBackpressure = function () { + return this.outboundPreparedMessageCount - this.outboundSentMessageCount; +}; AGServerSocket.prototype._startBatchOnHandshake = function () { this._startBatching(); diff --git a/test/integration.js b/test/integration.js index f065c58..edc8499 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1451,7 +1451,7 @@ describe('Integration tests', function () { }); describe('Socket backpressure', function () { - it('Should be able to get the message inboundBackpressure on a socket object', async function () { + it('Should be able to getInboundBackpressure() on a socket object', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE @@ -1462,7 +1462,7 @@ describe('Integration tests', function () { server.setMiddleware(server.MIDDLEWARE_INBOUND_RAW, async (middlewareStream) => { for await (let action of middlewareStream) { - backpressureHistory.push(action.socket.inboundBackpressure); + backpressureHistory.push(action.socket.getInboundBackpressure()); action.allow(); } }); @@ -1499,7 +1499,7 @@ describe('Integration tests', function () { assert.equal(backpressureHistory[19], 1); }); - it('Should be able to get the message outboundBackpressure on a socket object', async function () { + it('Should be able to getOutboundBackpressure() on a socket object', async function () { server = asyngularServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE @@ -1516,7 +1516,7 @@ describe('Integration tests', function () { for (let i = 0; i < 20; i++) { await wait(10); server.exchange.transmitPublish('foo', i); - backpressureHistory.push(socket.outboundBackpressure); + backpressureHistory.push(socket.getOutboundBackpressure()); } })(); } From 2603fb33b3d9e0aa0fe3422606f14fbf7190f138 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 23 Feb 2019 01:33:49 +0100 Subject: [PATCH 173/265] Add support for socket close stream cleanup modes --- server.js | 1 + serversocket.js | 109 ++++++++++++++++++++++++----- test/integration.js | 164 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 242 insertions(+), 32 deletions(-) diff --git a/server.js b/server.js index fb742ea..55ac075 100644 --- a/server.js +++ b/server.js @@ -38,6 +38,7 @@ function AGServer(options) { batchOnHandshakeDuration: 400, batchInterval: 50, middlewareEmitFailures: true, + socketStreamCleanupMode: 'kill', cloneData: false }; diff --git a/serversocket.js b/serversocket.js index 0aadb03..22642d0 100644 --- a/serversocket.js +++ b/serversocket.js @@ -43,8 +43,8 @@ function AGServerSocket(id, server, socket, protocolVersion) { this.cloneData = this.server.options.cloneData; - this._rawInboundMessageStream = new WritableConsumableStream(); - this._outboundPacketStream = new WritableConsumableStream(); + this.inboundMessageStream = new WritableConsumableStream(); + this.outboundPacketStream = new WritableConsumableStream(); this.middlewareInboundRawStream = new WritableConsumableStream(); this.middlewareInboundRawStream.type = this.server.MIDDLEWARE_INBOUND_RAW; @@ -121,7 +121,7 @@ function AGServerSocket(id, server, socket, protocolVersion) { this.server.pendingClients[this.id] = this; this.server.pendingClientsCount++; - this._handleRawInboundMessageStream(pongMessage); + this._handleInboundMessageStream(pongMessage); this._handleOutboundPacketStream(); // Receive incoming raw messages @@ -149,7 +149,7 @@ function AGServerSocket(id, server, socket, protocolVersion) { } } - this._rawInboundMessageStream.write(message); + this.inboundMessageStream.write(message); this.emit('message', {message}); }); } @@ -191,6 +191,18 @@ AGServerSocket.prototype.closeReceiver = function (receiverName) { this._receiverDemux.close(receiverName); }; +AGServerSocket.prototype.closeAllReceivers = function () { + this._receiverDemux.closeAll(); +}; + +AGServerSocket.prototype.killReceiver = function (receiverName) { + this._receiverDemux.kill(receiverName); +}; + +AGServerSocket.prototype.killAllReceivers = function () { + this._receiverDemux.killAll(); +}; + AGServerSocket.prototype.procedure = function (procedureName) { return this._procedureDemux.stream(procedureName); }; @@ -199,9 +211,20 @@ AGServerSocket.prototype.closeProcedure = function (procedureName) { this._procedureDemux.close(procedureName); }; +AGServerSocket.prototype.closeAllProcedures = function () { + this._procedureDemux.closeAll(); +}; -AGServerSocket.prototype._handleRawInboundMessageStream = async function (pongMessage) { - for await (let message of this._rawInboundMessageStream) { +AGServerSocket.prototype.killProcedure = function (procedureName) { + this._procedureDemux.kill(procedureName); +}; + +AGServerSocket.prototype.killAllProcedures = function () { + this._procedureDemux.killAll(); +}; + +AGServerSocket.prototype._handleInboundMessageStream = async function (pongMessage) { + for await (let message of this.inboundMessageStream) { this.inboundProcessedMessageCount++; let isPong = message === pongMessage; @@ -706,6 +729,52 @@ AGServerSocket.prototype._abortAllPendingEventsDueToBadConnection = function (fa }); }; +AGServerSocket.prototype.closeMiddlewareStreams = function () { + let middlewareHandshakeStream = this.request[this.server.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; + middlewareHandshakeStream.close(); + this.middlewareInboundRawStream.close(); + this.middlewareInboundStream.close(); + this.middlewareOutboundStream.close(); +}; + +AGServerSocket.prototype.closeIOStreams = function () { + this.inboundMessageStream.close(); + this.outboundPacketStream.close(); +}; + +AGServerSocket.prototype.closeAllStreams = function () { + this.closeMiddlewareStreams(); + + this.closeIOStreams(); + + this.closeAllReceivers(); + this.closeAllProcedures(); + this.closeAllListeners(); +}; + +AGServerSocket.prototype.killMiddlewareStreams = function () { + let middlewareHandshakeStream = this.request[this.server.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; + middlewareHandshakeStream.kill(); + this.middlewareInboundRawStream.kill(); + this.middlewareInboundStream.kill(); + this.middlewareOutboundStream.kill(); +}; + +AGServerSocket.prototype.killIOStreams = function () { + this.inboundMessageStream.kill(); + this.outboundPacketStream.kill(); +}; + +AGServerSocket.prototype.killAllStreams = function () { + this.killMiddlewareStreams(); + + this.killIOStreams(); + + this.killAllReceivers(); + this.killAllProcedures(); + this.killAllListeners(); +}; + AGServerSocket.prototype._onClose = function (code, reason) { clearInterval(this._pingIntervalTicker); clearTimeout(this._pingTimeoutTicker); @@ -734,6 +803,20 @@ AGServerSocket.prototype._onClose = function (code, reason) { reason }); } + + let cleanupMode = this.server.options.socketStreamCleanupMode; + if (cleanupMode === 'kill') { + (async () => { + await this.listener('close').once(); + this.killAllStreams(); + })(); + } else if (cleanupMode === 'close') { + (async () => { + await this.listener('close').once(); + this.closeAllStreams(); + })(); + } + this.emit('close', {code, reason}); this.server.emit('closure', { socket: this, @@ -756,14 +839,6 @@ AGServerSocket.prototype._onClose = function (code, reason) { this.server.pendingClientsCount--; } - let middlewareHandshakeStream = this.request[this.server.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; - middlewareHandshakeStream.close(); - this.middlewareInboundRawStream.close(); - this.middlewareInboundStream.close(); - this.middlewareOutboundStream.close(); - this._rawInboundMessageStream.close(); - this._outboundPacketStream.close(); - if (!AGServerSocket.ignoreStatuses[code]) { let closeMessage; if (reason) { @@ -907,7 +982,7 @@ AGServerSocket.prototype.sendObject = function (object) { }; AGServerSocket.prototype._handleOutboundPacketStream = async function () { - for await (let packet of this._outboundPacketStream) { + for await (let packet of this.outboundPacketStream) { if (packet.resolve) { // Invoke has no middleware, so there is no need to await here. (async () => { @@ -942,7 +1017,7 @@ AGServerSocket.prototype.transmit = async function (event, data, options) { data = cloneDeep(data); } this.outboundPreparedMessageCount++; - this._outboundPacketStream.write({ + this.outboundPacketStream.write({ event, data, options @@ -963,7 +1038,7 @@ AGServerSocket.prototype.invoke = async function (event, data, options) { } this.outboundPreparedMessageCount++; return new Promise((resolve, reject) => { - this._outboundPacketStream.write({ + this.outboundPacketStream.write({ event, data, options, diff --git a/test/integration.js b/test/integration.js index edc8499..5fac469 100644 --- a/test/integration.js +++ b/test/integration.js @@ -710,7 +710,7 @@ describe('Integration tests', function () { }); bindFailureHandlers(server); - let socketErrors = []; + let serverWarnings = []; (async () => { await server.listener('ready').once(); @@ -725,8 +725,8 @@ describe('Integration tests', function () { let {socket} = await server.listener('connection').once(); (async () => { - for await (let {error} of socket.listener('error')) { - socketErrors.push(error); + for await (let {warning} of server.listener('warning')) { + serverWarnings.push(warning); } })(); @@ -743,10 +743,10 @@ describe('Integration tests', function () { assert.notEqual(error, null); assert.equal(error.name, 'AuthError'); await wait(0); - assert.notEqual(socketErrors[0], null); - assert.equal(socketErrors[0].name, 'BadConnectionError'); - assert.notEqual(socketErrors[1], null); - assert.equal(socketErrors[1].name, 'AuthError'); + assert.notEqual(serverWarnings[0], null); + assert.equal(serverWarnings[0].name, 'BadConnectionError'); + assert.notEqual(serverWarnings[1], null); + assert.equal(serverWarnings[1].name, 'AuthError'); } else { let err = new Error('Failed to login'); err.name = 'FailedLoginError'; @@ -762,7 +762,7 @@ describe('Integration tests', function () { }); bindFailureHandlers(server); - let socketErrors = []; + let serverWarnings = []; (async () => { await server.listener('ready').once(); @@ -777,8 +777,8 @@ describe('Integration tests', function () { let {socket} = await server.listener('connection').once(); (async () => { - for await (let {error} of socket.listener('error')) { - socketErrors.push(error); + for await (let {warning} of server.listener('warning')) { + serverWarnings.push(warning); } })(); @@ -794,8 +794,8 @@ describe('Integration tests', function () { } assert.equal(error, null); await wait(0); - assert.notEqual(socketErrors[0], null); - assert.equal(socketErrors[0].name, 'BadConnectionError'); + assert.notEqual(serverWarnings[0], null); + assert.equal(serverWarnings[0].name, 'BadConnectionError'); } else { let err = new Error('Failed to login'); err.name = 'FailedLoginError'; @@ -1247,7 +1247,7 @@ describe('Integration tests', function () { }); let serverSocketClosed = false; - let serverSocketDisconnected = false; + let serverDisconnection = false; let serverClosure = false; (async () => { @@ -1261,7 +1261,7 @@ describe('Integration tests', function () { (async () => { for await (let event of server.listener('disconnection')) { - serverSocketDisconnected = true; + serverDisconnection = true; } })(); @@ -1277,7 +1277,7 @@ describe('Integration tests', function () { await wait(1000); assert.equal(serverSocketClosed, true); - assert.equal(serverSocketDisconnected, true); + assert.equal(serverDisconnection, true); assert.equal(serverClosure, true); }); @@ -1376,6 +1376,140 @@ describe('Integration tests', function () { // Since the client disconnect() call is executed on the 11th message, we can assume that the 'disconnect' event will trigger sooner. assert.equal(requestDataAtTimeOfDisconnect < 15, true); }); + + it('Socket streams should be killed immediately if socket disconnects (default/kill mode)', async function () { + server = asyngularServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + bindFailureHandlers(server); + + let handledPackets = []; + let closedReceiver = false; + + (async () => { + for await (let {socket} of server.listener('connection')) { + (async () => { + for await (let packet of socket.receiver('foo')) { + await wait(30); + handledPackets.push(packet); + } + closedReceiver = true; + })(); + } + })(); + + await server.listener('ready').once(); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + await wait(100); + + for (let i = 0; i < 15; i++) { + client.transmit('foo', i); + } + + await wait(110); + + client.disconnect(4445, 'Disconnect'); + + await wait(500); + assert.equal(handledPackets.length, 4); + assert.equal(closedReceiver, true); + }); + + it('Socket streams should be closed eventually if socket disconnects (close mode)', async function () { + server = asyngularServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, + socketStreamCleanupMode: 'close' + }); + bindFailureHandlers(server); + + let handledPackets = []; + let closedReceiver = false; + + (async () => { + for await (let {socket} of server.listener('connection')) { + (async () => { + for await (let packet of socket.receiver('foo')) { + await wait(30); + handledPackets.push(packet); + } + closedReceiver = true; + })(); + } + })(); + + await server.listener('ready').once(); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + await wait(100); + + for (let i = 0; i < 15; i++) { + client.transmit('foo', i); + } + + await wait(110); + + client.disconnect(4445, 'Disconnect'); + + await wait(500); + assert.equal(handledPackets.length, 15); + assert.equal(closedReceiver, true); + }); + + it('Socket streams should be closed eventually if socket disconnects (none mode)', async function () { + server = asyngularServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, + socketStreamCleanupMode: 'none' + }); + bindFailureHandlers(server); + + let handledPackets = []; + let closedReceiver = false; + + (async () => { + for await (let {socket} of server.listener('connection')) { + (async () => { + for await (let packet of socket.receiver('foo')) { + await wait(30); + handledPackets.push(packet); + } + closedReceiver = false; + })(); + } + })(); + + await server.listener('ready').once(); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + await wait(100); + + for (let i = 0; i < 15; i++) { + client.transmit('foo', i); + } + + await wait(110); + + client.disconnect(4445, 'Disconnect'); + + await wait(500); + assert.equal(handledPackets.length, 15); + assert.equal(closedReceiver, false); + }); }); describe('Socket RPC invoke', function () { From 1c0d8f890a375e7d26f1dfa3f5741933de434646 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 23 Feb 2019 21:22:06 +0100 Subject: [PATCH 174/265] v5.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d82aa9d..7ab1ffb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "5.0.1", + "version": "5.1.0", "description": "Server module for Asyngular", "main": "index.js", "repository": { From 1c0611b1013008910a1ad33aa982526a17cb3a0c Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 24 Feb 2019 19:40:07 +0100 Subject: [PATCH 175/265] Improve consumable interface --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 7ab1ffb..31e1840 100644 --- a/package.json +++ b/package.json @@ -10,18 +10,18 @@ "dependencies": { "ag-auth": "^1.0.1", "ag-request": "^1.0.0", - "ag-simple-broker": "^3.1.0", - "async-stream-emitter": "^2.1.0", + "ag-simple-broker": "^4.0.0", + "async-stream-emitter": "^3.0.0", "base64id": "^1.0.0", "lodash.clonedeep": "^4.5.0", "sc-errors": "^2.0.0", "sc-formatter": "^3.0.2", - "stream-demux": "^6.1.0", + "stream-demux": "^7.0.0", "writable-consumable-stream": "^1.1.1", "ws": "^6.1.2" }, "devDependencies": { - "asyngular-client": "^5.1.0", + "asyngular-client": "^5.2.0", "localStorage": "^1.0.3", "mocha": "^5.2.0" }, From 47fe8e3630fd1c251ba7d57f8ab8da67d9225f63 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 24 Feb 2019 21:30:11 +0100 Subject: [PATCH 176/265] v5.2.0 --- package.json | 8 ++--- serversocket.js | 86 +++++++++++++++++++++++++++++++++++++++++++++ test/integration.js | 48 +++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 31e1840..06e6375 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "5.1.0", + "version": "5.2.0", "description": "Server module for Asyngular", "main": "index.js", "repository": { @@ -10,8 +10,8 @@ "dependencies": { "ag-auth": "^1.0.1", "ag-request": "^1.0.0", - "ag-simple-broker": "^4.0.0", - "async-stream-emitter": "^3.0.0", + "ag-simple-broker": "^4.0.2", + "async-stream-emitter": "^3.0.1", "base64id": "^1.0.0", "lodash.clonedeep": "^4.5.0", "sc-errors": "^2.0.0", @@ -21,7 +21,7 @@ "ws": "^6.1.2" }, "devDependencies": { - "asyngular-client": "^5.2.0", + "asyngular-client": "^5.2.2", "localStorage": "^1.0.3", "mocha": "^5.2.0" }, diff --git a/serversocket.js b/serversocket.js index 22642d0..20e47db 100644 --- a/serversocket.js +++ b/serversocket.js @@ -166,6 +166,16 @@ AGServerSocket.UNAUTHENTICATED = AGServerSocket.prototype.UNAUTHENTICATED = 'una AGServerSocket.ignoreStatuses = scErrors.socketProtocolIgnoreStatuses; AGServerSocket.errorStatuses = scErrors.socketProtocolErrorStatuses; +AGServerSocket.prototype.getBackpressure = function () { + return Math.max( + this.getInboundBackpressure(), + this.getOutboundBackpressure(), + this.getAllListenersBackpressure(), + this.getAllReceiversBackpressure(), + this.getAllProceduresBackpressure() + ); +}; + AGServerSocket.prototype.getInboundBackpressure = function () { return this.inboundReceivedMessageCount - this.inboundProcessedMessageCount; }; @@ -183,6 +193,8 @@ AGServerSocket.prototype._startBatchOnHandshake = function () { }, this.batchOnHandshakeDuration); }; +// ---- Receiver logic ---- + AGServerSocket.prototype.receiver = function (receiverName) { return this._receiverDemux.stream(receiverName); }; @@ -203,6 +215,44 @@ AGServerSocket.prototype.killAllReceivers = function () { this._receiverDemux.killAll(); }; +AGServerSocket.prototype.killReceiverConsumer = function (consumerId) { + this._receiverDemux.killConsumer(consumerId); +}; + +AGServerSocket.prototype.getReceiverConsumerStats = function (consumerId) { + return this._receiverDemux.getConsumerStats(consumerId); +}; + +AGServerSocket.prototype.getReceiverConsumerStatsList = function (receiverName) { + return this._receiverDemux.getConsumerStatsList(receiverName); +}; + +AGServerSocket.prototype.getAllReceiversConsumerStatsList = function () { + return this._receiverDemux.getConsumerStatsListAll(); +}; + +AGServerSocket.prototype.getReceiverBackpressure = function (receiverName) { + return this._receiverDemux.getBackpressure(receiverName); +}; + +AGServerSocket.prototype.getAllReceiversBackpressure = function () { + return this._receiverDemux.getBackpressureAll(); +}; + +AGServerSocket.prototype.getReceiverConsumerBackpressure = function (consumerId) { + return this._receiverDemux.getConsumerBackpressure(consumerId); +}; + +AGServerSocket.prototype.hasReceiverConsumer = function (receiverName, consumerId) { + return this._receiverDemux.hasConsumer(receiverName, consumerId); +}; + +AGServerSocket.prototype.hasAnyReceiverConsumer = function (consumerId) { + return this._receiverDemux.hasConsumerAll(consumerId); +}; + +// ---- Procedure logic ---- + AGServerSocket.prototype.procedure = function (procedureName) { return this._procedureDemux.stream(procedureName); }; @@ -223,6 +273,42 @@ AGServerSocket.prototype.killAllProcedures = function () { this._procedureDemux.killAll(); }; +AGServerSocket.prototype.killProcedureConsumer = function (consumerId) { + this._procedureDemux.killConsumer(consumerId); +}; + +AGServerSocket.prototype.getProcedureConsumerStats = function (consumerId) { + return this._procedureDemux.getConsumerStats(consumerId); +}; + +AGServerSocket.prototype.getProcedureConsumerStatsList = function (procedureName) { + return this._procedureDemux.getConsumerStatsList(procedureName); +}; + +AGServerSocket.prototype.getAllProceduresConsumerStatsList = function () { + return this._procedureDemux.getConsumerStatsListAll(); +}; + +AGServerSocket.prototype.getProcedureBackpressure = function (procedureName) { + return this._procedureDemux.getBackpressure(procedureName); +}; + +AGServerSocket.prototype.getAllProceduresBackpressure = function () { + return this._procedureDemux.getBackpressureAll(); +}; + +AGServerSocket.prototype.getProcedureConsumerBackpressure = function (consumerId) { + return this._procedureDemux.getConsumerBackpressure(consumerId); +}; + +AGServerSocket.prototype.hasProcedureConsumer = function (procedureName, consumerId) { + return this._procedureDemux.hasConsumer(procedureName, consumerId); +}; + +AGServerSocket.prototype.hasAnyProcedureConsumer = function (consumerId) { + return this._procedureDemux.hasConsumerAll(consumerId); +}; + AGServerSocket.prototype._handleInboundMessageStream = async function (pongMessage) { for await (let message of this.inboundMessageStream) { this.inboundProcessedMessageCount++; diff --git a/test/integration.js b/test/integration.js index 5fac469..c54373b 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1683,6 +1683,54 @@ describe('Integration tests', function () { assert.equal(backpressureHistory[14] > 8, true); assert.equal(backpressureHistory[19], 1); }); + + it('Should be able to getBackpressure() on a socket object and it should be the highest backpressure', async function () { + server = asyngularServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + bindFailureHandlers(server); + + let backpressureHistory = []; + + server.setMiddleware(server.MIDDLEWARE_INBOUND_RAW, async (middlewareStream) => { + for await (let action of middlewareStream) { + backpressureHistory.push(action.socket.getBackpressure()); + action.allow(); + } + }); + + server.setMiddleware(server.MIDDLEWARE_INBOUND, async (middlewareStream) => { + for await (let action of middlewareStream) { + if (action.data === 5) { + await wait(100); + } + action.allow(); + } + }); + + await server.listener('ready').once(); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + await client.listener('connect').once(); + for (let i = 0; i < 20; i++) { + await wait(10); + client.transmitPublish('foo', i); + } + + await wait(400); + + // Backpressure should go up and come back down. + assert.equal(backpressureHistory.length, 21); + assert.equal(backpressureHistory[0], 1); + assert.equal(backpressureHistory[12] > 4, true); + assert.equal(backpressureHistory[14] > 6, true); + assert.equal(backpressureHistory[19], 1); + }); }); describe('Socket pub/sub', function () { From d1b3c7b193bca7cb4cf7757e8b4a399e18c97375 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 2 Mar 2019 17:55:18 +0100 Subject: [PATCH 177/265] v5.3.0 --- package.json | 10 +++++----- serversocket.js | 49 ++++++++++++++++++++++++++++++------------------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 06e6375..7837669 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "5.2.0", + "version": "5.3.0", "description": "Server module for Asyngular", "main": "index.js", "repository": { @@ -10,18 +10,18 @@ "dependencies": { "ag-auth": "^1.0.1", "ag-request": "^1.0.0", - "ag-simple-broker": "^4.0.2", - "async-stream-emitter": "^3.0.1", + "ag-simple-broker": "^4.0.3", + "async-stream-emitter": "^3.0.2", "base64id": "^1.0.0", "lodash.clonedeep": "^4.5.0", "sc-errors": "^2.0.0", "sc-formatter": "^3.0.2", - "stream-demux": "^7.0.0", + "stream-demux": "^7.0.1", "writable-consumable-stream": "^1.1.1", "ws": "^6.1.2" }, "devDependencies": { - "asyngular-client": "^5.2.2", + "asyngular-client": "^5.3.0", "localStorage": "^1.0.3", "mocha": "^5.2.0" }, diff --git a/serversocket.js b/serversocket.js index 20e47db..1ef0660 100644 --- a/serversocket.js +++ b/serversocket.js @@ -46,6 +46,8 @@ function AGServerSocket(id, server, socket, protocolVersion) { this.inboundMessageStream = new WritableConsumableStream(); this.outboundPacketStream = new WritableConsumableStream(); + this.middlewareHandshakeStream = this.request[this.server.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; + this.middlewareInboundRawStream = new WritableConsumableStream(); this.middlewareInboundRawStream.type = this.server.MIDDLEWARE_INBOUND_RAW; @@ -345,7 +347,6 @@ AGServerSocket.prototype._handleInboundMessageStream = async function (pongMessa }; AGServerSocket.prototype._handleHandshakeTimeout = function () { - let middlewareHandshakeStream = this.request[this.server.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; this.disconnect(4005); }; @@ -359,10 +360,8 @@ AGServerSocket.prototype._processHandshakeRequest = async function (request) { action.socket = this; action.type = AGAction.HANDSHAKE_AG; - let middlewareHandshakeStream = this.request[this.server.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; - try { - await this.server._processMiddlewareAction(middlewareHandshakeStream, action); + await this.server._processMiddlewareAction(this.middlewareHandshakeStream, action); } catch (error) { if (error.statusCode == null) { error.statusCode = HANDSHAKE_REJECTION_STATUS_CODE; @@ -430,7 +429,7 @@ AGServerSocket.prototype._processHandshakeRequest = async function (request) { this.emit('connect', serverSocketStatus); this.server.emit('connection', {socket: this, ...serverSocketStatus}); - middlewareHandshakeStream.close(); + this.middlewareHandshakeStream.close(); }; AGServerSocket.prototype._processAuthenticateRequest = async function (request) { @@ -815,46 +814,58 @@ AGServerSocket.prototype._abortAllPendingEventsDueToBadConnection = function (fa }); }; -AGServerSocket.prototype.closeMiddlewareStreams = function () { - let middlewareHandshakeStream = this.request[this.server.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; - middlewareHandshakeStream.close(); +AGServerSocket.prototype.closeAllMiddlewares = function () { + this.middlewareHandshakeStream.close(); this.middlewareInboundRawStream.close(); this.middlewareInboundStream.close(); this.middlewareOutboundStream.close(); }; -AGServerSocket.prototype.closeIOStreams = function () { +AGServerSocket.prototype.closeInput = function () { this.inboundMessageStream.close(); +}; + +AGServerSocket.prototype.closeOutput = function () { this.outboundPacketStream.close(); }; -AGServerSocket.prototype.closeAllStreams = function () { - this.closeMiddlewareStreams(); +AGServerSocket.prototype.closeIO = function () { + this.closeInput(); + this.closeOutput(); +}; - this.closeIOStreams(); +AGServerSocket.prototype.closeAllStreams = function () { + this.closeAllMiddlewares(); + this.closeIO(); this.closeAllReceivers(); this.closeAllProcedures(); this.closeAllListeners(); }; -AGServerSocket.prototype.killMiddlewareStreams = function () { - let middlewareHandshakeStream = this.request[this.server.SYMBOL_MIDDLEWARE_HANDSHAKE_STREAM]; - middlewareHandshakeStream.kill(); +AGServerSocket.prototype.killAllMiddlewares = function () { + this.middlewareHandshakeStream.kill(); this.middlewareInboundRawStream.kill(); this.middlewareInboundStream.kill(); this.middlewareOutboundStream.kill(); }; -AGServerSocket.prototype.killIOStreams = function () { +AGServerSocket.prototype.killInput = function () { this.inboundMessageStream.kill(); +}; + +AGServerSocket.prototype.killOutput = function () { this.outboundPacketStream.kill(); }; -AGServerSocket.prototype.killAllStreams = function () { - this.killMiddlewareStreams(); +AGServerSocket.prototype.killIO = function () { + this.killInput(); + this.killOutput(); +}; - this.killIOStreams(); +AGServerSocket.prototype.killAllStreams = function () { + this.killAllMiddlewares(); + this.killIO(); this.killAllReceivers(); this.killAllProcedures(); From 2d808af0a3ebad2ac2d3f31336755b719b2acc63 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 2 Mar 2019 18:40:11 +0100 Subject: [PATCH 178/265] 5.3.1 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7837669..40dd10f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "5.3.0", + "version": "5.3.1", "description": "Server module for Asyngular", "main": "index.js", "repository": { @@ -21,7 +21,7 @@ "ws": "^6.1.2" }, "devDependencies": { - "asyngular-client": "^5.3.0", + "asyngular-client": "^5.3.1", "localStorage": "^1.0.3", "mocha": "^5.2.0" }, From 0d6a326d68bd01c34ca638fc12828bb4907f49d2 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 2 Mar 2019 20:20:43 +0100 Subject: [PATCH 179/265] v5.3.2 --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 40dd10f..629e1fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "5.3.1", + "version": "5.3.2", "description": "Server module for Asyngular", "main": "index.js", "repository": { @@ -10,7 +10,7 @@ "dependencies": { "ag-auth": "^1.0.1", "ag-request": "^1.0.0", - "ag-simple-broker": "^4.0.3", + "ag-simple-broker": "^4.0.5", "async-stream-emitter": "^3.0.2", "base64id": "^1.0.0", "lodash.clonedeep": "^4.5.0", @@ -21,7 +21,7 @@ "ws": "^6.1.2" }, "devDependencies": { - "asyngular-client": "^5.3.1", + "asyngular-client": "^5.3.2", "localStorage": "^1.0.3", "mocha": "^5.2.0" }, From 30db7d8c779b364a44b2b475a99416bb8b71ddc6 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 6 Mar 2019 22:21:09 +0100 Subject: [PATCH 180/265] Do not emit error if the removeAuthToken transmit fails --- serversocket.js | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/serversocket.js b/serversocket.js index 1ef0660..692ad72 100644 --- a/serversocket.js +++ b/serversocket.js @@ -1096,20 +1096,12 @@ AGServerSocket.prototype._handleOutboundPacketStream = async function () { this.outboundSentMessageCount++; continue; } - await this._transmit(packet.event, packet.data, packet.options); + await this._processTransmit(packet.event, packet.data, packet.options); this.outboundSentMessageCount++; } }; -AGServerSocket.prototype.transmit = async function (event, data, options) { - if (this.state !== this.OPEN) { - let error = new BadConnectionError( - `Socket transmit "${event}" was aborted due to a bad connection`, - 'connectAbort' - ); - this.emitError(error); - return; - } +AGServerSocket.prototype._transmit = async function (event, data, options) { if (this.cloneData) { data = cloneDeep(data); } @@ -1121,6 +1113,18 @@ AGServerSocket.prototype.transmit = async function (event, data, options) { }); }; +AGServerSocket.prototype.transmit = async function (event, data, options) { + if (this.state !== this.OPEN) { + let error = new BadConnectionError( + `Socket transmit "${event}" was aborted due to a bad connection`, + 'connectAbort' + ); + this.emitError(error); + return; + } + this._transmit(event, data, options); +}; + AGServerSocket.prototype.invoke = async function (event, data, options) { if (this.state !== this.OPEN) { let error = new BadConnectionError( @@ -1145,7 +1149,7 @@ AGServerSocket.prototype.invoke = async function (event, data, options) { }); }; -AGServerSocket.prototype._transmit = async function (event, data, options) { +AGServerSocket.prototype._processTransmit = async function (event, data, options) { let newData; let useCache = options ? options.useCache : false; let packet = {event, data}; @@ -1372,7 +1376,7 @@ AGServerSocket.prototype.deauthenticate = async function (options) { } return; } - this.transmit('#removeAuthToken'); + this._transmit('#removeAuthToken'); }; AGServerSocket.prototype.kickOut = function (channel, message) { From 8c3584a01644b06c11a6bc981c311c2f0585a0df Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 6 Mar 2019 22:31:36 +0100 Subject: [PATCH 181/265] Bump asyngular-client --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 629e1fd..ac79f67 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "ws": "^6.1.2" }, "devDependencies": { - "asyngular-client": "^5.3.2", + "asyngular-client": "^5.3.3", "localStorage": "^1.0.3", "mocha": "^5.2.0" }, From bceb6693465447816756da107c91409ec60e21f9 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 6 Mar 2019 22:34:58 +0100 Subject: [PATCH 182/265] v5.3.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ac79f67..8e010ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "5.3.2", + "version": "5.3.3", "description": "Server module for Asyngular", "main": "index.js", "repository": { From 2eccef89514b7175d1c16c07a00cc05d60e57a3a Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 17 Mar 2019 23:21:23 +0100 Subject: [PATCH 183/265] Fix middleware block if no error is passed --- server.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/server.js b/server.js index 55ac075..0b7d664 100644 --- a/server.js +++ b/server.js @@ -299,10 +299,16 @@ AGServer.prototype._processMiddlewareAction = async function (middlewareStream, } } catch (error) { let clientError; - if (error.silent) { + if (!error) { + error = new SilentMiddlewareBlockedError( + `The ${action.type} AGAction was blocked by ${middlewareStream.type} middleware`, + middlewareStream.type + ); + clientError = error; + } else if (error.silent) { clientError = new SilentMiddlewareBlockedError( - `AGAction was blocked by ${action.name} middleware`, - action.name + `The ${action.type} AGAction was blocked by ${middlewareStream.type} middleware`, + middlewareStream.type ); } else { clientError = error; From be5fb31e4186450cfde4aeb8d7cad19b33c7f77e Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 17 Mar 2019 23:22:34 +0100 Subject: [PATCH 184/265] v5.3.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8e010ba..70cbf82 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "5.3.3", + "version": "5.3.4", "description": "Server module for Asyngular", "main": "index.js", "repository": { From 55460db7cfcf5ff4201a9fd1aae06db41600e645 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 19 Mar 2019 00:02:13 +0100 Subject: [PATCH 185/265] Allow each invoke and transmit call to have a custom ackTimeout --- serversocket.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/serversocket.js b/serversocket.js index 692ad72..8af38b5 100644 --- a/serversocket.js +++ b/serversocket.js @@ -1196,6 +1196,8 @@ AGServerSocket.prototype._processTransmit = async function (event, data, options }; AGServerSocket.prototype._invoke = async function (event, data, options) { + options = options || {}; + return new Promise((resolve, reject) => { let eventObject = { event, @@ -1205,11 +1207,13 @@ AGServerSocket.prototype._invoke = async function (event, data, options) { eventObject.data = data; } + let ackTimeout = options.ackTimeout == null ? this.server.ackTimeout : options.ackTimeout; + let timeout = setTimeout(() => { let error = new TimeoutError(`Event response for "${event}" timed out`); delete this._callbackMap[eventObject.cid]; reject(error); - }, this.server.ackTimeout); + }, ackTimeout); this._callbackMap[eventObject.cid] = { event, @@ -1223,7 +1227,7 @@ AGServerSocket.prototype._invoke = async function (event, data, options) { timeout }; - if (options && options.useCache && options.stringifiedData != null && !this.isBufferingBatch) { + if (options.useCache && options.stringifiedData != null && !this.isBufferingBatch) { // Optimized this.send(options.stringifiedData); } else { From 3e343fa2c2bebf1fc69af47234ef5a81ed833345 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 19 Mar 2019 00:03:16 +0100 Subject: [PATCH 186/265] v5.4.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 70cbf82..6fb25fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "5.3.4", + "version": "5.4.0", "description": "Server module for Asyngular", "main": "index.js", "repository": { @@ -21,7 +21,7 @@ "ws": "^6.1.2" }, "devDependencies": { - "asyngular-client": "^5.3.3", + "asyngular-client": "^5.4.0", "localStorage": "^1.0.3", "mocha": "^5.2.0" }, From b87fc1c3e6a3eabf47c07c94a0a616db1c52c345 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 2 Jun 2019 17:35:25 +0200 Subject: [PATCH 187/265] Simplify authentication --- test/integration.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/integration.js b/test/integration.js index c54373b..dece83f 100644 --- a/test/integration.js +++ b/test/integration.js @@ -696,8 +696,11 @@ describe('Integration tests', function () { }); await client.listener('connect').once(); - await client.invoke('login', {username: 'bob'}); - await client.listener('authenticate').once(); + + await Promise.all([ + client.invoke('login', {username: 'bob'}), + client.listener('authenticate').once() + ]); assert.equal(authTokenSignedEventEmitted, true); }); From cd93c6c67590867fff8cacd1cd6d89324d8def64 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 2 Jun 2019 19:29:50 +0200 Subject: [PATCH 188/265] v6.0.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6fb25fd..a25de98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "5.4.0", + "version": "6.0.0", "description": "Server module for Asyngular", "main": "index.js", "repository": { @@ -21,7 +21,7 @@ "ws": "^6.1.2" }, "devDependencies": { - "asyngular-client": "^5.4.0", + "asyngular-client": "^6.0.0", "localStorage": "^1.0.3", "mocha": "^5.2.0" }, From a1d6e5cb0db0b069d6b47cc55beaf177676b51b5 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 17 Jul 2019 23:17:47 +0200 Subject: [PATCH 189/265] Bump dependencies --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a25de98..7ae620e 100644 --- a/package.json +++ b/package.json @@ -18,10 +18,10 @@ "sc-formatter": "^3.0.2", "stream-demux": "^7.0.1", "writable-consumable-stream": "^1.1.1", - "ws": "^6.1.2" + "ws": "^7.1.0" }, "devDependencies": { - "asyngular-client": "^6.0.0", + "asyngular-client": "^6.1.0", "localStorage": "^1.0.3", "mocha": "^5.2.0" }, From 0d7ab4d7c0816beb8d5d1062aa0f3b877744edfe Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 17 Jul 2019 23:18:06 +0200 Subject: [PATCH 190/265] v6.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7ae620e..f150f94 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "6.0.0", + "version": "6.1.0", "description": "Server module for Asyngular", "main": "index.js", "repository": { From 6321d69de8d436ac6bda778fc400c185cb1c7b22 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 19 Jul 2019 08:57:52 +0200 Subject: [PATCH 191/265] Add invoke middleware tests --- test/integration.js | 79 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/test/integration.js b/test/integration.js index dece83f..646b52d 100644 --- a/test/integration.js +++ b/test/integration.js @@ -126,6 +126,12 @@ function connectionHandler(socket) { rpc.end(); } })(); + + (async () => { + for await (let rpc of socket.procedure('proc')) { + rpc.end('success ' + rpc.data); + } + })(); }; function bindFailureHandlers(server) { @@ -2592,6 +2598,12 @@ describe('Integration tests', function () { }); bindFailureHandlers(server); + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + await server.listener('ready').once(); }); @@ -2869,6 +2881,73 @@ describe('Integration tests', function () { }); describe('MIDDLEWARE_INBOUND', function () { + describe('INVOKE action', function () { + it('Should run INVOKE action in middleware if client invokes an RPC', async function () { + let middlewareWasExecuted = false; + let middlewareAction = null; + + let middlewareFunction = async function (middlewareStream) { + for await (let action of middlewareStream) { + if (action.type === AGAction.INVOKE) { + middlewareWasExecuted = true; + middlewareAction = action; + } + action.allow(); + } + }; + server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + let result = await client.invoke('proc', 123); + + assert.equal(middlewareWasExecuted, true); + assert.notEqual(middlewareAction, null); + assert.equal(result, 'success 123'); + }); + + it('Should send back custom Error if INVOKE action in middleware blocks the client RPC', async function () { + let middlewareWasExecuted = false; + let middlewareAction = null; + + let middlewareFunction = async function (middlewareStream) { + for await (let action of middlewareStream) { + if (action.type === AGAction.INVOKE) { + middlewareWasExecuted = true; + middlewareAction = action; + + let customError = new Error('Invoke action was blocked'); + customError.name = 'BlockedInvokeError'; + action.block(customError); + continue; + } + action.allow(); + } + }; + server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + let result; + let error; + try { + result = await client.invoke('proc', 123); + } catch (err) { + error = err; + } + + assert.equal(result, null); + assert.notEqual(error, null); + assert.equal(error.name, 'BlockedInvokeError'); + }); + }); + describe('AUTHENTICATE action', function () { it('Should not run AUTHENTICATE action in middleware if JWT token does not exist', async function () { let middlewareWasExecuted = false; From 8382a264bd971fb0a9169be3a6630b8d6702628f Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 31 Aug 2019 14:14:53 +0200 Subject: [PATCH 192/265] Add strict handshake mode --- package.json | 2 +- server.js | 2 + serversocket.js | 34 ++++++++++++++--- test/integration.js | 93 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 122 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index f150f94..33519c3 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "async-stream-emitter": "^3.0.2", "base64id": "^1.0.0", "lodash.clonedeep": "^4.5.0", - "sc-errors": "^2.0.0", + "sc-errors": "^2.0.1", "sc-formatter": "^3.0.2", "stream-demux": "^7.0.1", "writable-consumable-stream": "^1.1.1", diff --git a/server.js b/server.js index 0b7d664..165d12e 100644 --- a/server.js +++ b/server.js @@ -27,6 +27,7 @@ function AGServer(options) { allowClientPublish: true, ackTimeout: 10000, handshakeTimeout: 10000, + strictHandshake: true, pingTimeout: 20000, pingTimeoutDisabled: false, pingInterval: 8000, @@ -59,6 +60,7 @@ function AGServer(options) { this.httpServer = opts.httpServer; this.socketChannelLimit = opts.socketChannelLimit; this.protocolVersion = opts.protocolVersion; + this.strictHandshake = opts.strictHandshake; this.brokerEngine = opts.brokerEngine; this.middlewareEmitFailures = opts.middlewareEmitFailures; diff --git a/serversocket.js b/serversocket.js index 8af38b5..26b580d 100644 --- a/serversocket.js +++ b/serversocket.js @@ -89,7 +89,7 @@ function AGServerSocket(id, server, socket, protocolVersion) { }); this.socket.on('close', (code, data) => { - this._onClose(code, data); + this._destroy(code, data); }); let pongMessage; @@ -317,6 +317,11 @@ AGServerSocket.prototype._handleInboundMessageStream = async function (pongMessa let isPong = message === pongMessage; if (isPong) { + if (this.server.strictHandshake && this.state === this.CONNECTING) { + this._destroy(4009); + this.socket.close(4009); + continue; + } let token = this.getAuthToken(); if (this.isAuthTokenExpired(token)) { this.deauthenticate(); @@ -332,6 +337,10 @@ AGServerSocket.prototype._handleInboundMessageStream = async function (pongMessa error.name = 'InvalidMessageError'; } this.emitError(error); + if (this.server.strictHandshake && this.state === this.CONNECTING) { + this._destroy(4009); + this.socket.close(4009); + } continue; } @@ -627,6 +636,12 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message return; } + if (this.server.strictHandshake && this.state === this.CONNECTING) { + this._destroy(4009); + this.socket.close(4009); + + return; + } if (eventName === '#authenticate') { // Let AGServer handle these events. let request = new AGRequest(this, packet.cid, eventName, packet.data); @@ -753,6 +768,13 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message return; } + if (this.server.strictHandshake && this.state === this.CONNECTING) { + this._destroy(4009); + this.socket.close(4009); + + return; + } + if (packet && packet.rid != null) { // If incoming message is a response to a previously sent message let ret = this._callbackMap[packet.rid]; @@ -774,7 +796,7 @@ AGServerSocket.prototype._resetPongTimeout = function () { } clearTimeout(this._pingTimeoutTicker); this._pingTimeoutTicker = setTimeout(() => { - this._onClose(4001); + this._destroy(4001); this.socket.close(4001); }, this.server.pingTimeout); }; @@ -872,7 +894,7 @@ AGServerSocket.prototype.killAllStreams = function () { this.killAllListeners(); }; -AGServerSocket.prototype._onClose = function (code, reason) { +AGServerSocket.prototype._destroy = function (code, reason) { clearInterval(this._pingIntervalTicker); clearTimeout(this._pingTimeoutTicker); @@ -968,7 +990,7 @@ AGServerSocket.prototype.disconnect = function (code, data) { } if (this.state !== this.CLOSED) { - this._onClose(code, data); + this._destroy(code, data); this.socket.close(code, data); } }; @@ -981,7 +1003,7 @@ AGServerSocket.prototype.send = function (data, options) { this.socket.send(data, options, (error) => { if (error) { this.emitError(error); - this._onClose(1006, error.toString()); + this._destroy(1006, error.toString()); } }); }; @@ -1310,7 +1332,7 @@ AGServerSocket.prototype.setAuthToken = async function (data, options) { signedAuthToken = await this.server.auth.signToken(authToken, this.server.signatureKey, options); } catch (error) { this.emitError(error); - this._onClose(4002, error.toString()); + this._destroy(4002, error.toString()); this.socket.close(4002); throw error; } diff --git a/test/integration.js b/test/integration.js index 646b52d..de286ef 100644 --- a/test/integration.js +++ b/test/integration.js @@ -921,6 +921,95 @@ describe('Integration tests', function () { let {socket} = await server.listener('handshake').once(); assert.notEqual(socket.exchange, null); }); + + it('Should close the connection if the client tries to send a message before the handshake', async function () { + server = asyngularServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + + await server.listener('ready').once(); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + client.transport.socket.onopen = function () { + client.transport.socket.send(Buffer.alloc(0)); + }; + + let {code: closeCode} = await client.listener('close').once(200); + + assert.equal(closeCode, 4009); + }); + + it('Should close the connection if the client tries to send a ping before the handshake', async function () { + server = asyngularServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + + await server.listener('ready').once(); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + client.transport.socket.onopen = function () { + client.transport.socket.send(''); + }; + + let {code: closeCode} = await client.listener('close').once(200); + + assert.equal(closeCode, 4009); + }); + + it('Should not close the connection if the client tries to send a message before the handshake and strictHandshake is false', async function () { + server = asyngularServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, + strictHandshake: false + }); + + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + + await server.listener('ready').once(); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + let realOnOpenFunction = client.transport.socket.onopen; + + client.transport.socket.onopen = function () { + client.transport.socket.send(Buffer.alloc(0)); + return realOnOpenFunction.apply(this, arguments); + }; + + let packet = await client.listener('connect').once(200); + + assert.notEqual(packet, null); + assert.notEqual(packet.id, null); + }); }); describe('Socket connection', function () { @@ -1870,7 +1959,7 @@ describe('Integration tests', function () { await wait(1000); assert.equal(isSubscribed, false); assert.notEqual(error, null); - assert.equal(error.name, 'InvalidActionError'); + assert.equal(error.name, 'BadConnectionError'); }); it('Server should be able to handle invalid #subscribe and #unsubscribe and #publish events without crashing', async function () { @@ -2794,7 +2883,7 @@ describe('Integration tests', function () { assert.equal(abortReason, 'InvalidAuthQueryHandshakeError: AG handshake failed because of invalid query auth parameters'); }); - it('Should connect with a delay if next() is called after a timeout inside the middleware function', async function () { + it('Should connect with a delay if allow() is called after a timeout inside the middleware function', async function () { let createConnectionTime = null; let connectEventTime = null; let abortStatus; From cb6229a0c7d286bdd110fa975ffce88000f67462 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 31 Aug 2019 15:18:38 +0200 Subject: [PATCH 193/265] Emit default close reason if a custom one is not provided --- serversocket.js | 13 ++++++++----- test/integration.js | 11 ++++++++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/serversocket.js b/serversocket.js index 26b580d..5a36e78 100644 --- a/serversocket.js +++ b/serversocket.js @@ -88,8 +88,8 @@ function AGServerSocket(id, server, socket, protocolVersion) { this.emitError(err); }); - this.socket.on('close', (code, data) => { - this._destroy(code, data); + this.socket.on('close', (code, reason) => { + this._destroy(code, reason); }); let pongMessage; @@ -903,6 +903,9 @@ AGServerSocket.prototype._destroy = function (code, reason) { if (this.state === this.CLOSED) { this._abortAllPendingEventsDueToBadConnection('connectAbort'); } else { + if (!reason && AGServerSocket.errorStatuses[code]) { + reason = AGServerSocket.errorStatuses[code]; + } let prevState = this.state; this.state = this.CLOSED; if (prevState === this.CONNECTING) { @@ -981,7 +984,7 @@ AGServerSocket.prototype._destroy = function (code, reason) { } }; -AGServerSocket.prototype.disconnect = function (code, data) { +AGServerSocket.prototype.disconnect = function (code, reason) { code = code || 1000; if (typeof code !== 'number') { @@ -990,8 +993,8 @@ AGServerSocket.prototype.disconnect = function (code, data) { } if (this.state !== this.CLOSED) { - this._destroy(code, data); - this.socket.close(code, data); + this._destroy(code, reason); + this.socket.close(code, reason); } }; diff --git a/test/integration.js b/test/integration.js index de286ef..4408923 100644 --- a/test/integration.js +++ b/test/integration.js @@ -945,9 +945,14 @@ describe('Integration tests', function () { client.transport.socket.send(Buffer.alloc(0)); }; - let {code: closeCode} = await client.listener('close').once(200); - - assert.equal(closeCode, 4009); + let results = await Promise.all([ + server.listener('closure').once(200), + client.listener('close').once(200) + ]); + assert.equal(results[0].code, 4009); + assert.equal(results[0].reason, 'Server received a message before the client handshake'); + assert.equal(results[1].code, 4009); + assert.equal(results[1].reason, 'Server received a message before the client handshake'); }); it('Should close the connection if the client tries to send a ping before the handshake', async function () { From 29c5b638a4973a2b42861666aab0f3f31b63f33f Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 31 Aug 2019 15:31:30 +0200 Subject: [PATCH 194/265] v6.2.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 33519c3..301d65d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "6.1.0", + "version": "6.2.0", "description": "Server module for Asyngular", "main": "index.js", "repository": { @@ -21,7 +21,7 @@ "ws": "^7.1.0" }, "devDependencies": { - "asyngular-client": "^6.1.0", + "asyngular-client": "^6.2.0", "localStorage": "^1.0.3", "mocha": "^5.2.0" }, From cbcdfc6b1a315e251e87ae371f3224fac45e7c36 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 5 Nov 2019 20:33:18 +0100 Subject: [PATCH 195/265] Do not allow setting the auth token before the handshake has completed --- serversocket.js | 8 ++++++++ test/integration.js | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/serversocket.js b/serversocket.js index 5a36e78..312e15a 100644 --- a/serversocket.js +++ b/serversocket.js @@ -1282,6 +1282,14 @@ AGServerSocket.prototype.triggerAuthenticationEvents = function (oldAuthState) { }; AGServerSocket.prototype.setAuthToken = async function (data, options) { + if (this.state === this.CONNECTING) { + let err = new InvalidActionError( + 'Cannot call setAuthToken before completing the handshake' + ); + this.emitError(err); + throw err; + } + let authToken = cloneDeep(data); let oldAuthState = this.authState; this.authState = this.AUTHENTICATED; diff --git a/test/integration.js b/test/integration.js index 4408923..c730176 100644 --- a/test/integration.js +++ b/test/integration.js @@ -2921,6 +2921,42 @@ describe('Integration tests', function () { assert.equal(connectEventTime - createConnectionTime > 400, true); }); + it('Should not be allowed to call req.socket.setAuthToken from inside middleware', async function () { + let didAuthenticationEventTrigger = false; + let setAuthTokenError; + + server.setMiddleware(server.MIDDLEWARE_HANDSHAKE, async function (middlewareStream) { + for await (let {socket, type, allow, block} of middlewareStream) { + if (type === AGAction.HANDSHAKE_AG) { + try { + await socket.setAuthToken({username: 'alice'}); + } catch (error) { + setAuthTokenError = error; + } + } + allow(); + } + }); + + (async () => { + let event = await server.listener('authentication').once(); + didAuthenticationEventTrigger = true; + })(); + + client = asyngularClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + let event = await client.listener('connect').once(); + assert.equal(event.isAuthenticated, false); + assert.equal(client.authState, client.UNAUTHENTICATED); + assert.equal(client.authToken, null); + assert.equal(didAuthenticationEventTrigger, false); + assert.notEqual(setAuthTokenError, null); + assert.equal(setAuthTokenError.name, 'InvalidActionError'); + }); + it('Delaying handshake for one client should not affect other clients', async function () { let middlewareFunction = async function (middlewareStream) { for await (let action of middlewareStream) { From 674db23d4f68fc9b3ffb28b59d91fd1a44c51a55 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 12 Nov 2019 19:28:37 +0100 Subject: [PATCH 196/265] Bump ag-simple-broker to fix a bug related to exchange channel subscriptions --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 301d65d..624b9c7 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "dependencies": { "ag-auth": "^1.0.1", "ag-request": "^1.0.0", - "ag-simple-broker": "^4.0.5", + "ag-simple-broker": "^4.0.6", "async-stream-emitter": "^3.0.2", "base64id": "^1.0.0", "lodash.clonedeep": "^4.5.0", From 3ee83217e3091aa6f9fb67d0ba766dec182c1780 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 12 Nov 2019 19:29:02 +0100 Subject: [PATCH 197/265] v6.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 624b9c7..a430efb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asyngular-server", - "version": "6.2.0", + "version": "6.2.1", "description": "Server module for Asyngular", "main": "index.js", "repository": { From 85683d4bfbc586d898df3d15183a57346e05a939 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 19 Jan 2020 20:24:48 +0100 Subject: [PATCH 198/265] v15.0.0 - Rename asyngular to socketcluster --- README.md | 28 ++--- action.js | 2 +- index.js | 10 +- package.json | 13 +-- server.js | 2 +- serversocket.js | 2 +- test/integration.js | 278 ++++++++++++++++++++++---------------------- 7 files changed, 167 insertions(+), 168 deletions(-) diff --git a/README.md b/README.md index d794ae5..4103e1f 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ -# Asyngular server -Minimal server module for Asyngular. +# SocketCluster server +Minimal server module for SocketCluster. -This is a stand-alone server module for Asyngular (SocketCluster with full async/await support). -Asyngular's protocol is backwards compatible with the SocketCluster protocol. +This is a stand-alone server module for SocketCluster. +SocketCluster's protocol is backwards compatible with the SocketCluster protocol. ## Setting up -You will need to install both ```asyngular-server``` and ```asyngular-client``` (https://github.com/SocketCluster/asyngular-client). +You will need to install both ```socketcluster-server``` and ```socketcluster-client``` (https://github.com/SocketCluster/socketcluster-client). To install this module: ```bash -npm install asyngular-server +npm install socketcluster-server ``` ## Usage @@ -18,10 +18,10 @@ npm install asyngular-server You need to attach it to an existing Node.js http or https server (example): ```js const http = require('http'); -const asyngularServer = require('asyngular-server'); +const socketClusterServer = require('socketcluster-server'); let httpServer = http.createServer(); -let agServer = asyngularServer.attach(httpServer); +let agServer = socketClusterServer.attach(httpServer); (async () => { // Handle new inbound sockets. @@ -53,10 +53,10 @@ let agServer = asyngularServer.attach(httpServer); httpServer.listen(8000); ``` -For more detailed examples of how to use Asyngular, see `test/integration.js`. -Also, see tests from the `asyngular-client` module. +For more detailed examples of how to use SocketCluster, see `test/integration.js`. +Also, see tests from the `socketcluster-client` module. -Asyngular can work without the `for-await-of` loop; a `while` loop with `await` statements can be used instead. +SocketCluster can work without the `for-await-of` loop; a `while` loop with `await` statements can be used instead. See https://github.com/SocketCluster/stream-demux#usage ## Compatibility mode @@ -64,7 +64,7 @@ See https://github.com/SocketCluster/stream-demux#usage For compatibility with existing SocketCluster clients, set the `protocolVersion` to `1` and make sure that the `path` matches your old client path: ```js -let agServer = asyngularServer.attach(httpServer, { +let agServer = socketClusterServer.attach(httpServer, { protocolVersion: 1, path: '/socketcluster/' }); @@ -72,8 +72,8 @@ let agServer = asyngularServer.attach(httpServer, { ## Running the tests -- Clone this repo: `git clone git@github.com:SocketCluster/asyngular-server.git` -- Navigate to project directory: `cd asyngular-server` +- Clone this repo: `git clone git@github.com:SocketCluster/socketcluster-server.git` +- Navigate to project directory: `cd socketcluster-server` - Install all dependencies: `npm install` - Run the tests: `npm test` diff --git a/action.js b/action.js index ed5416e..1d4d3d7 100644 --- a/action.js +++ b/action.js @@ -26,7 +26,7 @@ function AGAction() { } AGAction.prototype.HANDSHAKE_WS = AGAction.HANDSHAKE_WS = 'handshakeWS'; -AGAction.prototype.HANDSHAKE_AG = AGAction.HANDSHAKE_AG = 'handshakeAG'; +AGAction.prototype.HANDSHAKE_SC = AGAction.HANDSHAKE_SC = 'handshakeSC'; AGAction.prototype.MESSAGE = AGAction.MESSAGE = 'message'; diff --git a/index.js b/index.js index 537f514..fda19d2 100644 --- a/index.js +++ b/index.js @@ -41,11 +41,11 @@ module.exports.listen = function (port, options, fn) { res.end('Not Implemented'); }); - let asyngularServer = module.exports.attach(server, options); - asyngularServer.httpServer = server; + let socketClusterServer = module.exports.attach(server, options); + socketClusterServer.httpServer = server; server.listen(port, fn); - return asyngularServer; + return socketClusterServer; }; /** @@ -62,6 +62,6 @@ module.exports.attach = function (server, options) { options = {}; } options.httpServer = server; - let asyngularServer = new module.exports.AGServer(options); - return asyngularServer; + let socketClusterServer = new module.exports.AGServer(options); + return socketClusterServer; }; diff --git a/package.json b/package.json index a430efb..6151acd 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { - "name": "asyngular-server", - "version": "6.2.1", - "description": "Server module for Asyngular", + "name": "socketcluster-server", + "version": "15.0.0", + "description": "Server module for SocketCluster", "main": "index.js", "repository": { "type": "git", - "url": "git://github.com/SocketCluster/asyngular-server.git" + "url": "git://github.com/SocketCluster/socketcluster-server.git" }, "dependencies": { "ag-auth": "^1.0.1", @@ -21,7 +21,7 @@ "ws": "^7.1.0" }, "devDependencies": { - "asyngular-client": "^6.2.0", + "socketcluster-client": "^15.0.0", "localStorage": "^1.0.3", "mocha": "^5.2.0" }, @@ -31,8 +31,7 @@ "keywords": [ "websocket", "realtime", - "socketcluster", - "asyngular" + "socketcluster" ], "author": "Jonathan Gros-Dubois ", "license": "MIT" diff --git a/server.js b/server.js index 165d12e..ff141b2 100644 --- a/server.js +++ b/server.js @@ -32,7 +32,7 @@ function AGServer(options) { pingTimeoutDisabled: false, pingInterval: 8000, origins: '*:*', - path: '/asyngular/', + path: '/socketcluster/', protocolVersion: 2, authDefaultExpiry: 86400, batchOnHandshake: false, diff --git a/serversocket.js b/serversocket.js index 312e15a..a87199b 100644 --- a/serversocket.js +++ b/serversocket.js @@ -367,7 +367,7 @@ AGServerSocket.prototype._processHandshakeRequest = async function (request) { let action = new AGAction(); action.request = this.request; action.socket = this; - action.type = AGAction.HANDSHAKE_AG; + action.type = AGAction.HANDSHAKE_SC; try { await this.server._processMiddlewareAction(this.middlewareHandshakeStream, action); diff --git a/test/integration.js b/test/integration.js index c730176..81e704c 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1,7 +1,7 @@ const assert = require('assert'); -const asyngularServer = require('../'); +const socketClusterServer = require('../'); const AGAction = require('../action'); -const asyngularClient = require('asyngular-client'); +const socketClusterClient = require('socketcluster-client'); const localStorage = require('localStorage'); const AGSimpleBroker = require('ag-simple-broker'); @@ -173,12 +173,12 @@ describe('Integration tests', function () { server.httpServer.close(); await server.close(); } - global.localStorage.removeItem('asyngular.authToken'); + global.localStorage.removeItem('socketcluster.authToken'); }); describe('Client authentication', function () { beforeEach('Run the server before start', async function () { - server = asyngularServer.listen(PORT_NUMBER, serverOptions); + server = socketClusterServer.listen(PORT_NUMBER, serverOptions); bindFailureHandlers(server); server.setMiddleware(server.MIDDLEWARE_INBOUND, async (middlewareStream) => { @@ -206,13 +206,13 @@ describe('Integration tests', function () { }); it('Should not send back error if JWT is not provided in handshake', async function () { - client = asyngularClient.create(clientOptions); + client = socketClusterClient.create(clientOptions); let event = await client.listener('connect').once(); assert.equal(event.authError === undefined, true); }); it('Should be authenticated on connect if previous JWT token is present', async function () { - client = asyngularClient.create(clientOptions); + client = socketClusterClient.create(clientOptions); await client.listener('connect').once(); client.invoke('login', {username: 'bob'}); await client.listener('authenticate').once(); @@ -225,9 +225,9 @@ describe('Integration tests', function () { }); it('Should send back error if JWT is invalid during handshake', async function () { - global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); + global.localStorage.setItem('socketcluster.authToken', validSignedAuthTokenBob); - client = asyngularClient.create(clientOptions); + client = socketClusterClient.create(clientOptions); await client.listener('connect').once(); // Change the setAuthKey to invalidate the current token. @@ -241,7 +241,7 @@ describe('Integration tests', function () { }); it('Should allow switching between users', async function () { - global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); + global.localStorage.setItem('socketcluster.authToken', validSignedAuthTokenBob); let authenticateEvents = []; let deauthenticateEvents = []; @@ -275,7 +275,7 @@ describe('Integration tests', function () { })(); let clientSocketId; - client = asyngularClient.create(clientOptions); + client = socketClusterClient.create(clientOptions); await client.listener('connect').once(); clientSocketId = client.id; client.invoke('login', {username: 'alice'}); @@ -303,7 +303,7 @@ describe('Integration tests', function () { }); it('Should emit correct events/data when socket is deauthenticated', async function () { - global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); + global.localStorage.setItem('socketcluster.authToken', validSignedAuthTokenBob); let authenticationStateChangeEvents = []; let authStateChangeEvents = []; @@ -314,7 +314,7 @@ describe('Integration tests', function () { } })(); - client = asyngularClient.create(clientOptions); + client = socketClusterClient.create(clientOptions); (async () => { for await (let event of client.listener('connect')) { @@ -356,9 +356,9 @@ describe('Integration tests', function () { }); it('Should throw error if server socket deauthenticate is called after client disconnected and rejectOnFailedDelivery is true', async function () { - global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); + global.localStorage.setItem('socketcluster.authToken', validSignedAuthTokenBob); - client = asyngularClient.create(clientOptions); + client = socketClusterClient.create(clientOptions); let {socket} = await server.listener('connection').once(); @@ -374,9 +374,9 @@ describe('Integration tests', function () { }); it('Should not throw error if server socket deauthenticate is called after client disconnected and rejectOnFailedDelivery is not true', async function () { - global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); + global.localStorage.setItem('socketcluster.authToken', validSignedAuthTokenBob); - client = asyngularClient.create(clientOptions); + client = socketClusterClient.create(clientOptions); let {socket} = await server.listener('connection').once(); @@ -385,9 +385,9 @@ describe('Integration tests', function () { }); it('Should not authenticate the client if MIDDLEWARE_INBOUND blocks the authentication', async function () { - global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenAlice); + global.localStorage.setItem('socketcluster.authToken', validSignedAuthTokenAlice); - client = asyngularClient.create(clientOptions); + client = socketClusterClient.create(clientOptions); // The previous test authenticated us as 'alice', so that token will be passed to the server as // part of the handshake. let event = await client.listener('connect').once(); @@ -401,7 +401,7 @@ describe('Integration tests', function () { describe('Server authentication', function () { it('Token should be available after the authenticate listener resolves', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -415,7 +415,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -431,7 +431,7 @@ describe('Integration tests', function () { }); it('Authentication can be captured using the authenticate listener', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -445,7 +445,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -461,7 +461,7 @@ describe('Integration tests', function () { }); it('Previously authenticated client should still be authenticated after reconnecting', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -475,7 +475,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -497,7 +497,7 @@ describe('Integration tests', function () { }); it('Should set the correct expiry when using expiresIn option when creating a JWT with socket.setAuthToken', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -511,7 +511,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -529,7 +529,7 @@ describe('Integration tests', function () { }); it('Should set the correct expiry when adding exp claim when creating a JWT with socket.setAuthToken', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -543,7 +543,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -561,7 +561,7 @@ describe('Integration tests', function () { }); it('The exp claim should have priority over expiresIn option when using socket.setAuthToken', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -575,7 +575,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -593,7 +593,7 @@ describe('Integration tests', function () { }); it('Should send back error if socket.setAuthToken tries to set both iss claim and issuer option', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -609,7 +609,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -661,7 +661,7 @@ describe('Integration tests', function () { }); it('Should trigger an authTokenSigned event and socket.signedAuthToken should be set after calling the socket.setAuthToken method', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -696,7 +696,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -712,7 +712,7 @@ describe('Integration tests', function () { }); it('The socket.setAuthToken call should reject if token delivery fails and rejectOnFailedDelivery option is true', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, ackTimeout: 1000 @@ -723,7 +723,7 @@ describe('Integration tests', function () { (async () => { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -764,7 +764,7 @@ describe('Integration tests', function () { }); it('The socket.setAuthToken call should not reject if token delivery fails and rejectOnFailedDelivery option is not true', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, ackTimeout: 1000 @@ -775,7 +775,7 @@ describe('Integration tests', function () { (async () => { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -813,9 +813,9 @@ describe('Integration tests', function () { }); it('The verifyToken method of the authEngine receives correct params', async function () { - global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); + global.localStorage.setItem('socketcluster.authToken', validSignedAuthTokenBob); - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -829,7 +829,7 @@ describe('Integration tests', function () { (async () => { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -851,7 +851,7 @@ describe('Integration tests', function () { }); it('Should remove client data from the server when client disconnects before authentication process finished', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -870,7 +870,7 @@ describe('Integration tests', function () { })(); await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -899,7 +899,7 @@ describe('Integration tests', function () { describe('Socket handshake', function () { it('Exchange is attached to socket before the handshake event is triggered', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -913,7 +913,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -923,7 +923,7 @@ describe('Integration tests', function () { }); it('Should close the connection if the client tries to send a message before the handshake', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -936,7 +936,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -956,7 +956,7 @@ describe('Integration tests', function () { }); it('Should close the connection if the client tries to send a ping before the handshake', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -969,7 +969,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -984,7 +984,7 @@ describe('Integration tests', function () { }); it('Should not close the connection if the client tries to send a message before the handshake and strictHandshake is false', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, strictHandshake: false @@ -998,7 +998,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -1019,7 +1019,7 @@ describe('Integration tests', function () { describe('Socket connection', function () { it('Server-side socket connect event and server connection event should trigger', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1038,7 +1038,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -1104,7 +1104,7 @@ describe('Integration tests', function () { describe('Socket disconnection', function () { it('Server-side socket disconnect event should not trigger if the socket did not complete the handshake; instead, it should trigger connectAbort', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1127,7 +1127,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -1182,7 +1182,7 @@ describe('Integration tests', function () { }); it('Server-side socket disconnect event should trigger if the socket completed the handshake (not connectAbort)', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1205,7 +1205,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -1261,7 +1261,7 @@ describe('Integration tests', function () { }); it('The close event should trigger when the socket loses the connection before the handshake', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1282,7 +1282,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -1323,7 +1323,7 @@ describe('Integration tests', function () { }); it('The close event should trigger when the socket loses the connection after the handshake', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1344,7 +1344,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -1385,7 +1385,7 @@ describe('Integration tests', function () { }); it('Disconnection should support socket message backpressure', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1400,7 +1400,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -1481,7 +1481,7 @@ describe('Integration tests', function () { }); it('Socket streams should be killed immediately if socket disconnects (default/kill mode)', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1504,7 +1504,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -1525,7 +1525,7 @@ describe('Integration tests', function () { }); it('Socket streams should be closed eventually if socket disconnects (close mode)', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, socketStreamCleanupMode: 'close' @@ -1549,7 +1549,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -1570,7 +1570,7 @@ describe('Integration tests', function () { }); it('Socket streams should be closed eventually if socket disconnects (none mode)', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, socketStreamCleanupMode: 'none' @@ -1594,7 +1594,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -1617,7 +1617,7 @@ describe('Integration tests', function () { describe('Socket RPC invoke', function () { it ('Should support invoking a remote procedure on the server', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1639,7 +1639,7 @@ describe('Integration tests', function () { } })(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -1660,7 +1660,7 @@ describe('Integration tests', function () { describe('Socket transmit', function () { it ('Should support receiving remote transmitted data on the server', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1669,7 +1669,7 @@ describe('Integration tests', function () { (async () => { await wait(10); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -1689,7 +1689,7 @@ describe('Integration tests', function () { describe('Socket backpressure', function () { it('Should be able to getInboundBackpressure() on a socket object', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1715,7 +1715,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -1737,7 +1737,7 @@ describe('Integration tests', function () { }); it('Should be able to getOutboundBackpressure() on a socket object', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1770,7 +1770,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -1788,7 +1788,7 @@ describe('Integration tests', function () { }); it('Should be able to getBackpressure() on a socket object and it should be the highest backpressure', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1814,7 +1814,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -1838,7 +1838,7 @@ describe('Integration tests', function () { describe('Socket pub/sub', function () { it('Should maintain order of publish and subscribe', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1852,7 +1852,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -1875,7 +1875,7 @@ describe('Integration tests', function () { }); it('Should maintain order of publish and subscribe when client starts out as disconnected', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1889,7 +1889,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER, autoConnect: false @@ -1913,7 +1913,7 @@ describe('Integration tests', function () { }); it('Client should not be able to subscribe to a channel before the handshake has completed', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1933,7 +1933,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -1968,7 +1968,7 @@ describe('Integration tests', function () { }); it('Server should be able to handle invalid #subscribe and #unsubscribe and #publish events without crashing', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -1982,7 +1982,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -2072,7 +2072,7 @@ describe('Integration tests', function () { }); it('When default AGSimpleBroker broker engine is used, disconnect event should trigger before unsubscribe event', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -2083,7 +2083,7 @@ describe('Integration tests', function () { (async () => { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -2118,7 +2118,7 @@ describe('Integration tests', function () { }); it('When default AGSimpleBroker broker engine is used, agServer.exchange should support consuming data from a channel', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -2126,7 +2126,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -2167,7 +2167,7 @@ describe('Integration tests', function () { }); it('When default AGSimpleBroker broker engine is used, agServer.exchange should support publishing data to a channel', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -2175,7 +2175,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -2218,7 +2218,7 @@ describe('Integration tests', function () { return resolveAfterTimeout(100, defaultUnsubscribeSocket.call(this, socket, channel)); }; - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, brokerEngine: customBrokerEngine @@ -2229,7 +2229,7 @@ describe('Integration tests', function () { (async () => { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -2268,7 +2268,7 @@ describe('Integration tests', function () { }); it('Socket should emit an error when trying to unsubscribe from a channel which it is not subscribed to', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -2288,7 +2288,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -2314,7 +2314,7 @@ describe('Integration tests', function () { return resolveAfterTimeout(300, defaultUnsubscribeSocket.call(this, socket, channel)); }; - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, brokerEngine: customBrokerEngine @@ -2335,7 +2335,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -2365,7 +2365,7 @@ describe('Integration tests', function () { }); it('Socket channelSubscriptions and channelSubscriptionsCount should update when socket.kickOut(channel) is called', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -2399,7 +2399,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -2416,7 +2416,7 @@ describe('Integration tests', function () { describe('Batching', function () { it('Should batch messages sent through sockets after the handshake when the batchOnHandshake option is true', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, batchOnHandshake: true, @@ -2464,7 +2464,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER, batchOnHandshake: true, @@ -2524,7 +2524,7 @@ describe('Integration tests', function () { }); it('The batchOnHandshake option should not break the order of subscribe and publish', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, batchOnHandshake: true, @@ -2541,7 +2541,7 @@ describe('Integration tests', function () { await server.listener('ready').once(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER, autoConnect: false, @@ -2567,7 +2567,7 @@ describe('Integration tests', function () { beforeEach('Launch server with ping options before start', async function () { // Intentionally make pingInterval higher than pingTimeout, that // way the client will never receive a ping or send back a pong. - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, pingInterval: 2000, @@ -2579,7 +2579,7 @@ describe('Integration tests', function () { }); it('Should disconnect socket if server does not receive a pong from client before timeout', async function () { - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -2627,7 +2627,7 @@ describe('Integration tests', function () { beforeEach('Launch server with ping options before start', async function () { // Intentionally make pingInterval higher than pingTimeout, that // way the client will never receive a ping or send back a pong. - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, pingInterval: 2000, @@ -2640,7 +2640,7 @@ describe('Integration tests', function () { }); it('Should not disconnect socket if server does not receive a pong from client before timeout', async function () { - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER, pingTimeoutDisabled: true @@ -2686,7 +2686,7 @@ describe('Integration tests', function () { describe('Middleware', function () { beforeEach('Launch server without middleware before start', async function () { - server = asyngularServer.listen(PORT_NUMBER, { + server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE }); @@ -2719,12 +2719,12 @@ describe('Integration tests', function () { }; server.setMiddleware(server.MIDDLEWARE_HANDSHAKE, middlewareFunction); - let clientA = asyngularClient.create({ + let clientA = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); - let clientB = asyngularClient.create({ + let clientB = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER, query: { @@ -2755,7 +2755,7 @@ describe('Integration tests', function () { }); }); - describe('HANDSHAKE_AG action', function () { + describe('HANDSHAKE_SC action', function () { it('Should trigger correct events if MIDDLEWARE_HANDSHAKE blocks with an error', async function () { let middlewareWasExecuted = false; let serverWarnings = []; @@ -2764,7 +2764,7 @@ describe('Integration tests', function () { let middlewareFunction = async function (middlewareStream) { for await (let {type, allow, block} of middlewareStream) { - if (type === AGAction.HANDSHAKE_AG) { + if (type === AGAction.HANDSHAKE_SC) { await wait(100); middlewareWasExecuted = true; let err = new Error('AG handshake failed because the server was too lazy'); @@ -2783,7 +2783,7 @@ describe('Integration tests', function () { } })(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -2817,7 +2817,7 @@ describe('Integration tests', function () { let middlewareFunction = async function (middlewareStream) { for await (let {type, allow, block} of middlewareStream) { - if (type === AGAction.HANDSHAKE_AG) { + if (type === AGAction.HANDSHAKE_SC) { await wait(100); middlewareWasExecuted = true; let err = new Error('AG handshake failed because the server was too lazy'); @@ -2830,7 +2830,7 @@ describe('Integration tests', function () { }; server.setMiddleware(server.MIDDLEWARE_HANDSHAKE, middlewareFunction); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -2854,7 +2854,7 @@ describe('Integration tests', function () { let middlewareFunction = async function (middlewareStream) { for await (let {type, allow, block} of middlewareStream) { - if (type === AGAction.HANDSHAKE_AG) { + if (type === AGAction.HANDSHAKE_SC) { await wait(100); middlewareWasExecuted = true; let err = new Error('AG handshake failed because of invalid query auth parameters'); @@ -2871,7 +2871,7 @@ describe('Integration tests', function () { }; server.setMiddleware(server.MIDDLEWARE_HANDSHAKE, middlewareFunction); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -2896,7 +2896,7 @@ describe('Integration tests', function () { let middlewareFunction = async function (middlewareStream) { for await (let {type, allow} of middlewareStream) { - if (type === AGAction.HANDSHAKE_AG) { + if (type === AGAction.HANDSHAKE_SC) { await wait(500); } allow(); @@ -2905,7 +2905,7 @@ describe('Integration tests', function () { server.setMiddleware(server.MIDDLEWARE_HANDSHAKE, middlewareFunction); createConnectionTime = Date.now(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -2927,7 +2927,7 @@ describe('Integration tests', function () { server.setMiddleware(server.MIDDLEWARE_HANDSHAKE, async function (middlewareStream) { for await (let {socket, type, allow, block} of middlewareStream) { - if (type === AGAction.HANDSHAKE_AG) { + if (type === AGAction.HANDSHAKE_SC) { try { await socket.setAuthToken({username: 'alice'}); } catch (error) { @@ -2943,7 +2943,7 @@ describe('Integration tests', function () { didAuthenticationEventTrigger = true; })(); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -2960,7 +2960,7 @@ describe('Integration tests', function () { it('Delaying handshake for one client should not affect other clients', async function () { let middlewareFunction = async function (middlewareStream) { for await (let action of middlewareStream) { - if (action.type === AGAction.HANDSHAKE_AG) { + if (action.type === AGAction.HANDSHAKE_SC) { if (action.socket.request.url.indexOf('?delayMe=true') !== -1) { // Long delay. await wait(5000); @@ -2973,12 +2973,12 @@ describe('Integration tests', function () { }; server.setMiddleware(server.MIDDLEWARE_HANDSHAKE, middlewareFunction); - let clientA = asyngularClient.create({ + let clientA = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); - let clientB = asyngularClient.create({ + let clientB = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER, query: { @@ -3027,7 +3027,7 @@ describe('Integration tests', function () { }; server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -3059,7 +3059,7 @@ describe('Integration tests', function () { }; server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -3091,7 +3091,7 @@ describe('Integration tests', function () { }; server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -3101,7 +3101,7 @@ describe('Integration tests', function () { }); it('Should run AUTHENTICATE action in middleware if JWT token exists', async function () { - global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); + global.localStorage.setItem('socketcluster.authToken', validSignedAuthTokenBob); let middlewareWasExecuted = false; let middlewareFunction = async function (middlewareStream) { @@ -3114,7 +3114,7 @@ describe('Integration tests', function () { }; server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -3146,7 +3146,7 @@ describe('Integration tests', function () { }; server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -3177,7 +3177,7 @@ describe('Integration tests', function () { }; server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -3222,12 +3222,12 @@ describe('Integration tests', function () { }; server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - let clientA = asyngularClient.create({ + let clientA = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); - let clientB = asyngularClient.create({ + let clientB = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER, query: { @@ -3235,7 +3235,7 @@ describe('Integration tests', function () { } }); - let clientC = asyngularClient.create({ + let clientC = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -3283,7 +3283,7 @@ describe('Integration tests', function () { }; server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -3311,7 +3311,7 @@ describe('Integration tests', function () { }; server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER, autoConnect: false @@ -3354,7 +3354,7 @@ describe('Integration tests', function () { }; server.setMiddleware(server.MIDDLEWARE_OUTBOUND, middlewareFunction); - client = asyngularClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); From b071684ec0108d5271023de1aa1cd08478e15385 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 20 Jan 2020 18:50:00 +0100 Subject: [PATCH 199/265] Change lodash.clonedeep to clone-deep --- package.json | 2 +- serversocket.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 6151acd..5d99112 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "ag-simple-broker": "^4.0.6", "async-stream-emitter": "^3.0.2", "base64id": "^1.0.0", - "lodash.clonedeep": "^4.5.0", + "clone-deep": "^4.0.1", "sc-errors": "^2.0.1", "sc-formatter": "^3.0.2", "stream-demux": "^7.0.1", diff --git a/serversocket.js b/serversocket.js index a87199b..52e495f 100644 --- a/serversocket.js +++ b/serversocket.js @@ -1,4 +1,4 @@ -const cloneDeep = require('lodash.clonedeep'); +const cloneDeep = require('clone-deep'); const WritableConsumableStream = require('writable-consumable-stream'); const StreamDemux = require('stream-demux'); const AsyncStreamEmitter = require('async-stream-emitter'); @@ -1289,7 +1289,7 @@ AGServerSocket.prototype.setAuthToken = async function (data, options) { this.emitError(err); throw err; } - + let authToken = cloneDeep(data); let oldAuthState = this.authState; this.authState = this.AUTHENTICATED; @@ -1297,7 +1297,7 @@ AGServerSocket.prototype.setAuthToken = async function (data, options) { if (options == null) { options = {}; } else { - options = cloneDeep(options); + options = {...options}; if (options.algorithm != null) { delete options.algorithm; let err = new InvalidArgumentsError( From 9a418c75bf8d4bb0f359d021e9d4292f44dfbfaa Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 20 Jan 2020 18:50:33 +0100 Subject: [PATCH 200/265] v15.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5d99112..70b0335 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "15.0.0", + "version": "15.0.1", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From d8a7a40e6d474905bd218cad9412c381b92642ee Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 7 Feb 2020 20:34:51 +0100 Subject: [PATCH 201/265] v16.0.0 --- package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 70b0335..14fd6df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "15.0.1", + "version": "16.0.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { @@ -10,18 +10,18 @@ "dependencies": { "ag-auth": "^1.0.1", "ag-request": "^1.0.0", - "ag-simple-broker": "^4.0.6", - "async-stream-emitter": "^3.0.2", + "ag-simple-broker": "^5.0.0", + "async-stream-emitter": "^4.0.0", "base64id": "^1.0.0", "clone-deep": "^4.0.1", "sc-errors": "^2.0.1", "sc-formatter": "^3.0.2", - "stream-demux": "^7.0.1", - "writable-consumable-stream": "^1.1.1", - "ws": "^7.1.0" + "stream-demux": "^8.0.0", + "writable-consumable-stream": "^2.0.0", + "ws": "^7.2.1" }, "devDependencies": { - "socketcluster-client": "^15.0.0", + "socketcluster-client": "^16.0.0", "localStorage": "^1.0.3", "mocha": "^5.2.0" }, From 80687ae37fb46d61c8acee84f50cf7bfaf271499 Mon Sep 17 00:00:00 2001 From: afdezayl Date: Sat, 8 Aug 2020 13:58:53 +0200 Subject: [PATCH 202/265] FIX: transmitPublish not allowing interception --- serversocket.js | 1 + test/integration.js | 84 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/serversocket.js b/serversocket.js index 52e495f..47a48b9 100644 --- a/serversocket.js +++ b/serversocket.js @@ -760,6 +760,7 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message } if (isPublish) { + packet.data.data = newData; await this._processInboundPublishPacket(packet); } diff --git a/test/integration.js b/test/integration.js index 81e704c..d7e071d 100644 --- a/test/integration.js +++ b/test/integration.js @@ -3265,6 +3265,90 @@ describe('Integration tests', function () { clientB.disconnect(); clientC.disconnect(); }); + it('Should allow to change message in middleware when client invokePublish', async function() { + const clientMessage = 'world'; + const middlewareMessage = 'intercepted'; + const middlewareFunction = async function (middlewareStream) { + for await (let action of middlewareStream) { + if (action.type === AGAction.PUBLISH_IN) { + action.allow({data: middlewareMessage}); + } else { + action.allow(); + } + } + }; + + server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); + + const client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + let helloChannel = client.subscribe('hello'); + await helloChannel.listener('subscribe').once(); + + let receivedMessages = []; + (async () => { + for await (let data of helloChannel) { + receivedMessages.push(data); + } + })(); + + let error; + try { + await client.invokePublish('hello', clientMessage); + } catch (err) { + error = err; + } + + await wait(100); + + assert.notEqual(clientMessage, middlewareMessage); + assert.equal(receivedMessages[0], middlewareMessage); + }); + it('Should allow to change message in middleware when client transmitPublish', async function() { + const clientMessage = 'world'; + const middlewareMessage = 'intercepted'; + const middlewareFunction = async function (middlewareStream) { + for await (let action of middlewareStream) { + if (action.type === AGAction.PUBLISH_IN) { + action.allow({data: middlewareMessage}); + } else { + action.allow(); + } + } + }; + + server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); + + const client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + let helloChannel = client.subscribe('hello'); + await helloChannel.listener('subscribe').once(); + + let receivedMessages = []; + (async () => { + for await (let data of helloChannel) { + receivedMessages.push(data); + } + })(); + + let error; + try { + await client.transmitPublish('hello', clientMessage); + } catch (err) { + error = err; + } + + await wait(100); + + assert.notEqual(clientMessage, middlewareMessage); + assert.equal(receivedMessages[0], middlewareMessage); + }) }); describe('SUBSCRIBE action', function () { From 0e339304664090e14ff5fbbb7157dbb5b9d392f4 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 8 Aug 2020 23:08:42 +0200 Subject: [PATCH 203/265] Cleanup logic related to publish intercept --- serversocket.js | 3 +++ test/integration.js | 22 ++++++++++++---------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/serversocket.js b/serversocket.js index 47a48b9..7a32e33 100644 --- a/serversocket.js +++ b/serversocket.js @@ -760,6 +760,9 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message } if (isPublish) { + if (!packet.data) { + packet.data = {}; + } packet.data.data = newData; await this._processInboundPublishPacket(packet); } diff --git a/test/integration.js b/test/integration.js index d7e071d..389a293 100644 --- a/test/integration.js +++ b/test/integration.js @@ -3265,10 +3265,11 @@ describe('Integration tests', function () { clientB.disconnect(); clientC.disconnect(); }); + it('Should allow to change message in middleware when client invokePublish', async function() { - const clientMessage = 'world'; - const middlewareMessage = 'intercepted'; - const middlewareFunction = async function (middlewareStream) { + let clientMessage = 'world'; + let middlewareMessage = 'intercepted'; + let middlewareFunction = async function (middlewareStream) { for await (let action of middlewareStream) { if (action.type === AGAction.PUBLISH_IN) { action.allow({data: middlewareMessage}); @@ -3280,7 +3281,7 @@ describe('Integration tests', function () { server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - const client = socketClusterClient.create({ + let client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -3303,14 +3304,15 @@ describe('Integration tests', function () { } await wait(100); - + assert.notEqual(clientMessage, middlewareMessage); assert.equal(receivedMessages[0], middlewareMessage); }); + it('Should allow to change message in middleware when client transmitPublish', async function() { - const clientMessage = 'world'; - const middlewareMessage = 'intercepted'; - const middlewareFunction = async function (middlewareStream) { + let clientMessage = 'world'; + let middlewareMessage = 'intercepted'; + let middlewareFunction = async function (middlewareStream) { for await (let action of middlewareStream) { if (action.type === AGAction.PUBLISH_IN) { action.allow({data: middlewareMessage}); @@ -3322,7 +3324,7 @@ describe('Integration tests', function () { server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - const client = socketClusterClient.create({ + let client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -3345,7 +3347,7 @@ describe('Integration tests', function () { } await wait(100); - + assert.notEqual(clientMessage, middlewareMessage); assert.equal(receivedMessages[0], middlewareMessage); }) From b5d9e7e9276ea0af7773637320822486d51ad494 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 8 Aug 2020 23:11:35 +0200 Subject: [PATCH 204/265] v16.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 14fd6df..bea47e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "16.0.0", + "version": "16.0.1", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 31ea69cab37482b17bc07b18d7457c693bab9582 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 8 Aug 2020 23:27:06 +0200 Subject: [PATCH 205/265] Fix test cases in node v12 --- test/integration.js | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/test/integration.js b/test/integration.js index 389a293..a39ac8b 100644 --- a/test/integration.js +++ b/test/integration.js @@ -2104,14 +2104,18 @@ describe('Integration tests', function () { } })(); - let disconnectPacket = await socket.listener('disconnect').once(); - eventList.push({ - type: 'disconnect', - code: disconnectPacket.code, - reason: disconnectPacket.data - }); + (async () => { + for await (let disconnectPacket of socket.listener('disconnect')) { + eventList.push({ + type: 'disconnect', + code: disconnectPacket.code, + reason: disconnectPacket.data + }); + } + })(); + + await wait(300); - await wait(0); assert.equal(eventList[0].type, 'disconnect'); assert.equal(eventList[1].type, 'unsubscribe'); assert.equal(eventList[1].channel, 'foo'); @@ -2253,15 +2257,17 @@ describe('Integration tests', function () { } })(); - let event = await socket.listener('disconnect').once(); - - eventList.push({ - type: 'disconnect', - code: event.code, - reason: event.reason - }); + (async () => { + for await (let event of socket.listener('disconnect')) { + eventList.push({ + type: 'disconnect', + code: event.code, + reason: event.reason + }); + } + })(); - await wait(0); + await wait(300); assert.equal(eventList[0].type, 'disconnect'); assert.equal(eventList[1].type, 'unsubscribe'); assert.equal(eventList[1].channel, 'foo'); From fafcc98dd02316fedb119f844a821d4775454009 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 8 Aug 2020 23:27:48 +0200 Subject: [PATCH 206/265] Remove space --- test/integration.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/integration.js b/test/integration.js index a39ac8b..ba35753 100644 --- a/test/integration.js +++ b/test/integration.js @@ -2115,7 +2115,6 @@ describe('Integration tests', function () { })(); await wait(300); - assert.equal(eventList[0].type, 'disconnect'); assert.equal(eventList[1].type, 'unsubscribe'); assert.equal(eventList[1].channel, 'foo'); From 746dfae9d08b08c59bca5f24fad6b6c776475c12 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 8 Aug 2020 23:50:42 +0200 Subject: [PATCH 207/265] Make sure that test process exits at the end --- test/integration.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/test/integration.js b/test/integration.js index ba35753..b6f3f64 100644 --- a/test/integration.js +++ b/test/integration.js @@ -2690,6 +2690,8 @@ describe('Integration tests', function () { }); describe('Middleware', function () { + let server; + beforeEach('Launch server without middleware before start', async function () { server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, @@ -2706,6 +2708,18 @@ describe('Integration tests', function () { await server.listener('ready').once(); }); + afterEach('Close server after each middleware test', async function () { + if (client) { + client.closeAllListeners(); + client.disconnect(); + } + if (server) { + server.closeAllListeners(); + server.httpServer.close(); + await server.close(); + } + }); + describe('MIDDLEWARE_HANDSHAKE', function () { describe('HANDSHAKE_WS action', function () { it('Delaying handshake for one client should not affect other clients', async function () { @@ -3286,7 +3300,7 @@ describe('Integration tests', function () { server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - let client = socketClusterClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); @@ -3329,7 +3343,7 @@ describe('Integration tests', function () { server.setMiddleware(server.MIDDLEWARE_INBOUND, middlewareFunction); - let client = socketClusterClient.create({ + client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER }); From 7cc8f3d824a86e91e4b072f263c66788220b280c Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 8 Aug 2020 23:52:29 +0200 Subject: [PATCH 208/265] Better client scope in test --- test/integration.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration.js b/test/integration.js index b6f3f64..a7a6187 100644 --- a/test/integration.js +++ b/test/integration.js @@ -2691,6 +2691,7 @@ describe('Integration tests', function () { describe('Middleware', function () { let server; + let client; beforeEach('Launch server without middleware before start', async function () { server = socketClusterServer.listen(PORT_NUMBER, { From 5fd1b0901718884849272637c0c3c9657de38133 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 16 Jun 2021 13:05:48 +0200 Subject: [PATCH 209/265] Bump dependencies --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index bea47e0..c89060a 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,12 @@ "sc-formatter": "^3.0.2", "stream-demux": "^8.0.0", "writable-consumable-stream": "^2.0.0", - "ws": "^7.2.1" + "ws": "^7.4.6" }, "devDependencies": { - "socketcluster-client": "^16.0.0", "localStorage": "^1.0.3", - "mocha": "^5.2.0" + "mocha": "^9.0.0", + "socketcluster-client": "^16.0.0" }, "scripts": { "test": "mocha --reporter spec --timeout 3000 --slow 3000" From a5a37d5d673e2b8226e12b92e26580e9a6f22920 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 16 Jun 2021 13:06:07 +0200 Subject: [PATCH 210/265] v16.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c89060a..35de84d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "16.0.1", + "version": "16.0.2", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From b3e8dd9573afc7fb434aa56088aa2f8599b797e5 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 29 Jul 2021 15:31:56 +0200 Subject: [PATCH 211/265] v16.0.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 35de84d..e678dc0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "16.0.2", + "version": "16.0.3", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 221051cb25fc6eaa693a3569b99b0380b9779109 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 29 Jul 2021 15:38:50 +0200 Subject: [PATCH 212/265] v16.0.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e678dc0..1a1c388 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "16.0.3", + "version": "16.0.4", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From ea2175a4d8548ca14bd9b5e7ad2cd3e94d70bc40 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 29 Jul 2021 15:50:25 +0200 Subject: [PATCH 213/265] v16.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1a1c388..c65a019 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "16.0.4", + "version": "16.1.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 8ad0e7cd121a65af34f36b759fd5e054e7399592 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 3 Jun 2022 00:13:07 +0200 Subject: [PATCH 214/265] Bump dependencies --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c65a019..c8c1ec6 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "base64id": "^1.0.0", "clone-deep": "^4.0.1", "sc-errors": "^2.0.1", - "sc-formatter": "^3.0.2", + "sc-formatter": "^3.0.3", "stream-demux": "^8.0.0", "writable-consumable-stream": "^2.0.0", "ws": "^7.4.6" @@ -23,7 +23,7 @@ "devDependencies": { "localStorage": "^1.0.3", "mocha": "^9.0.0", - "socketcluster-client": "^16.0.0" + "socketcluster-client": "^16.1.0" }, "scripts": { "test": "mocha --reporter spec --timeout 3000 --slow 3000" From 147aa7b1959bd5320134940c0a5359fbfdfd7dd1 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 3 Jun 2022 00:13:25 +0200 Subject: [PATCH 215/265] v16.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c8c1ec6..26e8d77 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "16.1.0", + "version": "16.2.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 633a3832dd302ed7a26f45fd1ba29c6dce608d82 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 3 Jun 2022 01:14:38 +0200 Subject: [PATCH 216/265] v16.12.1 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 26e8d77..2323484 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "16.2.0", + "version": "16.2.1", "description": "Server module for SocketCluster", "main": "index.js", "repository": { @@ -23,7 +23,7 @@ "devDependencies": { "localStorage": "^1.0.3", "mocha": "^9.0.0", - "socketcluster-client": "^16.1.0" + "socketcluster-client": "^16.1.1" }, "scripts": { "test": "mocha --reporter spec --timeout 3000 --slow 3000" From d629a9c1b4ed1bf677394864a80e3c5980c59620 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 23 Aug 2022 21:41:26 +0200 Subject: [PATCH 217/265] Add data property to HANDSHAKE_SC --- serversocket.js | 1 + 1 file changed, 1 insertion(+) diff --git a/serversocket.js b/serversocket.js index 7a32e33..14e19cd 100644 --- a/serversocket.js +++ b/serversocket.js @@ -368,6 +368,7 @@ AGServerSocket.prototype._processHandshakeRequest = async function (request) { action.request = this.request; action.socket = this; action.type = AGAction.HANDSHAKE_SC; + action.data = data; try { await this.server._processMiddlewareAction(this.middlewareHandshakeStream, action); From 08d8d7244cf472fd9cbe72248903b63084c25173 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 23 Aug 2022 21:41:58 +0200 Subject: [PATCH 218/265] v17.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2323484..7530ccb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "16.2.1", + "version": "17.0.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From a9549a0fe453a0919e4ede7db1783c3c8f20f487 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 23 Aug 2022 22:06:18 +0200 Subject: [PATCH 219/265] Bump socketcluster-client dev dependency --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7530ccb..0e849d5 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "devDependencies": { "localStorage": "^1.0.3", "mocha": "^9.0.0", - "socketcluster-client": "^16.1.1" + "socketcluster-client": "^17.0.0" }, "scripts": { "test": "mocha --reporter spec --timeout 3000 --slow 3000" From 9490c28cc3d784a2402384b83e06e344f2dc65e1 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 23 Aug 2022 22:06:51 +0200 Subject: [PATCH 220/265] v17.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0e849d5..14ef7eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "17.0.0", + "version": "17.0.1", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From ad0a41ea4aac624db202da9e982c4e453287234c Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 24 Aug 2022 22:17:10 +0200 Subject: [PATCH 221/265] Allow unauthenticated connections to be blocked earlier insude HANDSHAKE_SC middleware --- serversocket.js | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/serversocket.js b/serversocket.js index 14e19cd..ee2f47f 100644 --- a/serversocket.js +++ b/serversocket.js @@ -364,11 +364,13 @@ AGServerSocket.prototype._processHandshakeRequest = async function (request) { let signedAuthToken = data.authToken || null; clearTimeout(this._handshakeTimeoutRef); + let authInfo = await this._validateAuthToken(signedAuthToken); + let action = new AGAction(); action.request = this.request; action.socket = this; action.type = AGAction.HANDSHAKE_SC; - action.data = data; + action.data = authInfo; try { await this.server._processMiddlewareAction(this.middlewareHandshakeStream, action); @@ -392,7 +394,7 @@ AGServerSocket.prototype._processHandshakeRequest = async function (request) { let oldAuthState = this.authState; try { - await this._processAuthToken(signedAuthToken); + await this._processAuthentication(authInfo); if (this.state === this.CLOSED) { return; } @@ -445,8 +447,9 @@ AGServerSocket.prototype._processHandshakeRequest = async function (request) { AGServerSocket.prototype._processAuthenticateRequest = async function (request) { let signedAuthToken = request.data; let oldAuthState = this.authState; + let authInfo = await this._validateAuthToken(signedAuthToken); try { - await this._processAuthToken(signedAuthToken); + await this._processAuthentication(authInfo); } catch (error) { if (error.isBadToken) { this.deauthenticate(); @@ -1495,24 +1498,41 @@ AGServerSocket.prototype._emitBadAuthTokenError = function (error, signedAuthTok }); }; -AGServerSocket.prototype._processAuthToken = async function (signedAuthToken) { +AGServerSocket.prototype._validateAuthToken = async function (signedAuthToken) { let verificationOptions = Object.assign({}, this.server.defaultVerificationOptions, { socket: this }); - let authToken; + let authToken; try { authToken = await this.server.auth.verifyToken(signedAuthToken, this.server.verificationKey, verificationOptions); } catch (error) { + let authTokenError = this._processTokenError(error); + return { + signedAuthToken, + authTokenError, + authToken: null, + authState: this.UNAUTHENTICATED + }; + } + + return { + signedAuthToken, + authTokenError: null, + authToken, + authState: this.AUTHENTICATED + }; +}; + +AGServerSocket.prototype._processAuthentication = async function ({signedAuthToken, authTokenError, authToken, authState}) { + if (authTokenError) { this.signedAuthToken = null; this.authToken = null; this.authState = this.UNAUTHENTICATED; - let authTokenError = this._processTokenError(error); - // If the error is related to the JWT being badly formatted, then we will // treat the error as a socket error. - if (error && signedAuthToken != null) { + if (signedAuthToken != null) { this.emitError(authTokenError); if (authTokenError.isBadToken) { this._emitBadAuthTokenError(authTokenError, signedAuthToken); From c48d21e72f4b7be947c0d24136c35d59c67e0a98 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 24 Aug 2022 22:17:30 +0200 Subject: [PATCH 222/265] v17.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 14ef7eb..eb45d74 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "17.0.1", + "version": "17.1.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 560ebd86505432bbe551606adb93c71cce5bde73 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 27 Sep 2022 16:54:04 +1000 Subject: [PATCH 223/265] 17.2.0 - Bump ws dependency --- package.json | 4 +-- server.js | 7 ++++- serversocket.js | 6 ++-- test/integration.js | 77 +++++++++++++++++++++++++++++++++++---------- 4 files changed, 72 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index eb45d74..1ed65e9 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,12 @@ "sc-formatter": "^3.0.3", "stream-demux": "^8.0.0", "writable-consumable-stream": "^2.0.0", - "ws": "^7.4.6" + "ws": "^8.9.0" }, "devDependencies": { "localStorage": "^1.0.3", "mocha": "^9.0.0", - "socketcluster-client": "^17.0.0" + "socketcluster-client": "^17.1.0" }, "scripts": { "test": "mocha --reporter spec --timeout 3000 --slow 3000" diff --git a/server.js b/server.js index ff141b2..fd1e1db 100644 --- a/server.js +++ b/server.js @@ -239,7 +239,7 @@ AGServer.prototype._handleSocketConnection = function (wsSocket, upgradeReq) { this.emit('handshake', {socket: agSocket}); }; -AGServer.prototype.close = function () { +AGServer.prototype.close = function (keepSocketsOpen) { this.isReady = false; return new Promise((resolve, reject) => { this.wsServer.close((err) => { @@ -249,6 +249,11 @@ AGServer.prototype.close = function () { } resolve(); }); + if (!keepSocketsOpen) { + for (let socket of Object.values(this.clients)) { + socket.terminate(); + } + } }); }; diff --git a/serversocket.js b/serversocket.js index ee2f47f..5ddd2c8 100644 --- a/serversocket.js +++ b/serversocket.js @@ -88,7 +88,8 @@ function AGServerSocket(id, server, socket, protocolVersion) { this.emitError(err); }); - this.socket.on('close', (code, reason) => { + this.socket.on('close', (code, reasonBuffer) => { + let reason = reasonBuffer.toString(); this._destroy(code, reason); }); @@ -127,7 +128,8 @@ function AGServerSocket(id, server, socket, protocolVersion) { this._handleOutboundPacketStream(); // Receive incoming raw messages - this.socket.on('message', async (message, flags) => { + this.socket.on('message', async (messageBuffer, isBinary) => { + let message = isBinary ? messageBuffer : messageBuffer.toString(); this.inboundReceivedMessageCount++; let isPong = message === pongMessage; diff --git a/test/integration.js b/test/integration.js index a7a6187..8e79a77 100644 --- a/test/integration.js +++ b/test/integration.js @@ -2568,7 +2568,7 @@ describe('Integration tests', function () { }); describe('Socket Ping/pong', function () { - describe('When when pingTimeoutDisabled is not set', function () { + describe('When pingTimeoutDisabled is not set', function () { beforeEach('Launch server with ping options before start', async function () { // Intentionally make pingInterval higher than pingTimeout, that // way the client will never receive a ping or send back a pong. @@ -2628,7 +2628,7 @@ describe('Integration tests', function () { }); }); - describe('When when pingTimeoutDisabled is true', function () { + describe('When pingTimeoutDisabled is true', function () { beforeEach('Launch server with ping options before start', async function () { // Intentionally make pingInterval higher than pingTimeout, that // way the client will never receive a ping or send back a pong. @@ -2687,12 +2687,67 @@ describe('Integration tests', function () { assert.equal(serverDisconnectionCode, null); }); }); + + describe('When pingTimeout is greater than pingInterval', function () { + beforeEach('Launch server with ping options before start', async function () { + // Intentionally make pingInterval higher than pingTimeout, that + // way the client will never receive a ping or send back a pong. + server = socketClusterServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE, + pingInterval: 400, + pingTimeout: 1000 + }); + bindFailureHandlers(server); + + await server.listener('ready').once(); + }); + + it('Should not disconnect socket if server receives a pong from client before timeout', async function () { + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + let serverWarning = null; + (async () => { + for await (let {warning} of server.listener('warning')) { + serverWarning = warning; + } + })(); + + let serverDisconnectionCode = null; + (async () => { + for await (let event of server.listener('disconnection')) { + serverDisconnectionCode = event.code; + } + })(); + + let clientError = null; + (async () => { + for await (let {error} of client.listener('error')) { + clientError = error; + } + })(); + + let clientDisconnectCode = null; + (async () => { + for await (let event of client.listener('disconnect')) { + clientDisconnectCode = event.code; + } + })(); + + await wait(2000); + assert.equal(clientError, null); + assert.equal(clientDisconnectCode, null); + + assert.equal(serverWarning, null); + assert.equal(serverDisconnectionCode, null); + }); + }); }); describe('Middleware', function () { - let server; - let client; - beforeEach('Launch server without middleware before start', async function () { server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, @@ -2709,18 +2764,6 @@ describe('Integration tests', function () { await server.listener('ready').once(); }); - afterEach('Close server after each middleware test', async function () { - if (client) { - client.closeAllListeners(); - client.disconnect(); - } - if (server) { - server.closeAllListeners(); - server.httpServer.close(); - await server.close(); - } - }); - describe('MIDDLEWARE_HANDSHAKE', function () { describe('HANDSHAKE_WS action', function () { it('Delaying handshake for one client should not affect other clients', async function () { From 36a5e0cb3903bd1ce1cc52ace38b0ed669172d0c Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 21 Jan 2023 21:10:52 +1100 Subject: [PATCH 224/265] Bump ag-auth --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1ed65e9..cfa4cff 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "url": "git://github.com/SocketCluster/socketcluster-server.git" }, "dependencies": { - "ag-auth": "^1.0.1", + "ag-auth": "^2.0.0", "ag-request": "^1.0.0", "ag-simple-broker": "^5.0.0", "async-stream-emitter": "^4.0.0", From a56ee7b4b01a339d24b9a154f6773835cb0e3d13 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 21 Jan 2023 21:12:10 +1100 Subject: [PATCH 225/265] v17.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cfa4cff..d64dba3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "17.1.0", + "version": "17.3.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 557925c98cdf926ba4bdc5ab03707103b64b73af Mon Sep 17 00:00:00 2001 From: Mega Date: Sat, 21 Jan 2023 18:49:56 +0500 Subject: [PATCH 226/265] Bump base64id --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d64dba3..3ab351a 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "ag-request": "^1.0.0", "ag-simple-broker": "^5.0.0", "async-stream-emitter": "^4.0.0", - "base64id": "^1.0.0", + "base64id": "^2.0.0", "clone-deep": "^4.0.1", "sc-errors": "^2.0.1", "sc-formatter": "^3.0.3", From 49d290af78c2dcee8b422675bd509f41c0efe06e Mon Sep 17 00:00:00 2001 From: Mega Date: Sat, 21 Jan 2023 18:51:14 +0500 Subject: [PATCH 227/265] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4103e1f..d75d075 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ let agServer = socketClusterServer.attach(httpServer, { (The MIT License) -Copyright (c) 2013-2019 SocketCluster.io +Copyright (c) 2013-2023 SocketCluster.io Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: From e3e8ab0ef372703309d5813d5266add13372ec34 Mon Sep 17 00:00:00 2001 From: Mega Date: Tue, 24 Jan 2023 01:21:57 +0500 Subject: [PATCH 228/265] update LICENSE --- LICENSE | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index a5848a5..ca250f2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ (The MIT License) -Copyright (c) 2013-2018 SocketCluster.io +Copyright (c) 2013-2023 SocketCluster.io Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index 4103e1f..d75d075 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ let agServer = socketClusterServer.attach(httpServer, { (The MIT License) -Copyright (c) 2013-2019 SocketCluster.io +Copyright (c) 2013-2023 SocketCluster.io Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: From d5056fdaec859f6c933cceb3bbc6d17c6d8f20e5 Mon Sep 17 00:00:00 2001 From: Mega Date: Mon, 30 Jan 2023 08:08:30 +0500 Subject: [PATCH 229/265] fix socket.kickOut() --- serversocket.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/serversocket.js b/serversocket.js index 5ddd2c8..96498b0 100644 --- a/serversocket.js +++ b/serversocket.js @@ -1427,10 +1427,19 @@ AGServerSocket.prototype.deauthenticate = async function (options) { }; AGServerSocket.prototype.kickOut = function (channel, message) { - delete this.channelSubscriptions[channel]; - this.channelSubscriptionsCount--; - this.transmit('#kickOut', {channel, message}); - return this.server.brokerEngine.unsubscribeSocket(this, channel); + let channels = channel; + if (!channels) { + channels = Object.keys(this.channelSubscriptions); + } + if (!Array.isArray(channels)) { + channels = [channel]; + } + for (const channelName of channels) { + delete this.channelSubscriptions[channelName]; + this.channelSubscriptionsCount--; + this.transmit('#kickOut', {channel: channelName, message}); + this.server.brokerEngine.unsubscribeSocket(this, channelName); + } }; AGServerSocket.prototype.subscriptions = function () { From 6119d81eff3d371e6c68a9b15b592490a12729be Mon Sep 17 00:00:00 2001 From: Mega Date: Thu, 2 Feb 2023 03:26:58 +0500 Subject: [PATCH 230/265] account for broker errors when unsubscribing a socket --- serversocket.js | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/serversocket.js b/serversocket.js index 96498b0..e255369 100644 --- a/serversocket.js +++ b/serversocket.js @@ -543,12 +543,11 @@ AGServerSocket.prototype._processSubscribeRequest = async function (request) { }; AGServerSocket.prototype._unsubscribeFromAllChannels = function () { - Object.keys(this.channelSubscriptions).forEach((channelName) => { - this._unsubscribe(channelName); - }); + const channels = Object.keys(this.channelSubscriptions); + return Promise.all(channels.map((channel) => this._unsubscribe(channel))); }; -AGServerSocket.prototype._unsubscribe = function (channel) { +AGServerSocket.prototype._unsubscribe = async function (channel) { if (typeof channel !== 'string') { throw new InvalidActionError( `Socket ${this.id} tried to unsubscribe from an invalid channel name` @@ -560,21 +559,26 @@ AGServerSocket.prototype._unsubscribe = function (channel) { ); } - delete this.channelSubscriptions[channel]; - if (this.channelSubscriptionsCount != null) { - this.channelSubscriptionsCount--; + try { + await this.server.brokerEngine.unsubscribeSocket(this, channel); + delete this.channelSubscriptions[channel]; + if (this.channelSubscriptionsCount != null) { + this.channelSubscriptionsCount--; + } + this.emit('unsubscribe', {channel}); + this.server.emit('unsubscription', {socket: this, channel}); + } catch (err) { + const error = new BrokerError( + `Failed to unsubscribe socket from the ${channel} channel - ${err}` + ); + this.emitError(error); } - - this.server.brokerEngine.unsubscribeSocket(this, channel); - - this.emit('unsubscribe', {channel}); - this.server.emit('unsubscription', {socket: this, channel}); }; AGServerSocket.prototype._processUnsubscribePacket = async function (packet) { let channel = packet.data; try { - this._unsubscribe(channel); + await this._unsubscribe(channel); } catch (err) { let error = new BrokerError( `Failed to unsubscribe socket from the ${channel} channel - ${err}` @@ -586,7 +590,7 @@ AGServerSocket.prototype._processUnsubscribePacket = async function (packet) { AGServerSocket.prototype._processUnsubscribeRequest = async function (request) { let channel = request.data; try { - this._unsubscribe(channel); + await this._unsubscribe(channel); } catch (err) { let error = new BrokerError( `Failed to unsubscribe socket from the ${channel} channel - ${err}` @@ -1434,12 +1438,10 @@ AGServerSocket.prototype.kickOut = function (channel, message) { if (!Array.isArray(channels)) { channels = [channel]; } - for (const channelName of channels) { - delete this.channelSubscriptions[channelName]; - this.channelSubscriptionsCount--; + return Promise.all(channels.map((channelName) => { this.transmit('#kickOut', {channel: channelName, message}); - this.server.brokerEngine.unsubscribeSocket(this, channelName); - } + return this._unsubscribe(channelName); + })); }; AGServerSocket.prototype.subscriptions = function () { From 352c723cf3a1ed55315fc5071593ee010c197fd9 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 6 Feb 2023 00:34:20 +1100 Subject: [PATCH 231/265] Ensure that all socket detroy events are emitted in the correct order --- serversocket.js | 35 +++++++++++++++++++---------------- test/integration.js | 19 +++++++++++++++---- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/serversocket.js b/serversocket.js index e255369..7d25ea0 100644 --- a/serversocket.js +++ b/serversocket.js @@ -908,7 +908,7 @@ AGServerSocket.prototype.killAllStreams = function () { this.killAllListeners(); }; -AGServerSocket.prototype._destroy = function (code, reason) { +AGServerSocket.prototype._destroy = async function (code, reason) { clearInterval(this._pingIntervalTicker); clearTimeout(this._pingTimeoutTicker); @@ -940,26 +940,12 @@ AGServerSocket.prototype._destroy = function (code, reason) { }); } - let cleanupMode = this.server.options.socketStreamCleanupMode; - if (cleanupMode === 'kill') { - (async () => { - await this.listener('close').once(); - this.killAllStreams(); - })(); - } else if (cleanupMode === 'close') { - (async () => { - await this.listener('close').once(); - this.closeAllStreams(); - })(); - } - this.emit('close', {code, reason}); this.server.emit('closure', { socket: this, code, reason }); - this._unsubscribeFromAllChannels(); clearTimeout(this._handshakeTimeoutRef); let isClientFullyConnected = !!this.server.clients[this.id]; @@ -995,10 +981,27 @@ AGServerSocket.prototype._destroy = function (code, reason) { let err = new SocketProtocolError(AGServerSocket.errorStatuses[code] || closeMessage, code); this.emitError(err); } + + await this._unsubscribeFromAllChannels(); + + let cleanupMode = this.server.options.socketStreamCleanupMode; + if (cleanupMode === 'kill') { + (async () => { + await this.listener('end').once(); + this.killAllStreams(); + })(); + } else if (cleanupMode === 'close') { + (async () => { + await this.listener('end').once(); + this.closeAllStreams(); + })(); + } + + this.emit('end'); } }; -AGServerSocket.prototype.disconnect = function (code, reason) { +AGServerSocket.prototype.disconnect = async function (code, reason) { code = code || 1000; if (typeof code !== 'number') { diff --git a/test/integration.js b/test/integration.js index 8e79a77..f589546 100644 --- a/test/integration.js +++ b/test/integration.js @@ -2214,7 +2214,7 @@ describe('Integration tests', function () { assert.equal(receivedChannelData[1], 'hello2'); }); - it('When disconnecting a socket, the unsubscribe event should trigger after the disconnect event', async function () { + it('When disconnecting a socket, the unsubscribe event should trigger after the disconnect and close events', async function () { let customBrokerEngine = new AGSimpleBroker(); let defaultUnsubscribeSocket = customBrokerEngine.unsubscribeSocket; customBrokerEngine.unsubscribeSocket = function (socket, channel) { @@ -2266,10 +2266,21 @@ describe('Integration tests', function () { } })(); - await wait(300); + (async () => { + for await (let event of socket.listener('close')) { + eventList.push({ + type: 'close', + code: event.code, + reason: event.reason + }); + } + })(); + + await wait(700); assert.equal(eventList[0].type, 'disconnect'); - assert.equal(eventList[1].type, 'unsubscribe'); - assert.equal(eventList[1].channel, 'foo'); + assert.equal(eventList[1].type, 'close'); + assert.equal(eventList[2].type, 'unsubscribe'); + assert.equal(eventList[2].channel, 'foo'); }); it('Socket should emit an error when trying to unsubscribe from a channel which it is not subscribed to', async function () { From d36b7dcdabc9d336a71a8ed44eff0d85e902ddb7 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 6 Feb 2023 00:41:16 +1100 Subject: [PATCH 232/265] v17.3.1 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3ab351a..2e4e9e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "17.3.0", + "version": "17.3.1", "description": "Server module for SocketCluster", "main": "index.js", "repository": { @@ -23,7 +23,7 @@ "devDependencies": { "localStorage": "^1.0.3", "mocha": "^9.0.0", - "socketcluster-client": "^17.1.0" + "socketcluster-client": "^17.1.1" }, "scripts": { "test": "mocha --reporter spec --timeout 3000 --slow 3000" From 70e6d892cd7934e1f15dddca3c67957617cde0e2 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 6 Jul 2023 23:17:00 +1000 Subject: [PATCH 233/265] v17.4.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2e4e9e7..2b07315 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "17.3.1", + "version": "17.4.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { @@ -23,7 +23,7 @@ "devDependencies": { "localStorage": "^1.0.3", "mocha": "^9.0.0", - "socketcluster-client": "^17.1.1" + "socketcluster-client": "^17.2.1" }, "scripts": { "test": "mocha --reporter spec --timeout 3000 --slow 3000" From 2e75fa0b95ad1564e68711f61f68205ccfb71bd0 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 6 Jul 2023 23:18:09 +1000 Subject: [PATCH 234/265] v17.4.1 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2b07315..edfcf16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "17.4.0", + "version": "17.4.1", "description": "Server module for SocketCluster", "main": "index.js", "repository": { @@ -15,7 +15,7 @@ "base64id": "^2.0.0", "clone-deep": "^4.0.1", "sc-errors": "^2.0.1", - "sc-formatter": "^3.0.3", + "sc-formatter": "^4.0.0", "stream-demux": "^8.0.0", "writable-consumable-stream": "^2.0.0", "ws": "^8.9.0" From b4de2e0cb4b4d69c53bcf177e2269ab610f266e6 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 8 Sep 2023 00:25:19 +1000 Subject: [PATCH 235/265] Bump dependencies to improve performance and fix issues with once(timeout) --- package.json | 10 +++++----- server.js | 2 +- serversocket.js | 2 +- test/integration.js | 6 ++++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index edfcf16..d8214fd 100644 --- a/package.json +++ b/package.json @@ -11,19 +11,19 @@ "ag-auth": "^2.0.0", "ag-request": "^1.0.0", "ag-simple-broker": "^5.0.0", - "async-stream-emitter": "^4.0.0", + "async-stream-emitter": "^6.0.1", "base64id": "^2.0.0", "clone-deep": "^4.0.1", "sc-errors": "^2.0.1", "sc-formatter": "^4.0.0", - "stream-demux": "^8.0.0", - "writable-consumable-stream": "^2.0.0", + "stream-demux": "^9.0.2", + "writable-consumable-stream": "^4.0.1", "ws": "^8.9.0" }, "devDependencies": { "localStorage": "^1.0.3", - "mocha": "^9.0.0", - "socketcluster-client": "^17.2.1" + "mocha": "^10.2.0", + "socketcluster-client": "^18.0.0" }, "scripts": { "test": "mocha --reporter spec --timeout 3000 --slow 3000" diff --git a/server.js b/server.js index fd1e1db..bf67d57 100644 --- a/server.js +++ b/server.js @@ -17,7 +17,7 @@ const InvalidActionError = scErrors.InvalidActionError; const ServerProtocolError = scErrors.ServerProtocolError; function AGServer(options) { - AsyncStreamEmitter.call(this); + AsyncStreamEmitter.call(this, { usabilityMode: options?.usabilityMode ?? false }); let opts = { brokerEngine: new AGSimpleBroker(), diff --git a/serversocket.js b/serversocket.js index 7d25ea0..f3c3b5e 100644 --- a/serversocket.js +++ b/serversocket.js @@ -21,7 +21,7 @@ const BrokerError = scErrors.BrokerError; const HANDSHAKE_REJECTION_STATUS_CODE = 4008; function AGServerSocket(id, server, socket, protocolVersion) { - AsyncStreamEmitter.call(this); + AsyncStreamEmitter.call(this, { usabilityMode: server.usabilityMode }); this.id = id; this.server = server; diff --git a/test/integration.js b/test/integration.js index f589546..8f2e564 100644 --- a/test/integration.js +++ b/test/integration.js @@ -2071,10 +2071,12 @@ describe('Integration tests', function () { assert.notEqual(nullPublishError, null); }); - it('When default AGSimpleBroker broker engine is used, disconnect event should trigger before unsubscribe event', async function () { + it.only('When default AGSimpleBroker broker engine is used, disconnect event should trigger before unsubscribe event', async function () { + // Only the case in usabilityMode as there is a performance trade-off. server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, - wsEngine: WS_ENGINE + wsEngine: WS_ENGINE, + usabilityMode: true }); bindFailureHandlers(server); From aa97f310795adb14d03f96cb1ec4f47d441f6b15 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 8 Sep 2023 00:25:42 +1000 Subject: [PATCH 236/265] v18.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d8214fd..6cf3105 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "17.4.1", + "version": "18.0.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From f400a79425b4df5588f6f98a3764b4d95cee3e0f Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 15 Sep 2023 01:05:23 +1000 Subject: [PATCH 237/265] Bug fix and a cleanup --- server.js | 4 +-- serversocket.js | 85 +++++++++++++++++++++++++++------------------ test/integration.js | 6 ++-- 3 files changed, 56 insertions(+), 39 deletions(-) diff --git a/server.js b/server.js index bf67d57..4801b36 100644 --- a/server.js +++ b/server.js @@ -273,11 +273,11 @@ AGServer.prototype.setMiddleware = function (type, middleware) { type !== this.MIDDLEWARE_OUTBOUND ) { throw new InvalidArgumentsError( - `Middleware type "${type}" is not supported` + `Middleware ${type} type is not supported` ); } if (this._middleware[type]) { - throw new InvalidActionError(`Middleware type "${type}" has already been set`); + throw new InvalidActionError(`Middleware ${type} type has already been set`); } this._middleware[type] = middleware; }; diff --git a/serversocket.js b/serversocket.js index f3c3b5e..56e25d5 100644 --- a/serversocket.js +++ b/serversocket.js @@ -456,7 +456,6 @@ AGServerSocket.prototype._processAuthenticateRequest = async function (request) if (error.isBadToken) { this.deauthenticate(); request.error(error); - return; } @@ -464,7 +463,6 @@ AGServerSocket.prototype._processAuthenticateRequest = async function (request) isAuthenticated: !!this.authToken, authError: signedAuthToken == null ? null : scErrors.dehydrateError(error) }); - return; } this.triggerAuthenticationEvents(oldAuthState); @@ -474,21 +472,22 @@ AGServerSocket.prototype._processAuthenticateRequest = async function (request) }); }; -AGServerSocket.prototype._subscribeSocket = async function (channelName, subscriptionOptions) { - if (channelName === undefined || !subscriptionOptions) { - throw new InvalidActionError(`Socket ${this.id} provided a malformated channel payload`); +AGServerSocket.prototype._validateSubscribePacket = function (request) { + if (request.data == null || typeof request.data !== 'object') { + throw new InvalidActionError(`Socket ${this.id} provided a malformatted channel payload`); } + if (typeof request.data.channel !== 'string') { + throw new InvalidActionError(`Socket ${this.id} provided an invalid channel name`); + } +} +AGServerSocket.prototype._subscribeSocket = async function (channelName, subscriptionOptions) { if (this.server.socketChannelLimit && this.channelSubscriptionsCount >= this.server.socketChannelLimit) { throw new InvalidActionError( `Socket ${this.id} tried to exceed the channel subscription limit of ${this.server.socketChannelLimit}` ); } - if (typeof channelName !== 'string') { - throw new InvalidActionError(`Socket ${this.id} provided an invalid channel name`); - } - if (this.channelSubscriptionsCount == null) { this.channelSubscriptionsCount = 0; } @@ -516,23 +515,30 @@ AGServerSocket.prototype._subscribeSocket = async function (channelName, subscri }; AGServerSocket.prototype._processSubscribeRequest = async function (request) { - let subscriptionOptions = Object.assign({}, request.data); - let channelName = subscriptionOptions.channel; - delete subscriptionOptions.channel; - if (this.state === this.OPEN) { + try { + this._validateSubscribePacket(request); + } catch (err) { + let error = new BrokerError(`Failed to subscribe socket - ${err}`); + this.emitError(error); + request.error(error); + return; + } + + let subscriptionOptions = Object.assign({}, request.data); + let channelName = subscriptionOptions.channel; + delete subscriptionOptions.channel; + try { await this._subscribeSocket(channelName, subscriptionOptions); } catch (err) { let error = new BrokerError(`Failed to subscribe socket to the ${channelName} channel - ${err}`); this.emitError(error); request.error(error); - return; } request.end(); - return; } // This is an invalid state; it means the client tried to subscribe before @@ -547,18 +553,20 @@ AGServerSocket.prototype._unsubscribeFromAllChannels = function () { return Promise.all(channels.map((channel) => this._unsubscribe(channel))); }; -AGServerSocket.prototype._unsubscribe = async function (channel) { - if (typeof channel !== 'string') { +AGServerSocket.prototype._validateUnsubscribePacket = function (packet) { + if (typeof packet.data !== 'string') { throw new InvalidActionError( `Socket ${this.id} tried to unsubscribe from an invalid channel name` ); } +} + +AGServerSocket.prototype._unsubscribe = async function (channel) { if (!this.channelSubscriptions[channel]) { throw new InvalidActionError( `Socket ${this.id} tried to unsubscribe from a channel which it is not subscribed to` ); } - try { await this.server.brokerEngine.unsubscribeSocket(this, channel); delete this.channelSubscriptions[channel]; @@ -576,6 +584,15 @@ AGServerSocket.prototype._unsubscribe = async function (channel) { }; AGServerSocket.prototype._processUnsubscribePacket = async function (packet) { + try { + this._validateUnsubscribePacket(packet); + } catch (err) { + let error = new BrokerError( + `Failed to unsubscribe socket - ${err}` + ); + this.emitError(error); + return; + } let channel = packet.data; try { await this._unsubscribe(channel); @@ -588,6 +605,16 @@ AGServerSocket.prototype._processUnsubscribePacket = async function (packet) { }; AGServerSocket.prototype._processUnsubscribeRequest = async function (request) { + try { + this._validateUnsubscribePacket(request); + } catch (err) { + let error = new BrokerError( + `Failed to unsubscribe socket - ${err}` + ); + this.emitError(error); + request.error(error); + return; + } let channel = request.data; try { await this._unsubscribe(channel); @@ -605,7 +632,7 @@ AGServerSocket.prototype._processUnsubscribeRequest = async function (request) { AGServerSocket.prototype._processInboundPublishPacket = async function (packet) { let data = packet.data || {}; if (typeof data.channel !== 'string') { - let error = new InvalidActionError(`Socket ${this.id} tried to invoke publish to an invalid "${data.channel}" channel`); + let error = new InvalidActionError(`Socket ${this.id} tried to invoke publish to an invalid channel`); this.emitError(error); return; } @@ -619,7 +646,7 @@ AGServerSocket.prototype._processInboundPublishPacket = async function (packet) AGServerSocket.prototype._processInboundPublishRequest = async function (request) { let data = request.data || {}; if (typeof data.channel !== 'string') { - let error = new InvalidActionError(`Socket ${this.id} tried to transmit publish to an invalid "${data.channel}" channel`); + let error = new InvalidActionError(`Socket ${this.id} tried to transmit publish to an invalid channel`); this.emitError(error); request.error(error); return; @@ -643,13 +670,11 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message let request = new AGRequest(this, packet.cid, eventName, packet.data); await this._processHandshakeRequest(request); this._procedureDemux.write(eventName, request); - return; } if (this.server.strictHandshake && this.state === this.CONNECTING) { this._destroy(4009); this.socket.close(4009); - return; } if (eventName === '#authenticate') { @@ -657,13 +682,11 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message let request = new AGRequest(this, packet.cid, eventName, packet.data); await this._processAuthenticateRequest(request); this._procedureDemux.write(eventName, request); - return; } if (eventName === '#removeAuthToken') { this.deauthenticateSelf(); this._receiverDemux.write(eventName, packet.data); - return; } @@ -736,7 +759,6 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message newData = data; } catch (error) { request.error(error); - return; } @@ -757,7 +779,6 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message } this._procedureDemux.write(eventName, request); - return; } @@ -765,7 +786,6 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message let {data} = await this.server._processMiddlewareAction(this.middlewareInboundStream, action, this); newData = data; } catch (error) { - return; } @@ -778,14 +798,12 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message } this._receiverDemux.write(eventName, newData); - return; } if (this.server.strictHandshake && this.state === this.CONNECTING) { this._destroy(4009); this.socket.close(4009); - return; } @@ -840,7 +858,7 @@ AGServerSocket.prototype._abortAllPendingEventsDueToBadConnection = function (fa clearTimeout(eventObject.timeout); delete eventObject.timeout; - let errorMessage = `Event "${eventObject.event}" was aborted due to a bad connection`; + let errorMessage = `Event ${eventObject.event} was aborted due to a bad connection`; let badConnectionError = new BadConnectionError(errorMessage, failureType); let callback = eventObject.callback; @@ -1158,7 +1176,7 @@ AGServerSocket.prototype._transmit = async function (event, data, options) { AGServerSocket.prototype.transmit = async function (event, data, options) { if (this.state !== this.OPEN) { let error = new BadConnectionError( - `Socket transmit "${event}" was aborted due to a bad connection`, + `Socket transmit ${event} event was aborted due to a bad connection`, 'connectAbort' ); this.emitError(error); @@ -1170,7 +1188,7 @@ AGServerSocket.prototype.transmit = async function (event, data, options) { AGServerSocket.prototype.invoke = async function (event, data, options) { if (this.state !== this.OPEN) { let error = new BadConnectionError( - `Socket invoke "${event}" was aborted due to a bad connection`, + `Socket invoke ${event} event was aborted due to a bad connection`, 'connectAbort' ); this.emitError(error); @@ -1212,7 +1230,6 @@ AGServerSocket.prototype._processTransmit = async function (event, data, options newData = data; useCache = options == null ? useCache : options.useCache; } catch (error) { - return; } } else { @@ -1252,7 +1269,7 @@ AGServerSocket.prototype._invoke = async function (event, data, options) { let ackTimeout = options.ackTimeout == null ? this.server.ackTimeout : options.ackTimeout; let timeout = setTimeout(() => { - let error = new TimeoutError(`Event response for "${event}" timed out`); + let error = new TimeoutError(`Event response for ${event} event timed out`); delete this._callbackMap[eventObject.cid]; reject(error); }, ackTimeout); diff --git a/test/integration.js b/test/integration.js index 8f2e564..c1c5328 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1461,7 +1461,7 @@ describe('Integration tests', function () { // Expect a server warning (socket error) if transmit was called on a disconnected socket. assert.equal( serverWarnings.some((warning) => { - return warning.name === 'BadConnectionError' && warning.message.match(/Socket transmit "hi" was aborted/g); + return warning.name === 'BadConnectionError' && warning.message.match(/Socket transmit hi event was aborted/g); }), true ); @@ -1469,7 +1469,7 @@ describe('Integration tests', function () { // Expect a server warning (socket error) if invoke was called on a disconnected socket. assert.equal( serverWarnings.some((warning) => { - return warning.name === 'BadConnectionError' && warning.message.match(/Socket invoke "bla" was aborted/g); + return warning.name === 'BadConnectionError' && warning.message.match(/Socket invoke bla event was aborted/g); }), true ); @@ -2071,7 +2071,7 @@ describe('Integration tests', function () { assert.notEqual(nullPublishError, null); }); - it.only('When default AGSimpleBroker broker engine is used, disconnect event should trigger before unsubscribe event', async function () { + it('When default AGSimpleBroker broker engine is used, disconnect event should trigger before unsubscribe event', async function () { // Only the case in usabilityMode as there is a performance trade-off. server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, From 804069ac01bc3482e059994690f1f5bb440fbad9 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 15 Sep 2023 01:05:47 +1000 Subject: [PATCH 238/265] v18.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6cf3105..e7a3ee1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "18.0.0", + "version": "18.0.1", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From a8b253fbf3c2a96fb5d41d9f9faacd8ac164d164 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 16 Sep 2023 16:13:56 +1000 Subject: [PATCH 239/265] Added test case for concurrent connections --- package.json | 2 +- test/integration.js | 42 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e7a3ee1..007c7d5 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "socketcluster-client": "^18.0.0" }, "scripts": { - "test": "mocha --reporter spec --timeout 3000 --slow 3000" + "test": "mocha --reporter spec --timeout 10000 --slow 10000" }, "keywords": [ "websocket", diff --git a/test/integration.js b/test/integration.js index c1c5328..43dfc6a 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1100,6 +1100,42 @@ describe('Integration tests', function () { // a reference to the same object. assert.notEqual(clientConnectStatus.foo, connectStatus.foo); }); + + it('Server-side connection event should trigger with large number of concurrent connections', async function () { + server = socketClusterServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + bindFailureHandlers(server); + + let connectionList = []; + + (async () => { + for await (let event of server.listener('connection')) { + connectionList.push(event); + } + })(); + + await server.listener('ready').once(); + + let clientList = []; + + for (let i = 0; i < 100; i++) { + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + clientList.push(client); + } + + await wait(2000); + + assert.equal(connectionList.length, 100); + + for (let client of clientList) { + client.disconnect(); + } + }); }); describe('Socket disconnection', function () { @@ -2588,7 +2624,7 @@ describe('Integration tests', function () { server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, - pingInterval: 2000, + pingInterval: 5000, pingTimeout: 500 }); bindFailureHandlers(server); @@ -2648,7 +2684,7 @@ describe('Integration tests', function () { server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, wsEngine: WS_ENGINE, - pingInterval: 2000, + pingInterval: 1000, pingTimeout: 500, pingTimeoutDisabled: true }); @@ -2661,7 +2697,7 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER, - pingTimeoutDisabled: true + pingTimeoutDisabled: true, }); let serverWarning = null; From ef04e094277043fe7b2068737ee00ddd2bb297a9 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 16 Sep 2023 16:58:56 +1000 Subject: [PATCH 240/265] Add concurrency test case --- package.json | 2 +- test/integration.js | 48 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 007c7d5..15ba78a 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "devDependencies": { "localStorage": "^1.0.3", "mocha": "^10.2.0", - "socketcluster-client": "^18.0.0" + "socketcluster-client": "^18.0.1" }, "scripts": { "test": "mocha --reporter spec --timeout 10000 --slow 10000" diff --git a/test/integration.js b/test/integration.js index 43dfc6a..fa954bf 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1135,6 +1135,54 @@ describe('Integration tests', function () { for (let client of clientList) { client.disconnect(); } + await wait(1000); + }); + + it('Server should support large a number of connections invoking procedures concurrently immediately upon connect', async function () { + server = socketClusterServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + bindFailureHandlers(server); + + let connectionCount = 0; + let requestCount = 0; + + (async () => { + for await (let { socket } of server.listener('connection')) { + connectionCount++; + (async () => { + for await (let request of socket.procedure('greeting')) { + requestCount++; + await wait(20); + request.end('hello'); + } + })(); + } + })(); + + await server.listener('ready').once(); + + let clientList = []; + for (let i = 0; i < 100; i++) { + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER, + autoConnect: true, + }); + clientList.push(client); + await client.invoke('greeting'); + } + + await wait(2500); + + assert.equal(requestCount, 100); + assert.equal(connectionCount, 100); + + for (let client of clientList) { + client.disconnect(); + } + await wait(1000); }); }); From 8d57f2057d90ded3c3c32a7674da49017ce82d6c Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 16 Sep 2023 17:05:41 +1000 Subject: [PATCH 241/265] Bump ag-simple-broker --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 15ba78a..7fcda64 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "dependencies": { "ag-auth": "^2.0.0", "ag-request": "^1.0.0", - "ag-simple-broker": "^5.0.0", + "ag-simple-broker": "^5.0.1", "async-stream-emitter": "^6.0.1", "base64id": "^2.0.0", "clone-deep": "^4.0.1", From 5e1c04a8fd4164f96c216048ea69ea2196028e8c Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 16 Sep 2023 17:06:01 +1000 Subject: [PATCH 242/265] v18.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7fcda64..a6b3113 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "18.0.1", + "version": "18.0.2", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 4fc32c2b3cfee7eed511db76357525e34155e88d Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 18 Sep 2023 15:53:13 +1000 Subject: [PATCH 243/265] Improve error formatting for auth token response errors --- package.json | 2 +- serversocket.js | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a6b3113..a5e0d1d 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "async-stream-emitter": "^6.0.1", "base64id": "^2.0.0", "clone-deep": "^4.0.1", - "sc-errors": "^2.0.1", + "sc-errors": "^2.0.2", "sc-formatter": "^4.0.0", "stream-demux": "^9.0.2", "writable-consumable-stream": "^4.0.1", diff --git a/serversocket.js b/serversocket.js index 56e25d5..fe214c4 100644 --- a/serversocket.js +++ b/serversocket.js @@ -1397,7 +1397,14 @@ AGServerSocket.prototype.setAuthToken = async function (data, options) { try { await this.invoke('#setAuthToken', tokenData); } catch (err) { - let error = new AuthError(`Failed to deliver auth token to client - ${err}`); + let error; + if (err && typeof err.message === 'string') { + error = new AuthError(`Failed to deliver auth token to client - ${err.message}`); + } else { + error = new AuthError( + 'Failed to confirm delivery of auth token to client due to malformatted error response' + ); + } this.emitError(error); throw error; } From d17b9d15fbb7ad9ea0802e45f007d88be15cb72a Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 18 Sep 2023 15:54:17 +1000 Subject: [PATCH 244/265] Bump client for tests --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a5e0d1d..cce6bf4 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "devDependencies": { "localStorage": "^1.0.3", "mocha": "^10.2.0", - "socketcluster-client": "^18.0.1" + "socketcluster-client": "^18.0.2" }, "scripts": { "test": "mocha --reporter spec --timeout 10000 --slow 10000" From 1af70a3d9d5c11a45f599f07858d68212668a36f Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 18 Sep 2023 15:54:46 +1000 Subject: [PATCH 245/265] v18.0.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cce6bf4..b5b8f02 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "18.0.2", + "version": "18.0.3", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 4befef533eb5f54f5f657ad945f2d0f1795d35f7 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 18 Sep 2023 22:18:28 +1000 Subject: [PATCH 246/265] Move validation upstream and more strict to make middleware safer to use --- package.json | 2 +- server.js | 2 +- serversocket.js | 141 ++++++++++++++++++-------------------------- test/integration.js | 73 +++++++++++++++++++++++ 4 files changed, 132 insertions(+), 86 deletions(-) diff --git a/package.json b/package.json index b5b8f02..a4a4e10 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "ag-auth": "^2.0.0", - "ag-request": "^1.0.0", + "ag-request": "^1.0.1", "ag-simple-broker": "^5.0.1", "async-stream-emitter": "^6.0.1", "base64id": "^2.0.0", diff --git a/server.js b/server.js index 4801b36..b9c3561 100644 --- a/server.js +++ b/server.js @@ -340,7 +340,7 @@ AGServer.prototype._processMiddlewareAction = async function (middlewareStream, AGServer.prototype.verifyHandshake = async function (info, callback) { let req = info.req; let origin = info.origin; - if (origin === 'null' || origin == null) { + if (typeof origin !== 'string' || origin === 'null') { origin = '*'; } let ok = false; diff --git a/serversocket.js b/serversocket.js index fe214c4..a580641 100644 --- a/serversocket.js +++ b/serversocket.js @@ -472,15 +472,6 @@ AGServerSocket.prototype._processAuthenticateRequest = async function (request) }); }; -AGServerSocket.prototype._validateSubscribePacket = function (request) { - if (request.data == null || typeof request.data !== 'object') { - throw new InvalidActionError(`Socket ${this.id} provided a malformatted channel payload`); - } - if (typeof request.data.channel !== 'string') { - throw new InvalidActionError(`Socket ${this.id} provided an invalid channel name`); - } -} - AGServerSocket.prototype._subscribeSocket = async function (channelName, subscriptionOptions) { if (this.server.socketChannelLimit && this.channelSubscriptionsCount >= this.server.socketChannelLimit) { throw new InvalidActionError( @@ -516,15 +507,6 @@ AGServerSocket.prototype._subscribeSocket = async function (channelName, subscri AGServerSocket.prototype._processSubscribeRequest = async function (request) { if (this.state === this.OPEN) { - try { - this._validateSubscribePacket(request); - } catch (err) { - let error = new BrokerError(`Failed to subscribe socket - ${err}`); - this.emitError(error); - request.error(error); - return; - } - let subscriptionOptions = Object.assign({}, request.data); let channelName = subscriptionOptions.channel; delete subscriptionOptions.channel; @@ -553,14 +535,6 @@ AGServerSocket.prototype._unsubscribeFromAllChannels = function () { return Promise.all(channels.map((channel) => this._unsubscribe(channel))); }; -AGServerSocket.prototype._validateUnsubscribePacket = function (packet) { - if (typeof packet.data !== 'string') { - throw new InvalidActionError( - `Socket ${this.id} tried to unsubscribe from an invalid channel name` - ); - } -} - AGServerSocket.prototype._unsubscribe = async function (channel) { if (!this.channelSubscriptions[channel]) { throw new InvalidActionError( @@ -584,15 +558,6 @@ AGServerSocket.prototype._unsubscribe = async function (channel) { }; AGServerSocket.prototype._processUnsubscribePacket = async function (packet) { - try { - this._validateUnsubscribePacket(packet); - } catch (err) { - let error = new BrokerError( - `Failed to unsubscribe socket - ${err}` - ); - this.emitError(error); - return; - } let channel = packet.data; try { await this._unsubscribe(channel); @@ -605,16 +570,6 @@ AGServerSocket.prototype._processUnsubscribePacket = async function (packet) { }; AGServerSocket.prototype._processUnsubscribeRequest = async function (request) { - try { - this._validateUnsubscribePacket(request); - } catch (err) { - let error = new BrokerError( - `Failed to unsubscribe socket - ${err}` - ); - this.emitError(error); - request.error(error); - return; - } let channel = request.data; try { await this._unsubscribe(channel); @@ -630,29 +585,16 @@ AGServerSocket.prototype._processUnsubscribeRequest = async function (request) { }; AGServerSocket.prototype._processInboundPublishPacket = async function (packet) { - let data = packet.data || {}; - if (typeof data.channel !== 'string') { - let error = new InvalidActionError(`Socket ${this.id} tried to invoke publish to an invalid channel`); - this.emitError(error); - return; - } try { - await this.server.exchange.invokePublish(data.channel, data.data); + await this.server.exchange.invokePublish(packet.data.channel, packet.data.data); } catch (error) { this.emitError(error); } }; AGServerSocket.prototype._processInboundPublishRequest = async function (request) { - let data = request.data || {}; - if (typeof data.channel !== 'string') { - let error = new InvalidActionError(`Socket ${this.id} tried to transmit publish to an invalid channel`); - this.emitError(error); - request.error(error); - return; - } try { - await this.server.exchange.invokePublish(data.channel, data.data); + await this.server.exchange.invokePublish(request.data.channel, request.data.data); } catch (error) { this.emitError(error); request.error(error); @@ -662,11 +604,18 @@ AGServerSocket.prototype._processInboundPublishRequest = async function (request }; AGServerSocket.prototype._processInboundPacket = async function (packet, message) { - if (packet && packet.event != null) { + if (packet && typeof packet.event === 'string') { let eventName = packet.event; - let isRPC = packet.cid != null; + let isRPC = typeof packet.cid === 'number'; if (eventName === '#handshake') { + if (!isRPC) { + let error = new InvalidActionError('Handshake request was malformatted'); + this.emitError(error); + this._destroy(HANDSHAKE_REJECTION_STATUS_CODE); + this.socket.close(HANDSHAKE_REJECTION_STATUS_CODE); + return; + } let request = new AGRequest(this, packet.cid, eventName, packet.data); await this._processHandshakeRequest(request); this._procedureDemux.write(eventName, request); @@ -678,6 +627,13 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message return; } if (eventName === '#authenticate') { + if (!isRPC) { + let error = new InvalidActionError('Authenticate request was malformatted'); + this.emitError(error); + this._destroy(HANDSHAKE_REJECTION_STATUS_CODE); + this.socket.close(HANDSHAKE_REJECTION_STATUS_CODE); + return; + } // Let AGServer handle these events. let request = new AGRequest(this, packet.cid, eventName, packet.data); await this._processAuthenticateRequest(request); @@ -713,18 +669,44 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message } return; } - action.type = AGAction.PUBLISH_IN; - if (packet.data) { - action.channel = packet.data.channel; - action.data = packet.data.data; + if (!packet.data || typeof packet.data.channel !== 'string') { + let error = new InvalidActionError('Publish channel name was malformatted'); + this.emitError(error); + + if (isRPC) { + let request = new AGRequest(this, packet.cid, eventName, packet.data); + request.error(error); + } + return; } + action.type = AGAction.PUBLISH_IN; + action.channel = packet.data.channel; + action.data = packet.data.data; } else if (isSubscribe) { - action.type = AGAction.SUBSCRIBE; - if (packet.data) { - action.channel = packet.data.channel; - action.data = packet.data.data; + if (!packet.data || typeof packet.data.channel !== 'string') { + let error = new InvalidActionError('Subscribe channel name was malformatted'); + this.emitError(error); + + if (isRPC) { + let request = new AGRequest(this, packet.cid, eventName, packet.data); + request.error(error); + } + return; } + action.type = AGAction.SUBSCRIBE; + action.channel = packet.data.channel; + action.data = packet.data.data; } else if (isUnsubscribe) { + if (typeof packet.data !== 'string') { + let error = new InvalidActionError('Unsubscribe channel name was malformatted'); + this.emitError(error); + + if (isRPC) { + let request = new AGRequest(this, packet.cid, eventName, packet.data); + request.error(error); + } + return; + } if (isRPC) { let request = new AGRequest(this, packet.cid, eventName, packet.data); await this._processUnsubscribeRequest(request); @@ -763,15 +745,9 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message } if (isSubscribe) { - if (!request.data) { - request.data = {}; - } request.data.data = newData; await this._processSubscribeRequest(request); } else if (isPublish) { - if (!request.data) { - request.data = {}; - } request.data.data = newData; await this._processInboundPublishRequest(request); } else { @@ -790,9 +766,6 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message } if (isPublish) { - if (!packet.data) { - packet.data = {}; - } packet.data.data = newData; await this._processInboundPublishPacket(packet); } @@ -807,7 +780,7 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message return; } - if (packet && packet.rid != null) { + if (packet && typeof packet.rid === 'number') { // If incoming message is a response to a previously sent message let ret = this._callbackMap[packet.rid]; if (ret) { @@ -983,14 +956,14 @@ AGServerSocket.prototype._destroy = async function (code, reason) { let closeMessage; if (reason) { let reasonString; - if (typeof reason === 'object') { + if (typeof reason === 'string') { + reasonString = reason; + } else { try { reasonString = JSON.stringify(reason); } catch (error) { - reasonString = reason.toString(); + reasonString = typeof reason.toString === 'function' ? reason.toString() : 'Malformatted reason'; } - } else { - reasonString = reason; } closeMessage = `Socket connection closed with status code ${code} and reason: ${reasonString}`; } else { diff --git a/test/integration.js b/test/integration.js index fa954bf..971921b 100644 --- a/test/integration.js +++ b/test/integration.js @@ -895,6 +895,46 @@ describe('Integration tests', function () { assert.equal(server.pendingClientsCount, 0); assert.equal(JSON.stringify(server.pendingClients), '{}'); }); + + it('Should close the connection if the client tries to send a malformatted authenticate packet', async function () { + server = socketClusterServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + + await server.listener('ready').once(); + + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + let originalInvoke = client.invoke; + client.invoke = async function (...args) { + if (args[0] === '#authenticate') { + client.transmit(args[0], args[1]); + return; + } + return originalInvoke.apply(this, args); + }; + + client.authenticate(validSignedAuthTokenBob) + + let results = await Promise.all([ + server.listener('closure').once(500), + client.listener('close').once(100) + ]); + assert.equal(results[0].code, 4008); + assert.equal(results[0].reason, 'Server rejected handshake from client'); + assert.equal(results[1].code, 4008); + assert.equal(results[1].reason, 'Server rejected handshake from client'); + }); }); describe('Socket handshake', function () { @@ -983,6 +1023,39 @@ describe('Integration tests', function () { assert.equal(closeCode, 4009); }); + it('Should close the connection if the client tries to send a malformatted handshake', async function () { + server = socketClusterServer.listen(PORT_NUMBER, { + authKey: serverOptions.authKey, + wsEngine: WS_ENGINE + }); + + (async () => { + for await (let {socket} of server.listener('connection')) { + connectionHandler(socket); + } + })(); + + await server.listener('ready').once(); + + client = socketClusterClient.create({ + hostname: clientOptions.hostname, + port: PORT_NUMBER + }); + + client.transport._handshake = async function () { + this.transmit('#handshake', {}, {force: true}); + }; + + let results = await Promise.all([ + server.listener('closure').once(200), + client.listener('close').once(200) + ]); + assert.equal(results[0].code, 4008); + assert.equal(results[0].reason, 'Server rejected handshake from client'); + assert.equal(results[1].code, 4008); + assert.equal(results[1].reason, 'Server rejected handshake from client'); + }); + it('Should not close the connection if the client tries to send a message before the handshake and strictHandshake is false', async function () { server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, From da136d04d87be58d64b444630237c6aab77af66c Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 18 Sep 2023 22:20:04 +1000 Subject: [PATCH 247/265] v18.1.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a4a4e10..f77695e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "18.0.3", + "version": "18.1.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { @@ -23,7 +23,7 @@ "devDependencies": { "localStorage": "^1.0.3", "mocha": "^10.2.0", - "socketcluster-client": "^18.0.2" + "socketcluster-client": "^18.0.3" }, "scripts": { "test": "mocha --reporter spec --timeout 10000 --slow 10000" From 153953513d79924cd1f048cf59b067920f02d2ef Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 18 Sep 2023 22:59:35 +1000 Subject: [PATCH 248/265] Simplify handling of close reason --- serversocket.js | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/serversocket.js b/serversocket.js index a580641..22360fa 100644 --- a/serversocket.js +++ b/serversocket.js @@ -954,18 +954,8 @@ AGServerSocket.prototype._destroy = async function (code, reason) { if (!AGServerSocket.ignoreStatuses[code]) { let closeMessage; - if (reason) { - let reasonString; - if (typeof reason === 'string') { - reasonString = reason; - } else { - try { - reasonString = JSON.stringify(reason); - } catch (error) { - reasonString = typeof reason.toString === 'function' ? reason.toString() : 'Malformatted reason'; - } - } - closeMessage = `Socket connection closed with status code ${code} and reason: ${reasonString}`; + if (typeof reason === 'string') { + closeMessage = `Socket connection closed with status code ${code} and reason: ${reason}`; } else { closeMessage = `Socket connection closed with status code ${code}`; } From 8fc98efd5dc0a60d2d2d75b72d8924b6b3f12d2e Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 19 Sep 2023 00:51:59 +1000 Subject: [PATCH 249/265] v18.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f77695e..6e8a1d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "18.1.0", + "version": "18.1.1", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From c9a431c6eaa85c9be98158eefe1be2f799d85ee3 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 19 Sep 2023 00:55:46 +1000 Subject: [PATCH 250/265] v18.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6e8a1d5..ad24b58 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "devDependencies": { "localStorage": "^1.0.3", "mocha": "^10.2.0", - "socketcluster-client": "^18.0.3" + "socketcluster-client": "^18.1.0" }, "scripts": { "test": "mocha --reporter spec --timeout 10000 --slow 10000" From d7cbffa42942cd9460e76c424366b47d92de4a58 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 19 Sep 2023 23:14:40 +1000 Subject: [PATCH 251/265] v18.1.2 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ad24b58..c416185 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "18.1.1", + "version": "18.1.2", "description": "Server module for SocketCluster", "main": "index.js", "repository": { @@ -14,7 +14,7 @@ "async-stream-emitter": "^6.0.1", "base64id": "^2.0.0", "clone-deep": "^4.0.1", - "sc-errors": "^2.0.2", + "sc-errors": "^2.0.3", "sc-formatter": "^4.0.0", "stream-demux": "^9.0.2", "writable-consumable-stream": "^4.0.1", From 0120ac9dc932d5634d8f7092d0fe1ffffc9451ee Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 19 Sep 2023 23:20:55 +1000 Subject: [PATCH 252/265] Bump sc client --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c416185..0f68cff 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "devDependencies": { "localStorage": "^1.0.3", "mocha": "^10.2.0", - "socketcluster-client": "^18.1.0" + "socketcluster-client": "^18.1.2" }, "scripts": { "test": "mocha --reporter spec --timeout 10000 --slow 10000" From 52200e75afe8aa89892aca96057d0b01bfdeac9d Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 13 Oct 2023 22:38:47 +1100 Subject: [PATCH 253/265] Allow substituting the request creation function --- index.js | 8 ++++++++ serversocket.js | 21 +++++++++++++-------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index fda19d2..e230110 100644 --- a/index.js +++ b/index.js @@ -20,6 +20,14 @@ module.exports.AGServer = require('./server'); module.exports.AGServerSocket = require('./serversocket'); +/** + * Expose AGRequest constructor. + * + * @api public + */ + +module.exports.AGRequest = require('ag-request'); + /** * Creates an http.Server exclusively used for WS upgrades. * diff --git a/serversocket.js b/serversocket.js index 22360fa..00899c1 100644 --- a/serversocket.js +++ b/serversocket.js @@ -41,6 +41,7 @@ function AGServerSocket(id, server, socket, protocolVersion) { this.outboundPreparedMessageCount = 0; this.outboundSentMessageCount = 0; + this.createRequest = this.server.options.requestCreator || this.defaultRequestCreator; this.cloneData = this.server.options.cloneData; this.inboundMessageStream = new WritableConsumableStream(); @@ -197,6 +198,10 @@ AGServerSocket.prototype._startBatchOnHandshake = function () { }, this.batchOnHandshakeDuration); }; +AGServerSocket.prototype.defaultRequestCreator = function (socket, id, procedureName, data) { + return new AGRequest(socket, id, procedureName, data); +}; + // ---- Receiver logic ---- AGServerSocket.prototype.receiver = function (receiverName) { @@ -616,7 +621,7 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message this.socket.close(HANDSHAKE_REJECTION_STATUS_CODE); return; } - let request = new AGRequest(this, packet.cid, eventName, packet.data); + let request = this.createRequest(this, packet.cid, eventName, packet.data); await this._processHandshakeRequest(request); this._procedureDemux.write(eventName, request); return; @@ -635,7 +640,7 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message return; } // Let AGServer handle these events. - let request = new AGRequest(this, packet.cid, eventName, packet.data); + let request = this.createRequest(this, packet.cid, eventName, packet.data); await this._processAuthenticateRequest(request); this._procedureDemux.write(eventName, request); return; @@ -664,7 +669,7 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message this.emitError(error); if (isRPC) { - let request = new AGRequest(this, packet.cid, eventName, packet.data); + let request = this.createRequest(this, packet.cid, eventName, packet.data); request.error(error); } return; @@ -674,7 +679,7 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message this.emitError(error); if (isRPC) { - let request = new AGRequest(this, packet.cid, eventName, packet.data); + let request = this.createRequest(this, packet.cid, eventName, packet.data); request.error(error); } return; @@ -688,7 +693,7 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message this.emitError(error); if (isRPC) { - let request = new AGRequest(this, packet.cid, eventName, packet.data); + let request = this.createRequest(this, packet.cid, eventName, packet.data); request.error(error); } return; @@ -702,13 +707,13 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message this.emitError(error); if (isRPC) { - let request = new AGRequest(this, packet.cid, eventName, packet.data); + let request = this.createRequest(this, packet.cid, eventName, packet.data); request.error(error); } return; } if (isRPC) { - let request = new AGRequest(this, packet.cid, eventName, packet.data); + let request = this.createRequest(this, packet.cid, eventName, packet.data); await this._processUnsubscribeRequest(request); this._procedureDemux.write(eventName, request); return; @@ -735,7 +740,7 @@ AGServerSocket.prototype._processInboundPacket = async function (packet, message let newData; if (isRPC) { - let request = new AGRequest(this, packet.cid, eventName, packet.data); + let request = this.createRequest(this, packet.cid, eventName, packet.data); try { let {data} = await this.server._processMiddlewareAction(this.middlewareInboundStream, action, this); newData = data; From 35586a1529c5fafcab9cfe3dc32b26fff07def2b Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Fri, 13 Oct 2023 22:39:05 +1100 Subject: [PATCH 254/265] v18.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0f68cff..b430a7e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "18.1.2", + "version": "18.2.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 32bf21b202118ecf81a2500aa40e973996320e47 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 21 Oct 2023 03:25:29 +1100 Subject: [PATCH 255/265] Bump dependencies and fix tests --- package.json | 8 +- server.js | 2 +- serversocket.js | 2 +- test/integration.js | 225 +++++++++++++++++++++++++++++--------------- 4 files changed, 155 insertions(+), 82 deletions(-) diff --git a/package.json b/package.json index b430a7e..7a2d17f 100644 --- a/package.json +++ b/package.json @@ -10,20 +10,20 @@ "dependencies": { "ag-auth": "^2.0.0", "ag-request": "^1.0.1", - "ag-simple-broker": "^5.0.1", - "async-stream-emitter": "^6.0.1", + "ag-simple-broker": "^6.0.0", + "async-stream-emitter": "^7.0.0", "base64id": "^2.0.0", "clone-deep": "^4.0.1", "sc-errors": "^2.0.3", "sc-formatter": "^4.0.0", - "stream-demux": "^9.0.2", + "stream-demux": "^10.0.0", "writable-consumable-stream": "^4.0.1", "ws": "^8.9.0" }, "devDependencies": { "localStorage": "^1.0.3", "mocha": "^10.2.0", - "socketcluster-client": "^18.1.2" + "socketcluster-client": "18.x.x" }, "scripts": { "test": "mocha --reporter spec --timeout 10000 --slow 10000" diff --git a/server.js b/server.js index b9c3561..1d7c055 100644 --- a/server.js +++ b/server.js @@ -17,7 +17,7 @@ const InvalidActionError = scErrors.InvalidActionError; const ServerProtocolError = scErrors.ServerProtocolError; function AGServer(options) { - AsyncStreamEmitter.call(this, { usabilityMode: options?.usabilityMode ?? false }); + AsyncStreamEmitter.call(this); let opts = { brokerEngine: new AGSimpleBroker(), diff --git a/serversocket.js b/serversocket.js index 00899c1..1e640eb 100644 --- a/serversocket.js +++ b/serversocket.js @@ -21,7 +21,7 @@ const BrokerError = scErrors.BrokerError; const HANDSHAKE_REJECTION_STATUS_CODE = 4008; function AGServerSocket(id, server, socket, protocolVersion) { - AsyncStreamEmitter.call(this, { usabilityMode: server.usabilityMode }); + AsyncStreamEmitter.call(this); this.id = id; this.server = server; diff --git a/test/integration.js b/test/integration.js index 971921b..f5a671d 100644 --- a/test/integration.js +++ b/test/integration.js @@ -155,7 +155,8 @@ describe('Integration tests', function () { beforeEach('Prepare options', async function () { clientOptions = { hostname: '127.0.0.1', - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }; serverOptions = { authKey: 'testkey', @@ -417,7 +418,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.listener('connect').once(); @@ -433,7 +435,8 @@ describe('Integration tests', function () { it('Authentication can be captured using the authenticate listener', async function () { server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, - wsEngine: WS_ENGINE + wsEngine: WS_ENGINE, + authTokenName: 'socketcluster.authToken' }); bindFailureHandlers(server); @@ -447,7 +450,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.listener('connect').once(); @@ -477,7 +481,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.listener('connect').once(); @@ -513,7 +518,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.listener('connect').once(); @@ -545,7 +551,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.listener('connect').once(); @@ -577,7 +584,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.listener('connect').once(); @@ -611,7 +619,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.listener('connect').once(); @@ -698,7 +707,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.listener('connect').once(); @@ -725,7 +735,8 @@ describe('Integration tests', function () { await server.listener('ready').once(); client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.listener('connect').once(); client.invoke('login', {username: 'bob'}); @@ -777,7 +788,8 @@ describe('Integration tests', function () { await server.listener('ready').once(); client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.listener('connect').once(); client.invoke('login', {username: 'bob'}); @@ -831,7 +843,8 @@ describe('Integration tests', function () { await server.listener('ready').once(); client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); })(); @@ -872,7 +885,8 @@ describe('Integration tests', function () { await server.listener('ready').once(); client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let serverSocket; @@ -912,7 +926,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let originalInvoke = client.invoke; @@ -955,7 +970,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let {socket} = await server.listener('handshake').once(); @@ -978,7 +994,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); client.transport.socket.onopen = function () { @@ -1011,7 +1028,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); client.transport.socket.onopen = function () { @@ -1039,7 +1057,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); client.transport._handshake = async function () { @@ -1073,7 +1092,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let realOnOpenFunction = client.transport.socket.onopen; @@ -1113,7 +1133,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let connectEmitted = false; @@ -1196,7 +1217,8 @@ describe('Integration tests', function () { for (let i = 0; i < 100; i++) { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); clientList.push(client); } @@ -1242,6 +1264,7 @@ describe('Integration tests', function () { hostname: clientOptions.hostname, port: PORT_NUMBER, autoConnect: true, + authTokenName: 'socketcluster.authToken' }); clientList.push(client); await client.invoke('greeting'); @@ -1286,7 +1309,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let socketDisconnected = false; @@ -1364,7 +1388,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let socketDisconnected = false; @@ -1441,7 +1466,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let serverSocketClosed = false; @@ -1503,7 +1529,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let serverSocketClosed = false; @@ -1559,7 +1586,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let currentRequestData = null; @@ -1663,7 +1691,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await wait(100); @@ -1708,7 +1737,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await wait(100); @@ -1753,7 +1783,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await wait(100); @@ -1798,7 +1829,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let result = await client.invoke('customProc', {good: true}); @@ -1828,7 +1860,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); client.transmit('customRemoteEvent', 'This is data'); @@ -1874,7 +1907,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.listener('connect').once(); @@ -1929,7 +1963,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.subscribe('foo').listener('subscribe').once(); @@ -1973,7 +2008,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.listener('connect').once(); @@ -2011,7 +2047,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.listener('connect').once(); @@ -2049,7 +2086,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER, - autoConnect: false + autoConnect: false, + authTokenName: 'socketcluster.authToken' }); assert.equal(client.state, client.CLOSED); @@ -2092,7 +2130,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let isSubscribed = false; @@ -2141,7 +2180,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let nullInChannelArrayError; @@ -2229,11 +2269,9 @@ describe('Integration tests', function () { }); it('When default AGSimpleBroker broker engine is used, disconnect event should trigger before unsubscribe event', async function () { - // Only the case in usabilityMode as there is a performance trade-off. server = socketClusterServer.listen(PORT_NUMBER, { authKey: serverOptions.authKey, - wsEngine: WS_ENGINE, - usabilityMode: true + wsEngine: WS_ENGINE }); bindFailureHandlers(server); @@ -2244,7 +2282,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.subscribe('foo').listener('subscribe').once(); @@ -2290,7 +2329,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); (async () => { @@ -2339,7 +2379,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); (async () => { @@ -2393,7 +2434,8 @@ describe('Integration tests', function () { await server.listener('ready').once(); client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); for await (let event of client.subscribe('foo').listener('subscribe')) { @@ -2465,7 +2507,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let error; @@ -2512,7 +2555,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); // Stub the isSubscribed method so that it always returns true. // That way the client will always invoke watchers whenever @@ -2576,7 +2620,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); client.subscribe('foo'); @@ -2644,7 +2689,8 @@ describe('Integration tests', function () { port: PORT_NUMBER, batchOnHandshake: true, batchOnHandshakeDuration: 100, - batchInterval: 50 + batchInterval: 50, + authTokenName: 'socketcluster.authToken' }); let receivedClientMessages = []; @@ -2722,7 +2768,8 @@ describe('Integration tests', function () { autoConnect: false, batchOnHandshake: true, batchOnHandshakeDuration: 100, - batchInterval: 50 + batchInterval: 50, + authTokenName: 'socketcluster.authToken' }); let receivedMessage; @@ -2756,7 +2803,8 @@ describe('Integration tests', function () { it('Should disconnect socket if server does not receive a pong from client before timeout', async function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let serverWarning = null; @@ -2819,6 +2867,7 @@ describe('Integration tests', function () { hostname: clientOptions.hostname, port: PORT_NUMBER, pingTimeoutDisabled: true, + authTokenName: 'socketcluster.authToken' }); let serverWarning = null; @@ -2876,7 +2925,8 @@ describe('Integration tests', function () { it('Should not disconnect socket if server receives a pong from client before timeout', async function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let serverWarning = null; @@ -2954,7 +3004,8 @@ describe('Integration tests', function () { let clientA = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let clientB = socketClusterClient.create({ @@ -2962,7 +3013,8 @@ describe('Integration tests', function () { port: PORT_NUMBER, query: { delayMe: true - } + }, + authTokenName: 'socketcluster.authToken' }); let clientAIsConnected = false; @@ -3018,7 +3070,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); (async () => { @@ -3065,7 +3118,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); (async () => { @@ -3106,7 +3160,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); (async () => { @@ -3140,7 +3195,8 @@ describe('Integration tests', function () { createConnectionTime = Date.now(); client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); (async () => { @@ -3178,7 +3234,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let event = await client.listener('connect').once(); @@ -3208,7 +3265,8 @@ describe('Integration tests', function () { let clientA = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let clientB = socketClusterClient.create({ @@ -3216,7 +3274,8 @@ describe('Integration tests', function () { port: PORT_NUMBER, query: { delayMe: true - } + }, + authTokenName: 'socketcluster.authToken' }); let clientAIsConnected = false; @@ -3262,7 +3321,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let result = await client.invoke('proc', 123); @@ -3294,7 +3354,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let result; @@ -3326,7 +3387,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.listener('connect').once(); @@ -3349,7 +3411,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); (async () => { @@ -3381,7 +3444,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.invokePublish('hello', 'world'); @@ -3412,7 +3476,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let helloChannel = client.subscribe('hello'); @@ -3457,7 +3522,8 @@ describe('Integration tests', function () { let clientA = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let clientB = socketClusterClient.create({ @@ -3465,12 +3531,14 @@ describe('Integration tests', function () { port: PORT_NUMBER, query: { delayMe: true - } + }, + authTokenName: 'socketcluster.authToken' }); let clientC = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await clientC.listener('connect').once(); @@ -3516,7 +3584,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let helloChannel = client.subscribe('hello'); @@ -3559,7 +3628,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); let helloChannel = client.subscribe('hello'); @@ -3604,7 +3674,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.subscribe('hello').listener('subscribe').once(); @@ -3633,7 +3704,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, port: PORT_NUMBER, - autoConnect: false + autoConnect: false, + authTokenName: 'socketcluster.authToken' }); let receivedMessage; @@ -3675,7 +3747,8 @@ describe('Integration tests', function () { client = socketClusterClient.create({ hostname: clientOptions.hostname, - port: PORT_NUMBER + port: PORT_NUMBER, + authTokenName: 'socketcluster.authToken' }); await client.subscribe('hello').listener('subscribe').once(); From 0d67376b0606d341116e888ede3f9c737d725d96 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 21 Oct 2023 03:27:24 +1100 Subject: [PATCH 256/265] v19.0.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7a2d17f..57a3ecf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "18.2.0", + "version": "19.0.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { @@ -23,7 +23,7 @@ "devDependencies": { "localStorage": "^1.0.3", "mocha": "^10.2.0", - "socketcluster-client": "18.x.x" + "socketcluster-client": "19.x.x" }, "scripts": { "test": "mocha --reporter spec --timeout 10000 --slow 10000" From ce64e35719efa4e5fd17a18956d8c88711215de1 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sat, 21 Oct 2023 03:34:13 +1100 Subject: [PATCH 257/265] v19.0.1 --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 57a3ecf..58820ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "19.0.0", + "version": "19.0.1", "description": "Server module for SocketCluster", "main": "index.js", "repository": { @@ -11,13 +11,13 @@ "ag-auth": "^2.0.0", "ag-request": "^1.0.1", "ag-simple-broker": "^6.0.0", - "async-stream-emitter": "^7.0.0", + "async-stream-emitter": "^7.0.1", "base64id": "^2.0.0", "clone-deep": "^4.0.1", "sc-errors": "^2.0.3", "sc-formatter": "^4.0.0", - "stream-demux": "^10.0.0", - "writable-consumable-stream": "^4.0.1", + "stream-demux": "^10.0.1", + "writable-consumable-stream": "^4.1.0", "ws": "^8.9.0" }, "devDependencies": { From 4764f5b60ce977418beddf94669f1e67a0eb6ff4 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Wed, 8 Nov 2023 22:57:41 +1100 Subject: [PATCH 258/265] Bump ag-simple-broker --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 58820ff..1797052 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "dependencies": { "ag-auth": "^2.0.0", "ag-request": "^1.0.1", - "ag-simple-broker": "^6.0.0", + "ag-simple-broker": "^6.0.1", "async-stream-emitter": "^7.0.1", "base64id": "^2.0.0", "clone-deep": "^4.0.1", From b4107a77188b970c4e35c1af50bbf87c96ca1200 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 29 Apr 2024 14:59:11 +1000 Subject: [PATCH 259/265] Add code and reason properties to BadConnectionError --- package.json | 8 ++++---- serversocket.js | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 1797052..8aaefaf 100644 --- a/package.json +++ b/package.json @@ -8,13 +8,13 @@ "url": "git://github.com/SocketCluster/socketcluster-server.git" }, "dependencies": { - "ag-auth": "^2.0.0", - "ag-request": "^1.0.1", + "ag-auth": "^2.1.0", + "ag-request": "^1.1.0", "ag-simple-broker": "^6.0.1", "async-stream-emitter": "^7.0.1", "base64id": "^2.0.0", "clone-deep": "^4.0.1", - "sc-errors": "^2.0.3", + "sc-errors": "^3.0.0", "sc-formatter": "^4.0.0", "stream-demux": "^10.0.1", "writable-consumable-stream": "^4.1.0", @@ -23,7 +23,7 @@ "devDependencies": { "localStorage": "^1.0.3", "mocha": "^10.2.0", - "socketcluster-client": "19.x.x" + "socketcluster-client": "*" }, "scripts": { "test": "mocha --reporter spec --timeout 10000 --slow 10000" diff --git a/serversocket.js b/serversocket.js index 1e640eb..3071f17 100644 --- a/serversocket.js +++ b/serversocket.js @@ -828,7 +828,7 @@ AGServerSocket.prototype.emitError = function (error) { this.server.emitWarning(error); }; -AGServerSocket.prototype._abortAllPendingEventsDueToBadConnection = function (failureType) { +AGServerSocket.prototype._abortAllPendingEventsDueToBadConnection = function (failureType, code, reason) { Object.keys(this._callbackMap || {}).forEach((i) => { let eventObject = this._callbackMap[i]; delete this._callbackMap[i]; @@ -837,7 +837,7 @@ AGServerSocket.prototype._abortAllPendingEventsDueToBadConnection = function (fa delete eventObject.timeout; let errorMessage = `Event ${eventObject.event} was aborted due to a bad connection`; - let badConnectionError = new BadConnectionError(errorMessage, failureType); + let badConnectionError = new BadConnectionError(errorMessage, failureType, code, reason); let callback = eventObject.callback; delete eventObject.callback; @@ -911,7 +911,7 @@ AGServerSocket.prototype._destroy = async function (code, reason) { this._cancelBatching(); if (this.state === this.CLOSED) { - this._abortAllPendingEventsDueToBadConnection('connectAbort'); + this._abortAllPendingEventsDueToBadConnection('connectAbort', code, reason); } else { if (!reason && AGServerSocket.errorStatuses[code]) { reason = AGServerSocket.errorStatuses[code]; @@ -919,7 +919,7 @@ AGServerSocket.prototype._destroy = async function (code, reason) { let prevState = this.state; this.state = this.CLOSED; if (prevState === this.CONNECTING) { - this._abortAllPendingEventsDueToBadConnection('connectAbort'); + this._abortAllPendingEventsDueToBadConnection('connectAbort', code, reason); this.emit('connectAbort', {code, reason}); this.server.emit('connectionAbort', { socket: this, @@ -927,7 +927,7 @@ AGServerSocket.prototype._destroy = async function (code, reason) { reason }); } else { - this._abortAllPendingEventsDueToBadConnection('disconnect'); + this._abortAllPendingEventsDueToBadConnection('disconnect', code, reason); this.emit('disconnect', {code, reason}); this.server.emit('disconnection', { socket: this, From a5759a8d5e71b0e868ae59444850ad6996880fc8 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 29 Apr 2024 14:59:53 +1000 Subject: [PATCH 260/265] v19.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8aaefaf..f3cc708 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "19.0.1", + "version": "19.1.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From de0bb4ebe9154782ed783ede3fb5cc2d893f48be Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 4 Nov 2024 20:12:22 +1000 Subject: [PATCH 261/265] Bump ws --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f3cc708..80ef66f 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "sc-formatter": "^4.0.0", "stream-demux": "^10.0.1", "writable-consumable-stream": "^4.1.0", - "ws": "^8.9.0" + "ws": "^8.18.0" }, "devDependencies": { "localStorage": "^1.0.3", From 1e8c43f8b79db9e7da65d7b66f52be39081885a5 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Mon, 4 Nov 2024 20:13:06 +1000 Subject: [PATCH 262/265] v19.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 80ef66f..b154bf9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "19.1.0", + "version": "19.1.1", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 3fbc837d0a03694c3de36308018548375bde3620 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 15 Jun 2025 21:18:27 +1000 Subject: [PATCH 263/265] v19.1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b154bf9..604528a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "19.1.1", + "version": "19.1.2", "description": "Server module for SocketCluster", "main": "index.js", "repository": { From 809937f50c937e0e2332c9d99a356c69133bf0e9 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 24 Jun 2025 18:45:57 +1000 Subject: [PATCH 264/265] Increase default pingTimeout --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index 1d7c055..3a76461 100644 --- a/server.js +++ b/server.js @@ -28,7 +28,7 @@ function AGServer(options) { ackTimeout: 10000, handshakeTimeout: 10000, strictHandshake: true, - pingTimeout: 20000, + pingTimeout: 30000, pingTimeoutDisabled: false, pingInterval: 8000, origins: '*:*', From d1d3df84efddbb21986bdec59dcc6177d55a2682 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Tue, 24 Jun 2025 18:49:16 +1000 Subject: [PATCH 265/265] v19.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 604528a..8c86ae4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socketcluster-server", - "version": "19.1.2", + "version": "19.2.0", "description": "Server module for SocketCluster", "main": "index.js", "repository": {