From c1f6c759acf28bc88a16163ddc0f2c9b4f77c7f0 Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Thu, 27 Dec 2018 02:36:20 +0100 Subject: [PATCH 001/179] 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 002/179] 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 003/179] 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 004/179] 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 005/179] 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 006/179] 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 007/179] 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 008/179] 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 009/179] 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 010/179] 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 011/179] 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 012/179] 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 013/179] 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 014/179] 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 015/179] 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 016/179] 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 017/179] 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 018/179] 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 019/179] 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 020/179] 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 021/179] 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 022/179] 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 023/179] 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 024/179] 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 025/179] 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 026/179] 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 027/179] 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 028/179] 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 029/179] 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 030/179] 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 031/179] 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 032/179] 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 033/179] 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 034/179] 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 035/179] 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 036/179] 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 037/179] 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 038/179] 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 039/179] 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 040/179] 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 041/179] 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 042/179] 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 043/179] 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 044/179] 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 045/179] 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 046/179] 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 047/179] 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 048/179] 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 049/179] 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 050/179] 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 051/179] 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 052/179] 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 053/179] 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 054/179] 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 055/179] 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 056/179] 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 057/179] 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 058/179] 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 059/179] 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 060/179] 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 061/179] 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 062/179] 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 063/179] 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 064/179] 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 065/179] 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 066/179] 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 067/179] 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 068/179] 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 069/179] 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 070/179] 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 071/179] 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 072/179] 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 073/179] 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 074/179] 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 075/179] 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 076/179] 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 077/179] 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 078/179] 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 079/179] 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 080/179] 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 081/179] 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 082/179] 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 083/179] 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 084/179] 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 085/179] 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 086/179] 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 087/179] 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 088/179] 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 089/179] 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 090/179] 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 091/179] 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 092/179] 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 093/179] 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 094/179] 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 095/179] 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 096/179] 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 097/179] 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 098/179] 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 099/179] 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 100/179] 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 101/179] 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 102/179] 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 103/179] 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 104/179] 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 105/179] 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 106/179] 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 107/179] 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 108/179] 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 109/179] 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 110/179] 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 111/179] 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 112/179] 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 113/179] 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 114/179] 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 115/179] 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 116/179] 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 117/179] 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 118/179] 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 119/179] 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 120/179] 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 121/179] 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 122/179] 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 123/179] 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 124/179] 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 125/179] 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 126/179] 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 127/179] 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 128/179] 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 129/179] 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 130/179] 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 131/179] 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 132/179] 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 133/179] 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 134/179] 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 135/179] 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 136/179] 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 137/179] 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 138/179] 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 139/179] 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 140/179] 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 141/179] 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 142/179] 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 143/179] 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 144/179] 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 145/179] 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 146/179] 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 147/179] 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 148/179] 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 149/179] 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 150/179] 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 151/179] 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 152/179] 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 153/179] 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 154/179] 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 155/179] 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 156/179] 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 157/179] 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 158/179] 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 159/179] 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 160/179] 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 161/179] 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 162/179] 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 163/179] 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 164/179] 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 165/179] 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 166/179] 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 167/179] 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 168/179] 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 169/179] 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 170/179] 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 171/179] 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 172/179] 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 173/179] 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 174/179] 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 175/179] 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 176/179] 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 177/179] 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 178/179] 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 179/179] 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": {