From 8664f86d84972c1ee7c1c768f10f82b493d3ca62 Mon Sep 17 00:00:00 2001 From: Nicholas Moschopoulos Date: Tue, 22 Jul 2014 13:17:04 -0700 Subject: [PATCH 001/669] added small fix to prevent server from crashing on empty this.state --- lib/Receiver.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Receiver.js b/lib/Receiver.js index 004cd32c1..a9d219f5e 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -219,6 +219,7 @@ Receiver.prototype.processPacket = function (data) { */ Receiver.prototype.endPacket = function() { + if (this.dead) return; if (!this.state.fragmentedOperation) this.unfragmentedBufferPool.reset(true); else if (this.state.lastFragment) this.fragmentedBufferPool.reset(false); this.expectOffset = 0; From 34bada9b170db234656180da18cc49765f32092e Mon Sep 17 00:00:00 2001 From: Jonathan Gros-Dubois Date: Sun, 30 Aug 2015 01:01:45 +1000 Subject: [PATCH 002/669] Status code must be 1006 if the socket is closed without having received a close control frame --- lib/WebSocket.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 3d9c3f10e..00bac53c7 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -918,9 +918,10 @@ function cleanupWebsocketResources(error) { this._closeTimer = null; if (emitClose) { - // If the connection was closed abnormally (with an error), - // then the close code must default to 1006. - if (error) { + // If the connection was closed abnormally (with an error), or if + // the close control frame was not received then the close code + // must default to 1006. + if (error || !this._closeReceived) { this._closeCode = 1006; } this.emit('close', this._closeCode || 1000, this._closeMessage || ''); From 6a08cba6440c8a0e67a91312ddbe87c56b05323e Mon Sep 17 00:00:00 2001 From: Peter Sorowka Date: Mon, 31 Aug 2015 23:41:48 +0200 Subject: [PATCH 003/669] Fixed error emission on receiver error --- lib/WebSocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 3d9c3f10e..63e3e9b1a 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -848,7 +848,7 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { self._receiver.onerror = function onerror(reason, errorCode) { // close the connection when the receiver reports a HyBi error code self.close(typeof errorCode !== 'undefined' ? errorCode : 1002, ''); - self.emit('error', reason, errorCode); + self.emit('error', new Error(reason)); }; // finalize the client From f804cdbfd56f79d087f385c7ed97fc58b8509a5f Mon Sep 17 00:00:00 2001 From: Peter Sorowka Date: Tue, 1 Sep 2015 00:11:37 +0200 Subject: [PATCH 004/669] Added type check before receiver error emission --- lib/WebSocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 63e3e9b1a..53e3bf649 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -848,7 +848,7 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { self._receiver.onerror = function onerror(reason, errorCode) { // close the connection when the receiver reports a HyBi error code self.close(typeof errorCode !== 'undefined' ? errorCode : 1002, ''); - self.emit('error', new Error(reason)); + self.emit('error', (reason instanceof Error) ? reason : (new Error(reason))); }; // finalize the client From e642ad65ee8a8653b59362680ff94411ddfacb6e Mon Sep 17 00:00:00 2001 From: Peter Sorowka Date: Tue, 1 Sep 2015 00:40:32 +0200 Subject: [PATCH 005/669] Added unit tests for receiver error emission --- test/WebSocket.test.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 0c5ea0d36..37df75ee1 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -49,6 +49,40 @@ describe('WebSocket', function() { done(); } }); + + it('should emit an error object when the receiver throws an error string', function(done) { + + var wss = new WebSocketServer({port: ++port}, function() { + + var ws = new WebSocket('ws://localhost:' + port); + + ws.on('open', function () { + ws._receiver.error('This is an error string', 1002); + }); + + ws.on('error', function (error) { + error.should.be.an.instanceof(Error); + done(); + }); + }); + }); + + it('should emit an error object when the receiver throws an error object', function(done) { + + var wss = new WebSocketServer({port: ++port}, function() { + + var ws = new WebSocket('ws://localhost:' + port); + + ws.on('open', function () { + ws._receiver.error(new Error('This is an error object'), 1002); + }); + + ws.on('error', function (error) { + error.should.be.an.instanceof(Error); + done(); + }); + }); + }); }); describe('options', function() { From 7d071120333b8fa8d76d91e08884f81d97a441d0 Mon Sep 17 00:00:00 2001 From: nkzawa Date: Sat, 3 Oct 2015 03:09:50 +0900 Subject: [PATCH 006/669] don't reuse buffer --- lib/Receiver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index ff4590d4f..999af0a3f 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -245,7 +245,7 @@ Receiver.prototype.processPacket = function (data) { Receiver.prototype.endPacket = function() { if (!this.state.fragmentedOperation) this.unfragmentedBufferPool.reset(true); - else if (this.state.lastFragment) this.fragmentedBufferPool.reset(false); + else if (this.state.lastFragment) this.fragmentedBufferPool.reset(true); this.expectOffset = 0; this.expectBuffer = null; this.expectHandler = null; From 44d9e1cacf0978c5ad35ff2644de38507648d1f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Piquemal?= Date: Mon, 26 Oct 2015 12:46:50 +0200 Subject: [PATCH 007/669] Clean event handlers on WebSocketServer's internal http server when closing --- lib/WebSocketServer.js | 20 ++++++++++++++------ test/WebSocketServer.test.js | 12 ++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 08d03e78c..737a73a88 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -72,13 +72,15 @@ function WebSocketServer(options, callback) { this._server._webSocketPaths[options.value.path] = 1; } } - if (this._server) this._server.once('listening', function() { self.emit('listening'); }); + if (this._server) { + this._onceServerListening = function() { self.emit('listening'); }; + this._server.once('listening', this._onceServerListening); + } if (typeof this._server != 'undefined') { - this._server.on('error', function(error) { - self.emit('error', error) - }); - this._server.on('upgrade', function(req, socket, upgradeHead) { + this._onServerError = function(error) { self.emit('error', error) }; + this._server.on('error', this._onServerError); + this._onServerUpgrade = function(req, socket, upgradeHead) { //copy upgradeHead to avoid retention of large slab buffers used in node core var head = new Buffer(upgradeHead.length); upgradeHead.copy(head); @@ -87,7 +89,8 @@ function WebSocketServer(options, callback) { self.emit('connection'+req.url, client); self.emit('connection', client); }); - }); + }; + this._server.on('upgrade', this._onServerUpgrade); } this.options = options.value; @@ -134,6 +137,11 @@ WebSocketServer.prototype.close = function() { } } finally { + if (this._server) { + this._server.removeListener('listening', this._onceServerListening); + this._server.removeListener('error', this._onServerError); + this._server.removeListener('upgrade', this._onServerUpgrade); + } delete this._server; } if (error) throw error; diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 210a1ad12..03a6587f4 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -232,6 +232,18 @@ describe('WebSocketServer', function() { }); }); + it('cleans event handlers on precreated server', function(done) { + var srv = http.createServer(); + srv.listen(++port, function() { + var wss = new WebSocketServer({server: srv}); + wss.close(); + srv.emit('upgrade'); + srv.on('error', function() {}); + srv.emit('error'); + done() + }); + }); + it('cleans up websocket data on a precreated server', function(done) { var srv = http.createServer(); srv.listen(++port, function () { From 718ac2232a9069f209eb2280a514882c3c7731eb Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Sun, 1 Nov 2015 23:45:26 +0100 Subject: [PATCH 008/669] Release memory used by PerMessageDeflate extension --- lib/PerMessageDeflate.js | 21 +++++++++++++++++++++ lib/WebSocket.js | 6 ++++++ 2 files changed, 27 insertions(+) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index b1fd743a5..0060c5f74 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -68,6 +68,23 @@ PerMessageDeflate.prototype.accept = function(paramsList) { return params; }; +/** + * Releases all resources used by the extension + * + * @api public + */ + +PerMessageDeflate.prototype.cleanup = function() { + if (this._inflate) { + this._inflate.close(); + this._inflate = null; + } + if (this._deflate) { + this._deflate.close(); + this._deflate = null; + } +}; + /** * Accept extension offer from client * @@ -231,9 +248,11 @@ PerMessageDeflate.prototype.decompress = function (data, fin, callback) { } function cleanup() { + if (!self._inflate) return; self._inflate.removeListener('error', onError); self._inflate.removeListener('data', onData); if (fin && self.params[endpoint + '_no_context_takeover']) { + self._inflate.close(); self._inflate = null; } } @@ -281,9 +300,11 @@ PerMessageDeflate.prototype.compress = function (data, fin, callback) { } function cleanup() { + if (!self._deflate) return; self._deflate.removeListener('error', onError); self._deflate.removeListener('data', onData); if (fin && self.params[endpoint + '_no_context_takeover']) { + self._deflate.close(); self._deflate = null; } } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index b1b0c7c6c..27144c06d 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -952,6 +952,12 @@ function cleanupWebsocketResources(error) { this._receiver = null; } + if (this.extensions[PerMessageDeflate.extensionName]) { + this.extensions[PerMessageDeflate.extensionName].cleanup(); + } + + this.extensions = null; + this.removeAllListeners(); this.on('error', function onerror() {}); // catch all errors after this delete this._queue; From c4a427591e37d9ed21168d3cf6ff57b347441b88 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 7 Nov 2015 02:07:54 +0900 Subject: [PATCH 009/669] Callback for close() This will make close() compatible with Promise libraries by providing a callback when its finished. Note that this version only works correctly for the cases where the http server was **not** internally created. If you like, you can support the other case as well (would need to pass a callback to `this._closeServer()`). --- lib/WebSocketServer.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 08d03e78c..ba0e4c050 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -107,7 +107,7 @@ util.inherits(WebSocketServer, events.EventEmitter); * @api public */ -WebSocketServer.prototype.close = function() { +WebSocketServer.prototype.close = function(callback) { // terminate all associated clients var error = null; try { @@ -136,7 +136,10 @@ WebSocketServer.prototype.close = function() { finally { delete this._server; } - if (error) throw error; + if(callback) + callback(error); + else if(error) + throw error; } /** From 74f567e0221a14071bb40eb1902e946524a11862 Mon Sep 17 00:00:00 2001 From: Arnout Kazemier Date: Sun, 29 Nov 2015 20:24:03 +0100 Subject: [PATCH 010/669] [dist] 0.8.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 67ffb1aa5..47c76158e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "simple to use, blazing fast and thoroughly tested websocket client, server and console for node.js, up-to-date against RFC-6455", - "version": "0.8.0", + "version": "0.8.1", "license": "MIT", "keywords": [ "Hixie", From 6ac0a01d537ce76fe2b391c6c5d878c516366e6a Mon Sep 17 00:00:00 2001 From: playerone Date: Tue, 1 Dec 2015 19:08:14 +0900 Subject: [PATCH 011/669] fix for issue 603 --- lib/browser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/browser.js b/lib/browser.js index 8d3a755cd..fc10c8d8c 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -26,7 +26,7 @@ module.exports = WebSocket ? ws : null; * * @param {String} uri * @param {Array} protocols (optional) - * @param {Object) opts (optional) + * @param {Object} opts (optional) * @api public */ From 0e2a7b14c8aa66fbd314dbacf13789351cfc2f9f Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Tue, 1 Dec 2015 22:42:40 +0100 Subject: [PATCH 012/669] Fix an error on Node v0.8 (no 'close' method for InflateRaw/DeflateRaw) --- lib/PerMessageDeflate.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 0060c5f74..20e05ec1a 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -76,11 +76,11 @@ PerMessageDeflate.prototype.accept = function(paramsList) { PerMessageDeflate.prototype.cleanup = function() { if (this._inflate) { - this._inflate.close(); + if (this._inflate.close) this._inflate.close(); this._inflate = null; } if (this._deflate) { - this._deflate.close(); + if (this._deflate.close) this._deflate.close(); this._deflate = null; } }; @@ -252,7 +252,7 @@ PerMessageDeflate.prototype.decompress = function (data, fin, callback) { self._inflate.removeListener('error', onError); self._inflate.removeListener('data', onData); if (fin && self.params[endpoint + '_no_context_takeover']) { - self._inflate.close(); + if (self._inflate.close) self._inflate.close(); self._inflate = null; } } @@ -304,7 +304,7 @@ PerMessageDeflate.prototype.compress = function (data, fin, callback) { self._deflate.removeListener('error', onError); self._deflate.removeListener('data', onData); if (fin && self.params[endpoint + '_no_context_takeover']) { - self._deflate.close(); + if (self._deflate.close) self._deflate.close(); self._deflate = null; } } From 4bf5fb8233ac4aacc358639f53644ed26cc8c320 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Wed, 16 Dec 2015 08:24:46 +0100 Subject: [PATCH 013/669] Fix "zlib binding closed" errors --- lib/PerMessageDeflate.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 20e05ec1a..5324bd8e6 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -76,12 +76,20 @@ PerMessageDeflate.prototype.accept = function(paramsList) { PerMessageDeflate.prototype.cleanup = function() { if (this._inflate) { - if (this._inflate.close) this._inflate.close(); - this._inflate = null; + if (this._inflate.writeInProgress) { + this._inflate.pendingClose = true; + } else { + if (this._inflate.close) this._inflate.close(); + this._inflate = null; + } } if (this._deflate) { - if (this._deflate.close) this._deflate.close(); - this._deflate = null; + if (this._deflate.writeInProgress) { + this._deflate.pendingClose = true; + } else { + if (this._deflate.close) this._deflate.close(); + this._deflate = null; + } } }; @@ -224,6 +232,7 @@ PerMessageDeflate.prototype.decompress = function (data, fin, callback) { windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS }); } + this._inflate.writeInProgress = true; var self = this; var buffers = []; @@ -251,7 +260,8 @@ PerMessageDeflate.prototype.decompress = function (data, fin, callback) { if (!self._inflate) return; self._inflate.removeListener('error', onError); self._inflate.removeListener('data', onData); - if (fin && self.params[endpoint + '_no_context_takeover']) { + self._inflate.writeInProgress = false; + if ((fin && self.params[endpoint + '_no_context_takeover']) || self._inflate.pendingClose) { if (self._inflate.close) self._inflate.close(); self._inflate = null; } @@ -275,6 +285,7 @@ PerMessageDeflate.prototype.compress = function (data, fin, callback) { memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL }); } + this._deflate.writeInProgress = true; var self = this; var buffers = []; @@ -303,7 +314,8 @@ PerMessageDeflate.prototype.compress = function (data, fin, callback) { if (!self._deflate) return; self._deflate.removeListener('error', onError); self._deflate.removeListener('data', onData); - if (fin && self.params[endpoint + '_no_context_takeover']) { + self._deflate.writeInProgress = false; + if ((fin && self.params[endpoint + '_no_context_takeover']) || self._deflate.pendingClose) { if (self._deflate.close) self._deflate.close(); self._deflate = null; } From ca19f087875ca70e92b4f847500c86f44c0ff283 Mon Sep 17 00:00:00 2001 From: SEAPUNK Date: Fri, 18 Dec 2015 11:44:27 -0600 Subject: [PATCH 014/669] Sender: Don't reuse masking key --- lib/Sender.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Sender.js b/lib/Sender.js index 2f8f7c46c..8a781aa47 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -189,7 +189,7 @@ Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, if (maskData) { outputBuffer[1] = secondByte | 0x80; - var mask = this._randomMask || (this._randomMask = getRandomMask()); + var mask = getRandomMask(); outputBuffer[dataOffset - 4] = mask[0]; outputBuffer[dataOffset - 3] = mask[1]; outputBuffer[dataOffset - 2] = mask[2]; From 49b11093e9a009e5305dcde7003d3a896b2811dc Mon Sep 17 00:00:00 2001 From: Arnout Kazemier Date: Mon, 21 Dec 2015 17:37:02 +0100 Subject: [PATCH 015/669] [major] No longer use the binary addons as optional dependency, nuked completely --- package.json | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 47c76158e..2391fca02 100644 --- a/package.json +++ b/package.json @@ -24,17 +24,15 @@ "options": ">=0.0.5", "ultron": "1.0.x" }, - "optionalDependencies": { - "bufferutil": "1.2.x", - "utf-8-validate": "1.2.x" - }, "devDependencies": { "ansi": "0.3.x", "benchmark": "0.3.x", + "bufferutil": "1.2.x", "expect.js": "0.3.x", "mocha": "2.2.x", "should": "4.3.x", - "tinycolor": "0.0.x" + "tinycolor": "0.0.x", + "utf-8-validate": "1.2.x" }, "browser": "./lib/browser.js", "component": { From e54d45fbab3b19c2940e9057ce1e7b8f105873e0 Mon Sep 17 00:00:00 2001 From: Arnout Kazemier Date: Mon, 21 Dec 2015 17:39:40 +0100 Subject: [PATCH 016/669] [breaking] Remove browser code. Out of scope for this module. --- lib/browser.js | 43 ------------------------------------------- package.json | 6 ------ 2 files changed, 49 deletions(-) delete mode 100644 lib/browser.js diff --git a/lib/browser.js b/lib/browser.js deleted file mode 100644 index fc10c8d8c..000000000 --- a/lib/browser.js +++ /dev/null @@ -1,43 +0,0 @@ - -/** - * Module dependencies. - */ - -var global = (function() { return this; })(); - -/** - * WebSocket constructor. - */ - -var WebSocket = global.WebSocket || global.MozWebSocket; - -/** - * Module exports. - */ - -module.exports = WebSocket ? ws : null; - -/** - * WebSocket constructor. - * - * The third `opts` options object gets ignored in web browsers, since it's - * non-standard, and throws a TypeError if passed to the constructor. - * See: https://github.com/einaros/ws/issues/227 - * - * @param {String} uri - * @param {Array} protocols (optional) - * @param {Object} opts (optional) - * @api public - */ - -function ws(uri, protocols, opts) { - var instance; - if (protocols) { - instance = new WebSocket(uri, protocols); - } else { - instance = new WebSocket(uri); - } - return instance; -} - -if (WebSocket) ws.prototype = WebSocket.prototype; diff --git a/package.json b/package.json index 2391fca02..4077be414 100644 --- a/package.json +++ b/package.json @@ -34,11 +34,5 @@ "tinycolor": "0.0.x", "utf-8-validate": "1.2.x" }, - "browser": "./lib/browser.js", - "component": { - "scripts": { - "ws/index.js": "./lib/browser.js" - } - }, "gypfile": true } From 0d61b39efe221637c39460a58dc74281deffbc92 Mon Sep 17 00:00:00 2001 From: Arnout Kazemier Date: Mon, 21 Dec 2015 17:59:19 +0100 Subject: [PATCH 017/669] [pkg] Bump dependencies --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4077be414..66fc50cb2 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,8 @@ "benchmark": "0.3.x", "bufferutil": "1.2.x", "expect.js": "0.3.x", - "mocha": "2.2.x", - "should": "4.3.x", + "mocha": "2.3.x", + "should": "8.0.x", "tinycolor": "0.0.x", "utf-8-validate": "1.2.x" }, From bcecb42fe00cbac02561c0a82380ea630a18eeeb Mon Sep 17 00:00:00 2001 From: Arnout Kazemier Date: Mon, 21 Dec 2015 18:02:58 +0100 Subject: [PATCH 018/669] [travis] Remove deprecated node.js version 0.12 is the last to be supported --- .travis.yml | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index ccb864f05..5002b4984 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,9 @@ language: node_js sudo: false -npm_args: --ws:native node_js: + - "5" - "4" - - "3" - - "2" - - "1" - "0.12" - - "0.11" - - "0.10" - - "0.9" - - "0.8" addons: apt: sources: @@ -20,10 +13,3 @@ addons: - g++-4.9 before_install: - export CC="gcc-4.9" CXX="g++-4.9" - - "if [[ $(node --version) == v0.8.* ]]; then npm install -g npm@2.1.18; fi" -matrix: - fast_finish: true - allow_failures: - - node_js: "0.11" - - node_js: "0.9" - - node_js: "0.8" From 62b7abb2e92e441934c49101ddc696b08d87097e Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sun, 27 Dec 2015 11:59:32 +0100 Subject: [PATCH 019/669] fix: shrinkStrategy should always return an integer `Buffer` inherits from `Uint8Arry` so the constructor needs to be called with integers and mustn't be called with floats. This patch ensures that the result of the the calculation in the `shrinkStrategy` in `Receiver` returns an integer. The reason why this has not been a problem up to now is that a there is a [V8 Bug](https://code.google.com/p/v8/issues/detail?id=4552) that the [spec](http://tc39.github.io/ecma262/#sec-typedarray-length) is not adhered in this case. This is a blocking issue for [Karma](https://github.com/karma-runner/karma/issues/1768) as the upgrade to [core-js@2.0.0](https://github.com/zloirock/core-js) now enforces this error. --- lib/Receiver.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 999af0a3f..b3183bfb4 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -26,7 +26,7 @@ function Receiver (extensions) { return db.used + length; }, function(db) { return fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 ? - (fragmentedPoolPrevUsed + db.used) / 2 : + Math.ceil((fragmentedPoolPrevUsed + db.used) / 2) : db.used; }); @@ -36,7 +36,7 @@ function Receiver (extensions) { return db.used + length; }, function(db) { return unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 ? - (unfragmentedPoolPrevUsed + db.used) / 2 : + Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) : db.used; }); From f0b274146ffcd0df87b143e35e062b675c245f47 Mon Sep 17 00:00:00 2001 From: Arnout Kazemier Date: Wed, 30 Dec 2015 14:11:41 +0100 Subject: [PATCH 020/669] [doc] Document the new optional dependencies --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 9647d080c..9be2e51d9 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,23 @@ for the full reports. npm install --save ws ``` +### Opt-in for performance + +There are 2 optional modules that can be installed along side with the `ws` +module. These modules are binary addons which improve certain operations, but as +they are binary addons they require compilation which can fail if no c++ +compiler is installed on the host system. + +- `npm install --save bufferutil`: Improves internal buffer operations which + allows for faster processing of masked WebSocket frames and general buffer + operations. +- `npm install --save utf-8-validate`: The specification requires validation of + invalid UTF-8 chars, some of these validations could not be done in JavaScript + hence the need for a binary addon. In most cases you will already be + validating the input that you receive for security purposes leading to double + validation. But if you want to be 100% spec conform and fast validation of UTF-8 + then this module is a must. + ### Sending and receiving text data ```js From 753937ff1ddc0938513267b4d6d5139a6ad41746 Mon Sep 17 00:00:00 2001 From: Arnout Kazemier Date: Wed, 30 Dec 2015 17:34:55 +0100 Subject: [PATCH 021/669] [dist] 1.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 66fc50cb2..3c89851f7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "simple to use, blazing fast and thoroughly tested websocket client, server and console for node.js, up-to-date against RFC-6455", - "version": "0.8.1", + "version": "1.0.0", "license": "MIT", "keywords": [ "Hixie", From dd8f613fb0f32665eb3189319e4409536eac5a17 Mon Sep 17 00:00:00 2001 From: Olise Olisedumbi Tamunodiepiriye Date: Thu, 31 Dec 2015 21:31:48 +0100 Subject: [PATCH 022/669] Add maxpayload limit to hybi clients Limit the amount of data that can be sent to a websocketserver from a hybi client. Raise a 1009 - message too big error. Tests included. --- lib/PerMessageDeflate.js | 16 ++++- lib/Receiver.js | 114 ++++++++++++++++++++++++++++++++--- lib/WebSocket.js | 7 ++- lib/WebSocketServer.js | 9 ++- test/Receiver.test.js | 76 +++++++++++++++++++++++ test/WebSocketServer.test.js | 43 +++++++++++++ 6 files changed, 247 insertions(+), 18 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 5324bd8e6..00a6ea62a 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -11,7 +11,7 @@ PerMessageDeflate.extensionName = 'permessage-deflate'; * Per-message Compression Extensions implementation */ -function PerMessageDeflate(options, isServer) { +function PerMessageDeflate(options, isServer,maxPayload) { if (this instanceof PerMessageDeflate === false) { throw new TypeError("Classes can't be function-called"); } @@ -21,6 +21,7 @@ function PerMessageDeflate(options, isServer) { this._inflate = null; this._deflate = null; this.params = null; + this._maxPayload = maxPayload || 0; } /** @@ -236,6 +237,7 @@ PerMessageDeflate.prototype.decompress = function (data, fin, callback) { var self = this; var buffers = []; + var cumulativeBufferLength=0; this._inflate.on('error', onError).on('data', onData); this._inflate.write(data); @@ -253,7 +255,17 @@ PerMessageDeflate.prototype.decompress = function (data, fin, callback) { } function onData(data) { - buffers.push(data); + if(self._maxPayload!==undefined && self._maxPayload!==null && self._maxPayload>0){ + cumulativeBufferLength+=data.length; + if(cumulativeBufferLength>self._maxPayload){ + buffers=[]; + cleanup(); + var err={type:1009}; + callback(err); + return; + } + } + buffers.push(data); } function cleanup() { diff --git a/lib/Receiver.js b/lib/Receiver.js index b3183bfb4..aa020424e 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -15,10 +15,15 @@ var util = require('util') * HyBi Receiver implementation */ -function Receiver (extensions) { +function Receiver (extensions,maxPayload) { if (this instanceof Receiver === false) { throw new TypeError("Classes can't be function-called"); } + if(typeof extensions==='number'){ + maxPayload=extensions; + extensions={}; + } + // memory pool for fragmented messages var fragmentedPoolPrevUsed = -1; @@ -39,8 +44,9 @@ function Receiver (extensions) { Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) : db.used; }); - this.extensions = extensions || {}; + this.maxPayload = maxPayload || 0; + this.currentPayloadLength = 0; this.state = { activeFragmentedOperation: null, lastFragment: false, @@ -54,6 +60,7 @@ function Receiver (extensions) { this.expectBuffer = null; this.expectHandler = null; this.currentMessage = []; + this.currentMessageLength = 0; this.messageHandlers = []; this.expectHeader(2, this.processPacket); this.dead = false; @@ -253,6 +260,7 @@ Receiver.prototype.endPacket = function() { // end current fragmented operation this.state.activeFragmentedOperation = null; } + this.currentPayloadLength = 0; this.state.lastFragment = false; this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0; this.state.masked = false; @@ -281,7 +289,9 @@ Receiver.prototype.reset = function() { this.expectHandler = null; this.overflow = []; this.currentMessage = []; + this.currentMessageLength = 0; this.messageHandlers = []; + this.currentPayloadLength = 0; }; /** @@ -365,6 +375,27 @@ Receiver.prototype.applyExtensions = function(messageBuffer, fin, compressed, ca } }; +/** +* Checks payload size, disconnects socket when it exceeds maxPayload +* +* @api private +*/ +Receiver.prototype.maxPayloadExceeded = function(length) { + if (this.maxPayload=== undefined || this.maxPayload === null || this.maxPayload < 1) { + return false; + } + var fullLength = this.currentPayloadLength + length; + if (fullLength < this.maxPayload) { + this.currentPayloadLength = fullLength; + return false; + } + this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); + this.messageBuffer=[]; + this.cleanup(); + + return true; +}; + /** * Buffer utilities */ @@ -425,11 +456,20 @@ var opcodes = { // decode length var firstLength = data[1] & 0x7f; if (firstLength < 126) { + if (self.maxPayloadExceeded(firstLength)){ + self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); + return; + } opcodes['1'].getData.call(self, firstLength); } else if (firstLength == 126) { self.expectHeader(2, function(data) { - opcodes['1'].getData.call(self, readUInt16BE.call(data, 0)); + var length = readUInt16BE.call(data, 0); + if (self.maxPayloadExceeded(length)){ + self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); + return; + } + opcodes['1'].getData.call(self, length); }); } else if (firstLength == 127) { @@ -438,6 +478,11 @@ var opcodes = { self.error('packets with length spanning more than 32 bit is currently not supported', 1008); return; } + var length = readUInt32BE.call(data, 4); + if (self.maxPayloadExceeded(length)){ + self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); + return; + } opcodes['1'].getData.call(self, readUInt32BE.call(data, 4)); }); } @@ -464,12 +509,29 @@ var opcodes = { var state = clone(this.state); this.messageHandlers.push(function(callback) { self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { - if (err) return self.error(err.message, 1007); - if (buffer != null) self.currentMessage.push(buffer); - + if (err) { + if(err.type===1009){ + return self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); + } + return self.error(err.message, 1007); + } + if (buffer != null) { + if( self.maxPayload==0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ + self.currentMessage.push(buffer); + } + else{ + self.currentMessage=null; + self.currentMessage = []; + self.currentMessageLength = 0; + self.error(new Error('Maximum payload exceeded. maxPayload: '+self.maxPayload), 1009); + return; + } + self.currentMessageLength += buffer.length; + } if (state.lastFragment) { var messageBuffer = self.concatBuffers(self.currentMessage); self.currentMessage = []; + self.currentMessageLength = 0; if (!Validation.isValidUTF8(messageBuffer)) { self.error('invalid utf8 sequence', 1007); return; @@ -490,11 +552,20 @@ var opcodes = { // decode length var firstLength = data[1] & 0x7f; if (firstLength < 126) { + if (self.maxPayloadExceeded(firstLength)){ + self.error('Max payload exceeded in compressed text message. Aborting...', 1009); + return; + } opcodes['2'].getData.call(self, firstLength); } else if (firstLength == 126) { self.expectHeader(2, function(data) { - opcodes['2'].getData.call(self, readUInt16BE.call(data, 0)); + var length = readUInt16BE.call(data, 0); + if (self.maxPayloadExceeded(length)){ + self.error('Max payload exceeded in compressed text message. Aborting...', 1009); + return; + } + opcodes['2'].getData.call(self, length); }); } else if (firstLength == 127) { @@ -503,7 +574,12 @@ var opcodes = { self.error('packets with length spanning more than 32 bit is currently not supported', 1008); return; } - opcodes['2'].getData.call(self, readUInt32BE.call(data, 4, true)); + var length = readUInt32BE.call(data, 4, true); + if (self.maxPayloadExceeded(length)){ + self.error('Max payload exceeded in compressed text message. Aborting...', 1009); + return; + } + opcodes['2'].getData.call(self, length); }); } }, @@ -529,11 +605,29 @@ var opcodes = { var state = clone(this.state); this.messageHandlers.push(function(callback) { self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { - if (err) return self.error(err.message, 1007); - if (buffer != null) self.currentMessage.push(buffer); + if (err) { + if(err.type===1009){ + return self.error('Max payload exceeded in compressed binary message. Aborting...', 1009); + } + return self.error(err.message, 1007); + } + if (buffer != null) { + if( self.maxPayload==0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ + self.currentMessage.push(buffer); + } + else{ + self.currentMessage=null; + self.currentMessage = []; + self.currentMessageLength = 0; + self.error(new Error('Maximum payload exceeded'), 1009); + return; + } + self.currentMessageLength += buffer.length; + } if (state.lastFragment) { var messageBuffer = self.concatBuffers(self.currentMessage); self.currentMessage = []; + self.currentMessageLength = 0; self.onbinary(messageBuffer, {masked: state.masked, buffer: messageBuffer}); } callback(); diff --git a/lib/WebSocket.js b/lib/WebSocket.js index b80cc78af..d6a981001 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -523,7 +523,8 @@ function initAsServerClient(req, socket, upgradeHead, options) { options = new Options({ protocolVersion: protocolVersion, protocol: null, - extensions: {} + extensions: {}, + maxPayload: 0 }).merge(options); // expose state properties @@ -534,7 +535,7 @@ function initAsServerClient(req, socket, upgradeHead, options) { this.upgradeReq = req; this.readyState = WebSocket.CONNECTING; this._isServer = true; - + this.maxPayload = options.value.maxPayload; // establish connection if (options.value.protocolVersion === 'hixie-76') { establishConnection.call(this, ReceiverHixie, SenderHixie, socket, upgradeHead); @@ -770,7 +771,7 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { socket.setTimeout(0); socket.setNoDelay(true); - this._receiver = new ReceiverClass(this.extensions); + this._receiver = new ReceiverClass(this.extensions,this.maxPayload); this._socket = socket; // socket cleanup handlers diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index ba0e4c050..7ab5125d9 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -36,7 +36,8 @@ function WebSocketServer(options, callback) { noServer: false, disableHixie: false, clientTracking: true, - perMessageDeflate: true + perMessageDeflate: true, + maxPayload: null }).merge(options); if (!options.isDefinedAndNonNull('port') && !options.isDefinedAndNonNull('server') && !options.value.noServer) { @@ -256,7 +257,8 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { var client = new WebSocket([req, socket, upgradeHead], { protocolVersion: version, protocol: protocol, - extensions: extensions + extensions: extensions, + maxPayload: self.options.maxPayload }); if (self.options.clientTracking) { @@ -489,8 +491,9 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { function acceptExtensions(offer) { var extensions = {}; var options = this.options.perMessageDeflate; + var maxPayload = this.options.maxPayload; if (options && offer[PerMessageDeflate.extensionName]) { - var perMessageDeflate = new PerMessageDeflate(options !== true ? options : {}, true); + var perMessageDeflate = new PerMessageDeflate(options !== true ? options : {}, true, maxPayload); perMessageDeflate.accept(offer[PerMessageDeflate.extensionName]); extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 30fd3b803..a5ebab370 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -306,6 +306,82 @@ describe('Receiver', function() { }); }); }); + it('will raise an error on a 200kb long masked binary message when maxpayload is 20kb', function() { + var p = new Receiver(20480); + var length = 200 * 1024; + var message = new Buffer(length); + for (var i = 0; i < length; ++i) message[i] = i % 256; + var originalMessage = getHexStringFromBuffer(message); + var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); + + var gotError = false; + p.error = function(reason,code) { + gotError = true; + assert.equal(code, 1009); + }; + + p.add(getBufferFromHexString(packet)); + gotError.should.be.ok; + }); + it('will raise an error on a 200kb long unmasked binary message when maxpayload is 20kb', function() { + var p = new Receiver(20480); + var length = 200 * 1024; + var message = new Buffer(length); + for (var i = 0; i < length; ++i) message[i] = i % 256; + var originalMessage = getHexStringFromBuffer(message); + var packet = '82 ' + getHybiLengthAsHexString(length, false) + ' ' + getHexStringFromBuffer(message); + + var gotError = false; + p.error = function(reason,code) { + gotError = true; + assert.equal(code, 1009); + }; + + p.add(getBufferFromHexString(packet)); + gotError.should.be.ok; + }); + it('will raise an error on a compressed message that exceeds maxpayload of 3bytes', function(done) { + var perMessageDeflate = new PerMessageDeflate({},false,3); + perMessageDeflate.accept([{}]); + + var p = new Receiver({ 'permessage-deflate': perMessageDeflate },3); + var buf = new Buffer('Hellooooooooooooooooooooooooooooooooooooooo'); + + p.onerror = function(reason,code) { + assert.equal(code, 1009); + done(); + }; + + perMessageDeflate.compress(buf, true, function(err, compressed) { + if (err) return done(err); + p.add(new Buffer([0xc1, compressed.length])); + p.add(compressed); + }); + }); + it('will raise an error on a compressed fragment that exceeds maxpayload of 2 bytes', function(done) { + var perMessageDeflate = new PerMessageDeflate({},false,2); + perMessageDeflate.accept([{}]); + + var p = new Receiver({ 'permessage-deflate': perMessageDeflate },2); + var buf1 = new Buffer('fooooooooooooooooooooooooooooooooooooooooooooooooooooooo'); + var buf2 = new Buffer('baaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr'); + + p.onerror = function(reason,code) { + assert.equal(code, 1009); + done(); + }; + + perMessageDeflate.compress(buf1, false, function(err, compressed1) { + if (err) return done(err); + p.add(new Buffer([0x41, compressed1.length])); + p.add(compressed1); + + perMessageDeflate.compress(buf2, true, function(err, compressed2) { + p.add(new Buffer([0x80, compressed2.length])); + p.add(compressed2); + }); + }); + }); it('can cleanup during consuming data', function(done) { var perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 210a1ad12..5c28c8275 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -315,6 +315,49 @@ describe('WebSocketServer', function() { }); }); + describe('#maxpayload #hybiOnly', function() { + it('maxpayload is passed on to clients,', function(done) { + var _maxPayload = 20480; + var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { + wss.clients.length.should.eql(0); + var ws = new WebSocket('ws://localhost:' + port); + }); + wss.on('connection', function(client) { + wss.clients.length.should.eql(1); + wss.clients[0].maxPayload.should.eql(_maxPayload); + wss.close(); + done(); + }); + }); + it('maxpayload is passed on to hybi receivers', function(done) { + var _maxPayload = 20480; + var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { + wss.clients.length.should.eql(0); + var ws = new WebSocket('ws://localhost:' + port); + }); + wss.on('connection', function(client) { + wss.clients.length.should.eql(1); + wss.clients[0]._receiver.maxPayload.should.eql(_maxPayload); + wss.close(); + done(); + }); + }); + it('maxpayload is passed on to permessage-deflate', function(done) { + var PerMessageDeflate = require('../lib/PerMessageDeflate'); + var _maxPayload = 20480; + var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { + wss.clients.length.should.eql(0); + var ws = new WebSocket('ws://localhost:' + port); + }); + wss.on('connection', function(client) { + wss.clients.length.should.eql(1); + wss.clients[0]._receiver.extensions[PerMessageDeflate.extensionName]._maxPayload.should.eql(_maxPayload); + wss.close(); + done(); + }); + }); + }); + describe('#handleUpgrade', function() { it('can be used for a pre-existing server', function (done) { var srv = http.createServer(); From 29293ed11b679e0366fa0f6bb9310b330dafd795 Mon Sep 17 00:00:00 2001 From: Arnout Kazemier Date: Mon, 4 Jan 2016 13:34:58 +0100 Subject: [PATCH 023/669] [fix] Prevent allocation of memory through ping frames --- lib/Sender.js | 8 ++++++++ lib/WebSocket.js | 2 +- test/WebSocket.test.js | 19 ++++++++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 2f8f7c46c..d34061e07 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -155,6 +155,14 @@ Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, if (data && (typeof data.byteLength !== 'undefined' || typeof data.buffer !== 'undefined')) { data = getArrayBuffer(data); } else { + // + // If people want to send a number, this would allocate the number in + // bytes as memory size instead of storing the number as buffer value. So + // we need to transform it to string in order to prevent possible + // vulnerabilities / memory attacks. + // + if (typeof data === 'number') data = data.toString(); + data = new Buffer(data); } } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index b80cc78af..4e06c8071 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -918,7 +918,7 @@ function cleanupWebsocketResources(error) { this._closeTimer = null; if (emitClose) { - // If the connection was closed abnormally (with an error), or if + // If the connection was closed abnormally (with an error), or if // the close control frame was not received then the close code // must default to 1006. if (error || !this._closeReceived) { diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 038c7f27c..5b2f4ec93 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -40,7 +40,7 @@ describe('WebSocket', function() { done(); } }); - + it('should return a new instance if called without new', function(done) { var ws = WebSocket('ws://localhost:' + port); ws.should.be.an.instanceOf(WebSocket); @@ -585,6 +585,23 @@ describe('WebSocket', function() { }); }); + it('can send safely receive numbers as ping payload', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + + ws.on('open', function() { + ws.ping(200); + }); + + srv.on('ping', function(message) { + assert.equal('200', message); + srv.close(); + ws.terminate(); + done(); + }); + }); + }); + it('with encoded message is successfully transmitted to the server', function(done) { server.createServer(++port, function(srv) { var ws = new WebSocket('ws://localhost:' + port); From 40a9d686288b5d0be13f2bf2f3f5da07afc8cda2 Mon Sep 17 00:00:00 2001 From: Arnout Kazemier Date: Mon, 4 Jan 2016 13:35:39 +0100 Subject: [PATCH 024/669] [dist] 1.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3c89851f7..9e974c5b9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "simple to use, blazing fast and thoroughly tested websocket client, server and console for node.js, up-to-date against RFC-6455", - "version": "1.0.0", + "version": "1.0.1", "license": "MIT", "keywords": [ "Hixie", From 4ffed2ae284171ff6c273727f5684c5daa592380 Mon Sep 17 00:00:00 2001 From: Arnout Kazemier Date: Mon, 4 Jan 2016 14:19:27 +0100 Subject: [PATCH 025/669] [security] Added a missing Security.md --- SECURITY.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..fd8e07bc5 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,33 @@ +# Security Guidelines + +Please contact us directly at **security@3rd-Eden.com** for any bug that might +impact the security of this project. Please prefix the subject of your email +with `[security]` in lowercase and square brackets. Our email filters will +automatically prevent these messages from being moved to our spam box. + +You will receive an acknowledgement of your report within **24 hours**. + +All emails that do not include security vulnerabilities will be removed and +blocked instantly. + +## Exceptions + +If you do not receive an acknowledgement within the said time frame please give +us the benefit of the doubt as it's possible that we haven't seen it yet. In +this case please send us a message **without details** using one of the +following methods: + +- Contact the lead developers of this project on their personal e-mails. You + can find the e-mails in the git logs, for example using the following command: + `git --no-pager show -s --format='%an <%ae>' ` where `` is the + SHA1 of their latest commit in the project. +- Create a GitHub issue stating contact details and the severity of the issue. + +Once we have acknowledged receipt of your report and confirmed the bug +ourselves we will work with you to fix the vulnerability and publicly acknowledge +your responsible disclosure, if you wish. In addition to that we will report +all vulnerabilities to the [Node Security Project](https://nodesecurity.io/). + +## History + +04 Jan 2016: [Buffer vulnerablity](https://github.com/websockets/ws/releases/tag/1.0.1) From 724bd023ca0c7754cadf54b7d8a26cde9cb7252d Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Tue, 5 Jan 2016 14:13:59 +0800 Subject: [PATCH 026/669] remove the browserify users section --- README.md | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 9be2e51d9..ec05d65f1 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ compiler is installed on the host system. - `npm install --save bufferutil`: Improves internal buffer operations which allows for faster processing of masked WebSocket frames and general buffer - operations. + operations. - `npm install --save utf-8-validate`: The specification requires validation of invalid UTF-8 chars, some of these validations could not be done in JavaScript hence the need for a binary addon. In most cases you will already be @@ -110,7 +110,7 @@ wss.on('connection', function connection(ws) { var location = url.parse(ws.upgradeReq.url, true); // you might use location.query.access_token to authenticate or share sessions // or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312) - + ws.on('message', function incoming(message) { console.log('received: %s', message); }); @@ -161,7 +161,7 @@ catch (e) { /* handle error */ } ```js var WebSocket = require('ws'); var ws = new WebSocket('ws://echo.websocket.org/', { - protocolVersion: 8, + protocolVersion: 8, origin: 'http://websocket.org' }); @@ -183,13 +183,6 @@ ws.on('message', function message(data, flags) { }); ``` -### Browserify users -When including ws via a browserify bundle, ws returns global.WebSocket which has slightly different API. -You should use the standard WebSockets API instead. - -https://developer.mozilla.org/en-US/docs/WebSockets/Writing_WebSocket_client_applications#Availability_of_WebSockets - - ### Other examples For a full example with a browser client communicating with a ws server, see the From de008097d42bf6007f6121f7585f426a4e5aee86 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Tue, 5 Jan 2016 14:44:47 +0800 Subject: [PATCH 027/669] Add make coverage command to get test coverage info --- .gitignore | 1 + Makefile | 19 ++++++++++++------- package.json | 1 + 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index fd77e2902..182c7b910 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ node_modules .*.swp .lock-* build +coverage builderror.log diff --git a/Makefile b/Makefile index 00f19fa01..94612c5ce 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,6 @@ ALL_TESTS = $(shell find test/ -name '*.test.js') ALL_INTEGRATION = $(shell find test/ -name '*.integration.js') -all: - node-gyp configure build - -clean: - node-gyp clean - run-tests: @./node_modules/.bin/mocha \ -t 5000 \ @@ -21,12 +15,23 @@ run-integrationtests: $(TESTFLAGS) \ $(TESTS) +run-coverage: + @./node_modules/.bin/istanbul cover --report html \ + ./node_modules/.bin/_mocha -- \ + -t 5000 \ + -s 6000 \ + $(TESTFLAGS) \ + $(TESTS) + test: @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_PATH=lib TESTS="$(ALL_TESTS)" run-tests integrationtest: @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_PATH=lib TESTS="$(ALL_INTEGRATION)" run-integrationtests +coverage: + @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_PATH=lib TESTS="$(ALL_TESTS)" run-coverage + benchmark: @node bench/sender.benchmark.js @node bench/parser.benchmark.js @@ -37,4 +42,4 @@ autobahn: autobahn-server: @NODE_PATH=lib node test/autobahn-server.js -.PHONY: test +.PHONY: test coverage diff --git a/package.json b/package.json index 9e974c5b9..a246a8df6 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "benchmark": "0.3.x", "bufferutil": "1.2.x", "expect.js": "0.3.x", + "istanbul": "^0.4.1", "mocha": "2.3.x", "should": "8.0.x", "tinycolor": "0.0.x", From ed4a2780e1c848d7bfb9d60549d5c80891e24996 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Tue, 5 Jan 2016 14:57:17 +0800 Subject: [PATCH 028/669] Use Buffer.concat() instead of private concatBuffers method --- lib/BufferUtil.fallback.js | 2 +- lib/Receiver.js | 18 ++---------------- lib/Validation.fallback.js | 5 ++--- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/lib/BufferUtil.fallback.js b/lib/BufferUtil.fallback.js index 508542c9e..7abd0d8a6 100644 --- a/lib/BufferUtil.fallback.js +++ b/lib/BufferUtil.fallback.js @@ -4,7 +4,7 @@ * MIT Licensed */ -module.exports.BufferUtil = { +exports.BufferUtil = { merge: function(mergedBuffer, buffers) { var offset = 0; for (var i = 0, l = buffers.length; i < l; ++i) { diff --git a/lib/Receiver.js b/lib/Receiver.js index b3183bfb4..44cc32281 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -296,20 +296,6 @@ Receiver.prototype.unmask = function (mask, buf, binary) { return buf != null ? buf.toString('utf8') : ''; }; -/** - * Concatenates a list of buffers. - * - * @api private - */ - -Receiver.prototype.concatBuffers = function(buffers) { - var length = 0; - for (var i = 0, l = buffers.length; i < l; ++i) length += buffers[i].length; - var mergedBuffer = new Buffer(length); - bufferUtil.merge(mergedBuffer, buffers); - return mergedBuffer; -}; - /** * Handles an error * @@ -468,7 +454,7 @@ var opcodes = { if (buffer != null) self.currentMessage.push(buffer); if (state.lastFragment) { - var messageBuffer = self.concatBuffers(self.currentMessage); + var messageBuffer = Buffer.concat(self.currentMessage); self.currentMessage = []; if (!Validation.isValidUTF8(messageBuffer)) { self.error('invalid utf8 sequence', 1007); @@ -532,7 +518,7 @@ var opcodes = { if (err) return self.error(err.message, 1007); if (buffer != null) self.currentMessage.push(buffer); if (state.lastFragment) { - var messageBuffer = self.concatBuffers(self.currentMessage); + var messageBuffer = Buffer.concat(self.currentMessage); self.currentMessage = []; self.onbinary(messageBuffer, {masked: state.masked, buffer: messageBuffer}); } diff --git a/lib/Validation.fallback.js b/lib/Validation.fallback.js index 2c7c4fd48..639b0d316 100644 --- a/lib/Validation.fallback.js +++ b/lib/Validation.fallback.js @@ -3,10 +3,9 @@ * Copyright(c) 2011 Einar Otto Stangvik * MIT Licensed */ - -module.exports.Validation = { + +exports.Validation = { isValidUTF8: function(buffer) { return true; } }; - From 523b61f7d7aa92fdc15ac406c5c6b03b71b4259a Mon Sep 17 00:00:00 2001 From: calebboyd Date: Tue, 12 Jan 2016 13:41:45 -0600 Subject: [PATCH 029/669] [pkg]: add `main` field to package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 9e974c5b9..2c579b3a7 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "simple to use, blazing fast and thoroughly tested websocket client, server and console for node.js, up-to-date against RFC-6455", "version": "1.0.1", "license": "MIT", + "main": "index.js", "keywords": [ "Hixie", "HyBi", From 61de51465b8c78062f269d201792690f97a84497 Mon Sep 17 00:00:00 2001 From: Olise Olisedumbi Tamunodiepiriye Date: Thu, 31 Dec 2015 21:31:48 +0100 Subject: [PATCH 030/669] Add maxpayload limit to hybi clients Limit the amount of data that can be sent to a websocketserver from a hybi client. Raise a 1009 - message too big error. Tests included. --- lib/PerMessageDeflate.js | 16 ++++- lib/Receiver.js | 114 ++++++++++++++++++++++++++++++++--- lib/WebSocket.js | 7 ++- lib/WebSocketServer.js | 9 ++- test/Receiver.test.js | 76 +++++++++++++++++++++++ test/WebSocketServer.test.js | 43 +++++++++++++ 6 files changed, 247 insertions(+), 18 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 5324bd8e6..00a6ea62a 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -11,7 +11,7 @@ PerMessageDeflate.extensionName = 'permessage-deflate'; * Per-message Compression Extensions implementation */ -function PerMessageDeflate(options, isServer) { +function PerMessageDeflate(options, isServer,maxPayload) { if (this instanceof PerMessageDeflate === false) { throw new TypeError("Classes can't be function-called"); } @@ -21,6 +21,7 @@ function PerMessageDeflate(options, isServer) { this._inflate = null; this._deflate = null; this.params = null; + this._maxPayload = maxPayload || 0; } /** @@ -236,6 +237,7 @@ PerMessageDeflate.prototype.decompress = function (data, fin, callback) { var self = this; var buffers = []; + var cumulativeBufferLength=0; this._inflate.on('error', onError).on('data', onData); this._inflate.write(data); @@ -253,7 +255,17 @@ PerMessageDeflate.prototype.decompress = function (data, fin, callback) { } function onData(data) { - buffers.push(data); + if(self._maxPayload!==undefined && self._maxPayload!==null && self._maxPayload>0){ + cumulativeBufferLength+=data.length; + if(cumulativeBufferLength>self._maxPayload){ + buffers=[]; + cleanup(); + var err={type:1009}; + callback(err); + return; + } + } + buffers.push(data); } function cleanup() { diff --git a/lib/Receiver.js b/lib/Receiver.js index b3183bfb4..aa020424e 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -15,10 +15,15 @@ var util = require('util') * HyBi Receiver implementation */ -function Receiver (extensions) { +function Receiver (extensions,maxPayload) { if (this instanceof Receiver === false) { throw new TypeError("Classes can't be function-called"); } + if(typeof extensions==='number'){ + maxPayload=extensions; + extensions={}; + } + // memory pool for fragmented messages var fragmentedPoolPrevUsed = -1; @@ -39,8 +44,9 @@ function Receiver (extensions) { Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) : db.used; }); - this.extensions = extensions || {}; + this.maxPayload = maxPayload || 0; + this.currentPayloadLength = 0; this.state = { activeFragmentedOperation: null, lastFragment: false, @@ -54,6 +60,7 @@ function Receiver (extensions) { this.expectBuffer = null; this.expectHandler = null; this.currentMessage = []; + this.currentMessageLength = 0; this.messageHandlers = []; this.expectHeader(2, this.processPacket); this.dead = false; @@ -253,6 +260,7 @@ Receiver.prototype.endPacket = function() { // end current fragmented operation this.state.activeFragmentedOperation = null; } + this.currentPayloadLength = 0; this.state.lastFragment = false; this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0; this.state.masked = false; @@ -281,7 +289,9 @@ Receiver.prototype.reset = function() { this.expectHandler = null; this.overflow = []; this.currentMessage = []; + this.currentMessageLength = 0; this.messageHandlers = []; + this.currentPayloadLength = 0; }; /** @@ -365,6 +375,27 @@ Receiver.prototype.applyExtensions = function(messageBuffer, fin, compressed, ca } }; +/** +* Checks payload size, disconnects socket when it exceeds maxPayload +* +* @api private +*/ +Receiver.prototype.maxPayloadExceeded = function(length) { + if (this.maxPayload=== undefined || this.maxPayload === null || this.maxPayload < 1) { + return false; + } + var fullLength = this.currentPayloadLength + length; + if (fullLength < this.maxPayload) { + this.currentPayloadLength = fullLength; + return false; + } + this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); + this.messageBuffer=[]; + this.cleanup(); + + return true; +}; + /** * Buffer utilities */ @@ -425,11 +456,20 @@ var opcodes = { // decode length var firstLength = data[1] & 0x7f; if (firstLength < 126) { + if (self.maxPayloadExceeded(firstLength)){ + self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); + return; + } opcodes['1'].getData.call(self, firstLength); } else if (firstLength == 126) { self.expectHeader(2, function(data) { - opcodes['1'].getData.call(self, readUInt16BE.call(data, 0)); + var length = readUInt16BE.call(data, 0); + if (self.maxPayloadExceeded(length)){ + self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); + return; + } + opcodes['1'].getData.call(self, length); }); } else if (firstLength == 127) { @@ -438,6 +478,11 @@ var opcodes = { self.error('packets with length spanning more than 32 bit is currently not supported', 1008); return; } + var length = readUInt32BE.call(data, 4); + if (self.maxPayloadExceeded(length)){ + self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); + return; + } opcodes['1'].getData.call(self, readUInt32BE.call(data, 4)); }); } @@ -464,12 +509,29 @@ var opcodes = { var state = clone(this.state); this.messageHandlers.push(function(callback) { self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { - if (err) return self.error(err.message, 1007); - if (buffer != null) self.currentMessage.push(buffer); - + if (err) { + if(err.type===1009){ + return self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); + } + return self.error(err.message, 1007); + } + if (buffer != null) { + if( self.maxPayload==0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ + self.currentMessage.push(buffer); + } + else{ + self.currentMessage=null; + self.currentMessage = []; + self.currentMessageLength = 0; + self.error(new Error('Maximum payload exceeded. maxPayload: '+self.maxPayload), 1009); + return; + } + self.currentMessageLength += buffer.length; + } if (state.lastFragment) { var messageBuffer = self.concatBuffers(self.currentMessage); self.currentMessage = []; + self.currentMessageLength = 0; if (!Validation.isValidUTF8(messageBuffer)) { self.error('invalid utf8 sequence', 1007); return; @@ -490,11 +552,20 @@ var opcodes = { // decode length var firstLength = data[1] & 0x7f; if (firstLength < 126) { + if (self.maxPayloadExceeded(firstLength)){ + self.error('Max payload exceeded in compressed text message. Aborting...', 1009); + return; + } opcodes['2'].getData.call(self, firstLength); } else if (firstLength == 126) { self.expectHeader(2, function(data) { - opcodes['2'].getData.call(self, readUInt16BE.call(data, 0)); + var length = readUInt16BE.call(data, 0); + if (self.maxPayloadExceeded(length)){ + self.error('Max payload exceeded in compressed text message. Aborting...', 1009); + return; + } + opcodes['2'].getData.call(self, length); }); } else if (firstLength == 127) { @@ -503,7 +574,12 @@ var opcodes = { self.error('packets with length spanning more than 32 bit is currently not supported', 1008); return; } - opcodes['2'].getData.call(self, readUInt32BE.call(data, 4, true)); + var length = readUInt32BE.call(data, 4, true); + if (self.maxPayloadExceeded(length)){ + self.error('Max payload exceeded in compressed text message. Aborting...', 1009); + return; + } + opcodes['2'].getData.call(self, length); }); } }, @@ -529,11 +605,29 @@ var opcodes = { var state = clone(this.state); this.messageHandlers.push(function(callback) { self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { - if (err) return self.error(err.message, 1007); - if (buffer != null) self.currentMessage.push(buffer); + if (err) { + if(err.type===1009){ + return self.error('Max payload exceeded in compressed binary message. Aborting...', 1009); + } + return self.error(err.message, 1007); + } + if (buffer != null) { + if( self.maxPayload==0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ + self.currentMessage.push(buffer); + } + else{ + self.currentMessage=null; + self.currentMessage = []; + self.currentMessageLength = 0; + self.error(new Error('Maximum payload exceeded'), 1009); + return; + } + self.currentMessageLength += buffer.length; + } if (state.lastFragment) { var messageBuffer = self.concatBuffers(self.currentMessage); self.currentMessage = []; + self.currentMessageLength = 0; self.onbinary(messageBuffer, {masked: state.masked, buffer: messageBuffer}); } callback(); diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 4e06c8071..88e98cc13 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -523,7 +523,8 @@ function initAsServerClient(req, socket, upgradeHead, options) { options = new Options({ protocolVersion: protocolVersion, protocol: null, - extensions: {} + extensions: {}, + maxPayload: 0 }).merge(options); // expose state properties @@ -534,7 +535,7 @@ function initAsServerClient(req, socket, upgradeHead, options) { this.upgradeReq = req; this.readyState = WebSocket.CONNECTING; this._isServer = true; - + this.maxPayload = options.value.maxPayload; // establish connection if (options.value.protocolVersion === 'hixie-76') { establishConnection.call(this, ReceiverHixie, SenderHixie, socket, upgradeHead); @@ -770,7 +771,7 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { socket.setTimeout(0); socket.setNoDelay(true); - this._receiver = new ReceiverClass(this.extensions); + this._receiver = new ReceiverClass(this.extensions,this.maxPayload); this._socket = socket; // socket cleanup handlers diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index ba0e4c050..7ab5125d9 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -36,7 +36,8 @@ function WebSocketServer(options, callback) { noServer: false, disableHixie: false, clientTracking: true, - perMessageDeflate: true + perMessageDeflate: true, + maxPayload: null }).merge(options); if (!options.isDefinedAndNonNull('port') && !options.isDefinedAndNonNull('server') && !options.value.noServer) { @@ -256,7 +257,8 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { var client = new WebSocket([req, socket, upgradeHead], { protocolVersion: version, protocol: protocol, - extensions: extensions + extensions: extensions, + maxPayload: self.options.maxPayload }); if (self.options.clientTracking) { @@ -489,8 +491,9 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { function acceptExtensions(offer) { var extensions = {}; var options = this.options.perMessageDeflate; + var maxPayload = this.options.maxPayload; if (options && offer[PerMessageDeflate.extensionName]) { - var perMessageDeflate = new PerMessageDeflate(options !== true ? options : {}, true); + var perMessageDeflate = new PerMessageDeflate(options !== true ? options : {}, true, maxPayload); perMessageDeflate.accept(offer[PerMessageDeflate.extensionName]); extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 30fd3b803..a5ebab370 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -306,6 +306,82 @@ describe('Receiver', function() { }); }); }); + it('will raise an error on a 200kb long masked binary message when maxpayload is 20kb', function() { + var p = new Receiver(20480); + var length = 200 * 1024; + var message = new Buffer(length); + for (var i = 0; i < length; ++i) message[i] = i % 256; + var originalMessage = getHexStringFromBuffer(message); + var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); + + var gotError = false; + p.error = function(reason,code) { + gotError = true; + assert.equal(code, 1009); + }; + + p.add(getBufferFromHexString(packet)); + gotError.should.be.ok; + }); + it('will raise an error on a 200kb long unmasked binary message when maxpayload is 20kb', function() { + var p = new Receiver(20480); + var length = 200 * 1024; + var message = new Buffer(length); + for (var i = 0; i < length; ++i) message[i] = i % 256; + var originalMessage = getHexStringFromBuffer(message); + var packet = '82 ' + getHybiLengthAsHexString(length, false) + ' ' + getHexStringFromBuffer(message); + + var gotError = false; + p.error = function(reason,code) { + gotError = true; + assert.equal(code, 1009); + }; + + p.add(getBufferFromHexString(packet)); + gotError.should.be.ok; + }); + it('will raise an error on a compressed message that exceeds maxpayload of 3bytes', function(done) { + var perMessageDeflate = new PerMessageDeflate({},false,3); + perMessageDeflate.accept([{}]); + + var p = new Receiver({ 'permessage-deflate': perMessageDeflate },3); + var buf = new Buffer('Hellooooooooooooooooooooooooooooooooooooooo'); + + p.onerror = function(reason,code) { + assert.equal(code, 1009); + done(); + }; + + perMessageDeflate.compress(buf, true, function(err, compressed) { + if (err) return done(err); + p.add(new Buffer([0xc1, compressed.length])); + p.add(compressed); + }); + }); + it('will raise an error on a compressed fragment that exceeds maxpayload of 2 bytes', function(done) { + var perMessageDeflate = new PerMessageDeflate({},false,2); + perMessageDeflate.accept([{}]); + + var p = new Receiver({ 'permessage-deflate': perMessageDeflate },2); + var buf1 = new Buffer('fooooooooooooooooooooooooooooooooooooooooooooooooooooooo'); + var buf2 = new Buffer('baaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr'); + + p.onerror = function(reason,code) { + assert.equal(code, 1009); + done(); + }; + + perMessageDeflate.compress(buf1, false, function(err, compressed1) { + if (err) return done(err); + p.add(new Buffer([0x41, compressed1.length])); + p.add(compressed1); + + perMessageDeflate.compress(buf2, true, function(err, compressed2) { + p.add(new Buffer([0x80, compressed2.length])); + p.add(compressed2); + }); + }); + }); it('can cleanup during consuming data', function(done) { var perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 210a1ad12..5c28c8275 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -315,6 +315,49 @@ describe('WebSocketServer', function() { }); }); + describe('#maxpayload #hybiOnly', function() { + it('maxpayload is passed on to clients,', function(done) { + var _maxPayload = 20480; + var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { + wss.clients.length.should.eql(0); + var ws = new WebSocket('ws://localhost:' + port); + }); + wss.on('connection', function(client) { + wss.clients.length.should.eql(1); + wss.clients[0].maxPayload.should.eql(_maxPayload); + wss.close(); + done(); + }); + }); + it('maxpayload is passed on to hybi receivers', function(done) { + var _maxPayload = 20480; + var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { + wss.clients.length.should.eql(0); + var ws = new WebSocket('ws://localhost:' + port); + }); + wss.on('connection', function(client) { + wss.clients.length.should.eql(1); + wss.clients[0]._receiver.maxPayload.should.eql(_maxPayload); + wss.close(); + done(); + }); + }); + it('maxpayload is passed on to permessage-deflate', function(done) { + var PerMessageDeflate = require('../lib/PerMessageDeflate'); + var _maxPayload = 20480; + var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { + wss.clients.length.should.eql(0); + var ws = new WebSocket('ws://localhost:' + port); + }); + wss.on('connection', function(client) { + wss.clients.length.should.eql(1); + wss.clients[0]._receiver.extensions[PerMessageDeflate.extensionName]._maxPayload.should.eql(_maxPayload); + wss.close(); + done(); + }); + }); + }); + describe('#handleUpgrade', function() { it('can be used for a pre-existing server', function (done) { var srv = http.createServer(); From 913afa3de0fb9b46aab215c40e177d98988c2098 Mon Sep 17 00:00:00 2001 From: Olise Olisedumbi Tamunodiepiriye Date: Sat, 13 Feb 2016 11:21:40 +0100 Subject: [PATCH 031/669] Fix for #680 Maxpayload error (https://github.com/websockets/ws/issues/680) --- lib/Receiver.js | 2 ++ test/Receiver.test.js | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/lib/Receiver.js b/lib/Receiver.js index aa020424e..dbb61ceb9 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -83,6 +83,7 @@ module.exports = Receiver; */ Receiver.prototype.add = function(data) { + if (this.dead) return; var dataLength = data.length; if (dataLength == 0) return; if (this.expectBuffer == null) { @@ -327,6 +328,7 @@ Receiver.prototype.concatBuffers = function(buffers) { */ Receiver.prototype.error = function (reason, protocolErrorCode) { + if (this.dead) return; this.reset(); this.onerror(reason, protocolErrorCode); return this; diff --git a/test/Receiver.test.js b/test/Receiver.test.js index a5ebab370..9c3343bf7 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -382,6 +382,32 @@ describe('Receiver', function() { }); }); }); + it('will not crash if another message is received after receiving a message that exceeds maxpayload', function(done) { + var perMessageDeflate = new PerMessageDeflate({},false,2); + perMessageDeflate.accept([{}]); + + var p = new Receiver({ 'permessage-deflate': perMessageDeflate },2); + var buf1 = new Buffer('fooooooooooooooooooooooooooooooooooooooooooooooooooooooo'); + var buf2 = new Buffer('baaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr'); + + p.onerror = function(reason,code) { + assert.equal(code, 1009); + }; + + perMessageDeflate.compress(buf1, false, function(err, compressed1) { + if (err) return done(err); + p.add(new Buffer([0x41, compressed1.length])); + p.add(compressed1); + + assert.equal(p.onerror,null); + + perMessageDeflate.compress(buf2, true, function(err, compressed2) { + p.add(new Buffer([0x80, compressed2.length])); + p.add(compressed2); + done(); + }); + }); + }); it('can cleanup during consuming data', function(done) { var perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); From 6a531a597557e7b72bdd678c04684e3584fece89 Mon Sep 17 00:00:00 2001 From: Olise Olisedumbi Tamunodiepiriye Date: Sat, 13 Feb 2016 19:10:33 +0100 Subject: [PATCH 032/669] Fix for https://github.com/websockets/ws/issues/679 unhandled opcodes cause crash --- lib/Receiver.js | 10 +++++++++- test/WebSocketServer.test.js | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index dbb61ceb9..021449cf7 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -330,7 +330,15 @@ Receiver.prototype.concatBuffers = function(buffers) { Receiver.prototype.error = function (reason, protocolErrorCode) { if (this.dead) return; this.reset(); - this.onerror(reason, protocolErrorCode); + if(typeof reason == 'string'){ + this.onerror(new Error(reason), protocolErrorCode); + } + else if(reason.constructor == Error){ + this.onerror(reason, protocolErrorCode); + } + else{ + this.onerror(new Error("An error occured"),protocolErrorCode); + } return this; }; diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 5c28c8275..3198ce720 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -185,6 +185,21 @@ describe('WebSocketServer', function() { } }); }); + it('will not crash when it receives an unhandled opcode', function(done) { + var wss = new WebSocketServer({ port: 8080 }); + wss.on('connection', function connection(ws) { + ws.onerror = function(error) { + done(); + }; + }); + + var socket = new WebSocket('ws://127.0.0.1:8080/'); + + socket.onopen = function() { + socket._socket.write(new Buffer([5])); + socket.send(''); + }; + }); }); describe('#close', function() { From ecc5b1745c713256a12ee181aa19e4cc0e62d838 Mon Sep 17 00:00:00 2001 From: Olise Olisedumbi Tamunodiepiriye Date: Sat, 13 Feb 2016 22:25:29 +0100 Subject: [PATCH 033/669] Add fix to Receiver.hixie --- lib/Receiver.hixie.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index 66bc561b7..598ccbdaf 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -47,6 +47,7 @@ module.exports = Receiver; */ Receiver.prototype.add = function(data) { + if (this.dead) return; var self = this; function doAdd() { if (self.state === EMPTY) { @@ -153,8 +154,17 @@ Receiver.prototype.parse = function() { */ Receiver.prototype.error = function (reason, terminate) { + if (this.dead) return; this.reset(); - this.onerror(reason, terminate); + if(typeof reason == 'string'){ + this.onerror(new Error(reason), terminate); + } + else if(reason.constructor == Error){ + this.onerror(reason, terminate); + } + else{ + this.onerror(new Error("An error occured"),terminate); + } return this; }; From 9abd43de323a9e786b3d6304c47eb31621b5f844 Mon Sep 17 00:00:00 2001 From: Thomas Allen Date: Fri, 19 Feb 2016 10:06:50 -0600 Subject: [PATCH 034/669] Fix for #687 Hixie-76 doesn't work behind HAProxy - Solution is to send headers before receiving the nonce --- lib/WebSocketServer.js | 62 +++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index ba0e4c050..658ea42c8 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -359,8 +359,42 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { var location = ((req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws') + '://' + wshost + req.url , protocol = req.headers['sec-websocket-protocol']; + // build the response header and return a Buffer + var buildResponseHeader = function() { + var headers = [ + 'HTTP/1.1 101 Switching Protocols' + , 'Upgrade: WebSocket' + , 'Connection: Upgrade' + , 'Sec-WebSocket-Location: ' + location + ]; + if (typeof protocol != 'undefined') headers.push('Sec-WebSocket-Protocol: ' + protocol); + if (typeof origin != 'undefined') headers.push('Sec-WebSocket-Origin: ' + origin); + + return new Buffer(headers.concat('', '').join('\r\n')); + }; + + // send handshake response before receiving the nonce + var handshakeResponse = function() { + + socket.setTimeout(0); + socket.setNoDelay(true); + + var headerBuffer = buildResponseHeader(); + + try { + socket.write(headerBuffer, 'binary', function(err) { + // remove listener if there was an error + if (err) socket.removeListener('data', handler); + return; + }); + } catch (e) { + try { socket.destroy(); } catch (e) {} + return; + }; + }; + // handshake completion code to run once nonce has been successfully retrieved - var completeHandshake = function(nonce, rest) { + var completeHandshake = function(nonce, rest, headerBuffer) { // calculate key var k1 = req.headers['sec-websocket-key1'] , k2 = req.headers['sec-websocket-key2'] @@ -382,20 +416,10 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { }); md5.update(nonce.toString('binary')); - var headers = [ - 'HTTP/1.1 101 Switching Protocols' - , 'Upgrade: WebSocket' - , 'Connection: Upgrade' - , 'Sec-WebSocket-Location: ' + location - ]; - if (typeof protocol != 'undefined') headers.push('Sec-WebSocket-Protocol: ' + protocol); - if (typeof origin != 'undefined') headers.push('Sec-WebSocket-Origin: ' + origin); - socket.setTimeout(0); socket.setNoDelay(true); + try { - // merge header and hash buffer - var headerBuffer = new Buffer(headers.concat('', '').join('\r\n')); var hashBuffer = new Buffer(md5.digest('binary'), 'binary'); var handshakeBuffer = new Buffer(headerBuffer.length + hashBuffer.length); headerBuffer.copy(handshakeBuffer, 0); @@ -434,11 +458,10 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { if (upgradeHead && upgradeHead.length >= nonceLength) { var nonce = upgradeHead.slice(0, nonceLength); var rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null; - completeHandshake.call(self, nonce, rest); + completeHandshake.call(self, nonce, rest, buildResponseHeader()); } else { - // nonce not present in upgradeHead, so we must wait for enough data - // data to arrive before continuing + // nonce not present in upgradeHead var nonce = new Buffer(nonceLength); upgradeHead.copy(nonce, 0); var received = upgradeHead.length; @@ -451,10 +474,17 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { if (received == nonceLength) { socket.removeListener('data', handler); if (toRead < data.length) rest = data.slice(toRead); - completeHandshake.call(self, nonce, rest); + + // complete the handshake but send empty buffer for headers since they have already been sent + completeHandshake.call(self, nonce, rest, new Buffer(0)); } } + + // handle additional data as we receive it socket.on('data', handler); + + // send header response before we have the nonce to fix haproxy buffering + handshakeResponse(); } } From 826b461acc7ff8ef869a10320413de4c9312d2c8 Mon Sep 17 00:00:00 2001 From: Joseph Frazier <1212jtraceur@gmail.com> Date: Mon, 14 Mar 2016 20:34:01 -0400 Subject: [PATCH 035/669] [doc] Fix grammar of utf-8-validate info --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ec05d65f1..93106d7a3 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,8 @@ compiler is installed on the host system. invalid UTF-8 chars, some of these validations could not be done in JavaScript hence the need for a binary addon. In most cases you will already be validating the input that you receive for security purposes leading to double - validation. But if you want to be 100% spec conform and fast validation of UTF-8 - then this module is a must. + validation. But if you want to be 100% spec-conforming and have fast + validation of UTF-8 then this module is a must. ### Sending and receiving text data From b80b33588d92ebfdd9d12d75415dd5eace6c9531 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Tue, 15 Mar 2016 13:10:21 +0000 Subject: [PATCH 036/669] lib/WebSocket.js: expose binaryType property The W3C WebSocket interface includes a binaryType attributes for a socket and a corresponding BinaryType { 'blob', 'arraybuffer' } enum. This enables support for the 'arraybuffer' BinaryType, but avoiding changing the existing behaviour the default binaryType is set as 'nodebuffer'. The 'blob' type is left unsupported. This improves ws compatibility with some existing W3C WebSocket based client code. Fixes: #441 --- lib/WebSocket.js | 24 +++++++++++++++++ test/WebSocket.test.js | 61 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 88e98cc13..8ece3dc6b 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -71,6 +71,7 @@ function WebSocket(address, protocols, options) { this.readyState = null; this.supports = {}; this.extensions = {}; + this._binaryType = 'nodebuffer'; if (Array.isArray(address)) { initAsServerClient.apply(this, address.concat(options)); @@ -371,6 +372,27 @@ Object.defineProperty(WebSocket.prototype, 'bufferedAmount', { } }); +/** + * Expose binaryType + * + * This deviates from the W3C interface since ws doesn't support the required + * default "blob" type (instead we define a custom "nodebuffer" type). + * + * @see http://dev.w3.org/html5/websockets/#the-websocket-interface + * @api public + */ +Object.defineProperty(WebSocket.prototype, 'binaryType', { + get: function get() { + return this._binaryType; + }, + set: function set(type) { + if (type === 'arraybuffer' || type === 'nodebuffer') + this._binaryType = type; + else + throw new SyntaxError('unsupported binaryType: must be either "nodebuffer" or "arraybuffer"'); + } +}); + /** * Emulates the W3C Browser based WebSocket interface using function members. * @@ -415,6 +437,8 @@ WebSocket.prototype.addEventListener = function(method, listener) { var target = this; function onMessage (data, flags) { + if (flags.binary && this.binaryType === 'arraybuffer') + data = new Uint8Array(data).buffer; listener.call(target, new MessageEvent(data, !!flags.binary, target)); } diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 5b2f4ec93..3efe3112e 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1544,6 +1544,10 @@ describe('WebSocket', function() { ws.onclose = listener; ws.onopen = listener; + assert.ok(ws.binaryType === 'nodebuffer'); + ws.binaryType = 'arraybuffer'; + assert.ok(ws.binaryType === 'arraybuffer'); + assert.ok(ws.onopen === listener); assert.ok(ws.onmessage === listener); assert.ok(ws.onclose === listener); @@ -1694,6 +1698,63 @@ describe('WebSocket', function() { client.send('hi') }); }); + + it('should pass binary data as a node.js Buffer by default', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var array = new Uint8Array(4096); + + ws.onopen = function() { + ws.send(array, {binary: true}); + }; + ws.onmessage = function(messageEvent) { + assert.ok(messageEvent.binary); + assert.ok(ws.binaryType === 'nodebuffer'); + assert.ok(messageEvent.data instanceof Buffer); + ws.terminate(); + srv.close(); + done(); + }; + }); + }); + + it('should pass an ArrayBuffer for event.data if binaryType = arraybuffer', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.binaryType = 'arraybuffer'; + var array = new Uint8Array(4096); + + ws.onopen = function() { + ws.send(array, {binary: true}); + }; + ws.onmessage = function(messageEvent) { + assert.ok(messageEvent.binary); + assert.ok(messageEvent.data instanceof ArrayBuffer); + ws.terminate(); + srv.close(); + done(); + }; + }); + }); + + it('should ignore binaryType for text messages', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.binaryType = 'arraybuffer'; + + ws.onopen = function() { + ws.send('foobar'); + }; + ws.onmessage = function(messageEvent) { + assert.ok(!messageEvent.binary); + assert.ok(typeof messageEvent.data === 'string'); + ws.terminate(); + srv.close(); + done(); + }; + }); + }); + }); describe('ssl', function() { From c3c6554c23ff941122e0e6481fc5efd0654b5dd5 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Mon, 28 Mar 2016 14:55:05 -0500 Subject: [PATCH 037/669] added documentation for close callback --- doc/ws.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 8f23d51c7..25d08fec8 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -66,9 +66,9 @@ If `handleProtocols` is not set then the handshake is accepted regardless the va If a property is empty then either an offered configuration or a default value is used. -### server.close() +### server.close([callback]) -Close the server and terminate all clients +Close the server and terminate all clients, calls callback when done with an error if one occured. ### server.handleUpgrade(request, socket, upgradeHead, callback) From fc0d15bb266e2200ef382df03a9e08f6227df52c Mon Sep 17 00:00:00 2001 From: misobytes Date: Tue, 5 Apr 2016 20:55:08 -0700 Subject: [PATCH 038/669] emit both an error and close event when connection establishment fails --- lib/WebSocket.js | 15 ++++++--------- test/WebSocket.test.js | 8 +++++--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 8ece3dc6b..ec98d3bea 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -936,21 +936,18 @@ function sendStream(instance, stream, options, cb) { function cleanupWebsocketResources(error) { if (this.readyState === WebSocket.CLOSED) return; - var emitClose = this.readyState !== WebSocket.CONNECTING; this.readyState = WebSocket.CLOSED; clearTimeout(this._closeTimer); this._closeTimer = null; - if (emitClose) { - // If the connection was closed abnormally (with an error), or if - // the close control frame was not received then the close code - // must default to 1006. - if (error || !this._closeReceived) { - this._closeCode = 1006; - } - this.emit('close', this._closeCode || 1000, this._closeMessage || ''); + // If the connection was closed abnormally (with an error), or if + // the close control frame was not received then the close code + // must default to 1006. + if (error || !this._closeReceived) { + this._closeCode = 1006; } + this.emit('close', this._closeCode || 1000, this._closeMessage || ''); if (this._socket) { if (this._ultron) this._ultron.destroy(); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 3efe3112e..95aaed0c5 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -398,11 +398,13 @@ describe('WebSocket', function() { ws.on('open', function() { assert.fail('connect shouldnt be raised here'); }); - ws.on('close', function() { - assert.fail('close shouldnt be raised here'); - }); + var errorCallBackFired = false; ws.on('error', function() { + errorCallBackFired = true; + }); + ws.on('close', function() { setTimeout(function() { + assert.equal(true, errorCallBackFired); assert.equal(ws.readyState, WebSocket.CLOSED); done(); }, 50) From 4263f26d4dbe27e781c41a1ddfe3dab87dd9e1dc Mon Sep 17 00:00:00 2001 From: Arnout Kazemier Date: Mon, 11 Apr 2016 14:00:07 +0200 Subject: [PATCH 039/669] [dist] 1.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 584dcd6f9..b4968c3f3 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "simple to use, blazing fast and thoroughly tested websocket client, server and console for node.js, up-to-date against RFC-6455", - "version": "1.0.1", + "version": "1.1.0", "license": "MIT", "main": "index.js", "keywords": [ From 403a0ee7c32afeca844745bb2dae947a6c0ace2a Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 1 May 2016 20:17:36 -0400 Subject: [PATCH 040/669] test: update `should` usage These changes are in anticipation of upcoming changes in node v7.0.0. See: https://github.com/nodejs/node/pull/6102 --- test/WebSocket.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 9a696411f..6c6231732 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1990,7 +1990,7 @@ describe('WebSocket', function() { var srv = http.createServer(); srv.listen(++port, function() { srv.on('upgrade', function(req, socket, upgradeHeade) { - req.headers.should.not.have.property('origin'); + should(req.headers).not.have.property('origin'); srv.close(); done(); }); From a786c25e564b00b364f575d739757cc91bb4cabf Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Sun, 12 Jun 2016 21:12:36 +0800 Subject: [PATCH 041/669] Clean up code --- lib/BufferPool.js | 32 ++++++++++++++++---------------- lib/WebSocket.js | 9 +-------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/lib/BufferPool.js b/lib/BufferPool.js index 8ee599057..8e0fbe998 100644 --- a/lib/BufferPool.js +++ b/lib/BufferPool.js @@ -11,32 +11,32 @@ function BufferPool(initialSize, growStrategy, shrinkStrategy) { throw new TypeError("Classes can't be function-called"); } - if (typeof initialSize === 'function') { - shrinkStrategy = growStrategy; - growStrategy = initialSize; - initialSize = 0; - } - else if (typeof initialSize === 'undefined') { - initialSize = 0; - } this._growStrategy = (growStrategy || function(db, size) { return db.used + size; }).bind(null, this); + this._shrinkStrategy = (shrinkStrategy || function(db) { return initialSize; }).bind(null, this); - this._buffer = initialSize ? new Buffer(initialSize) : null; + + this._buffer = new Buffer(initialSize); this._offset = 0; this._used = 0; this._changeFactor = 0; - this.__defineGetter__('size', function(){ - return this._buffer == null ? 0 : this._buffer.length; - }); - this.__defineGetter__('used', function(){ - return this._used; - }); } +Object.defineProperty(BufferPool.prototype, 'size', { + get: function() { + return this._buffer.length; + } +}); + +Object.defineProperty(BufferPool.prototype, 'used', { + get: function() { + return this._used; + } +}); + BufferPool.prototype.get = function(length) { if (this._buffer == null || this._offset + length > this._buffer.length) { var newBuf = new Buffer(this._growStrategy(length)); @@ -54,7 +54,7 @@ BufferPool.prototype.reset = function(forceNewBuffer) { if (len < this.size) this._changeFactor -= 1; if (forceNewBuffer || this._changeFactor < -2) { this._changeFactor = 0; - this._buffer = len ? new Buffer(len) : null; + this._buffer = new Buffer(len); } this._offset = 0; this._used = 0; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index bb09e851b..dc53c4eda 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -232,14 +232,7 @@ WebSocket.prototype.send = function send(data, options, cb) { if (typeof options.binary === 'undefined') { options.binary = (data instanceof ArrayBuffer || data instanceof Buffer || - data instanceof Uint8Array || - data instanceof Uint16Array || - data instanceof Uint32Array || - data instanceof Int8Array || - data instanceof Int16Array || - data instanceof Int32Array || - data instanceof Float32Array || - data instanceof Float64Array); + ArrayBuffer.isView(data)); } if (typeof options.mask === 'undefined') options.mask = !this._isServer; From bb5e960eb4d1964093c1451bf58cd7093dee4e4a Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Sun, 12 Jun 2016 22:46:23 +0800 Subject: [PATCH 042/669] Add node 6 into travis list --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 5002b4984..2d5608479 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: node_js sudo: false node_js: + - "6" - "5" - "4" - "0.12" From 7ed327f6c80684b7f88731439c059405037eb47e Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Sun, 12 Jun 2016 22:57:45 +0800 Subject: [PATCH 043/669] Add eslint as lint tool --- .eslintignore | 1 + .eslintrc | 16 ++++++++++++++++ Makefile | 3 +++ package.json | 1 + 4 files changed, 21 insertions(+) create mode 100644 .eslintignore create mode 100644 .eslintrc diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..8d87b1d26 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +node_modules/* diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 000000000..d29181a59 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,16 @@ +{ + "rules": { + + }, + "env": { + "es6": true, + "node": true + }, + "globals": { + "describe": true, + "it": true, + "before": true, + "after": true + }, + "extends": "eslint:recommended" +} diff --git a/Makefile b/Makefile index 94612c5ce..db9b984af 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ ALL_TESTS = $(shell find test/ -name '*.test.js') ALL_INTEGRATION = $(shell find test/ -name '*.integration.js') +lint: + @./node_modules/.bin/eslint lib test bench + run-tests: @./node_modules/.bin/mocha \ -t 5000 \ diff --git a/package.json b/package.json index b4968c3f3..9a06b04c1 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "ansi": "0.3.x", "benchmark": "0.3.x", "bufferutil": "1.2.x", + "eslint": "^2.12.0", "expect.js": "0.3.x", "istanbul": "^0.4.1", "mocha": "2.3.x", From cc5afb528cf537030fc8f228a0912409b2a177e7 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Mon, 13 Jun 2016 11:05:36 +0800 Subject: [PATCH 044/669] fix PerMessageDeflate options pass issue --- lib/WebSocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index bb09e851b..8e178455d 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -606,7 +606,7 @@ function initAsClient(address, protocols, options) { var extensionsOffer = {}; var perMessageDeflate; if (options.value.perMessageDeflate) { - perMessageDeflate = new PerMessageDeflate(typeof options.value.perMessageDeflate !== true ? options.value.perMessageDeflate : {}, false); + perMessageDeflate = new PerMessageDeflate(options.value.perMessageDeflate !== true ? options.value.perMessageDeflate : {}, false); extensionsOffer[PerMessageDeflate.extensionName] = perMessageDeflate.offer(); } From 2a9e027b421332ac2d5ef8cb3391e8f544237a6c Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Mon, 13 Jun 2016 11:42:31 +0800 Subject: [PATCH 045/669] refine code via eslint --- .eslintrc | 5 ++++- Makefile | 2 +- lib/BufferPool.js | 2 -- lib/BufferUtil.fallback.js | 6 ++++-- lib/ErrorCodes.js | 4 ++-- lib/Extensions.js | 6 ++---- lib/PerMessageDeflate.js | 2 +- lib/Receiver.hixie.js | 2 -- lib/Receiver.js | 15 +++++---------- lib/Sender.hixie.js | 3 +-- lib/Sender.js | 1 - lib/WebSocket.js | 5 +++-- lib/WebSocketServer.js | 15 +++++++-------- 13 files changed, 30 insertions(+), 38 deletions(-) diff --git a/.eslintrc b/.eslintrc index d29181a59..d0c121051 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,9 @@ { "rules": { - + "no-unused-vars": ["error", { "args": "none" }], + "no-console": 0, + "no-constant-condition": ["error", { "checkLoops": false }], + "no-empty": ["error", { "allowEmptyCatch": true }] }, "env": { "es6": true, diff --git a/Makefile b/Makefile index db9b984af..c57f69109 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ ALL_TESTS = $(shell find test/ -name '*.test.js') ALL_INTEGRATION = $(shell find test/ -name '*.integration.js') lint: - @./node_modules/.bin/eslint lib test bench + @./node_modules/.bin/eslint lib run-tests: @./node_modules/.bin/mocha \ diff --git a/lib/BufferPool.js b/lib/BufferPool.js index 8ee599057..b23c0ca26 100644 --- a/lib/BufferPool.js +++ b/lib/BufferPool.js @@ -4,8 +4,6 @@ * MIT Licensed */ -var util = require('util'); - function BufferPool(initialSize, growStrategy, shrinkStrategy) { if (this instanceof BufferPool === false) { throw new TypeError("Classes can't be function-called"); diff --git a/lib/BufferUtil.fallback.js b/lib/BufferUtil.fallback.js index 7abd0d8a6..c8c7e1fe5 100644 --- a/lib/BufferUtil.fallback.js +++ b/lib/BufferUtil.fallback.js @@ -21,12 +21,13 @@ exports.BufferUtil = { if (num < 0) num = 4294967296 + num; output.writeUInt32LE(num, offset + i, true); } + /* eslint-disable no-fallthrough */ switch (length % 4) { case 3: output[offset + i + 2] = source[i + 2] ^ mask[2]; case 2: output[offset + i + 1] = source[i + 1] ^ mask[1]; case 1: output[offset + i] = source[i] ^ mask[0]; - case 0:; } + /* eslint-enable no-fallthrough */ }, unmask: function(data, mask) { var maskNum = mask.readUInt32LE(0, true); @@ -37,11 +38,12 @@ exports.BufferUtil = { if (num < 0) num = 4294967296 + num; data.writeUInt32LE(num, i, true); } + /* eslint-disable no-fallthrough */ switch (length % 4) { case 3: data[i + 2] = data[i + 2] ^ mask[2]; case 2: data[i + 1] = data[i + 1] ^ mask[1]; case 1: data[i] = data[i] ^ mask[0]; - case 0:; } + /* eslint-enable no-fallthrough */ } } diff --git a/lib/ErrorCodes.js b/lib/ErrorCodes.js index 55ebd529b..1b4e8eda6 100644 --- a/lib/ErrorCodes.js +++ b/lib/ErrorCodes.js @@ -20,5 +20,5 @@ module.exports = { 1008: 'policy violation', 1009: 'message too big', 1010: 'extension handshake missing', - 1011: 'an unexpected condition prevented the request from being fulfilled', -}; \ No newline at end of file + 1011: 'an unexpected condition prevented the request from being fulfilled' +}; diff --git a/lib/Extensions.js b/lib/Extensions.js index a465ace2b..40f2e002c 100644 --- a/lib/Extensions.js +++ b/lib/Extensions.js @@ -1,6 +1,4 @@ -var util = require('util'); - /** * Module exports. */ @@ -54,13 +52,13 @@ function parse(value) { function format(value) { return Object.keys(value).map(function(token) { var paramsList = value[token]; - if (!util.isArray(paramsList)) { + if (!Array.isArray(paramsList)) { paramsList = [paramsList]; } return paramsList.map(function(params) { return [token].concat(Object.keys(params).map(function(k) { var p = params[k]; - if (!util.isArray(p)) p = [p]; + if (!Array.isArray(p)) p = [p]; return p.map(function(v) { return v === true ? k : k + '=' + v; }).join('; '); diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 00a6ea62a..a0b014a2f 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -11,7 +11,7 @@ PerMessageDeflate.extensionName = 'permessage-deflate'; * Per-message Compression Extensions implementation */ -function PerMessageDeflate(options, isServer,maxPayload) { +function PerMessageDeflate(options, isServer, maxPayload) { if (this instanceof PerMessageDeflate === false) { throw new TypeError("Classes can't be function-called"); } diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index 598ccbdaf..a5c9c0fa3 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -4,8 +4,6 @@ * MIT Licensed */ -var util = require('util'); - /** * State constants */ diff --git a/lib/Receiver.js b/lib/Receiver.js index 0bf29d800..b80ae371c 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -4,8 +4,7 @@ * MIT Licensed */ -var util = require('util') - , Validation = require('./Validation').Validation +var Validation = require('./Validation').Validation , ErrorCodes = require('./ErrorCodes') , BufferPool = require('./BufferPool') , bufferUtil = require('./BufferUtil').BufferUtil @@ -410,6 +409,7 @@ function readUInt32BE(start) { } function fastCopy(length, srcBuffer, dstBuffer, dstOffset) { + /* eslint-disable no-fallthrough */ switch (length) { default: srcBuffer.copy(dstBuffer, dstOffset, 0, length); break; case 16: dstBuffer[dstOffset+15] = srcBuffer[15]; @@ -429,16 +429,11 @@ function fastCopy(length, srcBuffer, dstBuffer, dstOffset) { case 2: dstBuffer[dstOffset+1] = srcBuffer[1]; case 1: dstBuffer[dstOffset] = srcBuffer[0]; } + /* eslint-enable no-fallthrough */ } function clone(obj) { - var cloned = {}; - for (var k in obj) { - if (obj.hasOwnProperty(k)) { - cloned[k] = obj[k]; - } - } - return cloned; + return Object.assign({}, obj); } /** @@ -696,7 +691,7 @@ var opcodes = { self.reset(); }); this.flush(); - }, + } }, // ping '9': { diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index b87d9dd93..e7567f073 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -5,8 +5,7 @@ */ var events = require('events') - , util = require('util') - , EventEmitter = events.EventEmitter; + , util = require('util'); /** * Hixie Sender implementation diff --git a/lib/Sender.js b/lib/Sender.js index 6ef2ea271..8c2210b3e 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -6,7 +6,6 @@ var events = require('events') , util = require('util') - , EventEmitter = events.EventEmitter , ErrorCodes = require('./ErrorCodes') , bufferUtil = require('./BufferUtil').BufferUtil , PerMessageDeflate = require('./PerMessageDeflate'); diff --git a/lib/WebSocket.js b/lib/WebSocket.js index bb09e851b..fc7f892a0 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -221,8 +221,10 @@ WebSocket.prototype.send = function send(data, options, cb) { } if (!data) data = ''; + + var self = this; + if (this._queue) { - var self = this; this._queue.push(function() { self.send(data, options, cb); }); return; } @@ -254,7 +256,6 @@ WebSocket.prototype.send = function send(data, options, cb) { if (data instanceof readable) { startQueue(this); - var self = this; sendStream(this, data, options, function send(error) { process.nextTick(function tock() { diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 476cf7100..4a6cf9b3d 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -12,7 +12,6 @@ var util = require('util') , WebSocket = require('./WebSocket') , Extensions = require('./Extensions') , PerMessageDeflate = require('./PerMessageDeflate') - , tls = require('tls') , url = require('url'); /** @@ -291,7 +290,7 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { if (typeof self.options.handleProtocols == 'function') { var protList = (protocols || "").split(/, */); var callbackCalled = false; - var res = self.options.handleProtocols(protList, function(result, protocol) { + self.options.handleProtocols(protList, function(result, protocol) { callbackCalled = true; if (!result) abortConnection(socket, 401, 'Unauthorized'); else completeHybiUpgrade2(protocol); @@ -400,7 +399,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { } catch (e) { try { socket.destroy(); } catch (e) {} return; - }; + } }; // handshake completion code to run once nonce has been successfully retrieved @@ -465,17 +464,18 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { // retrieve nonce var nonceLength = 8; + var nonce, rest; if (upgradeHead && upgradeHead.length >= nonceLength) { - var nonce = upgradeHead.slice(0, nonceLength); - var rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null; + nonce = upgradeHead.slice(0, nonceLength); + rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null; completeHandshake.call(self, nonce, rest, buildResponseHeader()); } else { // nonce not present in upgradeHead - var nonce = new Buffer(nonceLength); + nonce = new Buffer(nonceLength); upgradeHead.copy(nonce, 0); var received = upgradeHead.length; - var rest = null; + rest = null; var handler = function (data) { var toRead = Math.min(data.length, nonceLength - received); if (toRead === 0) return; @@ -506,7 +506,6 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { req: req }; if (this.options.verifyClient.length == 2) { - var self = this; this.options.verifyClient(info, function(result, code, name) { if (typeof code === 'undefined') code = 401; if (typeof name === 'undefined') name = http.STATUS_CODES[code]; From 04ccdf79e210663db6977a92dcdf9fc4425ef6a4 Mon Sep 17 00:00:00 2001 From: Tejas Manohar Date: Mon, 13 Jun 2016 00:55:12 -0500 Subject: [PATCH 046/669] specify data encoding type 'binary' at hash#update --- lib/WebSocket.js | 2 +- lib/WebSocketServer.js | 6 +++--- test/testserver.js | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index bb09e851b..103fd8f28 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -619,7 +619,7 @@ function initAsClient(address, protocols, options) { // begin handshake var key = new Buffer(options.value.protocolVersion + '-' + Date.now()).toString('base64'); var shasum = crypto.createHash('sha1'); - shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'); + shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); var expectedServerKey = shasum.digest('base64'); var agent = options.value.agent; diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 476cf7100..789c8813c 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -218,7 +218,7 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { // calc key var key = req.headers['sec-websocket-key']; var shasum = crypto.createHash('sha1'); - shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 'binary'); key = shasum.digest('base64'); var headers = [ @@ -422,9 +422,9 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { n >> 24 & 0xFF, n >> 16 & 0xFF, n >> 8 & 0xFF, - n & 0xFF)); + n & 0xFF), 'binary'); }); - md5.update(nonce.toString('binary')); + md5.update(nonce.toString('binary'), 'binary'); socket.setTimeout(0); socket.setNoDelay(true); diff --git a/test/testserver.js b/test/testserver.js index e17cbb8ea..be158b5c4 100644 --- a/test/testserver.js +++ b/test/testserver.js @@ -49,7 +49,7 @@ function validServer(server, req, socket) { // calc key var key = req.headers['sec-websocket-key']; var shasum = crypto.createHash('sha1'); - shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 'binary'); key = shasum.digest('base64'); var headers = [ @@ -113,7 +113,7 @@ function invalidRequestHandler(server, req, socket) { // calc key var key = req.headers['sec-websocket-key']; var shasum = crypto.createHash('sha1'); - shasum.update(key + "bogus"); + shasum.update(key + "bogus", 'binary'); key = shasum.digest('base64'); var headers = [ @@ -142,7 +142,7 @@ function closeAfterConnectHandler(server, req, socket) { // calc key var key = req.headers['sec-websocket-key']; var shasum = crypto.createHash('sha1'); - shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 'binary'); key = shasum.digest('base64'); var headers = [ From dc936946463c2bd696339f8aedaaafe250bf0da0 Mon Sep 17 00:00:00 2001 From: Arnout Kazemier Date: Mon, 13 Jun 2016 09:25:55 +0200 Subject: [PATCH 047/669] [ci] Nuke 0.12 --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2d5608479..451ba0135 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ node_js: - "6" - "5" - "4" - - "0.12" addons: apt: sources: From d7db84cd296f15c71d4cad88ac8b7db64bb393ba Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Mon, 13 Jun 2016 16:41:54 +0800 Subject: [PATCH 048/669] add indent rule and update code --- .eslintrc | 3 +- lib/BufferUtil.fallback.js | 12 +++---- lib/PerMessageDeflate.js | 20 ++++++------ lib/Receiver.hixie.js | 12 +++---- lib/Receiver.js | 66 +++++++++++++++++++------------------- lib/Sender.js | 12 +++---- lib/WebSocket.js | 10 +++--- lib/WebSocketServer.js | 40 +++++++++++------------ 8 files changed, 88 insertions(+), 87 deletions(-) diff --git a/.eslintrc b/.eslintrc index d0c121051..91cc68d28 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,7 +3,8 @@ "no-unused-vars": ["error", { "args": "none" }], "no-console": 0, "no-constant-condition": ["error", { "checkLoops": false }], - "no-empty": ["error", { "allowEmptyCatch": true }] + "no-empty": ["error", { "allowEmptyCatch": true }], + "indent": ["error", 2] }, "env": { "es6": true, diff --git a/lib/BufferUtil.fallback.js b/lib/BufferUtil.fallback.js index c8c7e1fe5..265287619 100644 --- a/lib/BufferUtil.fallback.js +++ b/lib/BufferUtil.fallback.js @@ -23,9 +23,9 @@ exports.BufferUtil = { } /* eslint-disable no-fallthrough */ switch (length % 4) { - case 3: output[offset + i + 2] = source[i + 2] ^ mask[2]; - case 2: output[offset + i + 1] = source[i + 1] ^ mask[1]; - case 1: output[offset + i] = source[i] ^ mask[0]; + case 3: output[offset + i + 2] = source[i + 2] ^ mask[2]; + case 2: output[offset + i + 1] = source[i + 1] ^ mask[1]; + case 1: output[offset + i] = source[i] ^ mask[0]; } /* eslint-enable no-fallthrough */ }, @@ -40,9 +40,9 @@ exports.BufferUtil = { } /* eslint-disable no-fallthrough */ switch (length % 4) { - case 3: data[i + 2] = data[i + 2] ^ mask[2]; - case 2: data[i + 1] = data[i + 1] ^ mask[1]; - case 1: data[i] = data[i] ^ mask[0]; + case 3: data[i + 2] = data[i + 2] ^ mask[2]; + case 2: data[i + 1] = data[i + 1] ^ mask[1]; + case 1: data[i] = data[i] ^ mask[0]; } /* eslint-enable no-fallthrough */ } diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index a0b014a2f..fefec6c5b 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -255,17 +255,17 @@ PerMessageDeflate.prototype.decompress = function (data, fin, callback) { } function onData(data) { - if(self._maxPayload!==undefined && self._maxPayload!==null && self._maxPayload>0){ - cumulativeBufferLength+=data.length; - if(cumulativeBufferLength>self._maxPayload){ - buffers=[]; - cleanup(); - var err={type:1009}; - callback(err); - return; - } + if(self._maxPayload!==undefined && self._maxPayload!==null && self._maxPayload>0){ + cumulativeBufferLength+=data.length; + if(cumulativeBufferLength>self._maxPayload){ + buffers=[]; + cleanup(); + var err={type:1009}; + callback(err); + return; } - buffers.push(data); + } + buffers.push(data); } function cleanup() { diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index a5c9c0fa3..218fe1386 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -60,12 +60,12 @@ Receiver.prototype.add = function(data) { data = data.slice(1); } else { - if (data[0] !== 0x00) { - self.error('payload must start with 0x00 byte', true); - return; - } - data = data.slice(1); - self.state = BODY; + if (data[0] !== 0x00) { + self.error('payload must start with 0x00 byte', true); + return; + } + data = data.slice(1); + self.state = BODY; } } diff --git a/lib/Receiver.js b/lib/Receiver.js index b80ae371c..39701c136 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -411,23 +411,23 @@ function readUInt32BE(start) { function fastCopy(length, srcBuffer, dstBuffer, dstOffset) { /* eslint-disable no-fallthrough */ switch (length) { - default: srcBuffer.copy(dstBuffer, dstOffset, 0, length); break; - case 16: dstBuffer[dstOffset+15] = srcBuffer[15]; - case 15: dstBuffer[dstOffset+14] = srcBuffer[14]; - case 14: dstBuffer[dstOffset+13] = srcBuffer[13]; - case 13: dstBuffer[dstOffset+12] = srcBuffer[12]; - case 12: dstBuffer[dstOffset+11] = srcBuffer[11]; - case 11: dstBuffer[dstOffset+10] = srcBuffer[10]; - case 10: dstBuffer[dstOffset+9] = srcBuffer[9]; - case 9: dstBuffer[dstOffset+8] = srcBuffer[8]; - case 8: dstBuffer[dstOffset+7] = srcBuffer[7]; - case 7: dstBuffer[dstOffset+6] = srcBuffer[6]; - case 6: dstBuffer[dstOffset+5] = srcBuffer[5]; - case 5: dstBuffer[dstOffset+4] = srcBuffer[4]; - case 4: dstBuffer[dstOffset+3] = srcBuffer[3]; - case 3: dstBuffer[dstOffset+2] = srcBuffer[2]; - case 2: dstBuffer[dstOffset+1] = srcBuffer[1]; - case 1: dstBuffer[dstOffset] = srcBuffer[0]; + default: srcBuffer.copy(dstBuffer, dstOffset, 0, length); break; + case 16: dstBuffer[dstOffset+15] = srcBuffer[15]; + case 15: dstBuffer[dstOffset+14] = srcBuffer[14]; + case 14: dstBuffer[dstOffset+13] = srcBuffer[13]; + case 13: dstBuffer[dstOffset+12] = srcBuffer[12]; + case 12: dstBuffer[dstOffset+11] = srcBuffer[11]; + case 11: dstBuffer[dstOffset+10] = srcBuffer[10]; + case 10: dstBuffer[dstOffset+9] = srcBuffer[9]; + case 9: dstBuffer[dstOffset+8] = srcBuffer[8]; + case 8: dstBuffer[dstOffset+7] = srcBuffer[7]; + case 7: dstBuffer[dstOffset+6] = srcBuffer[6]; + case 6: dstBuffer[dstOffset+5] = srcBuffer[5]; + case 5: dstBuffer[dstOffset+4] = srcBuffer[4]; + case 4: dstBuffer[dstOffset+3] = srcBuffer[3]; + case 3: dstBuffer[dstOffset+2] = srcBuffer[2]; + case 2: dstBuffer[dstOffset+1] = srcBuffer[1]; + case 1: dstBuffer[dstOffset] = srcBuffer[0]; } /* eslint-enable no-fallthrough */ } @@ -503,7 +503,7 @@ var opcodes = { self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { if (err) { if(err.type===1009){ - return self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); + return self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); } return self.error(err.message, 1007); } @@ -512,11 +512,11 @@ var opcodes = { self.currentMessage.push(buffer); } else{ - self.currentMessage=null; - self.currentMessage = []; - self.currentMessageLength = 0; - self.error(new Error('Maximum payload exceeded. maxPayload: '+self.maxPayload), 1009); - return; + self.currentMessage=null; + self.currentMessage = []; + self.currentMessageLength = 0; + self.error(new Error('Maximum payload exceeded. maxPayload: '+self.maxPayload), 1009); + return; } self.currentMessageLength += buffer.length; } @@ -544,10 +544,10 @@ var opcodes = { // decode length var firstLength = data[1] & 0x7f; if (firstLength < 126) { - if (self.maxPayloadExceeded(firstLength)){ - self.error('Max payload exceeded in compressed text message. Aborting...', 1009); - return; - } + if (self.maxPayloadExceeded(firstLength)){ + self.error('Max payload exceeded in compressed text message. Aborting...', 1009); + return; + } opcodes['2'].getData.call(self, firstLength); } else if (firstLength == 126) { @@ -599,7 +599,7 @@ var opcodes = { self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { if (err) { if(err.type===1009){ - return self.error('Max payload exceeded in compressed binary message. Aborting...', 1009); + return self.error('Max payload exceeded in compressed binary message. Aborting...', 1009); } return self.error(err.message, 1007); } @@ -608,11 +608,11 @@ var opcodes = { self.currentMessage.push(buffer); } else{ - self.currentMessage=null; - self.currentMessage = []; - self.currentMessageLength = 0; - self.error(new Error('Maximum payload exceeded'), 1009); - return; + self.currentMessage=null; + self.currentMessage = []; + self.currentMessageLength = 0; + self.error(new Error('Maximum payload exceeded'), 1009); + return; } self.currentMessageLength += buffer.length; } diff --git a/lib/Sender.js b/lib/Sender.js index 8c2210b3e..7d49cbf15 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -186,12 +186,12 @@ Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, if (compressed) outputBuffer[0] |= 0x40; switch (secondByte) { - case 126: - writeUInt16BE.call(outputBuffer, dataLength, 2); - break; - case 127: - writeUInt32BE.call(outputBuffer, 0, 2); - writeUInt32BE.call(outputBuffer, dataLength, 6); + case 126: + writeUInt16BE.call(outputBuffer, dataLength, 2); + break; + case 127: + writeUInt32BE.call(outputBuffer, 0, 2); + writeUInt32BE.call(outputBuffer, dataLength, 6); } if (maskData) { diff --git a/lib/WebSocket.js b/lib/WebSocket.js index e868d2d95..881d23be7 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -89,7 +89,7 @@ util.inherits(WebSocket, EventEmitter); * Ready States */ ["CONNECTING", "OPEN", "CLOSING", "CLOSED"].forEach(function each(state, index) { - WebSocket.prototype[state] = WebSocket[state] = index; + WebSocket.prototype[state] = WebSocket[state] = index; }); /** @@ -432,7 +432,7 @@ WebSocket.prototype.addEventListener = function(method, listener) { function onMessage (data, flags) { if (flags.binary && this.binaryType === 'arraybuffer') - data = new Uint8Array(data).buffer; + data = new Uint8Array(data).buffer; listener.call(target, new MessageEvent(data, !!flags.binary, target)); } @@ -647,9 +647,9 @@ function initAsClient(address, protocols, options) { if (options.value.headers) { for (var header in options.value.headers) { - if (options.value.headers.hasOwnProperty(header)) { + if (options.value.headers.hasOwnProperty(header)) { requestOptions.headers[header] = options.value.headers[header]; - } + } } } @@ -675,7 +675,7 @@ function initAsClient(address, protocols, options) { if (!agent) { // global agent ignores client side certificates - agent = new httpObj.Agent(requestOptions); + agent = new httpObj.Agent(requestOptions); } } diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 52f3bc0fd..d56350e64 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -221,7 +221,7 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { key = shasum.digest('base64'); var headers = [ - 'HTTP/1.1 101 Switching Protocols' + 'HTTP/1.1 101 Switching Protocols' , 'Upgrade: websocket' , 'Connection: Upgrade' , 'Sec-WebSocket-Accept: ' + key @@ -288,25 +288,25 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { var completeHybiUpgrade1 = function() { // choose from the sub-protocols if (typeof self.options.handleProtocols == 'function') { - var protList = (protocols || "").split(/, */); - var callbackCalled = false; - self.options.handleProtocols(protList, function(result, protocol) { - callbackCalled = true; - if (!result) abortConnection(socket, 401, 'Unauthorized'); - else completeHybiUpgrade2(protocol); - }); - if (!callbackCalled) { + var protList = (protocols || "").split(/, */); + var callbackCalled = false; + self.options.handleProtocols(protList, function(result, protocol) { + callbackCalled = true; + if (!result) abortConnection(socket, 401, 'Unauthorized'); + else completeHybiUpgrade2(protocol); + }); + if (!callbackCalled) { // the handleProtocols handler never called our callback - abortConnection(socket, 501, 'Could not process protocols'); - } - return; + abortConnection(socket, 501, 'Could not process protocols'); + } + return; } else { - if (typeof protocols !== 'undefined') { - completeHybiUpgrade2(protocols.split(/, */)[0]); - } - else { - completeHybiUpgrade2(); - } + if (typeof protocols !== 'undefined') { + completeHybiUpgrade2(protocols.split(/, */)[0]); + } + else { + completeHybiUpgrade2(); + } } } @@ -362,7 +362,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { var onClientVerified = function() { var wshost; if (!req.headers['x-forwarded-host']) - wshost = req.headers.host; + wshost = req.headers.host; else wshost = req.headers['x-forwarded-host']; var location = ((req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws') + '://' + wshost + req.url @@ -371,7 +371,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { // build the response header and return a Buffer var buildResponseHeader = function() { var headers = [ - 'HTTP/1.1 101 Switching Protocols' + 'HTTP/1.1 101 Switching Protocols' , 'Upgrade: WebSocket' , 'Connection: Upgrade' , 'Sec-WebSocket-Location: ' + location From 51e7af23a02089c5d027c5693f054c0d75b57543 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Mon, 13 Jun 2016 17:08:30 +0800 Subject: [PATCH 049/669] Add space-infix-ops rule and refine code (#747) --- .eslintrc | 3 +- lib/PerMessageDeflate.js | 12 ++++---- lib/Receiver.js | 66 ++++++++++++++++++++-------------------- lib/Sender.hixie.js | 4 +-- lib/Sender.js | 14 ++++----- lib/WebSocketServer.js | 2 +- 6 files changed, 51 insertions(+), 50 deletions(-) diff --git a/.eslintrc b/.eslintrc index 91cc68d28..6d6b366e8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,7 +4,8 @@ "no-console": 0, "no-constant-condition": ["error", { "checkLoops": false }], "no-empty": ["error", { "allowEmptyCatch": true }], - "indent": ["error", 2] + "indent": ["error", 2], + "space-infix-ops": 2 }, "env": { "es6": true, diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index fefec6c5b..7576d939b 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -237,7 +237,7 @@ PerMessageDeflate.prototype.decompress = function (data, fin, callback) { var self = this; var buffers = []; - var cumulativeBufferLength=0; + var cumulativeBufferLength = 0; this._inflate.on('error', onError).on('data', onData); this._inflate.write(data); @@ -255,12 +255,12 @@ PerMessageDeflate.prototype.decompress = function (data, fin, callback) { } function onData(data) { - if(self._maxPayload!==undefined && self._maxPayload!==null && self._maxPayload>0){ - cumulativeBufferLength+=data.length; - if(cumulativeBufferLength>self._maxPayload){ - buffers=[]; + if(self._maxPayload !== undefined && self._maxPayload !== null && self._maxPayload > 0){ + cumulativeBufferLength += data.length; + if(cumulativeBufferLength > self._maxPayload){ + buffers = []; cleanup(); - var err={type:1009}; + var err = {type:1009}; callback(err); return; } diff --git a/lib/Receiver.js b/lib/Receiver.js index 39701c136..3db66af79 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -18,9 +18,9 @@ function Receiver (extensions,maxPayload) { if (this instanceof Receiver === false) { throw new TypeError("Classes can't be function-called"); } - if(typeof extensions==='number'){ - maxPayload=extensions; - extensions={}; + if(typeof extensions === 'number'){ + maxPayload = extensions; + extensions = {}; } @@ -377,7 +377,7 @@ Receiver.prototype.applyExtensions = function(messageBuffer, fin, compressed, ca * @api private */ Receiver.prototype.maxPayloadExceeded = function(length) { - if (this.maxPayload=== undefined || this.maxPayload === null || this.maxPayload < 1) { + if (this.maxPayload === undefined || this.maxPayload === null || this.maxPayload < 1) { return false; } var fullLength = this.currentPayloadLength + length; @@ -386,7 +386,7 @@ Receiver.prototype.maxPayloadExceeded = function(length) { return false; } this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); - this.messageBuffer=[]; + this.messageBuffer = []; this.cleanup(); return true; @@ -397,36 +397,36 @@ Receiver.prototype.maxPayloadExceeded = function(length) { */ function readUInt16BE(start) { - return (this[start]<<8) + - this[start+1]; + return (this[start] << 8) + + this[start + 1]; } function readUInt32BE(start) { - return (this[start]<<24) + - (this[start+1]<<16) + - (this[start+2]<<8) + - this[start+3]; + return (this[start] << 24) + + (this[start + 1] << 16) + + (this[start + 2] << 8) + + this[start + 3]; } function fastCopy(length, srcBuffer, dstBuffer, dstOffset) { /* eslint-disable no-fallthrough */ switch (length) { default: srcBuffer.copy(dstBuffer, dstOffset, 0, length); break; - case 16: dstBuffer[dstOffset+15] = srcBuffer[15]; - case 15: dstBuffer[dstOffset+14] = srcBuffer[14]; - case 14: dstBuffer[dstOffset+13] = srcBuffer[13]; - case 13: dstBuffer[dstOffset+12] = srcBuffer[12]; - case 12: dstBuffer[dstOffset+11] = srcBuffer[11]; - case 11: dstBuffer[dstOffset+10] = srcBuffer[10]; - case 10: dstBuffer[dstOffset+9] = srcBuffer[9]; - case 9: dstBuffer[dstOffset+8] = srcBuffer[8]; - case 8: dstBuffer[dstOffset+7] = srcBuffer[7]; - case 7: dstBuffer[dstOffset+6] = srcBuffer[6]; - case 6: dstBuffer[dstOffset+5] = srcBuffer[5]; - case 5: dstBuffer[dstOffset+4] = srcBuffer[4]; - case 4: dstBuffer[dstOffset+3] = srcBuffer[3]; - case 3: dstBuffer[dstOffset+2] = srcBuffer[2]; - case 2: dstBuffer[dstOffset+1] = srcBuffer[1]; + case 16: dstBuffer[dstOffset + 15] = srcBuffer[15]; + case 15: dstBuffer[dstOffset + 14] = srcBuffer[14]; + case 14: dstBuffer[dstOffset + 13] = srcBuffer[13]; + case 13: dstBuffer[dstOffset + 12] = srcBuffer[12]; + case 12: dstBuffer[dstOffset + 11] = srcBuffer[11]; + case 11: dstBuffer[dstOffset + 10] = srcBuffer[10]; + case 10: dstBuffer[dstOffset + 9] = srcBuffer[9]; + case 9: dstBuffer[dstOffset + 8] = srcBuffer[8]; + case 8: dstBuffer[dstOffset + 7] = srcBuffer[7]; + case 7: dstBuffer[dstOffset + 6] = srcBuffer[6]; + case 6: dstBuffer[dstOffset + 5] = srcBuffer[5]; + case 5: dstBuffer[dstOffset + 4] = srcBuffer[4]; + case 4: dstBuffer[dstOffset + 3] = srcBuffer[3]; + case 3: dstBuffer[dstOffset + 2] = srcBuffer[2]; + case 2: dstBuffer[dstOffset + 1] = srcBuffer[1]; case 1: dstBuffer[dstOffset] = srcBuffer[0]; } /* eslint-enable no-fallthrough */ @@ -502,20 +502,20 @@ var opcodes = { this.messageHandlers.push(function(callback) { self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { if (err) { - if(err.type===1009){ + if(err.type === 1009){ return self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); } return self.error(err.message, 1007); } if (buffer != null) { - if( self.maxPayload==0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ + if( self.maxPayload == 0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ self.currentMessage.push(buffer); } else{ - self.currentMessage=null; + self.currentMessage = null; self.currentMessage = []; self.currentMessageLength = 0; - self.error(new Error('Maximum payload exceeded. maxPayload: '+self.maxPayload), 1009); + self.error(new Error('Maximum payload exceeded. maxPayload: ' + self.maxPayload), 1009); return; } self.currentMessageLength += buffer.length; @@ -598,17 +598,17 @@ var opcodes = { this.messageHandlers.push(function(callback) { self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { if (err) { - if(err.type===1009){ + if(err.type === 1009){ return self.error('Max payload exceeded in compressed binary message. Aborting...', 1009); } return self.error(err.message, 1007); } if (buffer != null) { - if( self.maxPayload==0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ + if( self.maxPayload == 0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ self.currentMessage.push(buffer); } else{ - self.currentMessage=null; + self.currentMessage = null; self.currentMessage = []; self.currentMessageLength = 0; self.error(new Error('Maximum payload exceeded'), 1009); diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index e7567f073..ac1ecda92 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -53,8 +53,8 @@ Sender.prototype.send = function(data, options, cb) { buffer.write('\x80', 'binary'); // assume length less than 2**14 bytes if (lengthbytes > 1) - buffer.write(String.fromCharCode(128+length/128), offset++, 'binary'); - buffer.write(String.fromCharCode(length&0x7f), offset++, 'binary'); + buffer.write(String.fromCharCode(128 + length / 128), offset++, 'binary'); + buffer.write(String.fromCharCode(length & 0x7f), offset++, 'binary'); } else buffer.write('\x00', 'binary'); } diff --git a/lib/Sender.js b/lib/Sender.js index 7d49cbf15..dd348c48c 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -290,15 +290,15 @@ Sender.prototype.applyExtensions = function(data, fin, compress, callback) { module.exports = Sender; function writeUInt16BE(value, offset) { - this[offset] = (value & 0xff00)>>8; - this[offset+1] = value & 0xff; + this[offset] = (value & 0xff00) >> 8; + this[offset + 1] = value & 0xff; } function writeUInt32BE(value, offset) { - this[offset] = (value & 0xff000000)>>24; - this[offset+1] = (value & 0xff0000)>>16; - this[offset+2] = (value & 0xff00)>>8; - this[offset+3] = value & 0xff; + this[offset] = (value & 0xff000000) >> 24; + this[offset + 1] = (value & 0xff0000) >> 16; + this[offset + 2] = (value & 0xff00) >> 8; + this[offset + 3] = value & 0xff; } function getArrayBuffer(data) { @@ -308,7 +308,7 @@ function getArrayBuffer(data) { , o = data.byteOffset || 0 , buffer = new Buffer(l); for (var i = 0; i < l; ++i) { - buffer[i] = array[o+i]; + buffer[i] = array[o + i]; } return buffer; } diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index d56350e64..412d89006 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -86,7 +86,7 @@ function WebSocketServer(options, callback) { upgradeHead.copy(head); self.handleUpgrade(req, socket, head, function(client) { - self.emit('connection'+req.url, client); + self.emit('connection' + req.url, client); self.emit('connection', client); }); }; From da8215263298e49d35f0b4281e7d10deffd587b6 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Tue, 5 Jan 2016 15:02:59 +0800 Subject: [PATCH 050/669] Add strict rule and update code --- .eslintrc | 3 ++- bench/parser.benchmark.js | 12 +++++++----- bench/sender.benchmark.js | 6 ++++-- bench/speed.js | 4 +++- bench/util.js | 10 ++++++---- lib/BufferPool.js | 2 ++ lib/BufferUtil.fallback.js | 2 ++ lib/ErrorCodes.js | 2 ++ lib/Extensions.js | 1 + lib/PerMessageDeflate.js | 1 + lib/Receiver.hixie.js | 2 ++ lib/Receiver.js | 2 ++ lib/Sender.hixie.js | 2 ++ lib/Sender.js | 2 ++ lib/Validation.fallback.js | 2 ++ lib/Validation.js | 4 ++-- lib/WebSocket.js | 4 ++-- lib/WebSocketServer.js | 2 ++ 18 files changed, 46 insertions(+), 17 deletions(-) diff --git a/.eslintrc b/.eslintrc index 6d6b366e8..0a1bb12e0 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,7 +5,8 @@ "no-constant-condition": ["error", { "checkLoops": false }], "no-empty": ["error", { "allowEmptyCatch": true }], "indent": ["error", 2], - "space-infix-ops": 2 + "space-infix-ops": 2, + "strict": 2 }, "env": { "es6": true, diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index ff5f737c0..b912b1953 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + /** * Benchmark dependencies. */ @@ -17,7 +19,7 @@ require('./util'); /** * Setup receiver. */ - + suite.on('start', function () { receiver = new Receiver(); }); @@ -31,10 +33,10 @@ suite.on('cycle', function () { */ var pingMessage = 'Hello' - , pingPacket1 = getBufferFromHexString('89 ' + (pack(2, 0x80 | pingMessage.length)) + + , pingPacket1 = getBufferFromHexString('89 ' + (pack(2, 0x80 | pingMessage.length)) + ' 34 83 a8 68 '+ getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'))); suite.add('ping message', function () { - receiver.add(pingPacket1); + receiver.add(pingPacket1); }); var pingPacket2 = getBufferFromHexString('89 00') @@ -63,7 +65,7 @@ binaryDataPacket = (function() { suite.add('binary data (125 bytes)', function () { try { receiver.add(binaryDataPacket); - + } catch(e) {console.log(e)} }); @@ -100,7 +102,7 @@ suite.on('cycle', function (bench, details) { details.hz.toFixed(2).cyan + ' ops/sec'.grey , details.count.toString().white + ' times executed'.grey , 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey - , + , ].join(', '.grey)); }); diff --git a/bench/sender.benchmark.js b/bench/sender.benchmark.js index 20c171a50..7c059bfec 100644 --- a/bench/sender.benchmark.js +++ b/bench/sender.benchmark.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + /** * Benchmark dependencies. */ @@ -17,7 +19,7 @@ require('./util'); /** * Setup sender. */ - + suite.on('start', function () { sender = new Sender(); sender._socket = { write: function() {} }; @@ -51,7 +53,7 @@ suite.on('cycle', function (bench, details) { details.hz.toFixed(2).cyan + ' ops/sec'.grey , details.count.toString().white + ' times executed'.grey , 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey - , + , ].join(', '.grey)); }); diff --git a/bench/speed.js b/bench/speed.js index 3ce641461..f8ae9a49f 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -1,3 +1,5 @@ +'use strict'; + var cluster = require('cluster') , WebSocket = require('../') , WebSocketServer = WebSocket.Server @@ -102,4 +104,4 @@ else { config.push(run); roundtrip.apply(null, config); })(); -} \ No newline at end of file +} diff --git a/bench/util.js b/bench/util.js index 5f0128190..38896d934 100644 --- a/bench/util.js +++ b/bench/util.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + /** * Returns a Buffer from a "ff 00 ff"-type hex string. */ @@ -49,7 +51,7 @@ mask = function(buf, maskString) { if (typeof buf == 'string') buf = new Buffer(buf); var mask = getBufferFromHexString(maskString || '34 83 a8 68'); for (var i = 0; i < buf.length; ++i) { - buf[i] ^= mask[i % 4]; + buf[i] ^= mask[i % 4]; } return buf; } @@ -57,8 +59,8 @@ mask = function(buf, maskString) { /** * Returns a hex string representing the length of a message */ - -getHybiLengthAsHexString = function(len, masked) { + +getHybiLengthAsHexString = function(len, masked) { if (len < 126) { var buf = new Buffer(1); buf[0] = (masked ? 0x80 : 0) | len; @@ -100,6 +102,6 @@ pack = function(length, number) { * Left pads the string 's' to a total length of 'n' with char 'c'. */ -padl = function(s, n, c) { +padl = function(s, n, c) { return new Array(1 + n - s.length).join(c) + s; } diff --git a/lib/BufferPool.js b/lib/BufferPool.js index 990b8f12c..510d4e956 100644 --- a/lib/BufferPool.js +++ b/lib/BufferPool.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + function BufferPool(initialSize, growStrategy, shrinkStrategy) { if (this instanceof BufferPool === false) { throw new TypeError("Classes can't be function-called"); diff --git a/lib/BufferUtil.fallback.js b/lib/BufferUtil.fallback.js index 265287619..d81b5f7ab 100644 --- a/lib/BufferUtil.fallback.js +++ b/lib/BufferUtil.fallback.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + exports.BufferUtil = { merge: function(mergedBuffer, buffers) { var offset = 0; diff --git a/lib/ErrorCodes.js b/lib/ErrorCodes.js index 1b4e8eda6..335efb4e8 100644 --- a/lib/ErrorCodes.js +++ b/lib/ErrorCodes.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + module.exports = { isValidErrorCode: function(code) { return (code >= 1000 && code <= 1011 && code != 1004 && code != 1005 && code != 1006) || diff --git a/lib/Extensions.js b/lib/Extensions.js index 40f2e002c..2b7f41593 100644 --- a/lib/Extensions.js +++ b/lib/Extensions.js @@ -1,3 +1,4 @@ +'use strict'; /** * Module exports. diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 7576d939b..75ba86bba 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -1,3 +1,4 @@ +'use strict'; var zlib = require('zlib'); diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index 218fe1386..686b2c3ca 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + /** * State constants */ diff --git a/lib/Receiver.js b/lib/Receiver.js index 3db66af79..7351069df 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + var Validation = require('./Validation').Validation , ErrorCodes = require('./ErrorCodes') , BufferPool = require('./BufferPool') diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index ac1ecda92..0b6271e3e 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + var events = require('events') , util = require('util'); diff --git a/lib/Sender.js b/lib/Sender.js index dd348c48c..b9a355be9 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + var events = require('events') , util = require('util') , ErrorCodes = require('./ErrorCodes') diff --git a/lib/Validation.fallback.js b/lib/Validation.fallback.js index 639b0d316..b160d07a3 100644 --- a/lib/Validation.fallback.js +++ b/lib/Validation.fallback.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + exports.Validation = { isValidUTF8: function(buffer) { return true; diff --git a/lib/Validation.js b/lib/Validation.js index 0795fb7f0..284ab2fb1 100644 --- a/lib/Validation.js +++ b/lib/Validation.js @@ -1,11 +1,11 @@ -'use strict'; - /*! * ws: a node.js websocket client * Copyright(c) 2011 Einar Otto Stangvik * MIT Licensed */ +'use strict'; + try { module.exports = require('utf-8-validate'); } catch (e) { diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 881d23be7..a7ad0550d 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -1,11 +1,11 @@ -'use strict'; - /*! * ws: a node.js websocket client * Copyright(c) 2011 Einar Otto Stangvik * MIT Licensed */ +'use strict'; + var url = require('url') , util = require('util') , http = require('http') diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 412d89006..43fefbbac 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -4,6 +4,8 @@ * MIT Licensed */ +'use strict'; + var util = require('util') , events = require('events') , http = require('http') From aa36969301f27b330df1036be6934297eb754fd1 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Mon, 13 Jun 2016 17:18:44 +0800 Subject: [PATCH 051/669] lint the project before run test --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c57f69109..3e8a47c5f 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ run-coverage: $(TESTFLAGS) \ $(TESTS) -test: +test: lint @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_PATH=lib TESTS="$(ALL_TESTS)" run-tests integrationtest: From 47933b97b5c3b180976d469545258dd7df07689e Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Mon, 13 Jun 2016 16:27:11 +0800 Subject: [PATCH 052/669] Add quotes rule and fix coding style --- .eslintrc | 3 ++- lib/Receiver.hixie.js | 2 +- lib/Receiver.js | 2 +- lib/WebSocket.js | 4 ++-- lib/WebSocketServer.js | 4 ++-- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.eslintrc b/.eslintrc index 0a1bb12e0..3127ebb6f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -6,7 +6,8 @@ "no-empty": ["error", { "allowEmptyCatch": true }], "indent": ["error", 2], "space-infix-ops": 2, - "strict": 2 + "strict": 2, + "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}] }, "env": { "es6": true, diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index 686b2c3ca..96ca6a58c 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -163,7 +163,7 @@ Receiver.prototype.error = function (reason, terminate) { this.onerror(reason, terminate); } else{ - this.onerror(new Error("An error occured"),terminate); + this.onerror(new Error('An error occured'),terminate); } return this; }; diff --git a/lib/Receiver.js b/lib/Receiver.js index 7351069df..cf1f31ee3 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -325,7 +325,7 @@ Receiver.prototype.error = function (reason, protocolErrorCode) { this.onerror(reason, protocolErrorCode); } else{ - this.onerror(new Error("An error occured"),protocolErrorCode); + this.onerror(new Error('An error occured'),protocolErrorCode); } return this; }; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index a7ad0550d..6a5225aeb 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -88,7 +88,7 @@ util.inherits(WebSocket, EventEmitter); /** * Ready States */ -["CONNECTING", "OPEN", "CLOSING", "CLOSED"].forEach(function each(state, index) { +['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'].forEach(function each(state, index) { WebSocket.prototype[state] = WebSocket[state] = index; }); @@ -736,7 +736,7 @@ function initAsClient(address, protocols, options) { } var serverProt = res.headers['sec-websocket-protocol']; - var protList = (options.value.protocol || "").split(/, */); + var protList = (options.value.protocol || '').split(/, */); var protError = null; if (!options.value.protocol && serverProt) { diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 43fefbbac..dd5f59bab 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -219,7 +219,7 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { // calc key var key = req.headers['sec-websocket-key']; var shasum = crypto.createHash('sha1'); - shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 'binary'); + shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); key = shasum.digest('base64'); var headers = [ @@ -290,7 +290,7 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { var completeHybiUpgrade1 = function() { // choose from the sub-protocols if (typeof self.options.handleProtocols == 'function') { - var protList = (protocols || "").split(/, */); + var protList = (protocols || '').split(/, */); var callbackCalled = false; self.options.handleProtocols(protList, function(result, protocol) { callbackCalled = true; From b1de21be112e06cfbb04f09792d1fa7ba09d7c54 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Wed, 15 Jun 2016 10:57:43 +0800 Subject: [PATCH 053/669] Add keyword-spacing rule (#752) --- .eslintrc | 3 ++- lib/PerMessageDeflate.js | 4 ++-- lib/Receiver.hixie.js | 8 ++++---- lib/Receiver.js | 21 +++++++++++---------- lib/WebSocketServer.js | 4 ++-- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/.eslintrc b/.eslintrc index 3127ebb6f..2bbf987e5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,7 +7,8 @@ "indent": ["error", 2], "space-infix-ops": 2, "strict": 2, - "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}] + "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}], + "keyword-spacing": 2 }, "env": { "es6": true, diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 75ba86bba..eb7866b88 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -256,9 +256,9 @@ PerMessageDeflate.prototype.decompress = function (data, fin, callback) { } function onData(data) { - if(self._maxPayload !== undefined && self._maxPayload !== null && self._maxPayload > 0){ + if (self._maxPayload !== undefined && self._maxPayload !== null && self._maxPayload > 0){ cumulativeBufferLength += data.length; - if(cumulativeBufferLength > self._maxPayload){ + if (cumulativeBufferLength > self._maxPayload){ buffers = []; cleanup(); var err = {type:1009}; diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index 96ca6a58c..b6442933b 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -106,7 +106,7 @@ Receiver.prototype.add = function(data) { } else self.spanLength += data.length; } - while(data) data = doAdd(); + while (data) data = doAdd(); }; /** @@ -156,13 +156,13 @@ Receiver.prototype.parse = function() { Receiver.prototype.error = function (reason, terminate) { if (this.dead) return; this.reset(); - if(typeof reason == 'string'){ + if (typeof reason == 'string'){ this.onerror(new Error(reason), terminate); } - else if(reason.constructor == Error){ + else if (reason.constructor == Error){ this.onerror(reason, terminate); } - else{ + else { this.onerror(new Error('An error occured'),terminate); } return this; diff --git a/lib/Receiver.js b/lib/Receiver.js index cf1f31ee3..15f24cefd 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -20,7 +20,8 @@ function Receiver (extensions,maxPayload) { if (this instanceof Receiver === false) { throw new TypeError("Classes can't be function-called"); } - if(typeof extensions === 'number'){ + + if (typeof extensions === 'number'){ maxPayload = extensions; extensions = {}; } @@ -318,13 +319,13 @@ Receiver.prototype.unmask = function (mask, buf, binary) { Receiver.prototype.error = function (reason, protocolErrorCode) { if (this.dead) return; this.reset(); - if(typeof reason == 'string'){ + if (typeof reason == 'string'){ this.onerror(new Error(reason), protocolErrorCode); } - else if(reason.constructor == Error){ + else if (reason.constructor == Error){ this.onerror(reason, protocolErrorCode); } - else{ + else { this.onerror(new Error('An error occured'),protocolErrorCode); } return this; @@ -504,16 +505,16 @@ var opcodes = { this.messageHandlers.push(function(callback) { self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { if (err) { - if(err.type === 1009){ + if (err.type === 1009){ return self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); } return self.error(err.message, 1007); } if (buffer != null) { - if( self.maxPayload == 0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ + if ( self.maxPayload == 0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ self.currentMessage.push(buffer); } - else{ + else { self.currentMessage = null; self.currentMessage = []; self.currentMessageLength = 0; @@ -600,16 +601,16 @@ var opcodes = { this.messageHandlers.push(function(callback) { self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { if (err) { - if(err.type === 1009){ + if (err.type === 1009){ return self.error('Max payload exceeded in compressed binary message. Aborting...', 1009); } return self.error(err.message, 1007); } if (buffer != null) { - if( self.maxPayload == 0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ + if ( self.maxPayload == 0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ self.currentMessage.push(buffer); } - else{ + else { self.currentMessage = null; self.currentMessage = []; self.currentMessageLength = 0; diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index dd5f59bab..c5c3850aa 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -146,9 +146,9 @@ WebSocketServer.prototype.close = function(callback) { } delete this._server; } - if(callback) + if (callback) callback(error); - else if(error) + else if (error) throw error; } From cbc7548a4c5b89c761bf9daadc969d487a95a784 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Wed, 15 Jun 2016 12:37:05 +0800 Subject: [PATCH 054/669] refactor code (#754) --- .eslintrc | 6 +++++- lib/PerMessageDeflate.js | 10 +++++----- lib/Receiver.hixie.js | 17 +++-------------- lib/Receiver.js | 4 ++-- lib/WebSocket.js | 2 +- 5 files changed, 16 insertions(+), 23 deletions(-) diff --git a/.eslintrc b/.eslintrc index 2bbf987e5..1abddeefd 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,7 +8,11 @@ "space-infix-ops": 2, "strict": 2, "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}], - "keyword-spacing": 2 + "keyword-spacing": 2, + "linebreak-style": [2, "unix"], + "comma-dangle": ["error", "never"], + "no-unreachable": [2], + "comma-spacing": 2 }, "env": { "es6": true, diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index eb7866b88..a55b8cc1c 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -143,7 +143,7 @@ PerMessageDeflate.prototype.acceptAsServer = function(paramsList) { }, this); if (!result) { - throw new Error('Doesn\'t support the offered configuration'); + throw new Error(`Doesn't support the offered configuration`); } return accepted; @@ -194,7 +194,7 @@ PerMessageDeflate.prototype.normalizeParams = function(paramsList) { case 'server_no_context_takeover': case 'client_no_context_takeover': if (value !== true) { - throw new Error('invalid extension parameter value for ' + key + ' (' + value + ')'); + throw new Error(`invalid extension parameter value for ${key} (${value})`); } params[key] = true; break; @@ -203,16 +203,16 @@ PerMessageDeflate.prototype.normalizeParams = function(paramsList) { if (typeof value === 'string') { value = parseInt(value, 10); if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) { - throw new Error('invalid extension parameter value for ' + key + ' (' + value + ')'); + throw new Error(`invalid extension parameter value for ${key} (${value})`); } } if (!this._isServer && value === true) { - throw new Error('Missing extension parameter value for ' + key); + throw new Error(`Missing extension parameter value for ${key}`); } params[key] = value; break; default: - throw new Error('Not defined extension parameter (' + key + ')'); + throw new Error(`Not defined extension parameter (${key})`); } }, this); return params; diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index b6442933b..88f803c9e 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -21,7 +21,7 @@ var BINARYLENGTH = 2 function Receiver () { if (this instanceof Receiver === false) { - throw new TypeError("Classes can't be function-called"); + throw new TypeError(`Classes can't be function-called`); } this.state = EMPTY; @@ -100,7 +100,7 @@ Receiver.prototype.add = function(data) { return; } self.buffers.push(data); - if ((self.messageEnd = bufferIndex(data, 0xFF)) != -1) { + if ((self.messageEnd = data.indexOf(0xFF)) != -1) { self.spanLength += self.messageEnd; return self.parse(); } @@ -163,7 +163,7 @@ Receiver.prototype.error = function (reason, terminate) { this.onerror(reason, terminate); } else { - this.onerror(new Error('An error occured'),terminate); + this.onerror(new Error('An error occured'), terminate); } return this; }; @@ -181,14 +181,3 @@ Receiver.prototype.reset = function (reason) { this.messageEnd = -1; this.spanLength = 0; }; - -/** - * Internal api - */ - -function bufferIndex(buffer, byte) { - for (var i = 0, l = buffer.length; i < l; ++i) { - if (buffer[i] === byte) return i; - } - return -1; -} diff --git a/lib/Receiver.js b/lib/Receiver.js index 15f24cefd..a8441dfb0 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -16,7 +16,7 @@ var Validation = require('./Validation').Validation * HyBi Receiver implementation */ -function Receiver (extensions,maxPayload) { +function Receiver (extensions, maxPayload) { if (this instanceof Receiver === false) { throw new TypeError("Classes can't be function-called"); } @@ -326,7 +326,7 @@ Receiver.prototype.error = function (reason, protocolErrorCode) { this.onerror(reason, protocolErrorCode); } else { - this.onerror(new Error('An error occured'),protocolErrorCode); + this.onerror(new Error('An error occured'), protocolErrorCode); } return this; }; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 6a5225aeb..b67969945 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -789,7 +789,7 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { socket.setTimeout(0); socket.setNoDelay(true); - this._receiver = new ReceiverClass(this.extensions,this.maxPayload); + this._receiver = new ReceiverClass(this.extensions, this.maxPayload); this._socket = socket; // socket cleanup handlers From f30da5dad547595c7df134be97be919b4cb5062a Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Wed, 15 Jun 2016 12:45:07 +0800 Subject: [PATCH 055/669] no more dependen on options (#753) We just use a little options' feature, it can be implement with ES6 feature now. --- lib/WebSocket.js | 97 ++++++++++++++++++++++-------------------- lib/WebSocketServer.js | 29 +++++++------ package.json | 1 - 3 files changed, 66 insertions(+), 61 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index b67969945..a2f5ff553 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -13,7 +13,6 @@ var url = require('url') , crypto = require('crypto') , stream = require('stream') , Ultron = require('ultron') - , Options = require('options') , Sender = require('./Sender') , Receiver = require('./Receiver') , SenderHixie = require('./Sender.hixie') @@ -22,6 +21,10 @@ var url = require('url') , PerMessageDeflate = require('./PerMessageDeflate') , EventEmitter = require('events').EventEmitter; +var isDefinedAndNonNull = function (options, key) { + return typeof options[key] != 'undefined' && options[key] !== null; +}; + /** * Constants */ @@ -538,24 +541,24 @@ function buildHostHeader(isSecure, hostname, port) { * which may or may not be bound to a sepcific WebSocket instance. */ function initAsServerClient(req, socket, upgradeHead, options) { - options = new Options({ + options = Object.assign({ protocolVersion: protocolVersion, protocol: null, extensions: {}, maxPayload: 0 - }).merge(options); + }, options); // expose state properties - this.protocol = options.value.protocol; - this.protocolVersion = options.value.protocolVersion; - this.extensions = options.value.extensions; + this.protocol = options.protocol; + this.protocolVersion = options.protocolVersion; + this.extensions = options.extensions; this.supports.binary = (this.protocolVersion !== 'hixie-76'); this.upgradeReq = req; this.readyState = WebSocket.CONNECTING; this._isServer = true; - this.maxPayload = options.value.maxPayload; + this.maxPayload = options.maxPayload; // establish connection - if (options.value.protocolVersion === 'hixie-76') { + if (options.protocolVersion === 'hixie-76') { establishConnection.call(this, ReceiverHixie, SenderHixie, socket, upgradeHead); } else { establishConnection.call(this, Receiver, Sender, socket, upgradeHead); @@ -563,7 +566,7 @@ function initAsServerClient(req, socket, upgradeHead, options) { } function initAsClient(address, protocols, options) { - options = new Options({ + options = Object.assign({ origin: null, protocolVersion: protocolVersion, host: null, @@ -581,9 +584,9 @@ function initAsClient(address, protocols, options) { rejectUnauthorized: null, perMessageDeflate: true, localAddress: null - }).merge(options); + }, options); - if (options.value.protocolVersion !== 8 && options.value.protocolVersion !== 13) { + if (options.protocolVersion !== 8 && options.protocolVersion !== 13) { throw new Error('unsupported protocol version'); } @@ -599,24 +602,24 @@ function initAsClient(address, protocols, options) { // prepare extensions var extensionsOffer = {}; var perMessageDeflate; - if (options.value.perMessageDeflate) { - perMessageDeflate = new PerMessageDeflate(options.value.perMessageDeflate !== true ? options.value.perMessageDeflate : {}, false); + if (options.perMessageDeflate) { + perMessageDeflate = new PerMessageDeflate(options.perMessageDeflate !== true ? options.perMessageDeflate : {}, false); extensionsOffer[PerMessageDeflate.extensionName] = perMessageDeflate.offer(); } // expose state properties this._isServer = false; this.url = address; - this.protocolVersion = options.value.protocolVersion; + this.protocolVersion = options.protocolVersion; this.supports.binary = (this.protocolVersion !== 'hixie-76'); // begin handshake - var key = new Buffer(options.value.protocolVersion + '-' + Date.now()).toString('base64'); + var key = new Buffer(options.protocolVersion + '-' + Date.now()).toString('base64'); var shasum = crypto.createHash('sha1'); shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); var expectedServerKey = shasum.digest('base64'); - var agent = options.value.agent; + var agent = options.agent; var headerHost = buildHostHeader(isSecure, serverUrl.hostname, port) @@ -627,7 +630,7 @@ function initAsClient(address, protocols, options) { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Host': headerHost, - 'Sec-WebSocket-Version': options.value.protocolVersion, + 'Sec-WebSocket-Version': options.protocolVersion, 'Sec-WebSocket-Key': key } }; @@ -637,18 +640,18 @@ function initAsClient(address, protocols, options) { requestOptions.headers.Authorization = 'Basic ' + new Buffer(auth).toString('base64'); } - if (options.value.protocol) { - requestOptions.headers['Sec-WebSocket-Protocol'] = options.value.protocol; + if (options.protocol) { + requestOptions.headers['Sec-WebSocket-Protocol'] = options.protocol; } - if (options.value.host) { - requestOptions.headers.Host = options.value.host; + if (options.host) { + requestOptions.headers.Host = options.host; } - if (options.value.headers) { - for (var header in options.value.headers) { - if (options.value.headers.hasOwnProperty(header)) { - requestOptions.headers[header] = options.value.headers[header]; + if (options.headers) { + for (var header in options.headers) { + if (options.headers.hasOwnProperty(header)) { + requestOptions.headers[header] = options.headers[header]; } } } @@ -657,21 +660,21 @@ function initAsClient(address, protocols, options) { requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format(extensionsOffer); } - if (options.isDefinedAndNonNull('pfx') - || options.isDefinedAndNonNull('key') - || options.isDefinedAndNonNull('passphrase') - || options.isDefinedAndNonNull('cert') - || options.isDefinedAndNonNull('ca') - || options.isDefinedAndNonNull('ciphers') - || options.isDefinedAndNonNull('rejectUnauthorized')) { + if (isDefinedAndNonNull(options, 'pfx') + || isDefinedAndNonNull(options, 'key') + || isDefinedAndNonNull(options, 'passphrase') + || isDefinedAndNonNull(options, 'cert') + || isDefinedAndNonNull(options, 'ca') + || isDefinedAndNonNull(options, 'ciphers') + || isDefinedAndNonNull(options, 'rejectUnauthorized')) { - if (options.isDefinedAndNonNull('pfx')) requestOptions.pfx = options.value.pfx; - if (options.isDefinedAndNonNull('key')) requestOptions.key = options.value.key; - if (options.isDefinedAndNonNull('passphrase')) requestOptions.passphrase = options.value.passphrase; - if (options.isDefinedAndNonNull('cert')) requestOptions.cert = options.value.cert; - if (options.isDefinedAndNonNull('ca')) requestOptions.ca = options.value.ca; - if (options.isDefinedAndNonNull('ciphers')) requestOptions.ciphers = options.value.ciphers; - if (options.isDefinedAndNonNull('rejectUnauthorized')) requestOptions.rejectUnauthorized = options.value.rejectUnauthorized; + if (isDefinedAndNonNull(options, 'pfx')) requestOptions.pfx = options.pfx; + if (isDefinedAndNonNull(options, 'key')) requestOptions.key = options.key; + if (isDefinedAndNonNull(options, 'passphrase')) requestOptions.passphrase = options.passphrase; + if (isDefinedAndNonNull(options, 'cert')) requestOptions.cert = options.cert; + if (isDefinedAndNonNull(options, 'ca')) requestOptions.ca = options.ca; + if (isDefinedAndNonNull(options, 'ciphers')) requestOptions.ciphers = options.ciphers; + if (isDefinedAndNonNull(options, 'rejectUnauthorized')) requestOptions.rejectUnauthorized = options.rejectUnauthorized; if (!agent) { // global agent ignores client side certificates @@ -689,13 +692,13 @@ function initAsClient(address, protocols, options) { requestOptions.socketPath = serverUrl.pathname; } - if (options.value.localAddress) { - requestOptions.localAddress = options.value.localAddress; + if (options.localAddress) { + requestOptions.localAddress = options.localAddress; } - if (options.value.origin) { - if (options.value.protocolVersion < 13) requestOptions.headers['Sec-WebSocket-Origin'] = options.value.origin; - else requestOptions.headers.Origin = options.value.origin; + if (options.origin) { + if (options.protocolVersion < 13) requestOptions.headers['Sec-WebSocket-Origin'] = options.origin; + else requestOptions.headers.Origin = options.origin; } var self = this; @@ -736,12 +739,12 @@ function initAsClient(address, protocols, options) { } var serverProt = res.headers['sec-websocket-protocol']; - var protList = (options.value.protocol || '').split(/, */); + var protList = (options.protocol || '').split(/, */); var protError = null; - if (!options.value.protocol && serverProt) { + if (!options.protocol && serverProt) { protError = 'server sent a subprotocol even though none requested'; - } else if (options.value.protocol && !serverProt) { + } else if (options.protocol && !serverProt) { protError = 'server sent no subprotocol even though requested'; } else if (serverProt && protList.indexOf(serverProt) === -1) { protError = 'server responded with an invalid protocol'; diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index c5c3850aa..4047c0ce3 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -10,12 +10,15 @@ var util = require('util') , events = require('events') , http = require('http') , crypto = require('crypto') - , Options = require('options') , WebSocket = require('./WebSocket') , Extensions = require('./Extensions') , PerMessageDeflate = require('./PerMessageDeflate') , url = require('url'); +var isDefinedAndNonNull = function (options, key) { + return typeof options[key] != 'undefined' && options[key] !== null; +}; + /** * WebSocket Server implementation */ @@ -27,7 +30,7 @@ function WebSocketServer(options, callback) { events.EventEmitter.call(this); - options = new Options({ + options = Object.assign({ host: '0.0.0.0', port: null, server: null, @@ -39,15 +42,15 @@ function WebSocketServer(options, callback) { clientTracking: true, perMessageDeflate: true, maxPayload: null - }).merge(options); + }, options); - if (!options.isDefinedAndNonNull('port') && !options.isDefinedAndNonNull('server') && !options.value.noServer) { + if (!isDefinedAndNonNull(options, 'port') && !isDefinedAndNonNull(options, 'server') && !options.noServer) { throw new TypeError('`port` or a `server` must be provided'); } var self = this; - if (options.isDefinedAndNonNull('port')) { + if (isDefinedAndNonNull(options, 'port')) { this._server = http.createServer(function (req, res) { var body = http.STATUS_CODES[426]; res.writeHead(426, { @@ -57,21 +60,21 @@ function WebSocketServer(options, callback) { res.end(body); }); this._server.allowHalfOpen = false; - this._server.listen(options.value.port, options.value.host, callback); + this._server.listen(options.port, options.host, callback); this._closeServer = function() { if (self._server) self._server.close(); }; } - else if (options.value.server) { - this._server = options.value.server; - if (options.value.path) { + else if (options.server) { + this._server = options.server; + if (options.path) { // take note of the path, to avoid collisions when multiple websocket servers are // listening on the same http server - if (this._server._webSocketPaths && options.value.server._webSocketPaths[options.value.path]) { + if (this._server._webSocketPaths && options.server._webSocketPaths[options.path]) { throw new Error('two instances of WebSocketServer cannot listen on the same http server path'); } if (typeof this._server._webSocketPaths !== 'object') { this._server._webSocketPaths = {}; } - this._server._webSocketPaths[options.value.path] = 1; + this._server._webSocketPaths[options.path] = 1; } } if (this._server) { @@ -95,8 +98,8 @@ function WebSocketServer(options, callback) { this._server.on('upgrade', this._onServerUpgrade); } - this.options = options.value; - this.path = options.value.path; + this.options = options; + this.path = options.path; this.clients = []; } diff --git a/package.json b/package.json index 9a06b04c1..68d9f52e0 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "test": "make test" }, "dependencies": { - "options": ">=0.0.5", "ultron": "1.0.x" }, "devDependencies": { From 500aabadde29f2d6ad4c679fb890bc17e2c75e3d Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Wed, 15 Jun 2016 13:05:44 +0800 Subject: [PATCH 056/669] reduce usage of self = this --- lib/Receiver.js | 142 ++++++++++++++++++++--------------------- lib/WebSocket.js | 9 ++- lib/WebSocketServer.js | 3 +- 3 files changed, 76 insertions(+), 78 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index a8441dfb0..771297834 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -344,11 +344,10 @@ Receiver.prototype.flush = function() { if (!handler) return; this.processing = true; - var self = this; - handler(function() { - self.processing = false; - self.flush(); + handler(() => { + this.processing = false; + this.flush(); }); }; @@ -359,10 +358,10 @@ Receiver.prototype.flush = function() { */ Receiver.prototype.applyExtensions = function(messageBuffer, fin, compressed, callback) { - var self = this; if (compressed) { - this.extensions[PerMessageDeflate.extensionName].decompress(messageBuffer, fin, function(err, buffer) { - if (self.dead) return; + var extension = this.extensions[PerMessageDeflate.extensionName]; + extension.decompress(messageBuffer, fin, (err, buffer) => { + if (this.dead) return; if (err) { callback(new Error('invalid compressed data')); return; @@ -447,91 +446,90 @@ var opcodes = { // text '1': { start: function(data) { - var self = this; // decode length var firstLength = data[1] & 0x7f; if (firstLength < 126) { - if (self.maxPayloadExceeded(firstLength)){ - self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); + if (this.maxPayloadExceeded(firstLength)){ + this.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); return; } - opcodes['1'].getData.call(self, firstLength); + opcodes['1'].getData.call(this, firstLength); } else if (firstLength == 126) { - self.expectHeader(2, function(data) { + this.expectHeader(2, (data) => { var length = readUInt16BE.call(data, 0); - if (self.maxPayloadExceeded(length)){ - self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); + if (this.maxPayloadExceeded(length)){ + this.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); return; } - opcodes['1'].getData.call(self, length); + opcodes['1'].getData.call(this, length); }); } else if (firstLength == 127) { - self.expectHeader(8, function(data) { + this.expectHeader(8, (data) => { if (readUInt32BE.call(data, 0) != 0) { - self.error('packets with length spanning more than 32 bit is currently not supported', 1008); + this.error('packets with length spanning more than 32 bit is currently not supported', 1008); return; } var length = readUInt32BE.call(data, 4); - if (self.maxPayloadExceeded(length)){ - self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); + if (this.maxPayloadExceeded(length)){ + this.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); return; } - opcodes['1'].getData.call(self, readUInt32BE.call(data, 4)); + opcodes['1'].getData.call(this, readUInt32BE.call(data, 4)); }); } }, getData: function(length) { - var self = this; - if (self.state.masked) { - self.expectHeader(4, function(data) { + if (this.state.masked) { + this.expectHeader(4, (data) => { var mask = data; - self.expectData(length, function(data) { - opcodes['1'].finish.call(self, mask, data); + this.expectData(length, (data) => { + opcodes['1'].finish.call(this, mask, data); }); }); } else { - self.expectData(length, function(data) { - opcodes['1'].finish.call(self, null, data); + this.expectData(length, (data) => { + opcodes['1'].finish.call(this, null, data); }); } }, finish: function(mask, data) { - var self = this; var packet = this.unmask(mask, data, true) || new Buffer(0); var state = clone(this.state); - this.messageHandlers.push(function(callback) { - self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { + this.messageHandlers.push((callback) => { + this.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { if (err) { if (err.type === 1009){ - return self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); + return this.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); } - return self.error(err.message, 1007); + return this.error(err.message, 1007); } + if (buffer != null) { - if ( self.maxPayload == 0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ - self.currentMessage.push(buffer); + if (this.maxPayload == 0 || (this.maxPayload > 0 && + (this.currentMessageLength + buffer.length) < this.maxPayload) ){ + this.currentMessage.push(buffer); } else { - self.currentMessage = null; - self.currentMessage = []; - self.currentMessageLength = 0; - self.error(new Error('Maximum payload exceeded. maxPayload: ' + self.maxPayload), 1009); + this.currentMessage = null; + this.currentMessage = []; + this.currentMessageLength = 0; + this.error(new Error('Maximum payload exceeded. maxPayload: ' + this.maxPayload), 1009); return; } - self.currentMessageLength += buffer.length; + this.currentMessageLength += buffer.length; } if (state.lastFragment) { - var messageBuffer = Buffer.concat(self.currentMessage); - self.currentMessage = []; - self.currentMessageLength = 0; + var messageBuffer = Buffer.concat(this.currentMessage); + this.currentMessage = []; + this.currentMessageLength = 0; if (!Validation.isValidUTF8(messageBuffer)) { - self.error('invalid utf8 sequence', 1007); + this.error('invalid utf8 sequence', 1007); return; } - self.ontext(messageBuffer.toString('utf8'), {masked: state.masked, buffer: messageBuffer}); + this.ontext(messageBuffer.toString('utf8'), {masked: state.masked, buffer: messageBuffer}); } callback(); }); @@ -543,54 +541,52 @@ var opcodes = { // binary '2': { start: function(data) { - var self = this; // decode length var firstLength = data[1] & 0x7f; if (firstLength < 126) { - if (self.maxPayloadExceeded(firstLength)){ - self.error('Max payload exceeded in compressed text message. Aborting...', 1009); + if (this.maxPayloadExceeded(firstLength)){ + this.error('Max payload exceeded in compressed text message. Aborting...', 1009); return; } - opcodes['2'].getData.call(self, firstLength); + opcodes['2'].getData.call(this, firstLength); } else if (firstLength == 126) { - self.expectHeader(2, function(data) { + this.expectHeader(2, (data) => { var length = readUInt16BE.call(data, 0); - if (self.maxPayloadExceeded(length)){ - self.error('Max payload exceeded in compressed text message. Aborting...', 1009); + if (this.maxPayloadExceeded(length)){ + this.error('Max payload exceeded in compressed text message. Aborting...', 1009); return; } - opcodes['2'].getData.call(self, length); + opcodes['2'].getData.call(this, length); }); } else if (firstLength == 127) { - self.expectHeader(8, function(data) { + this.expectHeader(8, (data) => { if (readUInt32BE.call(data, 0) != 0) { - self.error('packets with length spanning more than 32 bit is currently not supported', 1008); + this.error('packets with length spanning more than 32 bit is currently not supported', 1008); return; } var length = readUInt32BE.call(data, 4, true); - if (self.maxPayloadExceeded(length)){ - self.error('Max payload exceeded in compressed text message. Aborting...', 1009); + if (this.maxPayloadExceeded(length)){ + this.error('Max payload exceeded in compressed text message. Aborting...', 1009); return; } - opcodes['2'].getData.call(self, length); + opcodes['2'].getData.call(this, length); }); } }, getData: function(length) { - var self = this; - if (self.state.masked) { - self.expectHeader(4, function(data) { + if (this.state.masked) { + this.expectHeader(4, (data) => { var mask = data; - self.expectData(length, function(data) { - opcodes['2'].finish.call(self, mask, data); + this.expectData(length, (data) => { + opcodes['2'].finish.call(this, mask, data); }); }); } else { - self.expectData(length, function(data) { - opcodes['2'].finish.call(self, null, data); + this.expectData(length, (data) => { + opcodes['2'].finish.call(this, null, data); }); } }, @@ -761,27 +757,25 @@ var opcodes = { } }, getData: function(length) { - var self = this; if (this.state.masked) { - this.expectHeader(4, function(data) { + this.expectHeader(4, (data) => { var mask = data; - self.expectData(length, function(data) { - opcodes['10'].finish.call(self, mask, data); + this.expectData(length, (data) => { + opcodes['10'].finish.call(this, mask, data); }); }); } else { - this.expectData(length, function(data) { - opcodes['10'].finish.call(self, null, data); + this.expectData(length, (data) => { + opcodes['10'].finish.call(this, null, data); }); } }, finish: function(mask, data) { - var self = this; - data = self.unmask(mask, data, true); + data = this.unmask(mask, data, true); var state = clone(this.state); - this.messageHandlers.push(function(callback) { - self.onpong(data, {masked: state.masked, binary: true}); + this.messageHandlers.push((callback) => { + this.onpong(data, {masked: state.masked, binary: true}); callback(); }); this.flush(); diff --git a/lib/WebSocket.js b/lib/WebSocket.js index a2f5ff553..684a734b8 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -269,7 +269,8 @@ WebSocket.prototype.send = function send(data, options, cb) { * Streams data through calls to a user supplied function * * @param {Object} Members - mask: boolean, binary: boolean, compress: boolean - * @param {function} 'function (error, send)' which is executed on successive ticks of which send is 'function (data, final)'. + * @param {function} 'function (error, send)' which is executed on successive + * ticks of which send is 'function (data, final)'. * @api public */ WebSocket.prototype.stream = function stream(options, cb) { @@ -603,7 +604,8 @@ function initAsClient(address, protocols, options) { var extensionsOffer = {}; var perMessageDeflate; if (options.perMessageDeflate) { - perMessageDeflate = new PerMessageDeflate(options.perMessageDeflate !== true ? options.perMessageDeflate : {}, false); + var opts = options.perMessageDeflate !== true ? options.perMessageDeflate : {}; + perMessageDeflate = new PerMessageDeflate(opts, false); extensionsOffer[PerMessageDeflate.extensionName] = perMessageDeflate.offer(); } @@ -674,7 +676,8 @@ function initAsClient(address, protocols, options) { if (isDefinedAndNonNull(options, 'cert')) requestOptions.cert = options.cert; if (isDefinedAndNonNull(options, 'ca')) requestOptions.ca = options.ca; if (isDefinedAndNonNull(options, 'ciphers')) requestOptions.ciphers = options.ciphers; - if (isDefinedAndNonNull(options, 'rejectUnauthorized')) requestOptions.rejectUnauthorized = options.rejectUnauthorized; + if (isDefinedAndNonNull(options, 'rejectUnauthorized')) + requestOptions.rejectUnauthorized = options.rejectUnauthorized; if (!agent) { // global agent ignores client side certificates diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 4047c0ce3..ee819a5a3 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -370,7 +370,8 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { wshost = req.headers.host; else wshost = req.headers['x-forwarded-host']; - var location = ((req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws') + '://' + wshost + req.url + var proto = (req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws'; + var location = (proto + '://' + wshost + req.url) , protocol = req.headers['sec-websocket-protocol']; // build the response header and return a Buffer From 3e1caf42088c7cd236f23b972917588368ad8531 Mon Sep 17 00:00:00 2001 From: Arnout Kazemier Date: Fri, 24 Jun 2016 14:16:30 +0200 Subject: [PATCH 057/669] [fix] Default to a sane value --- lib/WebSocketServer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 4047c0ce3..9174b2adb 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -41,7 +41,7 @@ function WebSocketServer(options, callback) { disableHixie: false, clientTracking: true, perMessageDeflate: true, - maxPayload: null + maxPayload: 100 * 1024 * 1024 }, options); if (!isDefinedAndNonNull(options, 'port') && !isDefinedAndNonNull(options, 'server') && !options.noServer) { From 0328a8f49f004f98d2913016214e93b2fc2713bc Mon Sep 17 00:00:00 2001 From: Arnout Kazemier Date: Fri, 24 Jun 2016 14:21:57 +0200 Subject: [PATCH 058/669] [fix] Default to a sane value --- lib/WebSocketServer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 476cf7100..92077cd5a 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -37,7 +37,7 @@ function WebSocketServer(options, callback) { disableHixie: false, clientTracking: true, perMessageDeflate: true, - maxPayload: null + maxPayload: 100 * 1024 * 1024 }).merge(options); if (!options.isDefinedAndNonNull('port') && !options.isDefinedAndNonNull('server') && !options.value.noServer) { From c7bb7306cb0e1d17df141f61a220056eaa5e3502 Mon Sep 17 00:00:00 2001 From: Arnout Kazemier Date: Fri, 24 Jun 2016 14:22:19 +0200 Subject: [PATCH 059/669] [dist] 1.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b4968c3f3..1a7b69893 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "simple to use, blazing fast and thoroughly tested websocket client, server and console for node.js, up-to-date against RFC-6455", - "version": "1.1.0", + "version": "1.1.1", "license": "MIT", "main": "index.js", "keywords": [ From 9b021ea6acc8108f552162fea1cf094d3d55f8a9 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 28 Jun 2016 16:40:07 +1000 Subject: [PATCH 060/669] wss: fix typo s/sepcific/specific (#765) --- lib/WebSocketServer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 9174b2adb..26fea6f3d 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -181,7 +181,7 @@ module.exports = WebSocketServer; /** * Entirely private apis, - * which may or may not be bound to a sepcific WebSocket instance. + * which may or may not be bound to a specific WebSocket instance. */ function handleHybiUpgrade(req, socket, upgradeHead, cb) { From e022a50458e1fb3eece5974e1928b87bf1a3e1fa Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Mon, 13 Jun 2016 16:39:16 +0800 Subject: [PATCH 061/669] add comman-style rule and update code --- .eslintrc | 3 ++- lib/Receiver.hixie.js | 6 ++--- lib/Receiver.js | 11 ++++----- lib/Sender.hixie.js | 18 +++++++-------- lib/Sender.js | 24 +++++++++---------- lib/WebSocket.js | 34 +++++++++++++-------------- lib/WebSocketServer.js | 52 +++++++++++++++++++++--------------------- 7 files changed, 73 insertions(+), 75 deletions(-) diff --git a/.eslintrc b/.eslintrc index 1abddeefd..a9f44bb73 100644 --- a/.eslintrc +++ b/.eslintrc @@ -12,7 +12,8 @@ "linebreak-style": [2, "unix"], "comma-dangle": ["error", "never"], "no-unreachable": [2], - "comma-spacing": 2 + "comma-spacing": 2, + "comma-style": ["error", "last"] }, "env": { "es6": true, diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index 88f803c9e..999fe6c3a 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -10,10 +10,8 @@ * State constants */ -var EMPTY = 0 - , BODY = 1; -var BINARYLENGTH = 2 - , BINARYBODY = 3; +var EMPTY = 0, BODY = 1; +var BINARYLENGTH = 2, BINARYBODY = 3; /** * Hixie Receiver implementation diff --git a/lib/Receiver.js b/lib/Receiver.js index 771297834..da27fe586 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -6,11 +6,11 @@ 'use strict'; -var Validation = require('./Validation').Validation - , ErrorCodes = require('./ErrorCodes') - , BufferPool = require('./BufferPool') - , bufferUtil = require('./BufferUtil').BufferUtil - , PerMessageDeflate = require('./PerMessageDeflate'); +var Validation = require('./Validation').Validation, + ErrorCodes = require('./ErrorCodes'), + BufferPool = require('./BufferPool'), + bufferUtil = require('./BufferUtil').BufferUtil, + PerMessageDeflate = require('./PerMessageDeflate'); /** * HyBi Receiver implementation @@ -26,7 +26,6 @@ function Receiver (extensions, maxPayload) { extensions = {}; } - // memory pool for fragmented messages var fragmentedPoolPrevUsed = -1; this.fragmentedBufferPool = new BufferPool(1024, function(db, length) { diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index 0b6271e3e..83f647633 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -6,8 +6,8 @@ 'use strict'; -var events = require('events') - , util = require('util'); +var events = require('events'), + util = require('util'); /** * Hixie Sender implementation @@ -42,13 +42,13 @@ util.inherits(Sender, events.EventEmitter); Sender.prototype.send = function(data, options, cb) { if (this.isClosed) return; - var isString = typeof data == 'string' - , length = isString ? Buffer.byteLength(data) : data.length - , lengthbytes = (length > 127) ? 2 : 1 // assume less than 2**14 bytes - , writeStartMarker = this.continuationFrame == false - , writeEndMarker = !options || !(typeof options.fin != 'undefined' && !options.fin) - , buffer = new Buffer((writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0) + length + ((writeEndMarker && !(options && options.binary)) ? 1 : 0)) - , offset = writeStartMarker ? 1 : 0; + var isString = typeof data == 'string', + length = isString ? Buffer.byteLength(data) : data.length, + lengthbytes = (length > 127) ? 2 : 1, // assume less than 2**14 bytes + writeStartMarker = this.continuationFrame == false, + writeEndMarker = !options || !(typeof options.fin != 'undefined' && !options.fin), + buffer = new Buffer((writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0) + length + ((writeEndMarker && !(options && options.binary)) ? 1 : 0)), + offset = writeStartMarker ? 1 : 0; if (writeStartMarker) { if (options && options.binary) { diff --git a/lib/Sender.js b/lib/Sender.js index b9a355be9..8d09c4751 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -6,11 +6,11 @@ 'use strict'; -var events = require('events') - , util = require('util') - , ErrorCodes = require('./ErrorCodes') - , bufferUtil = require('./BufferUtil').BufferUtil - , PerMessageDeflate = require('./PerMessageDeflate'); +var events = require('events'), + util = require('util'), + ErrorCodes = require('./ErrorCodes'), + bufferUtil = require('./BufferUtil').BufferUtil, + PerMessageDeflate = require('./PerMessageDeflate'); /** * HyBi Sender implementation @@ -168,9 +168,9 @@ Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, } } - var dataLength = data.length - , dataOffset = maskData ? 6 : 2 - , secondByte = dataLength; + var dataLength = data.length, + dataOffset = maskData ? 6 : 2, + secondByte = dataLength; if (dataLength >= 65536) { dataOffset += 8; @@ -305,10 +305,10 @@ function writeUInt32BE(value, offset) { function getArrayBuffer(data) { // data is either an ArrayBuffer or ArrayBufferView. - var array = new Uint8Array(data.buffer || data) - , l = data.byteLength || data.length - , o = data.byteOffset || 0 - , buffer = new Buffer(l); + var array = new Uint8Array(data.buffer || data), + l = data.byteLength || data.length, + o = data.byteOffset || 0, + buffer = new Buffer(l); for (var i = 0; i < l; ++i) { buffer[i] = array[o + i]; } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 684a734b8..30f02e0a6 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -6,20 +6,20 @@ 'use strict'; -var url = require('url') - , util = require('util') - , http = require('http') - , https = require('https') - , crypto = require('crypto') - , stream = require('stream') - , Ultron = require('ultron') - , Sender = require('./Sender') - , Receiver = require('./Receiver') - , SenderHixie = require('./Sender.hixie') - , ReceiverHixie = require('./Receiver.hixie') - , Extensions = require('./Extensions') - , PerMessageDeflate = require('./PerMessageDeflate') - , EventEmitter = require('events').EventEmitter; +var url = require('url'), + util = require('util'), + http = require('http'), + https = require('https'), + crypto = require('crypto'), + stream = require('stream'), + Ultron = require('ultron'), + Sender = require('./Sender'), + Receiver = require('./Receiver'), + SenderHixie = require('./Sender.hixie'), + ReceiverHixie = require('./Receiver.hixie'), + Extensions = require('./Extensions'), + PerMessageDeflate = require('./PerMessageDeflate'), + EventEmitter = require('events').EventEmitter; var isDefinedAndNonNull = function (options, key) { return typeof options[key] != 'undefined' && options[key] !== null; @@ -788,9 +788,9 @@ function initAsClient(address, protocols, options) { } function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { - var ultron = this._ultron = new Ultron(socket) - , called = false - , self = this; + var ultron = this._ultron = new Ultron(socket), + called = false, + self = this; socket.setTimeout(0); socket.setNoDelay(true); diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 49e99aa0a..9ffaed6b0 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -6,14 +6,14 @@ 'use strict'; -var util = require('util') - , events = require('events') - , http = require('http') - , crypto = require('crypto') - , WebSocket = require('./WebSocket') - , Extensions = require('./Extensions') - , PerMessageDeflate = require('./PerMessageDeflate') - , url = require('url'); +var util = require('util'), + events = require('events'), + http = require('http'), + crypto = require('crypto'), + WebSocket = require('./WebSocket'), + Extensions = require('./Extensions'), + PerMessageDeflate = require('./PerMessageDeflate'), + url = require('url'); var isDefinedAndNonNull = function (options, key) { return typeof options[key] != 'undefined' && options[key] !== null; @@ -226,10 +226,10 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { key = shasum.digest('base64'); var headers = [ - 'HTTP/1.1 101 Switching Protocols' - , 'Upgrade: websocket' - , 'Connection: Upgrade' - , 'Sec-WebSocket-Accept: ' + key + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: websocket', + 'Connection: Upgrade', + 'Sec-WebSocket-Accept: ' + key ]; if (typeof protocol != 'undefined') { @@ -360,8 +360,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { return; } - var origin = req.headers['origin'] - , self = this; + var origin = req.headers['origin'], self = this; // setup handshake completion to run after client has been verified var onClientVerified = function() { @@ -369,18 +368,19 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { if (!req.headers['x-forwarded-host']) wshost = req.headers.host; else - wshost = req.headers['x-forwarded-host']; + wshost = req.headers['x-forwarded-host']; + var proto = (req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws'; - var location = (proto + '://' + wshost + req.url) - , protocol = req.headers['sec-websocket-protocol']; + var location = (proto + '://' + wshost + req.url), + protocol = req.headers['sec-websocket-protocol']; // build the response header and return a Buffer var buildResponseHeader = function() { var headers = [ - 'HTTP/1.1 101 Switching Protocols' - , 'Upgrade: WebSocket' - , 'Connection: Upgrade' - , 'Sec-WebSocket-Location: ' + location + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: WebSocket', + 'Connection: Upgrade', + 'Sec-WebSocket-Location: ' + location ]; if (typeof protocol != 'undefined') headers.push('Sec-WebSocket-Protocol: ' + protocol); if (typeof origin != 'undefined') headers.push('Sec-WebSocket-Origin: ' + origin); @@ -411,13 +411,13 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { // handshake completion code to run once nonce has been successfully retrieved var completeHandshake = function(nonce, rest, headerBuffer) { // calculate key - var k1 = req.headers['sec-websocket-key1'] - , k2 = req.headers['sec-websocket-key2'] - , md5 = crypto.createHash('md5'); + var k1 = req.headers['sec-websocket-key1'], + k2 = req.headers['sec-websocket-key2'], + md5 = crypto.createHash('md5'); [k1, k2].forEach(function (k) { - var n = parseInt(k.replace(/[^\d]/g, '')) - , spaces = k.replace(/[^ ]/g, '').length; + var n = parseInt(k.replace(/[^\d]/g, '')), + spaces = k.replace(/[^ ]/g, '').length; if (spaces === 0 || n % spaces !== 0){ abortConnection(socket, 400, 'Bad Request'); return; From 14a8a8a553efcb501a6a6717e2f4a204dca73169 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Thu, 30 Jun 2016 10:58:57 +0800 Subject: [PATCH 062/669] use const --- lib/PerMessageDeflate.js | 8 ++++---- lib/Receiver.hixie.js | 4 ++-- lib/Receiver.js | 10 +++++----- lib/Sender.hixie.js | 4 ++-- lib/Sender.js | 10 +++++----- lib/WebSocket.js | 28 ++++++++++++++-------------- lib/WebSocketServer.js | 21 ++++++++++++--------- 7 files changed, 44 insertions(+), 41 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index a55b8cc1c..0be90f2d5 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -1,10 +1,10 @@ 'use strict'; -var zlib = require('zlib'); +const zlib = require('zlib'); -var AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15]; -var DEFAULT_WINDOW_BITS = 15; -var DEFAULT_MEM_LEVEL = 8; +const AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15]; +const DEFAULT_WINDOW_BITS = 15; +const DEFAULT_MEM_LEVEL = 8; PerMessageDeflate.extensionName = 'permessage-deflate'; diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index 999fe6c3a..5071b3313 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -10,8 +10,8 @@ * State constants */ -var EMPTY = 0, BODY = 1; -var BINARYLENGTH = 2, BINARYBODY = 3; +const EMPTY = 0, BODY = 1; +const BINARYLENGTH = 2, BINARYBODY = 3; /** * Hixie Receiver implementation diff --git a/lib/Receiver.js b/lib/Receiver.js index da27fe586..8cd54a719 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -6,11 +6,11 @@ 'use strict'; -var Validation = require('./Validation').Validation, - ErrorCodes = require('./ErrorCodes'), - BufferPool = require('./BufferPool'), - bufferUtil = require('./BufferUtil').BufferUtil, - PerMessageDeflate = require('./PerMessageDeflate'); +const Validation = require('./Validation').Validation; +const ErrorCodes = require('./ErrorCodes'); +const BufferPool = require('./BufferPool'); +const bufferUtil = require('./BufferUtil').BufferUtil; +const PerMessageDeflate = require('./PerMessageDeflate'); /** * HyBi Receiver implementation diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index 83f647633..af55b1be6 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -6,8 +6,8 @@ 'use strict'; -var events = require('events'), - util = require('util'); +const events = require('events'); +const util = require('util'); /** * Hixie Sender implementation diff --git a/lib/Sender.js b/lib/Sender.js index 8d09c4751..a6612f07f 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -6,11 +6,11 @@ 'use strict'; -var events = require('events'), - util = require('util'), - ErrorCodes = require('./ErrorCodes'), - bufferUtil = require('./BufferUtil').BufferUtil, - PerMessageDeflate = require('./PerMessageDeflate'); +const events = require('events'); +const util = require('util'); +const ErrorCodes = require('./ErrorCodes'); +const bufferUtil = require('./BufferUtil').BufferUtil; +const PerMessageDeflate = require('./PerMessageDeflate'); /** * HyBi Sender implementation diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 30f02e0a6..06cec66b9 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -6,20 +6,20 @@ 'use strict'; -var url = require('url'), - util = require('util'), - http = require('http'), - https = require('https'), - crypto = require('crypto'), - stream = require('stream'), - Ultron = require('ultron'), - Sender = require('./Sender'), - Receiver = require('./Receiver'), - SenderHixie = require('./Sender.hixie'), - ReceiverHixie = require('./Receiver.hixie'), - Extensions = require('./Extensions'), - PerMessageDeflate = require('./PerMessageDeflate'), - EventEmitter = require('events').EventEmitter; +const url = require('url'); +const util = require('util'); +const http = require('http'); +const https = require('https'); +const crypto = require('crypto'); +const stream = require('stream'); +const Ultron = require('ultron'); +const Sender = require('./Sender'); +const Receiver = require('./Receiver'); +const SenderHixie = require('./Sender.hixie'); +const ReceiverHixie = require('./Receiver.hixie'); +const Extensions = require('./Extensions'); +const PerMessageDeflate = require('./PerMessageDeflate'); +const EventEmitter = require('events').EventEmitter; var isDefinedAndNonNull = function (options, key) { return typeof options[key] != 'undefined' && options[key] !== null; diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 9ffaed6b0..e63f06230 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -6,14 +6,14 @@ 'use strict'; -var util = require('util'), - events = require('events'), - http = require('http'), - crypto = require('crypto'), - WebSocket = require('./WebSocket'), - Extensions = require('./Extensions'), - PerMessageDeflate = require('./PerMessageDeflate'), - url = require('url'); +const util = require('util'); +const events = require('events'); +const http = require('http'); +const crypto = require('crypto'); +const WebSocket = require('./WebSocket'); +const Extensions = require('./Extensions'); +const PerMessageDeflate = require('./PerMessageDeflate'); +const url = require('url'); var isDefinedAndNonNull = function (options, key) { return typeof options[key] != 'undefined' && options[key] !== null; @@ -61,7 +61,10 @@ function WebSocketServer(options, callback) { }); this._server.allowHalfOpen = false; this._server.listen(options.port, options.host, callback); - this._closeServer = function() { if (self._server) self._server.close(); }; + this._closeServer = function() { + if (self._server) + self._server.close(); + }; } else if (options.server) { this._server = options.server; From 7c57252a7936eee166cd8bf48d9fd467b259a354 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Thu, 30 Jun 2016 11:20:53 +0800 Subject: [PATCH 063/669] use EventEmittor = require('events') directly --- lib/Sender.hixie.js | 6 +++--- lib/Sender.js | 6 +++--- lib/WebSocketServer.js | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index af55b1be6..b17853331 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -6,7 +6,7 @@ 'use strict'; -const events = require('events'); +const EventEmitter = require('events'); const util = require('util'); /** @@ -18,7 +18,7 @@ function Sender(socket) { throw new TypeError("Classes can't be function-called"); } - events.EventEmitter.call(this); + EventEmitter.call(this); this.socket = socket; this.continuationFrame = false; @@ -31,7 +31,7 @@ module.exports = Sender; * Inherits from EventEmitter. */ -util.inherits(Sender, events.EventEmitter); +util.inherits(Sender, EventEmitter); /** * Frames and writes data. diff --git a/lib/Sender.js b/lib/Sender.js index a6612f07f..366476cae 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -6,7 +6,7 @@ 'use strict'; -const events = require('events'); +const EventEmitter = require('events'); const util = require('util'); const ErrorCodes = require('./ErrorCodes'); const bufferUtil = require('./BufferUtil').BufferUtil; @@ -21,7 +21,7 @@ function Sender(socket, extensions) { throw new TypeError("Classes can't be function-called"); } - events.EventEmitter.call(this); + EventEmitter.call(this); this._socket = socket; this.extensions = extensions || {}; @@ -35,7 +35,7 @@ function Sender(socket, extensions) { * Inherits from EventEmitter. */ -util.inherits(Sender, events.EventEmitter); +util.inherits(Sender, EventEmitter); /** * Sends a close instruction to the remote party. diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index e63f06230..f8a480fd0 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -7,7 +7,7 @@ 'use strict'; const util = require('util'); -const events = require('events'); +const EventEmitter = require('events'); const http = require('http'); const crypto = require('crypto'); const WebSocket = require('./WebSocket'); @@ -28,7 +28,7 @@ function WebSocketServer(options, callback) { return new WebSocketServer(options, callback); } - events.EventEmitter.call(this); + EventEmitter.call(this); options = Object.assign({ host: '0.0.0.0', @@ -110,7 +110,7 @@ function WebSocketServer(options, callback) { * Inherits from EventEmitter. */ -util.inherits(WebSocketServer, events.EventEmitter); +util.inherits(WebSocketServer, EventEmitter); /** * Immediately shuts down the connection. From 3e00cae77dbee4df2c5045d703cfefc71ab25f6b Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Thu, 30 Jun 2016 12:25:39 +0800 Subject: [PATCH 064/669] remove the NODE_PATH environment variable --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 3e8a47c5f..c2e44181c 100644 --- a/Makefile +++ b/Makefile @@ -27,13 +27,13 @@ run-coverage: $(TESTS) test: lint - @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_PATH=lib TESTS="$(ALL_TESTS)" run-tests + @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 TESTS="$(ALL_TESTS)" run-tests integrationtest: - @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_PATH=lib TESTS="$(ALL_INTEGRATION)" run-integrationtests + @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 TESTS="$(ALL_INTEGRATION)" run-integrationtests coverage: - @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_PATH=lib TESTS="$(ALL_TESTS)" run-coverage + @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 TESTS="$(ALL_TESTS)" run-coverage benchmark: @node bench/sender.benchmark.js From 0e6bf5c7a31be9d31150ce34c74775726571dcee Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Thu, 30 Jun 2016 14:32:05 +0800 Subject: [PATCH 065/669] Use native writeUInt{16|32}BE method --- lib/Sender.js | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 366476cae..925bd98c7 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -50,7 +50,7 @@ Sender.prototype.close = function(code, data, mask, cb) { } code = code || 1000; var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0)); - writeUInt16BE.call(dataBuffer, code, 0); + dataBuffer.writeUInt16BE(code, 0); if (dataBuffer.length > 2) dataBuffer.write(data, 2); var self = this; @@ -189,11 +189,11 @@ Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, switch (secondByte) { case 126: - writeUInt16BE.call(outputBuffer, dataLength, 2); + outputBuffer.writeUInt16BE(dataLength, 2); break; case 127: - writeUInt32BE.call(outputBuffer, 0, 2); - writeUInt32BE.call(outputBuffer, dataLength, 6); + outputBuffer.writeUInt32BE(0, 2); + outputBuffer.writeUInt32BE(dataLength, 6); } if (maskData) { @@ -291,18 +291,6 @@ Sender.prototype.applyExtensions = function(data, fin, compress, callback) { module.exports = Sender; -function writeUInt16BE(value, offset) { - this[offset] = (value & 0xff00) >> 8; - this[offset + 1] = value & 0xff; -} - -function writeUInt32BE(value, offset) { - this[offset] = (value & 0xff000000) >> 24; - this[offset + 1] = (value & 0xff0000) >> 16; - this[offset + 2] = (value & 0xff00) >> 8; - this[offset + 3] = value & 0xff; -} - function getArrayBuffer(data) { // data is either an ArrayBuffer or ArrayBufferView. var array = new Uint8Array(data.buffer || data), From 4e285c4e91f77e498c7a8ded20dbd8189169d465 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Thu, 14 Jul 2016 11:39:23 +0800 Subject: [PATCH 066/669] add max-len rule --- .eslintrc | 3 ++- Makefile | 6 +++--- lib/Receiver.js | 6 ++++-- lib/Sender.hixie.js | 19 ++++++++++++------- lib/Sender.js | 4 +++- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/.eslintrc b/.eslintrc index a9f44bb73..e50702170 100644 --- a/.eslintrc +++ b/.eslintrc @@ -13,7 +13,8 @@ "comma-dangle": ["error", "never"], "no-unreachable": [2], "comma-spacing": 2, - "comma-style": ["error", "last"] + "comma-style": ["error", "last"], + "max-len": ["error", 120] }, "env": { "es6": true, diff --git a/Makefile b/Makefile index c2e44181c..920bc1ff1 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ -ALL_TESTS = $(shell find test/ -name '*.test.js') -ALL_INTEGRATION = $(shell find test/ -name '*.integration.js') +ALL_TESTS = $(shell find test -name '*.test.js') +ALL_INTEGRATION = $(shell find test -name '*.integration.js') lint: - @./node_modules/.bin/eslint lib + @./node_modules/.bin/eslint lib index.js run-tests: @./node_modules/.bin/mocha \ diff --git a/lib/Receiver.js b/lib/Receiver.js index 8cd54a719..87d616922 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -602,8 +602,10 @@ var opcodes = { return self.error(err.message, 1007); } if (buffer != null) { - if ( self.maxPayload == 0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ + var length = (self.currentMessageLength + buffer.length); + if (self.maxPayload == 0 || (self.maxPayload > 0 && length < self.maxPayload)) { self.currentMessage.push(buffer); + self.currentMessageLength += length; } else { self.currentMessage = null; @@ -612,8 +614,8 @@ var opcodes = { self.error(new Error('Maximum payload exceeded'), 1009); return; } - self.currentMessageLength += buffer.length; } + if (state.lastFragment) { var messageBuffer = Buffer.concat(self.currentMessage); self.currentMessage = []; diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index b17853331..8e5ec6188 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -42,13 +42,18 @@ util.inherits(Sender, EventEmitter); Sender.prototype.send = function(data, options, cb) { if (this.isClosed) return; - var isString = typeof data == 'string', - length = isString ? Buffer.byteLength(data) : data.length, - lengthbytes = (length > 127) ? 2 : 1, // assume less than 2**14 bytes - writeStartMarker = this.continuationFrame == false, - writeEndMarker = !options || !(typeof options.fin != 'undefined' && !options.fin), - buffer = new Buffer((writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0) + length + ((writeEndMarker && !(options && options.binary)) ? 1 : 0)), - offset = writeStartMarker ? 1 : 0; + var isString = typeof data == 'string'; + var length = isString ? Buffer.byteLength(data) : data.length; + var lengthbytes = (length > 127) ? 2 : 1; // assume less than 2**14 bytes + var writeStartMarker = this.continuationFrame == false; + var writeEndMarker = !options || !(typeof options.fin != 'undefined' && !options.fin); + + var bufferLength = writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0; + bufferLength += length; + bufferLength += (writeEndMarker && !(options && options.binary)) ? 1 : 0; + + var buffer = new Buffer(bufferLength); + var offset = writeStartMarker ? 1 : 0; if (writeStartMarker) { if (options && options.binary) { diff --git a/lib/Sender.js b/lib/Sender.js index 925bd98c7..732f855c5 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -142,7 +142,9 @@ Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, if (!data) { try { - this._socket.write(new Buffer([opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)].concat(maskData ? [0, 0, 0, 0] : [])), 'binary', cb); + var buff = [opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)] + .concat(maskData ? [0, 0, 0, 0] : []); + this._socket.write(new Buffer(buff), 'binary', cb); } catch (e) { if (typeof cb == 'function') cb(e); From ec7ee1ab57b167d70544f4946fe180c0d0ae1260 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Thu, 30 Jun 2016 11:13:06 +0800 Subject: [PATCH 067/669] use classes --- lib/BufferPool.js | 74 ++--- lib/PerMessageDeflate.js | 550 ++++++++++++++++---------------- lib/Receiver.hixie.js | 281 ++++++++-------- lib/Receiver.js | 668 +++++++++++++++++++-------------------- lib/Sender.hixie.js | 204 ++++++------ lib/Sender.js | 456 +++++++++++++------------- lib/WebSocketServer.js | 9 +- 7 files changed, 1100 insertions(+), 1142 deletions(-) diff --git a/lib/BufferPool.js b/lib/BufferPool.js index 510d4e956..57517774e 100644 --- a/lib/BufferPool.js +++ b/lib/BufferPool.js @@ -6,58 +6,52 @@ 'use strict'; -function BufferPool(initialSize, growStrategy, shrinkStrategy) { - if (this instanceof BufferPool === false) { - throw new TypeError("Classes can't be function-called"); - } - - this._growStrategy = (growStrategy || function(db, size) { - return db.used + size; - }).bind(null, this); +class BufferPool { + constructor(initialSize, growStrategy, shrinkStrategy) { + this._growStrategy = (growStrategy || function(db, size) { + return db.used + size; + }).bind(null, this); - this._shrinkStrategy = (shrinkStrategy || function(db) { - return initialSize; - }).bind(null, this); + this._shrinkStrategy = (shrinkStrategy || function(db) { + return initialSize; + }).bind(null, this); - this._buffer = new Buffer(initialSize); - this._offset = 0; - this._used = 0; - this._changeFactor = 0; -} + this._buffer = new Buffer(initialSize); + this._offset = 0; + this._used = 0; + this._changeFactor = 0; + } -Object.defineProperty(BufferPool.prototype, 'size', { - get: function() { + get size() { return this._buffer.length; } -}); -Object.defineProperty(BufferPool.prototype, 'used', { - get: function() { + get used() { return this._used; } -}); -BufferPool.prototype.get = function(length) { - if (this._buffer == null || this._offset + length > this._buffer.length) { - var newBuf = new Buffer(this._growStrategy(length)); - this._buffer = newBuf; - this._offset = 0; + get(length) { + if (this._buffer == null || this._offset + length > this._buffer.length) { + var newBuf = new Buffer(this._growStrategy(length)); + this._buffer = newBuf; + this._offset = 0; + } + this._used += length; + var buf = this._buffer.slice(this._offset, this._offset + length); + this._offset += length; + return buf; } - this._used += length; - var buf = this._buffer.slice(this._offset, this._offset + length); - this._offset += length; - return buf; -} -BufferPool.prototype.reset = function(forceNewBuffer) { - var len = this._shrinkStrategy(); - if (len < this.size) this._changeFactor -= 1; - if (forceNewBuffer || this._changeFactor < -2) { - this._changeFactor = 0; - this._buffer = new Buffer(len); + reset(forceNewBuffer) { + var len = this._shrinkStrategy(); + if (len < this.size) this._changeFactor -= 1; + if (forceNewBuffer || this._changeFactor < -2) { + this._changeFactor = 0; + this._buffer = new Buffer(len); + } + this._offset = 0; + this._used = 0; } - this._offset = 0; - this._used = 0; } module.exports = BufferPool; diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 0be90f2d5..c61d6ef4f 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -6,333 +6,327 @@ const AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15]; const DEFAULT_WINDOW_BITS = 15; const DEFAULT_MEM_LEVEL = 8; -PerMessageDeflate.extensionName = 'permessage-deflate'; - /** * Per-message Compression Extensions implementation */ - -function PerMessageDeflate(options, isServer, maxPayload) { - if (this instanceof PerMessageDeflate === false) { - throw new TypeError("Classes can't be function-called"); +class PerMessageDeflate { + constructor(options, isServer, maxPayload) { + this._options = options || {}; + this._isServer = !!isServer; + this._inflate = null; + this._deflate = null; + this.params = null; + this._maxPayload = maxPayload || 0; } - this._options = options || {}; - this._isServer = !!isServer; - this._inflate = null; - this._deflate = null; - this.params = null; - this._maxPayload = maxPayload || 0; -} - -/** - * Create extension parameters offer - * - * @api public - */ + /** + * Create extension parameters offer + * + * @api public + */ -PerMessageDeflate.prototype.offer = function() { - var params = {}; - if (this._options.serverNoContextTakeover) { - params.server_no_context_takeover = true; - } - if (this._options.clientNoContextTakeover) { - params.client_no_context_takeover = true; - } - if (this._options.serverMaxWindowBits) { - params.server_max_window_bits = this._options.serverMaxWindowBits; - } - if (this._options.clientMaxWindowBits) { - params.client_max_window_bits = this._options.clientMaxWindowBits; - } else if (this._options.clientMaxWindowBits == null) { - params.client_max_window_bits = true; + offer() { + var params = {}; + if (this._options.serverNoContextTakeover) { + params.server_no_context_takeover = true; + } + if (this._options.clientNoContextTakeover) { + params.client_no_context_takeover = true; + } + if (this._options.serverMaxWindowBits) { + params.server_max_window_bits = this._options.serverMaxWindowBits; + } + if (this._options.clientMaxWindowBits) { + params.client_max_window_bits = this._options.clientMaxWindowBits; + } else if (this._options.clientMaxWindowBits == null) { + params.client_max_window_bits = true; + } + return params; } - return params; -}; -/** - * Accept extension offer - * - * @api public - */ - -PerMessageDeflate.prototype.accept = function(paramsList) { - paramsList = this.normalizeParams(paramsList); + /** + * Accept extension offer + * + * @api public + */ + accept(paramsList) { + paramsList = this.normalizeParams(paramsList); + + var params; + if (this._isServer) { + params = this.acceptAsServer(paramsList); + } else { + params = this.acceptAsClient(paramsList); + } - var params; - if (this._isServer) { - params = this.acceptAsServer(paramsList); - } else { - params = this.acceptAsClient(paramsList); + this.params = params; + return params; } - this.params = params; - return params; -}; - -/** - * Releases all resources used by the extension - * - * @api public - */ - -PerMessageDeflate.prototype.cleanup = function() { - if (this._inflate) { - if (this._inflate.writeInProgress) { - this._inflate.pendingClose = true; - } else { - if (this._inflate.close) this._inflate.close(); - this._inflate = null; + /** + * Releases all resources used by the extension + * + * @api public + */ + cleanup() { + if (this._inflate) { + if (this._inflate.writeInProgress) { + this._inflate.pendingClose = true; + } else { + if (this._inflate.close) this._inflate.close(); + this._inflate = null; + } } - } - if (this._deflate) { - if (this._deflate.writeInProgress) { - this._deflate.pendingClose = true; - } else { - if (this._deflate.close) this._deflate.close(); - this._deflate = null; + if (this._deflate) { + if (this._deflate.writeInProgress) { + this._deflate.pendingClose = true; + } else { + if (this._deflate.close) this._deflate.close(); + this._deflate = null; + } } } -}; -/** - * Accept extension offer from client - * - * @api private - */ + /** + * Accept extension offer from client + * + * @api private + */ + + acceptAsServer(paramsList) { + var accepted = {}; + var result = paramsList.some(function(params) { + accepted = {}; + if (this._options.serverNoContextTakeover === false && params.server_no_context_takeover) { + return; + } + if (this._options.serverMaxWindowBits === false && params.server_max_window_bits) { + return; + } + if (typeof this._options.serverMaxWindowBits === 'number' && + typeof params.server_max_window_bits === 'number' && + this._options.serverMaxWindowBits > params.server_max_window_bits) { + return; + } + if (typeof this._options.clientMaxWindowBits === 'number' && !params.client_max_window_bits) { + return; + } -PerMessageDeflate.prototype.acceptAsServer = function(paramsList) { - var accepted = {}; - var result = paramsList.some(function(params) { - accepted = {}; - if (this._options.serverNoContextTakeover === false && params.server_no_context_takeover) { - return; - } - if (this._options.serverMaxWindowBits === false && params.server_max_window_bits) { - return; - } - if (typeof this._options.serverMaxWindowBits === 'number' && - typeof params.server_max_window_bits === 'number' && - this._options.serverMaxWindowBits > params.server_max_window_bits) { - return; - } - if (typeof this._options.clientMaxWindowBits === 'number' && !params.client_max_window_bits) { - return; - } + if (this._options.serverNoContextTakeover || params.server_no_context_takeover) { + accepted.server_no_context_takeover = true; + } + if (this._options.clientNoContextTakeover) { + accepted.client_no_context_takeover = true; + } + if (this._options.clientNoContextTakeover !== false && params.client_no_context_takeover) { + accepted.client_no_context_takeover = true; + } + if (typeof this._options.serverMaxWindowBits === 'number') { + accepted.server_max_window_bits = this._options.serverMaxWindowBits; + } else if (typeof params.server_max_window_bits === 'number') { + accepted.server_max_window_bits = params.server_max_window_bits; + } + if (typeof this._options.clientMaxWindowBits === 'number') { + accepted.client_max_window_bits = this._options.clientMaxWindowBits; + } else if (this._options.clientMaxWindowBits !== false && typeof params.client_max_window_bits === 'number') { + accepted.client_max_window_bits = params.client_max_window_bits; + } + return true; + }, this); - if (this._options.serverNoContextTakeover || params.server_no_context_takeover) { - accepted.server_no_context_takeover = true; - } - if (this._options.clientNoContextTakeover) { - accepted.client_no_context_takeover = true; - } - if (this._options.clientNoContextTakeover !== false && params.client_no_context_takeover) { - accepted.client_no_context_takeover = true; - } - if (typeof this._options.serverMaxWindowBits === 'number') { - accepted.server_max_window_bits = this._options.serverMaxWindowBits; - } else if (typeof params.server_max_window_bits === 'number') { - accepted.server_max_window_bits = params.server_max_window_bits; + if (!result) { + throw new Error(`Doesn't support the offered configuration`); } - if (typeof this._options.clientMaxWindowBits === 'number') { - accepted.client_max_window_bits = this._options.clientMaxWindowBits; - } else if (this._options.clientMaxWindowBits !== false && typeof params.client_max_window_bits === 'number') { - accepted.client_max_window_bits = params.client_max_window_bits; - } - return true; - }, this); - if (!result) { - throw new Error(`Doesn't support the offered configuration`); + return accepted; } - return accepted; -}; - -/** - * Accept extension response from server - * - * @api privaye - */ - -PerMessageDeflate.prototype.acceptAsClient = function(paramsList) { - var params = paramsList[0]; - if (this._options.clientNoContextTakeover != null) { - if (this._options.clientNoContextTakeover === false && params.client_no_context_takeover) { - throw new Error('Invalid value for "client_no_context_takeover"'); - } - } - if (this._options.clientMaxWindowBits != null) { - if (this._options.clientMaxWindowBits === false && params.client_max_window_bits) { - throw new Error('Invalid value for "client_max_window_bits"'); + /** + * Accept extension response from server + * + * @api privaye + */ + + acceptAsClient(paramsList) { + var params = paramsList[0]; + if (this._options.clientNoContextTakeover != null) { + if (this._options.clientNoContextTakeover === false && params.client_no_context_takeover) { + throw new Error('Invalid value for "client_no_context_takeover"'); + } } - if (typeof this._options.clientMaxWindowBits === 'number' && - (!params.client_max_window_bits || params.client_max_window_bits > this._options.clientMaxWindowBits)) { - throw new Error('Invalid value for "client_max_window_bits"'); + if (this._options.clientMaxWindowBits != null) { + if (this._options.clientMaxWindowBits === false && params.client_max_window_bits) { + throw new Error('Invalid value for "client_max_window_bits"'); + } + if (typeof this._options.clientMaxWindowBits === 'number' && + (!params.client_max_window_bits || params.client_max_window_bits > this._options.clientMaxWindowBits)) { + throw new Error('Invalid value for "client_max_window_bits"'); + } } + return params; } - return params; -}; - -/** - * Normalize extensions parameters - * - * @api private - */ -PerMessageDeflate.prototype.normalizeParams = function(paramsList) { - return paramsList.map(function(params) { - Object.keys(params).forEach(function(key) { - var value = params[key]; - if (value.length > 1) { - throw new Error('Multiple extension parameters for ' + key); - } + /** + * Normalize extensions parameters + * + * @api private + */ + + normalizeParams(paramsList) { + return paramsList.map(function(params) { + Object.keys(params).forEach(function(key) { + var value = params[key]; + if (value.length > 1) { + throw new Error('Multiple extension parameters for ' + key); + } - value = value[0]; + value = value[0]; - switch (key) { - case 'server_no_context_takeover': - case 'client_no_context_takeover': - if (value !== true) { - throw new Error(`invalid extension parameter value for ${key} (${value})`); - } - params[key] = true; - break; - case 'server_max_window_bits': - case 'client_max_window_bits': - if (typeof value === 'string') { - value = parseInt(value, 10); - if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) { + switch (key) { + case 'server_no_context_takeover': + case 'client_no_context_takeover': + if (value !== true) { throw new Error(`invalid extension parameter value for ${key} (${value})`); } + params[key] = true; + break; + case 'server_max_window_bits': + case 'client_max_window_bits': + if (typeof value === 'string') { + value = parseInt(value, 10); + if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) { + throw new Error(`invalid extension parameter value for ${key} (${value})`); + } + } + if (!this._isServer && value === true) { + throw new Error(`Missing extension parameter value for ${key}`); + } + params[key] = value; + break; + default: + throw new Error(`Not defined extension parameter (${key})`); } - if (!this._isServer && value === true) { - throw new Error(`Missing extension parameter value for ${key}`); - } - params[key] = value; - break; - default: - throw new Error(`Not defined extension parameter (${key})`); - } + }, this); + return params; }, this); - return params; - }, this); -}; + } -/** - * Decompress message - * - * @api public - */ + /** + * Decompress message + * + * @api public + */ + decompress(data, fin, callback) { + var endpoint = this._isServer ? 'client' : 'server'; + + if (!this._inflate) { + var maxWindowBits = this.params[endpoint + '_max_window_bits']; + this._inflate = zlib.createInflateRaw({ + windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS + }); + } + this._inflate.writeInProgress = true; -PerMessageDeflate.prototype.decompress = function (data, fin, callback) { - var endpoint = this._isServer ? 'client' : 'server'; + var self = this; + var buffers = []; + var cumulativeBufferLength = 0; - if (!this._inflate) { - var maxWindowBits = this.params[endpoint + '_max_window_bits']; - this._inflate = zlib.createInflateRaw({ - windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS + this._inflate.on('error', onError).on('data', onData); + this._inflate.write(data); + if (fin) { + this._inflate.write(new Buffer([0x00, 0x00, 0xff, 0xff])); + } + this._inflate.flush(function() { + cleanup(); + callback(null, Buffer.concat(buffers)); }); - } - this._inflate.writeInProgress = true; - var self = this; - var buffers = []; - var cumulativeBufferLength = 0; - - this._inflate.on('error', onError).on('data', onData); - this._inflate.write(data); - if (fin) { - this._inflate.write(new Buffer([0x00, 0x00, 0xff, 0xff])); - } - this._inflate.flush(function() { - cleanup(); - callback(null, Buffer.concat(buffers)); - }); - - function onError(err) { - cleanup(); - callback(err); - } + function onError(err) { + cleanup(); + callback(err); + } - function onData(data) { - if (self._maxPayload !== undefined && self._maxPayload !== null && self._maxPayload > 0){ - cumulativeBufferLength += data.length; - if (cumulativeBufferLength > self._maxPayload){ - buffers = []; - cleanup(); - var err = {type:1009}; - callback(err); - return; + function onData(data) { + if (self._maxPayload !== undefined && self._maxPayload !== null && self._maxPayload > 0){ + cumulativeBufferLength += data.length; + if (cumulativeBufferLength > self._maxPayload){ + buffers = []; + cleanup(); + var err = {type:1009}; + callback(err); + return; + } } + buffers.push(data); } - buffers.push(data); - } - function cleanup() { - if (!self._inflate) return; - self._inflate.removeListener('error', onError); - self._inflate.removeListener('data', onData); - self._inflate.writeInProgress = false; - if ((fin && self.params[endpoint + '_no_context_takeover']) || self._inflate.pendingClose) { - if (self._inflate.close) self._inflate.close(); - self._inflate = null; + function cleanup() { + if (!self._inflate) return; + self._inflate.removeListener('error', onError); + self._inflate.removeListener('data', onData); + self._inflate.writeInProgress = false; + if ((fin && self.params[endpoint + '_no_context_takeover']) || self._inflate.pendingClose) { + if (self._inflate.close) self._inflate.close(); + self._inflate = null; + } } } -}; - -/** - * Compress message - * - * @api public - */ - -PerMessageDeflate.prototype.compress = function (data, fin, callback) { - var endpoint = this._isServer ? 'server' : 'client'; - if (!this._deflate) { - var maxWindowBits = this.params[endpoint + '_max_window_bits']; - this._deflate = zlib.createDeflateRaw({ - flush: zlib.Z_SYNC_FLUSH, - windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS, - memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL + /** + * Compress message + * + * @api public + */ + + compress(data, fin, callback) { + var endpoint = this._isServer ? 'server' : 'client'; + + if (!this._deflate) { + var maxWindowBits = this.params[endpoint + '_max_window_bits']; + this._deflate = zlib.createDeflateRaw({ + flush: zlib.Z_SYNC_FLUSH, + windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS, + memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL + }); + } + this._deflate.writeInProgress = true; + + var self = this; + var buffers = []; + + this._deflate.on('error', onError).on('data', onData); + this._deflate.write(data); + this._deflate.flush(function() { + cleanup(); + var data = Buffer.concat(buffers); + if (fin) { + data = data.slice(0, data.length - 4); + } + callback(null, data); }); - } - this._deflate.writeInProgress = true; - - var self = this; - var buffers = []; - this._deflate.on('error', onError).on('data', onData); - this._deflate.write(data); - this._deflate.flush(function() { - cleanup(); - var data = Buffer.concat(buffers); - if (fin) { - data = data.slice(0, data.length - 4); + function onError(err) { + cleanup(); + callback(err); } - callback(null, data); - }); - - function onError(err) { - cleanup(); - callback(err); - } - function onData(data) { - buffers.push(data); - } + function onData(data) { + buffers.push(data); + } - function cleanup() { - if (!self._deflate) return; - self._deflate.removeListener('error', onError); - self._deflate.removeListener('data', onData); - self._deflate.writeInProgress = false; - if ((fin && self.params[endpoint + '_no_context_takeover']) || self._deflate.pendingClose) { - if (self._deflate.close) self._deflate.close(); - self._deflate = null; + function cleanup() { + if (!self._deflate) return; + self._deflate.removeListener('error', onError); + self._deflate.removeListener('data', onData); + self._deflate.writeInProgress = false; + if ((fin && self.params[endpoint + '_no_context_takeover']) || self._deflate.pendingClose) { + if (self._deflate.close) self._deflate.close(); + self._deflate = null; + } } } -}; +} + +PerMessageDeflate.extensionName = 'permessage-deflate'; module.exports = PerMessageDeflate; diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index 5071b3313..e90422efc 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -17,165 +17,162 @@ const BINARYLENGTH = 2, BINARYBODY = 3; * Hixie Receiver implementation */ -function Receiver () { - if (this instanceof Receiver === false) { - throw new TypeError(`Classes can't be function-called`); +class Receiver { + constructor() { + this.state = EMPTY; + this.buffers = []; + this.messageEnd = -1; + this.spanLength = 0; + this.dead = false; + + this.onerror = function() {}; + this.ontext = function() {}; + this.onbinary = function() {}; + this.onclose = function() {}; + this.onping = function() {}; + this.onpong = function() {}; } - this.state = EMPTY; - this.buffers = []; - this.messageEnd = -1; - this.spanLength = 0; - this.dead = false; - - this.onerror = function() {}; - this.ontext = function() {}; - this.onbinary = function() {}; - this.onclose = function() {}; - this.onping = function() {}; - this.onpong = function() {}; -} - -module.exports = Receiver; - -/** - * Add new data to the parser. - * - * @api public - */ - -Receiver.prototype.add = function(data) { - if (this.dead) return; - var self = this; - function doAdd() { - if (self.state === EMPTY) { - if (data.length == 2 && data[0] == 0xFF && data[1] == 0x00) { - self.reset(); - self.onclose(); - return; - } - if (data[0] === 0x80) { - self.messageEnd = 0; - self.state = BINARYLENGTH; - data = data.slice(1); - } else { - - if (data[0] !== 0x00) { - self.error('payload must start with 0x00 byte', true); + /** + * Add new data to the parser. + * + * @api public + */ + + add(data) { + if (this.dead) return; + var self = this; + function doAdd() { + if (self.state === EMPTY) { + if (data.length == 2 && data[0] == 0xFF && data[1] == 0x00) { + self.reset(); + self.onclose(); return; } - data = data.slice(1); - self.state = BODY; + if (data[0] === 0x80) { + self.messageEnd = 0; + self.state = BINARYLENGTH; + data = data.slice(1); + } else { + + if (data[0] !== 0x00) { + self.error('payload must start with 0x00 byte', true); + return; + } + data = data.slice(1); + self.state = BODY; + } } - } - if (self.state === BINARYLENGTH) { - var i = 0; - while ((i < data.length) && (data[i] & 0x80)) { - self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); - ++i; - } - if (i < data.length) { - self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); - self.state = BINARYBODY; - ++i; + if (self.state === BINARYLENGTH) { + var i = 0; + while ((i < data.length) && (data[i] & 0x80)) { + self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); + ++i; + } + if (i < data.length) { + self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); + self.state = BINARYBODY; + ++i; + } + if (i > 0) + data = data.slice(i); } - if (i > 0) - data = data.slice(i); - } - if (self.state === BINARYBODY) { - var dataleft = self.messageEnd - self.spanLength; - if (data.length >= dataleft) { - // consume the whole buffer to finish the frame + if (self.state === BINARYBODY) { + var dataleft = self.messageEnd - self.spanLength; + if (data.length >= dataleft) { + // consume the whole buffer to finish the frame + self.buffers.push(data); + self.spanLength += dataleft; + self.messageEnd = dataleft; + return self.parse(); + } + // frame's not done even if we consume it all self.buffers.push(data); - self.spanLength += dataleft; - self.messageEnd = dataleft; - return self.parse(); + self.spanLength += data.length; + return; } - // frame's not done even if we consume it all self.buffers.push(data); - self.spanLength += data.length; - return; - } - self.buffers.push(data); - if ((self.messageEnd = data.indexOf(0xFF)) != -1) { - self.spanLength += self.messageEnd; - return self.parse(); + if ((self.messageEnd = data.indexOf(0xFF)) != -1) { + self.spanLength += self.messageEnd; + return self.parse(); + } + else self.spanLength += data.length; } - else self.spanLength += data.length; + while (data) data = doAdd(); } - while (data) data = doAdd(); -}; -/** - * Releases all resources used by the receiver. - * - * @api public - */ - -Receiver.prototype.cleanup = function() { - this.dead = true; - this.state = EMPTY; - this.buffers = []; -}; - -/** - * Process buffered data. - * - * @api public - */ + /** + * Releases all resources used by the receiver. + * + * @api public + */ -Receiver.prototype.parse = function() { - var output = new Buffer(this.spanLength); - var outputIndex = 0; - for (var bi = 0, bl = this.buffers.length; bi < bl - 1; ++bi) { - var buffer = this.buffers[bi]; - buffer.copy(output, outputIndex); - outputIndex += buffer.length; - } - var lastBuffer = this.buffers[this.buffers.length - 1]; - if (this.messageEnd > 0) lastBuffer.copy(output, outputIndex, 0, this.messageEnd); - if (this.state !== BODY) --this.messageEnd; - var tail = null; - if (this.messageEnd < lastBuffer.length - 1) { - tail = lastBuffer.slice(this.messageEnd + 1); + cleanup() { + this.dead = true; + this.state = EMPTY; + this.buffers = []; } - this.reset(); - this.ontext(output.toString('utf8')); - return tail; -}; -/** - * Handles an error - * - * @api private - */ - -Receiver.prototype.error = function (reason, terminate) { - if (this.dead) return; - this.reset(); - if (typeof reason == 'string'){ - this.onerror(new Error(reason), terminate); - } - else if (reason.constructor == Error){ - this.onerror(reason, terminate); + /** + * Process buffered data. + * + * @api public + */ + + parse() { + var output = new Buffer(this.spanLength); + var outputIndex = 0; + for (var bi = 0, bl = this.buffers.length; bi < bl - 1; ++bi) { + var buffer = this.buffers[bi]; + buffer.copy(output, outputIndex); + outputIndex += buffer.length; + } + var lastBuffer = this.buffers[this.buffers.length - 1]; + if (this.messageEnd > 0) lastBuffer.copy(output, outputIndex, 0, this.messageEnd); + if (this.state !== BODY) --this.messageEnd; + var tail = null; + if (this.messageEnd < lastBuffer.length - 1) { + tail = lastBuffer.slice(this.messageEnd + 1); + } + this.reset(); + this.ontext(output.toString('utf8')); + return tail; } - else { - this.onerror(new Error('An error occured'), terminate); + + /** + * Handles an error + * + * @api private + */ + + error(reason, terminate) { + if (this.dead) return; + this.reset(); + if (typeof reason == 'string'){ + this.onerror(new Error(reason), terminate); + } + else if (reason.constructor == Error){ + this.onerror(reason, terminate); + } + else { + this.onerror(new Error('An error occured'), terminate); + } + return this; } - return this; -}; -/** - * Reset parser state - * - * @api private - */ + /** + * Reset parser state + * + * @api private + */ + reset(reason) { + if (this.dead) return; + this.state = EMPTY; + this.buffers = []; + this.messageEnd = -1; + this.spanLength = 0; + } +} -Receiver.prototype.reset = function (reason) { - if (this.dead) return; - this.state = EMPTY; - this.buffers = []; - this.messageEnd = -1; - this.spanLength = 0; -}; +module.exports = Receiver; diff --git a/lib/Receiver.js b/lib/Receiver.js index 87d616922..6a4c70638 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -16,382 +16,382 @@ const PerMessageDeflate = require('./PerMessageDeflate'); * HyBi Receiver implementation */ -function Receiver (extensions, maxPayload) { - if (this instanceof Receiver === false) { - throw new TypeError("Classes can't be function-called"); - } - - if (typeof extensions === 'number'){ - maxPayload = extensions; - extensions = {}; - } - - // memory pool for fragmented messages - var fragmentedPoolPrevUsed = -1; - this.fragmentedBufferPool = new BufferPool(1024, function(db, length) { - return db.used + length; - }, function(db) { - return fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 ? - Math.ceil((fragmentedPoolPrevUsed + db.used) / 2) : - db.used; - }); - - // memory pool for unfragmented messages - var unfragmentedPoolPrevUsed = -1; - this.unfragmentedBufferPool = new BufferPool(1024, function(db, length) { - return db.used + length; - }, function(db) { - return unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 ? - Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) : - db.used; - }); - this.extensions = extensions || {}; - this.maxPayload = maxPayload || 0; - this.currentPayloadLength = 0; - this.state = { - activeFragmentedOperation: null, - lastFragment: false, - masked: false, - opcode: 0, - fragmentedOperation: false - }; - this.overflow = []; - this.headerBuffer = new Buffer(10); - this.expectOffset = 0; - this.expectBuffer = null; - this.expectHandler = null; - this.currentMessage = []; - this.currentMessageLength = 0; - this.messageHandlers = []; - this.expectHeader(2, this.processPacket); - this.dead = false; - this.processing = false; - - this.onerror = function() {}; - this.ontext = function() {}; - this.onbinary = function() {}; - this.onclose = function() {}; - this.onping = function() {}; - this.onpong = function() {}; -} - -module.exports = Receiver; +class Receiver { + constructor(extensions, maxPayload) { + if (typeof extensions === 'number'){ + maxPayload = extensions; + extensions = {}; + } -/** - * Add new data to the parser. - * - * @api public - */ + // memory pool for fragmented messages + var fragmentedPoolPrevUsed = -1; + this.fragmentedBufferPool = new BufferPool(1024, function(db, length) { + return db.used + length; + }, function(db) { + return fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 ? + Math.ceil((fragmentedPoolPrevUsed + db.used) / 2) : + db.used; + }); -Receiver.prototype.add = function(data) { - if (this.dead) return; - var dataLength = data.length; - if (dataLength == 0) return; - if (this.expectBuffer == null) { - this.overflow.push(data); - return; - } - var toRead = Math.min(dataLength, this.expectBuffer.length - this.expectOffset); - fastCopy(toRead, data, this.expectBuffer, this.expectOffset); - this.expectOffset += toRead; - if (toRead < dataLength) { - this.overflow.push(data.slice(toRead)); - } - while (this.expectBuffer && this.expectOffset == this.expectBuffer.length) { - var bufferForHandler = this.expectBuffer; - this.expectBuffer = null; + // memory pool for unfragmented messages + var unfragmentedPoolPrevUsed = -1; + this.unfragmentedBufferPool = new BufferPool(1024, function(db, length) { + return db.used + length; + }, function(db) { + return unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 ? + Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) : + db.used; + }); + this.extensions = extensions || {}; + this.maxPayload = maxPayload || 0; + this.currentPayloadLength = 0; + this.state = { + activeFragmentedOperation: null, + lastFragment: false, + masked: false, + opcode: 0, + fragmentedOperation: false + }; + this.overflow = []; + this.headerBuffer = new Buffer(10); this.expectOffset = 0; - this.expectHandler.call(this, bufferForHandler); - } -}; - -/** - * Releases all resources used by the receiver. - * - * @api public - */ - -Receiver.prototype.cleanup = function() { - this.dead = true; - this.overflow = null; - this.headerBuffer = null; - this.expectBuffer = null; - this.expectHandler = null; - this.unfragmentedBufferPool = null; - this.fragmentedBufferPool = null; - this.state = null; - this.currentMessage = null; - this.onerror = null; - this.ontext = null; - this.onbinary = null; - this.onclose = null; - this.onping = null; - this.onpong = null; -}; - -/** - * Waits for a certain amount of header bytes to be available, then fires a callback. - * - * @api private - */ + this.expectBuffer = null; + this.expectHandler = null; + this.currentMessage = []; + this.currentMessageLength = 0; + this.messageHandlers = []; + this.expectHeader(2, this.processPacket); + this.dead = false; + this.processing = false; -Receiver.prototype.expectHeader = function(length, handler) { - if (length == 0) { - handler(null); - return; - } - this.expectBuffer = this.headerBuffer.slice(this.expectOffset, this.expectOffset + length); - this.expectHandler = handler; - var toRead = length; - while (toRead > 0 && this.overflow.length > 0) { - var fromOverflow = this.overflow.pop(); - if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); - var read = Math.min(fromOverflow.length, toRead); - fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); - this.expectOffset += read; - toRead -= read; + this.onerror = function() {}; + this.ontext = function() {}; + this.onbinary = function() {}; + this.onclose = function() {}; + this.onping = function() {}; + this.onpong = function() {}; } -}; - -/** - * Waits for a certain amount of data bytes to be available, then fires a callback. - * - * @api private - */ -Receiver.prototype.expectData = function(length, handler) { - if (length == 0) { - handler(null); - return; - } - this.expectBuffer = this.allocateFromPool(length, this.state.fragmentedOperation); - this.expectHandler = handler; - var toRead = length; - while (toRead > 0 && this.overflow.length > 0) { - var fromOverflow = this.overflow.pop(); - if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); - var read = Math.min(fromOverflow.length, toRead); - fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); - this.expectOffset += read; - toRead -= read; + /** + * Add new data to the parser. + * + * @api public + */ + + add(data) { + if (this.dead) return; + var dataLength = data.length; + if (dataLength == 0) return; + if (this.expectBuffer == null) { + this.overflow.push(data); + return; + } + var toRead = Math.min(dataLength, this.expectBuffer.length - this.expectOffset); + fastCopy(toRead, data, this.expectBuffer, this.expectOffset); + this.expectOffset += toRead; + if (toRead < dataLength) { + this.overflow.push(data.slice(toRead)); + } + while (this.expectBuffer && this.expectOffset == this.expectBuffer.length) { + var bufferForHandler = this.expectBuffer; + this.expectBuffer = null; + this.expectOffset = 0; + this.expectHandler.call(this, bufferForHandler); + } } -}; -/** - * Allocates memory from the buffer pool. - * - * @api private - */ + /** + * Releases all resources used by the receiver. + * + * @api public + */ -Receiver.prototype.allocateFromPool = function(length, isFragmented) { - return (isFragmented ? this.fragmentedBufferPool : this.unfragmentedBufferPool).get(length); -}; + cleanup() { + this.dead = true; + this.overflow = null; + this.headerBuffer = null; + this.expectBuffer = null; + this.expectHandler = null; + this.unfragmentedBufferPool = null; + this.fragmentedBufferPool = null; + this.state = null; + this.currentMessage = null; + this.onerror = null; + this.ontext = null; + this.onbinary = null; + this.onclose = null; + this.onping = null; + this.onpong = null; + } -/** - * Start processing a new packet. - * - * @api private - */ + /** + * Waits for a certain amount of header bytes to be available, then fires a callback. + * + * @api private + */ -Receiver.prototype.processPacket = function (data) { - if (this.extensions[PerMessageDeflate.extensionName]) { - if ((data[0] & 0x30) != 0) { - this.error('reserved fields (2, 3) must be empty', 1002); + expectHeader(length, handler) { + if (length == 0) { + handler(null); return; } - } else { - if ((data[0] & 0x70) != 0) { - this.error('reserved fields must be empty', 1002); - return; + this.expectBuffer = this.headerBuffer.slice(this.expectOffset, this.expectOffset + length); + this.expectHandler = handler; + var toRead = length; + while (toRead > 0 && this.overflow.length > 0) { + var fromOverflow = this.overflow.pop(); + if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); + var read = Math.min(fromOverflow.length, toRead); + fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); + this.expectOffset += read; + toRead -= read; } } - this.state.lastFragment = (data[0] & 0x80) == 0x80; - this.state.masked = (data[1] & 0x80) == 0x80; - var compressed = (data[0] & 0x40) == 0x40; - var opcode = data[0] & 0xf; - if (opcode === 0) { - if (compressed) { - this.error('continuation frame cannot have the Per-message Compressed bits', 1002); + + /** + * Waits for a certain amount of data bytes to be available, then fires a callback. + * + * @api private + */ + + expectData(length, handler) { + if (length == 0) { + handler(null); return; } - // continuation frame - this.state.fragmentedOperation = true; - this.state.opcode = this.state.activeFragmentedOperation; - if (!(this.state.opcode == 1 || this.state.opcode == 2)) { - this.error('continuation frame cannot follow current opcode', 1002); - return; + this.expectBuffer = this.allocateFromPool(length, this.state.fragmentedOperation); + this.expectHandler = handler; + var toRead = length; + while (toRead > 0 && this.overflow.length > 0) { + var fromOverflow = this.overflow.pop(); + if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); + var read = Math.min(fromOverflow.length, toRead); + fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); + this.expectOffset += read; + toRead -= read; } } - else { - if (opcode < 3 && this.state.activeFragmentedOperation != null) { - this.error('data frames after the initial data frame must have opcode 0', 1002); - return; - } - if (opcode >= 8 && compressed) { - this.error('control frames cannot have the Per-message Compressed bits', 1002); - return; + + /** + * Allocates memory from the buffer pool. + * + * @api private + */ + + allocateFromPool(length, isFragmented) { + return (isFragmented ? this.fragmentedBufferPool : this.unfragmentedBufferPool).get(length); + } + + /** + * Start processing a new packet. + * + * @api private + */ + + processPacket (data) { + if (this.extensions[PerMessageDeflate.extensionName]) { + if ((data[0] & 0x30) != 0) { + this.error('reserved fields (2, 3) must be empty', 1002); + return; + } + } else { + if ((data[0] & 0x70) != 0) { + this.error('reserved fields must be empty', 1002); + return; + } } - this.state.compressed = compressed; - this.state.opcode = opcode; - if (this.state.lastFragment === false) { + this.state.lastFragment = (data[0] & 0x80) == 0x80; + this.state.masked = (data[1] & 0x80) == 0x80; + var compressed = (data[0] & 0x40) == 0x40; + var opcode = data[0] & 0xf; + if (opcode === 0) { + if (compressed) { + this.error('continuation frame cannot have the Per-message Compressed bits', 1002); + return; + } + // continuation frame this.state.fragmentedOperation = true; - this.state.activeFragmentedOperation = opcode; + this.state.opcode = this.state.activeFragmentedOperation; + if (!(this.state.opcode == 1 || this.state.opcode == 2)) { + this.error('continuation frame cannot follow current opcode', 1002); + return; + } + } + else { + if (opcode < 3 && this.state.activeFragmentedOperation != null) { + this.error('data frames after the initial data frame must have opcode 0', 1002); + return; + } + if (opcode >= 8 && compressed) { + this.error('control frames cannot have the Per-message Compressed bits', 1002); + return; + } + this.state.compressed = compressed; + this.state.opcode = opcode; + if (this.state.lastFragment === false) { + this.state.fragmentedOperation = true; + this.state.activeFragmentedOperation = opcode; + } + else this.state.fragmentedOperation = false; + } + var handler = opcodes[this.state.opcode]; + if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode, 1002); + else { + handler.start.call(this, data); } - else this.state.fragmentedOperation = false; - } - var handler = opcodes[this.state.opcode]; - if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode, 1002); - else { - handler.start.call(this, data); } -}; -/** - * Endprocessing a packet. - * - * @api private - */ + /** + * Endprocessing a packet. + * + * @api private + */ -Receiver.prototype.endPacket = function() { - if (this.dead) return; - if (!this.state.fragmentedOperation) this.unfragmentedBufferPool.reset(true); - else if (this.state.lastFragment) this.fragmentedBufferPool.reset(true); - this.expectOffset = 0; - this.expectBuffer = null; - this.expectHandler = null; - if (this.state.lastFragment && this.state.opcode === this.state.activeFragmentedOperation) { - // end current fragmented operation - this.state.activeFragmentedOperation = null; + endPacket() { + if (this.dead) return; + if (!this.state.fragmentedOperation) this.unfragmentedBufferPool.reset(true); + else if (this.state.lastFragment) this.fragmentedBufferPool.reset(true); + this.expectOffset = 0; + this.expectBuffer = null; + this.expectHandler = null; + if (this.state.lastFragment && this.state.opcode === this.state.activeFragmentedOperation) { + // end current fragmented operation + this.state.activeFragmentedOperation = null; + } + this.currentPayloadLength = 0; + this.state.lastFragment = false; + this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0; + this.state.masked = false; + this.expectHeader(2, this.processPacket); } - this.currentPayloadLength = 0; - this.state.lastFragment = false; - this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0; - this.state.masked = false; - this.expectHeader(2, this.processPacket); -}; - -/** - * Reset the parser state. - * - * @api private - */ - -Receiver.prototype.reset = function() { - if (this.dead) return; - this.state = { - activeFragmentedOperation: null, - lastFragment: false, - masked: false, - opcode: 0, - fragmentedOperation: false - }; - this.fragmentedBufferPool.reset(true); - this.unfragmentedBufferPool.reset(true); - this.expectOffset = 0; - this.expectBuffer = null; - this.expectHandler = null; - this.overflow = []; - this.currentMessage = []; - this.currentMessageLength = 0; - this.messageHandlers = []; - this.currentPayloadLength = 0; -}; - -/** - * Unmask received data. - * - * @api private - */ -Receiver.prototype.unmask = function (mask, buf, binary) { - if (mask != null && buf != null) bufferUtil.unmask(buf, mask); - if (binary) return buf; - return buf != null ? buf.toString('utf8') : ''; -}; + /** + * Reset the parser state. + * + * @api private + */ + + reset() { + if (this.dead) return; + this.state = { + activeFragmentedOperation: null, + lastFragment: false, + masked: false, + opcode: 0, + fragmentedOperation: false + }; + this.fragmentedBufferPool.reset(true); + this.unfragmentedBufferPool.reset(true); + this.expectOffset = 0; + this.expectBuffer = null; + this.expectHandler = null; + this.overflow = []; + this.currentMessage = []; + this.currentMessageLength = 0; + this.messageHandlers = []; + this.currentPayloadLength = 0; + } -/** - * Handles an error - * - * @api private - */ + /** + * Unmask received data. + * + * @api private + */ -Receiver.prototype.error = function (reason, protocolErrorCode) { - if (this.dead) return; - this.reset(); - if (typeof reason == 'string'){ - this.onerror(new Error(reason), protocolErrorCode); - } - else if (reason.constructor == Error){ - this.onerror(reason, protocolErrorCode); + unmask (mask, buf, binary) { + if (mask != null && buf != null) bufferUtil.unmask(buf, mask); + if (binary) return buf; + return buf != null ? buf.toString('utf8') : ''; } - else { - this.onerror(new Error('An error occured'), protocolErrorCode); - } - return this; -}; -/** - * Execute message handler buffers - * - * @api private - */ + /** + * Handles an error + * + * @api private + */ + + error (reason, protocolErrorCode) { + if (this.dead) return; + this.reset(); + if (typeof reason == 'string'){ + this.onerror(new Error(reason), protocolErrorCode); + } + else if (reason.constructor == Error){ + this.onerror(reason, protocolErrorCode); + } + else { + this.onerror(new Error('An error occured'), protocolErrorCode); + } + return this; + } -Receiver.prototype.flush = function() { - if (this.processing || this.dead) return; + /** + * Execute message handler buffers + * + * @api private + */ - var handler = this.messageHandlers.shift(); - if (!handler) return; + flush() { + if (this.processing || this.dead) return; - this.processing = true; + var handler = this.messageHandlers.shift(); + if (!handler) return; - handler(() => { - this.processing = false; - this.flush(); - }); -}; + this.processing = true; -/** - * Apply extensions to message - * - * @api private - */ - -Receiver.prototype.applyExtensions = function(messageBuffer, fin, compressed, callback) { - if (compressed) { - var extension = this.extensions[PerMessageDeflate.extensionName]; - extension.decompress(messageBuffer, fin, (err, buffer) => { - if (this.dead) return; - if (err) { - callback(new Error('invalid compressed data')); - return; - } - callback(null, buffer); + handler(() => { + this.processing = false; + this.flush(); }); - } else { - callback(null, messageBuffer); } -}; -/** -* Checks payload size, disconnects socket when it exceeds maxPayload -* -* @api private -*/ -Receiver.prototype.maxPayloadExceeded = function(length) { - if (this.maxPayload === undefined || this.maxPayload === null || this.maxPayload < 1) { - return false; + /** + * Apply extensions to message + * + * @api private + */ + + applyExtensions(messageBuffer, fin, compressed, callback) { + if (compressed) { + var extension = this.extensions[PerMessageDeflate.extensionName]; + extension.decompress(messageBuffer, fin, (err, buffer) => { + if (this.dead) return; + if (err) { + callback(new Error('invalid compressed data')); + return; + } + callback(null, buffer); + }); + } else { + callback(null, messageBuffer); + } } - var fullLength = this.currentPayloadLength + length; - if (fullLength < this.maxPayload) { - this.currentPayloadLength = fullLength; - return false; + + /** + * Checks payload size, disconnects socket when it exceeds maxPayload + * + * @api private + */ + maxPayloadExceeded(length) { + if (this.maxPayload === undefined || this.maxPayload === null || this.maxPayload < 1) { + return false; + } + var fullLength = this.currentPayloadLength + length; + if (fullLength < this.maxPayload) { + this.currentPayloadLength = fullLength; + return false; + } + this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); + this.messageBuffer = []; + this.cleanup(); + + return true; } - this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); - this.messageBuffer = []; - this.cleanup(); +} + +module.exports = Receiver; + - return true; -}; /** * Buffer utilities diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index 8e5ec6188..69f4ae053 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -7,124 +7,112 @@ 'use strict'; const EventEmitter = require('events'); -const util = require('util'); /** - * Hixie Sender implementation + * Hixie Sender implementation, Inherits from EventEmitter. */ -function Sender(socket) { - if (this instanceof Sender === false) { - throw new TypeError("Classes can't be function-called"); - } - - EventEmitter.call(this); - - this.socket = socket; - this.continuationFrame = false; - this.isClosed = false; -} - -module.exports = Sender; - -/** - * Inherits from EventEmitter. - */ - -util.inherits(Sender, EventEmitter); - -/** - * Frames and writes data. - * - * @api public - */ - -Sender.prototype.send = function(data, options, cb) { - if (this.isClosed) return; - - var isString = typeof data == 'string'; - var length = isString ? Buffer.byteLength(data) : data.length; - var lengthbytes = (length > 127) ? 2 : 1; // assume less than 2**14 bytes - var writeStartMarker = this.continuationFrame == false; - var writeEndMarker = !options || !(typeof options.fin != 'undefined' && !options.fin); - - var bufferLength = writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0; - bufferLength += length; - bufferLength += (writeEndMarker && !(options && options.binary)) ? 1 : 0; - - var buffer = new Buffer(bufferLength); - var offset = writeStartMarker ? 1 : 0; - - if (writeStartMarker) { - if (options && options.binary) { - buffer.write('\x80', 'binary'); - // assume length less than 2**14 bytes - if (lengthbytes > 1) - buffer.write(String.fromCharCode(128 + length / 128), offset++, 'binary'); - buffer.write(String.fromCharCode(length & 0x7f), offset++, 'binary'); - } else - buffer.write('\x00', 'binary'); - } - - if (isString) buffer.write(data, offset, 'utf8'); - else data.copy(buffer, offset, 0); +class Sender extends EventEmitter { + constructor(socket) { + super(); - if (writeEndMarker) { - if (options && options.binary) { - // sending binary, not writing end marker - } else - buffer.write('\xff', offset + length, 'binary'); + this.socket = socket; this.continuationFrame = false; + this.isClosed = false; } - else this.continuationFrame = true; - try { - this.socket.write(buffer, 'binary', cb); - } catch (e) { - this.error(e.toString()); + /** + * Frames and writes data. + * + * @api public + */ + send(data, options, cb) { + if (this.isClosed) return; + + var isString = typeof data == 'string'; + var length = isString ? Buffer.byteLength(data) : data.length; + var lengthbytes = (length > 127) ? 2 : 1; // assume less than 2**14 bytes + var writeStartMarker = this.continuationFrame == false; + var writeEndMarker = !options || !(typeof options.fin != 'undefined' && !options.fin); + + var bufferLength = writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0; + bufferLength += length; + bufferLength += (writeEndMarker && !(options && options.binary)) ? 1 : 0; + + var buffer = new Buffer(bufferLength); + var offset = writeStartMarker ? 1 : 0; + + if (writeStartMarker) { + if (options && options.binary) { + buffer.write('\x80', 'binary'); + // assume length less than 2**14 bytes + if (lengthbytes > 1) + buffer.write(String.fromCharCode(128 + length / 128), offset++, 'binary'); + buffer.write(String.fromCharCode(length & 0x7f), offset++, 'binary'); + } else + buffer.write('\x00', 'binary'); + } + + if (isString) buffer.write(data, offset, 'utf8'); + else data.copy(buffer, offset, 0); + + if (writeEndMarker) { + if (options && options.binary) { + // sending binary, not writing end marker + } else + buffer.write('\xff', offset + length, 'binary'); + this.continuationFrame = false; + } + else this.continuationFrame = true; + + try { + this.socket.write(buffer, 'binary', cb); + } catch (e) { + this.error(e.toString()); + } } -}; -/** - * Sends a close instruction to the remote party. - * - * @api public - */ - -Sender.prototype.close = function(code, data, mask, cb) { - if (this.isClosed) return; - this.isClosed = true; - try { - if (this.continuationFrame) this.socket.write(new Buffer([0xff], 'binary')); - this.socket.write(new Buffer([0xff, 0x00]), 'binary', cb); - } catch (e) { - this.error(e.toString()); + /** + * Sends a close instruction to the remote party. + * + * @api public + */ + + close(code, data, mask, cb) { + if (this.isClosed) return; + this.isClosed = true; + try { + if (this.continuationFrame) this.socket.write(new Buffer([0xff], 'binary')); + this.socket.write(new Buffer([0xff, 0x00]), 'binary', cb); + } catch (e) { + this.error(e.toString()); + } } -}; - -/** - * Sends a ping message to the remote party. Not available for hixie. - * - * @api public - */ - -Sender.prototype.ping = function(data, options) {}; - -/** - * Sends a pong message to the remote party. Not available for hixie. - * - * @api public - */ -Sender.prototype.pong = function(data, options) {}; - -/** - * Handles an error - * - * @api private - */ + /** + * Sends a ping message to the remote party. Not available for hixie. + * + * @api public + */ + + ping(data, options) {} + + /** + * Sends a pong message to the remote party. Not available for hixie. + * + * @api public + */ + pong(data, options) {} + + /** + * Handles an error + * + * @api private + */ + error(reason) { + this.emit('error', reason); + return this; + } +} -Sender.prototype.error = function (reason) { - this.emit('error', reason); - return this; -}; +module.exports = Sender; diff --git a/lib/Sender.js b/lib/Sender.js index 732f855c5..ff54e6fe2 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -7,289 +7,275 @@ 'use strict'; const EventEmitter = require('events'); -const util = require('util'); const ErrorCodes = require('./ErrorCodes'); const bufferUtil = require('./BufferUtil').BufferUtil; const PerMessageDeflate = require('./PerMessageDeflate'); /** - * HyBi Sender implementation + * HyBi Sender implementation, Inherits from EventEmitter. */ - -function Sender(socket, extensions) { - if (this instanceof Sender === false) { - throw new TypeError("Classes can't be function-called"); +class Sender extends EventEmitter { + constructor(socket, extensions) { + super(); + + this._socket = socket; + this.extensions = extensions || {}; + this.firstFragment = true; + this.compress = false; + this.messageHandlers = []; + this.processing = false; } - EventEmitter.call(this); - - this._socket = socket; - this.extensions = extensions || {}; - this.firstFragment = true; - this.compress = false; - this.messageHandlers = []; - this.processing = false; -} - -/** - * Inherits from EventEmitter. - */ - -util.inherits(Sender, EventEmitter); - -/** - * Sends a close instruction to the remote party. - * - * @api public - */ - -Sender.prototype.close = function(code, data, mask, cb) { - if (typeof code !== 'undefined') { - if (typeof code !== 'number' || - !ErrorCodes.isValidErrorCode(code)) throw new Error('first argument must be a valid error code number'); + /** + * Sends a close instruction to the remote party. + * + * @api public + */ + close(code, data, mask, cb) { + if (typeof code !== 'undefined') { + if (typeof code !== 'number' || + !ErrorCodes.isValidErrorCode(code)) throw new Error('first argument must be a valid error code number'); + } + code = code || 1000; + var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0)); + dataBuffer.writeUInt16BE(code, 0); + if (dataBuffer.length > 2) dataBuffer.write(data, 2); + + var self = this; + this.messageHandlers.push(function(callback) { + self.frameAndSend(0x8, dataBuffer, true, mask); + callback(); + if (typeof cb == 'function') cb(); + }); + this.flush(); } - code = code || 1000; - var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0)); - dataBuffer.writeUInt16BE(code, 0); - if (dataBuffer.length > 2) dataBuffer.write(data, 2); - - var self = this; - this.messageHandlers.push(function(callback) { - self.frameAndSend(0x8, dataBuffer, true, mask); - callback(); - if (typeof cb == 'function') cb(); - }); - this.flush(); -}; -/** - * Sends a ping message to the remote party. - * - * @api public - */ - -Sender.prototype.ping = function(data, options) { - var mask = options && options.mask; - var self = this; - this.messageHandlers.push(function(callback) { - self.frameAndSend(0x9, data || '', true, mask); - callback(); - }); - this.flush(); -}; - -/** - * Sends a pong message to the remote party. - * - * @api public - */ - -Sender.prototype.pong = function(data, options) { - var mask = options && options.mask; - var self = this; - this.messageHandlers.push(function(callback) { - self.frameAndSend(0xa, data || '', true, mask); - callback(); - }); - this.flush(); -}; - -/** - * Sends text or binary data to the remote party. - * - * @api public - */ - -Sender.prototype.send = function(data, options, cb) { - var finalFragment = options && options.fin === false ? false : true; - var mask = options && options.mask; - var compress = options && options.compress; - var opcode = options && options.binary ? 2 : 1; - if (this.firstFragment === false) { - opcode = 0; - compress = false; - } else { - this.firstFragment = false; - this.compress = compress; + /** + * Sends a ping message to the remote party. + * + * @api public + */ + ping(data, options) { + var mask = options && options.mask; + var self = this; + this.messageHandlers.push(function(callback) { + self.frameAndSend(0x9, data || '', true, mask); + callback(); + }); + this.flush(); } - if (finalFragment) this.firstFragment = true - - var compressFragment = this.compress; - var self = this; - this.messageHandlers.push(function(callback) { - self.applyExtensions(data, finalFragment, compressFragment, function(err, data) { - if (err) { - if (typeof cb == 'function') cb(err); - else self.emit('error', err); - return; - } - self.frameAndSend(opcode, data, finalFragment, mask, compress, cb); + /** + * Sends a pong message to the remote party. + * + * @api public + */ + pong(data, options) { + var mask = options && options.mask; + var self = this; + this.messageHandlers.push(function(callback) { + self.frameAndSend(0xa, data || '', true, mask); callback(); }); - }); - this.flush(); -}; - -/** - * Frames and sends a piece of data according to the HyBi WebSocket protocol. - * - * @api private - */ - -Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, compressed, cb) { - var canModifyData = false; - if (!data) { - try { - var buff = [opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)] - .concat(maskData ? [0, 0, 0, 0] : []); - this._socket.write(new Buffer(buff), 'binary', cb); - } - catch (e) { - if (typeof cb == 'function') cb(e); - else this.emit('error', e); - } - return; + this.flush(); } - if (!Buffer.isBuffer(data)) { - canModifyData = true; - if (data && (typeof data.byteLength !== 'undefined' || typeof data.buffer !== 'undefined')) { - data = getArrayBuffer(data); + /** + * Sends text or binary data to the remote party. + * + * @api public + */ + + send(data, options, cb) { + var finalFragment = options && options.fin === false ? false : true; + var mask = options && options.mask; + var compress = options && options.compress; + var opcode = options && options.binary ? 2 : 1; + if (this.firstFragment === false) { + opcode = 0; + compress = false; } else { - // - // If people want to send a number, this would allocate the number in - // bytes as memory size instead of storing the number as buffer value. So - // we need to transform it to string in order to prevent possible - // vulnerabilities / memory attacks. - // - if (typeof data === 'number') data = data.toString(); - - data = new Buffer(data); + this.firstFragment = false; + this.compress = compress; } + if (finalFragment) this.firstFragment = true + + var compressFragment = this.compress; + + var self = this; + this.messageHandlers.push(function(callback) { + self.applyExtensions(data, finalFragment, compressFragment, function(err, data) { + if (err) { + if (typeof cb == 'function') cb(err); + else self.emit('error', err); + return; + } + self.frameAndSend(opcode, data, finalFragment, mask, compress, cb); + callback(); + }); + }); + this.flush(); } - var dataLength = data.length, - dataOffset = maskData ? 6 : 2, - secondByte = dataLength; - - if (dataLength >= 65536) { - dataOffset += 8; - secondByte = 127; - } - else if (dataLength > 125) { - dataOffset += 2; - secondByte = 126; - } + /** + * Frames and sends a piece of data according to the HyBi WebSocket protocol. + * + * @api private + */ + frameAndSend(opcode, data, finalFragment, maskData, compressed, cb) { + var canModifyData = false; - var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData); - var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset; - var outputBuffer = new Buffer(totalLength); - outputBuffer[0] = finalFragment ? opcode | 0x80 : opcode; - if (compressed) outputBuffer[0] |= 0x40; - - switch (secondByte) { - case 126: - outputBuffer.writeUInt16BE(dataLength, 2); - break; - case 127: - outputBuffer.writeUInt32BE(0, 2); - outputBuffer.writeUInt32BE(dataLength, 6); - } + if (!data) { - if (maskData) { - outputBuffer[1] = secondByte | 0x80; - var mask = getRandomMask(); - outputBuffer[dataOffset - 4] = mask[0]; - outputBuffer[dataOffset - 3] = mask[1]; - outputBuffer[dataOffset - 2] = mask[2]; - outputBuffer[dataOffset - 1] = mask[3]; - if (mergeBuffers) { - bufferUtil.mask(data, mask, outputBuffer, dataOffset, dataLength); try { - this._socket.write(outputBuffer, 'binary', cb); + var buff = [opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)] + .concat(maskData ? [0, 0, 0, 0] : []); + this._socket.write(new Buffer(buff), 'binary', cb); } catch (e) { if (typeof cb == 'function') cb(e); else this.emit('error', e); } + return; } - else { - bufferUtil.mask(data, mask, data, 0, dataLength); - try { - this._socket.write(outputBuffer, 'binary'); - this._socket.write(data, 'binary', cb); - } - catch (e) { - if (typeof cb == 'function') cb(e); - else this.emit('error', e); + + if (!Buffer.isBuffer(data)) { + canModifyData = true; + if (data && (typeof data.byteLength !== 'undefined' || typeof data.buffer !== 'undefined')) { + data = getArrayBuffer(data); + } else { + // + // If people want to send a number, this would allocate the number in + // bytes as memory size instead of storing the number as buffer value. So + // we need to transform it to string in order to prevent possible + // vulnerabilities / memory attacks. + // + if (typeof data === 'number') data = data.toString(); + + data = new Buffer(data); } } - } - else { - outputBuffer[1] = secondByte; - if (mergeBuffers) { - data.copy(outputBuffer, dataOffset); - try { - this._socket.write(outputBuffer, 'binary', cb); + + var dataLength = data.length, + dataOffset = maskData ? 6 : 2, + secondByte = dataLength; + + if (dataLength >= 65536) { + dataOffset += 8; + secondByte = 127; + } + else if (dataLength > 125) { + dataOffset += 2; + secondByte = 126; + } + + var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData); + var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset; + var outputBuffer = new Buffer(totalLength); + outputBuffer[0] = finalFragment ? opcode | 0x80 : opcode; + if (compressed) outputBuffer[0] |= 0x40; + + switch (secondByte) { + case 126: + outputBuffer.writeUInt16BE(dataLength, 2); + break; + case 127: + outputBuffer.writeUInt32BE(0, 2); + outputBuffer.writeUInt32BE(dataLength, 6); + } + + if (maskData) { + outputBuffer[1] = secondByte | 0x80; + var mask = getRandomMask(); + outputBuffer[dataOffset - 4] = mask[0]; + outputBuffer[dataOffset - 3] = mask[1]; + outputBuffer[dataOffset - 2] = mask[2]; + outputBuffer[dataOffset - 1] = mask[3]; + if (mergeBuffers) { + bufferUtil.mask(data, mask, outputBuffer, dataOffset, dataLength); + try { + this._socket.write(outputBuffer, 'binary', cb); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } } - catch (e) { - if (typeof cb == 'function') cb(e); - else this.emit('error', e); + else { + bufferUtil.mask(data, mask, data, 0, dataLength); + try { + this._socket.write(outputBuffer, 'binary'); + this._socket.write(data, 'binary', cb); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } } } else { - try { - this._socket.write(outputBuffer, 'binary'); - this._socket.write(data, 'binary', cb); + outputBuffer[1] = secondByte; + if (mergeBuffers) { + data.copy(outputBuffer, dataOffset); + try { + this._socket.write(outputBuffer, 'binary', cb); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } } - catch (e) { - if (typeof cb == 'function') cb(e); - else this.emit('error', e); + else { + try { + this._socket.write(outputBuffer, 'binary'); + this._socket.write(data, 'binary', cb); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } } } } -}; - -/** - * Execute message handler buffers - * - * @api private - */ - -Sender.prototype.flush = function() { - if (this.processing) return; - var handler = this.messageHandlers.shift(); - if (!handler) return; + /** + * Execute message handler buffers + * + * @api private + */ + flush() { + if (this.processing) return; - this.processing = true; + var handler = this.messageHandlers.shift(); + if (!handler) return; - var self = this; + this.processing = true; - handler(function() { - self.processing = false; - self.flush(); - }); -}; + var self = this; -/** - * Apply extensions to message - * - * @api private - */ + handler(function() { + self.processing = false; + self.flush(); + }); + } -Sender.prototype.applyExtensions = function(data, fin, compress, callback) { - if (compress && data) { - if ((data.buffer || data) instanceof ArrayBuffer) { - data = getArrayBuffer(data); + /** + * Apply extensions to message + * + * @api private + */ + applyExtensions(data, fin, compress, callback) { + if (compress && data) { + if ((data.buffer || data) instanceof ArrayBuffer) { + data = getArrayBuffer(data); + } + this.extensions[PerMessageDeflate.extensionName].compress(data, fin, callback); + } else { + callback(null, data); } - this.extensions[PerMessageDeflate.extensionName].compress(data, fin, callback); - } else { - callback(null, data); } -}; +} module.exports = Sender; diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index f8a480fd0..9d3f2b900 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -548,11 +548,10 @@ function acceptExtensions(offer) { function abortConnection(socket, code, name) { try { - var response = [ - 'HTTP/1.1 ' + code + ' ' + name, - 'Content-type: text/html' - ]; - socket.write(response.concat('', '').join('\r\n')); + var response = `HTTP/1.1 ${code} ${name}\r\n` + + `Content-type: text/html\r\n` + + `\r\n\r\n`; + socket.write(response); } catch (e) { /* ignore errors - we've aborted this connection */ } finally { From 2b8a484448a1ce6e1daa051c31b825343a6d34af Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 17 Jul 2016 08:52:02 +0200 Subject: [PATCH 068/669] [minor] Remove unnecessary assignment --- lib/Receiver.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 6a4c70638..75c467d2c 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -368,10 +368,11 @@ class Receiver { } /** - * Checks payload size, disconnects socket when it exceeds maxPayload - * - * @api private - */ + * Checks payload size, disconnects socket when it exceeds maxPayload + * + * @api private + */ + maxPayloadExceeded(length) { if (this.maxPayload === undefined || this.maxPayload === null || this.maxPayload < 1) { return false; @@ -382,7 +383,6 @@ class Receiver { return false; } this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); - this.messageBuffer = []; this.cleanup(); return true; From 3cbdeff4d195299884f2fece094390db92690f3a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 18 Jul 2016 17:05:27 +0200 Subject: [PATCH 069/669] [minor] Remove some dead code --- lib/PerMessageDeflate.js | 9 +++-- lib/Receiver.js | 81 ++++++++++++++-------------------------- 2 files changed, 33 insertions(+), 57 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index c61d6ef4f..515e30e9e 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -247,12 +247,13 @@ class PerMessageDeflate { } function onData(data) { - if (self._maxPayload !== undefined && self._maxPayload !== null && self._maxPayload > 0){ + if (self._maxPayload > 0) { cumulativeBufferLength += data.length; - if (cumulativeBufferLength > self._maxPayload){ - buffers = []; + if (cumulativeBufferLength > self._maxPayload) { + const err = new Error(`payload cannot exceed ${self._maxPayload} bytes`); + err.closeCode = 1009; + buffers.length = 0; cleanup(); - var err = {type:1009}; callback(err); return; } diff --git a/lib/Receiver.js b/lib/Receiver.js index 75c467d2c..b840115ed 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -357,7 +357,7 @@ class Receiver { extension.decompress(messageBuffer, fin, (err, buffer) => { if (this.dead) return; if (err) { - callback(new Error('invalid compressed data')); + callback(err.closeCode === 1009 ? err : new Error('invalid compressed data')); return; } callback(null, buffer); @@ -448,19 +448,13 @@ var opcodes = { // decode length var firstLength = data[1] & 0x7f; if (firstLength < 126) { - if (this.maxPayloadExceeded(firstLength)){ - this.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); - return; - } + if (this.maxPayloadExceeded(firstLength)) return; opcodes['1'].getData.call(this, firstLength); } else if (firstLength == 126) { this.expectHeader(2, (data) => { var length = readUInt16BE.call(data, 0); - if (this.maxPayloadExceeded(length)){ - this.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); - return; - } + if (this.maxPayloadExceeded(length)) return; opcodes['1'].getData.call(this, length); }); } @@ -471,11 +465,8 @@ var opcodes = { return; } var length = readUInt32BE.call(data, 4); - if (this.maxPayloadExceeded(length)){ - this.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); - return; - } - opcodes['1'].getData.call(this, readUInt32BE.call(data, 4)); + if (this.maxPayloadExceeded(length)) return; + opcodes['1'].getData.call(this, length); }); } }, @@ -500,22 +491,19 @@ var opcodes = { this.messageHandlers.push((callback) => { this.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { if (err) { - if (err.type === 1009){ - return this.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); - } - return this.error(err.message, 1007); + this.error(err.message, err.closeCode === 1009 ? 1009 : 1007); + return; } if (buffer != null) { if (this.maxPayload == 0 || (this.maxPayload > 0 && - (this.currentMessageLength + buffer.length) < this.maxPayload) ){ + (this.currentMessageLength + buffer.length) < this.maxPayload)) { this.currentMessage.push(buffer); } else { - this.currentMessage = null; this.currentMessage = []; this.currentMessageLength = 0; - this.error(new Error('Maximum payload exceeded. maxPayload: ' + this.maxPayload), 1009); + this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); return; } this.currentMessageLength += buffer.length; @@ -543,19 +531,13 @@ var opcodes = { // decode length var firstLength = data[1] & 0x7f; if (firstLength < 126) { - if (this.maxPayloadExceeded(firstLength)){ - this.error('Max payload exceeded in compressed text message. Aborting...', 1009); - return; - } + if (this.maxPayloadExceeded(firstLength)) return; opcodes['2'].getData.call(this, firstLength); } else if (firstLength == 126) { this.expectHeader(2, (data) => { var length = readUInt16BE.call(data, 0); - if (this.maxPayloadExceeded(length)){ - this.error('Max payload exceeded in compressed text message. Aborting...', 1009); - return; - } + if (this.maxPayloadExceeded(length)) return; opcodes['2'].getData.call(this, length); }); } @@ -566,10 +548,7 @@ var opcodes = { return; } var length = readUInt32BE.call(data, 4, true); - if (this.maxPayloadExceeded(length)){ - this.error('Max payload exceeded in compressed text message. Aborting...', 1009); - return; - } + if (this.maxPayloadExceeded(length)) return; opcodes['2'].getData.call(this, length); }); } @@ -590,37 +569,33 @@ var opcodes = { } }, finish: function(mask, data) { - var self = this; var packet = this.unmask(mask, data, true) || new Buffer(0); var state = clone(this.state); - this.messageHandlers.push(function(callback) { - self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { + this.messageHandlers.push((callback) => { + this.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { if (err) { - if (err.type === 1009){ - return self.error('Max payload exceeded in compressed binary message. Aborting...', 1009); - } - return self.error(err.message, 1007); + this.error(err.message, err.closeCode === 1009 ? 1009 : 1007); + return; } + if (buffer != null) { - var length = (self.currentMessageLength + buffer.length); - if (self.maxPayload == 0 || (self.maxPayload > 0 && length < self.maxPayload)) { - self.currentMessage.push(buffer); - self.currentMessageLength += length; + if (this.maxPayload == 0 || (this.maxPayload > 0 && + (this.currentMessageLength + buffer.length) < this.maxPayload)) { + this.currentMessage.push(buffer); } else { - self.currentMessage = null; - self.currentMessage = []; - self.currentMessageLength = 0; - self.error(new Error('Maximum payload exceeded'), 1009); + this.currentMessage = []; + this.currentMessageLength = 0; + this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); return; } + this.currentMessageLength += buffer.length; } - if (state.lastFragment) { - var messageBuffer = Buffer.concat(self.currentMessage); - self.currentMessage = []; - self.currentMessageLength = 0; - self.onbinary(messageBuffer, {masked: state.masked, buffer: messageBuffer}); + var messageBuffer = Buffer.concat(this.currentMessage); + this.currentMessage = []; + this.currentMessageLength = 0; + this.onbinary(messageBuffer, {masked: state.masked, buffer: messageBuffer}); } callback(); }); From 7e401e4a4e5d4120aad7556618f124d34a5a5b76 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 20 Jul 2016 17:50:23 +0200 Subject: [PATCH 070/669] [minor] Make sure that no string is emitted as an error --- lib/Receiver.hixie.js | 16 +++----------- lib/Receiver.js | 47 ++++++++++++++++++------------------------ lib/Sender.hixie.js | 14 ++----------- lib/WebSocket.js | 14 ++++++------- test/WebSocket.test.js | 34 ------------------------------ 5 files changed, 32 insertions(+), 93 deletions(-) diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index e90422efc..8123c6e96 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -40,7 +40,6 @@ class Receiver { */ add(data) { - if (this.dead) return; var self = this; function doAdd() { if (self.state === EMPTY) { @@ -56,7 +55,7 @@ class Receiver { } else { if (data[0] !== 0x00) { - self.error('payload must start with 0x00 byte', true); + self.error(new Error('payload must start with 0x00 byte'), true); return; } data = data.slice(1); @@ -146,18 +145,9 @@ class Receiver { * @api private */ - error(reason, terminate) { - if (this.dead) return; + error(err, terminate) { this.reset(); - if (typeof reason == 'string'){ - this.onerror(new Error(reason), terminate); - } - else if (reason.constructor == Error){ - this.onerror(reason, terminate); - } - else { - this.onerror(new Error('An error occured'), terminate); - } + this.onerror(err, terminate) return this; } diff --git a/lib/Receiver.js b/lib/Receiver.js index b840115ed..2c0181f6b 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -191,12 +191,12 @@ class Receiver { processPacket (data) { if (this.extensions[PerMessageDeflate.extensionName]) { if ((data[0] & 0x30) != 0) { - this.error('reserved fields (2, 3) must be empty', 1002); + this.error(new Error('reserved fields (2, 3) must be empty'), 1002); return; } } else { if ((data[0] & 0x70) != 0) { - this.error('reserved fields must be empty', 1002); + this.error(new Error('reserved fields must be empty'), 1002); return; } } @@ -206,24 +206,24 @@ class Receiver { var opcode = data[0] & 0xf; if (opcode === 0) { if (compressed) { - this.error('continuation frame cannot have the Per-message Compressed bits', 1002); + this.error(new Error('continuation frame cannot have the Per-message Compressed bits'), 1002); return; } // continuation frame this.state.fragmentedOperation = true; this.state.opcode = this.state.activeFragmentedOperation; if (!(this.state.opcode == 1 || this.state.opcode == 2)) { - this.error('continuation frame cannot follow current opcode', 1002); + this.error(new Error('continuation frame cannot follow current opcode'), 1002); return; } } else { if (opcode < 3 && this.state.activeFragmentedOperation != null) { - this.error('data frames after the initial data frame must have opcode 0', 1002); + this.error(new Error('data frames after the initial data frame must have opcode 0'), 1002); return; } if (opcode >= 8 && compressed) { - this.error('control frames cannot have the Per-message Compressed bits', 1002); + this.error(new Error('control frames cannot have the Per-message Compressed bits'), 1002); return; } this.state.compressed = compressed; @@ -235,7 +235,9 @@ class Receiver { else this.state.fragmentedOperation = false; } var handler = opcodes[this.state.opcode]; - if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode, 1002); + if (typeof handler == 'undefined') { + this.error(new Error(`no handler for opcode ${this.state.opcode}`), 1002); + } else { handler.start.call(this, data); } @@ -298,7 +300,7 @@ class Receiver { * @api private */ - unmask (mask, buf, binary) { + unmask(mask, buf, binary) { if (mask != null && buf != null) bufferUtil.unmask(buf, mask); if (binary) return buf; return buf != null ? buf.toString('utf8') : ''; @@ -310,18 +312,9 @@ class Receiver { * @api private */ - error (reason, protocolErrorCode) { - if (this.dead) return; + error(err, protocolErrorCode) { this.reset(); - if (typeof reason == 'string'){ - this.onerror(new Error(reason), protocolErrorCode); - } - else if (reason.constructor == Error){ - this.onerror(reason, protocolErrorCode); - } - else { - this.onerror(new Error('An error occured'), protocolErrorCode); - } + this.onerror(err, protocolErrorCode); return this; } @@ -382,7 +375,7 @@ class Receiver { this.currentPayloadLength = fullLength; return false; } - this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); + this.error(new Error(`payload cannot exceed ${this.maxPayload} bytes`), 1009); this.cleanup(); return true; @@ -461,7 +454,7 @@ var opcodes = { else if (firstLength == 127) { this.expectHeader(8, (data) => { if (readUInt32BE.call(data, 0) != 0) { - this.error('packets with length spanning more than 32 bit is currently not supported', 1008); + this.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; } var length = readUInt32BE.call(data, 4); @@ -491,7 +484,7 @@ var opcodes = { this.messageHandlers.push((callback) => { this.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { if (err) { - this.error(err.message, err.closeCode === 1009 ? 1009 : 1007); + this.error(err, err.closeCode === 1009 ? 1009 : 1007); return; } @@ -503,7 +496,7 @@ var opcodes = { else { this.currentMessage = []; this.currentMessageLength = 0; - this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); + this.error(new Error(`payload cannot exceed ${this.maxPayload} bytes`), 1009); return; } this.currentMessageLength += buffer.length; @@ -513,7 +506,7 @@ var opcodes = { this.currentMessage = []; this.currentMessageLength = 0; if (!Validation.isValidUTF8(messageBuffer)) { - this.error('invalid utf8 sequence', 1007); + this.error(new Error('invalid utf8 sequence'), 1007); return; } this.ontext(messageBuffer.toString('utf8'), {masked: state.masked, buffer: messageBuffer}); @@ -544,7 +537,7 @@ var opcodes = { else if (firstLength == 127) { this.expectHeader(8, (data) => { if (readUInt32BE.call(data, 0) != 0) { - this.error('packets with length spanning more than 32 bit is currently not supported', 1008); + this.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; } var length = readUInt32BE.call(data, 4, true); @@ -574,7 +567,7 @@ var opcodes = { this.messageHandlers.push((callback) => { this.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { if (err) { - this.error(err.message, err.closeCode === 1009 ? 1009 : 1007); + this.error(err, err.closeCode === 1009 ? 1009 : 1007); return; } @@ -586,7 +579,7 @@ var opcodes = { else { this.currentMessage = []; this.currentMessageLength = 0; - this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); + this.error(new Error(`payload cannot exceed ${this.maxPayload} bytes`), 1009); return; } this.currentMessageLength += buffer.length; diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index 69f4ae053..18235d3a6 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -68,7 +68,7 @@ class Sender extends EventEmitter { try { this.socket.write(buffer, 'binary', cb); } catch (e) { - this.error(e.toString()); + this.emit('error', e) } } @@ -85,7 +85,7 @@ class Sender extends EventEmitter { if (this.continuationFrame) this.socket.write(new Buffer([0xff], 'binary')); this.socket.write(new Buffer([0xff, 0x00]), 'binary', cb); } catch (e) { - this.error(e.toString()); + this.emit('error', e); } } @@ -103,16 +103,6 @@ class Sender extends EventEmitter { * @api public */ pong(data, options) {} - - /** - * Handles an error - * - * @api private - */ - error(reason) { - this.emit('error', reason); - return this; - } } module.exports = Sender; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 06cec66b9..8daec5a25 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -716,7 +716,7 @@ function initAsClient(address, protocols, options) { var error; if (!self.emit('unexpected-response', req, res)) { - error = new Error('unexpected server response (' + res.statusCode + ')'); + error = new Error(`unexpected server response (${res.statusCode})`); req.abort(); self.emit('error', error); } @@ -735,7 +735,7 @@ function initAsClient(address, protocols, options) { var serverKey = res.headers['sec-websocket-accept']; if (typeof serverKey === 'undefined' || serverKey !== expectedServerKey) { - self.emit('error', 'invalid server key'); + self.emit('error', new Error('invalid server key')); self.removeAllListeners(); socket.end(); return; @@ -754,7 +754,7 @@ function initAsClient(address, protocols, options) { } if (protError) { - self.emit('error', protError); + self.emit('error', new Error(protError)); self.removeAllListeners(); socket.end(); return; @@ -767,7 +767,7 @@ function initAsClient(address, protocols, options) { try { perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]); } catch (err) { - self.emit('error', 'invalid extension parameter'); + self.emit('error', new Error('invalid extension parameter')); self.removeAllListeners(); socket.end(); return; @@ -870,10 +870,10 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { self.close(code, data); }; - self._receiver.onerror = function onerror(reason, errorCode) { + self._receiver.onerror = function onerror(error, errorCode) { // close the connection when the receiver reports a HyBi error code - self.close(typeof errorCode !== 'undefined' ? errorCode : 1002, ''); - self.emit('error', (reason instanceof Error) ? reason : (new Error(reason))); + self.close(errorCode, ''); + self.emit('error', error); }; // finalize the client diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 6c6231732..610cf1bb7 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -46,40 +46,6 @@ describe('WebSocket', function() { ws.should.be.an.instanceOf(WebSocket); done(); }); - - it('should emit an error object when the receiver throws an error string', function(done) { - - var wss = new WebSocketServer({port: ++port}, function() { - - var ws = new WebSocket('ws://localhost:' + port); - - ws.on('open', function () { - ws._receiver.error('This is an error string', 1002); - }); - - ws.on('error', function (error) { - error.should.be.an.instanceof(Error); - done(); - }); - }); - }); - - it('should emit an error object when the receiver throws an error object', function(done) { - - var wss = new WebSocketServer({port: ++port}, function() { - - var ws = new WebSocket('ws://localhost:' + port); - - ws.on('open', function () { - ws._receiver.error(new Error('This is an error object'), 1002); - }); - - ws.on('error', function (error) { - error.should.be.an.instanceof(Error); - done(); - }); - }); - }); }); describe('options', function() { From df2ebff2574222168bf71c204958216e2a0bfc39 Mon Sep 17 00:00:00 2001 From: Pavel Kriz Date: Wed, 27 Jul 2016 17:55:10 +0200 Subject: [PATCH 071/669] Allow 'backlog' option for listening socket --- lib/WebSocketServer.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 9d3f2b900..4d23bcecc 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -41,7 +41,8 @@ function WebSocketServer(options, callback) { disableHixie: false, clientTracking: true, perMessageDeflate: true, - maxPayload: 100 * 1024 * 1024 + maxPayload: 100 * 1024 * 1024, + backlog: null // use default (511 as implemented in net.js) }, options); if (!isDefinedAndNonNull(options, 'port') && !isDefinedAndNonNull(options, 'server') && !options.noServer) { @@ -60,7 +61,12 @@ function WebSocketServer(options, callback) { res.end(body); }); this._server.allowHalfOpen = false; - this._server.listen(options.port, options.host, callback); + // maybe use a generic server.listen(options[, callback]) variant here, instead of two overloaded variants? + if (isDefinedAndNonNull(options, 'backlog')) { + this._server.listen(options.port, options.host, options.backlog, callback); + } else { + this._server.listen(options.port, options.host, callback); + } this._closeServer = function() { if (self._server) self._server.close(); From 55c68e76e05c808bad905388e3d11159ca050ab3 Mon Sep 17 00:00:00 2001 From: Tim Robinson Date: Fri, 19 Aug 2016 23:24:18 +0000 Subject: [PATCH 072/669] Fix stack overflow crash --- lib/Sender.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Sender.js b/lib/Sender.js index ff54e6fe2..5caa90f25 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -256,7 +256,9 @@ class Sender extends EventEmitter { handler(function() { self.processing = false; - self.flush(); + process.nextTick(function() { + self.flush(); + }); }); } From 4d57782a76f28358d56a18a3cdba67405fd30581 Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Fri, 19 Aug 2016 23:39:29 -0400 Subject: [PATCH 073/669] increased performance of conversion between Buffers and ArrayBuffers --- lib/Sender.js | 18 +++++++----------- lib/WebSocket.js | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index ff54e6fe2..bca164be7 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -142,8 +142,8 @@ class Sender extends EventEmitter { if (!Buffer.isBuffer(data)) { canModifyData = true; - if (data && (typeof data.byteLength !== 'undefined' || typeof data.buffer !== 'undefined')) { - data = getArrayBuffer(data); + if (data && (data.buffer || data) instanceof ArrayBuffer) { + data = getBufferFromNative(data); } else { // // If people want to send a number, this would allocate the number in @@ -268,7 +268,7 @@ class Sender extends EventEmitter { applyExtensions(data, fin, compress, callback) { if (compress && data) { if ((data.buffer || data) instanceof ArrayBuffer) { - data = getArrayBuffer(data); + data = getBufferFromNative(data); } this.extensions[PerMessageDeflate.extensionName].compress(data, fin, callback); } else { @@ -279,16 +279,12 @@ class Sender extends EventEmitter { module.exports = Sender; -function getArrayBuffer(data) { +function getBufferFromNative(data) { // data is either an ArrayBuffer or ArrayBufferView. - var array = new Uint8Array(data.buffer || data), - l = data.byteLength || data.length, - o = data.byteOffset || 0, - buffer = new Buffer(l); - for (var i = 0; i < l; ++i) { - buffer[i] = array[o + i]; + if (data.buffer) { + data = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength); } - return buffer; + return new Buffer(data); } function getRandomMask() { diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 8daec5a25..19abc5e2f 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -436,7 +436,7 @@ WebSocket.prototype.addEventListener = function(method, listener) { function onMessage (data, flags) { if (flags.binary && this.binaryType === 'arraybuffer') - data = new Uint8Array(data).buffer; + data = data.buffer.slice(data.byteOffset, data.byteOffset + data.length); listener.call(target, new MessageEvent(data, !!flags.binary, target)); } From bed724a3280027457c8919d6216d0f40f1c97198 Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Sat, 20 Aug 2016 01:41:44 -0400 Subject: [PATCH 074/669] improved getBufferFromNative performance --- lib/Sender.js | 7 +++---- lib/WebSocket.js | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index bca164be7..62e590539 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -281,10 +281,9 @@ module.exports = Sender; function getBufferFromNative(data) { // data is either an ArrayBuffer or ArrayBufferView. - if (data.buffer) { - data = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength); - } - return new Buffer(data); + return !data.buffer + ? new Buffer(data) + : new Buffer(data.buffer).slice(data.byteOffset, data.byteOffset + data.byteLength) } function getRandomMask() { diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 19abc5e2f..8daec5a25 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -436,7 +436,7 @@ WebSocket.prototype.addEventListener = function(method, listener) { function onMessage (data, flags) { if (flags.binary && this.binaryType === 'arraybuffer') - data = data.buffer.slice(data.byteOffset, data.byteOffset + data.length); + data = new Uint8Array(data).buffer; listener.call(target, new MessageEvent(data, !!flags.binary, target)); } From 9c231998a90b8364189eb21ff409c38a759de914 Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Sat, 20 Aug 2016 12:43:35 -0400 Subject: [PATCH 075/669] cleaned up frameAndSend, and improved v8 optimization potential --- lib/Sender.js | 66 ++++++++++++++++----------------------------------- 1 file changed, 21 insertions(+), 45 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 62e590539..f5bbc101c 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -127,24 +127,17 @@ class Sender extends EventEmitter { var canModifyData = false; if (!data) { - - try { - var buff = [opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)] - .concat(maskData ? [0, 0, 0, 0] : []); - this._socket.write(new Buffer(buff), 'binary', cb); - } - catch (e) { - if (typeof cb == 'function') cb(e); - else this.emit('error', e); - } + var buff = [opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)] + .concat(maskData ? [0, 0, 0, 0] : []); + sendFramedData.call(this, new Buffer(buff), null, cb); return; } if (!Buffer.isBuffer(data)) { - canModifyData = true; if (data && (data.buffer || data) instanceof ArrayBuffer) { data = getBufferFromNative(data); } else { + canModifyData = true; // // If people want to send a number, this would allocate the number in // bytes as memory size instead of storing the number as buffer value. So @@ -194,49 +187,17 @@ class Sender extends EventEmitter { outputBuffer[dataOffset - 1] = mask[3]; if (mergeBuffers) { bufferUtil.mask(data, mask, outputBuffer, dataOffset, dataLength); - try { - this._socket.write(outputBuffer, 'binary', cb); - } - catch (e) { - if (typeof cb == 'function') cb(e); - else this.emit('error', e); - } - } - else { + } else { bufferUtil.mask(data, mask, data, 0, dataLength); - try { - this._socket.write(outputBuffer, 'binary'); - this._socket.write(data, 'binary', cb); - } - catch (e) { - if (typeof cb == 'function') cb(e); - else this.emit('error', e); - } } } else { outputBuffer[1] = secondByte; if (mergeBuffers) { data.copy(outputBuffer, dataOffset); - try { - this._socket.write(outputBuffer, 'binary', cb); - } - catch (e) { - if (typeof cb == 'function') cb(e); - else this.emit('error', e); - } - } - else { - try { - this._socket.write(outputBuffer, 'binary'); - this._socket.write(data, 'binary', cb); - } - catch (e) { - if (typeof cb == 'function') cb(e); - else this.emit('error', e); - } } } + sendFramedData.call(this, outputBuffer, mergeBuffers ? null : data, cb); } /** @@ -294,3 +255,18 @@ function getRandomMask() { ~~(Math.random() * 255) ]); } + +function sendFramedData(outputBuffer, data, cb) { + try { + if (data) { + this._socket.write(outputBuffer, 'binary'); + this._socket.write(data, 'binary', cb); + } else { + this._socket.write(outputBuffer, 'binary', cb); + } + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } +} From becd825443537d2410c915dd55f47c02798aa213 Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Sat, 20 Aug 2016 12:46:35 -0400 Subject: [PATCH 076/669] fixed benchmark syntax --- bench/sender.benchmark.js | 3 ++- bench/util.js | 16 ++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/bench/sender.benchmark.js b/bench/sender.benchmark.js index 7c059bfec..0fc1d4f63 100644 --- a/bench/sender.benchmark.js +++ b/bench/sender.benchmark.js @@ -20,6 +20,7 @@ require('./util'); * Setup sender. */ +var sender; suite.on('start', function () { sender = new Sender(); sender._socket = { write: function() {} }; @@ -34,7 +35,7 @@ suite.on('cycle', function () { * Benchmarks */ -framePacket = new Buffer(200*1024); +var framePacket = new Buffer(200*1024); framePacket.fill(99); suite.add('frameAndSend, unmasked (200 kB)', function () { sender.frameAndSend(0x2, framePacket, true, false); diff --git a/bench/util.js b/bench/util.js index 38896d934..adce2c0c0 100644 --- a/bench/util.js +++ b/bench/util.js @@ -10,7 +10,7 @@ * Returns a Buffer from a "ff 00 ff"-type hex string. */ -getBufferFromHexString = function(byteStr) { +global.getBufferFromHexString = function(byteStr) { var bytes = byteStr.split(' '); var buf = new Buffer(bytes.length); for (var i = 0; i < bytes.length; ++i) { @@ -23,7 +23,7 @@ getBufferFromHexString = function(byteStr) { * Returns a hex string from a Buffer. */ -getHexStringFromBuffer = function(data) { +global.getHexStringFromBuffer = function(data) { var s = ''; for (var i = 0; i < data.length; ++i) { s += padl(data[i].toString(16), 2, '0') + ' '; @@ -35,7 +35,7 @@ getHexStringFromBuffer = function(data) { * Splits a buffer in two parts. */ -splitBuffer = function(buffer) { +global.splitBuffer = function(buffer) { var b1 = new Buffer(Math.ceil(buffer.length / 2)); buffer.copy(b1, 0, 0, b1.length); var b2 = new Buffer(Math.floor(buffer.length / 2)); @@ -47,7 +47,7 @@ splitBuffer = function(buffer) { * Performs hybi07+ type masking on a hex string or buffer. */ -mask = function(buf, maskString) { +global.mask = function(buf, maskString) { if (typeof buf == 'string') buf = new Buffer(buf); var mask = getBufferFromHexString(maskString || '34 83 a8 68'); for (var i = 0; i < buf.length; ++i) { @@ -60,7 +60,7 @@ mask = function(buf, maskString) { * Returns a hex string representing the length of a message */ -getHybiLengthAsHexString = function(len, masked) { +global.getHybiLengthAsHexString = function(len, masked) { if (len < 126) { var buf = new Buffer(1); buf[0] = (masked ? 0x80 : 0) | len; @@ -82,7 +82,7 @@ getHybiLengthAsHexString = function(len, masked) { * Unpacks a Buffer into a number. */ -unpack = function(buffer) { +global.unpack = function(buffer) { var n = 0; for (var i = 0; i < buffer.length; ++i) { n = (i == 0) ? buffer[i] : (n * 256) + buffer[i]; @@ -94,7 +94,7 @@ unpack = function(buffer) { * Returns a hex string, representing a specific byte count 'length', from a number. */ -pack = function(length, number) { +global.pack = function(length, number) { return padl(number.toString(16), length, '0').replace(/([0-9a-f][0-9a-f])/gi, '$1 ').trim(); } @@ -102,6 +102,6 @@ pack = function(length, number) { * Left pads the string 's' to a total length of 'n' with char 'c'. */ -padl = function(s, n, c) { +global.padl = function(s, n, c) { return new Array(1 + n - s.length).join(c) + s; } From 584842a991fad1745ffc82a58b07c14d177c40d6 Mon Sep 17 00:00:00 2001 From: Tim Robinson Date: Thu, 15 Sep 2016 09:55:14 +0000 Subject: [PATCH 077/669] Adding test case to reproduce crash --- test/Sender.test.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/test/Sender.test.js b/test/Sender.test.js index 8b5ccc06b..42b23ff7d 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -60,8 +60,26 @@ describe('Sender', function() { }); sender.send('hi', { compress: true }); }); - }); + + it('Should be able to handle many send calls while processing without crashing on flush', function(done) { + var messageCount = 0; + var maxMessages = 5000; + var sender = new Sender({ + write: function(data) { + messageCount++; + if (messageCount > maxMessages) return done(); + } + }); + for (var i = 0; i < maxMessages; i++) { + sender.processing = true; + sender.send('hi', { compress: false, fin: true, binary: false, mask: false }); + } + sender.processing = false; + sender.send('hi', { compress: false, fin: true, binary: false, mask: false }); + }); + }); + describe('#close', function() { it('should consume all data before closing', function(done) { var perMessageDeflate = new PerMessageDeflate(); From a45c991afd9e0624487773315f3010d69f07a582 Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Thu, 15 Sep 2016 16:55:25 -0400 Subject: [PATCH 078/669] Added checkServerIdentity option to WebSocket (#701) * Added checkServerIdentity option to WebSocket --- lib/WebSocket.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 8daec5a25..a4a2e4040 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -583,6 +583,7 @@ function initAsClient(address, protocols, options) { ca: null, ciphers: null, rejectUnauthorized: null, + checkServerIdentity: null, perMessageDeflate: true, localAddress: null }, options); @@ -668,7 +669,8 @@ function initAsClient(address, protocols, options) { || isDefinedAndNonNull(options, 'cert') || isDefinedAndNonNull(options, 'ca') || isDefinedAndNonNull(options, 'ciphers') - || isDefinedAndNonNull(options, 'rejectUnauthorized')) { + || isDefinedAndNonNull(options, 'rejectUnauthorized') + || isDefinedAndNonNull(options, 'checkServerIdentity')) { if (isDefinedAndNonNull(options, 'pfx')) requestOptions.pfx = options.pfx; if (isDefinedAndNonNull(options, 'key')) requestOptions.key = options.key; @@ -678,6 +680,8 @@ function initAsClient(address, protocols, options) { if (isDefinedAndNonNull(options, 'ciphers')) requestOptions.ciphers = options.ciphers; if (isDefinedAndNonNull(options, 'rejectUnauthorized')) requestOptions.rejectUnauthorized = options.rejectUnauthorized; + if (isDefinedAndNonNull(options, 'checkServerIdentity')) + requestOptions.checkServerIdentity = options.checkServerIdentity; if (!agent) { // global agent ignores client side certificates From acce83e6c65f1b14df16da4f6d67232ff0be73a6 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Sat, 17 Sep 2016 18:46:44 +0200 Subject: [PATCH 079/669] Avoid using Buffer.concat() on single part messages and provide length otherwise (#826) --- lib/Receiver.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 2c0181f6b..2b9db15cb 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -502,7 +502,9 @@ var opcodes = { this.currentMessageLength += buffer.length; } if (state.lastFragment) { - var messageBuffer = Buffer.concat(this.currentMessage); + var messageBuffer = this.currentMessage.length === 1 ? + this.currentMessage[0] : + Buffer.concat(this.currentMessage, this.currentMessageLength); this.currentMessage = []; this.currentMessageLength = 0; if (!Validation.isValidUTF8(messageBuffer)) { @@ -585,7 +587,9 @@ var opcodes = { this.currentMessageLength += buffer.length; } if (state.lastFragment) { - var messageBuffer = Buffer.concat(this.currentMessage); + var messageBuffer = this.currentMessage.length === 1 ? + this.currentMessage[0] : + Buffer.concat(this.currentMessage, this.currentMessageLength); this.currentMessage = []; this.currentMessageLength = 0; this.onbinary(messageBuffer, {masked: state.masked, buffer: messageBuffer}); From e0776e5c0198784cd3c1d27cf7164baba0c3b74f Mon Sep 17 00:00:00 2001 From: Ed S Date: Wed, 21 Sep 2016 13:14:09 -0700 Subject: [PATCH 080/669] Correct spelling of 'occurred' (#834) --- doc/ws.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index 25d08fec8..401688806 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -68,7 +68,7 @@ If a property is empty then either an offered configuration or a default value i ### server.close([callback]) -Close the server and terminate all clients, calls callback when done with an error if one occured. +Close the server and terminate all clients, calls callback when done with an error if one occurred. ### server.handleUpgrade(request, socket, upgradeHead, callback) From fc4edd4f5d946c7dccb3fe49adadcca421c7cdb5 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 23 Sep 2016 19:11:51 +0200 Subject: [PATCH 081/669] [benchmark] Fix speed benchmark (#839) --- bench/speed.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bench/speed.js b/bench/speed.js index f8ae9a49f..2a50ec79e 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -28,7 +28,12 @@ function generateRandomData(size) { } if (cluster.isMaster) { - var wss = new WebSocketServer({port: 8181}, function() { + var wss = new WebSocketServer({ + perMessageDeflate: false, + clientTracking: false, + maxPayload: Infinity, + port: 8181 + }, function() { cluster.fork(); }); wss.on('connection', function(ws) { @@ -37,7 +42,7 @@ if (cluster.isMaster) { }); ws.on('close', function() {}); }); - cluster.on('death', function(worker) { + cluster.on('exit', function(worker) { wss.close(); }); } From a2e7338b8311f8697a1375a86c3c5a06185fb259 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 25 Sep 2016 20:17:02 +0200 Subject: [PATCH 082/669] [benchmark] Fix and clean up parser and sender benchmarks --- bench/parser.benchmark.js | 132 ++++++++++---------------------------- bench/sender.benchmark.js | 64 ++++-------------- bench/util.js | 100 ++++++++--------------------- package.json | 2 +- 4 files changed, 74 insertions(+), 224 deletions(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index b912b1953..a8d245833 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -6,112 +6,50 @@ 'use strict'; -/** - * Benchmark dependencies. - */ - -var benchmark = require('benchmark') - , Receiver = require('../').Receiver - , suite = new benchmark.Suite('Receiver'); -require('tinycolor'); -require('./util'); +const benchmark = require('benchmark') -/** - * Setup receiver. - */ +const Receiver = require('../').Receiver +const util = require('./util'); -suite.on('start', function () { - receiver = new Receiver(); -}); +function createBinaryPacket(length) { + const message = Buffer.alloc(length); -suite.on('cycle', function () { - receiver = new Receiver(); -}); - -/** - * Benchmarks. - */ - -var pingMessage = 'Hello' - , pingPacket1 = getBufferFromHexString('89 ' + (pack(2, 0x80 | pingMessage.length)) + - ' 34 83 a8 68 '+ getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'))); -suite.add('ping message', function () { - receiver.add(pingPacket1); -}); + for (var i = 0; i < length; ++i) message[i] = i % 10; -var pingPacket2 = getBufferFromHexString('89 00') -suite.add('ping with no data', function () { - receiver.add(pingPacket2); -}); + return Buffer.from('82' + util.getHybiLengthAsHexString(length, true) + '3483a868' + + util.mask(message, '3483a868').toString('hex'), 'hex'); +} -var closePacket = getBufferFromHexString('88 00'); -suite.add('close message', function () { +const pingMessage = 'Hello' +const pingPacket1 = Buffer.from('89' + util.pack(2, 0x80 | pingMessage.length) + + '3483a868' + util.mask(pingMessage, '3483a868').toString('hex'), 'hex'); +const pingPacket2 = Buffer.from('8900', 'hex'); +const closePacket = Buffer.from('8800', 'hex'); +const maskedTextPacket = Buffer.from('81933483a86801b992524fa1c60959e68a5216e6cb005ba1d5', 'hex'); +const binaryDataPacket = createBinaryPacket(125); +const binaryDataPacket2 = createBinaryPacket(65535); +const binaryDataPacket3 = createBinaryPacket(200 * 1024) + +var receiver = new Receiver({}, Infinity); +const suite = new benchmark.Suite(); + +suite.add('ping message', () => receiver.add(pingPacket1)); +suite.add('ping with no data', () => receiver.add(pingPacket2)); +suite.add('close message', () => { receiver.add(closePacket); receiver.endPacket(); +}) +suite.add('masked text message', () => receiver.add(maskedTextPacket)); +suite.add('binary data (125 bytes)', () => receiver.add(binaryDataPacket)); +suite.add('binary data (65535 bytes)', () => receiver.add(binaryDataPacket2)); +suite.add('binary data (200 kB)', () => receiver.add(binaryDataPacket3)); +suite.on('cycle', (e) => { + console.log(e.target.toString()); + receiver = new Receiver(); }); -var maskedTextPacket = getBufferFromHexString('81 93 34 83 a8 68 01 b9 92 52 4f a1 c6 09 59 e6 8a 52 16 e6 cb 00 5b a1 d5'); -suite.add('masked text message', function () { - receiver.add(maskedTextPacket); -}); - -binaryDataPacket = (function() { - var length = 125 - , message = new Buffer(length) - for (var i = 0; i < length; ++i) message[i] = i % 10; - return getBufferFromHexString('82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' - + getHexStringFromBuffer(mask(message), '34 83 a8 68')); -})(); -suite.add('binary data (125 bytes)', function () { - try { - receiver.add(binaryDataPacket); - - } - catch(e) {console.log(e)} -}); - -binaryDataPacket2 = (function() { - var length = 65535 - , message = new Buffer(length) - for (var i = 0; i < length; ++i) message[i] = i % 10; - return getBufferFromHexString('82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' - + getHexStringFromBuffer(mask(message), '34 83 a8 68')); -})(); -suite.add('binary data (65535 bytes)', function () { - receiver.add(binaryDataPacket2); -}); - -binaryDataPacket3 = (function() { - var length = 200*1024 - , message = new Buffer(length) - for (var i = 0; i < length; ++i) message[i] = i % 10; - return getBufferFromHexString('82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' - + getHexStringFromBuffer(mask(message), '34 83 a8 68')); -})(); -suite.add('binary data (200 kB)', function () { - receiver.add(binaryDataPacket3); -}); - -/** - * Output progress. - */ - -suite.on('cycle', function (bench, details) { - console.log('\n ' + suite.name.grey, details.name.white.bold); - console.log(' ' + [ - details.hz.toFixed(2).cyan + ' ops/sec'.grey - , details.count.toString().white + ' times executed'.grey - , 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey - , - ].join(', '.grey)); -}); - -/** - * Run/export benchmarks. - */ - -if (!module.parent) { - suite.run(); +if (require.main === module) { + suite.run({ async: true }); } else { module.exports = suite; } diff --git a/bench/sender.benchmark.js b/bench/sender.benchmark.js index 0fc1d4f63..7e9f28728 100644 --- a/bench/sender.benchmark.js +++ b/bench/sender.benchmark.js @@ -6,64 +6,26 @@ 'use strict'; -/** - * Benchmark dependencies. - */ +const benchmark = require('benchmark') -var benchmark = require('benchmark') - , Sender = require('../').Sender - , suite = new benchmark.Suite('Sender'); -require('tinycolor'); -require('./util'); +const Sender = require('../').Sender -/** - * Setup sender. - */ +const framePacket = Buffer.alloc(200 * 1024).fill(99); -var sender; -suite.on('start', function () { - sender = new Sender(); - sender._socket = { write: function() {} }; -}); +const suite = new benchmark.Suite(); +var sender = new Sender(); +sender._socket = { write() {} }; -suite.on('cycle', function () { +suite.add('frameAndSend, unmasked (200 kB)', () => sender.frameAndSend(0x2, framePacket, true, false)); +suite.add('frameAndSend, masked (200 kB)', () => sender.frameAndSend(0x2, framePacket, true, true)); +suite.on('cycle', (e) => { + console.log(e.target.toString()); sender = new Sender(); - sender._socket = { write: function() {} }; -}); - -/** - * Benchmarks - */ - -var framePacket = new Buffer(200*1024); -framePacket.fill(99); -suite.add('frameAndSend, unmasked (200 kB)', function () { - sender.frameAndSend(0x2, framePacket, true, false); + sender._socket = { write() {} }; }); -suite.add('frameAndSend, masked (200 kB)', function () { - sender.frameAndSend(0x2, framePacket, true, true); -}); - -/** - * Output progress. - */ - -suite.on('cycle', function (bench, details) { - console.log('\n ' + suite.name.grey, details.name.white.bold); - console.log(' ' + [ - details.hz.toFixed(2).cyan + ' ops/sec'.grey - , details.count.toString().white + ' times executed'.grey - , 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey - , - ].join(', '.grey)); -}); - -/** - * Run/export benchmarks. - */ -if (!module.parent) { - suite.run(); +if (require.main === module) { + suite.run({ async: true }); } else { module.exports = suite; } diff --git a/bench/util.js b/bench/util.js index adce2c0c0..3b94c63ff 100644 --- a/bench/util.js +++ b/bench/util.js @@ -7,101 +7,51 @@ 'use strict'; /** - * Returns a Buffer from a "ff 00 ff"-type hex string. + * Performs hybi07+ type masking on a hex string or buffer. */ +function mask(buf, maskString) { + const _mask = Buffer.from(maskString || '3483a868', 'hex'); + + if (typeof buf === 'string') buf = Buffer.from(buf); -global.getBufferFromHexString = function(byteStr) { - var bytes = byteStr.split(' '); - var buf = new Buffer(bytes.length); - for (var i = 0; i < bytes.length; ++i) { - buf[i] = parseInt(bytes[i], 16); + for (var i = 0; i < buf.length; ++i) { + buf[i] ^= _mask[i % 4]; } + return buf; } /** - * Returns a hex string from a Buffer. + * Left pads the string `s` to a total length of `n` with char `c`. */ - -global.getHexStringFromBuffer = function(data) { - var s = ''; - for (var i = 0; i < data.length; ++i) { - s += padl(data[i].toString(16), 2, '0') + ' '; - } - return s.trim(); +function padl(s, n, c) { + return c.repeat(n - s.length) + s; } /** - * Splits a buffer in two parts. + * Returns a hex string, representing a specific byte count `length`, from a number. */ - -global.splitBuffer = function(buffer) { - var b1 = new Buffer(Math.ceil(buffer.length / 2)); - buffer.copy(b1, 0, 0, b1.length); - var b2 = new Buffer(Math.floor(buffer.length / 2)); - buffer.copy(b2, 0, b1.length, b1.length + b2.length); - return [b1, b2]; +function pack(length, number) { + return padl(number.toString(16), length, '0'); } /** - * Performs hybi07+ type masking on a hex string or buffer. + * Returns a hex string representing the length of a message. */ +function getHybiLengthAsHexString(len, masked) { + var s; -global.mask = function(buf, maskString) { - if (typeof buf == 'string') buf = new Buffer(buf); - var mask = getBufferFromHexString(maskString || '34 83 a8 68'); - for (var i = 0; i < buf.length; ++i) { - buf[i] ^= mask[i % 4]; - } - return buf; -} + masked = masked ? 0x80 : 0; -/** - * Returns a hex string representing the length of a message - */ - -global.getHybiLengthAsHexString = function(len, masked) { if (len < 126) { - var buf = new Buffer(1); - buf[0] = (masked ? 0x80 : 0) | len; - } - else if (len < 65536) { - var buf = new Buffer(3); - buf[0] = (masked ? 0x80 : 0) | 126; - getBufferFromHexString(pack(4, len)).copy(buf, 1); + s = pack(2, masked | len); + } else if (len < 65536) { + s = pack(2, masked | 126) + pack(4, len); + } else { + s = pack(2, masked | 127) + pack(16, len); } - else { - var buf = new Buffer(9); - buf[0] = (masked ? 0x80 : 0) | 127; - getBufferFromHexString(pack(16, len)).copy(buf, 1); - } - return getHexStringFromBuffer(buf); -} - -/** - * Unpacks a Buffer into a number. - */ -global.unpack = function(buffer) { - var n = 0; - for (var i = 0; i < buffer.length; ++i) { - n = (i == 0) ? buffer[i] : (n * 256) + buffer[i]; - } - return n; + return s; } -/** - * Returns a hex string, representing a specific byte count 'length', from a number. - */ - -global.pack = function(length, number) { - return padl(number.toString(16), length, '0').replace(/([0-9a-f][0-9a-f])/gi, '$1 ').trim(); -} - -/** - * Left pads the string 's' to a total length of 'n' with char 'c'. - */ - -global.padl = function(s, n, c) { - return new Array(1 + n - s.length).join(c) + s; -} +module.exports = { getHybiLengthAsHexString, mask, pack }; diff --git a/package.json b/package.json index 68d9f52e0..80c85195b 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ }, "devDependencies": { "ansi": "0.3.x", - "benchmark": "0.3.x", + "benchmark": "2.1.x", "bufferutil": "1.2.x", "eslint": "^2.12.0", "expect.js": "0.3.x", From d2175a3a0c1db3aba22ff147906f4fcf1b295e4c Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Mon, 26 Sep 2016 03:11:24 +0200 Subject: [PATCH 083/669] Replace bufferSize with highWatermark. (#842) * Replace bufferSize with highWatermark. bufferSize was removed from node in 0.9.12 so this test was ineffective * Set stream encoding in options argument --- test/WebSocket.test.js | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 610cf1bb7..f1c48a0af 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -928,8 +928,7 @@ describe('WebSocket', function() { var ws = new WebSocket('ws://localhost:' + port); var callbackFired = false; ws.on('open', function() { - var fileStream = fs.createReadStream('test/fixtures/textfile'); - fileStream.bufferSize = 100; + var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); ws.send(fileStream, {binary: true}, function(error) { assert.equal(null, error); callbackFired = true; @@ -953,9 +952,7 @@ describe('WebSocket', function() { var ws = new WebSocket('ws://localhost:' + port); var callbackFired = false; ws.on('open', function() { - var fileStream = fs.createReadStream('test/fixtures/textfile'); - fileStream.setEncoding('utf8'); - fileStream.bufferSize = 100; + var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream, {binary: false}, function(error) { assert.equal(null, error); callbackFired = true; @@ -978,9 +975,7 @@ describe('WebSocket', function() { server.createServer(++port, function(srv) { var ws = new WebSocket('ws://localhost:' + port); ws.on('open', function() { - var fileStream = fs.createReadStream('test/fixtures/textfile'); - fileStream.setEncoding('utf8'); - fileStream.bufferSize = 100; + var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.send('foobar'); ws.send('baz'); @@ -1011,9 +1006,7 @@ describe('WebSocket', function() { server.createServer(++port, function(srv) { var ws = new WebSocket('ws://localhost:' + port); ws.on('open', function() { - var fileStream = fs.createReadStream('test/fixtures/textfile'); - fileStream.setEncoding('utf8'); - fileStream.bufferSize = 100; + var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); var i = 0; ws.stream(function(error, send) { @@ -1044,9 +1037,7 @@ describe('WebSocket', function() { server.createServer(++port, function(srv) { var ws = new WebSocket('ws://localhost:' + port); ws.on('open', function() { - var fileStream = fs.createReadStream('test/fixtures/textfile'); - fileStream.setEncoding('utf8'); - fileStream.bufferSize = 100; + var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.ping('foobar'); }); @@ -1075,9 +1066,7 @@ describe('WebSocket', function() { server.createServer(++port, function(srv) { var ws = new WebSocket('ws://localhost:' + port); ws.on('open', function() { - var fileStream = fs.createReadStream('test/fixtures/textfile'); - fileStream.setEncoding('utf8'); - fileStream.bufferSize = 100; + var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.pong('foobar'); }); @@ -1106,9 +1095,7 @@ describe('WebSocket', function() { server.createServer(++port, function(srv) { var ws = new WebSocket('ws://localhost:' + port); ws.on('open', function() { - var fileStream = fs.createReadStream('test/fixtures/textfile'); - fileStream.setEncoding('utf8'); - fileStream.bufferSize = 100; + var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.close(1000, 'foobar'); }); @@ -1397,9 +1384,7 @@ describe('WebSocket', function() { var ws = new WebSocket('ws://localhost:' + port); var errorGiven = false; ws.on('open', function() { - var fileStream = fs.createReadStream('test/fixtures/textfile'); - fileStream.setEncoding('utf8'); - fileStream.bufferSize = 100; + var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream, function(error) { errorGiven = error != null; }); @@ -2120,8 +2105,7 @@ describe('WebSocket', function() { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); var callbackFired = false; ws.on('open', function() { - var fileStream = fs.createReadStream('test/fixtures/textfile'); - fileStream.bufferSize = 100; + var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); ws.send(fileStream, {binary: true, compress: true}, function(error) { assert.equal(null, error); callbackFired = true; From 250b1d5e52af8f23987cb8a0cd40262a83d2008c Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Mon, 26 Sep 2016 03:13:07 +0200 Subject: [PATCH 084/669] Cleanups (#841) * Remove third argument to unmask as it is always true. * Remove readUInt16BE and readUInt32BE and use the builtin functions. --- lib/Receiver.js | 41 ++++++++++++++--------------------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 2b9db15cb..ccf37ae37 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -300,10 +300,9 @@ class Receiver { * @api private */ - unmask(mask, buf, binary) { + unmask(mask, buf) { if (mask != null && buf != null) bufferUtil.unmask(buf, mask); - if (binary) return buf; - return buf != null ? buf.toString('utf8') : ''; + return buf; } /** @@ -390,18 +389,6 @@ module.exports = Receiver; * Buffer utilities */ -function readUInt16BE(start) { - return (this[start] << 8) + - this[start + 1]; -} - -function readUInt32BE(start) { - return (this[start] << 24) + - (this[start + 1] << 16) + - (this[start + 2] << 8) + - this[start + 3]; -} - function fastCopy(length, srcBuffer, dstBuffer, dstOffset) { /* eslint-disable no-fallthrough */ switch (length) { @@ -446,18 +433,18 @@ var opcodes = { } else if (firstLength == 126) { this.expectHeader(2, (data) => { - var length = readUInt16BE.call(data, 0); + var length = data.readUInt16BE(0, true); if (this.maxPayloadExceeded(length)) return; opcodes['1'].getData.call(this, length); }); } else if (firstLength == 127) { this.expectHeader(8, (data) => { - if (readUInt32BE.call(data, 0) != 0) { + if (data.readUInt32BE(0, true) != 0) { this.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; } - var length = readUInt32BE.call(data, 4); + var length = data.readUInt32BE(4, true); if (this.maxPayloadExceeded(length)) return; opcodes['1'].getData.call(this, length); }); @@ -479,7 +466,7 @@ var opcodes = { } }, finish: function(mask, data) { - var packet = this.unmask(mask, data, true) || new Buffer(0); + var packet = this.unmask(mask, data) || new Buffer(0); var state = clone(this.state); this.messageHandlers.push((callback) => { this.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { @@ -531,18 +518,18 @@ var opcodes = { } else if (firstLength == 126) { this.expectHeader(2, (data) => { - var length = readUInt16BE.call(data, 0); + var length = data.readUInt16BE(0, true); if (this.maxPayloadExceeded(length)) return; opcodes['2'].getData.call(this, length); }); } else if (firstLength == 127) { this.expectHeader(8, (data) => { - if (readUInt32BE.call(data, 0) != 0) { + if (data.readUInt32BE(0, true) != 0) { this.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; } - var length = readUInt32BE.call(data, 4, true); + var length = data.readUInt32BE(4, true); if (this.maxPayloadExceeded(length)) return; opcodes['2'].getData.call(this, length); }); @@ -564,7 +551,7 @@ var opcodes = { } }, finish: function(mask, data) { - var packet = this.unmask(mask, data, true) || new Buffer(0); + var packet = this.unmask(mask, data) || new Buffer(0); var state = clone(this.state); this.messageHandlers.push((callback) => { this.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { @@ -637,7 +624,7 @@ var opcodes = { }, finish: function(mask, data) { var self = this; - data = self.unmask(mask, data, true); + data = self.unmask(mask, data); var state = clone(this.state); this.messageHandlers.push(function() { @@ -645,7 +632,7 @@ var opcodes = { self.error('close packets with data must be at least two bytes long', 1002); return; } - var code = data && data.length > 1 ? readUInt16BE.call(data, 0) : 1000; + var code = data && data.length > 1 ? data.readUInt16BE(0, true) : 1000; if (!ErrorCodes.isValidErrorCode(code)) { self.error('invalid error code', 1002); return; @@ -701,7 +688,7 @@ var opcodes = { }, finish: function(mask, data) { var self = this; - data = this.unmask(mask, data, true); + data = this.unmask(mask, data); var state = clone(this.state); this.messageHandlers.push(function(callback) { self.onping(data, {masked: state.masked, binary: true}); @@ -745,7 +732,7 @@ var opcodes = { } }, finish: function(mask, data) { - data = this.unmask(mask, data, true); + data = this.unmask(mask, data); var state = clone(this.state); this.messageHandlers.push((callback) => { this.onpong(data, {masked: state.masked, binary: true}); From cad49db8a825d99c68a9330be86e0aa590ecf53e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 26 Sep 2016 19:06:33 +0200 Subject: [PATCH 085/669] Avoid using `Function.prototype.call()` (#846) * [minor] Avoid using `Function.prototype.call()` * [codestyle] Replace `var` with `const` in lib/Receiver.js --- lib/Receiver.js | 400 +++++++++++++++++++++--------------------------- 1 file changed, 178 insertions(+), 222 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index ccf37ae37..518f0cfe3 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -80,23 +80,23 @@ class Receiver { add(data) { if (this.dead) return; - var dataLength = data.length; + const dataLength = data.length; if (dataLength == 0) return; if (this.expectBuffer == null) { this.overflow.push(data); return; } - var toRead = Math.min(dataLength, this.expectBuffer.length - this.expectOffset); + const toRead = Math.min(dataLength, this.expectBuffer.length - this.expectOffset); fastCopy(toRead, data, this.expectBuffer, this.expectOffset); this.expectOffset += toRead; if (toRead < dataLength) { this.overflow.push(data.slice(toRead)); } while (this.expectBuffer && this.expectOffset == this.expectBuffer.length) { - var bufferForHandler = this.expectBuffer; + const bufferForHandler = this.expectBuffer; this.expectBuffer = null; this.expectOffset = 0; - this.expectHandler.call(this, bufferForHandler); + this.expectHandler(bufferForHandler); } } @@ -140,8 +140,8 @@ class Receiver { var toRead = length; while (toRead > 0 && this.overflow.length > 0) { var fromOverflow = this.overflow.pop(); - if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); var read = Math.min(fromOverflow.length, toRead); + if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); this.expectOffset += read; toRead -= read; @@ -164,8 +164,8 @@ class Receiver { var toRead = length; while (toRead > 0 && this.overflow.length > 0) { var fromOverflow = this.overflow.pop(); - if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); var read = Math.min(fromOverflow.length, toRead); + if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); this.expectOffset += read; toRead -= read; @@ -188,7 +188,7 @@ class Receiver { * @api private */ - processPacket (data) { + processPacket(data) { if (this.extensions[PerMessageDeflate.extensionName]) { if ((data[0] & 0x30) != 0) { this.error(new Error('reserved fields (2, 3) must be empty'), 1002); @@ -202,8 +202,8 @@ class Receiver { } this.state.lastFragment = (data[0] & 0x80) == 0x80; this.state.masked = (data[1] & 0x80) == 0x80; - var compressed = (data[0] & 0x40) == 0x40; - var opcode = data[0] & 0xf; + const compressed = (data[0] & 0x40) == 0x40; + const opcode = data[0] & 0xf; if (opcode === 0) { if (compressed) { this.error(new Error('continuation frame cannot have the Per-message Compressed bits'), 1002); @@ -216,8 +216,7 @@ class Receiver { this.error(new Error('continuation frame cannot follow current opcode'), 1002); return; } - } - else { + } else { if (opcode < 3 && this.state.activeFragmentedOperation != null) { this.error(new Error('data frames after the initial data frame must have opcode 0'), 1002); return; @@ -231,15 +230,15 @@ class Receiver { if (this.state.lastFragment === false) { this.state.fragmentedOperation = true; this.state.activeFragmentedOperation = opcode; + } else { + this.state.fragmentedOperation = false; } - else this.state.fragmentedOperation = false; } - var handler = opcodes[this.state.opcode]; + const handler = opcodes[this.state.opcode]; if (typeof handler == 'undefined') { this.error(new Error(`no handler for opcode ${this.state.opcode}`), 1002); - } - else { - handler.start.call(this, data); + } else { + handler.start(this, data); } } @@ -345,7 +344,7 @@ class Receiver { applyExtensions(messageBuffer, fin, compressed, callback) { if (compressed) { - var extension = this.extensions[PerMessageDeflate.extensionName]; + const extension = this.extensions[PerMessageDeflate.extensionName]; extension.decompress(messageBuffer, fin, (err, buffer) => { if (this.dead) return; if (err) { @@ -369,7 +368,7 @@ class Receiver { if (this.maxPayload === undefined || this.maxPayload === null || this.maxPayload < 1) { return false; } - var fullLength = this.currentPayloadLength + length; + const fullLength = this.currentPayloadLength + length; if (fullLength < this.maxPayload) { this.currentPayloadLength = fullLength; return false; @@ -383,8 +382,6 @@ class Receiver { module.exports = Receiver; - - /** * Buffer utilities */ @@ -421,325 +418,284 @@ function clone(obj) { * Opcode handlers */ -var opcodes = { +const opcodes = { // text '1': { - start: function(data) { + start: (receiver, data) => { // decode length - var firstLength = data[1] & 0x7f; + const firstLength = data[1] & 0x7f; if (firstLength < 126) { - if (this.maxPayloadExceeded(firstLength)) return; - opcodes['1'].getData.call(this, firstLength); - } - else if (firstLength == 126) { - this.expectHeader(2, (data) => { - var length = data.readUInt16BE(0, true); - if (this.maxPayloadExceeded(length)) return; - opcodes['1'].getData.call(this, length); + if (receiver.maxPayloadExceeded(firstLength)) return; + opcodes['1'].getData(receiver, firstLength); + } else if (firstLength == 126) { + receiver.expectHeader(2, (data) => { + const length = data.readUInt16BE(0, true); + if (receiver.maxPayloadExceeded(length)) return; + opcodes['1'].getData(receiver, length); }); - } - else if (firstLength == 127) { - this.expectHeader(8, (data) => { + } else if (firstLength == 127) { + receiver.expectHeader(8, (data) => { if (data.readUInt32BE(0, true) != 0) { - this.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); + receiver.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; } - var length = data.readUInt32BE(4, true); - if (this.maxPayloadExceeded(length)) return; - opcodes['1'].getData.call(this, length); + const length = data.readUInt32BE(4, true); + if (receiver.maxPayloadExceeded(length)) return; + opcodes['1'].getData(receiver, length); }); } }, - getData: function(length) { - if (this.state.masked) { - this.expectHeader(4, (data) => { - var mask = data; - this.expectData(length, (data) => { - opcodes['1'].finish.call(this, mask, data); - }); - }); - } - else { - this.expectData(length, (data) => { - opcodes['1'].finish.call(this, null, data); + getData: (receiver, length) => { + if (receiver.state.masked) { + receiver.expectHeader(4, (mask) => { + receiver.expectData(length, (data) => opcodes['1'].finish(receiver, mask, data)); }); + } else { + receiver.expectData(length, (data) => opcodes['1'].finish(receiver, null, data)); } }, - finish: function(mask, data) { - var packet = this.unmask(mask, data) || new Buffer(0); - var state = clone(this.state); - this.messageHandlers.push((callback) => { - this.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { + finish: (receiver, mask, data) => { + const packet = receiver.unmask(mask, data) || new Buffer(0); + const state = clone(receiver.state); + receiver.messageHandlers.push((callback) => { + receiver.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { if (err) { - this.error(err, err.closeCode === 1009 ? 1009 : 1007); + receiver.error(err, err.closeCode === 1009 ? 1009 : 1007); return; } if (buffer != null) { - if (this.maxPayload == 0 || (this.maxPayload > 0 && - (this.currentMessageLength + buffer.length) < this.maxPayload)) { - this.currentMessage.push(buffer); - } - else { - this.currentMessage = []; - this.currentMessageLength = 0; - this.error(new Error(`payload cannot exceed ${this.maxPayload} bytes`), 1009); + if (receiver.maxPayload == 0 || (receiver.maxPayload > 0 && + (receiver.currentMessageLength + buffer.length) < receiver.maxPayload)) { + receiver.currentMessage.push(buffer); + } else { + receiver.currentMessage = []; + receiver.currentMessageLength = 0; + receiver.error(new Error(`payload cannot exceed ${receiver.maxPayload} bytes`), 1009); return; } - this.currentMessageLength += buffer.length; + receiver.currentMessageLength += buffer.length; } if (state.lastFragment) { - var messageBuffer = this.currentMessage.length === 1 ? - this.currentMessage[0] : - Buffer.concat(this.currentMessage, this.currentMessageLength); - this.currentMessage = []; - this.currentMessageLength = 0; + const messageBuffer = receiver.currentMessage.length === 1 ? + receiver.currentMessage[0] : + Buffer.concat(receiver.currentMessage, receiver.currentMessageLength); + receiver.currentMessage = []; + receiver.currentMessageLength = 0; if (!Validation.isValidUTF8(messageBuffer)) { - this.error(new Error('invalid utf8 sequence'), 1007); + receiver.error(new Error('invalid utf8 sequence'), 1007); return; } - this.ontext(messageBuffer.toString('utf8'), {masked: state.masked, buffer: messageBuffer}); + receiver.ontext(messageBuffer.toString('utf8'), { + masked: state.masked, + buffer: messageBuffer + }); } callback(); }); }); - this.flush(); - this.endPacket(); + receiver.flush(); + receiver.endPacket(); } }, // binary '2': { - start: function(data) { + start: (receiver, data) => { // decode length - var firstLength = data[1] & 0x7f; + const firstLength = data[1] & 0x7f; if (firstLength < 126) { - if (this.maxPayloadExceeded(firstLength)) return; - opcodes['2'].getData.call(this, firstLength); - } - else if (firstLength == 126) { - this.expectHeader(2, (data) => { - var length = data.readUInt16BE(0, true); - if (this.maxPayloadExceeded(length)) return; - opcodes['2'].getData.call(this, length); + if (receiver.maxPayloadExceeded(firstLength)) return; + opcodes['2'].getData(receiver, firstLength); + } else if (firstLength == 126) { + receiver.expectHeader(2, (data) => { + const length = data.readUInt16BE(0, true); + if (receiver.maxPayloadExceeded(length)) return; + opcodes['2'].getData(receiver, length); }); - } - else if (firstLength == 127) { - this.expectHeader(8, (data) => { + } else if (firstLength == 127) { + receiver.expectHeader(8, (data) => { if (data.readUInt32BE(0, true) != 0) { - this.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); + receiver.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; } - var length = data.readUInt32BE(4, true); - if (this.maxPayloadExceeded(length)) return; - opcodes['2'].getData.call(this, length); + const length = data.readUInt32BE(4, true); + if (receiver.maxPayloadExceeded(length)) return; + opcodes['2'].getData(receiver, length); }); } }, - getData: function(length) { - if (this.state.masked) { - this.expectHeader(4, (data) => { - var mask = data; - this.expectData(length, (data) => { - opcodes['2'].finish.call(this, mask, data); - }); - }); - } - else { - this.expectData(length, (data) => { - opcodes['2'].finish.call(this, null, data); + getData: (receiver, length) => { + if (receiver.state.masked) { + receiver.expectHeader(4, (mask) => { + receiver.expectData(length, (data) => opcodes['2'].finish(receiver, mask, data)); }); + } else { + receiver.expectData(length, (data) => opcodes['2'].finish(receiver, null, data)); } }, - finish: function(mask, data) { - var packet = this.unmask(mask, data) || new Buffer(0); - var state = clone(this.state); - this.messageHandlers.push((callback) => { - this.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { + finish: (receiver, mask, data) => { + const packet = receiver.unmask(mask, data) || new Buffer(0); + const state = clone(receiver.state); + receiver.messageHandlers.push((callback) => { + receiver.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { if (err) { - this.error(err, err.closeCode === 1009 ? 1009 : 1007); + receiver.error(err, err.closeCode === 1009 ? 1009 : 1007); return; } if (buffer != null) { - if (this.maxPayload == 0 || (this.maxPayload > 0 && - (this.currentMessageLength + buffer.length) < this.maxPayload)) { - this.currentMessage.push(buffer); - } - else { - this.currentMessage = []; - this.currentMessageLength = 0; - this.error(new Error(`payload cannot exceed ${this.maxPayload} bytes`), 1009); + if (receiver.maxPayload == 0 || (receiver.maxPayload > 0 && + (receiver.currentMessageLength + buffer.length) < receiver.maxPayload)) { + receiver.currentMessage.push(buffer); + } else { + receiver.currentMessage = []; + receiver.currentMessageLength = 0; + receiver.error(new Error(`payload cannot exceed ${receiver.maxPayload} bytes`), 1009); return; } - this.currentMessageLength += buffer.length; + receiver.currentMessageLength += buffer.length; } if (state.lastFragment) { - var messageBuffer = this.currentMessage.length === 1 ? - this.currentMessage[0] : - Buffer.concat(this.currentMessage, this.currentMessageLength); - this.currentMessage = []; - this.currentMessageLength = 0; - this.onbinary(messageBuffer, {masked: state.masked, buffer: messageBuffer}); + const messageBuffer = receiver.currentMessage.length === 1 ? + receiver.currentMessage[0] : + Buffer.concat(receiver.currentMessage, receiver.currentMessageLength); + receiver.currentMessage = []; + receiver.currentMessageLength = 0; + receiver.onbinary(messageBuffer, { + masked: state.masked, + buffer: messageBuffer + }); } callback(); }); }); - this.flush(); - this.endPacket(); + receiver.flush(); + receiver.endPacket(); } }, // close '8': { - start: function(data) { - var self = this; - if (self.state.lastFragment == false) { - self.error('fragmented close is not supported', 1002); + start: (receiver, data) => { + if (receiver.state.lastFragment == false) { + receiver.error('fragmented close is not supported', 1002); return; } // decode length - var firstLength = data[1] & 0x7f; + const firstLength = data[1] & 0x7f; if (firstLength < 126) { - opcodes['8'].getData.call(self, firstLength); - } - else { - self.error('control frames cannot have more than 125 bytes of data', 1002); + opcodes['8'].getData(receiver, firstLength); + } else { + receiver.error('control frames cannot have more than 125 bytes of data', 1002); } }, - getData: function(length) { - var self = this; - if (self.state.masked) { - self.expectHeader(4, function(data) { - var mask = data; - self.expectData(length, function(data) { - opcodes['8'].finish.call(self, mask, data); - }); - }); - } - else { - self.expectData(length, function(data) { - opcodes['8'].finish.call(self, null, data); + getData: (receiver, length) => { + if (receiver.state.masked) { + receiver.expectHeader(4, (mask) => { + receiver.expectData(length, (data) => opcodes['8'].finish(receiver, mask, data)); }); + } else { + receiver.expectData(length, (data) => opcodes['8'].finish(receiver, null, data)); } }, - finish: function(mask, data) { - var self = this; - data = self.unmask(mask, data); - - var state = clone(this.state); - this.messageHandlers.push(function() { - if (data && data.length == 1) { - self.error('close packets with data must be at least two bytes long', 1002); + finish: (receiver, mask, data) => { + const packet = receiver.unmask(mask, data); + const state = clone(receiver.state); + receiver.messageHandlers.push(() => { + if (packet && packet.length == 1) { + receiver.error('close packets with data must be at least two bytes long', 1002); return; } - var code = data && data.length > 1 ? data.readUInt16BE(0, true) : 1000; + const code = packet && packet.length > 1 ? packet.readUInt16BE(0, true) : 1000; if (!ErrorCodes.isValidErrorCode(code)) { - self.error('invalid error code', 1002); + receiver.error('invalid error code', 1002); return; } var message = ''; - if (data && data.length > 2) { - var messageBuffer = data.slice(2); + if (packet && packet.length > 2) { + const messageBuffer = packet.slice(2); if (!Validation.isValidUTF8(messageBuffer)) { - self.error('invalid utf8 sequence', 1007); + receiver.error('invalid utf8 sequence', 1007); return; } message = messageBuffer.toString('utf8'); } - self.onclose(code, message, {masked: state.masked}); - self.reset(); + receiver.onclose(code, message, { masked: state.masked }); + receiver.reset(); }); - this.flush(); + receiver.flush(); } }, // ping '9': { - start: function(data) { - var self = this; - if (self.state.lastFragment == false) { - self.error('fragmented ping is not supported', 1002); + start: (receiver, data) => { + if (receiver.state.lastFragment == false) { + receiver.error('fragmented ping is not supported', 1002); return; } // decode length - var firstLength = data[1] & 0x7f; + const firstLength = data[1] & 0x7f; if (firstLength < 126) { - opcodes['9'].getData.call(self, firstLength); - } - else { - self.error('control frames cannot have more than 125 bytes of data', 1002); + opcodes['9'].getData(receiver, firstLength); + } else { + receiver.error('control frames cannot have more than 125 bytes of data', 1002); } }, - getData: function(length) { - var self = this; - if (self.state.masked) { - self.expectHeader(4, function(data) { - var mask = data; - self.expectData(length, function(data) { - opcodes['9'].finish.call(self, mask, data); - }); - }); - } - else { - self.expectData(length, function(data) { - opcodes['9'].finish.call(self, null, data); + getData: (receiver, length) => { + if (receiver.state.masked) { + receiver.expectHeader(4, (mask) => { + receiver.expectData(length, (data) => opcodes['9'].finish(receiver, mask, data)); }); + } else { + receiver.expectData(length, (data) => opcodes['9'].finish(receiver, null, data)); } }, - finish: function(mask, data) { - var self = this; - data = this.unmask(mask, data); - var state = clone(this.state); - this.messageHandlers.push(function(callback) { - self.onping(data, {masked: state.masked, binary: true}); + finish: (receiver, mask, data) => { + const packet = receiver.unmask(mask, data); + const state = clone(receiver.state); + receiver.messageHandlers.push((callback) => { + receiver.onping(packet, { masked: state.masked, binary: true }); callback(); }); - this.flush(); - this.endPacket(); + receiver.flush(); + receiver.endPacket(); } }, // pong '10': { - start: function(data) { - var self = this; - if (self.state.lastFragment == false) { - self.error('fragmented pong is not supported', 1002); + start: (receiver, data) => { + if (receiver.state.lastFragment == false) { + receiver.error('fragmented pong is not supported', 1002); return; } // decode length - var firstLength = data[1] & 0x7f; + const firstLength = data[1] & 0x7f; if (firstLength < 126) { - opcodes['10'].getData.call(self, firstLength); - } - else { - self.error('control frames cannot have more than 125 bytes of data', 1002); + opcodes['10'].getData(receiver, firstLength); + } else { + receiver.error('control frames cannot have more than 125 bytes of data', 1002); } }, - getData: function(length) { - if (this.state.masked) { - this.expectHeader(4, (data) => { - var mask = data; - this.expectData(length, (data) => { - opcodes['10'].finish.call(this, mask, data); - }); - }); - } - else { - this.expectData(length, (data) => { - opcodes['10'].finish.call(this, null, data); + getData: (receiver, length) => { + if (receiver.state.masked) { + receiver.expectHeader(4, (mask) => { + receiver.expectData(length, (data) => opcodes['10'].finish(receiver, mask, data)); }); + } else { + receiver.expectData(length, (data) => opcodes['10'].finish(receiver, null, data)); } }, - finish: function(mask, data) { - data = this.unmask(mask, data); - var state = clone(this.state); - this.messageHandlers.push((callback) => { - this.onpong(data, {masked: state.masked, binary: true}); + finish: (receiver, mask, data) => { + const packet = receiver.unmask(mask, data); + const state = clone(receiver.state); + receiver.messageHandlers.push((callback) => { + receiver.onpong(packet, { masked: state.masked, binary: true }); callback(); }); - this.flush(); - this.endPacket(); + receiver.flush(); + receiver.endPacket(); } } } From ded20731dd1c9e5df5a31787a66c561cbf6c0b9f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 27 Sep 2016 17:11:10 +0200 Subject: [PATCH 086/669] [benchmark] Clean up speed benchmark --- bench/speed.js | 146 ++++++++++++++++++++++--------------------------- package.json | 2 - 2 files changed, 66 insertions(+), 82 deletions(-) diff --git a/bench/speed.js b/bench/speed.js index 2a50ec79e..8f23e36b5 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -1,112 +1,98 @@ 'use strict'; -var cluster = require('cluster') - , WebSocket = require('../') - , WebSocketServer = WebSocket.Server - , crypto = require('crypto') - , util = require('util') - , ansi = require('ansi'); -require('tinycolor'); +const cluster = require('cluster'); + +const ws = require('../'); + +const port = 8181; function roundPrec(num, prec) { - var mul = Math.pow(10, prec); + const mul = Math.pow(10, prec); return Math.round(num * mul) / mul; } function humanSize(bytes) { - if (bytes >= 1048576) return roundPrec(bytes / 1048576, 2) + ' MB'; - if (bytes >= 1024) return roundPrec(bytes / 1024, 2) + ' kB'; + if (bytes >= 1048576) return roundPrec(bytes / 1048576, 2) + ' MiB'; + if (bytes >= 1024) return roundPrec(bytes / 1024, 2) + ' KiB'; return roundPrec(bytes, 2) + ' B'; } function generateRandomData(size) { - var buffer = new Buffer(size); + const buffer = Buffer.alloc(size); for (var i = 0; i < size; ++i) { buffer[i] = ~~(Math.random() * 127); } return buffer; } -if (cluster.isMaster) { - var wss = new WebSocketServer({ - perMessageDeflate: false, - clientTracking: false, - maxPayload: Infinity, - port: 8181 - }, function() { - cluster.fork(); +function runConfig(useBinary, roundtrips, size, randomBytes, cb) { + const data = randomBytes.slice(0, size); + const client = new ws(`ws://localhost:${port}`); + var roundtrip = 0; + var time; + + client.on('error', (err) => { + console.error(err.stack); + cluster.worker.kill(); }); - wss.on('connection', function(ws) { - ws.on('message', function(data, flags) { - ws.send(data, {binary: flags&&flags.binary}); - }); - ws.on('close', function() {}); + client.on('open', () => { + time = process.hrtime(); + client.send(data, { binary: useBinary }); }); - cluster.on('exit', function(worker) { - wss.close(); + client.on('message', () => { + if (++roundtrip !== roundtrips) return client.send(data, { binary: useBinary }); + + var elapsed = process.hrtime(time); + elapsed = elapsed[0] * 1e9 + elapsed[1]; + + console.log( + '%d roundtrips of %s %s data:\t%ss\t%s', + roundtrips, + humanSize(size), + useBinary ? 'binary' : 'text', + roundPrec(elapsed / 1e9, 1), + humanSize(size * roundtrips / elapsed * 1e9) + '/s' + ); + + client.close(); + cb(); }); } -else { - var cursor = ansi(process.stdout); - var configs = [ +if (cluster.isMaster) { + const wss = new ws.Server({ + maxPayload: 600 * 1024 * 1024, + perMessageDeflate: false, + clientTracking: false, + port + }, () => cluster.fork()); + + wss.on('connection', (ws) => { + ws.on('message', (data, flags) => ws.send(data, { binary: flags.binary || false })); + }); + + cluster.on('exit', () => wss.close()); +} else { + const configs = [ [true, 10000, 64], - [true, 5000, 16*1024], - [true, 1000, 128*1024], - [true, 100, 1024*1024], - [true, 1, 500*1024*1024], + [true, 5000, 16 * 1024], + [true, 1000, 128 * 1024], + [true, 100, 1024 * 1024], + [true, 1, 500 * 1024 * 1024], [false, 10000, 64], - [false, 5000, 16*1024], - [false, 1000, 128*1024], - [false, 100, 1024*1024], + [false, 5000, 16 * 1024], + [false, 1000, 128 * 1024], + [false, 100, 1024 * 1024] ]; - var largest = configs[0][1]; - for (var i = 0, l = configs.length; i < l; ++i) { - if (configs[i][2] > largest) largest = configs[i][2]; - } - - console.log('Generating %s of test data ...', humanSize(largest)); - var randomBytes = generateRandomData(largest); - - function roundtrip(useBinary, roundtrips, size, cb) { - var data = randomBytes.slice(0, size); - var prefix = util.format('Running %d roundtrips of %s %s data', roundtrips, humanSize(size), useBinary ? 'binary' : 'text'); - console.log(prefix); - var client = new WebSocket('ws://localhost:' + '8181'); - var dt; - var roundtrip = 0; - function send() { - client.send(data, {binary: useBinary}); - } - client.on('error', function(e) { - console.error(e); - process.exit(); - }); - client.on('open', function() { - dt = Date.now(); - send(); - }); - client.on('message', function(data, flags) { - if (++roundtrip == roundtrips) { - var elapsed = Date.now() - dt; - cursor.up(); - console.log('%s:\t%ss\t%s' - , useBinary ? prefix.green : prefix.cyan - , roundPrec(elapsed / 1000, 1).toString().green.bold - , (humanSize((size * roundtrips) / elapsed * 1000) + '/s').blue.bold); - client.close(); - cb(); - return; - } - process.nextTick(send); - }); - } + const largest = configs.reduce((prev, curr) => curr[2] > prev ? curr[2] : prev, 0); + console.log('Generating %s of test data...', humanSize(largest)); + const randomBytes = generateRandomData(largest); (function run() { - if (configs.length == 0) process.exit(); + if (configs.length === 0) return cluster.worker.kill(); var config = configs.shift(); - config.push(run); - roundtrip.apply(null, config); + config.push(randomBytes, run); + runConfig.apply(null, config); })(); } diff --git a/package.json b/package.json index 80c85195b..734f9e350 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "ultron": "1.0.x" }, "devDependencies": { - "ansi": "0.3.x", "benchmark": "2.1.x", "bufferutil": "1.2.x", "eslint": "^2.12.0", @@ -33,7 +32,6 @@ "istanbul": "^0.4.1", "mocha": "2.3.x", "should": "8.0.x", - "tinycolor": "0.0.x", "utf-8-validate": "1.2.x" }, "gypfile": true From 58c7722fab635c1253d1793632b177e23938fd31 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 27 Sep 2016 19:35:00 +0200 Subject: [PATCH 087/669] [benchmark] Use a sane value for the `maxPayload` argument --- bench/parser.benchmark.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index a8d245833..4a73ec25d 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -30,7 +30,7 @@ const binaryDataPacket = createBinaryPacket(125); const binaryDataPacket2 = createBinaryPacket(65535); const binaryDataPacket3 = createBinaryPacket(200 * 1024) -var receiver = new Receiver({}, Infinity); +var receiver = new Receiver({}, 1024 * 1024); const suite = new benchmark.Suite(); suite.add('ping message', () => receiver.add(pingPacket1)); From 87e36b63fa9c491b7b158743721187e293947e57 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 2 Oct 2016 16:19:54 +0200 Subject: [PATCH 088/669] [codestyle] Add mocha env to eslint config --- .eslintrc | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.eslintrc b/.eslintrc index e50702170..fa633f7b4 100644 --- a/.eslintrc +++ b/.eslintrc @@ -18,13 +18,8 @@ }, "env": { "es6": true, - "node": true - }, - "globals": { - "describe": true, - "it": true, - "before": true, - "after": true + "node": true, + "mocha": true }, "extends": "eslint:recommended" } From 335f1ad578414e7bf84e0bf3707032b8029c701d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 2 Oct 2016 16:33:52 +0200 Subject: [PATCH 089/669] [deps] Bump eslint to version 3.7.x --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 80c85195b..f63bedfec 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "ansi": "0.3.x", "benchmark": "2.1.x", "bufferutil": "1.2.x", - "eslint": "^2.12.0", + "eslint": "3.7.x", "expect.js": "0.3.x", "istanbul": "^0.4.1", "mocha": "2.3.x", From 88ec9c941c61d6f2d937de30a4d87b3f8ef4094f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 10 Oct 2016 15:13:55 +0200 Subject: [PATCH 090/669] [minor] Remove redundant check --- lib/Sender.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Sender.js b/lib/Sender.js index d048b4fd0..9ddf9a7e2 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -134,7 +134,7 @@ class Sender extends EventEmitter { } if (!Buffer.isBuffer(data)) { - if (data && (data.buffer || data) instanceof ArrayBuffer) { + if ((data.buffer || data) instanceof ArrayBuffer) { data = getBufferFromNative(data); } else { canModifyData = true; From 149ad9cd004dc692e95f77807deed516b45dac6a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 12 Oct 2016 10:05:31 +0200 Subject: [PATCH 091/669] [deps] Bump mocha to version 3.1.x --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f63bedfec..6d938b029 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,8 @@ "bufferutil": "1.2.x", "eslint": "3.7.x", "expect.js": "0.3.x", - "istanbul": "^0.4.1", - "mocha": "2.3.x", + "istanbul": "0.4.x", + "mocha": "3.1.x", "should": "8.0.x", "tinycolor": "0.0.x", "utf-8-validate": "1.2.x" From e721cd5354e9b4da5ca6031a549241b20b3a57f5 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 12 Oct 2016 10:43:27 +0200 Subject: [PATCH 092/669] [lint] Use semistandard instead of a custom set of rules --- .eslintignore | 3 ++- .eslintrc | 25 ------------------------- .eslintrc.yaml | 3 +++ package.json | 4 ++++ 4 files changed, 9 insertions(+), 26 deletions(-) delete mode 100644 .eslintrc create mode 100644 .eslintrc.yaml diff --git a/.eslintignore b/.eslintignore index 8d87b1d26..25fbf5a1c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ -node_modules/* +node_modules/ +coverage/ diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index fa633f7b4..000000000 --- a/.eslintrc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "rules": { - "no-unused-vars": ["error", { "args": "none" }], - "no-console": 0, - "no-constant-condition": ["error", { "checkLoops": false }], - "no-empty": ["error", { "allowEmptyCatch": true }], - "indent": ["error", 2], - "space-infix-ops": 2, - "strict": 2, - "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}], - "keyword-spacing": 2, - "linebreak-style": [2, "unix"], - "comma-dangle": ["error", "never"], - "no-unreachable": [2], - "comma-spacing": 2, - "comma-style": ["error", "last"], - "max-len": ["error", 120] - }, - "env": { - "es6": true, - "node": true, - "mocha": true - }, - "extends": "eslint:recommended" -} diff --git a/.eslintrc.yaml b/.eslintrc.yaml new file mode 100644 index 000000000..5b53411e0 --- /dev/null +++ b/.eslintrc.yaml @@ -0,0 +1,3 @@ +extends: semistandard +env: + mocha: true diff --git a/package.json b/package.json index 6d938b029..7790e2cdc 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,10 @@ "benchmark": "2.1.x", "bufferutil": "1.2.x", "eslint": "3.7.x", + "eslint-config-semistandard": "7.0.x", + "eslint-config-standard": "6.2.x", + "eslint-plugin-promise": "3.0.x", + "eslint-plugin-standard": "2.0.x", "expect.js": "0.3.x", "istanbul": "0.4.x", "mocha": "3.1.x", From 9fe7d78f897ea0f3d5c43e25b63e693cb2f6e53a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 12 Oct 2016 10:50:21 +0200 Subject: [PATCH 093/669] [lint] Run eslint --fix on index.js and lib/* --- index.js | 4 +- lib/BufferPool.js | 14 ++--- lib/BufferUtil.fallback.js | 20 +++---- lib/ErrorCodes.js | 2 +- lib/Extensions.js | 16 +++--- lib/PerMessageDeflate.js | 84 ++++++++++++++-------------- lib/Receiver.hixie.js | 30 +++++----- lib/Receiver.js | 90 +++++++++++++++--------------- lib/Sender.hixie.js | 12 ++-- lib/Sender.js | 52 +++++++++--------- lib/Validation.fallback.js | 2 +- lib/WebSocket.js | 109 ++++++++++++++++++------------------- lib/WebSocketServer.js | 86 ++++++++++++++--------------- 13 files changed, 258 insertions(+), 263 deletions(-) diff --git a/index.js b/index.js index a7e8644b9..3cfe33714 100644 --- a/index.js +++ b/index.js @@ -20,7 +20,7 @@ WS.Receiver = require('./lib/Receiver'); * @returns {WS.Server} * @api public */ -WS.createServer = function createServer(options, fn) { +WS.createServer = function createServer (options, fn) { var server = new WS.Server(options); if (typeof fn === 'function') { @@ -38,7 +38,7 @@ WS.createServer = function createServer(options, fn) { * @returns {WS} * @api public */ -WS.connect = WS.createConnection = function connect(address, fn) { +WS.connect = WS.createConnection = function connect (address, fn) { var client = new WS(address); if (typeof fn === 'function') { diff --git a/lib/BufferPool.js b/lib/BufferPool.js index 57517774e..c006512d1 100644 --- a/lib/BufferPool.js +++ b/lib/BufferPool.js @@ -7,12 +7,12 @@ 'use strict'; class BufferPool { - constructor(initialSize, growStrategy, shrinkStrategy) { - this._growStrategy = (growStrategy || function(db, size) { + constructor (initialSize, growStrategy, shrinkStrategy) { + this._growStrategy = (growStrategy || function (db, size) { return db.used + size; }).bind(null, this); - this._shrinkStrategy = (shrinkStrategy || function(db) { + this._shrinkStrategy = (shrinkStrategy || function (db) { return initialSize; }).bind(null, this); @@ -22,15 +22,15 @@ class BufferPool { this._changeFactor = 0; } - get size() { + get size () { return this._buffer.length; } - get used() { + get used () { return this._used; } - get(length) { + get (length) { if (this._buffer == null || this._offset + length > this._buffer.length) { var newBuf = new Buffer(this._growStrategy(length)); this._buffer = newBuf; @@ -42,7 +42,7 @@ class BufferPool { return buf; } - reset(forceNewBuffer) { + reset (forceNewBuffer) { var len = this._shrinkStrategy(); if (len < this.size) this._changeFactor -= 1; if (forceNewBuffer || this._changeFactor < -2) { diff --git a/lib/BufferUtil.fallback.js b/lib/BufferUtil.fallback.js index d81b5f7ab..a1f9ed41b 100644 --- a/lib/BufferUtil.fallback.js +++ b/lib/BufferUtil.fallback.js @@ -7,7 +7,7 @@ 'use strict'; exports.BufferUtil = { - merge: function(mergedBuffer, buffers) { + merge: function (mergedBuffer, buffers) { var offset = 0; for (var i = 0, l = buffers.length; i < l; ++i) { var buf = buffers[i]; @@ -15,7 +15,7 @@ exports.BufferUtil = { offset += buf.length; } }, - mask: function(source, mask, output, offset, length) { + mask: function (source, mask, output, offset, length) { var maskNum = mask.readUInt32LE(0, true); var i = 0; for (; i < length - 3; i += 4) { @@ -25,13 +25,13 @@ exports.BufferUtil = { } /* eslint-disable no-fallthrough */ switch (length % 4) { - case 3: output[offset + i + 2] = source[i + 2] ^ mask[2]; - case 2: output[offset + i + 1] = source[i + 1] ^ mask[1]; - case 1: output[offset + i] = source[i] ^ mask[0]; + case 3: output[offset + i + 2] = source[i + 2] ^ mask[2]; + case 2: output[offset + i + 1] = source[i + 1] ^ mask[1]; + case 1: output[offset + i] = source[i] ^ mask[0]; } /* eslint-enable no-fallthrough */ }, - unmask: function(data, mask) { + unmask: function (data, mask) { var maskNum = mask.readUInt32LE(0, true); var length = data.length; var i = 0; @@ -42,10 +42,10 @@ exports.BufferUtil = { } /* eslint-disable no-fallthrough */ switch (length % 4) { - case 3: data[i + 2] = data[i + 2] ^ mask[2]; - case 2: data[i + 1] = data[i + 1] ^ mask[1]; - case 1: data[i] = data[i] ^ mask[0]; + case 3: data[i + 2] = data[i + 2] ^ mask[2]; + case 2: data[i + 1] = data[i + 1] ^ mask[1]; + case 1: data[i] = data[i] ^ mask[0]; } /* eslint-enable no-fallthrough */ } -} +}; diff --git a/lib/ErrorCodes.js b/lib/ErrorCodes.js index 335efb4e8..3e587a755 100644 --- a/lib/ErrorCodes.js +++ b/lib/ErrorCodes.js @@ -7,7 +7,7 @@ 'use strict'; module.exports = { - isValidErrorCode: function(code) { + isValidErrorCode: function (code) { return (code >= 1000 && code <= 1011 && code != 1004 && code != 1005 && code != 1006) || (code >= 3000 && code <= 4999); }, diff --git a/lib/Extensions.js b/lib/Extensions.js index 2b7f41593..574677129 100644 --- a/lib/Extensions.js +++ b/lib/Extensions.js @@ -11,18 +11,18 @@ exports.format = format; * Parse extensions header value */ -function parse(value) { +function parse (value) { value = value || ''; var extensions = {}; - value.split(',').forEach(function(v) { + value.split(',').forEach(function (v) { var params = v.split(';'); var token = params.shift().trim(); var paramsList = extensions[token] = extensions[token] || []; var parsedParams = {}; - params.forEach(function(param) { + params.forEach(function (param) { var parts = param.trim().split('='); var key = parts[0]; var value = parts[1]; @@ -50,17 +50,17 @@ function parse(value) { * Format extensions header value */ -function format(value) { - return Object.keys(value).map(function(token) { +function format (value) { + return Object.keys(value).map(function (token) { var paramsList = value[token]; if (!Array.isArray(paramsList)) { paramsList = [paramsList]; } - return paramsList.map(function(params) { - return [token].concat(Object.keys(params).map(function(k) { + return paramsList.map(function (params) { + return [token].concat(Object.keys(params).map(function (k) { var p = params[k]; if (!Array.isArray(p)) p = [p]; - return p.map(function(v) { + return p.map(function (v) { return v === true ? k : k + '=' + v; }).join('; '); })).join('; '); diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 515e30e9e..4efae7971 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -10,7 +10,7 @@ const DEFAULT_MEM_LEVEL = 8; * Per-message Compression Extensions implementation */ class PerMessageDeflate { - constructor(options, isServer, maxPayload) { + constructor (options, isServer, maxPayload) { this._options = options || {}; this._isServer = !!isServer; this._inflate = null; @@ -25,7 +25,7 @@ class PerMessageDeflate { * @api public */ - offer() { + offer () { var params = {}; if (this._options.serverNoContextTakeover) { params.server_no_context_takeover = true; @@ -49,7 +49,7 @@ class PerMessageDeflate { * * @api public */ - accept(paramsList) { + accept (paramsList) { paramsList = this.normalizeParams(paramsList); var params; @@ -68,7 +68,7 @@ class PerMessageDeflate { * * @api public */ - cleanup() { + cleanup () { if (this._inflate) { if (this._inflate.writeInProgress) { this._inflate.pendingClose = true; @@ -93,9 +93,9 @@ class PerMessageDeflate { * @api private */ - acceptAsServer(paramsList) { + acceptAsServer (paramsList) { var accepted = {}; - var result = paramsList.some(function(params) { + var result = paramsList.some(function (params) { accepted = {}; if (this._options.serverNoContextTakeover === false && params.server_no_context_takeover) { return; @@ -147,7 +147,7 @@ class PerMessageDeflate { * @api privaye */ - acceptAsClient(paramsList) { + acceptAsClient (paramsList) { var params = paramsList[0]; if (this._options.clientNoContextTakeover != null) { if (this._options.clientNoContextTakeover === false && params.client_no_context_takeover) { @@ -172,9 +172,9 @@ class PerMessageDeflate { * @api private */ - normalizeParams(paramsList) { - return paramsList.map(function(params) { - Object.keys(params).forEach(function(key) { + normalizeParams (paramsList) { + return paramsList.map(function (params) { + Object.keys(params).forEach(function (key) { var value = params[key]; if (value.length > 1) { throw new Error('Multiple extension parameters for ' + key); @@ -183,28 +183,28 @@ class PerMessageDeflate { value = value[0]; switch (key) { - case 'server_no_context_takeover': - case 'client_no_context_takeover': - if (value !== true) { - throw new Error(`invalid extension parameter value for ${key} (${value})`); - } - params[key] = true; - break; - case 'server_max_window_bits': - case 'client_max_window_bits': - if (typeof value === 'string') { - value = parseInt(value, 10); - if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) { + case 'server_no_context_takeover': + case 'client_no_context_takeover': + if (value !== true) { throw new Error(`invalid extension parameter value for ${key} (${value})`); } - } - if (!this._isServer && value === true) { - throw new Error(`Missing extension parameter value for ${key}`); - } - params[key] = value; - break; - default: - throw new Error(`Not defined extension parameter (${key})`); + params[key] = true; + break; + case 'server_max_window_bits': + case 'client_max_window_bits': + if (typeof value === 'string') { + value = parseInt(value, 10); + if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) { + throw new Error(`invalid extension parameter value for ${key} (${value})`); + } + } + if (!this._isServer && value === true) { + throw new Error(`Missing extension parameter value for ${key}`); + } + params[key] = value; + break; + default: + throw new Error(`Not defined extension parameter (${key})`); } }, this); return params; @@ -216,13 +216,13 @@ class PerMessageDeflate { * * @api public */ - decompress(data, fin, callback) { + decompress (data, fin, callback) { var endpoint = this._isServer ? 'client' : 'server'; if (!this._inflate) { var maxWindowBits = this.params[endpoint + '_max_window_bits']; this._inflate = zlib.createInflateRaw({ - windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS + windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS }); } this._inflate.writeInProgress = true; @@ -236,17 +236,17 @@ class PerMessageDeflate { if (fin) { this._inflate.write(new Buffer([0x00, 0x00, 0xff, 0xff])); } - this._inflate.flush(function() { + this._inflate.flush(function () { cleanup(); callback(null, Buffer.concat(buffers)); }); - function onError(err) { + function onError (err) { cleanup(); callback(err); } - function onData(data) { + function onData (data) { if (self._maxPayload > 0) { cumulativeBufferLength += data.length; if (cumulativeBufferLength > self._maxPayload) { @@ -261,7 +261,7 @@ class PerMessageDeflate { buffers.push(data); } - function cleanup() { + function cleanup () { if (!self._inflate) return; self._inflate.removeListener('error', onError); self._inflate.removeListener('data', onData); @@ -279,14 +279,14 @@ class PerMessageDeflate { * @api public */ - compress(data, fin, callback) { + compress (data, fin, callback) { var endpoint = this._isServer ? 'server' : 'client'; if (!this._deflate) { var maxWindowBits = this.params[endpoint + '_max_window_bits']; this._deflate = zlib.createDeflateRaw({ flush: zlib.Z_SYNC_FLUSH, - windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS, + windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS, memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL }); } @@ -297,7 +297,7 @@ class PerMessageDeflate { this._deflate.on('error', onError).on('data', onData); this._deflate.write(data); - this._deflate.flush(function() { + this._deflate.flush(function () { cleanup(); var data = Buffer.concat(buffers); if (fin) { @@ -306,16 +306,16 @@ class PerMessageDeflate { callback(null, data); }); - function onError(err) { + function onError (err) { cleanup(); callback(err); } - function onData(data) { + function onData (data) { buffers.push(data); } - function cleanup() { + function cleanup () { if (!self._deflate) return; self._deflate.removeListener('error', onError); self._deflate.removeListener('data', onData); diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index 8123c6e96..d64ea746b 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -18,19 +18,19 @@ const BINARYLENGTH = 2, BINARYBODY = 3; */ class Receiver { - constructor() { + constructor () { this.state = EMPTY; this.buffers = []; this.messageEnd = -1; this.spanLength = 0; this.dead = false; - this.onerror = function() {}; - this.ontext = function() {}; - this.onbinary = function() {}; - this.onclose = function() {}; - this.onping = function() {}; - this.onpong = function() {}; + this.onerror = function () {}; + this.ontext = function () {}; + this.onbinary = function () {}; + this.onclose = function () {}; + this.onping = function () {}; + this.onpong = function () {}; } /** @@ -39,9 +39,9 @@ class Receiver { * @api public */ - add(data) { + add (data) { var self = this; - function doAdd() { + function doAdd () { if (self.state === EMPTY) { if (data.length == 2 && data[0] == 0xFF && data[1] == 0x00) { self.reset(); @@ -53,14 +53,12 @@ class Receiver { self.state = BINARYLENGTH; data = data.slice(1); } else { - if (data[0] !== 0x00) { self.error(new Error('payload must start with 0x00 byte'), true); return; } data = data.slice(1); self.state = BODY; - } } if (self.state === BINARYLENGTH) { @@ -107,7 +105,7 @@ class Receiver { * @api public */ - cleanup() { + cleanup () { this.dead = true; this.state = EMPTY; this.buffers = []; @@ -119,7 +117,7 @@ class Receiver { * @api public */ - parse() { + parse () { var output = new Buffer(this.spanLength); var outputIndex = 0; for (var bi = 0, bl = this.buffers.length; bi < bl - 1; ++bi) { @@ -145,9 +143,9 @@ class Receiver { * @api private */ - error(err, terminate) { + error (err, terminate) { this.reset(); - this.onerror(err, terminate) + this.onerror(err, terminate); return this; } @@ -156,7 +154,7 @@ class Receiver { * * @api private */ - reset(reason) { + reset (reason) { if (this.dead) return; this.state = EMPTY; this.buffers = []; diff --git a/lib/Receiver.js b/lib/Receiver.js index 518f0cfe3..d49378a0c 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -17,17 +17,17 @@ const PerMessageDeflate = require('./PerMessageDeflate'); */ class Receiver { - constructor(extensions, maxPayload) { - if (typeof extensions === 'number'){ + constructor (extensions, maxPayload) { + if (typeof extensions === 'number') { maxPayload = extensions; extensions = {}; } // memory pool for fragmented messages var fragmentedPoolPrevUsed = -1; - this.fragmentedBufferPool = new BufferPool(1024, function(db, length) { + this.fragmentedBufferPool = new BufferPool(1024, function (db, length) { return db.used + length; - }, function(db) { + }, function (db) { return fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 ? Math.ceil((fragmentedPoolPrevUsed + db.used) / 2) : db.used; @@ -35,9 +35,9 @@ class Receiver { // memory pool for unfragmented messages var unfragmentedPoolPrevUsed = -1; - this.unfragmentedBufferPool = new BufferPool(1024, function(db, length) { + this.unfragmentedBufferPool = new BufferPool(1024, function (db, length) { return db.used + length; - }, function(db) { + }, function (db) { return unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 ? Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) : db.used; @@ -64,12 +64,12 @@ class Receiver { this.dead = false; this.processing = false; - this.onerror = function() {}; - this.ontext = function() {}; - this.onbinary = function() {}; - this.onclose = function() {}; - this.onping = function() {}; - this.onpong = function() {}; + this.onerror = function () {}; + this.ontext = function () {}; + this.onbinary = function () {}; + this.onclose = function () {}; + this.onping = function () {}; + this.onpong = function () {}; } /** @@ -78,7 +78,7 @@ class Receiver { * @api public */ - add(data) { + add (data) { if (this.dead) return; const dataLength = data.length; if (dataLength == 0) return; @@ -106,7 +106,7 @@ class Receiver { * @api public */ - cleanup() { + cleanup () { this.dead = true; this.overflow = null; this.headerBuffer = null; @@ -130,7 +130,7 @@ class Receiver { * @api private */ - expectHeader(length, handler) { + expectHeader (length, handler) { if (length == 0) { handler(null); return; @@ -154,7 +154,7 @@ class Receiver { * @api private */ - expectData(length, handler) { + expectData (length, handler) { if (length == 0) { handler(null); return; @@ -178,7 +178,7 @@ class Receiver { * @api private */ - allocateFromPool(length, isFragmented) { + allocateFromPool (length, isFragmented) { return (isFragmented ? this.fragmentedBufferPool : this.unfragmentedBufferPool).get(length); } @@ -188,7 +188,7 @@ class Receiver { * @api private */ - processPacket(data) { + processPacket (data) { if (this.extensions[PerMessageDeflate.extensionName]) { if ((data[0] & 0x30) != 0) { this.error(new Error('reserved fields (2, 3) must be empty'), 1002); @@ -248,7 +248,7 @@ class Receiver { * @api private */ - endPacket() { + endPacket () { if (this.dead) return; if (!this.state.fragmentedOperation) this.unfragmentedBufferPool.reset(true); else if (this.state.lastFragment) this.fragmentedBufferPool.reset(true); @@ -272,7 +272,7 @@ class Receiver { * @api private */ - reset() { + reset () { if (this.dead) return; this.state = { activeFragmentedOperation: null, @@ -299,7 +299,7 @@ class Receiver { * @api private */ - unmask(mask, buf) { + unmask (mask, buf) { if (mask != null && buf != null) bufferUtil.unmask(buf, mask); return buf; } @@ -310,7 +310,7 @@ class Receiver { * @api private */ - error(err, protocolErrorCode) { + error (err, protocolErrorCode) { this.reset(); this.onerror(err, protocolErrorCode); return this; @@ -322,7 +322,7 @@ class Receiver { * @api private */ - flush() { + flush () { if (this.processing || this.dead) return; var handler = this.messageHandlers.shift(); @@ -342,7 +342,7 @@ class Receiver { * @api private */ - applyExtensions(messageBuffer, fin, compressed, callback) { + applyExtensions (messageBuffer, fin, compressed, callback) { if (compressed) { const extension = this.extensions[PerMessageDeflate.extensionName]; extension.decompress(messageBuffer, fin, (err, buffer) => { @@ -364,7 +364,7 @@ class Receiver { * @api private */ - maxPayloadExceeded(length) { + maxPayloadExceeded (length) { if (this.maxPayload === undefined || this.maxPayload === null || this.maxPayload < 1) { return false; } @@ -386,31 +386,31 @@ module.exports = Receiver; * Buffer utilities */ -function fastCopy(length, srcBuffer, dstBuffer, dstOffset) { +function fastCopy (length, srcBuffer, dstBuffer, dstOffset) { /* eslint-disable no-fallthrough */ switch (length) { - default: srcBuffer.copy(dstBuffer, dstOffset, 0, length); break; - case 16: dstBuffer[dstOffset + 15] = srcBuffer[15]; - case 15: dstBuffer[dstOffset + 14] = srcBuffer[14]; - case 14: dstBuffer[dstOffset + 13] = srcBuffer[13]; - case 13: dstBuffer[dstOffset + 12] = srcBuffer[12]; - case 12: dstBuffer[dstOffset + 11] = srcBuffer[11]; - case 11: dstBuffer[dstOffset + 10] = srcBuffer[10]; - case 10: dstBuffer[dstOffset + 9] = srcBuffer[9]; - case 9: dstBuffer[dstOffset + 8] = srcBuffer[8]; - case 8: dstBuffer[dstOffset + 7] = srcBuffer[7]; - case 7: dstBuffer[dstOffset + 6] = srcBuffer[6]; - case 6: dstBuffer[dstOffset + 5] = srcBuffer[5]; - case 5: dstBuffer[dstOffset + 4] = srcBuffer[4]; - case 4: dstBuffer[dstOffset + 3] = srcBuffer[3]; - case 3: dstBuffer[dstOffset + 2] = srcBuffer[2]; - case 2: dstBuffer[dstOffset + 1] = srcBuffer[1]; - case 1: dstBuffer[dstOffset] = srcBuffer[0]; + default: srcBuffer.copy(dstBuffer, dstOffset, 0, length); break; + case 16: dstBuffer[dstOffset + 15] = srcBuffer[15]; + case 15: dstBuffer[dstOffset + 14] = srcBuffer[14]; + case 14: dstBuffer[dstOffset + 13] = srcBuffer[13]; + case 13: dstBuffer[dstOffset + 12] = srcBuffer[12]; + case 12: dstBuffer[dstOffset + 11] = srcBuffer[11]; + case 11: dstBuffer[dstOffset + 10] = srcBuffer[10]; + case 10: dstBuffer[dstOffset + 9] = srcBuffer[9]; + case 9: dstBuffer[dstOffset + 8] = srcBuffer[8]; + case 8: dstBuffer[dstOffset + 7] = srcBuffer[7]; + case 7: dstBuffer[dstOffset + 6] = srcBuffer[6]; + case 6: dstBuffer[dstOffset + 5] = srcBuffer[5]; + case 5: dstBuffer[dstOffset + 4] = srcBuffer[4]; + case 4: dstBuffer[dstOffset + 3] = srcBuffer[3]; + case 3: dstBuffer[dstOffset + 2] = srcBuffer[2]; + case 2: dstBuffer[dstOffset + 1] = srcBuffer[1]; + case 1: dstBuffer[dstOffset] = srcBuffer[0]; } /* eslint-enable no-fallthrough */ } -function clone(obj) { +function clone (obj) { return Object.assign({}, obj); } @@ -698,4 +698,4 @@ const opcodes = { receiver.endPacket(); } } -} +}; diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index 18235d3a6..e7ad36d7b 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -13,7 +13,7 @@ const EventEmitter = require('events'); */ class Sender extends EventEmitter { - constructor(socket) { + constructor (socket) { super(); this.socket = socket; @@ -26,7 +26,7 @@ class Sender extends EventEmitter { * * @api public */ - send(data, options, cb) { + send (data, options, cb) { if (this.isClosed) return; var isString = typeof data == 'string'; @@ -68,7 +68,7 @@ class Sender extends EventEmitter { try { this.socket.write(buffer, 'binary', cb); } catch (e) { - this.emit('error', e) + this.emit('error', e); } } @@ -78,7 +78,7 @@ class Sender extends EventEmitter { * @api public */ - close(code, data, mask, cb) { + close (code, data, mask, cb) { if (this.isClosed) return; this.isClosed = true; try { @@ -95,14 +95,14 @@ class Sender extends EventEmitter { * @api public */ - ping(data, options) {} + ping (data, options) {} /** * Sends a pong message to the remote party. Not available for hixie. * * @api public */ - pong(data, options) {} + pong (data, options) {} } module.exports = Sender; diff --git a/lib/Sender.js b/lib/Sender.js index d048b4fd0..d29f31bc5 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -15,7 +15,7 @@ const PerMessageDeflate = require('./PerMessageDeflate'); * HyBi Sender implementation, Inherits from EventEmitter. */ class Sender extends EventEmitter { - constructor(socket, extensions) { + constructor (socket, extensions) { super(); this._socket = socket; @@ -31,7 +31,7 @@ class Sender extends EventEmitter { * * @api public */ - close(code, data, mask, cb) { + close (code, data, mask, cb) { if (typeof code !== 'undefined') { if (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code)) throw new Error('first argument must be a valid error code number'); @@ -42,7 +42,7 @@ class Sender extends EventEmitter { if (dataBuffer.length > 2) dataBuffer.write(data, 2); var self = this; - this.messageHandlers.push(function(callback) { + this.messageHandlers.push(function (callback) { self.frameAndSend(0x8, dataBuffer, true, mask); callback(); if (typeof cb == 'function') cb(); @@ -55,10 +55,10 @@ class Sender extends EventEmitter { * * @api public */ - ping(data, options) { + ping (data, options) { var mask = options && options.mask; var self = this; - this.messageHandlers.push(function(callback) { + this.messageHandlers.push(function (callback) { self.frameAndSend(0x9, data || '', true, mask); callback(); }); @@ -70,10 +70,10 @@ class Sender extends EventEmitter { * * @api public */ - pong(data, options) { + pong (data, options) { var mask = options && options.mask; var self = this; - this.messageHandlers.push(function(callback) { + this.messageHandlers.push(function (callback) { self.frameAndSend(0xa, data || '', true, mask); callback(); }); @@ -87,7 +87,7 @@ class Sender extends EventEmitter { * @api public */ - send(data, options, cb) { + send (data, options, cb) { var finalFragment = options && options.fin === false ? false : true; var mask = options && options.mask; var compress = options && options.compress; @@ -99,13 +99,13 @@ class Sender extends EventEmitter { this.firstFragment = false; this.compress = compress; } - if (finalFragment) this.firstFragment = true + if (finalFragment) this.firstFragment = true; var compressFragment = this.compress; var self = this; - this.messageHandlers.push(function(callback) { - self.applyExtensions(data, finalFragment, compressFragment, function(err, data) { + this.messageHandlers.push(function (callback) { + self.applyExtensions(data, finalFragment, compressFragment, function (err, data) { if (err) { if (typeof cb == 'function') cb(err); else self.emit('error', err); @@ -123,7 +123,7 @@ class Sender extends EventEmitter { * * @api private */ - frameAndSend(opcode, data, finalFragment, maskData, compressed, cb) { + frameAndSend (opcode, data, finalFragment, maskData, compressed, cb) { var canModifyData = false; if (!data) { @@ -170,12 +170,12 @@ class Sender extends EventEmitter { if (compressed) outputBuffer[0] |= 0x40; switch (secondByte) { - case 126: - outputBuffer.writeUInt16BE(dataLength, 2); - break; - case 127: - outputBuffer.writeUInt32BE(0, 2); - outputBuffer.writeUInt32BE(dataLength, 6); + case 126: + outputBuffer.writeUInt16BE(dataLength, 2); + break; + case 127: + outputBuffer.writeUInt32BE(0, 2); + outputBuffer.writeUInt32BE(dataLength, 6); } if (maskData) { @@ -205,7 +205,7 @@ class Sender extends EventEmitter { * * @api private */ - flush() { + flush () { if (this.processing) return; var handler = this.messageHandlers.shift(); @@ -215,9 +215,9 @@ class Sender extends EventEmitter { var self = this; - handler(function() { + handler(function () { self.processing = false; - process.nextTick(function() { + process.nextTick(function () { self.flush(); }); }); @@ -228,7 +228,7 @@ class Sender extends EventEmitter { * * @api private */ - applyExtensions(data, fin, compress, callback) { + applyExtensions (data, fin, compress, callback) { if (compress && data) { if ((data.buffer || data) instanceof ArrayBuffer) { data = getBufferFromNative(data); @@ -242,14 +242,14 @@ class Sender extends EventEmitter { module.exports = Sender; -function getBufferFromNative(data) { +function getBufferFromNative (data) { // data is either an ArrayBuffer or ArrayBufferView. return !data.buffer ? new Buffer(data) - : new Buffer(data.buffer).slice(data.byteOffset, data.byteOffset + data.byteLength) + : new Buffer(data.buffer).slice(data.byteOffset, data.byteOffset + data.byteLength); } -function getRandomMask() { +function getRandomMask () { return new Buffer([ ~~(Math.random() * 255), ~~(Math.random() * 255), @@ -258,7 +258,7 @@ function getRandomMask() { ]); } -function sendFramedData(outputBuffer, data, cb) { +function sendFramedData (outputBuffer, data, cb) { try { if (data) { this._socket.write(outputBuffer, 'binary'); diff --git a/lib/Validation.fallback.js b/lib/Validation.fallback.js index b160d07a3..1b1432463 100644 --- a/lib/Validation.fallback.js +++ b/lib/Validation.fallback.js @@ -7,7 +7,7 @@ 'use strict'; exports.Validation = { - isValidUTF8: function(buffer) { + isValidUTF8: function (buffer) { return true; } }; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index a4a2e4040..a6ab62173 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -46,20 +46,20 @@ var closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cl * @param {Object} options Additional connection options. * @api public */ -function WebSocket(address, protocols, options) { +function WebSocket (address, protocols, options) { if (this instanceof WebSocket === false) { return new WebSocket(address, protocols, options); } EventEmitter.call(this); - if (protocols && !Array.isArray(protocols) && 'object' === typeof protocols) { + if (protocols && !Array.isArray(protocols) && typeof protocols === 'object') { // accept the "options" Object as the 2nd argument options = protocols; protocols = null; } - if ('string' === typeof protocols) { + if (typeof protocols === 'string') { protocols = [ protocols ]; } @@ -91,7 +91,7 @@ util.inherits(WebSocket, EventEmitter); /** * Ready States */ -['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'].forEach(function each(state, index) { +['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'].forEach(function each (state, index) { WebSocket.prototype[state] = WebSocket[state] = index; }); @@ -101,7 +101,7 @@ util.inherits(WebSocket, EventEmitter); * @param {Object} data to be sent to the server * @api public */ -WebSocket.prototype.close = function close(code, data) { +WebSocket.prototype.close = function close (code, data) { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { @@ -122,7 +122,7 @@ WebSocket.prototype.close = function close(code, data) { this._closeCode = code; this._closeMessage = data; var mask = !this._isServer; - this._sender.close(code, data, mask, function(err) { + this._sender.close(code, data, mask, function (err) { if (err) self.emit('error', err); if (self._closeReceived && self._isServer) { @@ -143,7 +143,7 @@ WebSocket.prototype.close = function close(code, data) { * * @api public */ -WebSocket.prototype.pause = function pauser() { +WebSocket.prototype.pause = function pauser () { if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); return this._socket.pause(); @@ -157,7 +157,7 @@ WebSocket.prototype.pause = function pauser() { * @param {boolean} dontFailWhenClosed indicates whether or not to throw if the connection isnt open * @api public */ -WebSocket.prototype.ping = function ping(data, options, dontFailWhenClosed) { +WebSocket.prototype.ping = function ping (data, options, dontFailWhenClosed) { if (this.readyState !== WebSocket.OPEN) { if (dontFailWhenClosed === true) return; throw new Error('not opened'); @@ -178,7 +178,7 @@ WebSocket.prototype.ping = function ping(data, options, dontFailWhenClosed) { * @param {boolean} dontFailWhenClosed indicates whether or not to throw if the connection isnt open * @api public */ -WebSocket.prototype.pong = function(data, options, dontFailWhenClosed) { +WebSocket.prototype.pong = function (data, options, dontFailWhenClosed) { if (this.readyState !== WebSocket.OPEN) { if (dontFailWhenClosed === true) return; throw new Error('not opened'); @@ -196,7 +196,7 @@ WebSocket.prototype.pong = function(data, options, dontFailWhenClosed) { * * @api public */ -WebSocket.prototype.resume = function resume() { +WebSocket.prototype.resume = function resume () { if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); return this._socket.resume(); @@ -211,7 +211,7 @@ WebSocket.prototype.resume = function resume() { * @api public */ -WebSocket.prototype.send = function send(data, options, cb) { +WebSocket.prototype.send = function send (data, options, cb) { if (typeof options === 'function') { cb = options; options = {}; @@ -228,7 +228,7 @@ WebSocket.prototype.send = function send(data, options, cb) { var self = this; if (this._queue) { - this._queue.push(function() { self.send(data, options, cb); }); + this._queue.push(function () { self.send(data, options, cb); }); return; } @@ -253,8 +253,8 @@ WebSocket.prototype.send = function send(data, options, cb) { if (data instanceof readable) { startQueue(this); - sendStream(this, data, options, function send(error) { - process.nextTick(function tock() { + sendStream(this, data, options, function send (error) { + process.nextTick(function tock () { executeQueueSends(self); }); @@ -273,7 +273,7 @@ WebSocket.prototype.send = function send(data, options, cb) { * ticks of which send is 'function (data, final)'. * @api public */ -WebSocket.prototype.stream = function stream(options, cb) { +WebSocket.prototype.stream = function stream (options, cb) { if (typeof options === 'function') { cb = options; options = {}; @@ -304,7 +304,7 @@ WebSocket.prototype.stream = function stream(options, cb) { startQueue(this); - function send(data, final) { + function send (data, final) { try { if (self.readyState !== WebSocket.OPEN) throw new Error('not opened'); options.fin = final === true; @@ -328,7 +328,7 @@ WebSocket.prototype.stream = function stream(options, cb) { * * @api public */ -WebSocket.prototype.terminate = function terminate() { +WebSocket.prototype.terminate = function terminate () { if (this.readyState === WebSocket.CLOSED) return; if (this._socket) { @@ -361,7 +361,7 @@ WebSocket.prototype.terminate = function terminate() { * @api public */ Object.defineProperty(WebSocket.prototype, 'bufferedAmount', { - get: function get() { + get: function get () { var amount = 0; if (this._socket) { amount = this._socket.bufferSize || 0; @@ -380,10 +380,10 @@ Object.defineProperty(WebSocket.prototype, 'bufferedAmount', { * @api public */ Object.defineProperty(WebSocket.prototype, 'binaryType', { - get: function get() { + get: function get () { return this._binaryType; }, - set: function set(type) { + set: function set (type) { if (type === 'arraybuffer' || type === 'nodebuffer') this._binaryType = type; else @@ -397,7 +397,7 @@ Object.defineProperty(WebSocket.prototype, 'binaryType', { * @see http://dev.w3.org/html5/websockets/#the-websocket-interface * @api public */ -['open', 'error', 'close', 'message'].forEach(function(method) { +['open', 'error', 'close', 'message'].forEach(function (method) { Object.defineProperty(WebSocket.prototype, 'on' + method, { /** * Returns the current listener @@ -405,7 +405,7 @@ Object.defineProperty(WebSocket.prototype, 'binaryType', { * @returns {Mixed} the set function or undefined * @api public */ - get: function get() { + get: function get () { var listener = this.listeners(method)[0]; return listener ? (listener._listener ? listener._listener : listener) : undefined; }, @@ -417,7 +417,7 @@ Object.defineProperty(WebSocket.prototype, 'binaryType', { * @returns {Mixed} the set function or undefined * @api public */ - set: function set(listener) { + set: function set (listener) { this.removeAllListeners(method); this.addEventListener(method, listener); } @@ -431,7 +431,7 @@ Object.defineProperty(WebSocket.prototype, 'binaryType', { * @see http://dev.w3.org/html5/websockets/#the-websocket-interface * @api public */ -WebSocket.prototype.addEventListener = function(method, listener) { +WebSocket.prototype.addEventListener = function (method, listener) { var target = this; function onMessage (data, flags) { @@ -482,7 +482,7 @@ WebSocket.prototype.addEventListener = function(method, listener) { }; module.exports = WebSocket; -module.exports.buildHostHeader = buildHostHeader +module.exports.buildHostHeader = buildHostHeader; /** * W3C MessageEvent @@ -491,7 +491,7 @@ module.exports.buildHostHeader = buildHostHeader * @constructor * @api private */ -function MessageEvent(dataArg, isBinary, target) { +function MessageEvent (dataArg, isBinary, target) { this.type = 'message'; this.data = dataArg; this.target = target; @@ -505,7 +505,7 @@ function MessageEvent(dataArg, isBinary, target) { * @constructor * @api private */ -function CloseEvent(code, reason, target) { +function CloseEvent (code, reason, target) { this.type = 'close'; this.wasClean = (typeof code === 'undefined' || code === 1000); this.code = code; @@ -520,17 +520,17 @@ function CloseEvent(code, reason, target) { * @constructor * @api private */ -function OpenEvent(target) { +function OpenEvent (target) { this.type = 'open'; this.target = target; } // Append port number to Host header, only if specified in the url // and non-default -function buildHostHeader(isSecure, hostname, port) { +function buildHostHeader (isSecure, hostname, port) { var headerHost = hostname; if (hostname) { - if ((isSecure && (port != 443)) || (!isSecure && (port != 80))){ + if ((isSecure && (port != 443)) || (!isSecure && (port != 80))) { headerHost = headerHost + ':' + port; } } @@ -541,7 +541,7 @@ function buildHostHeader(isSecure, hostname, port) { * Entirely private apis, * which may or may not be bound to a sepcific WebSocket instance. */ -function initAsServerClient(req, socket, upgradeHead, options) { +function initAsServerClient (req, socket, upgradeHead, options) { options = Object.assign({ protocolVersion: protocolVersion, protocol: null, @@ -566,7 +566,7 @@ function initAsServerClient(req, socket, upgradeHead, options) { } } -function initAsClient(address, protocols, options) { +function initAsClient (address, protocols, options) { options = Object.assign({ origin: null, protocolVersion: protocolVersion, @@ -624,7 +624,7 @@ function initAsClient(address, protocols, options) { var agent = options.agent; - var headerHost = buildHostHeader(isSecure, serverUrl.hostname, port) + var headerHost = buildHostHeader(isSecure, serverUrl.hostname, port); var requestOptions = { port: port, @@ -671,7 +671,6 @@ function initAsClient(address, protocols, options) { || isDefinedAndNonNull(options, 'ciphers') || isDefinedAndNonNull(options, 'rejectUnauthorized') || isDefinedAndNonNull(options, 'checkServerIdentity')) { - if (isDefinedAndNonNull(options, 'pfx')) requestOptions.pfx = options.pfx; if (isDefinedAndNonNull(options, 'key')) requestOptions.key = options.key; if (isDefinedAndNonNull(options, 'passphrase')) requestOptions.passphrase = options.passphrase; @@ -711,12 +710,12 @@ function initAsClient(address, protocols, options) { var self = this; var req = httpObj.request(requestOptions); - req.on('error', function onerror(error) { + req.on('error', function onerror (error) { self.emit('error', error); cleanupWebsocketResources.call(self, error); }); - req.once('response', function response(res) { + req.once('response', function response (res) { var error; if (!self.emit('unexpected-response', req, res)) { @@ -728,7 +727,7 @@ function initAsClient(address, protocols, options) { cleanupWebsocketResources.call(self, error); }); - req.once('upgrade', function upgrade(res, socket, upgradeHead) { + req.once('upgrade', function upgrade (res, socket, upgradeHead) { if (self.readyState === WebSocket.CLOSED) { // client closed before server accepted connection self.emit('close'); @@ -791,7 +790,7 @@ function initAsClient(address, protocols, options) { this.readyState = WebSocket.CONNECTING; } -function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { +function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { var ultron = this._ultron = new Ultron(socket), called = false, self = this; @@ -808,7 +807,7 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { ultron.on('error', cleanupWebsocketResources.bind(this)); // ensure that the upgradeHead is added to the receiver - function firstHandler(data) { + function firstHandler (data) { if (called || self.readyState === WebSocket.CLOSED) return; called = true; @@ -824,7 +823,7 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { } // subsequent packets are pushed straight to the receiver - function realHandler(data) { + function realHandler (data) { self.bytesReceived += data.length; self._receiver.add(data); } @@ -839,20 +838,20 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { process.nextTick(firstHandler); // receiver event handlers - self._receiver.ontext = function ontext(data, flags) { + self._receiver.ontext = function ontext (data, flags) { flags = flags || {}; self.emit('message', data, flags); }; - self._receiver.onbinary = function onbinary(data, flags) { + self._receiver.onbinary = function onbinary (data, flags) { flags = flags || {}; flags.binary = true; self.emit('message', data, flags); }; - self._receiver.onping = function onping(data, flags) { + self._receiver.onping = function onping (data, flags) { flags = flags || {}; self.pong(data, { @@ -863,18 +862,18 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { self.emit('ping', data, flags); }; - self._receiver.onpong = function onpong(data, flags) { + self._receiver.onpong = function onpong (data, flags) { self.emit('pong', data, flags || {}); }; - self._receiver.onclose = function onclose(code, data, flags) { + self._receiver.onclose = function onclose (code, data, flags) { flags = flags || {}; self._closeReceived = true; self.close(code, data); }; - self._receiver.onerror = function onerror(error, errorCode) { + self._receiver.onerror = function onerror (error, errorCode) { // close the connection when the receiver reports a HyBi error code self.close(errorCode, ''); self.emit('error', error); @@ -882,7 +881,7 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { // finalize the client this._sender = new SenderClass(socket, this.extensions); - this._sender.on('error', function onerror(error) { + this._sender.on('error', function onerror (error) { self.close(1002, ''); self.emit('error', error); }); @@ -891,11 +890,11 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { this.emit('open'); } -function startQueue(instance) { +function startQueue (instance) { instance._queue = instance._queue || []; } -function executeQueueSends(instance) { +function executeQueueSends (instance) { var queue = instance._queue; if (typeof queue === 'undefined') return; @@ -905,8 +904,8 @@ function executeQueueSends(instance) { } } -function sendStream(instance, stream, options, cb) { - stream.on('data', function incoming(data) { +function sendStream (instance, stream, options, cb) { + stream.on('data', function incoming (data) { if (instance.readyState !== WebSocket.OPEN) { if (typeof cb === 'function') cb(new Error('not opened')); else { @@ -920,7 +919,7 @@ function sendStream(instance, stream, options, cb) { instance._sender.send(data, options); }); - stream.on('end', function end() { + stream.on('end', function end () { if (instance.readyState !== WebSocket.OPEN) { if (typeof cb === 'function') cb(new Error('not opened')); else { @@ -937,7 +936,7 @@ function sendStream(instance, stream, options, cb) { }); } -function cleanupWebsocketResources(error) { +function cleanupWebsocketResources (error) { if (this.readyState === WebSocket.CLOSED) return; this.readyState = WebSocket.CLOSED; @@ -955,7 +954,7 @@ function cleanupWebsocketResources(error) { if (this._socket) { if (this._ultron) this._ultron.destroy(); - this._socket.on('error', function onerror() { + this._socket.on('error', function onerror () { try { this.destroy(); } catch (e) {} }); @@ -986,6 +985,6 @@ function cleanupWebsocketResources(error) { this.extensions = null; this.removeAllListeners(); - this.on('error', function onerror() {}); // catch all errors after this + this.on('error', function onerror () {}); // catch all errors after this delete this._queue; } diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 4d23bcecc..0c0f98c2a 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -23,7 +23,7 @@ var isDefinedAndNonNull = function (options, key) { * WebSocket Server implementation */ -function WebSocketServer(options, callback) { +function WebSocketServer (options, callback) { if (this instanceof WebSocketServer === false) { return new WebSocketServer(options, callback); } @@ -67,7 +67,7 @@ function WebSocketServer(options, callback) { } else { this._server.listen(options.port, options.host, callback); } - this._closeServer = function() { + this._closeServer = function () { if (self._server) self._server.close(); }; @@ -87,19 +87,19 @@ function WebSocketServer(options, callback) { } } if (this._server) { - this._onceServerListening = function() { self.emit('listening'); }; + this._onceServerListening = function () { self.emit('listening'); }; this._server.once('listening', this._onceServerListening); } if (typeof this._server != 'undefined') { - this._onServerError = function(error) { self.emit('error', error) }; + this._onServerError = function (error) { self.emit('error', error); }; this._server.on('error', this._onServerError); - this._onServerUpgrade = function(req, socket, upgradeHead) { - //copy upgradeHead to avoid retention of large slab buffers used in node core + this._onServerUpgrade = function (req, socket, upgradeHead) { + // copy upgradeHead to avoid retention of large slab buffers used in node core var head = new Buffer(upgradeHead.length); upgradeHead.copy(head); - self.handleUpgrade(req, socket, head, function(client) { + self.handleUpgrade(req, socket, head, function (client) { self.emit('connection' + req.url, client); self.emit('connection', client); }); @@ -124,7 +124,7 @@ util.inherits(WebSocketServer, EventEmitter); * @api public */ -WebSocketServer.prototype.close = function(callback) { +WebSocketServer.prototype.close = function (callback) { // terminate all associated clients var error = null; try { @@ -162,7 +162,7 @@ WebSocketServer.prototype.close = function(callback) { callback(error); else if (error) throw error; -} +}; /** * Handle a HTTP Upgrade request. @@ -170,7 +170,7 @@ WebSocketServer.prototype.close = function(callback) { * @api public */ -WebSocketServer.prototype.handleUpgrade = function(req, socket, upgradeHead, cb) { +WebSocketServer.prototype.handleUpgrade = function (req, socket, upgradeHead, cb) { // check for wrong path if (this.options.path) { var u = url.parse(req.url); @@ -184,7 +184,7 @@ WebSocketServer.prototype.handleUpgrade = function(req, socket, upgradeHead, cb) if (req.headers['sec-websocket-key1']) handleHixieUpgrade.apply(this, arguments); else handleHybiUpgrade.apply(this, arguments); -} +}; module.exports = WebSocketServer; @@ -193,11 +193,11 @@ module.exports = WebSocketServer; * which may or may not be bound to a specific WebSocket instance. */ -function handleHybiUpgrade(req, socket, upgradeHead, cb) { +function handleHybiUpgrade (req, socket, upgradeHead, cb) { // handle premature socket errors - var errorHandler = function() { + var errorHandler = function () { try { socket.destroy(); } catch (e) {} - } + }; socket.on('error', errorHandler); // verify key presence @@ -226,8 +226,7 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { // handler to call when the connection sequence completes var self = this; - var completeHybiUpgrade2 = function(protocol) { - + var completeHybiUpgrade2 = function (protocol) { // calc key var key = req.headers['sec-websocket-key']; var shasum = crypto.createHash('sha1'); @@ -255,8 +254,8 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { if (Object.keys(extensions).length) { var serverExtensions = {}; - Object.keys(extensions).forEach(function(token) { - serverExtensions[token] = [extensions[token].params] + Object.keys(extensions).forEach(function (token) { + serverExtensions[token] = [extensions[token].params]; }); headers.push('Sec-WebSocket-Extensions: ' + Extensions.format(serverExtensions)); } @@ -284,7 +283,7 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { if (self.options.clientTracking) { self.clients.push(client); - client.on('close', function() { + client.on('close', function () { var index = self.clients.indexOf(client); if (index != -1) { self.clients.splice(index, 1); @@ -295,16 +294,16 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { // signal upgrade complete socket.removeListener('error', errorHandler); cb(client); - } + }; // optionally call external protocol selection handler before // calling completeHybiUpgrade2 - var completeHybiUpgrade1 = function() { + var completeHybiUpgrade1 = function () { // choose from the sub-protocols if (typeof self.options.handleProtocols == 'function') { var protList = (protocols || '').split(/, */); var callbackCalled = false; - self.options.handleProtocols(protList, function(result, protocol) { + self.options.handleProtocols(protList, function (result, protocol) { callbackCalled = true; if (!result) abortConnection(socket, 401, 'Unauthorized'); else completeHybiUpgrade2(protocol); @@ -322,7 +321,7 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { completeHybiUpgrade2(); } } - } + }; // optionally call external client verification handler if (typeof this.options.verifyClient == 'function') { @@ -332,7 +331,7 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { req: req }; if (this.options.verifyClient.length == 2) { - this.options.verifyClient(info, function(result, code, name) { + this.options.verifyClient(info, function (result, code, name) { if (typeof code === 'undefined') code = 401; if (typeof name === 'undefined') name = http.STATUS_CODES[code]; @@ -350,11 +349,11 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) { completeHybiUpgrade1(); } -function handleHixieUpgrade(req, socket, upgradeHead, cb) { +function handleHixieUpgrade (req, socket, upgradeHead, cb) { // handle premature socket errors - var errorHandler = function() { + var errorHandler = function () { try { socket.destroy(); } catch (e) {} - } + }; socket.on('error', errorHandler); // bail if options prevent hixie @@ -372,7 +371,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { var origin = req.headers['origin'], self = this; // setup handshake completion to run after client has been verified - var onClientVerified = function() { + var onClientVerified = function () { var wshost; if (!req.headers['x-forwarded-host']) wshost = req.headers.host; @@ -384,7 +383,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { protocol = req.headers['sec-websocket-protocol']; // build the response header and return a Buffer - var buildResponseHeader = function() { + var buildResponseHeader = function () { var headers = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: WebSocket', @@ -398,15 +397,14 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { }; // send handshake response before receiving the nonce - var handshakeResponse = function() { - + var handshakeResponse = function () { socket.setTimeout(0); socket.setNoDelay(true); var headerBuffer = buildResponseHeader(); try { - socket.write(headerBuffer, 'binary', function(err) { + socket.write(headerBuffer, 'binary', function (err) { // remove listener if there was an error if (err) socket.removeListener('data', handler); return; @@ -418,7 +416,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { }; // handshake completion code to run once nonce has been successfully retrieved - var completeHandshake = function(nonce, rest, headerBuffer) { + var completeHandshake = function (nonce, rest, headerBuffer) { // calculate key var k1 = req.headers['sec-websocket-key1'], k2 = req.headers['sec-websocket-key2'], @@ -427,7 +425,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { [k1, k2].forEach(function (k) { var n = parseInt(k.replace(/[^\d]/g, '')), spaces = k.replace(/[^ ]/g, '').length; - if (spaces === 0 || n % spaces !== 0){ + if (spaces === 0 || n % spaces !== 0) { abortConnection(socket, 400, 'Bad Request'); return; } @@ -435,8 +433,8 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { md5.update(String.fromCharCode( n >> 24 & 0xFF, n >> 16 & 0xFF, - n >> 8 & 0xFF, - n & 0xFF), 'binary'); + n >> 8 & 0xFF, + n & 0xFF), 'binary'); }); md5.update(nonce.toString('binary'), 'binary'); @@ -450,7 +448,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { hashBuffer.copy(handshakeBuffer, headerBuffer.length); // do a single write, which - upon success - causes a new client websocket to be setup - socket.write(handshakeBuffer, 'binary', function(err) { + socket.write(handshakeBuffer, 'binary', function (err) { if (err) return; // do not create client if an error happens var client = new WebSocket([req, socket, rest], { protocolVersion: 'hixie-76', @@ -458,7 +456,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { }); if (self.options.clientTracking) { self.clients.push(client); - client.on('close', function() { + client.on('close', function () { var index = self.clients.indexOf(client); if (index != -1) { self.clients.splice(index, 1); @@ -475,7 +473,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { try { socket.destroy(); } catch (e) {} return; } - } + }; // retrieve nonce var nonceLength = 8; @@ -503,7 +501,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { // complete the handshake but send empty buffer for headers since they have already been sent completeHandshake.call(self, nonce, rest, new Buffer(0)); } - } + }; // handle additional data as we receive it socket.on('data', handler); @@ -511,7 +509,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { // send header response before we have the nonce to fix haproxy buffering handshakeResponse(); } - } + }; // verify client if (typeof this.options.verifyClient == 'function') { @@ -521,7 +519,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { req: req }; if (this.options.verifyClient.length == 2) { - this.options.verifyClient(info, function(result, code, name) { + this.options.verifyClient(info, function (result, code, name) { if (typeof code === 'undefined') code = 401; if (typeof name === 'undefined') name = http.STATUS_CODES[code]; @@ -540,7 +538,7 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) { onClientVerified(); } -function acceptExtensions(offer) { +function acceptExtensions (offer) { var extensions = {}; var options = this.options.perMessageDeflate; var maxPayload = this.options.maxPayload; @@ -552,7 +550,7 @@ function acceptExtensions(offer) { return extensions; } -function abortConnection(socket, code, name) { +function abortConnection (socket, code, name) { try { var response = `HTTP/1.1 ${code} ${name}\r\n` + `Content-type: text/html\r\n` + From 9330da4465aa2ecea0017487b0b7c917861c16c5 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 12 Oct 2016 14:22:24 +0200 Subject: [PATCH 094/669] [lint] Fix lint issues not fixed by eslint --fix --- lib/ErrorCodes.js | 4 +- lib/Receiver.hixie.js | 16 ++++--- lib/Receiver.js | 72 ++++++++++++++++--------------- lib/Sender.hixie.js | 18 +++++--- lib/Sender.js | 24 +++++------ lib/WebSocket.js | 52 ++++++++++++---------- lib/WebSocketServer.js | 97 ++++++++++++++++++++---------------------- 7 files changed, 145 insertions(+), 138 deletions(-) diff --git a/lib/ErrorCodes.js b/lib/ErrorCodes.js index 3e587a755..dfd9d8644 100644 --- a/lib/ErrorCodes.js +++ b/lib/ErrorCodes.js @@ -8,8 +8,8 @@ module.exports = { isValidErrorCode: function (code) { - return (code >= 1000 && code <= 1011 && code != 1004 && code != 1005 && code != 1006) || - (code >= 3000 && code <= 4999); + return (code >= 1000 && code <= 1011 && code !== 1004 && code !== 1005 && code !== 1006) || + (code >= 3000 && code <= 4999); }, 1000: 'normal', 1001: 'going away', diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js index d64ea746b..cda7ecdb6 100644 --- a/lib/Receiver.hixie.js +++ b/lib/Receiver.hixie.js @@ -10,8 +10,10 @@ * State constants */ -const EMPTY = 0, BODY = 1; -const BINARYLENGTH = 2, BINARYBODY = 3; +const EMPTY = 0; +const BODY = 1; +const BINARYLENGTH = 2; +const BINARYBODY = 3; /** * Hixie Receiver implementation @@ -43,7 +45,7 @@ class Receiver { var self = this; function doAdd () { if (self.state === EMPTY) { - if (data.length == 2 && data[0] == 0xFF && data[1] == 0x00) { + if (data.length === 2 && data[0] === 0xFF && data[1] === 0x00) { self.reset(); self.onclose(); return; @@ -72,8 +74,9 @@ class Receiver { self.state = BINARYBODY; ++i; } - if (i > 0) + if (i > 0) { data = data.slice(i); + } } if (self.state === BINARYBODY) { var dataleft = self.messageEnd - self.spanLength; @@ -90,11 +93,12 @@ class Receiver { return; } self.buffers.push(data); - if ((self.messageEnd = data.indexOf(0xFF)) != -1) { + if ((self.messageEnd = data.indexOf(0xFF)) !== -1) { self.spanLength += self.messageEnd; return self.parse(); + } else { + self.spanLength += data.length; } - else self.spanLength += data.length; } while (data) data = doAdd(); } diff --git a/lib/Receiver.js b/lib/Receiver.js index d49378a0c..354565047 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -28,9 +28,10 @@ class Receiver { this.fragmentedBufferPool = new BufferPool(1024, function (db, length) { return db.used + length; }, function (db) { - return fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 ? - Math.ceil((fragmentedPoolPrevUsed + db.used) / 2) : - db.used; + fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 + ? Math.ceil((fragmentedPoolPrevUsed + db.used) / 2) + : db.used; + return fragmentedPoolPrevUsed; }); // memory pool for unfragmented messages @@ -38,9 +39,10 @@ class Receiver { this.unfragmentedBufferPool = new BufferPool(1024, function (db, length) { return db.used + length; }, function (db) { - return unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 ? - Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) : - db.used; + unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 + ? Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) + : db.used; + return unfragmentedPoolPrevUsed; }); this.extensions = extensions || {}; this.maxPayload = maxPayload || 0; @@ -81,7 +83,7 @@ class Receiver { add (data) { if (this.dead) return; const dataLength = data.length; - if (dataLength == 0) return; + if (dataLength === 0) return; if (this.expectBuffer == null) { this.overflow.push(data); return; @@ -92,7 +94,7 @@ class Receiver { if (toRead < dataLength) { this.overflow.push(data.slice(toRead)); } - while (this.expectBuffer && this.expectOffset == this.expectBuffer.length) { + while (this.expectBuffer && this.expectOffset === this.expectBuffer.length) { const bufferForHandler = this.expectBuffer; this.expectBuffer = null; this.expectOffset = 0; @@ -131,7 +133,7 @@ class Receiver { */ expectHeader (length, handler) { - if (length == 0) { + if (length === 0) { handler(null); return; } @@ -155,7 +157,7 @@ class Receiver { */ expectData (length, handler) { - if (length == 0) { + if (length === 0) { handler(null); return; } @@ -190,19 +192,19 @@ class Receiver { processPacket (data) { if (this.extensions[PerMessageDeflate.extensionName]) { - if ((data[0] & 0x30) != 0) { + if ((data[0] & 0x30) !== 0) { this.error(new Error('reserved fields (2, 3) must be empty'), 1002); return; } } else { - if ((data[0] & 0x70) != 0) { + if ((data[0] & 0x70) !== 0) { this.error(new Error('reserved fields must be empty'), 1002); return; } } - this.state.lastFragment = (data[0] & 0x80) == 0x80; - this.state.masked = (data[1] & 0x80) == 0x80; - const compressed = (data[0] & 0x40) == 0x40; + this.state.lastFragment = (data[0] & 0x80) === 0x80; + this.state.masked = (data[1] & 0x80) === 0x80; + const compressed = (data[0] & 0x40) === 0x40; const opcode = data[0] & 0xf; if (opcode === 0) { if (compressed) { @@ -212,7 +214,7 @@ class Receiver { // continuation frame this.state.fragmentedOperation = true; this.state.opcode = this.state.activeFragmentedOperation; - if (!(this.state.opcode == 1 || this.state.opcode == 2)) { + if (!(this.state.opcode === 1 || this.state.opcode === 2)) { this.error(new Error('continuation frame cannot follow current opcode'), 1002); return; } @@ -235,7 +237,7 @@ class Receiver { } } const handler = opcodes[this.state.opcode]; - if (typeof handler == 'undefined') { + if (typeof handler === 'undefined') { this.error(new Error(`no handler for opcode ${this.state.opcode}`), 1002); } else { handler.start(this, data); @@ -427,15 +429,15 @@ const opcodes = { if (firstLength < 126) { if (receiver.maxPayloadExceeded(firstLength)) return; opcodes['1'].getData(receiver, firstLength); - } else if (firstLength == 126) { + } else if (firstLength === 126) { receiver.expectHeader(2, (data) => { const length = data.readUInt16BE(0, true); if (receiver.maxPayloadExceeded(length)) return; opcodes['1'].getData(receiver, length); }); - } else if (firstLength == 127) { + } else if (firstLength === 127) { receiver.expectHeader(8, (data) => { - if (data.readUInt32BE(0, true) != 0) { + if (data.readUInt32BE(0, true) !== 0) { receiver.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; } @@ -465,7 +467,7 @@ const opcodes = { } if (buffer != null) { - if (receiver.maxPayload == 0 || (receiver.maxPayload > 0 && + if (receiver.maxPayload === 0 || (receiver.maxPayload > 0 && (receiver.currentMessageLength + buffer.length) < receiver.maxPayload)) { receiver.currentMessage.push(buffer); } else { @@ -477,9 +479,9 @@ const opcodes = { receiver.currentMessageLength += buffer.length; } if (state.lastFragment) { - const messageBuffer = receiver.currentMessage.length === 1 ? - receiver.currentMessage[0] : - Buffer.concat(receiver.currentMessage, receiver.currentMessageLength); + const messageBuffer = receiver.currentMessage.length === 1 + ? receiver.currentMessage[0] + : Buffer.concat(receiver.currentMessage, receiver.currentMessageLength); receiver.currentMessage = []; receiver.currentMessageLength = 0; if (!Validation.isValidUTF8(messageBuffer)) { @@ -506,15 +508,15 @@ const opcodes = { if (firstLength < 126) { if (receiver.maxPayloadExceeded(firstLength)) return; opcodes['2'].getData(receiver, firstLength); - } else if (firstLength == 126) { + } else if (firstLength === 126) { receiver.expectHeader(2, (data) => { const length = data.readUInt16BE(0, true); if (receiver.maxPayloadExceeded(length)) return; opcodes['2'].getData(receiver, length); }); - } else if (firstLength == 127) { + } else if (firstLength === 127) { receiver.expectHeader(8, (data) => { - if (data.readUInt32BE(0, true) != 0) { + if (data.readUInt32BE(0, true) !== 0) { receiver.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; } @@ -544,7 +546,7 @@ const opcodes = { } if (buffer != null) { - if (receiver.maxPayload == 0 || (receiver.maxPayload > 0 && + if (receiver.maxPayload === 0 || (receiver.maxPayload > 0 && (receiver.currentMessageLength + buffer.length) < receiver.maxPayload)) { receiver.currentMessage.push(buffer); } else { @@ -556,9 +558,9 @@ const opcodes = { receiver.currentMessageLength += buffer.length; } if (state.lastFragment) { - const messageBuffer = receiver.currentMessage.length === 1 ? - receiver.currentMessage[0] : - Buffer.concat(receiver.currentMessage, receiver.currentMessageLength); + const messageBuffer = receiver.currentMessage.length === 1 + ? receiver.currentMessage[0] + : Buffer.concat(receiver.currentMessage, receiver.currentMessageLength); receiver.currentMessage = []; receiver.currentMessageLength = 0; receiver.onbinary(messageBuffer, { @@ -576,7 +578,7 @@ const opcodes = { // close '8': { start: (receiver, data) => { - if (receiver.state.lastFragment == false) { + if (receiver.state.lastFragment === false) { receiver.error('fragmented close is not supported', 1002); return; } @@ -602,7 +604,7 @@ const opcodes = { const packet = receiver.unmask(mask, data); const state = clone(receiver.state); receiver.messageHandlers.push(() => { - if (packet && packet.length == 1) { + if (packet && packet.length === 1) { receiver.error('close packets with data must be at least two bytes long', 1002); return; } @@ -629,7 +631,7 @@ const opcodes = { // ping '9': { start: (receiver, data) => { - if (receiver.state.lastFragment == false) { + if (receiver.state.lastFragment === false) { receiver.error('fragmented ping is not supported', 1002); return; } @@ -665,7 +667,7 @@ const opcodes = { // pong '10': { start: (receiver, data) => { - if (receiver.state.lastFragment == false) { + if (receiver.state.lastFragment === false) { receiver.error('fragmented pong is not supported', 1002); return; } diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js index e7ad36d7b..439eb302f 100644 --- a/lib/Sender.hixie.js +++ b/lib/Sender.hixie.js @@ -29,11 +29,11 @@ class Sender extends EventEmitter { send (data, options, cb) { if (this.isClosed) return; - var isString = typeof data == 'string'; + var isString = typeof data === 'string'; var length = isString ? Buffer.byteLength(data) : data.length; var lengthbytes = (length > 127) ? 2 : 1; // assume less than 2**14 bytes - var writeStartMarker = this.continuationFrame == false; - var writeEndMarker = !options || !(typeof options.fin != 'undefined' && !options.fin); + var writeStartMarker = this.continuationFrame === false; + var writeEndMarker = !options || !(typeof options.fin !== 'undefined' && !options.fin); var bufferLength = writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0; bufferLength += length; @@ -46,11 +46,13 @@ class Sender extends EventEmitter { if (options && options.binary) { buffer.write('\x80', 'binary'); // assume length less than 2**14 bytes - if (lengthbytes > 1) + if (lengthbytes > 1) { buffer.write(String.fromCharCode(128 + length / 128), offset++, 'binary'); + } buffer.write(String.fromCharCode(length & 0x7f), offset++, 'binary'); - } else + } else { buffer.write('\x00', 'binary'); + } } if (isString) buffer.write(data, offset, 'utf8'); @@ -59,11 +61,13 @@ class Sender extends EventEmitter { if (writeEndMarker) { if (options && options.binary) { // sending binary, not writing end marker - } else + } else { buffer.write('\xff', offset + length, 'binary'); + } this.continuationFrame = false; + } else { + this.continuationFrame = true; } - else this.continuationFrame = true; try { this.socket.write(buffer, 'binary', cb); diff --git a/lib/Sender.js b/lib/Sender.js index d29f31bc5..a128feb9e 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -45,7 +45,7 @@ class Sender extends EventEmitter { this.messageHandlers.push(function (callback) { self.frameAndSend(0x8, dataBuffer, true, mask); callback(); - if (typeof cb == 'function') cb(); + if (typeof cb === 'function') cb(); }); this.flush(); } @@ -86,9 +86,8 @@ class Sender extends EventEmitter { * * @api public */ - send (data, options, cb) { - var finalFragment = options && options.fin === false ? false : true; + var finalFragment = !options || options.fin !== false; var mask = options && options.mask; var compress = options && options.compress; var opcode = options && options.binary ? 2 : 1; @@ -107,7 +106,7 @@ class Sender extends EventEmitter { this.messageHandlers.push(function (callback) { self.applyExtensions(data, finalFragment, compressFragment, function (err, data) { if (err) { - if (typeof cb == 'function') cb(err); + if (typeof cb === 'function') cb(err); else self.emit('error', err); return; } @@ -150,15 +149,14 @@ class Sender extends EventEmitter { } } - var dataLength = data.length, - dataOffset = maskData ? 6 : 2, - secondByte = dataLength; + var dataLength = data.length; + var dataOffset = maskData ? 6 : 2; + var secondByte = dataLength; if (dataLength >= 65536) { dataOffset += 8; secondByte = 127; - } - else if (dataLength > 125) { + } else if (dataLength > 125) { dataOffset += 2; secondByte = 126; } @@ -190,8 +188,7 @@ class Sender extends EventEmitter { } else { bufferUtil.mask(data, mask, data, 0, dataLength); } - } - else { + } else { outputBuffer[1] = secondByte; if (mergeBuffers) { data.copy(outputBuffer, dataOffset); @@ -266,9 +263,8 @@ function sendFramedData (outputBuffer, data, cb) { } else { this._socket.write(outputBuffer, 'binary', cb); } - } - catch (e) { - if (typeof cb == 'function') cb(e); + } catch (e) { + if (typeof cb === 'function') cb(e); else this.emit('error', e); } } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index a6ab62173..ca89e9b90 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -22,7 +22,7 @@ const PerMessageDeflate = require('./PerMessageDeflate'); const EventEmitter = require('events').EventEmitter; var isDefinedAndNonNull = function (options, key) { - return typeof options[key] != 'undefined' && options[key] !== null; + return options[key] !== undefined && options[key] !== null; }; /** @@ -335,8 +335,9 @@ WebSocket.prototype.terminate = function terminate () { this.readyState = WebSocket.CLOSING; // End the connection - try { this._socket.end(); } - catch (e) { + try { + this._socket.end(); + } catch (e) { // Socket error during end() call, so just destroy it right now cleanupWebsocketResources.call(this, true); return; @@ -384,10 +385,11 @@ Object.defineProperty(WebSocket.prototype, 'binaryType', { return this._binaryType; }, set: function set (type) { - if (type === 'arraybuffer' || type === 'nodebuffer') + if (type === 'arraybuffer' || type === 'nodebuffer') { this._binaryType = type; - else + } else { throw new SyntaxError('unsupported binaryType: must be either "nodebuffer" or "arraybuffer"'); + } } }); @@ -435,8 +437,9 @@ WebSocket.prototype.addEventListener = function (method, listener) { var target = this; function onMessage (data, flags) { - if (flags.binary && this.binaryType === 'arraybuffer') + if (flags.binary && this.binaryType === 'arraybuffer') { data = new Uint8Array(data).buffer; + } listener.call(target, new MessageEvent(data, !!flags.binary, target)); } @@ -530,7 +533,7 @@ function OpenEvent (target) { function buildHostHeader (isSecure, hostname, port) { var headerHost = hostname; if (hostname) { - if ((isSecure && (port != 443)) || (!isSecure && (port != 80))) { + if ((isSecure && (port !== 443)) || (!isSecure && (port !== 80))) { headerHost = headerHost + ':' + port; } } @@ -663,27 +666,29 @@ function initAsClient (address, protocols, options) { requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format(extensionsOffer); } - if (isDefinedAndNonNull(options, 'pfx') - || isDefinedAndNonNull(options, 'key') - || isDefinedAndNonNull(options, 'passphrase') - || isDefinedAndNonNull(options, 'cert') - || isDefinedAndNonNull(options, 'ca') - || isDefinedAndNonNull(options, 'ciphers') - || isDefinedAndNonNull(options, 'rejectUnauthorized') - || isDefinedAndNonNull(options, 'checkServerIdentity')) { + if (isDefinedAndNonNull(options, 'pfx') || + isDefinedAndNonNull(options, 'key') || + isDefinedAndNonNull(options, 'passphrase') || + isDefinedAndNonNull(options, 'cert') || + isDefinedAndNonNull(options, 'ca') || + isDefinedAndNonNull(options, 'ciphers') || + isDefinedAndNonNull(options, 'rejectUnauthorized') || + isDefinedAndNonNull(options, 'checkServerIdentity')) { if (isDefinedAndNonNull(options, 'pfx')) requestOptions.pfx = options.pfx; if (isDefinedAndNonNull(options, 'key')) requestOptions.key = options.key; if (isDefinedAndNonNull(options, 'passphrase')) requestOptions.passphrase = options.passphrase; if (isDefinedAndNonNull(options, 'cert')) requestOptions.cert = options.cert; if (isDefinedAndNonNull(options, 'ca')) requestOptions.ca = options.ca; if (isDefinedAndNonNull(options, 'ciphers')) requestOptions.ciphers = options.ciphers; - if (isDefinedAndNonNull(options, 'rejectUnauthorized')) + if (isDefinedAndNonNull(options, 'rejectUnauthorized')) { requestOptions.rejectUnauthorized = options.rejectUnauthorized; - if (isDefinedAndNonNull(options, 'checkServerIdentity')) + } + if (isDefinedAndNonNull(options, 'checkServerIdentity')) { requestOptions.checkServerIdentity = options.checkServerIdentity; + } if (!agent) { - // global agent ignores client side certificates + // global agent ignores client side certificates agent = new httpObj.Agent(requestOptions); } } @@ -791,9 +796,9 @@ function initAsClient (address, protocols, options) { } function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { - var ultron = this._ultron = new Ultron(socket), - called = false, - self = this; + var ultron = this._ultron = new Ultron(socket); + var called = false; + var self = this; socket.setTimeout(0); socket.setNoDelay(true); @@ -955,8 +960,9 @@ function cleanupWebsocketResources (error) { if (this._socket) { if (this._ultron) this._ultron.destroy(); this._socket.on('error', function onerror () { - try { this.destroy(); } - catch (e) {} + try { + this.destroy(); + } catch (e) {} }); try { diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 0c0f98c2a..899a05f74 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -16,7 +16,7 @@ const PerMessageDeflate = require('./PerMessageDeflate'); const url = require('url'); var isDefinedAndNonNull = function (options, key) { - return typeof options[key] != 'undefined' && options[key] !== null; + return options[key] !== undefined && options[key] !== null; }; /** @@ -68,11 +68,11 @@ function WebSocketServer (options, callback) { this._server.listen(options.port, options.host, callback); } this._closeServer = function () { - if (self._server) + if (self._server) { self._server.close(); + } }; - } - else if (options.server) { + } else if (options.server) { this._server = options.server; if (options.path) { // take note of the path, to avoid collisions when multiple websocket servers are @@ -91,7 +91,7 @@ function WebSocketServer (options, callback) { this._server.once('listening', this._onceServerListening); } - if (typeof this._server != 'undefined') { + if (typeof this._server !== 'undefined') { this._onServerError = function (error) { self.emit('error', error); }; this._server.on('error', this._onServerError); this._onServerUpgrade = function (req, socket, upgradeHead) { @@ -131,15 +131,14 @@ WebSocketServer.prototype.close = function (callback) { for (var i = 0, l = this.clients.length; i < l; ++i) { this.clients[i].terminate(); } - } - catch (e) { + } catch (e) { error = e; } // remove path descriptor, if any if (this.path && this._server._webSocketPaths) { delete this._server._webSocketPaths[this.path]; - if (Object.keys(this._server._webSocketPaths).length == 0) { + if (Object.keys(this._server._webSocketPaths).length === 0) { delete this._server._webSocketPaths; } } @@ -149,8 +148,7 @@ WebSocketServer.prototype.close = function (callback) { if (typeof this._closeServer !== 'undefined') { this._closeServer(); } - } - finally { + } finally { if (this._server) { this._server.removeListener('listening', this._onceServerListening); this._server.removeListener('error', this._onServerError); @@ -158,10 +156,11 @@ WebSocketServer.prototype.close = function (callback) { } delete this._server; } - if (callback) + if (callback) { callback(error); - else if (error) + } else if (error) { throw error; + } }; /** @@ -217,9 +216,9 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { var protocols = req.headers['sec-websocket-protocol']; // verify client - var origin = version < 13 ? - req.headers['sec-websocket-origin'] : - req.headers['origin']; + var origin = version < 13 + ? req.headers['sec-websocket-origin'] + : req.headers['origin']; // handle extensions offer var extensionsOffer = Extensions.parse(req.headers['sec-websocket-extensions']); @@ -240,7 +239,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { 'Sec-WebSocket-Accept: ' + key ]; - if (typeof protocol != 'undefined') { + if (typeof protocol !== 'undefined') { headers.push('Sec-WebSocket-Protocol: ' + protocol); } @@ -267,8 +266,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { socket.setNoDelay(true); try { socket.write(headers.concat('', '').join('\r\n')); - } - catch (e) { + } catch (e) { // if the upgrade write fails, shut the connection down hard try { socket.destroy(); } catch (e) {} return; @@ -285,7 +283,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { self.clients.push(client); client.on('close', function () { var index = self.clients.indexOf(client); - if (index != -1) { + if (index !== -1) { self.clients.splice(index, 1); } }); @@ -300,7 +298,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { // calling completeHybiUpgrade2 var completeHybiUpgrade1 = function () { // choose from the sub-protocols - if (typeof self.options.handleProtocols == 'function') { + if (typeof self.options.handleProtocols === 'function') { var protList = (protocols || '').split(/, */); var callbackCalled = false; self.options.handleProtocols(protList, function (result, protocol) { @@ -316,21 +314,20 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { } else { if (typeof protocols !== 'undefined') { completeHybiUpgrade2(protocols.split(/, */)[0]); - } - else { + } else { completeHybiUpgrade2(); } } }; // optionally call external client verification handler - if (typeof this.options.verifyClient == 'function') { + if (typeof this.options.verifyClient === 'function') { var info = { origin: origin, secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined', req: req }; - if (this.options.verifyClient.length == 2) { + if (this.options.verifyClient.length === 2) { this.options.verifyClient(info, function (result, code, name) { if (typeof code === 'undefined') code = 401; if (typeof name === 'undefined') name = http.STATUS_CODES[code]; @@ -339,8 +336,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { else completeHybiUpgrade1(); }); return; - } - else if (!this.options.verifyClient(info)) { + } else if (!this.options.verifyClient(info)) { abortConnection(socket, 401, 'Unauthorized'); return; } @@ -368,19 +364,21 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { return; } - var origin = req.headers['origin'], self = this; + var origin = req.headers['origin']; + var self = this; // setup handshake completion to run after client has been verified var onClientVerified = function () { var wshost; - if (!req.headers['x-forwarded-host']) + if (!req.headers['x-forwarded-host']) { wshost = req.headers.host; - else + } else { wshost = req.headers['x-forwarded-host']; + } var proto = (req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws'; - var location = (proto + '://' + wshost + req.url), - protocol = req.headers['sec-websocket-protocol']; + var location = proto + '://' + wshost + req.url; + var protocol = req.headers['sec-websocket-protocol']; // build the response header and return a Buffer var buildResponseHeader = function () { @@ -390,8 +388,8 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { 'Connection: Upgrade', 'Sec-WebSocket-Location: ' + location ]; - if (typeof protocol != 'undefined') headers.push('Sec-WebSocket-Protocol: ' + protocol); - if (typeof origin != 'undefined') headers.push('Sec-WebSocket-Origin: ' + origin); + if (typeof protocol !== 'undefined') headers.push('Sec-WebSocket-Protocol: ' + protocol); + if (typeof origin !== 'undefined') headers.push('Sec-WebSocket-Origin: ' + origin); return new Buffer(headers.concat('', '').join('\r\n')); }; @@ -418,13 +416,13 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { // handshake completion code to run once nonce has been successfully retrieved var completeHandshake = function (nonce, rest, headerBuffer) { // calculate key - var k1 = req.headers['sec-websocket-key1'], - k2 = req.headers['sec-websocket-key2'], - md5 = crypto.createHash('md5'); + var k1 = req.headers['sec-websocket-key1']; + var k2 = req.headers['sec-websocket-key2']; + var md5 = crypto.createHash('md5'); [k1, k2].forEach(function (k) { - var n = parseInt(k.replace(/[^\d]/g, '')), - spaces = k.replace(/[^ ]/g, '').length; + var n = parseInt(k.replace(/[^\d]/g, '')); + var spaces = k.replace(/[^ ]/g, '').length; if (spaces === 0 || n % spaces !== 0) { abortConnection(socket, 400, 'Bad Request'); return; @@ -458,7 +456,7 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { self.clients.push(client); client.on('close', function () { var index = self.clients.indexOf(client); - if (index != -1) { + if (index !== -1) { self.clients.splice(index, 1); } }); @@ -468,8 +466,7 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { socket.removeListener('error', errorHandler); cb(client); }); - } - catch (e) { + } catch (e) { try { socket.destroy(); } catch (e) {} return; } @@ -482,8 +479,7 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { nonce = upgradeHead.slice(0, nonceLength); rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null; completeHandshake.call(self, nonce, rest, buildResponseHeader()); - } - else { + } else { // nonce not present in upgradeHead nonce = new Buffer(nonceLength); upgradeHead.copy(nonce, 0); @@ -494,7 +490,7 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { if (toRead === 0) return; data.copy(nonce, received, 0, toRead); received += toRead; - if (received == nonceLength) { + if (received === nonceLength) { socket.removeListener('data', handler); if (toRead < data.length) rest = data.slice(toRead); @@ -512,13 +508,13 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { }; // verify client - if (typeof this.options.verifyClient == 'function') { + if (typeof this.options.verifyClient === 'function') { var info = { origin: origin, secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined', req: req }; - if (this.options.verifyClient.length == 2) { + if (this.options.verifyClient.length === 2) { this.options.verifyClient(info, function (result, code, name) { if (typeof code === 'undefined') code = 401; if (typeof name === 'undefined') name = http.STATUS_CODES[code]; @@ -527,8 +523,7 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { else onClientVerified.apply(self); }); return; - } - else if (!this.options.verifyClient(info)) { + } else if (!this.options.verifyClient(info)) { abortConnection(socket, 401, 'Unauthorized'); return; } @@ -556,9 +551,9 @@ function abortConnection (socket, code, name) { `Content-type: text/html\r\n` + `\r\n\r\n`; socket.write(response); - } - catch (e) { /* ignore errors - we've aborted this connection */ } - finally { + } catch (e) { + // ignore errors - we've aborted this connection + } finally { // ensure that an early aborted connection is shut down completely try { socket.destroy(); } catch (e) {} } From d2c5bb831530108038df94a241aebc4feeebc898 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 14 Aug 2016 12:16:20 +0200 Subject: [PATCH 095/669] [major] Use a set instead of an array to track clients --- lib/WebSocketServer.js | 33 +++++++++++++++------------------ test/WebSocketServer.test.js | 34 +++++++++++++++++----------------- 2 files changed, 32 insertions(+), 35 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 899a05f74..38b7dd117 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -107,9 +107,9 @@ function WebSocketServer (options, callback) { this._server.on('upgrade', this._onServerUpgrade); } + if (options.clientTracking) this.clients = new Set(); this.options = options; this.path = options.path; - this.clients = []; } /** @@ -127,12 +127,15 @@ util.inherits(WebSocketServer, EventEmitter); WebSocketServer.prototype.close = function (callback) { // terminate all associated clients var error = null; - try { - for (var i = 0, l = this.clients.length; i < l; ++i) { - this.clients[i].terminate(); + + if (this.clients) { + for (const client of this.clients) { + try { + client.terminate(); + } catch (e) { + error = e; + } } - } catch (e) { - error = e; } // remove path descriptor, if any @@ -279,13 +282,10 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { maxPayload: self.options.maxPayload }); - if (self.options.clientTracking) { - self.clients.push(client); + if (self.clients) { + self.clients.add(client); client.on('close', function () { - var index = self.clients.indexOf(client); - if (index !== -1) { - self.clients.splice(index, 1); - } + self.clients.delete(client); }); } @@ -452,13 +452,10 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { protocolVersion: 'hixie-76', protocol: protocol }); - if (self.options.clientTracking) { - self.clients.push(client); + if (self.clients) { + self.clients.add(client); client.on('close', function () { - var index = self.clients.indexOf(client); - if (index !== -1) { - self.clients.splice(index, 1); - } + self.clients.delete(client); }); } diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 5dce73cae..4c42a3219 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -31,7 +31,7 @@ describe('WebSocketServer', function() { ws.should.be.an.instanceOf(WebSocketServer); done(); }); - + it('throws an error if no option object is passed', function() { var gotException = false; try { @@ -199,7 +199,7 @@ describe('WebSocketServer', function() { socket._socket.write(new Buffer([5])); socket.send(''); }; - }); + }); }); describe('#close', function() { @@ -279,11 +279,11 @@ describe('WebSocketServer', function() { describe('#clients', function() { it('returns a list of connected clients', function(done) { var wss = new WebSocketServer({port: ++port}, function() { - wss.clients.length.should.eql(0); + wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); wss.on('connection', function(client) { - wss.clients.length.should.eql(1); + wss.clients.size.should.eql(1); wss.close(); done(); }); @@ -291,11 +291,11 @@ describe('WebSocketServer', function() { it('can be disabled', function(done) { var wss = new WebSocketServer({port: ++port, clientTracking: false}, function() { - wss.clients.length.should.eql(0); + wss.should.not.have.property('clients'); var ws = new WebSocket('ws://localhost:' + port); }); wss.on('connection', function(client) { - wss.clients.length.should.eql(0); + wss.should.not.have.property('clients'); wss.close(); done(); }); @@ -308,7 +308,7 @@ describe('WebSocketServer', function() { }); wss.on('connection', function(client) { client.on('close', function() { - wss.clients.length.should.eql(0); + wss.clients.size.should.eql(0); wss.close(); done(); }); @@ -323,7 +323,7 @@ describe('WebSocketServer', function() { }); wss.on('connection', function(client) { client.on('close', function() { - wss.clients.length.should.eql(0); + wss.clients.size.should.eql(0); wss.close(); done(); }); @@ -346,12 +346,12 @@ describe('WebSocketServer', function() { it('maxpayload is passed on to clients,', function(done) { var _maxPayload = 20480; var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { - wss.clients.length.should.eql(0); + wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); wss.on('connection', function(client) { - wss.clients.length.should.eql(1); - wss.clients[0].maxPayload.should.eql(_maxPayload); + wss.clients.size.should.eql(1); + client.maxPayload.should.eql(_maxPayload); wss.close(); done(); }); @@ -359,12 +359,12 @@ describe('WebSocketServer', function() { it('maxpayload is passed on to hybi receivers', function(done) { var _maxPayload = 20480; var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { - wss.clients.length.should.eql(0); + wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); wss.on('connection', function(client) { - wss.clients.length.should.eql(1); - wss.clients[0]._receiver.maxPayload.should.eql(_maxPayload); + wss.clients.size.should.eql(1); + client._receiver.maxPayload.should.eql(_maxPayload); wss.close(); done(); }); @@ -373,12 +373,12 @@ describe('WebSocketServer', function() { var PerMessageDeflate = require('../lib/PerMessageDeflate'); var _maxPayload = 20480; var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { - wss.clients.length.should.eql(0); + wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); wss.on('connection', function(client) { - wss.clients.length.should.eql(1); - wss.clients[0]._receiver.extensions[PerMessageDeflate.extensionName]._maxPayload.should.eql(_maxPayload); + wss.clients.size.should.eql(1); + client._receiver.extensions[PerMessageDeflate.extensionName]._maxPayload.should.eql(_maxPayload); wss.close(); done(); }); From 03847d34c8ec35e18debbb65c3858cf60c4f392f Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Wed, 12 Oct 2016 15:08:02 +0200 Subject: [PATCH 096/669] [perf] Improve performance of fallback mask and unmask functions (#856) * Fix lint --- lib/BufferUtil.fallback.js | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/lib/BufferUtil.fallback.js b/lib/BufferUtil.fallback.js index a1f9ed41b..a9607e7a0 100644 --- a/lib/BufferUtil.fallback.js +++ b/lib/BufferUtil.fallback.js @@ -16,36 +16,15 @@ exports.BufferUtil = { } }, mask: function (source, mask, output, offset, length) { - var maskNum = mask.readUInt32LE(0, true); - var i = 0; - for (; i < length - 3; i += 4) { - var num = maskNum ^ source.readUInt32LE(i, true); - if (num < 0) num = 4294967296 + num; - output.writeUInt32LE(num, offset + i, true); + for (var i = 0; i < length; i++) { + output[offset + i] = source[i] ^ mask[i & 3]; } - /* eslint-disable no-fallthrough */ - switch (length % 4) { - case 3: output[offset + i + 2] = source[i + 2] ^ mask[2]; - case 2: output[offset + i + 1] = source[i + 1] ^ mask[1]; - case 1: output[offset + i] = source[i] ^ mask[0]; - } - /* eslint-enable no-fallthrough */ }, unmask: function (data, mask) { - var maskNum = mask.readUInt32LE(0, true); + // required until https://github.com/nodejs/node/issues/9006 is resolved var length = data.length; - var i = 0; - for (; i < length - 3; i += 4) { - var num = maskNum ^ data.readUInt32LE(i, true); - if (num < 0) num = 4294967296 + num; - data.writeUInt32LE(num, i, true); - } - /* eslint-disable no-fallthrough */ - switch (length % 4) { - case 3: data[i + 2] = data[i + 2] ^ mask[2]; - case 2: data[i + 1] = data[i + 1] ^ mask[1]; - case 1: data[i] = data[i] ^ mask[0]; + for (var i = 0; i < length; i++) { + data[i] ^= mask[i & 3]; } - /* eslint-enable no-fallthrough */ } }; From da953df605f375822ca7b72b722a5e1720ca2208 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Wed, 12 Oct 2016 15:30:19 +0200 Subject: [PATCH 097/669] [minor] Simplify upgradeHead handling by using socket.unshift() (#854) --- lib/WebSocket.js | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index ca89e9b90..1ed78fc32 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -797,7 +797,6 @@ function initAsClient (address, protocols, options) { function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { var ultron = this._ultron = new Ultron(socket); - var called = false; var self = this; socket.setTimeout(0); @@ -812,35 +811,17 @@ function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { ultron.on('error', cleanupWebsocketResources.bind(this)); // ensure that the upgradeHead is added to the receiver - function firstHandler (data) { - if (called || self.readyState === WebSocket.CLOSED) return; - called = true; - socket.removeListener('data', firstHandler); - ultron.on('data', realHandler); - - if (upgradeHead && upgradeHead.length > 0) { - realHandler(upgradeHead); - upgradeHead = null; - } - - if (data) realHandler(data); + if (upgradeHead && upgradeHead.length > 0) { + socket.unshift(upgradeHead); + upgradeHead = null; } - // subsequent packets are pushed straight to the receiver - function realHandler (data) { + // subsequent packets are pushed to the receiver + ultron.on('data', (data) => { self.bytesReceived += data.length; self._receiver.add(data); - } - - ultron.on('data', firstHandler); - - // if data was passed along with the http upgrade, - // this will schedule a push of that on to the receiver. - // this has to be done on next tick, since the caller - // hasn't had a chance to set event handlers on this client - // object yet. - process.nextTick(firstHandler); + }); // receiver event handlers self._receiver.ontext = function ontext (data, flags) { From 00cd45add4ab551b8ae3ed6d361741b908e2ce54 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 12 Oct 2016 16:01:59 +0200 Subject: [PATCH 098/669] [lint] Fix lint issues on bench/* --- bench/parser.benchmark.js | 12 ++++++------ bench/sender.benchmark.js | 8 ++++---- bench/speed.js | 16 ++++++++-------- bench/util.js | 8 ++++---- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index 4a73ec25d..3d795d338 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -6,12 +6,12 @@ 'use strict'; -const benchmark = require('benchmark') +const benchmark = require('benchmark'); -const Receiver = require('../').Receiver +const Receiver = require('../').Receiver; const util = require('./util'); -function createBinaryPacket(length) { +function createBinaryPacket (length) { const message = Buffer.alloc(length); for (var i = 0; i < length; ++i) message[i] = i % 10; @@ -20,7 +20,7 @@ function createBinaryPacket(length) { util.mask(message, '3483a868').toString('hex'), 'hex'); } -const pingMessage = 'Hello' +const pingMessage = 'Hello'; const pingPacket1 = Buffer.from('89' + util.pack(2, 0x80 | pingMessage.length) + '3483a868' + util.mask(pingMessage, '3483a868').toString('hex'), 'hex'); const pingPacket2 = Buffer.from('8900', 'hex'); @@ -28,7 +28,7 @@ const closePacket = Buffer.from('8800', 'hex'); const maskedTextPacket = Buffer.from('81933483a86801b992524fa1c60959e68a5216e6cb005ba1d5', 'hex'); const binaryDataPacket = createBinaryPacket(125); const binaryDataPacket2 = createBinaryPacket(65535); -const binaryDataPacket3 = createBinaryPacket(200 * 1024) +const binaryDataPacket3 = createBinaryPacket(200 * 1024); var receiver = new Receiver({}, 1024 * 1024); const suite = new benchmark.Suite(); @@ -38,7 +38,7 @@ suite.add('ping with no data', () => receiver.add(pingPacket2)); suite.add('close message', () => { receiver.add(closePacket); receiver.endPacket(); -}) +}); suite.add('masked text message', () => receiver.add(maskedTextPacket)); suite.add('binary data (125 bytes)', () => receiver.add(binaryDataPacket)); suite.add('binary data (65535 bytes)', () => receiver.add(binaryDataPacket2)); diff --git a/bench/sender.benchmark.js b/bench/sender.benchmark.js index 7e9f28728..e0ac8d31b 100644 --- a/bench/sender.benchmark.js +++ b/bench/sender.benchmark.js @@ -6,22 +6,22 @@ 'use strict'; -const benchmark = require('benchmark') +const benchmark = require('benchmark'); -const Sender = require('../').Sender +const Sender = require('../').Sender; const framePacket = Buffer.alloc(200 * 1024).fill(99); const suite = new benchmark.Suite(); var sender = new Sender(); -sender._socket = { write() {} }; +sender._socket = { write () {} }; suite.add('frameAndSend, unmasked (200 kB)', () => sender.frameAndSend(0x2, framePacket, true, false)); suite.add('frameAndSend, masked (200 kB)', () => sender.frameAndSend(0x2, framePacket, true, true)); suite.on('cycle', (e) => { console.log(e.target.toString()); sender = new Sender(); - sender._socket = { write() {} }; + sender._socket = { write () {} }; }); if (require.main === module) { diff --git a/bench/speed.js b/bench/speed.js index 8f23e36b5..b61cb2c8d 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -2,22 +2,22 @@ const cluster = require('cluster'); -const ws = require('../'); +const WebSocket = require('../'); const port = 8181; -function roundPrec(num, prec) { +function roundPrec (num, prec) { const mul = Math.pow(10, prec); return Math.round(num * mul) / mul; } -function humanSize(bytes) { +function humanSize (bytes) { if (bytes >= 1048576) return roundPrec(bytes / 1048576, 2) + ' MiB'; if (bytes >= 1024) return roundPrec(bytes / 1024, 2) + ' KiB'; return roundPrec(bytes, 2) + ' B'; } -function generateRandomData(size) { +function generateRandomData (size) { const buffer = Buffer.alloc(size); for (var i = 0; i < size; ++i) { buffer[i] = ~~(Math.random() * 127); @@ -25,9 +25,9 @@ function generateRandomData(size) { return buffer; } -function runConfig(useBinary, roundtrips, size, randomBytes, cb) { +function runConfig (useBinary, roundtrips, size, randomBytes, cb) { const data = randomBytes.slice(0, size); - const client = new ws(`ws://localhost:${port}`); + const client = new WebSocket(`ws://localhost:${port}`); var roundtrip = 0; var time; @@ -60,7 +60,7 @@ function runConfig(useBinary, roundtrips, size, randomBytes, cb) { } if (cluster.isMaster) { - const wss = new ws.Server({ + const wss = new WebSocket.Server({ maxPayload: 600 * 1024 * 1024, perMessageDeflate: false, clientTracking: false, @@ -89,7 +89,7 @@ if (cluster.isMaster) { console.log('Generating %s of test data...', humanSize(largest)); const randomBytes = generateRandomData(largest); - (function run() { + (function run () { if (configs.length === 0) return cluster.worker.kill(); var config = configs.shift(); config.push(randomBytes, run); diff --git a/bench/util.js b/bench/util.js index 3b94c63ff..7363e8b29 100644 --- a/bench/util.js +++ b/bench/util.js @@ -9,7 +9,7 @@ /** * Performs hybi07+ type masking on a hex string or buffer. */ -function mask(buf, maskString) { +function mask (buf, maskString) { const _mask = Buffer.from(maskString || '3483a868', 'hex'); if (typeof buf === 'string') buf = Buffer.from(buf); @@ -24,21 +24,21 @@ function mask(buf, maskString) { /** * Left pads the string `s` to a total length of `n` with char `c`. */ -function padl(s, n, c) { +function padl (s, n, c) { return c.repeat(n - s.length) + s; } /** * Returns a hex string, representing a specific byte count `length`, from a number. */ -function pack(length, number) { +function pack (length, number) { return padl(number.toString(16), length, '0'); } /** * Returns a hex string representing the length of a message. */ -function getHybiLengthAsHexString(len, masked) { +function getHybiLengthAsHexString (len, masked) { var s; masked = masked ? 0x80 : 0; From 3c8acc1f78bff777852e4ab57a1be689b1fc480d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 12 Oct 2016 16:42:50 +0200 Subject: [PATCH 099/669] [benchmark] Move functions only used by the worker in the worker branch --- bench/speed.js | 106 ++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 55 deletions(-) diff --git a/bench/speed.js b/bench/speed.js index b61cb2c8d..40c3f30cf 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -6,59 +6,6 @@ const WebSocket = require('../'); const port = 8181; -function roundPrec (num, prec) { - const mul = Math.pow(10, prec); - return Math.round(num * mul) / mul; -} - -function humanSize (bytes) { - if (bytes >= 1048576) return roundPrec(bytes / 1048576, 2) + ' MiB'; - if (bytes >= 1024) return roundPrec(bytes / 1024, 2) + ' KiB'; - return roundPrec(bytes, 2) + ' B'; -} - -function generateRandomData (size) { - const buffer = Buffer.alloc(size); - for (var i = 0; i < size; ++i) { - buffer[i] = ~~(Math.random() * 127); - } - return buffer; -} - -function runConfig (useBinary, roundtrips, size, randomBytes, cb) { - const data = randomBytes.slice(0, size); - const client = new WebSocket(`ws://localhost:${port}`); - var roundtrip = 0; - var time; - - client.on('error', (err) => { - console.error(err.stack); - cluster.worker.kill(); - }); - client.on('open', () => { - time = process.hrtime(); - client.send(data, { binary: useBinary }); - }); - client.on('message', () => { - if (++roundtrip !== roundtrips) return client.send(data, { binary: useBinary }); - - var elapsed = process.hrtime(time); - elapsed = elapsed[0] * 1e9 + elapsed[1]; - - console.log( - '%d roundtrips of %s %s data:\t%ss\t%s', - roundtrips, - humanSize(size), - useBinary ? 'binary' : 'text', - roundPrec(elapsed / 1e9, 1), - humanSize(size * roundtrips / elapsed * 1e9) + '/s' - ); - - client.close(); - cb(); - }); -} - if (cluster.isMaster) { const wss = new WebSocket.Server({ maxPayload: 600 * 1024 * 1024, @@ -85,14 +32,63 @@ if (cluster.isMaster) { [false, 100, 1024 * 1024] ]; + const roundPrec = (num, prec) => { + const mul = Math.pow(10, prec); + return Math.round(num * mul) / mul; + }; + + const humanSize = (bytes) => { + if (bytes >= 1048576) return roundPrec(bytes / 1048576, 2) + ' MiB'; + if (bytes >= 1024) return roundPrec(bytes / 1024, 2) + ' KiB'; + return roundPrec(bytes, 2) + ' B'; + }; + const largest = configs.reduce((prev, curr) => curr[2] > prev ? curr[2] : prev, 0); console.log('Generating %s of test data...', humanSize(largest)); - const randomBytes = generateRandomData(largest); + const randomBytes = Buffer.allocUnsafe(largest); + + for (var i = 0; i < largest; ++i) { + randomBytes[i] = ~~(Math.random() * 127); + } + + const runConfig = (useBinary, roundtrips, size, cb) => { + const data = randomBytes.slice(0, size); + const ws = new WebSocket(`ws://localhost:${port}`); + var roundtrip = 0; + var time; + + ws.on('error', (err) => { + console.error(err.stack); + cluster.worker.kill(); + }); + ws.on('open', () => { + time = process.hrtime(); + ws.send(data, { binary: useBinary }); + }); + ws.on('message', () => { + if (++roundtrip !== roundtrips) return ws.send(data, { binary: useBinary }); + + var elapsed = process.hrtime(time); + elapsed = elapsed[0] * 1e9 + elapsed[1]; + + console.log( + '%d roundtrips of %s %s data:\t%ss\t%s', + roundtrips, + humanSize(size), + useBinary ? 'binary' : 'text', + roundPrec(elapsed / 1e9, 1), + humanSize(size * roundtrips / elapsed * 1e9) + '/s' + ); + + ws.close(); + cb(); + }); + }; (function run () { if (configs.length === 0) return cluster.worker.kill(); var config = configs.shift(); - config.push(randomBytes, run); + config.push(run); runConfig.apply(null, config); })(); } From 73298bf278a59f663be03873469ca7d983bc0c6a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 13 Oct 2016 11:02:02 +0200 Subject: [PATCH 100/669] [minor] Use arrow functions for lexical `this` in lib/WebSocket.js --- lib/WebSocket.js | 137 +++++++++++++++++++++-------------------------- 1 file changed, 60 insertions(+), 77 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 1ed78fc32..f6c276a7e 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -19,7 +19,7 @@ const SenderHixie = require('./Sender.hixie'); const ReceiverHixie = require('./Receiver.hixie'); const Extensions = require('./Extensions'); const PerMessageDeflate = require('./PerMessageDeflate'); -const EventEmitter = require('events').EventEmitter; +const EventEmitter = require('events'); var isDefinedAndNonNull = function (options, key) { return options[key] !== undefined && options[key] !== null; @@ -116,21 +116,20 @@ WebSocket.prototype.close = function close (code, data) { return; } - var self = this; try { this.readyState = WebSocket.CLOSING; this._closeCode = code; this._closeMessage = data; var mask = !this._isServer; - this._sender.close(code, data, mask, function (err) { - if (err) self.emit('error', err); + this._sender.close(code, data, mask, (err) => { + if (err) this.emit('error', err); - if (self._closeReceived && self._isServer) { - self.terminate(); + if (this._closeReceived && this._isServer) { + this.terminate(); } else { // ensure that the connection is cleaned up even when no response of closing handshake. - clearTimeout(self._closeTimer); - self._closeTimer = setTimeout(cleanupWebsocketResources.bind(self, true), closeTimeout); + clearTimeout(this._closeTimer); + this._closeTimer = setTimeout(cleanupWebsocketResources.bind(this, true), closeTimeout); } }); } catch (e) { @@ -225,10 +224,8 @@ WebSocket.prototype.send = function send (data, options, cb) { if (!data) data = ''; - var self = this; - if (this._queue) { - this._queue.push(function () { self.send(data, options, cb); }); + this._queue.push(() => this.send(data, options, cb)); return; } @@ -253,11 +250,8 @@ WebSocket.prototype.send = function send (data, options, cb) { if (data instanceof readable) { startQueue(this); - sendStream(this, data, options, function send (error) { - process.nextTick(function tock () { - executeQueueSends(self); - }); - + sendStream(this, data, options, (error) => { + process.nextTick(() => executeQueueSends(this)); if (typeof cb === 'function') cb(error); }); } else { @@ -279,8 +273,6 @@ WebSocket.prototype.stream = function stream (options, cb) { options = {}; } - var self = this; - if (typeof cb !== 'function') throw new Error('callback must be provided'); if (this.readyState !== WebSocket.OPEN) { @@ -290,7 +282,7 @@ WebSocket.prototype.stream = function stream (options, cb) { } if (this._queue) { - this._queue.push(function () { self.stream(options, cb); }); + this._queue.push(() => this.stream(options, cb)); return; } @@ -304,23 +296,23 @@ WebSocket.prototype.stream = function stream (options, cb) { startQueue(this); - function send (data, final) { + const send = (data, final) => { try { - if (self.readyState !== WebSocket.OPEN) throw new Error('not opened'); + if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); options.fin = final === true; - self._sender.send(data, options); - if (!final) process.nextTick(cb.bind(null, null, send)); - else executeQueueSends(self); + this._sender.send(data, options); + if (!final) process.nextTick(cb, null, send); + else executeQueueSends(this); } catch (e) { if (typeof cb === 'function') cb(e); else { - delete self._queue; - self.emit('error', e); + delete this._queue; + this.emit('error', e); } } - } + }; - process.nextTick(cb.bind(null, null, send)); + process.nextTick(cb, null, send); }; /** @@ -712,39 +704,38 @@ function initAsClient (address, protocols, options) { else requestOptions.headers.Origin = options.origin; } - var self = this; var req = httpObj.request(requestOptions); - req.on('error', function onerror (error) { - self.emit('error', error); - cleanupWebsocketResources.call(self, error); + req.on('error', (error) => { + this.emit('error', error); + cleanupWebsocketResources.call(this, error); }); - req.once('response', function response (res) { + req.once('response', (res) => { var error; - if (!self.emit('unexpected-response', req, res)) { + if (!this.emit('unexpected-response', req, res)) { error = new Error(`unexpected server response (${res.statusCode})`); req.abort(); - self.emit('error', error); + this.emit('error', error); } - cleanupWebsocketResources.call(self, error); + cleanupWebsocketResources.call(this, error); }); - req.once('upgrade', function upgrade (res, socket, upgradeHead) { - if (self.readyState === WebSocket.CLOSED) { + req.once('upgrade', (res, socket, upgradeHead) => { + if (this.readyState === WebSocket.CLOSED) { // client closed before server accepted connection - self.emit('close'); - self.removeAllListeners(); + this.emit('close'); + this.removeAllListeners(); socket.end(); return; } var serverKey = res.headers['sec-websocket-accept']; if (typeof serverKey === 'undefined' || serverKey !== expectedServerKey) { - self.emit('error', new Error('invalid server key')); - self.removeAllListeners(); + this.emit('error', new Error('invalid server key')); + this.removeAllListeners(); socket.end(); return; } @@ -762,12 +753,12 @@ function initAsClient (address, protocols, options) { } if (protError) { - self.emit('error', new Error(protError)); - self.removeAllListeners(); + this.emit('error', new Error(protError)); + this.removeAllListeners(); socket.end(); return; } else if (serverProt) { - self.protocol = serverProt; + this.protocol = serverProt; } var serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']); @@ -775,15 +766,15 @@ function initAsClient (address, protocols, options) { try { perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]); } catch (err) { - self.emit('error', new Error('invalid extension parameter')); - self.removeAllListeners(); + this.emit('error', new Error('invalid extension parameter')); + this.removeAllListeners(); socket.end(); return; } - self.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } - establishConnection.call(self, Receiver, Sender, socket, upgradeHead); + establishConnection.call(this, Receiver, Sender, socket, upgradeHead); // perform cleanup on http resources req.removeAllListeners(); @@ -797,7 +788,6 @@ function initAsClient (address, protocols, options) { function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { var ultron = this._ultron = new Ultron(socket); - var self = this; socket.setTimeout(0); socket.setNoDelay(true); @@ -811,7 +801,6 @@ function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { ultron.on('error', cleanupWebsocketResources.bind(this)); // ensure that the upgradeHead is added to the receiver - if (upgradeHead && upgradeHead.length > 0) { socket.unshift(upgradeHead); upgradeHead = null; @@ -819,57 +808,51 @@ function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { // subsequent packets are pushed to the receiver ultron.on('data', (data) => { - self.bytesReceived += data.length; - self._receiver.add(data); + this.bytesReceived += data.length; + this._receiver.add(data); }); // receiver event handlers - self._receiver.ontext = function ontext (data, flags) { - flags = flags || {}; - - self.emit('message', data, flags); - }; + this._receiver.ontext = (data, flags) => this.emit('message', data, flags || {}); - self._receiver.onbinary = function onbinary (data, flags) { + this._receiver.onbinary = (data, flags) => { flags = flags || {}; - flags.binary = true; - self.emit('message', data, flags); + + this.emit('message', data, flags); }; - self._receiver.onping = function onping (data, flags) { + this._receiver.onping = (data, flags) => { flags = flags || {}; - self.pong(data, { - mask: !self._isServer, + this.pong(data, { + mask: !this._isServer, binary: flags.binary === true }, true); - self.emit('ping', data, flags); + this.emit('ping', data, flags); }; - self._receiver.onpong = function onpong (data, flags) { - self.emit('pong', data, flags || {}); - }; + this._receiver.onpong = (data, flags) => this.emit('pong', data, flags || {}); - self._receiver.onclose = function onclose (code, data, flags) { + this._receiver.onclose = (code, data, flags) => { flags = flags || {}; - self._closeReceived = true; - self.close(code, data); + this._closeReceived = true; + this.close(code, data); }; - self._receiver.onerror = function onerror (error, errorCode) { + this._receiver.onerror = (error, errorCode) => { // close the connection when the receiver reports a HyBi error code - self.close(errorCode, ''); - self.emit('error', error); + this.close(errorCode, ''); + this.emit('error', error); }; // finalize the client this._sender = new SenderClass(socket, this.extensions); - this._sender.on('error', function onerror (error) { - self.close(1002, ''); - self.emit('error', error); + this._sender.on('error', (error) => { + this.close(1002, ''); + this.emit('error', error); }); this.readyState = WebSocket.OPEN; From 98a9121533dc63249624c8453eeb557d828c112b Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Thu, 13 Oct 2016 12:45:13 +0200 Subject: [PATCH 101/669] [perf] Use messageHandlers only if permessage-deflate is in use (#853) --- lib/Sender.js | 136 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 94 insertions(+), 42 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index ffb903ef7..4a4cfd980 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -41,13 +41,24 @@ class Sender extends EventEmitter { dataBuffer.writeUInt16BE(code, 0); if (dataBuffer.length > 2) dataBuffer.write(data, 2); - var self = this; - this.messageHandlers.push(function (callback) { - self.frameAndSend(0x8, dataBuffer, true, mask); - callback(); - if (typeof cb === 'function') cb(); - }); - this.flush(); + if (this.extensions[PerMessageDeflate.extensionName]) { + this.enqueue([this.doClose, [dataBuffer, mask, cb]]); + } else { + this.doClose(dataBuffer, mask, cb); + } + } + + /** + * Sends a close frame. + * + * @api private + */ + doClose (data, mask, cb) { + this.frameAndSend(0x8, data, true, mask); + if (this.extensions[PerMessageDeflate.extensionName]) { + this.messageHandlerCallback(); + } + if (cb) cb(); } /** @@ -56,13 +67,24 @@ class Sender extends EventEmitter { * @api public */ ping (data, options) { + if (this.extensions[PerMessageDeflate.extensionName]) { + this.enqueue([this.doPing, [data, options]]); + } else { + this.doPing(data, options); + } + } + + /** + * Sends a ping frame. + * + * @api private + */ + doPing (data, options) { var mask = options && options.mask; - var self = this; - this.messageHandlers.push(function (callback) { - self.frameAndSend(0x9, data || '', true, mask); - callback(); - }); - this.flush(); + this.frameAndSend(0x9, data || '', true, mask); + if (this.extensions[PerMessageDeflate.extensionName]) { + this.messageHandlerCallback(); + } } /** @@ -71,14 +93,24 @@ class Sender extends EventEmitter { * @api public */ pong (data, options) { - var mask = options && options.mask; - var self = this; - this.messageHandlers.push(function (callback) { - self.frameAndSend(0xa, data || '', true, mask); - callback(); - }); + if (this.extensions[PerMessageDeflate.extensionName]) { + this.enqueue([this.doPong, [data, options]]); + } else { + this.doPong(data, options); + } + } - this.flush(); + /** + * Sends a pong frame. + * + * @api private + */ + doPong (data, options) { + var mask = options && options.mask; + this.frameAndSend(0xa, data || '', true, mask); + if (this.extensions[PerMessageDeflate.extensionName]) { + this.messageHandlerCallback(); + } } /** @@ -100,21 +132,28 @@ class Sender extends EventEmitter { } if (finalFragment) this.firstFragment = true; - var compressFragment = this.compress; - - var self = this; - this.messageHandlers.push(function (callback) { - self.applyExtensions(data, finalFragment, compressFragment, function (err, data) { - if (err) { - if (typeof cb === 'function') cb(err); - else self.emit('error', err); - return; - } - self.frameAndSend(opcode, data, finalFragment, mask, compress, cb); - callback(); - }); + if (this.extensions[PerMessageDeflate.extensionName]) { + this.enqueue([this.sendCompressed, [opcode, data, finalFragment, mask, compress, cb]]); + } else { + this.frameAndSend(opcode, data, finalFragment, mask, false, cb); + } + } + + /** + * Sends compressed data. + * + * @api private + */ + sendCompressed (opcode, data, finalFragment, mask, compress, cb) { + this.applyExtensions(data, finalFragment, this.compress, (err, data) => { + if (err) { + if (cb) cb(err); + else this.emit('error', err); + return; + } + this.frameAndSend(opcode, data, finalFragment, mask, compress, cb); + this.messageHandlerCallback(); }); - this.flush(); } /** @@ -210,14 +249,27 @@ class Sender extends EventEmitter { this.processing = true; - var self = this; + handler[0].apply(this, handler[1]); + } - handler(function () { - self.processing = false; - process.nextTick(function () { - self.flush(); - }); - }); + /** + * Callback to indicate message handler completion. + * + * @api private + */ + messageHandlerCallback () { + this.processing = false; + process.nextTick(() => this.flush()); + } + + /** + * Enqueues a send frame operation. + * + * @api private + */ + enqueue (params) { + this.messageHandlers.push(params); + this.flush(); } /** @@ -264,7 +316,7 @@ function sendFramedData (outputBuffer, data, cb) { this._socket.write(outputBuffer, 'binary', cb); } } catch (e) { - if (typeof cb === 'function') cb(e); + if (cb) cb(e); else this.emit('error', e); } } From 5a0b6f5e168d87c75fd1a8c06609851825ddea39 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 14 Oct 2016 07:56:03 +0200 Subject: [PATCH 102/669] [major] Remove Sender inheritance from EventEmitter (#861) --- lib/Sender.js | 27 ++++++++++++--------------- lib/WebSocket.js | 7 +++---- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 4a4cfd980..f68036a58 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -6,7 +6,6 @@ 'use strict'; -const EventEmitter = require('events'); const ErrorCodes = require('./ErrorCodes'); const bufferUtil = require('./BufferUtil').BufferUtil; const PerMessageDeflate = require('./PerMessageDeflate'); @@ -14,16 +13,15 @@ const PerMessageDeflate = require('./PerMessageDeflate'); /** * HyBi Sender implementation, Inherits from EventEmitter. */ -class Sender extends EventEmitter { +class Sender { constructor (socket, extensions) { - super(); - this._socket = socket; this.extensions = extensions || {}; this.firstFragment = true; this.compress = false; this.messageHandlers = []; this.processing = false; + this.onerror = null; } /** @@ -32,9 +30,8 @@ class Sender extends EventEmitter { * @api public */ close (code, data, mask, cb) { - if (typeof code !== 'undefined') { - if (typeof code !== 'number' || - !ErrorCodes.isValidErrorCode(code)) throw new Error('first argument must be a valid error code number'); + if (code !== undefined && (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code))) { + throw new Error('first argument must be a valid error code number'); } code = code || 1000; var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0)); @@ -148,7 +145,7 @@ class Sender extends EventEmitter { this.applyExtensions(data, finalFragment, this.compress, (err, data) => { if (err) { if (cb) cb(err); - else this.emit('error', err); + else this.onerror(err); return; } this.frameAndSend(opcode, data, finalFragment, mask, compress, cb); @@ -167,7 +164,7 @@ class Sender extends EventEmitter { if (!data) { var buff = [opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)] .concat(maskData ? [0, 0, 0, 0] : []); - sendFramedData.call(this, new Buffer(buff), null, cb); + sendFramedData(this, new Buffer(buff), null, cb); return; } @@ -233,7 +230,7 @@ class Sender extends EventEmitter { data.copy(outputBuffer, dataOffset); } } - sendFramedData.call(this, outputBuffer, mergeBuffers ? null : data, cb); + sendFramedData(this, outputBuffer, mergeBuffers ? null : data, cb); } /** @@ -307,16 +304,16 @@ function getRandomMask () { ]); } -function sendFramedData (outputBuffer, data, cb) { +function sendFramedData (sender, outputBuffer, data, cb) { try { if (data) { - this._socket.write(outputBuffer, 'binary'); - this._socket.write(data, 'binary', cb); + sender._socket.write(outputBuffer, 'binary'); + sender._socket.write(data, 'binary', cb); } else { - this._socket.write(outputBuffer, 'binary', cb); + sender._socket.write(outputBuffer, 'binary', cb); } } catch (e) { if (cb) cb(e); - else this.emit('error', e); + else sender.onerror(e); } } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index f6c276a7e..286925797 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -850,10 +850,10 @@ function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { // finalize the client this._sender = new SenderClass(socket, this.extensions); - this._sender.on('error', (error) => { + this._sender.onerror = (error) => { this.close(1002, ''); this.emit('error', error); - }); + }; this.readyState = WebSocket.OPEN; this.emit('open'); @@ -939,8 +939,7 @@ function cleanupWebsocketResources (error) { } if (this._sender) { - this._sender.removeAllListeners(); - this._sender = null; + this._sender = this._sender.onerror = null; } if (this._receiver) { From 73ab3701987774005a3a4c17df6e239bd1380fd1 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Fri, 14 Oct 2016 12:26:09 +0200 Subject: [PATCH 103/669] Replace BufferPool with a buffer list --- lib/BufferPool.js | 57 ----------- lib/Receiver.js | 209 ++++++++++++++-------------------------- test/BufferPool.test.js | 72 -------------- 3 files changed, 75 insertions(+), 263 deletions(-) delete mode 100644 lib/BufferPool.js delete mode 100644 test/BufferPool.test.js diff --git a/lib/BufferPool.js b/lib/BufferPool.js deleted file mode 100644 index c006512d1..000000000 --- a/lib/BufferPool.js +++ /dev/null @@ -1,57 +0,0 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - -'use strict'; - -class BufferPool { - constructor (initialSize, growStrategy, shrinkStrategy) { - this._growStrategy = (growStrategy || function (db, size) { - return db.used + size; - }).bind(null, this); - - this._shrinkStrategy = (shrinkStrategy || function (db) { - return initialSize; - }).bind(null, this); - - this._buffer = new Buffer(initialSize); - this._offset = 0; - this._used = 0; - this._changeFactor = 0; - } - - get size () { - return this._buffer.length; - } - - get used () { - return this._used; - } - - get (length) { - if (this._buffer == null || this._offset + length > this._buffer.length) { - var newBuf = new Buffer(this._growStrategy(length)); - this._buffer = newBuf; - this._offset = 0; - } - this._used += length; - var buf = this._buffer.slice(this._offset, this._offset + length); - this._offset += length; - return buf; - } - - reset (forceNewBuffer) { - var len = this._shrinkStrategy(); - if (len < this.size) this._changeFactor -= 1; - if (forceNewBuffer || this._changeFactor < -2) { - this._changeFactor = 0; - this._buffer = new Buffer(len); - } - this._offset = 0; - this._used = 0; - } -} - -module.exports = BufferPool; diff --git a/lib/Receiver.js b/lib/Receiver.js index 354565047..5e51ebda5 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -8,7 +8,6 @@ const Validation = require('./Validation').Validation; const ErrorCodes = require('./ErrorCodes'); -const BufferPool = require('./BufferPool'); const bufferUtil = require('./BufferUtil').BufferUtil; const PerMessageDeflate = require('./PerMessageDeflate'); @@ -23,27 +22,6 @@ class Receiver { extensions = {}; } - // memory pool for fragmented messages - var fragmentedPoolPrevUsed = -1; - this.fragmentedBufferPool = new BufferPool(1024, function (db, length) { - return db.used + length; - }, function (db) { - fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 - ? Math.ceil((fragmentedPoolPrevUsed + db.used) / 2) - : db.used; - return fragmentedPoolPrevUsed; - }); - - // memory pool for unfragmented messages - var unfragmentedPoolPrevUsed = -1; - this.unfragmentedBufferPool = new BufferPool(1024, function (db, length) { - return db.used + length; - }, function (db) { - unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 - ? Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) - : db.used; - return unfragmentedPoolPrevUsed; - }); this.extensions = extensions || {}; this.maxPayload = maxPayload || 0; this.currentPayloadLength = 0; @@ -54,15 +32,12 @@ class Receiver { opcode: 0, fragmentedOperation: false }; - this.overflow = []; - this.headerBuffer = new Buffer(10); - this.expectOffset = 0; - this.expectBuffer = null; + this.expectBytes = 0; this.expectHandler = null; this.currentMessage = []; this.currentMessageLength = 0; this.messageHandlers = []; - this.expectHeader(2, this.processPacket); + this.expectData(2, this.processPacket); this.dead = false; this.processing = false; @@ -72,6 +47,9 @@ class Receiver { this.onclose = function () {}; this.onping = function () {}; this.onpong = function () {}; + + this.buffers = []; + this.bufferedBytes = 0; } /** @@ -82,24 +60,61 @@ class Receiver { add (data) { if (this.dead) return; - const dataLength = data.length; - if (dataLength === 0) return; - if (this.expectBuffer == null) { - this.overflow.push(data); - return; + if (!this.expectBytes) return; + + this.buffers.push(data); + this.bufferedBytes += data.length; + + while (this.expectBytes && this.expectBytes <= this.bufferedBytes) { + var bufferForHandler = this.readBuffer(this.expectBytes); + this.expectBytes = 0; + this.expectHandler(bufferForHandler); } - const toRead = Math.min(dataLength, this.expectBuffer.length - this.expectOffset); - fastCopy(toRead, data, this.expectBuffer, this.expectOffset); - this.expectOffset += toRead; - if (toRead < dataLength) { - this.overflow.push(data.slice(toRead)); + } + + /** + * Consume bytes from the available buffered data. + * + * @api private + */ + + readBuffer (bytes) { + var dst; + var l; + var bufoff = 0; + + if (bytes === this.buffers[0].length) { + this.bufferedBytes -= bytes; + return this.buffers.shift(); } - while (this.expectBuffer && this.expectOffset === this.expectBuffer.length) { - const bufferForHandler = this.expectBuffer; - this.expectBuffer = null; - this.expectOffset = 0; - this.expectHandler(bufferForHandler); + + if (bytes < this.buffers[0].length) { + dst = this.buffers[0].slice(0, bytes); + this.buffers[0] = this.buffers[0].slice(bytes); + this.bufferedBytes -= bytes; + return dst; + } + + dst = new Buffer(bytes); + + while (bytes > 0) { + l = this.buffers[0].length; + + if (bytes > l) { + this.buffers[0].copy(dst, bufoff); + bufoff += l; + this.buffers.shift(); + this.bufferedBytes -= l; + } else { + this.buffers[0].copy(dst, bufoff, 0, bytes); + this.buffers[0] = this.buffers[0].slice(bytes); + this.bufferedBytes -= bytes; + } + + bytes -= l; } + + return dst; } /** @@ -110,12 +125,10 @@ class Receiver { cleanup () { this.dead = true; - this.overflow = null; - this.headerBuffer = null; - this.expectBuffer = null; + this.expectBytes = 0; this.expectHandler = null; - this.unfragmentedBufferPool = null; - this.fragmentedBufferPool = null; + this.buffers = []; + this.bufferedBytes = 0; this.state = null; this.currentMessage = null; this.onerror = null; @@ -126,30 +139,6 @@ class Receiver { this.onpong = null; } - /** - * Waits for a certain amount of header bytes to be available, then fires a callback. - * - * @api private - */ - - expectHeader (length, handler) { - if (length === 0) { - handler(null); - return; - } - this.expectBuffer = this.headerBuffer.slice(this.expectOffset, this.expectOffset + length); - this.expectHandler = handler; - var toRead = length; - while (toRead > 0 && this.overflow.length > 0) { - var fromOverflow = this.overflow.pop(); - var read = Math.min(fromOverflow.length, toRead); - if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); - fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); - this.expectOffset += read; - toRead -= read; - } - } - /** * Waits for a certain amount of data bytes to be available, then fires a callback. * @@ -161,27 +150,8 @@ class Receiver { handler(null); return; } - this.expectBuffer = this.allocateFromPool(length, this.state.fragmentedOperation); + this.expectBytes = length; this.expectHandler = handler; - var toRead = length; - while (toRead > 0 && this.overflow.length > 0) { - var fromOverflow = this.overflow.pop(); - var read = Math.min(fromOverflow.length, toRead); - if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); - fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); - this.expectOffset += read; - toRead -= read; - } - } - - /** - * Allocates memory from the buffer pool. - * - * @api private - */ - - allocateFromPool (length, isFragmented) { - return (isFragmented ? this.fragmentedBufferPool : this.unfragmentedBufferPool).get(length); } /** @@ -252,10 +222,7 @@ class Receiver { endPacket () { if (this.dead) return; - if (!this.state.fragmentedOperation) this.unfragmentedBufferPool.reset(true); - else if (this.state.lastFragment) this.fragmentedBufferPool.reset(true); - this.expectOffset = 0; - this.expectBuffer = null; + this.expectBytes = 0; this.expectHandler = null; if (this.state.lastFragment && this.state.opcode === this.state.activeFragmentedOperation) { // end current fragmented operation @@ -265,7 +232,7 @@ class Receiver { this.state.lastFragment = false; this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0; this.state.masked = false; - this.expectHeader(2, this.processPacket); + this.expectData(2, this.processPacket); } /** @@ -283,12 +250,10 @@ class Receiver { opcode: 0, fragmentedOperation: false }; - this.fragmentedBufferPool.reset(true); - this.unfragmentedBufferPool.reset(true); - this.expectOffset = 0; - this.expectBuffer = null; + this.expectBytes = 0; this.expectHandler = null; - this.overflow = []; + this.buffers = []; + this.bufferedBytes = 0; this.currentMessage = []; this.currentMessageLength = 0; this.messageHandlers = []; @@ -388,30 +353,6 @@ module.exports = Receiver; * Buffer utilities */ -function fastCopy (length, srcBuffer, dstBuffer, dstOffset) { - /* eslint-disable no-fallthrough */ - switch (length) { - default: srcBuffer.copy(dstBuffer, dstOffset, 0, length); break; - case 16: dstBuffer[dstOffset + 15] = srcBuffer[15]; - case 15: dstBuffer[dstOffset + 14] = srcBuffer[14]; - case 14: dstBuffer[dstOffset + 13] = srcBuffer[13]; - case 13: dstBuffer[dstOffset + 12] = srcBuffer[12]; - case 12: dstBuffer[dstOffset + 11] = srcBuffer[11]; - case 11: dstBuffer[dstOffset + 10] = srcBuffer[10]; - case 10: dstBuffer[dstOffset + 9] = srcBuffer[9]; - case 9: dstBuffer[dstOffset + 8] = srcBuffer[8]; - case 8: dstBuffer[dstOffset + 7] = srcBuffer[7]; - case 7: dstBuffer[dstOffset + 6] = srcBuffer[6]; - case 6: dstBuffer[dstOffset + 5] = srcBuffer[5]; - case 5: dstBuffer[dstOffset + 4] = srcBuffer[4]; - case 4: dstBuffer[dstOffset + 3] = srcBuffer[3]; - case 3: dstBuffer[dstOffset + 2] = srcBuffer[2]; - case 2: dstBuffer[dstOffset + 1] = srcBuffer[1]; - case 1: dstBuffer[dstOffset] = srcBuffer[0]; - } - /* eslint-enable no-fallthrough */ -} - function clone (obj) { return Object.assign({}, obj); } @@ -430,13 +371,13 @@ const opcodes = { if (receiver.maxPayloadExceeded(firstLength)) return; opcodes['1'].getData(receiver, firstLength); } else if (firstLength === 126) { - receiver.expectHeader(2, (data) => { + receiver.expectData(2, (data) => { const length = data.readUInt16BE(0, true); if (receiver.maxPayloadExceeded(length)) return; opcodes['1'].getData(receiver, length); }); } else if (firstLength === 127) { - receiver.expectHeader(8, (data) => { + receiver.expectData(8, (data) => { if (data.readUInt32BE(0, true) !== 0) { receiver.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; @@ -449,7 +390,7 @@ const opcodes = { }, getData: (receiver, length) => { if (receiver.state.masked) { - receiver.expectHeader(4, (mask) => { + receiver.expectData(4, (mask) => { receiver.expectData(length, (data) => opcodes['1'].finish(receiver, mask, data)); }); } else { @@ -509,13 +450,13 @@ const opcodes = { if (receiver.maxPayloadExceeded(firstLength)) return; opcodes['2'].getData(receiver, firstLength); } else if (firstLength === 126) { - receiver.expectHeader(2, (data) => { + receiver.expectData(2, (data) => { const length = data.readUInt16BE(0, true); if (receiver.maxPayloadExceeded(length)) return; opcodes['2'].getData(receiver, length); }); } else if (firstLength === 127) { - receiver.expectHeader(8, (data) => { + receiver.expectData(8, (data) => { if (data.readUInt32BE(0, true) !== 0) { receiver.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); return; @@ -528,7 +469,7 @@ const opcodes = { }, getData: (receiver, length) => { if (receiver.state.masked) { - receiver.expectHeader(4, (mask) => { + receiver.expectData(4, (mask) => { receiver.expectData(length, (data) => opcodes['2'].finish(receiver, mask, data)); }); } else { @@ -593,7 +534,7 @@ const opcodes = { }, getData: (receiver, length) => { if (receiver.state.masked) { - receiver.expectHeader(4, (mask) => { + receiver.expectData(4, (mask) => { receiver.expectData(length, (data) => opcodes['8'].finish(receiver, mask, data)); }); } else { @@ -646,7 +587,7 @@ const opcodes = { }, getData: (receiver, length) => { if (receiver.state.masked) { - receiver.expectHeader(4, (mask) => { + receiver.expectData(4, (mask) => { receiver.expectData(length, (data) => opcodes['9'].finish(receiver, mask, data)); }); } else { @@ -682,7 +623,7 @@ const opcodes = { }, getData: (receiver, length) => { if (receiver.state.masked) { - receiver.expectHeader(4, (mask) => { + receiver.expectData(4, (mask) => { receiver.expectData(length, (data) => opcodes['10'].finish(receiver, mask, data)); }); } else { diff --git a/test/BufferPool.test.js b/test/BufferPool.test.js deleted file mode 100644 index ccd087ecb..000000000 --- a/test/BufferPool.test.js +++ /dev/null @@ -1,72 +0,0 @@ -var BufferPool = require('../lib/BufferPool'); -require('should'); - -describe('BufferPool', function() { - describe('#ctor', function() { - it('allocates pool', function() { - var db = new BufferPool(1000); - db.size.should.eql(1000); - }); - it('throws TypeError when called without new', function(done) { - try { - var db = BufferPool(1000); - } - catch (e) { - e.should.be.instanceof(TypeError); - done(); - } - }); - }); - describe('#get', function() { - it('grows the pool if necessary', function() { - var db = new BufferPool(1000); - var buf = db.get(2000); - db.size.should.be.above(1000); - db.used.should.eql(2000); - buf.length.should.eql(2000); - }); - it('grows the pool after the first call, if necessary', function() { - var db = new BufferPool(1000); - var buf = db.get(1000); - db.used.should.eql(1000); - db.size.should.eql(1000); - buf.length.should.eql(1000); - var buf2 = db.get(1000); - db.used.should.eql(2000); - db.size.should.be.above(1000); - buf2.length.should.eql(1000); - }); - it('grows the pool according to the growStrategy if necessary', function() { - var db = new BufferPool(1000, function(db, length) { - return db.size + 2345; - }); - var buf = db.get(2000); - db.size.should.eql(3345); - buf.length.should.eql(2000); - }); - it('doesnt grow the pool if theres enough room available', function() { - var db = new BufferPool(1000); - var buf = db.get(1000); - db.size.should.eql(1000); - buf.length.should.eql(1000); - }); - }); - describe('#reset', function() { - it('shinks the pool', function() { - var db = new BufferPool(1000); - var buf = db.get(2000); - db.reset(true); - db.size.should.eql(1000); - }); - it('shrinks the pool according to the shrinkStrategy', function() { - var db = new BufferPool(1000, function(db, length) { - return db.used + length; - }, function(db) { - return 0; - }); - var buf = db.get(2000); - db.reset(true); - db.size.should.eql(0); - }); - }); -}); From a432e95fe954ab6eb198cfa3a7dbcab2dcb42709 Mon Sep 17 00:00:00 2001 From: i8alery <8alery.gmail.com> Date: Mon, 12 Sep 2016 13:53:50 +0300 Subject: [PATCH 104/669] [fix] Add leading slash to requestOptions.path if missing Fixes #821 --- lib/WebSocket.js | 10 +++++++++- test/WebSocket.test.js | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 286925797..9d7c332c3 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -624,6 +624,7 @@ function initAsClient (address, protocols, options) { var requestOptions = { port: port, host: serverUrl.hostname, + path: '/', headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', @@ -685,7 +686,14 @@ function initAsClient (address, protocols, options) { } } - requestOptions.path = serverUrl.path || '/'; + // make sure that path starts with `/` + if (serverUrl.path) { + if (serverUrl.path.charAt(0) !== '/') { + requestOptions.path = `/${serverUrl.path}`; + } else { + requestOptions.path = serverUrl.path; + } + } if (agent) { requestOptions.agent = agent; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index f1c48a0af..65c8e80e7 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -494,6 +494,26 @@ describe('WebSocket', function() { }); }); + describe('connection with query string', function () { + it('connects when pathname is not null', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { + var ws = new WebSocket(`ws://localhost:${port}/?token=qwerty`); + ws.on('open', function () { + wss.close(done); + }); + }); + }); + + it('connects when pathname is null', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { + var ws = new WebSocket(`ws://localhost:${port}?token=qwerty`); + ws.on('open', function () { + wss.close(done); + }); + }); + }); + }); + describe('#pause and #resume', function() { it('pauses the underlying stream', function(done) { // this test is sort-of racecondition'y, since an unlikely slow connection From b58f688bf0fa0e87a3cef87c2f5d01bf43590668 Mon Sep 17 00:00:00 2001 From: SEAPUNK Date: Fri, 15 Apr 2016 12:26:58 -0500 Subject: [PATCH 105/669] [fix] Allow close codes 1012 and 1013 IANA has them assigned as "Service Restart", and "Try Again Later", respectively. http://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent --- lib/ErrorCodes.js | 6 ++++-- test/WebSocket.test.js | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/ErrorCodes.js b/lib/ErrorCodes.js index dfd9d8644..f51557162 100644 --- a/lib/ErrorCodes.js +++ b/lib/ErrorCodes.js @@ -8,7 +8,7 @@ module.exports = { isValidErrorCode: function (code) { - return (code >= 1000 && code <= 1011 && code !== 1004 && code !== 1005 && code !== 1006) || + return (code >= 1000 && code <= 1013 && code !== 1004 && code !== 1005 && code !== 1006) || (code >= 3000 && code <= 4999); }, 1000: 'normal', @@ -22,5 +22,7 @@ module.exports = { 1008: 'policy violation', 1009: 'message too big', 1010: 'extension handshake missing', - 1011: 'an unexpected condition prevented the request from being fulfilled' + 1011: 'an unexpected condition prevented the request from being fulfilled', + 1012: 'service restart', + 1013: 'try again later' }; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 65c8e80e7..f6937833d 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1538,6 +1538,20 @@ describe('WebSocket', function() { }); }); }); + + it('allows close code 1013', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { + var ws = new WebSocket(`ws://localhost:${port}`); + ws.on('close', function (code) { + assert.strictEqual(code, 1013); + wss.close(done); + }); + }); + + wss.on('connection', function (ws) { + ws.close(1013); + }); + }); }); describe('W3C API emulation', function() { From 192a9bd2ea179cf78379a4144ed82735e987f2f6 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 16 Oct 2016 13:28:00 +0200 Subject: [PATCH 106/669] [deps] Bump eslint to version 3.8.x --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 93ae72c2b..29cf55b19 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "devDependencies": { "benchmark": "2.1.x", "bufferutil": "1.2.x", - "eslint": "3.7.x", + "eslint": "3.8.x", "eslint-config-semistandard": "7.0.x", "eslint-config-standard": "6.2.x", "eslint-plugin-promise": "3.0.x", From 0cfa5cc94d50bebd6f85b78ec1094dd9a7a17d62 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 16 Oct 2016 13:26:19 +0200 Subject: [PATCH 107/669] [minor] Use arrow functions for lexical `this` in lib/WebSocketServer.js --- lib/WebSocketServer.js | 111 ++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 63 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 38b7dd117..f65c953b0 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -49,10 +49,8 @@ function WebSocketServer (options, callback) { throw new TypeError('`port` or a `server` must be provided'); } - var self = this; - if (isDefinedAndNonNull(options, 'port')) { - this._server = http.createServer(function (req, res) { + this._server = http.createServer((req, res) => { var body = http.STATUS_CODES[426]; res.writeHead(426, { 'Content-Length': body.length, @@ -67,11 +65,7 @@ function WebSocketServer (options, callback) { } else { this._server.listen(options.port, options.host, callback); } - this._closeServer = function () { - if (self._server) { - self._server.close(); - } - }; + this._closeServer = () => this._server && this._server.close(); } else if (options.server) { this._server = options.server; if (options.path) { @@ -86,22 +80,20 @@ function WebSocketServer (options, callback) { this._server._webSocketPaths[options.path] = 1; } } + if (this._server) { - this._onceServerListening = function () { self.emit('listening'); }; + this._onceServerListening = () => this.emit('listening'); this._server.once('listening', this._onceServerListening); - } - - if (typeof this._server !== 'undefined') { - this._onServerError = function (error) { self.emit('error', error); }; + this._onServerError = (error) => this.emit('error', error); this._server.on('error', this._onServerError); - this._onServerUpgrade = function (req, socket, upgradeHead) { + this._onServerUpgrade = (req, socket, upgradeHead) => { // copy upgradeHead to avoid retention of large slab buffers used in node core var head = new Buffer(upgradeHead.length); upgradeHead.copy(head); - self.handleUpgrade(req, socket, head, function (client) { - self.emit('connection' + req.url, client); - self.emit('connection', client); + this.handleUpgrade(req, socket, head, (client) => { + this.emit(`connection${req.url}`, client); + this.emit('connection', client); }); }; this._server.on('upgrade', this._onServerUpgrade); @@ -197,7 +189,7 @@ module.exports = WebSocketServer; function handleHybiUpgrade (req, socket, upgradeHead, cb) { // handle premature socket errors - var errorHandler = function () { + var errorHandler = () => { try { socket.destroy(); } catch (e) {} }; socket.on('error', errorHandler); @@ -209,8 +201,8 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { } // verify version - var version = parseInt(req.headers['sec-websocket-version']); - if ([8, 13].indexOf(version) === -1) { + var version = +req.headers['sec-websocket-version']; + if (version !== 8 && version !== 13) { abortConnection(socket, 400, 'Bad Request'); return; } @@ -219,7 +211,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { var protocols = req.headers['sec-websocket-protocol']; // verify client - var origin = version < 13 + var origin = version !== 13 ? req.headers['sec-websocket-origin'] : req.headers['origin']; @@ -227,28 +219,26 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { var extensionsOffer = Extensions.parse(req.headers['sec-websocket-extensions']); // handler to call when the connection sequence completes - var self = this; - var completeHybiUpgrade2 = function (protocol) { + var completeHybiUpgrade2 = (protocol) => { // calc key - var key = req.headers['sec-websocket-key']; - var shasum = crypto.createHash('sha1'); - shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); - key = shasum.digest('base64'); + var key = crypto.createHash('sha1') + .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'latin1') + .digest('base64'); var headers = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', - 'Sec-WebSocket-Accept: ' + key + `Sec-WebSocket-Accept: ${key}` ]; if (typeof protocol !== 'undefined') { - headers.push('Sec-WebSocket-Protocol: ' + protocol); + headers.push(`Sec-WebSocket-Protocol: ${protocol}`); } var extensions = {}; try { - extensions = acceptExtensions.call(self, extensionsOffer); + extensions = acceptExtensions.call(this, extensionsOffer); } catch (err) { abortConnection(socket, 400, 'Bad Request'); return; @@ -256,14 +246,14 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { if (Object.keys(extensions).length) { var serverExtensions = {}; - Object.keys(extensions).forEach(function (token) { + Object.keys(extensions).forEach((token) => { serverExtensions[token] = [extensions[token].params]; }); - headers.push('Sec-WebSocket-Extensions: ' + Extensions.format(serverExtensions)); + headers.push(`Sec-WebSocket-Extensions: ${Extensions.format(serverExtensions)}`); } // allows external modification/inspection of handshake headers - self.emit('headers', headers); + this.emit('headers', headers); socket.setTimeout(0); socket.setNoDelay(true); @@ -279,14 +269,12 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { protocolVersion: version, protocol: protocol, extensions: extensions, - maxPayload: self.options.maxPayload + maxPayload: this.options.maxPayload }); - if (self.clients) { - self.clients.add(client); - client.on('close', function () { - self.clients.delete(client); - }); + if (this.clients) { + this.clients.add(client); + client.on('close', () => this.clients.delete(client)); } // signal upgrade complete @@ -296,18 +284,18 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { // optionally call external protocol selection handler before // calling completeHybiUpgrade2 - var completeHybiUpgrade1 = function () { + var completeHybiUpgrade1 = () => { // choose from the sub-protocols - if (typeof self.options.handleProtocols === 'function') { + if (typeof this.options.handleProtocols === 'function') { var protList = (protocols || '').split(/, */); var callbackCalled = false; - self.options.handleProtocols(protList, function (result, protocol) { + this.options.handleProtocols(protList, (result, protocol) => { callbackCalled = true; if (!result) abortConnection(socket, 401, 'Unauthorized'); else completeHybiUpgrade2(protocol); }); if (!callbackCalled) { - // the handleProtocols handler never called our callback + // the handleProtocols handler never called our callback abortConnection(socket, 501, 'Could not process protocols'); } return; @@ -328,7 +316,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { req: req }; if (this.options.verifyClient.length === 2) { - this.options.verifyClient(info, function (result, code, name) { + this.options.verifyClient(info, (result, code, name) => { if (typeof code === 'undefined') code = 401; if (typeof name === 'undefined') name = http.STATUS_CODES[code]; @@ -347,7 +335,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { function handleHixieUpgrade (req, socket, upgradeHead, cb) { // handle premature socket errors - var errorHandler = function () { + var errorHandler = () => { try { socket.destroy(); } catch (e) {} }; socket.on('error', errorHandler); @@ -365,10 +353,9 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { } var origin = req.headers['origin']; - var self = this; // setup handshake completion to run after client has been verified - var onClientVerified = function () { + var onClientVerified = () => { var wshost; if (!req.headers['x-forwarded-host']) { wshost = req.headers.host; @@ -381,7 +368,7 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { var protocol = req.headers['sec-websocket-protocol']; // build the response header and return a Buffer - var buildResponseHeader = function () { + var buildResponseHeader = () => { var headers = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: WebSocket', @@ -395,14 +382,14 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { }; // send handshake response before receiving the nonce - var handshakeResponse = function () { + var handshakeResponse = () => { socket.setTimeout(0); socket.setNoDelay(true); var headerBuffer = buildResponseHeader(); try { - socket.write(headerBuffer, 'binary', function (err) { + socket.write(headerBuffer, 'binary', (err) => { // remove listener if there was an error if (err) socket.removeListener('data', handler); return; @@ -414,13 +401,13 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { }; // handshake completion code to run once nonce has been successfully retrieved - var completeHandshake = function (nonce, rest, headerBuffer) { + var completeHandshake = (nonce, rest, headerBuffer) => { // calculate key var k1 = req.headers['sec-websocket-key1']; var k2 = req.headers['sec-websocket-key2']; var md5 = crypto.createHash('md5'); - [k1, k2].forEach(function (k) { + [k1, k2].forEach((k) => { var n = parseInt(k.replace(/[^\d]/g, '')); var spaces = k.replace(/[^ ]/g, '').length; if (spaces === 0 || n % spaces !== 0) { @@ -446,17 +433,15 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { hashBuffer.copy(handshakeBuffer, headerBuffer.length); // do a single write, which - upon success - causes a new client websocket to be setup - socket.write(handshakeBuffer, 'binary', function (err) { + socket.write(handshakeBuffer, 'binary', (err) => { if (err) return; // do not create client if an error happens var client = new WebSocket([req, socket, rest], { protocolVersion: 'hixie-76', protocol: protocol }); - if (self.clients) { - self.clients.add(client); - client.on('close', function () { - self.clients.delete(client); - }); + if (this.clients) { + this.clients.add(client); + client.on('close', () => this.clients.delete(client)); } // signal upgrade complete @@ -475,14 +460,14 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { if (upgradeHead && upgradeHead.length >= nonceLength) { nonce = upgradeHead.slice(0, nonceLength); rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null; - completeHandshake.call(self, nonce, rest, buildResponseHeader()); + completeHandshake(nonce, rest, buildResponseHeader()); } else { // nonce not present in upgradeHead nonce = new Buffer(nonceLength); upgradeHead.copy(nonce, 0); var received = upgradeHead.length; rest = null; - var handler = function (data) { + var handler = (data) => { var toRead = Math.min(data.length, nonceLength - received); if (toRead === 0) return; data.copy(nonce, received, 0, toRead); @@ -492,7 +477,7 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { if (toRead < data.length) rest = data.slice(toRead); // complete the handshake but send empty buffer for headers since they have already been sent - completeHandshake.call(self, nonce, rest, new Buffer(0)); + completeHandshake(nonce, rest, new Buffer(0)); } }; @@ -512,12 +497,12 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { req: req }; if (this.options.verifyClient.length === 2) { - this.options.verifyClient(info, function (result, code, name) { + this.options.verifyClient(info, (result, code, name) => { if (typeof code === 'undefined') code = 401; if (typeof name === 'undefined') name = http.STATUS_CODES[code]; if (!result) abortConnection(socket, code, name); - else onClientVerified.apply(self); + else onClientVerified(); }); return; } else if (!this.options.verifyClient(info)) { From 8c5476fe69790a7c2d014b236b8fe9561ba78711 Mon Sep 17 00:00:00 2001 From: wangxiaolei Date: Mon, 17 Oct 2016 22:55:58 +0800 Subject: [PATCH 108/669] [fix] Call `abortConnection()` when path validation fails (#534) --- lib/WebSocketServer.js | 5 ++++- test/WebSocketServer.test.js | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index f65c953b0..de4cb9095 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -168,7 +168,10 @@ WebSocketServer.prototype.handleUpgrade = function (req, socket, upgradeHead, cb // check for wrong path if (this.options.path) { var u = url.parse(req.url); - if (u && u.pathname !== this.options.path) return; + if (u && u.pathname !== this.options.path) { + abortConnection(socket, 400, 'Bad Request'); + return; + } } if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 4c42a3219..d6d333015 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -404,6 +404,26 @@ describe('WebSocketServer', function() { }); }); }); + + it('can not finish upgrade when path is not right', function(done) { + var wss = new WebSocketServer({port: ++port, path: '/ws'}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket' + }, + }; + var req = http.request(options); + req.end(); + req.on('response', function(res) { + res.statusCode.should.eql(400); + wss.close(); + done(); + }); + }); + }); }); describe('hybi mode', function() { From 41e7caef067bc9674355eae830e08057ebac9d76 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 18 Oct 2016 12:24:20 +0200 Subject: [PATCH 109/669] [major] Refactor the `abortConnection()` function --- lib/WebSocketServer.js | 103 ++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 58 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index de4cb9095..34848b3ff 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -169,14 +169,12 @@ WebSocketServer.prototype.handleUpgrade = function (req, socket, upgradeHead, cb if (this.options.path) { var u = url.parse(req.url); if (u && u.pathname !== this.options.path) { - abortConnection(socket, 400, 'Bad Request'); - return; + return abortConnection(socket, 400); } } - if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { - abortConnection(socket, 400, 'Bad Request'); - return; + if (!req.headers.upgrade || req.headers.upgrade.toLowerCase() !== 'websocket') { + return abortConnection(socket, 400); } if (req.headers['sec-websocket-key1']) handleHixieUpgrade.apply(this, arguments); @@ -199,15 +197,13 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { // verify key presence if (!req.headers['sec-websocket-key']) { - abortConnection(socket, 400, 'Bad Request'); - return; + return abortConnection(socket, 400); } // verify version var version = +req.headers['sec-websocket-version']; if (version !== 8 && version !== 13) { - abortConnection(socket, 400, 'Bad Request'); - return; + return abortConnection(socket, 400); } // verify protocol @@ -235,7 +231,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { `Sec-WebSocket-Accept: ${key}` ]; - if (typeof protocol !== 'undefined') { + if (protocol) { headers.push(`Sec-WebSocket-Protocol: ${protocol}`); } @@ -243,8 +239,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { try { extensions = acceptExtensions.call(this, extensionsOffer); } catch (err) { - abortConnection(socket, 400, 'Bad Request'); - return; + return abortConnection(socket, 400); } if (Object.keys(extensions).length) { @@ -260,6 +255,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { socket.setTimeout(0); socket.setNoDelay(true); + try { socket.write(headers.concat('', '').join('\r\n')); } catch (e) { @@ -294,42 +290,35 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { var callbackCalled = false; this.options.handleProtocols(protList, (result, protocol) => { callbackCalled = true; - if (!result) abortConnection(socket, 401, 'Unauthorized'); - else completeHybiUpgrade2(protocol); + if (!result) return abortConnection(socket, 401); + + completeHybiUpgrade2(protocol); }); if (!callbackCalled) { // the handleProtocols handler never called our callback abortConnection(socket, 501, 'Could not process protocols'); } - return; } else { - if (typeof protocols !== 'undefined') { - completeHybiUpgrade2(protocols.split(/, */)[0]); - } else { - completeHybiUpgrade2(); - } + completeHybiUpgrade2(protocols && protocols.split(/, */)[0]); } }; // optionally call external client verification handler if (typeof this.options.verifyClient === 'function') { var info = { + secure: req.connection.authorized !== undefined || req.connection.encrypted !== undefined, origin: origin, - secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined', req: req }; if (this.options.verifyClient.length === 2) { - this.options.verifyClient(info, (result, code, name) => { - if (typeof code === 'undefined') code = 401; - if (typeof name === 'undefined') name = http.STATUS_CODES[code]; + this.options.verifyClient(info, (result, code, message) => { + if (!result) return abortConnection(socket, code || 401, message); - if (!result) abortConnection(socket, code, name); - else completeHybiUpgrade1(); + completeHybiUpgrade1(); }); return; } else if (!this.options.verifyClient(info)) { - abortConnection(socket, 401, 'Unauthorized'); - return; + return abortConnection(socket, 401); } } @@ -345,14 +334,12 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { // bail if options prevent hixie if (this.options.disableHixie) { - abortConnection(socket, 401, 'Hixie support disabled'); - return; + return abortConnection(socket, 401, 'Hixie support disabled'); } // verify key presence if (!req.headers['sec-websocket-key2']) { - abortConnection(socket, 400, 'Bad Request'); - return; + return abortConnection(socket, 400); } var origin = req.headers['origin']; @@ -367,7 +354,7 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { } var proto = (req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws'; - var location = proto + '://' + wshost + req.url; + var location = `${proto}://${wshost}${req.url}`; var protocol = req.headers['sec-websocket-protocol']; // build the response header and return a Buffer @@ -378,8 +365,8 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { 'Connection: Upgrade', 'Sec-WebSocket-Location: ' + location ]; - if (typeof protocol !== 'undefined') headers.push('Sec-WebSocket-Protocol: ' + protocol); - if (typeof origin !== 'undefined') headers.push('Sec-WebSocket-Origin: ' + origin); + if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); + if (origin) headers.push(`Sec-WebSocket-Origin: ${origin}`); return new Buffer(headers.concat('', '').join('\r\n')); }; @@ -395,11 +382,9 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { socket.write(headerBuffer, 'binary', (err) => { // remove listener if there was an error if (err) socket.removeListener('data', handler); - return; }); } catch (e) { try { socket.destroy(); } catch (e) {} - return; } }; @@ -414,8 +399,7 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { var n = parseInt(k.replace(/[^\d]/g, '')); var spaces = k.replace(/[^ ]/g, '').length; if (spaces === 0 || n % spaces !== 0) { - abortConnection(socket, 400, 'Bad Request'); - return; + return abortConnection(socket, 400); } n /= spaces; md5.update(String.fromCharCode( @@ -495,22 +479,19 @@ function handleHixieUpgrade (req, socket, upgradeHead, cb) { // verify client if (typeof this.options.verifyClient === 'function') { var info = { + secure: req.connection.authorized !== undefined || req.connection.encrypted !== undefined, origin: origin, - secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined', req: req }; if (this.options.verifyClient.length === 2) { - this.options.verifyClient(info, (result, code, name) => { - if (typeof code === 'undefined') code = 401; - if (typeof name === 'undefined') name = http.STATUS_CODES[code]; + this.options.verifyClient(info, (result, code, message) => { + if (!result) return abortConnection(socket, code || 401, message); - if (!result) abortConnection(socket, code, name); - else onClientVerified(); + onClientVerified(); }); return; } else if (!this.options.verifyClient(info)) { - abortConnection(socket, 401, 'Unauthorized'); - return; + return abortConnection(socket, 401); } } @@ -530,16 +511,22 @@ function acceptExtensions (offer) { return extensions; } -function abortConnection (socket, code, name) { - try { - var response = `HTTP/1.1 ${code} ${name}\r\n` + - `Content-type: text/html\r\n` + - `\r\n\r\n`; - socket.write(response); - } catch (e) { - // ignore errors - we've aborted this connection - } finally { - // ensure that an early aborted connection is shut down completely - try { socket.destroy(); } catch (e) {} +/** + * Close the connection when preconditions are not fulfilled. + * + * @param {net.Socket} socket The socket of the upgrade request + * @param {Number} code The HTTP response status code + * @param {String} [message] The HTTP response body + * @api private + */ +function abortConnection (socket, code, message) { + if (socket.writable) { + message = message || http.STATUS_CODES[code]; + socket.write(`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n`); + socket.write('Connection: close\r\n'); + socket.write('Content-type: text/html\r\n'); + socket.write(`Content-Length: ${Buffer.byteLength(message)}\r\n\r\n`); + socket.write(message); } + socket.destroy(); } From e5a2ab4a9e2921597a835c244b7b284f6e269926 Mon Sep 17 00:00:00 2001 From: Mattijah Date: Tue, 18 Mar 2014 15:11:39 +0000 Subject: [PATCH 110/669] [minor] Remove redundant check for binary support --- lib/WebSocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 9d7c332c3..06f91baf2 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -609,7 +609,7 @@ function initAsClient (address, protocols, options) { this._isServer = false; this.url = address; this.protocolVersion = options.protocolVersion; - this.supports.binary = (this.protocolVersion !== 'hixie-76'); + this.supports.binary = true; // begin handshake var key = new Buffer(options.protocolVersion + '-' + Date.now()).toString('base64'); From df49d99ef5261fdfc7bab811580cbb6b531861f3 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Wed, 19 Oct 2016 14:44:08 +0200 Subject: [PATCH 111/669] Remove messageHandler mechanism --- lib/Receiver.js | 272 +++++++++++++++++++++--------------------------- 1 file changed, 116 insertions(+), 156 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 5e51ebda5..b01b2e58f 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -36,10 +36,8 @@ class Receiver { this.expectHandler = null; this.currentMessage = []; this.currentMessageLength = 0; - this.messageHandlers = []; this.expectData(2, this.processPacket); this.dead = false; - this.processing = false; this.onerror = function () {}; this.ontext = function () {}; @@ -60,12 +58,21 @@ class Receiver { add (data) { if (this.dead) return; - if (!this.expectBytes) return; this.buffers.push(data); this.bufferedBytes += data.length; - while (this.expectBytes && this.expectBytes <= this.bufferedBytes) { + this.process(); + } + + /** + * Check buffer for data. + * + * @api private + */ + + process () { + if (this.expectBytes && this.expectBytes <= this.bufferedBytes) { var bufferForHandler = this.readBuffer(this.expectBytes); this.expectBytes = 0; this.expectHandler(bufferForHandler); @@ -152,6 +159,8 @@ class Receiver { } this.expectBytes = length; this.expectHandler = handler; + + this.process(); } /** @@ -256,7 +265,6 @@ class Receiver { this.bufferedBytes = 0; this.currentMessage = []; this.currentMessageLength = 0; - this.messageHandlers = []; this.currentPayloadLength = 0; } @@ -283,46 +291,22 @@ class Receiver { return this; } - /** - * Execute message handler buffers - * - * @api private - */ - - flush () { - if (this.processing || this.dead) return; - - var handler = this.messageHandlers.shift(); - if (!handler) return; - - this.processing = true; - - handler(() => { - this.processing = false; - this.flush(); - }); - } - /** * Apply extensions to message * * @api private */ - applyExtensions (messageBuffer, fin, compressed, callback) { - if (compressed) { - const extension = this.extensions[PerMessageDeflate.extensionName]; - extension.decompress(messageBuffer, fin, (err, buffer) => { - if (this.dead) return; - if (err) { - callback(err.closeCode === 1009 ? err : new Error('invalid compressed data')); - return; - } - callback(null, buffer); - }); - } else { - callback(null, messageBuffer); - } + applyExtensions (messageBuffer, fin, callback) { + const extension = this.extensions[PerMessageDeflate.extensionName]; + extension.decompress(messageBuffer, fin, (err, buffer) => { + if (this.dead) return; + if (err) { + callback(err.closeCode === 1009 ? err : new Error('invalid compressed data')); + return; + } + callback(null, buffer); + }); } /** @@ -345,18 +329,70 @@ class Receiver { return true; } -} -module.exports = Receiver; + /** + * Handles compressed data. + * + * @api private + */ -/** - * Buffer utilities - */ + handleDataCompressed (packet) { + this.applyExtensions(packet, this.state.lastFragment, (err, buffer) => { + if (err) { + this.error(err, err.closeCode === 1009 ? 1009 : 1007); + return; + } + + this.handleData(buffer); + this.endPacket(); + }); + } + + /** + * Handles uncompressed data. + * + * @api private + */ -function clone (obj) { - return Object.assign({}, obj); + handleData (buffer) { + if (buffer != null) { + if (this.maxPayload === 0 || (this.maxPayload > 0 && + (this.currentMessageLength + buffer.length) < this.maxPayload)) { + this.currentMessage.push(buffer); + } else { + this.currentMessage = []; + this.currentMessageLength = 0; + this.error(new Error(`payload cannot exceed ${this.maxPayload} bytes`), 1009); + return; + } + this.currentMessageLength += buffer.length; + } + if (this.state.lastFragment) { + const messageBuffer = this.currentMessage.length === 1 + ? this.currentMessage[0] + : Buffer.concat(this.currentMessage, this.currentMessageLength); + this.currentMessage = []; + this.currentMessageLength = 0; + + if (this.state.opcode === 2) { + this.onbinary(messageBuffer, { + masked: this.state.masked + }); + } else { + if (!Validation.isValidUTF8(messageBuffer)) { + this.error(new Error('invalid utf8 sequence'), 1007); + return; + } + this.ontext(messageBuffer.toString('utf8'), { + masked: this.state.masked + }); + } + } + } } +module.exports = Receiver; + /** * Opcode handlers */ @@ -399,46 +435,12 @@ const opcodes = { }, finish: (receiver, mask, data) => { const packet = receiver.unmask(mask, data) || new Buffer(0); - const state = clone(receiver.state); - receiver.messageHandlers.push((callback) => { - receiver.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { - if (err) { - receiver.error(err, err.closeCode === 1009 ? 1009 : 1007); - return; - } - - if (buffer != null) { - if (receiver.maxPayload === 0 || (receiver.maxPayload > 0 && - (receiver.currentMessageLength + buffer.length) < receiver.maxPayload)) { - receiver.currentMessage.push(buffer); - } else { - receiver.currentMessage = []; - receiver.currentMessageLength = 0; - receiver.error(new Error(`payload cannot exceed ${receiver.maxPayload} bytes`), 1009); - return; - } - receiver.currentMessageLength += buffer.length; - } - if (state.lastFragment) { - const messageBuffer = receiver.currentMessage.length === 1 - ? receiver.currentMessage[0] - : Buffer.concat(receiver.currentMessage, receiver.currentMessageLength); - receiver.currentMessage = []; - receiver.currentMessageLength = 0; - if (!Validation.isValidUTF8(messageBuffer)) { - receiver.error(new Error('invalid utf8 sequence'), 1007); - return; - } - receiver.ontext(messageBuffer.toString('utf8'), { - masked: state.masked, - buffer: messageBuffer - }); - } - callback(); - }); - }); - receiver.flush(); - receiver.endPacket(); + if (receiver.state.compressed) { + receiver.handleDataCompressed(packet); + } else { + receiver.handleData(packet); + receiver.endPacket(); + } } }, // binary @@ -478,42 +480,12 @@ const opcodes = { }, finish: (receiver, mask, data) => { const packet = receiver.unmask(mask, data) || new Buffer(0); - const state = clone(receiver.state); - receiver.messageHandlers.push((callback) => { - receiver.applyExtensions(packet, state.lastFragment, state.compressed, (err, buffer) => { - if (err) { - receiver.error(err, err.closeCode === 1009 ? 1009 : 1007); - return; - } - - if (buffer != null) { - if (receiver.maxPayload === 0 || (receiver.maxPayload > 0 && - (receiver.currentMessageLength + buffer.length) < receiver.maxPayload)) { - receiver.currentMessage.push(buffer); - } else { - receiver.currentMessage = []; - receiver.currentMessageLength = 0; - receiver.error(new Error(`payload cannot exceed ${receiver.maxPayload} bytes`), 1009); - return; - } - receiver.currentMessageLength += buffer.length; - } - if (state.lastFragment) { - const messageBuffer = receiver.currentMessage.length === 1 - ? receiver.currentMessage[0] - : Buffer.concat(receiver.currentMessage, receiver.currentMessageLength); - receiver.currentMessage = []; - receiver.currentMessageLength = 0; - receiver.onbinary(messageBuffer, { - masked: state.masked, - buffer: messageBuffer - }); - } - callback(); - }); - }); - receiver.flush(); - receiver.endPacket(); + if (receiver.state.compressed) { + receiver.handleDataCompressed(packet); + } else { + receiver.handleData(packet); + receiver.endPacket(); + } } }, // close @@ -543,30 +515,26 @@ const opcodes = { }, finish: (receiver, mask, data) => { const packet = receiver.unmask(mask, data); - const state = clone(receiver.state); - receiver.messageHandlers.push(() => { - if (packet && packet.length === 1) { - receiver.error('close packets with data must be at least two bytes long', 1002); - return; - } - const code = packet && packet.length > 1 ? packet.readUInt16BE(0, true) : 1000; - if (!ErrorCodes.isValidErrorCode(code)) { - receiver.error('invalid error code', 1002); + if (packet && packet.length === 1) { + receiver.error('close packets with data must be at least two bytes long', 1002); + return; + } + const code = packet && packet.length > 1 ? packet.readUInt16BE(0, true) : 1000; + if (!ErrorCodes.isValidErrorCode(code)) { + receiver.error('invalid error code', 1002); + return; + } + var message = ''; + if (packet && packet.length > 2) { + const messageBuffer = packet.slice(2); + if (!Validation.isValidUTF8(messageBuffer)) { + receiver.error('invalid utf8 sequence', 1007); return; } - var message = ''; - if (packet && packet.length > 2) { - const messageBuffer = packet.slice(2); - if (!Validation.isValidUTF8(messageBuffer)) { - receiver.error('invalid utf8 sequence', 1007); - return; - } - message = messageBuffer.toString('utf8'); - } - receiver.onclose(code, message, { masked: state.masked }); - receiver.reset(); - }); - receiver.flush(); + message = messageBuffer.toString('utf8'); + } + receiver.onclose(code, message, { masked: receiver.state.masked }); + receiver.reset(); } }, // ping @@ -596,12 +564,8 @@ const opcodes = { }, finish: (receiver, mask, data) => { const packet = receiver.unmask(mask, data); - const state = clone(receiver.state); - receiver.messageHandlers.push((callback) => { - receiver.onping(packet, { masked: state.masked, binary: true }); - callback(); - }); - receiver.flush(); + const flags = { masked: receiver.state.masked, binary: true }; + receiver.onping(packet, flags); receiver.endPacket(); } }, @@ -632,12 +596,8 @@ const opcodes = { }, finish: (receiver, mask, data) => { const packet = receiver.unmask(mask, data); - const state = clone(receiver.state); - receiver.messageHandlers.push((callback) => { - receiver.onpong(packet, { masked: state.masked, binary: true }); - callback(); - }); - receiver.flush(); + const flags = { masked: receiver.state.masked, binary: true }; + receiver.onpong(packet, flags); receiver.endPacket(); } } From c4c5a0e8b8fccbd74edca8405001740558f072f7 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Wed, 19 Oct 2016 20:19:48 +0200 Subject: [PATCH 112/669] Remove applyExtensions and call decompress directly --- lib/Receiver.js | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index b01b2e58f..12ae01a4c 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -291,24 +291,6 @@ class Receiver { return this; } - /** - * Apply extensions to message - * - * @api private - */ - - applyExtensions (messageBuffer, fin, callback) { - const extension = this.extensions[PerMessageDeflate.extensionName]; - extension.decompress(messageBuffer, fin, (err, buffer) => { - if (this.dead) return; - if (err) { - callback(err.closeCode === 1009 ? err : new Error('invalid compressed data')); - return; - } - callback(null, buffer); - }); - } - /** * Checks payload size, disconnects socket when it exceeds maxPayload * @@ -337,7 +319,9 @@ class Receiver { */ handleDataCompressed (packet) { - this.applyExtensions(packet, this.state.lastFragment, (err, buffer) => { + const extension = this.extensions[PerMessageDeflate.extensionName]; + extension.decompress(packet, this.state.lastFragment, (err, buffer) => { + if (this.dead) return; if (err) { this.error(err, err.closeCode === 1009 ? 1009 : 1007); return; From faf201a1c1230e8f3b143b348d84124893b5436c Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Wed, 19 Oct 2016 20:22:49 +0200 Subject: [PATCH 113/669] Use a shared noop function --- lib/Receiver.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 12ae01a4c..f6d81bdd7 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -11,6 +11,8 @@ const ErrorCodes = require('./ErrorCodes'); const bufferUtil = require('./BufferUtil').BufferUtil; const PerMessageDeflate = require('./PerMessageDeflate'); +function noop () {} + /** * HyBi Receiver implementation */ @@ -39,12 +41,12 @@ class Receiver { this.expectData(2, this.processPacket); this.dead = false; - this.onerror = function () {}; - this.ontext = function () {}; - this.onbinary = function () {}; - this.onclose = function () {}; - this.onping = function () {}; - this.onpong = function () {}; + this.onerror = noop; + this.ontext = noop; + this.onbinary = noop; + this.onclose = noop; + this.onping = noop; + this.onpong = noop; this.buffers = []; this.bufferedBytes = 0; From c0e62781bd347342a905ee9e9ea54309ac4179df Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 20 Oct 2016 10:02:02 +0200 Subject: [PATCH 114/669] [minor] Coerce `maxPayload` to a number in `Receiver` constructor --- lib/Receiver.js | 51 ++++++++++++++---------------------------------- lib/WebSocket.js | 16 +++------------ 2 files changed, 18 insertions(+), 49 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index f6d81bdd7..d291cc0ca 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -11,12 +11,11 @@ const ErrorCodes = require('./ErrorCodes'); const bufferUtil = require('./BufferUtil').BufferUtil; const PerMessageDeflate = require('./PerMessageDeflate'); -function noop () {} +const noop = () => {}; /** - * HyBi Receiver implementation + * HyBi Receiver implementation. */ - class Receiver { constructor (extensions, maxPayload) { if (typeof extensions === 'number') { @@ -25,7 +24,7 @@ class Receiver { } this.extensions = extensions || {}; - this.maxPayload = maxPayload || 0; + this.maxPayload = maxPayload | 0; this.currentPayloadLength = 0; this.state = { activeFragmentedOperation: null, @@ -57,7 +56,6 @@ class Receiver { * * @api public */ - add (data) { if (this.dead) return; @@ -72,7 +70,6 @@ class Receiver { * * @api private */ - process () { if (this.expectBytes && this.expectBytes <= this.bufferedBytes) { var bufferForHandler = this.readBuffer(this.expectBytes); @@ -86,7 +83,6 @@ class Receiver { * * @api private */ - readBuffer (bytes) { var dst; var l; @@ -131,7 +127,6 @@ class Receiver { * * @api public */ - cleanup () { this.dead = true; this.expectBytes = 0; @@ -153,7 +148,6 @@ class Receiver { * * @api private */ - expectData (length, handler) { if (length === 0) { handler(null); @@ -170,7 +164,6 @@ class Receiver { * * @api private */ - processPacket (data) { if (this.extensions[PerMessageDeflate.extensionName]) { if ((data[0] & 0x30) !== 0) { @@ -230,7 +223,6 @@ class Receiver { * * @api private */ - endPacket () { if (this.dead) return; this.expectBytes = 0; @@ -251,7 +243,6 @@ class Receiver { * * @api private */ - reset () { if (this.dead) return; this.state = { @@ -275,18 +266,16 @@ class Receiver { * * @api private */ - unmask (mask, buf) { if (mask != null && buf != null) bufferUtil.unmask(buf, mask); return buf; } /** - * Handles an error + * Handles an error. * * @api private */ - error (err, protocolErrorCode) { this.reset(); this.onerror(err, protocolErrorCode); @@ -294,17 +283,15 @@ class Receiver { } /** - * Checks payload size, disconnects socket when it exceeds maxPayload + * Checks payload size, disconnects socket when it exceeds `maxPayload`. * * @api private */ - maxPayloadExceeded (length) { - if (this.maxPayload === undefined || this.maxPayload === null || this.maxPayload < 1) { - return false; - } + if (this.maxPayload < 1) return false; + const fullLength = this.currentPayloadLength + length; - if (fullLength < this.maxPayload) { + if (fullLength <= this.maxPayload) { this.currentPayloadLength = fullLength; return false; } @@ -319,7 +306,6 @@ class Receiver { * * @api private */ - handleDataCompressed (packet) { const extension = this.extensions[PerMessageDeflate.extensionName]; extension.decompress(packet, this.state.lastFragment, (err, buffer) => { @@ -339,11 +325,9 @@ class Receiver { * * @api private */ - handleData (buffer) { if (buffer != null) { - if (this.maxPayload === 0 || (this.maxPayload > 0 && - (this.currentMessageLength + buffer.length) < this.maxPayload)) { + if (this.maxPayload < 1 || this.currentMessageLength + buffer.length <= this.maxPayload) { this.currentMessage.push(buffer); } else { this.currentMessage = []; @@ -361,17 +345,13 @@ class Receiver { this.currentMessageLength = 0; if (this.state.opcode === 2) { - this.onbinary(messageBuffer, { - masked: this.state.masked - }); + this.onbinary(messageBuffer, { masked: this.state.masked }); } else { if (!Validation.isValidUTF8(messageBuffer)) { this.error(new Error('invalid utf8 sequence'), 1007); return; } - this.ontext(messageBuffer.toString('utf8'), { - masked: this.state.masked - }); + this.ontext(messageBuffer.toString(), { masked: this.state.masked }); } } } @@ -379,10 +359,9 @@ class Receiver { module.exports = Receiver; -/** - * Opcode handlers - */ - +// +// Opcode handlers. +// const opcodes = { // text '1': { @@ -517,7 +496,7 @@ const opcodes = { receiver.error('invalid utf8 sequence', 1007); return; } - message = messageBuffer.toString('utf8'); + message = messageBuffer.toString(); } receiver.onclose(code, message, { masked: receiver.state.masked }); receiver.reset(); diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 06f91baf2..f4a63d910 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -537,22 +537,12 @@ function buildHostHeader (isSecure, hostname, port) { * which may or may not be bound to a sepcific WebSocket instance. */ function initAsServerClient (req, socket, upgradeHead, options) { - options = Object.assign({ - protocolVersion: protocolVersion, - protocol: null, - extensions: {}, - maxPayload: 0 - }, options); - // expose state properties - this.protocol = options.protocol; - this.protocolVersion = options.protocolVersion; - this.extensions = options.extensions; - this.supports.binary = (this.protocolVersion !== 'hixie-76'); - this.upgradeReq = req; + Object.assign(this, options); + this.supports.binary = this.protocolVersion !== 'hixie-76'; this.readyState = WebSocket.CONNECTING; + this.upgradeReq = req; this._isServer = true; - this.maxPayload = options.maxPayload; // establish connection if (options.protocolVersion === 'hixie-76') { establishConnection.call(this, ReceiverHixie, SenderHixie, socket, upgradeHead); From 5f53194a3192db730c6043771a0abbdf4edd420f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 20 Oct 2016 14:08:55 +0200 Subject: [PATCH 115/669] [major] Make `extensions` a required argument for `Receiver` constructor --- lib/Receiver.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index d291cc0ca..eea740171 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -18,25 +18,20 @@ const noop = () => {}; */ class Receiver { constructor (extensions, maxPayload) { - if (typeof extensions === 'number') { - maxPayload = extensions; - extensions = {}; - } - this.extensions = extensions || {}; this.maxPayload = maxPayload | 0; - this.currentPayloadLength = 0; this.state = { activeFragmentedOperation: null, + fragmentedOperation: false, lastFragment: false, masked: false, - opcode: 0, - fragmentedOperation: false + opcode: 0 }; this.expectBytes = 0; this.expectHandler = null; this.currentMessage = []; this.currentMessageLength = 0; + this.currentPayloadLength = 0; this.expectData(2, this.processPacket); this.dead = false; From b7ae3b49218fb58fe2af2560d010f6691671efff Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 20 Oct 2016 20:10:47 +0200 Subject: [PATCH 116/669] [test] Clean up `Receiver` tests --- bench/util.js | 19 +- test/Receiver.test.js | 594 ++++++++++++++++++++++-------------------- 2 files changed, 322 insertions(+), 291 deletions(-) diff --git a/bench/util.js b/bench/util.js index 7363e8b29..47446b0de 100644 --- a/bench/util.js +++ b/bench/util.js @@ -7,12 +7,12 @@ 'use strict'; /** - * Performs hybi07+ type masking on a hex string or buffer. + * Performs hybi07+ type masking. */ function mask (buf, maskString) { const _mask = Buffer.from(maskString || '3483a868', 'hex'); - if (typeof buf === 'string') buf = Buffer.from(buf); + buf = Buffer.from(buf); for (var i = 0; i < buf.length; ++i) { buf[i] ^= _mask[i % 4]; @@ -54,4 +54,17 @@ function getHybiLengthAsHexString (len, masked) { return s; } -module.exports = { getHybiLengthAsHexString, mask, pack }; +/** + * Split a buffer in two. + */ +function splitBuffer (buf) { + const i = Math.floor(buf.length / 2); + return [buf.slice(0, i), buf.slice(i)]; +} + +module.exports = { + getHybiLengthAsHexString, + splitBuffer, + mask, + pack +}; diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 9c3343bf7..0138ddedc 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -1,423 +1,441 @@ -var assert = require('assert') - , Receiver = require('../lib/Receiver') - , PerMessageDeflate = require('../lib/PerMessageDeflate'); -require('should'); -require('./hybi-common'); - -describe('Receiver', function() { - describe('#ctor', function() { - it('throws TypeError when called without new', function(done) { - try { - var p = Receiver(); - } - catch (e) { - e.should.be.instanceof(TypeError); - done(); - } +'use strict'; + +const assert = require('assert'); +const crypto = require('crypto'); + +const PerMessageDeflate = require('../lib/PerMessageDeflate'); +const Receiver = require('../lib/Receiver'); +const util = require('../bench/util'); + +describe('Receiver', function () { + describe('#ctor', function () { + it('throws TypeError when called without new', function () { + assert.throws(Receiver, TypeError); }); }); - it('can parse unmasked text message', function() { - var p = new Receiver(); - var packet = '81 05 48 65 6c 6c 6f'; + it('can parse unmasked text message', function (done) { + const p = new Receiver(); - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal('Hello', data); + p.ontext = function (data) { + assert.strictEqual(data, 'Hello'); + done(); }; - p.add(getBufferFromHexString(packet)); - gotData.should.be.ok; + p.add(Buffer.from('810548656c6c6f', 'hex')); }); - it('can parse close message', function() { - var p = new Receiver(); - var packet = '88 00'; - var gotClose = false; - p.onclose = function(data) { - gotClose = true; + it('can parse close message', function (done) { + const p = new Receiver(); + + p.onclose = function (code, data) { + assert.strictEqual(code, 1000); + assert.strictEqual(data, ''); + done(); }; - p.add(getBufferFromHexString(packet)); - gotClose.should.be.ok; + p.add(Buffer.from('8800', 'hex')); }); - it('can parse masked text message', function() { - var p = new Receiver(); - var packet = '81 93 34 83 a8 68 01 b9 92 52 4f a1 c6 09 59 e6 8a 52 16 e6 cb 00 5b a1 d5'; - - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal('5:::{"name":"echo"}', data); + + it('can parse masked text message', function (done) { + const p = new Receiver(); + + p.ontext = function (data) { + assert.strictEqual(data, '5:::{"name":"echo"}'); + done(); }; - p.add(getBufferFromHexString(packet)); - gotData.should.be.ok; + p.add(Buffer.from('81933483a86801b992524fa1c60959e68a5216e6cb005ba1d5', 'hex')); }); - it('can parse a masked text message longer than 125 bytes', function() { - var p = new Receiver(); - var message = 'A'; - for (var i = 0; i < 300; ++i) message += (i % 5).toString(); - var packet = '81 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); - - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal(message, data); + + it('can parse a masked text message longer than 125 bytes', function (done) { + const p = new Receiver(); + const msg = 'A'.repeat(200); + + const mask = '3483a868'; + const frame = '81FE' + util.pack(4, msg.length) + mask + + util.mask(msg, mask).toString('hex'); + + p.ontext = function (data) { + assert.strictEqual(data, msg); + done(); }; - p.add(getBufferFromHexString(packet)); - gotData.should.be.ok; + p.add(Buffer.from(frame, 'hex')); }); - it('can parse a really long masked text message', function() { - var p = new Receiver(); - var message = 'A'; - for (var i = 0; i < 64*1024; ++i) message += (i % 5).toString(); - var packet = '81 FF ' + pack(16, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); - - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal(message, data); + + it('can parse a really long masked text message', function (done) { + const p = new Receiver(); + const msg = 'A'.repeat(64 * 1024); + + const mask = '3483a868'; + const frame = '81FF' + util.pack(16, msg.length) + mask + + util.mask(msg, mask).toString('hex'); + + p.ontext = function (data) { + assert.strictEqual(data, msg); + done(); }; - p.add(getBufferFromHexString(packet)); - gotData.should.be.ok; + p.add(Buffer.from(frame, 'hex')); }); - it('can parse a fragmented masked text message of 300 bytes', function() { - var p = new Receiver(); - var message = 'A'; - for (var i = 0; i < 300; ++i) message += (i % 5).toString(); - var msgpiece1 = message.substr(0, 150); - var msgpiece2 = message.substr(150); - var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68')); - var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68')); - - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal(message, data); + + it('can parse a fragmented masked text message of 300 bytes', function (done) { + const p = new Receiver(); + const msg = 'A'.repeat(300); + + const fragment1 = msg.substr(0, 150); + const fragment2 = msg.substr(150); + + const mask = '3483a868'; + const frame1 = '01FE' + util.pack(4, fragment1.length) + mask + + util.mask(fragment1, mask).toString('hex'); + const frame2 = '80FE' + util.pack(4, fragment2.length) + mask + + util.mask(fragment2, mask).toString('hex'); + + p.ontext = function (data) { + assert.strictEqual(data, msg); + done(); }; - p.add(getBufferFromHexString(packet1)); - p.add(getBufferFromHexString(packet2)); - gotData.should.be.ok; + p.add(Buffer.from(frame1, 'hex')); + p.add(Buffer.from(frame2, 'hex')); }); - it('can parse a ping message', function() { - var p = new Receiver(); - var message = 'Hello'; - var packet = '89 ' + getHybiLengthAsHexString(message.length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); - var gotPing = false; - p.onping = function(data) { - gotPing = true; - assert.equal(message, data); + it('can parse a ping message', function (done) { + const p = new Receiver(); + const msg = 'Hello'; + + const mask = '3483a868'; + const frame = '89' + util.getHybiLengthAsHexString(msg.length, true) + mask + + util.mask(msg, mask).toString('hex'); + + p.onping = function (data) { + assert.strictEqual(data.toString(), msg); + done(); }; - p.add(getBufferFromHexString(packet)); - gotPing.should.be.ok; + p.add(Buffer.from(frame, 'hex')); }); - it('can parse a ping with no data', function() { - var p = new Receiver(); - var packet = '89 00'; - var gotPing = false; - p.onping = function(data) { - gotPing = true; + it('can parse a ping with no data', function (done) { + const p = new Receiver(); + + p.onping = function (data) { + assert.strictEqual(data, null); + done(); }; - p.add(getBufferFromHexString(packet)); - gotPing.should.be.ok; + p.add(Buffer.from('8900', 'hex')); }); - it('can parse a fragmented masked text message of 300 bytes with a ping in the middle', function() { - var p = new Receiver(); - var message = 'A'; - for (var i = 0; i < 300; ++i) message += (i % 5).toString(); - var msgpiece1 = message.substr(0, 150); - var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68')); + it('can parse a fragmented masked text message of 300 bytes with a ping in the middle (1/2)', function (done) { + const p = new Receiver(); + const msg = 'A'.repeat(300); + const pingMessage = 'Hello'; - var pingMessage = 'Hello'; - var pingPacket = '89 ' + getHybiLengthAsHexString(pingMessage.length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68')); + const fragment1 = msg.substr(0, 150); + const fragment2 = msg.substr(150); + + const mask = '3483a868'; + const frame1 = '01FE' + util.pack(4, fragment1.length) + mask + + util.mask(fragment1, mask).toString('hex'); + const frame2 = '89' + util.getHybiLengthAsHexString(pingMessage.length, true) + mask + + util.mask(pingMessage, mask).toString('hex'); + const frame3 = '80FE' + util.pack(4, fragment2.length) + mask + + util.mask(fragment2, mask).toString('hex'); - var msgpiece2 = message.substr(150); - var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68')); + let gotPing = false; - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal(message, data); + p.ontext = function (data) { + assert.strictEqual(data, msg); + assert.ok(gotPing); + done(); }; - var gotPing = false; - p.onping = function(data) { + p.onping = function (data) { gotPing = true; - assert.equal(pingMessage, data); + assert.strictEqual(data.toString(), pingMessage); }; - p.add(getBufferFromHexString(packet1)); - p.add(getBufferFromHexString(pingPacket)); - p.add(getBufferFromHexString(packet2)); - gotData.should.be.ok; - gotPing.should.be.ok; + p.add(Buffer.from(frame1, 'hex')); + p.add(Buffer.from(frame2, 'hex')); + p.add(Buffer.from(frame3, 'hex')); }); - it('can parse a fragmented masked text message of 300 bytes with a ping in the middle, which is delievered over sevaral tcp packets', function() { - var p = new Receiver(); - var message = 'A'; - for (var i = 0; i < 300; ++i) message += (i % 5).toString(); - - var msgpiece1 = message.substr(0, 150); - var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68')); + it('can parse a fragmented masked text message of 300 bytes with a ping in the middle (2/2)', function (done) { + const p = new Receiver(); + const msg = 'A'.repeat(300); var pingMessage = 'Hello'; - var pingPacket = '89 ' + getHybiLengthAsHexString(pingMessage.length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68')); - var msgpiece2 = message.substr(150); - var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68')); + var fragment1 = msg.substr(0, 150); + var fragment2 = msg.substr(150); + + const mask = '3483a868'; + const frame1 = '01FE' + util.pack(4, fragment1.length) + mask + + util.mask(fragment1, mask).toString('hex'); + const frame2 = '89' + util.getHybiLengthAsHexString(pingMessage.length, true) + mask + + util.mask(pingMessage, mask).toString('hex'); + const frame3 = '80FE' + util.pack(4, fragment2.length) + mask + + util.mask(fragment2, mask).toString('hex'); + + let buffers = []; + + buffers = buffers.concat(util.splitBuffer(Buffer.from(frame1, 'hex'))); + buffers = buffers.concat(util.splitBuffer(Buffer.from(frame2, 'hex'))); + buffers = buffers.concat(util.splitBuffer(Buffer.from(frame3, 'hex'))); - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal(message, data); + let gotPing = false; + + p.ontext = function (data) { + assert.strictEqual(data, msg); + assert.ok(gotPing); + done(); }; - var gotPing = false; - p.onping = function(data) { + p.onping = function (data) { gotPing = true; - assert.equal(pingMessage, data); + assert.strictEqual(data.toString(), pingMessage); }; - var buffers = []; - buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet1))); - buffers = buffers.concat(splitBuffer(getBufferFromHexString(pingPacket))); - buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet2))); for (var i = 0; i < buffers.length; ++i) { p.add(buffers[i]); } - gotData.should.be.ok; - gotPing.should.be.ok; }); - it('can parse a 100 byte long masked binary message', function() { - var p = new Receiver(); - var length = 100; - var message = new Buffer(length); - for (var i = 0; i < length; ++i) message[i] = i % 256; - var originalMessage = getHexStringFromBuffer(message); - var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); - - var gotData = false; - p.onbinary = function(data) { - gotData = true; - assert.equal(originalMessage, getHexStringFromBuffer(data)); + + it('can parse a 100 byte long masked binary message', function (done) { + const p = new Receiver(); + const msg = crypto.randomBytes(100); + + const mask = '3483a868'; + const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + + util.mask(msg, mask).toString('hex'); + + p.onbinary = function (data) { + assert.deepStrictEqual(data.toString('hex'), msg.toString('hex')); + done(); }; - p.add(getBufferFromHexString(packet)); - gotData.should.be.ok; + p.add(Buffer.from(frame, 'hex')); }); - it('can parse a 256 byte long masked binary message', function() { - var p = new Receiver(); - var length = 256; - var message = new Buffer(length); - for (var i = 0; i < length; ++i) message[i] = i % 256; - var originalMessage = getHexStringFromBuffer(message); - var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); - - var gotData = false; - p.onbinary = function(data) { - gotData = true; - assert.equal(originalMessage, getHexStringFromBuffer(data)); + + it('can parse a 256 byte long masked binary message', function (done) { + const p = new Receiver(); + const msg = crypto.randomBytes(256); + + const mask = '3483a868'; + const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + + util.mask(msg, mask).toString('hex'); + + p.onbinary = function (data) { + assert.deepStrictEqual(data, msg); + done(); }; - p.add(getBufferFromHexString(packet)); - gotData.should.be.ok; + p.add(Buffer.from(frame, 'hex')); }); - it('can parse a 200kb long masked binary message', function() { - var p = new Receiver(); - var length = 200 * 1024; - var message = new Buffer(length); - for (var i = 0; i < length; ++i) message[i] = i % 256; - var originalMessage = getHexStringFromBuffer(message); - var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); - - var gotData = false; - p.onbinary = function(data) { - gotData = true; - assert.equal(originalMessage, getHexStringFromBuffer(data)); + + it('can parse a 200kb long masked binary message', function (done) { + const p = new Receiver(); + const msg = crypto.randomBytes(200 * 1024); + + const mask = '3483a868'; + const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + + util.mask(msg, mask).toString('hex'); + + p.onbinary = function (data) { + assert.deepStrictEqual(data, msg); + done(); }; - p.add(getBufferFromHexString(packet)); - gotData.should.be.ok; + p.add(Buffer.from(frame, 'hex')); }); - it('can parse a 200kb long unmasked binary message', function() { - var p = new Receiver(); - var length = 200 * 1024; - var message = new Buffer(length); - for (var i = 0; i < length; ++i) message[i] = i % 256; - var originalMessage = getHexStringFromBuffer(message); - var packet = '82 ' + getHybiLengthAsHexString(length, false) + ' ' + getHexStringFromBuffer(message); - - var gotData = false; - p.onbinary = function(data) { - gotData = true; - assert.equal(originalMessage, getHexStringFromBuffer(data)); + + it('can parse a 200kb long unmasked binary message', function (done) { + const p = new Receiver(); + const msg = crypto.randomBytes(200 * 1024); + + const frame = '82' + util.getHybiLengthAsHexString(msg.length, false) + + msg.toString('hex'); + + p.onbinary = function (data) { + assert.deepStrictEqual(data, msg); + done(); }; - p.add(getBufferFromHexString(packet)); - gotData.should.be.ok; + p.add(Buffer.from(frame, 'hex')); }); - it('can parse compressed message', function(done) { - var perMessageDeflate = new PerMessageDeflate(); + + it('can parse compressed message', function (done) { + const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - var p = new Receiver({ 'permessage-deflate': perMessageDeflate }); - var buf = new Buffer('Hello'); + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const buf = Buffer.from('Hello'); - p.ontext = function(data) { - assert.equal('Hello', data); + p.ontext = function (data) { + assert.strictEqual(data, 'Hello'); done(); }; - perMessageDeflate.compress(buf, true, function(err, compressed) { + perMessageDeflate.compress(buf, true, function (err, compressed) { if (err) return done(err); - p.add(new Buffer([0xc1, compressed.length])); + + p.add(Buffer.from([0xc1, compressed.length])); p.add(compressed); }); }); - it('can parse compressed fragments', function(done) { - var perMessageDeflate = new PerMessageDeflate(); + + it('can parse compressed fragments', function (done) { + const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - var p = new Receiver({ 'permessage-deflate': perMessageDeflate }); - var buf1 = new Buffer('foo'); - var buf2 = new Buffer('bar'); + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const buf1 = Buffer.from('foo'); + const buf2 = Buffer.from('bar'); - p.ontext = function(data) { - assert.equal('foobar', data); + p.ontext = function (data) { + assert.strictEqual(data, 'foobar'); done(); }; - perMessageDeflate.compress(buf1, false, function(err, compressed1) { + perMessageDeflate.compress(buf1, false, function (err, compressed1) { if (err) return done(err); - p.add(new Buffer([0x41, compressed1.length])); + + p.add(Buffer.from([0x41, compressed1.length])); p.add(compressed1); - perMessageDeflate.compress(buf2, true, function(err, compressed2) { - p.add(new Buffer([0x80, compressed2.length])); + perMessageDeflate.compress(buf2, true, function (err, compressed2) { + if (err) return done(err); + + p.add(Buffer.from([0x80, compressed2.length])); p.add(compressed2); }); }); }); - it('will raise an error on a 200kb long masked binary message when maxpayload is 20kb', function() { - var p = new Receiver(20480); - var length = 200 * 1024; - var message = new Buffer(length); - for (var i = 0; i < length; ++i) message[i] = i % 256; - var originalMessage = getHexStringFromBuffer(message); - var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); - - var gotError = false; - p.error = function(reason,code) { - gotError = true; - assert.equal(code, 1009); + + it('will raise an error on a 200kb long masked binary message when maxpayload is 20kb', function (done) { + const p = new Receiver({}, 20 * 1024); + const msg = crypto.randomBytes(200 * 1024); + + const mask = '3483a868'; + const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + + util.mask(msg, mask).toString('hex'); + + p.error = function (reason, code) { + assert.strictEqual(code, 1009); + done(); }; - p.add(getBufferFromHexString(packet)); - gotError.should.be.ok; + p.add(Buffer.from(frame, 'hex')); }); - it('will raise an error on a 200kb long unmasked binary message when maxpayload is 20kb', function() { - var p = new Receiver(20480); - var length = 200 * 1024; - var message = new Buffer(length); - for (var i = 0; i < length; ++i) message[i] = i % 256; - var originalMessage = getHexStringFromBuffer(message); - var packet = '82 ' + getHybiLengthAsHexString(length, false) + ' ' + getHexStringFromBuffer(message); - - var gotError = false; - p.error = function(reason,code) { - gotError = true; - assert.equal(code, 1009); + + it('will raise an error on a 200kb long unmasked binary message when maxpayload is 20kb', function (done) { + const p = new Receiver({}, 20 * 1024); + const msg = crypto.randomBytes(200 * 1024); + + const frame = '82' + util.getHybiLengthAsHexString(msg.length, false) + + msg.toString('hex'); + + p.error = function (reason, code) { + assert.strictEqual(code, 1009); + done(); }; - p.add(getBufferFromHexString(packet)); - gotError.should.be.ok; + p.add(Buffer.from(frame, 'hex')); }); - it('will raise an error on a compressed message that exceeds maxpayload of 3bytes', function(done) { - var perMessageDeflate = new PerMessageDeflate({},false,3); + + it('will raise an error on a compressed message that exceeds maxpayload of 3 bytes', function (done) { + const perMessageDeflate = new PerMessageDeflate({}, false, 3); perMessageDeflate.accept([{}]); - var p = new Receiver({ 'permessage-deflate': perMessageDeflate },3); - var buf = new Buffer('Hellooooooooooooooooooooooooooooooooooooooo'); + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 3); + const buf = Buffer.from('Hellooooooooooooooooooooooooooooooooooooooo'); - p.onerror = function(reason,code) { - assert.equal(code, 1009); + p.onerror = function (reason, code) { + assert.strictEqual(code, 1009); done(); }; - perMessageDeflate.compress(buf, true, function(err, compressed) { + perMessageDeflate.compress(buf, true, function (err, compressed) { if (err) return done(err); - p.add(new Buffer([0xc1, compressed.length])); + + p.add(Buffer.from([0xc1, compressed.length])); p.add(compressed); }); }); - it('will raise an error on a compressed fragment that exceeds maxpayload of 2 bytes', function(done) { - var perMessageDeflate = new PerMessageDeflate({},false,2); + + it('will raise an error on a compressed fragment that exceeds maxpayload of 2 bytes', function (done) { + const perMessageDeflate = new PerMessageDeflate({}, false, 2); perMessageDeflate.accept([{}]); - var p = new Receiver({ 'permessage-deflate': perMessageDeflate },2); - var buf1 = new Buffer('fooooooooooooooooooooooooooooooooooooooooooooooooooooooo'); - var buf2 = new Buffer('baaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr'); + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 2); + const buf1 = Buffer.from('foooooooooooooooooooooooooooooooooooooooooooooo'); + const buf2 = Buffer.from('baaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr'); - p.onerror = function(reason,code) { - assert.equal(code, 1009); + p.onerror = function (reason, code) { + assert.strictEqual(code, 1009); done(); }; - perMessageDeflate.compress(buf1, false, function(err, compressed1) { + perMessageDeflate.compress(buf1, false, function (err, compressed1) { if (err) return done(err); - p.add(new Buffer([0x41, compressed1.length])); + + p.add(Buffer.from([0x41, compressed1.length])); p.add(compressed1); - perMessageDeflate.compress(buf2, true, function(err, compressed2) { - p.add(new Buffer([0x80, compressed2.length])); + perMessageDeflate.compress(buf2, true, function (err, compressed2) { + if (err) return done(err); + + p.add(Buffer.from([0x80, compressed2.length])); p.add(compressed2); }); }); }); - it('will not crash if another message is received after receiving a message that exceeds maxpayload', function(done) { - var perMessageDeflate = new PerMessageDeflate({},false,2); + + it('will not crash if another message is received after receiving a message that exceeds maxpayload', function (done) { + const perMessageDeflate = new PerMessageDeflate({}, false, 2); perMessageDeflate.accept([{}]); - var p = new Receiver({ 'permessage-deflate': perMessageDeflate },2); - var buf1 = new Buffer('fooooooooooooooooooooooooooooooooooooooooooooooooooooooo'); - var buf2 = new Buffer('baaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr'); + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 2); + const buf1 = Buffer.from('foooooooooooooooooooooooooooooooooooooooooooooo'); + const buf2 = Buffer.from('baaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr'); + + let gotError = false; - p.onerror = function(reason,code) { - assert.equal(code, 1009); + p.onerror = function (reason, code) { + gotError = true; + assert.strictEqual(code, 1009); }; - perMessageDeflate.compress(buf1, false, function(err, compressed1) { + perMessageDeflate.compress(buf1, false, function (err, compressed1) { if (err) return done(err); - p.add(new Buffer([0x41, compressed1.length])); + + p.add(Buffer.from([0x41, compressed1.length])); p.add(compressed1); - assert.equal(p.onerror,null); + assert.ok(gotError); + assert.strictEqual(p.onerror, null); - perMessageDeflate.compress(buf2, true, function(err, compressed2) { - p.add(new Buffer([0x80, compressed2.length])); - p.add(compressed2); - done(); + perMessageDeflate.compress(buf2, true, function (err, compressed2) { + if (err) return done(err); + + p.add(Buffer.from([0x80, compressed2.length])); + p.add(compressed2); + done(); }); }); }); - it('can cleanup during consuming data', function(done) { - var perMessageDeflate = new PerMessageDeflate(); + + it('can cleanup when consuming data', function (done) { + const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - var p = new Receiver({ 'permessage-deflate': perMessageDeflate }); - var buf = new Buffer('Hello'); + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const buf = Buffer.from('Hello'); - perMessageDeflate.compress(buf, true, function(err, compressed) { + perMessageDeflate.compress(buf, true, function (err, compressed) { if (err) return done(err); - var data = Buffer.concat([new Buffer([0xc1, compressed.length]), compressed]); + + const data = Buffer.concat([Buffer.from([0xc1, compressed.length]), compressed]); p.add(data); p.add(data); p.add(data); From 78425d0c3360acd67280f33cab89c84a46d3eb0d Mon Sep 17 00:00:00 2001 From: Dirk Krause Date: Wed, 19 Oct 2016 21:56:25 +0200 Subject: [PATCH 117/669] [doc] Add broadcast to everyone else example --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 93106d7a3..1e189e3de 100644 --- a/README.md +++ b/README.md @@ -128,11 +128,21 @@ server.listen(port, function () { console.log('Listening on ' + server.address() var WebSocketServer = require('ws').Server , wss = new WebSocketServer({ port: 8080 }); +// Broadcast to all. wss.broadcast = function broadcast(data) { wss.clients.forEach(function each(client) { client.send(data); }); }; + +wss.on('connection', function connection(ws) { + ws.on('message', function message(data) { + // Broadcast to everyone else. + wss.clients.forEach(function each(client) { + if (client !== ws) client.send(data); + }); + }); +}); ``` ### Error handling best practices From ea50be7ab12ad028b41a315e924aa8c442c095d7 Mon Sep 17 00:00:00 2001 From: codingphil Date: Wed, 5 Feb 2014 11:18:32 +0100 Subject: [PATCH 118/669] [fix] Do not override the `fin` option of the `send` method --- lib/WebSocket.js | 2 +- test/WebSocket.test.js | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index f4a63d910..a0a292ff7 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -230,7 +230,7 @@ WebSocket.prototype.send = function send (data, options, cb) { } options = options || {}; - options.fin = true; + if (options.fin !== false) options.fin = true; if (typeof options.binary === 'undefined') { options.binary = (data instanceof ArrayBuffer || data instanceof Buffer || diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index f6937833d..6049b4dda 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -748,6 +748,24 @@ describe('WebSocket', function() { }); }); + it('does not override the `fin` option', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => { + ws.send('fragment', { fin: false }); + ws.send('fragment', { fin: true }); + }); + }); + + wss.on('connection', (ws) => { + ws.on('message', (msg) => { + assert.strictEqual(msg, 'fragmentfragment'); + wss.close(done); + }); + }); + }); + it('send and receive binary data as an array', function(done) { server.createServer(++port, function(srv) { var ws = new WebSocket('ws://localhost:' + port); From dbfa5bab1f4413fc484b258192b2aba338a2e2d8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 22 Oct 2016 16:02:40 +0200 Subject: [PATCH 119/669] [benchmark] Add 1 MiB test to the benchmark suite --- bench/parser.benchmark.js | 4 +++- bench/sender.benchmark.js | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index 3d795d338..bbda00293 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -29,6 +29,7 @@ const maskedTextPacket = Buffer.from('81933483a86801b992524fa1c60959e68a5216e6cb const binaryDataPacket = createBinaryPacket(125); const binaryDataPacket2 = createBinaryPacket(65535); const binaryDataPacket3 = createBinaryPacket(200 * 1024); +const binaryDataPacket4 = createBinaryPacket(1024 * 1024); var receiver = new Receiver({}, 1024 * 1024); const suite = new benchmark.Suite(); @@ -42,7 +43,8 @@ suite.add('close message', () => { suite.add('masked text message', () => receiver.add(maskedTextPacket)); suite.add('binary data (125 bytes)', () => receiver.add(binaryDataPacket)); suite.add('binary data (65535 bytes)', () => receiver.add(binaryDataPacket2)); -suite.add('binary data (200 kB)', () => receiver.add(binaryDataPacket3)); +suite.add('binary data (200 KiB)', () => receiver.add(binaryDataPacket3)); +suite.add('binary data (1 MiB)', () => receiver.add(binaryDataPacket4)); suite.on('cycle', (e) => { console.log(e.target.toString()); receiver = new Receiver(); diff --git a/bench/sender.benchmark.js b/bench/sender.benchmark.js index e0ac8d31b..11e856a33 100644 --- a/bench/sender.benchmark.js +++ b/bench/sender.benchmark.js @@ -10,14 +10,17 @@ const benchmark = require('benchmark'); const Sender = require('../').Sender; -const framePacket = Buffer.alloc(200 * 1024).fill(99); +const data1 = Buffer.alloc(200 * 1024, 99); +const data2 = Buffer.alloc(1024 * 1024, 99); const suite = new benchmark.Suite(); var sender = new Sender(); sender._socket = { write () {} }; -suite.add('frameAndSend, unmasked (200 kB)', () => sender.frameAndSend(0x2, framePacket, true, false)); -suite.add('frameAndSend, masked (200 kB)', () => sender.frameAndSend(0x2, framePacket, true, true)); +suite.add('frameAndSend, unmasked (200 KiB)', () => sender.frameAndSend(0x2, data1, true, false)); +suite.add('frameAndSend, masked (200 KiB)', () => sender.frameAndSend(0x2, data1, true, true)); +suite.add('frameAndSend, unmasked (1 MiB)', () => sender.frameAndSend(0x2, data2, true, false)); +suite.add('frameAndSend, masked (1 MiB)', () => sender.frameAndSend(0x2, data2, true, true)); suite.on('cycle', (e) => { console.log(e.target.toString()); sender = new Sender(); From c60078f4fa2a7f4558416e479017173209ae547f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 22 Oct 2016 16:43:29 +0200 Subject: [PATCH 120/669] [minor] Remove duplicated cleanup code `Receiver.prototype.reset()`, called by `Receiver.prototype.error()`, does the same cleanup. --- lib/Receiver.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index eea740171..66607f17b 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -323,14 +323,12 @@ class Receiver { handleData (buffer) { if (buffer != null) { if (this.maxPayload < 1 || this.currentMessageLength + buffer.length <= this.maxPayload) { + this.currentMessageLength += buffer.length; this.currentMessage.push(buffer); } else { - this.currentMessage = []; - this.currentMessageLength = 0; this.error(new Error(`payload cannot exceed ${this.maxPayload} bytes`), 1009); return; } - this.currentMessageLength += buffer.length; } if (this.state.lastFragment) { const messageBuffer = this.currentMessage.length === 1 From 4ffadc21af35633f634dd64a7be04fec096688a1 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Sat, 22 Oct 2016 22:25:34 +0200 Subject: [PATCH 121/669] Use a common buffer for the fixed trailing bytes in deflate --- lib/PerMessageDeflate.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 4efae7971..3b2e4d76e 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -5,6 +5,7 @@ const zlib = require('zlib'); const AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15]; const DEFAULT_WINDOW_BITS = 15; const DEFAULT_MEM_LEVEL = 8; +const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); /** * Per-message Compression Extensions implementation @@ -234,7 +235,7 @@ class PerMessageDeflate { this._inflate.on('error', onError).on('data', onData); this._inflate.write(data); if (fin) { - this._inflate.write(new Buffer([0x00, 0x00, 0xff, 0xff])); + this._inflate.write(TRAILER); } this._inflate.flush(function () { cleanup(); From 63314f7d1c9942b1e5d937f50619f635097acfee Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Sat, 22 Oct 2016 22:27:33 +0200 Subject: [PATCH 122/669] Remove unnecessary checks --- lib/PerMessageDeflate.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 3b2e4d76e..8ae51f356 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -74,7 +74,7 @@ class PerMessageDeflate { if (this._inflate.writeInProgress) { this._inflate.pendingClose = true; } else { - if (this._inflate.close) this._inflate.close(); + this._inflate.close(); this._inflate = null; } } @@ -82,7 +82,7 @@ class PerMessageDeflate { if (this._deflate.writeInProgress) { this._deflate.pendingClose = true; } else { - if (this._deflate.close) this._deflate.close(); + this._deflate.close(); this._deflate = null; } } @@ -268,7 +268,7 @@ class PerMessageDeflate { self._inflate.removeListener('data', onData); self._inflate.writeInProgress = false; if ((fin && self.params[endpoint + '_no_context_takeover']) || self._inflate.pendingClose) { - if (self._inflate.close) self._inflate.close(); + self._inflate.close(); self._inflate = null; } } @@ -322,7 +322,7 @@ class PerMessageDeflate { self._deflate.removeListener('data', onData); self._deflate.writeInProgress = false; if ((fin && self.params[endpoint + '_no_context_takeover']) || self._deflate.pendingClose) { - if (self._deflate.close) self._deflate.close(); + self._deflate.close(); self._deflate = null; } } From 6b3904b42dbd48aed2e0d8d599787cca04f05384 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Fri, 21 Oct 2016 19:45:38 +0200 Subject: [PATCH 123/669] Add threshold option to compression. --- doc/ws.md | 1 + lib/PerMessageDeflate.js | 1 + lib/Sender.js | 43 ++++++++++++++++++---------------------- test/Sender.test.js | 21 +++++++++++++++++--- test/WebSocket.test.js | 2 +- 5 files changed, 40 insertions(+), 28 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 401688806..7740866ad 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -63,6 +63,7 @@ If `handleProtocols` is not set then the handshake is accepted regardless the va * `serverMaxWindowBits` Number: The value of windowBits. * `clientMaxWindowBits` Number: The value of max windowBits to be requested to clients. * `memLevel` Number: The value of memLevel. +* `threshold` Number: Payloads smaller than this will not be compressed. Default 1024 bytes. If a property is empty then either an offered configuration or a default value is used. diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 8ae51f356..397377b91 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -18,6 +18,7 @@ class PerMessageDeflate { this._deflate = null; this.params = null; this._maxPayload = maxPayload || 0; + this.threshold = this._options.threshold === undefined ? 1024 : this._options.threshold; } /** diff --git a/lib/Sender.js b/lib/Sender.js index f68036a58..5e2535c4c 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -78,7 +78,7 @@ class Sender { */ doPing (data, options) { var mask = options && options.mask; - this.frameAndSend(0x9, data || '', true, mask); + this.frameAndSend(0x9, data ? Buffer.from(data.toString()) : null, true, mask); if (this.extensions[PerMessageDeflate.extensionName]) { this.messageHandlerCallback(); } @@ -104,7 +104,7 @@ class Sender { */ doPong (data, options) { var mask = options && options.mask; - this.frameAndSend(0xa, data || '', true, mask); + this.frameAndSend(0xa, data ? Buffer.from(data.toString()) : null, true, mask); if (this.extensions[PerMessageDeflate.extensionName]) { this.messageHandlerCallback(); } @@ -129,6 +129,17 @@ class Sender { } if (finalFragment) this.firstFragment = true; + if (data && !Buffer.isBuffer(data)) { + if ((data.buffer || data) instanceof ArrayBuffer) { + data = getBufferFromNative(data); + } else { + if (typeof data === 'number') { + data = data.toString(); + } + data = Buffer.from(data); + } + } + if (this.extensions[PerMessageDeflate.extensionName]) { this.enqueue([this.sendCompressed, [opcode, data, finalFragment, mask, compress, cb]]); } else { @@ -142,6 +153,11 @@ class Sender { * @api private */ sendCompressed (opcode, data, finalFragment, mask, compress, cb) { + if (data && data.length < this.extensions[PerMessageDeflate.extensionName].threshold) { + this.frameAndSend(opcode, data, finalFragment, mask, false, cb); + this.messageHandlerCallback(); + return; + } this.applyExtensions(data, finalFragment, this.compress, (err, data) => { if (err) { if (cb) cb(err); @@ -159,8 +175,6 @@ class Sender { * @api private */ frameAndSend (opcode, data, finalFragment, maskData, compressed, cb) { - var canModifyData = false; - if (!data) { var buff = [opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)] .concat(maskData ? [0, 0, 0, 0] : []); @@ -168,23 +182,6 @@ class Sender { return; } - if (!Buffer.isBuffer(data)) { - if ((data.buffer || data) instanceof ArrayBuffer) { - data = getBufferFromNative(data); - } else { - canModifyData = true; - // - // If people want to send a number, this would allocate the number in - // bytes as memory size instead of storing the number as buffer value. So - // we need to transform it to string in order to prevent possible - // vulnerabilities / memory attacks. - // - if (typeof data === 'number') data = data.toString(); - - data = new Buffer(data); - } - } - var dataLength = data.length; var dataOffset = maskData ? 6 : 2; var secondByte = dataLength; @@ -197,6 +194,7 @@ class Sender { secondByte = 126; } + var canModifyData = opcode === 1 || compressed; var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData); var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset; var outputBuffer = new Buffer(totalLength); @@ -276,9 +274,6 @@ class Sender { */ applyExtensions (data, fin, compress, callback) { if (compress && data) { - if ((data.buffer || data) instanceof ArrayBuffer) { - data = getBufferFromNative(data); - } this.extensions[PerMessageDeflate.extensionName].compress(data, fin, callback); } else { callback(null, data); diff --git a/test/Sender.test.js b/test/Sender.test.js index 42b23ff7d..dc82c4eb0 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -30,7 +30,7 @@ describe('Sender', function() { it('does not modify a masked text buffer', function() { var sender = new Sender({ write: function() {} }); var text = 'hi there'; - sender.frameAndSend(1, text, true, true); + sender.frameAndSend(1, Buffer.from(text), true, true); text.should.eql('hi there'); }); @@ -41,13 +41,13 @@ describe('Sender', function() { done(); } }); - sender.frameAndSend(1, 'hi', true, false, true); + sender.frameAndSend(1, Buffer.from('hi'), true, false, true); }); }); describe('#send', function() { it('compresses data if compress option is enabled', function(done) { - var perMessageDeflate = new PerMessageDeflate(); + var perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); perMessageDeflate.accept([{}]); var sender = new Sender({ @@ -61,6 +61,21 @@ describe('Sender', function() { sender.send('hi', { compress: true }); }); + it('does not compress data for small payloads', function(done) { + var perMessageDeflate = new PerMessageDeflate(); + perMessageDeflate.accept([{}]); + + var sender = new Sender({ + write: function(data) { + (data[0] & 0x40).should.not.equal(0x40); + done(); + } + }, { + 'permessage-deflate': perMessageDeflate + }); + sender.send('hi', { compress: true }); + }); + it('Should be able to handle many send calls while processing without crashing on flush', function(done) { var messageCount = 0; var maxMessages = 5000; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 6049b4dda..2c72f2eaa 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -2226,7 +2226,7 @@ describe('WebSocket', function() { describe('#terminate', function() { it('will raise error callback, if any, if called during send data', function(done) { var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function() { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); + var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: { threshold: 0 }}); var errorGiven = false; ws.on('open', function() { ws.send('hi', function(error) { From 545635d2e616fc4c0b7693de1a33be93e078f32d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 23 Oct 2016 18:29:09 +0200 Subject: [PATCH 124/669] [fix] Reset `currentPayloadLength` only on final frame --- lib/Receiver.js | 7 +++-- test/Receiver.test.js | 61 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 66607f17b..fcfed88fa 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -226,9 +226,12 @@ class Receiver { // end current fragmented operation this.state.activeFragmentedOperation = null; } - this.currentPayloadLength = 0; + if (this.state.activeFragmentedOperation !== null) { + this.state.opcode = this.state.activeFragmentedOperation; + } else { + this.currentPayloadLength = this.state.opcode = 0; + } this.state.lastFragment = false; - this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0; this.state.masked = false; this.expectData(2, this.processPacket); } diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 0138ddedc..6e548f7d2 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -48,7 +48,7 @@ describe('Receiver', function () { p.add(Buffer.from('81933483a86801b992524fa1c60959e68a5216e6cb005ba1d5', 'hex')); }); - it('can parse a masked text message longer than 125 bytes', function (done) { + it('can parse a masked text message longer than 125 B', function (done) { const p = new Receiver(); const msg = 'A'.repeat(200); @@ -80,7 +80,7 @@ describe('Receiver', function () { p.add(Buffer.from(frame, 'hex')); }); - it('can parse a fragmented masked text message of 300 bytes', function (done) { + it('can parse a fragmented masked text message of 300 B', function (done) { const p = new Receiver(); const msg = 'A'.repeat(300); @@ -129,7 +129,7 @@ describe('Receiver', function () { p.add(Buffer.from('8900', 'hex')); }); - it('can parse a fragmented masked text message of 300 bytes with a ping in the middle (1/2)', function (done) { + it('can parse a fragmented masked text message of 300 B with a ping in the middle (1/2)', function (done) { const p = new Receiver(); const msg = 'A'.repeat(300); const pingMessage = 'Hello'; @@ -162,7 +162,7 @@ describe('Receiver', function () { p.add(Buffer.from(frame3, 'hex')); }); - it('can parse a fragmented masked text message of 300 bytes with a ping in the middle (2/2)', function (done) { + it('can parse a fragmented masked text message of 300 B with a ping in the middle (2/2)', function (done) { const p = new Receiver(); const msg = 'A'.repeat(300); var pingMessage = 'Hello'; @@ -201,7 +201,7 @@ describe('Receiver', function () { } }); - it('can parse a 100 byte long masked binary message', function (done) { + it('can parse a 100 B long masked binary message', function (done) { const p = new Receiver(); const msg = crypto.randomBytes(100); @@ -217,7 +217,7 @@ describe('Receiver', function () { p.add(Buffer.from(frame, 'hex')); }); - it('can parse a 256 byte long masked binary message', function (done) { + it('can parse a 256 B long masked binary message', function (done) { const p = new Receiver(); const msg = crypto.randomBytes(256); @@ -233,7 +233,7 @@ describe('Receiver', function () { p.add(Buffer.from(frame, 'hex')); }); - it('can parse a 200kb long masked binary message', function (done) { + it('can parse a 200 KiB long masked binary message', function (done) { const p = new Receiver(); const msg = crypto.randomBytes(200 * 1024); @@ -249,7 +249,7 @@ describe('Receiver', function () { p.add(Buffer.from(frame, 'hex')); }); - it('can parse a 200kb long unmasked binary message', function (done) { + it('can parse a 200 KiB long unmasked binary message', function (done) { const p = new Receiver(); const msg = crypto.randomBytes(200 * 1024); @@ -312,7 +312,44 @@ describe('Receiver', function () { }); }); - it('will raise an error on a 200kb long masked binary message when maxpayload is 20kb', function (done) { + it('resets `currentPayloadLength` only on final frame (unfragmented)', function () { + const p = new Receiver({}, 10); + + assert.strictEqual(p.currentPayloadLength, 0); + p.add(Buffer.from('810548656c6c6f', 'hex')); + assert.strictEqual(p.currentPayloadLength, 0); + }); + + it('resets `currentPayloadLength` only on final frame (fragmented)', function () { + const p = new Receiver({}, 10); + + const frame1 = '01024865'; + const frame2 = '80036c6c6f'; + + assert.strictEqual(p.currentPayloadLength, 0); + p.add(Buffer.from(frame1, 'hex')); + assert.strictEqual(p.currentPayloadLength, 2); + p.add(Buffer.from(frame2, 'hex')); + assert.strictEqual(p.currentPayloadLength, 0); + }); + + it('resets `currentPayloadLength` only on final frame (fragmented + ping)', function () { + const p = new Receiver({}, 10); + + const frame1 = '01024865'; + const frame2 = '8900'; + const frame3 = '80036c6c6f'; + + assert.strictEqual(p.currentPayloadLength, 0); + p.add(Buffer.from(frame1, 'hex')); + assert.strictEqual(p.currentPayloadLength, 2); + p.add(Buffer.from(frame2, 'hex')); + assert.strictEqual(p.currentPayloadLength, 2); + p.add(Buffer.from(frame3, 'hex')); + assert.strictEqual(p.currentPayloadLength, 0); + }); + + it('will raise an error on a 200 KiB long masked binary message when maxpayload is 20 KiB', function (done) { const p = new Receiver({}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); @@ -328,7 +365,7 @@ describe('Receiver', function () { p.add(Buffer.from(frame, 'hex')); }); - it('will raise an error on a 200kb long unmasked binary message when maxpayload is 20kb', function (done) { + it('will raise an error on a 200 KiB long unmasked binary message when maxpayload is 20 KiB', function (done) { const p = new Receiver({}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); @@ -343,7 +380,7 @@ describe('Receiver', function () { p.add(Buffer.from(frame, 'hex')); }); - it('will raise an error on a compressed message that exceeds maxpayload of 3 bytes', function (done) { + it('will raise an error on a compressed message that exceeds maxpayload of 3 B', function (done) { const perMessageDeflate = new PerMessageDeflate({}, false, 3); perMessageDeflate.accept([{}]); @@ -363,7 +400,7 @@ describe('Receiver', function () { }); }); - it('will raise an error on a compressed fragment that exceeds maxpayload of 2 bytes', function (done) { + it('will raise an error on a compressed fragment that exceeds maxpayload of 2 B', function (done) { const perMessageDeflate = new PerMessageDeflate({}, false, 2); perMessageDeflate.accept([{}]); From 27e15ed13b144d070fe24b78e0484fb916dbe03a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 23 Oct 2016 18:33:46 +0200 Subject: [PATCH 125/669] [minor] Remove redundant code --- lib/Receiver.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index fcfed88fa..b35756836 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -220,8 +220,6 @@ class Receiver { */ endPacket () { if (this.dead) return; - this.expectBytes = 0; - this.expectHandler = null; if (this.state.lastFragment && this.state.opcode === this.state.activeFragmentedOperation) { // end current fragmented operation this.state.activeFragmentedOperation = null; From f026859be3568f880056b4e75aa6d47200526615 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 23 Oct 2016 18:41:44 +0200 Subject: [PATCH 126/669] [minor] Remove unnecessary `fragmentedOperation` flag --- lib/Receiver.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index b35756836..c5c527729 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -22,7 +22,6 @@ class Receiver { this.maxPayload = maxPayload | 0; this.state = { activeFragmentedOperation: null, - fragmentedOperation: false, lastFragment: false, masked: false, opcode: 0 @@ -181,7 +180,6 @@ class Receiver { return; } // continuation frame - this.state.fragmentedOperation = true; this.state.opcode = this.state.activeFragmentedOperation; if (!(this.state.opcode === 1 || this.state.opcode === 2)) { this.error(new Error('continuation frame cannot follow current opcode'), 1002); @@ -199,10 +197,7 @@ class Receiver { this.state.compressed = compressed; this.state.opcode = opcode; if (this.state.lastFragment === false) { - this.state.fragmentedOperation = true; this.state.activeFragmentedOperation = opcode; - } else { - this.state.fragmentedOperation = false; } } const handler = opcodes[this.state.opcode]; @@ -245,8 +240,7 @@ class Receiver { activeFragmentedOperation: null, lastFragment: false, masked: false, - opcode: 0, - fragmentedOperation: false + opcode: 0 }; this.expectBytes = 0; this.expectHandler = null; From d6717423f839d9929a6c49b37a7060ebfa6aced0 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 25 Oct 2016 08:26:12 +0200 Subject: [PATCH 127/669] [major] Drop support for Hixie-76 (#871) --- README.md | 3 - doc/ws.md | 5 +- lib/Receiver.hixie.js | 170 ---------------- lib/Sender.hixie.js | 112 ----------- lib/WebSocket.js | 20 +- lib/WebSocketServer.js | 193 +----------------- package.json | 2 - test/Receiver.hixie.test.js | 170 ---------------- test/Sender.hixie.test.js | 146 -------------- test/WebSocket.test.js | 25 +-- test/WebSocketServer.test.js | 379 +++-------------------------------- 11 files changed, 47 insertions(+), 1178 deletions(-) delete mode 100644 lib/Receiver.hixie.js delete mode 100644 lib/Sender.hixie.js delete mode 100644 test/Receiver.hixie.test.js delete mode 100644 test/Sender.hixie.test.js diff --git a/README.md b/README.md index 1e189e3de..3d7863d60 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,6 @@ for the full reports. ## Protocol support -* **Hixie draft 76** (Old and deprecated, but still in use by Safari and Opera. - Added to ws version 0.4.2, but server only. Can be disabled by setting the - `disableHixie` option to true.) * **HyBi drafts 07-12** (Use the option `protocolVersion: 8`) * **HyBi drafts 13-17** (Current default, alternatively option `protocolVersion: 13`) diff --git a/doc/ws.md b/doc/ws.md index 7740866ad..ce9f58ffd 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -14,7 +14,6 @@ This class is a WebSocket server. It is an `EventEmitter`. * `handleProtocols` Function * `path` String * `noServer` Boolean - * `disableHixie` Boolean * `clientTracking` Boolean * `perMessageDeflate` Boolean|Object * `callback` Function @@ -108,7 +107,7 @@ This class represents a WebSocket connection. It is an `EventEmitter`. * `protocol` String * `agent` Agent * `headers` Object - * `protocolVersion` Number|String + * `protocolVersion` Number -- the following only apply if `address` is a String * `host` String * `origin` String @@ -138,7 +137,7 @@ Possible states are `WebSocket.CONNECTING`, `WebSocket.OPEN`, `WebSocket.CLOSING ### websocket.protocolVersion -The WebSocket protocol version used for this connection, `8`, `13` or `hixie-76` (the latter only for server clients). +The WebSocket protocol version used for this connection, `8`, `13`. ### websocket.url diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js deleted file mode 100644 index cda7ecdb6..000000000 --- a/lib/Receiver.hixie.js +++ /dev/null @@ -1,170 +0,0 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - -'use strict'; - -/** - * State constants - */ - -const EMPTY = 0; -const BODY = 1; -const BINARYLENGTH = 2; -const BINARYBODY = 3; - -/** - * Hixie Receiver implementation - */ - -class Receiver { - constructor () { - this.state = EMPTY; - this.buffers = []; - this.messageEnd = -1; - this.spanLength = 0; - this.dead = false; - - this.onerror = function () {}; - this.ontext = function () {}; - this.onbinary = function () {}; - this.onclose = function () {}; - this.onping = function () {}; - this.onpong = function () {}; - } - - /** - * Add new data to the parser. - * - * @api public - */ - - add (data) { - var self = this; - function doAdd () { - if (self.state === EMPTY) { - if (data.length === 2 && data[0] === 0xFF && data[1] === 0x00) { - self.reset(); - self.onclose(); - return; - } - if (data[0] === 0x80) { - self.messageEnd = 0; - self.state = BINARYLENGTH; - data = data.slice(1); - } else { - if (data[0] !== 0x00) { - self.error(new Error('payload must start with 0x00 byte'), true); - return; - } - data = data.slice(1); - self.state = BODY; - } - } - if (self.state === BINARYLENGTH) { - var i = 0; - while ((i < data.length) && (data[i] & 0x80)) { - self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); - ++i; - } - if (i < data.length) { - self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); - self.state = BINARYBODY; - ++i; - } - if (i > 0) { - data = data.slice(i); - } - } - if (self.state === BINARYBODY) { - var dataleft = self.messageEnd - self.spanLength; - if (data.length >= dataleft) { - // consume the whole buffer to finish the frame - self.buffers.push(data); - self.spanLength += dataleft; - self.messageEnd = dataleft; - return self.parse(); - } - // frame's not done even if we consume it all - self.buffers.push(data); - self.spanLength += data.length; - return; - } - self.buffers.push(data); - if ((self.messageEnd = data.indexOf(0xFF)) !== -1) { - self.spanLength += self.messageEnd; - return self.parse(); - } else { - self.spanLength += data.length; - } - } - while (data) data = doAdd(); - } - - /** - * Releases all resources used by the receiver. - * - * @api public - */ - - cleanup () { - this.dead = true; - this.state = EMPTY; - this.buffers = []; - } - - /** - * Process buffered data. - * - * @api public - */ - - parse () { - var output = new Buffer(this.spanLength); - var outputIndex = 0; - for (var bi = 0, bl = this.buffers.length; bi < bl - 1; ++bi) { - var buffer = this.buffers[bi]; - buffer.copy(output, outputIndex); - outputIndex += buffer.length; - } - var lastBuffer = this.buffers[this.buffers.length - 1]; - if (this.messageEnd > 0) lastBuffer.copy(output, outputIndex, 0, this.messageEnd); - if (this.state !== BODY) --this.messageEnd; - var tail = null; - if (this.messageEnd < lastBuffer.length - 1) { - tail = lastBuffer.slice(this.messageEnd + 1); - } - this.reset(); - this.ontext(output.toString('utf8')); - return tail; - } - - /** - * Handles an error - * - * @api private - */ - - error (err, terminate) { - this.reset(); - this.onerror(err, terminate); - return this; - } - - /** - * Reset parser state - * - * @api private - */ - reset (reason) { - if (this.dead) return; - this.state = EMPTY; - this.buffers = []; - this.messageEnd = -1; - this.spanLength = 0; - } -} - -module.exports = Receiver; diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js deleted file mode 100644 index 439eb302f..000000000 --- a/lib/Sender.hixie.js +++ /dev/null @@ -1,112 +0,0 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - -'use strict'; - -const EventEmitter = require('events'); - -/** - * Hixie Sender implementation, Inherits from EventEmitter. - */ - -class Sender extends EventEmitter { - constructor (socket) { - super(); - - this.socket = socket; - this.continuationFrame = false; - this.isClosed = false; - } - - /** - * Frames and writes data. - * - * @api public - */ - send (data, options, cb) { - if (this.isClosed) return; - - var isString = typeof data === 'string'; - var length = isString ? Buffer.byteLength(data) : data.length; - var lengthbytes = (length > 127) ? 2 : 1; // assume less than 2**14 bytes - var writeStartMarker = this.continuationFrame === false; - var writeEndMarker = !options || !(typeof options.fin !== 'undefined' && !options.fin); - - var bufferLength = writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0; - bufferLength += length; - bufferLength += (writeEndMarker && !(options && options.binary)) ? 1 : 0; - - var buffer = new Buffer(bufferLength); - var offset = writeStartMarker ? 1 : 0; - - if (writeStartMarker) { - if (options && options.binary) { - buffer.write('\x80', 'binary'); - // assume length less than 2**14 bytes - if (lengthbytes > 1) { - buffer.write(String.fromCharCode(128 + length / 128), offset++, 'binary'); - } - buffer.write(String.fromCharCode(length & 0x7f), offset++, 'binary'); - } else { - buffer.write('\x00', 'binary'); - } - } - - if (isString) buffer.write(data, offset, 'utf8'); - else data.copy(buffer, offset, 0); - - if (writeEndMarker) { - if (options && options.binary) { - // sending binary, not writing end marker - } else { - buffer.write('\xff', offset + length, 'binary'); - } - this.continuationFrame = false; - } else { - this.continuationFrame = true; - } - - try { - this.socket.write(buffer, 'binary', cb); - } catch (e) { - this.emit('error', e); - } - } - - /** - * Sends a close instruction to the remote party. - * - * @api public - */ - - close (code, data, mask, cb) { - if (this.isClosed) return; - this.isClosed = true; - try { - if (this.continuationFrame) this.socket.write(new Buffer([0xff], 'binary')); - this.socket.write(new Buffer([0xff, 0x00]), 'binary', cb); - } catch (e) { - this.emit('error', e); - } - } - - /** - * Sends a ping message to the remote party. Not available for hixie. - * - * @api public - */ - - ping (data, options) {} - - /** - * Sends a pong message to the remote party. Not available for hixie. - * - * @api public - */ - pong (data, options) {} -} - -module.exports = Sender; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index a0a292ff7..e0eead112 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -15,8 +15,6 @@ const stream = require('stream'); const Ultron = require('ultron'); const Sender = require('./Sender'); const Receiver = require('./Receiver'); -const SenderHixie = require('./Sender.hixie'); -const ReceiverHixie = require('./Receiver.hixie'); const Extensions = require('./Extensions'); const PerMessageDeflate = require('./PerMessageDeflate'); const EventEmitter = require('events'); @@ -72,7 +70,7 @@ function WebSocket (address, protocols, options) { this._closeReceived = false; this.bytesReceived = 0; this.readyState = null; - this.supports = {}; + this.supports = { binary: true }; this.extensions = {}; this._binaryType = 'nodebuffer'; @@ -539,16 +537,11 @@ function buildHostHeader (isSecure, hostname, port) { function initAsServerClient (req, socket, upgradeHead, options) { // expose state properties Object.assign(this, options); - this.supports.binary = this.protocolVersion !== 'hixie-76'; this.readyState = WebSocket.CONNECTING; this.upgradeReq = req; this._isServer = true; // establish connection - if (options.protocolVersion === 'hixie-76') { - establishConnection.call(this, ReceiverHixie, SenderHixie, socket, upgradeHead); - } else { - establishConnection.call(this, Receiver, Sender, socket, upgradeHead); - } + establishConnection.call(this, socket, upgradeHead); } function initAsClient (address, protocols, options) { @@ -599,7 +592,6 @@ function initAsClient (address, protocols, options) { this._isServer = false; this.url = address; this.protocolVersion = options.protocolVersion; - this.supports.binary = true; // begin handshake var key = new Buffer(options.protocolVersion + '-' + Date.now()).toString('base64'); @@ -772,7 +764,7 @@ function initAsClient (address, protocols, options) { this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } - establishConnection.call(this, Receiver, Sender, socket, upgradeHead); + establishConnection.call(this, socket, upgradeHead); // perform cleanup on http resources req.removeAllListeners(); @@ -784,13 +776,13 @@ function initAsClient (address, protocols, options) { this.readyState = WebSocket.CONNECTING; } -function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { +function establishConnection (socket, upgradeHead) { var ultron = this._ultron = new Ultron(socket); socket.setTimeout(0); socket.setNoDelay(true); - this._receiver = new ReceiverClass(this.extensions, this.maxPayload); + this._receiver = new Receiver(this.extensions, this.maxPayload); this._socket = socket; // socket cleanup handlers @@ -847,7 +839,7 @@ function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { }; // finalize the client - this._sender = new SenderClass(socket, this.extensions); + this._sender = new Sender(socket, this.extensions); this._sender.onerror = (error) => { this.close(1002, ''); this.emit('error', error); diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 34848b3ff..e51c2e1c5 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -38,7 +38,6 @@ function WebSocketServer (options, callback) { handleProtocols: null, path: null, noServer: false, - disableHixie: false, clientTracking: true, perMessageDeflate: true, maxPayload: 100 * 1024 * 1024, @@ -173,12 +172,11 @@ WebSocketServer.prototype.handleUpgrade = function (req, socket, upgradeHead, cb } } - if (!req.headers.upgrade || req.headers.upgrade.toLowerCase() !== 'websocket') { + if (!req.headers.upgrade || req.headers.upgrade !== 'websocket') { return abortConnection(socket, 400); } - if (req.headers['sec-websocket-key1']) handleHixieUpgrade.apply(this, arguments); - else handleHybiUpgrade.apply(this, arguments); + handleHybiUpgrade.apply(this, arguments); }; module.exports = WebSocketServer; @@ -325,180 +323,6 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { completeHybiUpgrade1(); } -function handleHixieUpgrade (req, socket, upgradeHead, cb) { - // handle premature socket errors - var errorHandler = () => { - try { socket.destroy(); } catch (e) {} - }; - socket.on('error', errorHandler); - - // bail if options prevent hixie - if (this.options.disableHixie) { - return abortConnection(socket, 401, 'Hixie support disabled'); - } - - // verify key presence - if (!req.headers['sec-websocket-key2']) { - return abortConnection(socket, 400); - } - - var origin = req.headers['origin']; - - // setup handshake completion to run after client has been verified - var onClientVerified = () => { - var wshost; - if (!req.headers['x-forwarded-host']) { - wshost = req.headers.host; - } else { - wshost = req.headers['x-forwarded-host']; - } - - var proto = (req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws'; - var location = `${proto}://${wshost}${req.url}`; - var protocol = req.headers['sec-websocket-protocol']; - - // build the response header and return a Buffer - var buildResponseHeader = () => { - var headers = [ - 'HTTP/1.1 101 Switching Protocols', - 'Upgrade: WebSocket', - 'Connection: Upgrade', - 'Sec-WebSocket-Location: ' + location - ]; - if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); - if (origin) headers.push(`Sec-WebSocket-Origin: ${origin}`); - - return new Buffer(headers.concat('', '').join('\r\n')); - }; - - // send handshake response before receiving the nonce - var handshakeResponse = () => { - socket.setTimeout(0); - socket.setNoDelay(true); - - var headerBuffer = buildResponseHeader(); - - try { - socket.write(headerBuffer, 'binary', (err) => { - // remove listener if there was an error - if (err) socket.removeListener('data', handler); - }); - } catch (e) { - try { socket.destroy(); } catch (e) {} - } - }; - - // handshake completion code to run once nonce has been successfully retrieved - var completeHandshake = (nonce, rest, headerBuffer) => { - // calculate key - var k1 = req.headers['sec-websocket-key1']; - var k2 = req.headers['sec-websocket-key2']; - var md5 = crypto.createHash('md5'); - - [k1, k2].forEach((k) => { - var n = parseInt(k.replace(/[^\d]/g, '')); - var spaces = k.replace(/[^ ]/g, '').length; - if (spaces === 0 || n % spaces !== 0) { - return abortConnection(socket, 400); - } - n /= spaces; - md5.update(String.fromCharCode( - n >> 24 & 0xFF, - n >> 16 & 0xFF, - n >> 8 & 0xFF, - n & 0xFF), 'binary'); - }); - md5.update(nonce.toString('binary'), 'binary'); - - socket.setTimeout(0); - socket.setNoDelay(true); - - try { - var hashBuffer = new Buffer(md5.digest('binary'), 'binary'); - var handshakeBuffer = new Buffer(headerBuffer.length + hashBuffer.length); - headerBuffer.copy(handshakeBuffer, 0); - hashBuffer.copy(handshakeBuffer, headerBuffer.length); - - // do a single write, which - upon success - causes a new client websocket to be setup - socket.write(handshakeBuffer, 'binary', (err) => { - if (err) return; // do not create client if an error happens - var client = new WebSocket([req, socket, rest], { - protocolVersion: 'hixie-76', - protocol: protocol - }); - if (this.clients) { - this.clients.add(client); - client.on('close', () => this.clients.delete(client)); - } - - // signal upgrade complete - socket.removeListener('error', errorHandler); - cb(client); - }); - } catch (e) { - try { socket.destroy(); } catch (e) {} - return; - } - }; - - // retrieve nonce - var nonceLength = 8; - var nonce, rest; - if (upgradeHead && upgradeHead.length >= nonceLength) { - nonce = upgradeHead.slice(0, nonceLength); - rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null; - completeHandshake(nonce, rest, buildResponseHeader()); - } else { - // nonce not present in upgradeHead - nonce = new Buffer(nonceLength); - upgradeHead.copy(nonce, 0); - var received = upgradeHead.length; - rest = null; - var handler = (data) => { - var toRead = Math.min(data.length, nonceLength - received); - if (toRead === 0) return; - data.copy(nonce, received, 0, toRead); - received += toRead; - if (received === nonceLength) { - socket.removeListener('data', handler); - if (toRead < data.length) rest = data.slice(toRead); - - // complete the handshake but send empty buffer for headers since they have already been sent - completeHandshake(nonce, rest, new Buffer(0)); - } - }; - - // handle additional data as we receive it - socket.on('data', handler); - - // send header response before we have the nonce to fix haproxy buffering - handshakeResponse(); - } - }; - - // verify client - if (typeof this.options.verifyClient === 'function') { - var info = { - secure: req.connection.authorized !== undefined || req.connection.encrypted !== undefined, - origin: origin, - req: req - }; - if (this.options.verifyClient.length === 2) { - this.options.verifyClient(info, (result, code, message) => { - if (!result) return abortConnection(socket, code || 401, message); - - onClientVerified(); - }); - return; - } else if (!this.options.verifyClient(info)) { - return abortConnection(socket, 401); - } - } - - // no client verification required - onClientVerified(); -} - function acceptExtensions (offer) { var extensions = {}; var options = this.options.perMessageDeflate; @@ -522,11 +346,14 @@ function acceptExtensions (offer) { function abortConnection (socket, code, message) { if (socket.writable) { message = message || http.STATUS_CODES[code]; - socket.write(`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n`); - socket.write('Connection: close\r\n'); - socket.write('Content-type: text/html\r\n'); - socket.write(`Content-Length: ${Buffer.byteLength(message)}\r\n\r\n`); - socket.write(message); + socket.write( + `HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` + + 'Connection: close\r\n' + + 'Content-type: text/html\r\n' + + `Content-Length: ${Buffer.byteLength(message)}\r\n` + + '\r\n' + + message + ); } socket.destroy(); } diff --git a/package.json b/package.json index 29cf55b19..888898f60 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "license": "MIT", "main": "index.js", "keywords": [ - "Hixie", "HyBi", "Push", "RFC-6455", @@ -32,7 +31,6 @@ "eslint-config-standard": "6.2.x", "eslint-plugin-promise": "3.0.x", "eslint-plugin-standard": "2.0.x", - "expect.js": "0.3.x", "istanbul": "0.4.x", "mocha": "3.1.x", "should": "8.0.x", diff --git a/test/Receiver.hixie.test.js b/test/Receiver.hixie.test.js deleted file mode 100644 index 8646d7683..000000000 --- a/test/Receiver.hixie.test.js +++ /dev/null @@ -1,170 +0,0 @@ -var assert = require('assert') - , expect = require('expect.js') - , Receiver = require('../lib/Receiver.hixie'); -require('./hybi-common'); - -describe('Receiver', function() { - describe('#ctor', function() { - it('throws TypeError when called without new', function(done) { - try { - var p = Receiver(); - } - catch (e) { - e.should.be.instanceof(TypeError); - done(); - } - }); - }); - - it('can parse text message', function() { - var p = new Receiver(); - var packet = '00 48 65 6c 6c 6f ff'; - - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal('Hello', data); - }; - - p.add(getBufferFromHexString(packet)); - expect(gotData).to.equal(true); - }); - - it('can parse multiple text messages', function() { - var p = new Receiver(); - var packet = '00 48 65 6c 6c 6f ff 00 48 65 6c 6c 6f ff'; - - var gotData = false; - var messages = []; - p.ontext = function(data) { - gotData = true; - messages.push(data); - }; - - p.add(getBufferFromHexString(packet)); - expect(gotData).to.equal(true); - for (var i = 0; i < 2; ++i) { - expect(messages[i]).to.equal('Hello'); - } - }); - - it('can parse empty message', function() { - var p = new Receiver(); - var packet = '00 ff'; - - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal('', data); - }; - - p.add(getBufferFromHexString(packet)); - expect(gotData).to.equal(true); - }); - - it('can parse text messages delivered over multiple frames', function() { - var p = new Receiver(); - var packets = [ - '00 48', - '65 6c 6c', - '6f ff 00 48', - '65', - '6c 6c 6f', - 'ff' - ]; - - var gotData = false; - var messages = []; - p.ontext = function(data) { - gotData = true; - messages.push(data); - }; - - for (var i = 0; i < packets.length; ++i) { - p.add(getBufferFromHexString(packets[i])); - } - expect(gotData).to.equal(true); - for (var i = 0; i < 2; ++i) { - expect(messages[i]).to.equal('Hello'); - } - }); - - it('emits an error if a payload doesnt start with 0x00', function() { - var p = new Receiver(); - var packets = [ - '00 6c ff', - '00 6c ff ff', - 'ff 00 6c ff 00 6c ff', - '00', - '6c 6c 6f', - 'ff' - ]; - - var gotData = false; - var gotError = false; - var messages = []; - p.ontext = function(data) { - gotData = true; - messages.push(data); - }; - p.onerror = function(reason, code) { - gotError = code == true; - }; - - for (var i = 0; i < packets.length && !gotError; ++i) { - p.add(getBufferFromHexString(packets[i])); - } - expect(gotError).to.equal(true); - expect(messages[0]).to.equal('l'); - expect(messages[1]).to.equal('l'); - expect(messages.length).to.equal(2); - }); - - it('can parse close messages', function() { - var p = new Receiver(); - var packets = [ - 'ff 00' - ]; - - var gotClose = false; - var gotError = false; - p.onclose = function() { - gotClose = true; - }; - p.onerror = function(reason, code) { - gotError = code == true; - }; - - for (var i = 0; i < packets.length && !gotError; ++i) { - p.add(getBufferFromHexString(packets[i])); - } - expect(gotClose).to.equal(true); - expect(gotError).to.equal(false); - }); - - it('can parse binary messages delivered over multiple frames', function() { - var p = new Receiver(); - var packets = [ - '80 05 48', - '65 6c 6c', - '6f 80 80 05 48', - '65', - '6c 6c 6f' - ]; - - var gotData = false; - var messages = []; - p.ontext = function(data) { - gotData = true; - messages.push(data); - }; - - for (var i = 0; i < packets.length; ++i) { - p.add(getBufferFromHexString(packets[i])); - } - expect(gotData).to.equal(true); - for (var i = 0; i < 2; ++i) { - expect(messages[i]).to.equal('Hello'); - } - }); -}); diff --git a/test/Sender.hixie.test.js b/test/Sender.hixie.test.js deleted file mode 100644 index 3bf3e6474..000000000 --- a/test/Sender.hixie.test.js +++ /dev/null @@ -1,146 +0,0 @@ -var assert = require('assert') - , Sender = require('../lib/Sender.hixie'); -require('should'); -require('./hybi-common'); - -describe('Sender', function() { - describe('#ctor', function() { - it('throws TypeError when called without new', function(done) { - try { - var sender = Sender({ write: function() {} }); - } - catch (e) { - e.should.be.instanceof(TypeError); - done(); - } - }); - }); - - describe('#send', function() { - it('frames and sends a text message', function(done) { - var message = 'Hello world'; - var received; - var socket = { - write: function(data, encoding, cb) { - received = data; - process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.send(message, {}, function() { - received.toString('utf8').should.eql('\u0000' + message + '\ufffd'); - done(); - }); - }); - - it('frames and sends an empty message', function(done) { - var socket = { - write: function(data, encoding, cb) { - done(); - } - }; - var sender = new Sender(socket, {}); - sender.send('', {}, function() {}); - }); - - it('frames and sends a buffer', function(done) { - var received; - var socket = { - write: function(data, encoding, cb) { - received = data; - process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.send(new Buffer('foobar'), {}, function() { - received.toString('utf8').should.eql('\u0000foobar\ufffd'); - done(); - }); - }); - - it('frames and sends a binary message', function(done) { - var message = 'Hello world'; - var received; - var socket = { - write: function(data, encoding, cb) { - received = data; - process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.send(message, {binary: true}, function() { - received.toString('hex').should.eql( - // 0x80 0x0b H e l l o w o r l d - '800b48656c6c6f20776f726c64'); - done(); - }); - }); -/* - it('throws an exception for binary data', function(done) { - var socket = { - write: function(data, encoding, cb) { - process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.on('error', function() { - done(); - }); - sender.send(new Buffer(100), {binary: true}, function() {}); - }); -*/ - it('can fauxe stream data', function(done) { - var received = []; - var socket = { - write: function(data, encoding, cb) { - received.push(data); - process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.send(new Buffer('foobar'), { fin: false }, function() {}); - sender.send('bazbar', { fin: false }, function() {}); - sender.send(new Buffer('end'), { fin: true }, function() { - received[0].toString('utf8').should.eql('\u0000foobar'); - received[1].toString('utf8').should.eql('bazbar'); - received[2].toString('utf8').should.eql('end\ufffd'); - done(); - }); - }); - }); - - describe('#close', function() { - it('sends a hixie close frame', function(done) { - var received; - var socket = { - write: function(data, encoding, cb) { - received = data; - process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.close(null, null, null, function() { - received.toString('utf8').should.eql('\ufffd\u0000'); - done(); - }); - }); - - it('sends a message end marker if fauxe streaming has started, before hixie close frame', function(done) { - var received = []; - var socket = { - write: function(data, encoding, cb) { - received.push(data); - if (cb) process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.send(new Buffer('foobar'), { fin: false }, function() {}); - sender.close(null, null, null, function() { - received[0].toString('utf8').should.eql('\u0000foobar'); - received[1].toString('utf8').should.eql('\ufffd'); - received[2].toString('utf8').should.eql('\ufffd\u0000'); - done(); - }); - }); - }); -}); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 2c72f2eaa..212ea8cf1 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1939,7 +1939,7 @@ describe('WebSocket', function() { describe('protocol support discovery', function() { describe('#supports', function() { describe('#binary', function() { - it('returns true for hybi transport', function(done) { + it('returns true', function(done) { var wss = new WebSocketServer({port: ++port}, function() { var ws = new WebSocket('ws://localhost:' + port); }); @@ -1949,29 +1949,6 @@ describe('WebSocket', function() { done(); }); }); - - it('returns false for hixie transport', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - }); - wss.on('connection', function(client) { - assert.equal(false, client.supports.binary); - wss.close(); - done(); - }); - }); }); }); }); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index d6d333015..e8a08d80c 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -342,10 +342,10 @@ describe('WebSocketServer', function() { }); }); - describe('#maxpayload #hybiOnly', function() { + describe('#maxpayload', function() { it('maxpayload is passed on to clients,', function(done) { var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { + var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload}, function() { wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); @@ -358,7 +358,7 @@ describe('WebSocketServer', function() { }); it('maxpayload is passed on to hybi receivers', function(done) { var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { + var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload}, function() { wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); @@ -372,7 +372,7 @@ describe('WebSocketServer', function() { it('maxpayload is passed on to permessage-deflate', function(done) { var PerMessageDeflate = require('../lib/PerMessageDeflate'); var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { + var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload}, function() { wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); @@ -405,7 +405,7 @@ describe('WebSocketServer', function() { }); }); - it('can not finish upgrade when path is not right', function(done) { + it('closes the connection when path does not match', function (done) { var wss = new WebSocketServer({port: ++port, path: '/ws'}, function() { var options = { port: port, @@ -413,7 +413,7 @@ describe('WebSocketServer', function() { headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket' - }, + } }; var req = http.request(options); req.end(); @@ -424,6 +424,28 @@ describe('WebSocketServer', function() { }); }); }); + + it('closes the connection when protocol version is Hixie-76', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { + var options = { + port: port, + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'WebSocket', + 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', + 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', + 'Sec-WebSocket-Protocol': 'sample' + } + }; + var req = http.request(options); + req.on('response', function (res) { + res.statusCode.should.eql(400); + wss.close(); + done(); + }); + req.end(); + }); + }); }); describe('hybi mode', function() { @@ -968,351 +990,6 @@ describe('WebSocketServer', function() { }); }); - describe('hixie mode', function() { - it('can be disabled', function(done) { - var wss = new WebSocketServer({port: ++port, disableHixie: true}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - req.on('response', function(res) { - res.statusCode.should.eql(401); - process.nextTick(function() { - wss.close(); - done(); - }); - }); - }); - wss.on('connection', function(ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function() {}); - }); - - describe('connection establishing', function() { - it('does not accept connections with no sec-websocket-key1', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80' - } - }; - var req = http.request(options); - req.end(); - req.on('response', function(res) { - res.statusCode.should.eql(400); - wss.close(); - done(); - }); - }); - wss.on('connection', function(ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function() {}); - }); - - it('does not accept connections with no sec-websocket-key2', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.end(); - req.on('response', function(res) { - res.statusCode.should.eql(400); - wss.close(); - done(); - }); - }); - wss.on('connection', function(ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function() {}); - }); - - it('accepts connections with valid handshake', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - }); - wss.on('connection', function(ws) { - ws.terminate(); - wss.close(); - done(); - }); - wss.on('error', function() {}); - }); - - it('client can be denied', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { - return false; - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - req.on('response', function(res) { - res.statusCode.should.eql(401); - process.nextTick(function() { - wss.close(); - done(); - }); - }); - }); - wss.on('connection', function(ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function() {}); - }); - - it('client can be accepted', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { - return true; - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - }); - wss.on('connection', function(ws) { - ws.terminate(); - wss.close(); - done(); - }); - wss.on('error', function() {}); - }); - - it('verifyClient gets client origin', function(done) { - var verifyClientCalled = false; - var wss = new WebSocketServer({port: ++port, verifyClient: function(info) { - info.origin.should.eql('http://foobarbaz.com'); - verifyClientCalled = true; - return false; - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Origin': 'http://foobarbaz.com', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - req.on('response', function(res) { - verifyClientCalled.should.be.ok; - wss.close(); - done(); - }); - }); - wss.on('error', function() {}); - }); - - it('verifyClient gets original request', function(done) { - var verifyClientCalled = false; - var wss = new WebSocketServer({port: ++port, verifyClient: function(info) { - info.req.headers['sec-websocket-key1'].should.eql('3e6b263 4 17 80'); - verifyClientCalled = true; - return false; - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Origin': 'http://foobarbaz.com', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - req.on('response', function(res) { - verifyClientCalled.should.be.ok; - wss.close(); - done(); - }); - }); - wss.on('error', function() {}); - }); - - it('client can be denied asynchronously', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { - cb(false); - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Origin': 'http://foobarbaz.com', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - req.on('response', function(res) { - res.statusCode.should.eql(401); - process.nextTick(function() { - wss.close(); - done(); - }); - }); - }); - wss.on('connection', function(ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function() {}); - }); - - it('client can be denied asynchronously with custom response code', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { - cb(false, 404, 'Not Found'); - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Origin': 'http://foobarbaz.com', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - req.on('response', function(res) { - res.statusCode.should.eql(404); - process.nextTick(function() { - wss.close(); - done(); - }); - }); - }); - wss.on('connection', function(ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function() {}); - }); - - it('client can be accepted asynchronously', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { - cb(true); - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Origin': 'http://foobarbaz.com', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - }); - wss.on('connection', function(ws) { - wss.close(); - done(); - }); - wss.on('error', function() {}); - }); - - it('handles messages passed along with the upgrade request (upgrade head)', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { - return true; - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90', - 'Origin': 'http://foobar.com' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.write(new Buffer([0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff], 'binary')); - req.end(); - }); - wss.on('connection', function(ws) { - ws.on('message', function(data) { - data.should.eql('Hello'); - ws.terminate(); - wss.close(); - done(); - }); - }); - wss.on('error', function() {}); - }); - }); - }); - describe('client properties', function() { it('protocol is exposed', function(done) { var wss = new WebSocketServer({port: ++port}, function() { From 2b74c1f9fc73244ce7f9457a3c494f160253de00 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 25 Oct 2016 08:46:40 +0200 Subject: [PATCH 128/669] [doc] Clarify how classes are exported Fixes #828 --- doc/ws.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index ce9f58ffd..26137b9b9 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -1,10 +1,10 @@ # ws -## Class: ws.Server +## Class: WebSocket.Server This class is a WebSocket server. It is an `EventEmitter`. -### new ws.Server([options], [callback]) +### new WebSocket.Server([options], [callback]) * `options` Object * `host` String @@ -74,7 +74,7 @@ Close the server and terminate all clients, calls callback when done with an err Handles a HTTP Upgrade request. `request` is an instance of `http.ServerRequest`, `socket` is an instance of `net.Socket`. -When the Upgrade was successfully, the `callback` will be called with a `ws.WebSocket` object as parameter. +When the Upgrade was successfully, the `callback` will be called with a `WebSocket` object as parameter. ### Event: 'error' @@ -92,14 +92,14 @@ Emitted with the object of HTTP headers that are going to be written to the `Str `function (socket) { }` -When a new WebSocket connection is established. `socket` is an object of type `ws.WebSocket`. +When a new WebSocket connection is established. `socket` is an object of type `WebSocket`. -## Class: ws.WebSocket +## Class: WebSocket This class represents a WebSocket connection. It is an `EventEmitter`. -### new ws.WebSocket(address, [protocols], [options]) +### new WebSocket(address, [protocols], [options]) * `address` String * `protocols` String|Array @@ -121,11 +121,11 @@ This class represents a WebSocket connection. It is an `EventEmitter`. * `perMessageDeflate` Boolean|Object * `localAddress` String -Instantiating with an `address` creates a new WebSocket client object. If `address` is an Array (request, socket, rest), it is instantiated as a Server client (e.g. called from the `ws.Server`). +Instantiating with an `address` creates a new WebSocket client object. If `address` is an Array (request, socket, rest), it is instantiated as a Server client (e.g. called from the `WebSocket.Server`). ### options.perMessageDeflate -Parameters of permessage-deflate extension which have the same form with the one for `ws.Server` except the direction of requests. (e.g. `serverNoContextTakeover` is the value to be requested to the server) +Parameters of permessage-deflate extension which have the same form with the one for `WebSocket.Server` except the direction of requests. (e.g. `serverNoContextTakeover` is the value to be requested to the server) ### websocket.bytesReceived From 200db3354fed6b97ee0c5bfbf4d37586783fc1c6 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 25 Oct 2016 10:09:28 +0200 Subject: [PATCH 129/669] [doc] Improve documentation for `WebSocket.prototype.send()` --- doc/ws.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index 26137b9b9..897f875c2 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -174,7 +174,20 @@ Resume the client stream ### websocket.send(data, [options], [callback]) -Sends `data` through the connection. `options` can be an object with members `mask`, `binary` and `compress`. The optional `callback` is executed after the send completes. +* `data` Any The data to send. +* `options` Object An options object. + * `compress` Boolean Specifies whether `data` should be compressed or not. + Defaults to `true` when permessage-deflate is enabled. + * `binary` Boolean Specifies whether `data` should be sent as a binary or not. + Default is autodetected. + * `mask` Boolean Specifies whether `data` should be masked or not. Defaults + to `true` when `websocket` is not a server client. + * `fin` Boolean Specifies whether `data` is the last fragment of a message or + not. Defaults to `true`. +* `callback` Function An optional callback which is invoked when the send + completes. + +Sends `data` through the connection. ### websocket.stream([options], callback) From dd885a8d95db7686484db8b57c37f65c5ed36f86 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 25 Oct 2016 10:45:56 +0200 Subject: [PATCH 130/669] [minor] Rename hybi-common.js to hybi-util.js --- bench/parser.benchmark.js | 2 +- test/Receiver.test.js | 2 +- test/hybi-common.js | 99 ------------------------------ bench/util.js => test/hybi-util.js | 0 4 files changed, 2 insertions(+), 101 deletions(-) delete mode 100644 test/hybi-common.js rename bench/util.js => test/hybi-util.js (100%) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index bbda00293..f0b9cae72 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -8,8 +8,8 @@ const benchmark = require('benchmark'); +const util = require('../test/hybi-util'); const Receiver = require('../').Receiver; -const util = require('./util'); function createBinaryPacket (length) { const message = Buffer.alloc(length); diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 6e548f7d2..f9f017d2b 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -5,7 +5,7 @@ const crypto = require('crypto'); const PerMessageDeflate = require('../lib/PerMessageDeflate'); const Receiver = require('../lib/Receiver'); -const util = require('../bench/util'); +const util = require('./hybi-util'); describe('Receiver', function () { describe('#ctor', function () { diff --git a/test/hybi-common.js b/test/hybi-common.js deleted file mode 100644 index 006f9c693..000000000 --- a/test/hybi-common.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Returns a Buffer from a "ff 00 ff"-type hex string. - */ - -getBufferFromHexString = function(byteStr) { - var bytes = byteStr.split(' '); - var buf = new Buffer(bytes.length); - for (var i = 0; i < bytes.length; ++i) { - buf[i] = parseInt(bytes[i], 16); - } - return buf; -} - -/** - * Returns a hex string from a Buffer. - */ - -getHexStringFromBuffer = function(data) { - var s = ''; - for (var i = 0; i < data.length; ++i) { - s += padl(data[i].toString(16), 2, '0') + ' '; - } - return s.trim(); -} - -/** - * Splits a buffer in two parts. - */ - -splitBuffer = function(buffer) { - var b1 = new Buffer(Math.ceil(buffer.length / 2)); - buffer.copy(b1, 0, 0, b1.length); - var b2 = new Buffer(Math.floor(buffer.length / 2)); - buffer.copy(b2, 0, b1.length, b1.length + b2.length); - return [b1, b2]; -} - -/** - * Performs hybi07+ type masking on a hex string or buffer. - */ - -mask = function(buf, maskString) { - if (typeof buf == 'string') buf = new Buffer(buf); - var mask = getBufferFromHexString(maskString || '34 83 a8 68'); - for (var i = 0; i < buf.length; ++i) { - buf[i] ^= mask[i % 4]; - } - return buf; -} - -/** - * Returns a hex string representing the length of a message - */ - -getHybiLengthAsHexString = function(len, masked) { - if (len < 126) { - var buf = new Buffer(1); - buf[0] = (masked ? 0x80 : 0) | len; - } - else if (len < 65536) { - var buf = new Buffer(3); - buf[0] = (masked ? 0x80 : 0) | 126; - getBufferFromHexString(pack(4, len)).copy(buf, 1); - } - else { - var buf = new Buffer(9); - buf[0] = (masked ? 0x80 : 0) | 127; - getBufferFromHexString(pack(16, len)).copy(buf, 1); - } - return getHexStringFromBuffer(buf); -} - -/** - * Unpacks a Buffer into a number. - */ - -unpack = function(buffer) { - var n = 0; - for (var i = 0; i < buffer.length; ++i) { - n = (i == 0) ? buffer[i] : (n * 256) + buffer[i]; - } - return n; -} - -/** - * Returns a hex string, representing a specific byte count 'length', from a number. - */ - -pack = function(length, number) { - return padl(number.toString(16), length, '0').replace(/([0-9a-f][0-9a-f])/gi, '$1 ').trim(); -} - -/** - * Left pads the string 's' to a total length of 'n' with char 'c'. - */ - -padl = function(s, n, c) { - return new Array(1 + n - s.length).join(c) + s; -} diff --git a/bench/util.js b/test/hybi-util.js similarity index 100% rename from bench/util.js rename to test/hybi-util.js From bd0851d4c860657892a5f5a5a43654604b68e8ed Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Tue, 25 Oct 2016 13:02:02 +0200 Subject: [PATCH 131/669] Fix sending buffers as text --- lib/Sender.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Sender.js b/lib/Sender.js index 5e2535c4c..f1acac557 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -194,7 +194,7 @@ class Sender { secondByte = 126; } - var canModifyData = opcode === 1 || compressed; + var canModifyData = compressed; var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData); var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset; var outputBuffer = new Buffer(totalLength); From a10f3c113e0e65bce30f7cc4e538223233321da1 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 26 Oct 2016 08:52:18 +0200 Subject: [PATCH 132/669] [deps] Bump eslint-plugin-promise to version 3.3.x --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 888898f60..1b0a893a1 100644 --- a/package.json +++ b/package.json @@ -29,12 +29,11 @@ "eslint": "3.8.x", "eslint-config-semistandard": "7.0.x", "eslint-config-standard": "6.2.x", - "eslint-plugin-promise": "3.0.x", + "eslint-plugin-promise": "3.3.x", "eslint-plugin-standard": "2.0.x", "istanbul": "0.4.x", "mocha": "3.1.x", "should": "8.0.x", "utf-8-validate": "1.2.x" - }, - "gypfile": true + } } From 398d88a864557a8cfdb45eb8c723fc15b990cb1b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 26 Oct 2016 09:14:26 +0200 Subject: [PATCH 133/669] [ci] Add node 7 to .travis.yml --- .travis.yml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 451ba0135..4b92d55ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,7 @@ language: node_js -sudo: false +sudo: required +dist: trusty node_js: + - "7" - "6" - - "5" - "4" -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - gcc-4.9 - - g++-4.9 -before_install: - - export CC="gcc-4.9" CXX="g++-4.9" From 460e37488227131b961a6c148088aed9ba30332f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 26 Oct 2016 09:29:44 +0200 Subject: [PATCH 134/669] [lint] Run eslint --fix on test/* --- test/Extensions.test.js | 20 +- test/PerMessageDeflate.test.js | 118 ++-- test/Sender.test.js | 52 +- test/Validation.test.js | 12 +- test/WebSocket.integration.js | 20 +- test/WebSocket.test.js | 1034 ++++++++++++++++---------------- test/WebSocketServer.test.js | 504 ++++++++-------- test/autobahn-server.js | 12 +- test/autobahn.js | 24 +- test/testserver.js | 71 ++- 10 files changed, 932 insertions(+), 935 deletions(-) diff --git a/test/Extensions.test.js b/test/Extensions.test.js index 84ec5edac..ce0af8381 100644 --- a/test/Extensions.test.js +++ b/test/Extensions.test.js @@ -1,21 +1,21 @@ var Extensions = require('../lib/Extensions'); require('should'); -describe('Extensions', function() { - describe('parse', function() { - it('should parse', function() { +describe('Extensions', function () { + describe('parse', function () { + it('should parse', function () { var extensions = Extensions.parse('foo'); extensions.should.eql({ foo: [{}] }); }); - it('should parse params', function() { + it('should parse params', function () { var extensions = Extensions.parse('foo; bar; baz=1; bar=2'); extensions.should.eql({ foo: [{ bar: [true, '2'], baz: ['1'] }] }); }); - it('should parse multiple extensions', function() { + it('should parse multiple extensions', function () { var extensions = Extensions.parse('foo, bar; baz, foo; baz'); extensions.should.eql({ foo: [{}, { baz: [true] }], @@ -23,7 +23,7 @@ describe('Extensions', function() { }); }); - it('should parse quoted params', function() { + it('should parse quoted params', function () { var extensions = Extensions.parse('foo; bar="hi"'); extensions.should.eql({ foo: [{ bar: ['hi'] }] @@ -31,18 +31,18 @@ describe('Extensions', function() { }); }); - describe('format', function() { - it('should format', function() { + describe('format', function () { + it('should format', function () { var extensions = Extensions.format({ foo: {} }); extensions.should.eql('foo'); }); - it('should format params', function() { + it('should format params', function () { var extensions = Extensions.format({ foo: { bar: [true, 2], baz: 1 } }); extensions.should.eql('foo; bar; bar=2; baz=1'); }); - it('should format multiple extensions', function() { + it('should format multiple extensions', function () { var extensions = Extensions.format({ foo: [{}, { baz: true }], bar: { baz: true } diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index 6b70ccbdf..c9608d40a 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -2,9 +2,9 @@ var PerMessageDeflate = require('../lib/PerMessageDeflate'); var Extensions = require('../lib/Extensions'); require('should'); -describe('PerMessageDeflate', function() { - describe('#ctor', function() { - it('throws TypeError when called without new', function(done) { +describe('PerMessageDeflate', function () { + describe('#ctor', function () { + it('throws TypeError when called without new', function (done) { try { var perMessageDeflate = PerMessageDeflate(); } @@ -15,13 +15,13 @@ describe('PerMessageDeflate', function() { }); }); - describe('#offer', function() { - it('should create default params', function() { + describe('#offer', function () { + it('should create default params', function () { var perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.offer().should.eql({ client_max_window_bits: true }); }); - it('should create params from options', function() { + it('should create params from options', function () { var perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: true, clientNoContextTakeover: true, @@ -37,14 +37,14 @@ describe('PerMessageDeflate', function() { }); }); - describe('#accept', function() { - describe('as server', function() { - it('should accept empty offer', function() { + describe('#accept', function () { + describe('as server', function () { + it('should accept empty offer', function () { var perMessageDeflate = new PerMessageDeflate({}, true); perMessageDeflate.accept([{}]).should.eql({}); }); - it('should accept offer', function() { + it('should accept offer', function () { var perMessageDeflate = new PerMessageDeflate({}, true); var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; client_no_context_takeover; server_max_window_bits=10; client_max_window_bits=11'); perMessageDeflate.accept(extensions['permessage-deflate']).should.eql({ @@ -55,7 +55,7 @@ describe('PerMessageDeflate', function() { }); }); - it('should prefer configuration than offer', function() { + it('should prefer configuration than offer', function () { var perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: true, clientNoContextTakeover: true, @@ -71,7 +71,7 @@ describe('PerMessageDeflate', function() { }); }); - it('should fallback', function() { + it('should fallback', function () { var perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); var extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10, permessage-deflate'); perMessageDeflate.accept(extensions['permessage-deflate']).should.eql({ @@ -79,46 +79,46 @@ describe('PerMessageDeflate', function() { }); }); - it('should throw an error if server_no_context_takeover is unsupported', function() { + it('should throw an error if server_no_context_takeover is unsupported', function () { var perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: false }, true); var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if server_max_window_bits is unsupported', function() { + it('should throw an error if server_max_window_bits is unsupported', function () { var perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: false }, true); var extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if server_max_window_bits is less than configuration', function() { + it('should throw an error if server_max_window_bits is less than configuration', function () { var perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); var extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if client_max_window_bits is unsupported on client', function() { + it('should throw an error if client_max_window_bits is unsupported on client', function () { var perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }, true); var extensions = Extensions.parse('permessage-deflate'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); }); - describe('as client', function() { - it('should accept empty response', function() { + describe('as client', function () { + it('should accept empty response', function () { var perMessageDeflate = new PerMessageDeflate({}); perMessageDeflate.accept([{}]).should.eql({}); }); - it('should accept response parameter', function() { + it('should accept response parameter', function () { var perMessageDeflate = new PerMessageDeflate({}); var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; client_no_context_takeover; server_max_window_bits=10; client_max_window_bits=11'); perMessageDeflate.accept(extensions['permessage-deflate']).should.eql({ @@ -129,89 +129,89 @@ describe('PerMessageDeflate', function() { }); }); - it('should throw an error if client_no_context_takeover is unsupported', function() { + it('should throw an error if client_no_context_takeover is unsupported', function () { var perMessageDeflate = new PerMessageDeflate({ clientNoContextTakeover: false }); var extensions = Extensions.parse('permessage-deflate; client_no_context_takeover'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if client_max_window_bits is unsupported', function() { + it('should throw an error if client_max_window_bits is unsupported', function () { var perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: false }); var extensions = Extensions.parse('permessage-deflate; client_max_window_bits=10'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if client_max_window_bits is greater than configuration', function() { + it('should throw an error if client_max_window_bits is greater than configuration', function () { var perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }); var extensions = Extensions.parse('permessage-deflate; client_max_window_bits=11'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); }); - describe('validate parameters', function() { - it('should throw an error if a parameter has multiple values', function() { + describe('validate parameters', function () { + it('should throw an error if a parameter has multiple values', function () { var perMessageDeflate = new PerMessageDeflate(); var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; server_no_context_takeover'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if a parameter is undefined', function() { + it('should throw an error if a parameter is undefined', function () { var perMessageDeflate = new PerMessageDeflate(); var extensions = Extensions.parse('permessage-deflate; foo;'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if server_no_context_takeover has a value', function() { + it('should throw an error if server_no_context_takeover has a value', function () { var perMessageDeflate = new PerMessageDeflate(); var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover=10'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if client_no_context_takeover has a value', function() { + it('should throw an error if client_no_context_takeover has a value', function () { var perMessageDeflate = new PerMessageDeflate(); var extensions = Extensions.parse('permessage-deflate; client_no_context_takeover=10'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if server_max_window_bits has an invalid value', function() { + it('should throw an error if server_max_window_bits has an invalid value', function () { var perMessageDeflate = new PerMessageDeflate(); var extensions = Extensions.parse('permessage-deflate; server_max_window_bits=7'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); - it('should throw an error if client_max_window_bits has an invalid value', function() { + it('should throw an error if client_max_window_bits has an invalid value', function () { var perMessageDeflate = new PerMessageDeflate(); var extensions = Extensions.parse('permessage-deflate; client_max_window_bits=16'); - (function() { + (function () { perMessageDeflate.accept(extensions['permessage-deflate']); }).should.throw(); }); }); }); - describe('#compress/#decompress', function() { - it('should compress/decompress data', function(done) { + describe('#compress/#decompress', function () { + it('should compress/decompress data', function (done) { var perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - perMessageDeflate.compress(new Buffer([1, 2, 3]), true, function(err, compressed) { + perMessageDeflate.compress(new Buffer([1, 2, 3]), true, function (err, compressed) { if (err) return done(err); - perMessageDeflate.decompress(compressed, true, function(err, data) { + perMessageDeflate.decompress(compressed, true, function (err, data) { if (err) return done(err); data.should.eql(new Buffer([1, 2, 3])); done(); @@ -219,18 +219,18 @@ describe('PerMessageDeflate', function() { }); }); - it('should compress/decompress fragments', function(done) { + it('should compress/decompress fragments', function (done) { var perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); var buf = new Buffer([1, 2, 3, 4]); - perMessageDeflate.compress(buf.slice(0, 2), false, function(err, compressed1) { + perMessageDeflate.compress(buf.slice(0, 2), false, function (err, compressed1) { if (err) return done(err); - perMessageDeflate.compress(buf.slice(2), true, function(err, compressed2) { + perMessageDeflate.compress(buf.slice(2), true, function (err, compressed2) { if (err) return done(err); - perMessageDeflate.decompress(compressed1, false, function(err, data1) { + perMessageDeflate.decompress(compressed1, false, function (err, data1) { if (err) return done(err); - perMessageDeflate.decompress(compressed2, true, function(err, data2) { + perMessageDeflate.decompress(compressed2, true, function (err, data2) { if (err) return done(err); new Buffer.concat([data1, data2]).should.eql(new Buffer([1, 2, 3, 4])); done(); @@ -240,13 +240,13 @@ describe('PerMessageDeflate', function() { }); }); - it('should compress/decompress data with parameters', function(done) { + it('should compress/decompress data with parameters', function (done) { var perMessageDeflate = new PerMessageDeflate({ memLevel: 5 }); var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; client_no_context_takeover; server_max_window_bits=10; client_max_window_bits=11'); perMessageDeflate.accept(extensions['permessage-deflate']); - perMessageDeflate.compress(new Buffer([1, 2, 3]), true, function(err, compressed) { + perMessageDeflate.compress(new Buffer([1, 2, 3]), true, function (err, compressed) { if (err) return done(err); - perMessageDeflate.decompress(compressed, true, function(err, data) { + perMessageDeflate.decompress(compressed, true, function (err, data) { if (err) return done(err); data.should.eql(new Buffer([1, 2, 3])); done(); @@ -254,18 +254,18 @@ describe('PerMessageDeflate', function() { }); }); - it('should compress/decompress data with no context takeover', function(done) { + it('should compress/decompress data with no context takeover', function (done) { var perMessageDeflate = new PerMessageDeflate(); var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; client_no_context_takeover'); perMessageDeflate.accept(extensions['permessage-deflate']); var buf = new Buffer('foofoo'); - perMessageDeflate.compress(buf, true, function(err, compressed1) { + perMessageDeflate.compress(buf, true, function (err, compressed1) { if (err) return done(err); - perMessageDeflate.decompress(compressed1, true, function(err, data) { + perMessageDeflate.decompress(compressed1, true, function (err, data) { if (err) return done(err); - perMessageDeflate.compress(data, true, function(err, compressed2) { + perMessageDeflate.compress(data, true, function (err, compressed2) { if (err) return done(err); - perMessageDeflate.decompress(compressed2, true, function(err, data) { + perMessageDeflate.decompress(compressed2, true, function (err, data) { if (err) return done(err); compressed2.length.should.equal(compressed1.length); data.should.eql(buf); diff --git a/test/Sender.test.js b/test/Sender.test.js index dc82c4eb0..862071c04 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -1,12 +1,12 @@ -var Sender = require('../lib/Sender') - , PerMessageDeflate = require('../lib/PerMessageDeflate'); +var Sender = require('../lib/Sender'), + PerMessageDeflate = require('../lib/PerMessageDeflate'); require('should'); -describe('Sender', function() { - describe('#ctor', function() { - it('throws TypeError when called without new', function(done) { +describe('Sender', function () { + describe('#ctor', function () { + it('throws TypeError when called without new', function (done) { try { - var sender = Sender({ write: function() {} }); + var sender = Sender({ write: function () {} }); } catch (e) { e.should.be.instanceof(TypeError); @@ -15,9 +15,9 @@ describe('Sender', function() { }); }); - describe('#frameAndSend', function() { - it('does not modify a masked binary buffer', function() { - var sender = new Sender({ write: function() {} }); + describe('#frameAndSend', function () { + it('does not modify a masked binary buffer', function () { + var sender = new Sender({ write: function () {} }); var buf = new Buffer([1, 2, 3, 4, 5]); sender.frameAndSend(2, buf, true, true); buf[0].should.eql(1); @@ -27,16 +27,16 @@ describe('Sender', function() { buf[4].should.eql(5); }); - it('does not modify a masked text buffer', function() { - var sender = new Sender({ write: function() {} }); + it('does not modify a masked text buffer', function () { + var sender = new Sender({ write: function () {} }); var text = 'hi there'; sender.frameAndSend(1, Buffer.from(text), true, true); text.should.eql('hi there'); }); - it('sets rsv1 flag if compressed', function(done) { + it('sets rsv1 flag if compressed', function (done) { var sender = new Sender({ - write: function(data) { + write: function (data) { (data[0] & 0x40).should.equal(0x40); done(); } @@ -45,13 +45,13 @@ describe('Sender', function() { }); }); - describe('#send', function() { - it('compresses data if compress option is enabled', function(done) { + describe('#send', function () { + it('compresses data if compress option is enabled', function (done) { var perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); perMessageDeflate.accept([{}]); var sender = new Sender({ - write: function(data) { + write: function (data) { (data[0] & 0x40).should.equal(0x40); done(); } @@ -60,13 +60,13 @@ describe('Sender', function() { }); sender.send('hi', { compress: true }); }); - - it('does not compress data for small payloads', function(done) { + + it('does not compress data for small payloads', function (done) { var perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); var sender = new Sender({ - write: function(data) { + write: function (data) { (data[0] & 0x40).should.not.equal(0x40); done(); } @@ -76,12 +76,12 @@ describe('Sender', function() { sender.send('hi', { compress: true }); }); - it('Should be able to handle many send calls while processing without crashing on flush', function(done) { + it('Should be able to handle many send calls while processing without crashing on flush', function (done) { var messageCount = 0; var maxMessages = 5000; var sender = new Sender({ - write: function(data) { + write: function (data) { messageCount++; if (messageCount > maxMessages) return done(); } @@ -94,15 +94,15 @@ describe('Sender', function() { sender.send('hi', { compress: false, fin: true, binary: false, mask: false }); }); }); - - describe('#close', function() { - it('should consume all data before closing', function(done) { + + describe('#close', function () { + it('should consume all data before closing', function (done) { var perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); var count = 0; var sender = new Sender({ - write: function(data) { + write: function (data) { count++; } }, { @@ -111,7 +111,7 @@ describe('Sender', function() { sender.send('foo', {compress: true}); sender.send('bar', {compress: true}); sender.send('baz', {compress: true}); - sender.close(1000, null, false, function(err) { + sender.close(1000, null, false, function (err) { count.should.be.equal(4); done(err); }); diff --git a/test/Validation.test.js b/test/Validation.test.js index 37c339935..8038a7c63 100644 --- a/test/Validation.test.js +++ b/test/Validation.test.js @@ -1,21 +1,21 @@ var Validation = require('../lib/Validation').Validation; require('should'); -describe('Validation', function() { - describe('isValidUTF8', function() { - it('should return true for a valid utf8 string', function() { +describe('Validation', function () { + describe('isValidUTF8', function () { + it('should return true for a valid utf8 string', function () { var validBuffer = new Buffer('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque gravida mattis rhoncus. Donec iaculis, metus quis varius accumsan, erat mauris condimentum diam, et egestas erat enim ut ligula. Praesent sollicitudin tellus eget dolor euismod euismod. Nullam ac augue nec neque varius luctus. Curabitur elit mi, consequat ultricies adipiscing mollis, scelerisque in erat. Phasellus facilisis fermentum ullamcorper. Nulla et sem eu arcu pharetra pellentesque. Praesent consectetur tempor justo, vel iaculis dui ullamcorper sit amet. Integer tristique viverra ullamcorper. Vivamus laoreet, nulla eget suscipit eleifend, lacus lectus feugiat libero, non fermentum erat nisi at risus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut pulvinar dignissim tellus, eu dignissim lorem vulputate quis. Morbi ut pulvinar augue.'); Validation.isValidUTF8(validBuffer).should.be.ok; }); - it('should return false for an erroneous string', function() { + it('should return false for an erroneous string', function () { var invalidBuffer = new Buffer([0xce, 0xba, 0xe1, 0xbd, 0xb9, 0xcf, 0x83, 0xce, 0xbc, 0xce, 0xb5, 0xed, 0xa0, 0x80, 0x65, 0x64, 0x69, 0x74, 0x65, 0x64]); Validation.isValidUTF8(invalidBuffer).should.not.be.ok; }); - it('should return true for valid cases from the autobahn test suite', function() { + it('should return true for valid cases from the autobahn test suite', function () { Validation.isValidUTF8(new Buffer('\xf0\x90\x80\x80')).should.be.ok; Validation.isValidUTF8(new Buffer([0xf0, 0x90, 0x80, 0x80])).should.be.ok; }); - it('should return false for erroneous autobahn strings', function() { + it('should return false for erroneous autobahn strings', function () { Validation.isValidUTF8(new Buffer([0xce, 0xba, 0xe1, 0xbd])).should.not.be.ok; }); }); diff --git a/test/WebSocket.integration.js b/test/WebSocket.integration.js index 5d4f426f4..c21821566 100644 --- a/test/WebSocket.integration.js +++ b/test/WebSocket.integration.js @@ -1,10 +1,10 @@ -var assert = require('assert') - , WebSocket = require('../') - , server = require('./testserver'); +var assert = require('assert'), + WebSocket = require('../'), + server = require('./testserver'); var port = 20000; -function getArrayBuffer(buf) { +function getArrayBuffer (buf) { var l = buf.length; var arrayBuf = new ArrayBuffer(l); var uint8View = new Uint8Array(arrayBuf); @@ -15,7 +15,7 @@ function getArrayBuffer(buf) { return uint8View.buffer; } -function areArraysEqual(x, y) { +function areArraysEqual (x, y) { if (x.length != y.length) return false; for (var i = 0, l = x.length; i < l; ++i) { if (x[i] !== y[i]) return false; @@ -23,19 +23,19 @@ function areArraysEqual(x, y) { return true; } -describe('WebSocket', function() { - it('communicates successfully with echo service', function(done) { +describe('WebSocket', function () { + it('communicates successfully with echo service', function (done) { var ws = new WebSocket('ws://echo.websocket.org/', {protocolVersion: 13, origin: 'http://websocket.org'}); var str = Date.now().toString(); var dataReceived = false; - ws.on('open', function() { + ws.on('open', function () { ws.send(str, {mask: true}); }); - ws.on('close', function() { + ws.on('close', function () { assert.equal(true, dataReceived); done(); }); - ws.on('message', function(data, flags) { + ws.on('message', function (data, flags) { assert.equal(str, data); ws.terminate(); dataReceived = true; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 212ea8cf1..6fbbc7801 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1,17 +1,17 @@ -var assert = require('assert') - , https = require('https') - , http = require('http') - , should = require('should') - , WebSocket = require('../') - , WebSocketServer = require('../').Server - , fs = require('fs') - , os = require('os') - , server = require('./testserver') - , crypto = require('crypto'); +var assert = require('assert'), + https = require('https'), + http = require('http'), + should = require('should'), + WebSocket = require('../'), + WebSocketServer = require('../').Server, + fs = require('fs'), + os = require('os'), + server = require('./testserver'), + crypto = require('crypto'); var port = 20000; -function getArrayBuffer(buf) { +function getArrayBuffer (buf) { var l = buf.length; var arrayBuf = new ArrayBuffer(l); var uint8View = new Uint8Array(arrayBuf); @@ -21,8 +21,7 @@ function getArrayBuffer(buf) { return uint8View.buffer; } - -function areArraysEqual(x, y) { +function areArraysEqual (x, y) { if (x.length != y.length) return false; for (var i = 0, l = x.length; i < l; ++i) { if (x[i] !== y[i]) return false; @@ -30,9 +29,9 @@ function areArraysEqual(x, y) { return true; } -describe('WebSocket', function() { - describe('#ctor', function() { - it('throws exception for invalid url', function(done) { +describe('WebSocket', function () { + describe('#ctor', function () { + it('throws exception for invalid url', function (done) { try { var ws = new WebSocket('echo.websocket.org'); } @@ -41,18 +40,18 @@ describe('WebSocket', function() { } }); - it('should return a new instance if called without new', function(done) { + it('should return a new instance if called without new', function (done) { var ws = WebSocket('ws://localhost:' + port); ws.should.be.an.instanceOf(WebSocket); done(); }); }); - describe('options', function() { - it('should accept an `agent` option', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + describe('options', function () { + it('should accept an `agent` option', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var agent = { - addRequest: function() { + addRequest: function () { wss.close(); done(); } @@ -61,10 +60,10 @@ describe('WebSocket', function() { }); }); // GH-227 - it('should accept the `options` object as the 3rd argument', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('should accept the `options` object as the 3rd argument', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var agent = { - addRequest: function() { + addRequest: function () { wss.close(); done(); } @@ -73,21 +72,21 @@ describe('WebSocket', function() { }); }); - it('should accept the localAddress option', function(done) { + it('should accept the localAddress option', function (done) { // explore existing interfaces - var devs = os.networkInterfaces() - , localAddresses = [] - , j, ifc, dev, devname; - for ( devname in devs ) { + var devs = os.networkInterfaces(), + localAddresses = [], + j, ifc, dev, devname; + for (devname in devs) { dev = devs[devname]; - for ( j=0;j 0) break; ws.send((new Array(10000)).join('hello')); } ws.terminate(); - ws.on('close', function() { + ws.on('close', function () { wss.close(); done(); }); @@ -203,7 +202,7 @@ describe('WebSocket', function() { }); }); - describe('Custom headers', function() { + describe('Custom headers', function () { it('request has an authorization header', function (done) { var auth = 'test:testpass'; var srv = http.createServer(function (req, res) {}); @@ -247,23 +246,23 @@ describe('WebSocket', function() { }); }); - describe('#readyState', function() { - it('defaults to connecting', function(done) { - server.createServer(++port, function(srv) { + describe('#readyState', function () { + it('defaults to connecting', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); assert.equal(WebSocket.CONNECTING, ws.readyState); ws.terminate(); - ws.on('close', function() { + ws.on('close', function () { srv.close(); done(); }); }); }); - it('set to open once connection is established', function(done) { - server.createServer(++port, function(srv) { + it('set to open once connection is established', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { assert.equal(WebSocket.OPEN, ws.readyState); srv.close(); done(); @@ -271,11 +270,11 @@ describe('WebSocket', function() { }); }); - it('set to closed once connection is closed', function(done) { - server.createServer(++port, function(srv) { + it('set to closed once connection is closed', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); ws.close(1001); - ws.on('close', function() { + ws.on('close', function () { assert.equal(WebSocket.CLOSED, ws.readyState); srv.close(); done(); @@ -283,11 +282,11 @@ describe('WebSocket', function() { }); }); - it('set to closed once connection is terminated', function(done) { - server.createServer(++port, function(srv) { + it('set to closed once connection is terminated', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); ws.terminate(); - ws.on('close', function() { + ws.on('close', function () { assert.equal(WebSocket.CLOSED, ws.readyState); srv.close(); done(); @@ -311,21 +310,21 @@ describe('WebSocket', function() { * Ready state constant tests */ - Object.keys(readyStates).forEach(function(state) { - describe('.' + state, function() { - it('is enumerable property of class', function() { - var propertyDescripter = Object.getOwnPropertyDescriptor(WebSocket, state) + Object.keys(readyStates).forEach(function (state) { + describe('.' + state, function () { + it('is enumerable property of class', function () { + var propertyDescripter = Object.getOwnPropertyDescriptor(WebSocket, state); assert.equal(readyStates[state], propertyDescripter.value); assert.equal(true, propertyDescripter.enumerable); }); }); }); - server.createServer(++port, function(srv) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - Object.keys(readyStates).forEach(function(state) { - describe('.' + state, function() { - it('is property of instance', function() { + Object.keys(readyStates).forEach(function (state) { + describe('.' + state, function () { + it('is property of instance', function () { assert.equal(readyStates[state], ws[state]); }); }); @@ -333,27 +332,27 @@ describe('WebSocket', function() { }); }); - describe('events', function() { - it('emits a ping event', function(done) { + describe('events', function () { + it('emits a ping event', function (done) { var wss = new WebSocketServer({port: ++port}); - wss.on('connection', function(client) { + wss.on('connection', function (client) { client.ping(); }); var ws = new WebSocket('ws://localhost:' + port); - ws.on('ping', function() { + ws.on('ping', function () { ws.terminate(); wss.close(); done(); }); }); - it('emits a pong event', function(done) { + it('emits a pong event', function (done) { var wss = new WebSocketServer({port: ++port}); - wss.on('connection', function(client) { + wss.on('connection', function (client) { client.pong(); }); var ws = new WebSocket('ws://localhost:' + port); - ws.on('pong', function() { + ws.on('pong', function () { ws.terminate(); wss.close(); done(); @@ -361,96 +360,96 @@ describe('WebSocket', function() { }); }); - describe('connection establishing', function() { - it('can disconnect before connection is established', function(done) { - server.createServer(++port, function(srv) { + describe('connection establishing', function () { + it('can disconnect before connection is established', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); ws.terminate(); - ws.on('open', function() { + ws.on('open', function () { assert.fail('connect shouldnt be raised here'); }); - ws.on('close', function() { + ws.on('close', function () { srv.close(); done(); }); }); }); - it('can close before connection is established', function(done) { - server.createServer(++port, function(srv) { + it('can close before connection is established', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); ws.close(1001); - ws.on('open', function() { + ws.on('open', function () { assert.fail('connect shouldnt be raised here'); }); - ws.on('close', function() { + ws.on('close', function () { srv.close(); done(); }); }); }); - it('can handle error before request is upgraded', function(done) { + it('can handle error before request is upgraded', function (done) { // Here, we don't create a server, to guarantee that the connection will // fail before the request is upgraded - ++port; - var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { - assert.fail('connect shouldnt be raised here'); - }); - var errorCallBackFired = false; - ws.on('error', function() { - errorCallBackFired = true; - }); - ws.on('close', function() { - setTimeout(function() { - assert.equal(true, errorCallBackFired); - assert.equal(ws.readyState, WebSocket.CLOSED); - done(); - }, 50) - }); + ++port; + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function () { + assert.fail('connect shouldnt be raised here'); + }); + var errorCallBackFired = false; + ws.on('error', function () { + errorCallBackFired = true; + }); + ws.on('close', function () { + setTimeout(function () { + assert.equal(true, errorCallBackFired); + assert.equal(ws.readyState, WebSocket.CLOSED); + done(); + }, 50); + }); }); - it('invalid server key is denied', function(done) { - server.createServer(++port, server.handlers.invalidKey, function(srv) { + it('invalid server key is denied', function (done) { + server.createServer(++port, server.handlers.invalidKey, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('error', function() { + ws.on('error', function () { srv.close(); done(); }); }); }); - it('close event is raised when server closes connection', function(done) { - server.createServer(++port, server.handlers.closeAfterConnect, function(srv) { + it('close event is raised when server closes connection', function (done) { + server.createServer(++port, server.handlers.closeAfterConnect, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('close', function() { + ws.on('close', function () { srv.close(); done(); }); }); }); - it('error is emitted if server aborts connection', function(done) { - server.createServer(++port, server.handlers.return401, function(srv) { + it('error is emitted if server aborts connection', function (done) { + server.createServer(++port, server.handlers.return401, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { assert.fail('connect shouldnt be raised here'); }); - ws.on('error', function() { + ws.on('error', function () { srv.close(); done(); }); }); }); - it('unexpected response can be read when sent by server', function(done) { - server.createServer(++port, server.handlers.return401, function(srv) { + it('unexpected response can be read when sent by server', function (done) { + server.createServer(++port, server.handlers.return401, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { assert.fail('connect shouldnt be raised here'); }); - ws.on('unexpected-response', function(req, res) { + ws.on('unexpected-response', function (req, res) { assert.equal(res.statusCode, 401); var data = ''; @@ -471,13 +470,13 @@ describe('WebSocket', function() { }); }); - it('request can be aborted when unexpected response is sent by server', function(done) { - server.createServer(++port, server.handlers.return401, function(srv) { + it('request can be aborted when unexpected response is sent by server', function (done) { + server.createServer(++port, server.handlers.return401, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { assert.fail('connect shouldnt be raised here'); }); - ws.on('unexpected-response', function(req, res) { + ws.on('unexpected-response', function (req, res) { assert.equal(res.statusCode, 401); res.on('end', function () { @@ -514,8 +513,8 @@ describe('WebSocket', function() { }); }); - describe('#pause and #resume', function() { - it('pauses the underlying stream', function(done) { + describe('#pause and #resume', function () { + it('pauses the underlying stream', function (done) { // this test is sort-of racecondition'y, since an unlikely slow connection // to localhost can cause the test to succeed even when the stream pausing // isn't working as intended. that is an extremely unlikely scenario, though @@ -523,39 +522,39 @@ describe('WebSocket', function() { var client; var serverClient; var openCount = 0; - function onOpen() { + function onOpen () { if (++openCount == 2) { var paused = true; - serverClient.on('message', function() { + serverClient.on('message', function () { paused.should.not.be.ok; wss.close(); done(); }); serverClient.pause(); - setTimeout(function() { + setTimeout(function () { paused = false; serverClient.resume(); }, 200); client.send('foo'); } } - var wss = new WebSocketServer({port: ++port}, function() { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); serverClient = ws; serverClient.on('open', onOpen); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { client = ws; onOpen(); }); }); }); - describe('#ping', function() { - it('before connect should fail', function(done) { - server.createServer(++port, function(srv) { + describe('#ping', function () { + it('before connect should fail', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('error', function() {}); + ws.on('error', function () {}); try { ws.ping(); } @@ -567,10 +566,10 @@ describe('WebSocket', function() { }); }); - it('before connect can silently fail', function(done) { - server.createServer(++port, function(srv) { + it('before connect can silently fail', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('error', function() {}); + ws.on('error', function () {}); ws.ping('', {}, true); srv.close(); ws.terminate(); @@ -578,13 +577,13 @@ describe('WebSocket', function() { }); }); - it('without message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('without message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.ping(); }); - srv.on('ping', function(message) { + srv.on('ping', function (message) { srv.close(); ws.terminate(); done(); @@ -592,13 +591,13 @@ describe('WebSocket', function() { }); }); - it('with message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.ping('hi'); }); - srv.on('ping', function(message) { + srv.on('ping', function (message) { assert.equal('hi', message); srv.close(); ws.terminate(); @@ -607,15 +606,15 @@ describe('WebSocket', function() { }); }); - it('can send safely receive numbers as ping payload', function(done) { - server.createServer(++port, function(srv) { + it('can send safely receive numbers as ping payload', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.ping(200); }); - srv.on('ping', function(message) { + srv.on('ping', function (message) { assert.equal('200', message); srv.close(); ws.terminate(); @@ -624,13 +623,13 @@ describe('WebSocket', function() { }); }); - it('with encoded message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with encoded message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.ping('hi', {mask: true}); }); - srv.on('ping', function(message, flags) { + srv.on('ping', function (message, flags) { assert.ok(flags.masked); assert.equal('hi', message); srv.close(); @@ -641,11 +640,11 @@ describe('WebSocket', function() { }); }); - describe('#pong', function() { - it('before connect should fail', function(done) { - server.createServer(++port, function(srv) { + describe('#pong', function () { + it('before connect should fail', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('error', function() {}); + ws.on('error', function () {}); try { ws.pong(); } @@ -657,10 +656,10 @@ describe('WebSocket', function() { }); }); - it('before connect can silently fail', function(done) { - server.createServer(++port, function(srv) { + it('before connect can silently fail', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('error', function() {}); + ws.on('error', function () {}); ws.pong('', {}, true); srv.close(); ws.terminate(); @@ -668,13 +667,13 @@ describe('WebSocket', function() { }); }); - it('without message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('without message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.pong(); }); - srv.on('pong', function(message) { + srv.on('pong', function (message) { srv.close(); ws.terminate(); done(); @@ -682,13 +681,13 @@ describe('WebSocket', function() { }); }); - it('with message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.pong('hi'); }); - srv.on('pong', function(message) { + srv.on('pong', function (message) { assert.equal('hi', message); srv.close(); ws.terminate(); @@ -697,13 +696,13 @@ describe('WebSocket', function() { }); }); - it('with encoded message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with encoded message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.pong('hi', {mask: true}); }); - srv.on('pong', function(message, flags) { + srv.on('pong', function (message, flags) { assert.ok(flags.masked); assert.equal('hi', message); srv.close(); @@ -714,16 +713,16 @@ describe('WebSocket', function() { }); }); - describe('#send', function() { - it('very long binary data can be sent and received (with echoing server)', function(done) { - server.createServer(++port, function(srv) { + describe('#send', function () { + it('very long binary data can be sent and received (with echoing server)', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var array = new Float32Array(5 * 1024 * 1024); for (var i = 0; i < array.length; ++i) array[i] = i / 5; - ws.on('open', function() { + ws.on('open', function () { ws.send(array, {binary: true}); }); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { assert.ok(flags.binary); assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); ws.terminate(); @@ -733,13 +732,13 @@ describe('WebSocket', function() { }); }); - it('can send and receive text data', function(done) { - server.createServer(++port, function(srv) { + it('can send and receive text data', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.send('hi'); }); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { assert.equal('hi', message); ws.terminate(); srv.close(); @@ -766,16 +765,16 @@ describe('WebSocket', function() { }); }); - it('send and receive binary data as an array', function(done) { - server.createServer(++port, function(srv) { + it('send and receive binary data as an array', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var array = new Float32Array(6); for (var i = 0; i < array.length; ++i) array[i] = i / 2; var partial = array.subarray(2, 5); - ws.on('open', function() { + ws.on('open', function () { ws.send(partial, {binary: true}); }); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { assert.ok(flags.binary); assert.ok(areArraysEqual(partial, new Float32Array(getArrayBuffer(message)))); ws.terminate(); @@ -785,14 +784,14 @@ describe('WebSocket', function() { }); }); - it('binary data can be sent and received as buffer', function(done) { - server.createServer(++port, function(srv) { + it('binary data can be sent and received as buffer', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var buf = new Buffer('foobar'); - ws.on('open', function() { + ws.on('open', function () { ws.send(buf, {binary: true}); }); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { assert.ok(flags.binary); assert.ok(areArraysEqual(buf, message)); ws.terminate(); @@ -802,12 +801,12 @@ describe('WebSocket', function() { }); }); - it('ArrayBuffer is auto-detected without binary flag', function(done) { - server.createServer(++port, function(srv) { + it('ArrayBuffer is auto-detected without binary flag', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var array = new Float32Array(5); for (var i = 0; i < array.length; ++i) array[i] = i / 2; - ws.on('open', function() { + ws.on('open', function () { ws.send(array.buffer); }); ws.onmessage = function (event) { @@ -820,11 +819,11 @@ describe('WebSocket', function() { }); }); - it('Buffer is auto-detected without binary flag', function(done) { - server.createServer(++port, function(srv) { + it('Buffer is auto-detected without binary flag', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var buf = new Buffer('foobar'); - ws.on('open', function() { + ws.on('open', function () { ws.send(buf); }); ws.onmessage = function (event) { @@ -837,10 +836,10 @@ describe('WebSocket', function() { }); }); - it('before connect should fail', function(done) { - server.createServer(++port, function(srv) { + it('before connect should fail', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('error', function() {}); + ws.on('error', function () {}); try { ws.send('hi'); } @@ -852,11 +851,11 @@ describe('WebSocket', function() { }); }); - it('before connect should pass error through callback, if present', function(done) { - server.createServer(++port, function(srv) { + it('before connect should pass error through callback, if present', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('error', function() {}); - ws.send('hi', function(error) { + ws.on('error', function () {}); + ws.send('hi', function (error) { assert.ok(error instanceof Error); ws.terminate(); srv.close(); @@ -865,13 +864,13 @@ describe('WebSocket', function() { }); }); - it('without data should be successful', function(done) { - server.createServer(++port, function(srv) { + it('without data should be successful', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.send(); }); - srv.on('message', function(message, flags) { + srv.on('message', function (message, flags) { assert.equal('', message); srv.close(); ws.terminate(); @@ -880,11 +879,11 @@ describe('WebSocket', function() { }); }); - it('calls optional callback when flushed', function(done) { - server.createServer(++port, function(srv) { + it('calls optional callback when flushed', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { - ws.send('hi', function() { + ws.on('open', function () { + ws.send('hi', function () { srv.close(); ws.terminate(); done(); @@ -893,13 +892,13 @@ describe('WebSocket', function() { }); }); - it('with unencoded message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with unencoded message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.send('hi'); }); - srv.on('message', function(message, flags) { + srv.on('message', function (message, flags) { assert.equal('hi', message); srv.close(); ws.terminate(); @@ -908,13 +907,13 @@ describe('WebSocket', function() { }); }); - it('with encoded message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with encoded message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.send('hi', {mask: true}); }); - srv.on('message', function(message, flags) { + srv.on('message', function (message, flags) { assert.ok(flags.masked); assert.equal('hi', message); srv.close(); @@ -924,15 +923,15 @@ describe('WebSocket', function() { }); }); - it('with unencoded binary message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with unencoded binary message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var array = new Float32Array(5); for (var i = 0; i < array.length; ++i) array[i] = i / 2; - ws.on('open', function() { + ws.on('open', function () { ws.send(array, {binary: true}); }); - srv.on('message', function(message, flags) { + srv.on('message', function (message, flags) { assert.ok(flags.binary); assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); srv.close(); @@ -942,15 +941,15 @@ describe('WebSocket', function() { }); }); - it('with encoded binary message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with encoded binary message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var array = new Float32Array(5); for (var i = 0; i < array.length; ++i) array[i] = i / 2; - ws.on('open', function() { + ws.on('open', function () { ws.send(array, {mask: true, binary: true}); }); - srv.on('message', function(message, flags) { + srv.on('message', function (message, flags) { assert.ok(flags.binary); assert.ok(flags.masked); assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); @@ -961,23 +960,23 @@ describe('WebSocket', function() { }); }); - it('with binary stream will send fragmented data', function(done) { - server.createServer(++port, function(srv) { + it('with binary stream will send fragmented data', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var callbackFired = false; - ws.on('open', function() { + ws.on('open', function () { var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); - ws.send(fileStream, {binary: true}, function(error) { + ws.send(fileStream, {binary: true}, function (error) { assert.equal(null, error); callbackFired = true; }); }); - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { assert.ok(flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile'), data)); ws.terminate(); }); - ws.on('close', function() { + ws.on('close', function () { assert.ok(callbackFired); srv.close(); done(); @@ -985,23 +984,23 @@ describe('WebSocket', function() { }); }); - it('with text stream will send fragmented data', function(done) { - server.createServer(++port, function(srv) { + it('with text stream will send fragmented data', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var callbackFired = false; - ws.on('open', function() { + ws.on('open', function () { var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); - ws.send(fileStream, {binary: false}, function(error) { + ws.send(fileStream, {binary: false}, function (error) { assert.equal(null, error); callbackFired = true; }); }); - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); ws.terminate(); }); - ws.on('close', function() { + ws.on('close', function () { assert.ok(callbackFired); srv.close(); done(); @@ -1009,17 +1008,17 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent send to be delayed in order', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent send to be delayed in order', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.send('foobar'); ws.send('baz'); }); var receivedIndex = 0; - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { ++receivedIndex; if (receivedIndex == 1) { assert.ok(!flags.binary); @@ -1040,21 +1039,21 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent stream to be delayed in order', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent stream to be delayed in order', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); var i = 0; - ws.stream(function(error, send) { + ws.stream(function (error, send) { assert.ok(!error); if (++i == 1) send('foo'); else send('bar', true); }); }); var receivedIndex = 0; - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { ++receivedIndex; if (receivedIndex == 1) { assert.ok(!flags.binary); @@ -1071,16 +1070,16 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent ping to be delivered', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent ping to be delivered', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.ping('foobar'); }); var receivedIndex = 0; - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); if (++receivedIndex == 2) { @@ -1089,7 +1088,7 @@ describe('WebSocket', function() { done(); } }); - srv.on('ping', function(data) { + srv.on('ping', function (data) { assert.equal('foobar', data); if (++receivedIndex == 2) { srv.close(); @@ -1100,16 +1099,16 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent pong to be delivered', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent pong to be delivered', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.pong('foobar'); }); var receivedIndex = 0; - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); if (++receivedIndex == 2) { @@ -1118,7 +1117,7 @@ describe('WebSocket', function() { done(); } }); - srv.on('pong', function(data) { + srv.on('pong', function (data) { assert.equal('foobar', data); if (++receivedIndex == 2) { srv.close(); @@ -1129,25 +1128,25 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent close to be delivered', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent close to be delivered', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.close(1000, 'foobar'); }); - ws.on('close', function() { + ws.on('close', function () { srv.close(); ws.terminate(); done(); }); - ws.on('error', function() { /* That's quite alright -- a send was attempted after close */ }); - srv.on('message', function(data, flags) { + ws.on('error', function () { /* That's quite alright -- a send was attempted after close */ }); + srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); }); - srv.on('close', function(code, data) { + srv.on('close', function (code, data) { assert.equal(1000, code); assert.equal('foobar', data); }); @@ -1155,17 +1154,17 @@ describe('WebSocket', function() { }); }); - describe('#stream', function() { - it('very long binary data can be streamed', function(done) { - server.createServer(++port, function(srv) { + describe('#stream', function () { + it('very long binary data can be streamed', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var buffer = new Buffer(10 * 1024); for (var i = 0; i < buffer.length; ++i) buffer[i] = i % 0xff; - ws.on('open', function() { + ws.on('open', function () { var i = 0; var blockSize = 800; var bufLen = buffer.length; - ws.stream({binary: true}, function(error, send) { + ws.stream({binary: true}, function (error, send) { assert.ok(!error); var start = i * blockSize; var toSend = Math.min(blockSize, bufLen - (i * blockSize)); @@ -1175,7 +1174,7 @@ describe('WebSocket', function() { i += 1; }); }); - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { assert.ok(flags.binary); assert.ok(areArraysEqual(buffer, data)); ws.terminate(); @@ -1185,11 +1184,11 @@ describe('WebSocket', function() { }); }); - it('before connect should pass error through callback', function(done) { - server.createServer(++port, function(srv) { + it('before connect should pass error through callback', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('error', function() {}); - ws.stream(function(error) { + ws.on('error', function () {}); + ws.stream(function (error) { assert.ok(error instanceof Error); ws.terminate(); srv.close(); @@ -1198,11 +1197,11 @@ describe('WebSocket', function() { }); }); - it('without callback should fail', function(done) { - server.createServer(++port, function(srv) { + it('without callback should fail', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var payload = 'HelloWorld'; - ws.on('open', function() { + ws.on('open', function () { try { ws.stream(); } @@ -1215,13 +1214,13 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent send to be delayed in order', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent send to be delayed in order', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var payload = 'HelloWorld'; - ws.on('open', function() { + ws.on('open', function () { var i = 0; - ws.stream(function(error, send) { + ws.stream(function (error, send) { assert.ok(!error); if (++i == 1) { send(payload.substr(0, 5)); @@ -1234,7 +1233,7 @@ describe('WebSocket', function() { }); }); var receivedIndex = 0; - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { ++receivedIndex; if (receivedIndex == 1) { assert.ok(!flags.binary); @@ -1255,18 +1254,18 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent stream to be delayed in order', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent stream to be delayed in order', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var payload = 'HelloWorld'; - ws.on('open', function() { + ws.on('open', function () { var i = 0; - ws.stream(function(error, send) { + ws.stream(function (error, send) { assert.ok(!error); if (++i == 1) { send(payload.substr(0, 5)); var i2 = 0; - ws.stream(function(error, send) { + ws.stream(function (error, send) { assert.ok(!error); if (++i2 == 1) send('foo'); else send('bar', true); @@ -1277,7 +1276,7 @@ describe('WebSocket', function() { }); }); var receivedIndex = 0; - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { ++receivedIndex; if (receivedIndex == 1) { assert.ok(!flags.binary); @@ -1287,10 +1286,10 @@ describe('WebSocket', function() { assert.ok(!flags.binary); assert.equal('foobar', data); } - else if (receivedIndex == 3){ + else if (receivedIndex == 3) { assert.ok(!flags.binary); assert.equal('baz', data); - setTimeout(function() { + setTimeout(function () { srv.close(); ws.terminate(); done(); @@ -1301,13 +1300,13 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent ping to be delivered', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent ping to be delivered', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var payload = 'HelloWorld'; - ws.on('open', function() { + ws.on('open', function () { var i = 0; - ws.stream(function(error, send) { + ws.stream(function (error, send) { assert.ok(!error); if (++i == 1) { send(payload.substr(0, 5)); @@ -1319,7 +1318,7 @@ describe('WebSocket', function() { }); }); var receivedIndex = 0; - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.equal(payload, data); if (++receivedIndex == 2) { @@ -1328,7 +1327,7 @@ describe('WebSocket', function() { done(); } }); - srv.on('ping', function(data) { + srv.on('ping', function (data) { assert.equal('foobar', data); if (++receivedIndex == 2) { srv.close(); @@ -1339,13 +1338,13 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent pong to be delivered', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent pong to be delivered', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var payload = 'HelloWorld'; - ws.on('open', function() { + ws.on('open', function () { var i = 0; - ws.stream(function(error, send) { + ws.stream(function (error, send) { assert.ok(!error); if (++i == 1) { send(payload.substr(0, 5)); @@ -1357,7 +1356,7 @@ describe('WebSocket', function() { }); }); var receivedIndex = 0; - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.equal(payload, data); if (++receivedIndex == 2) { @@ -1366,7 +1365,7 @@ describe('WebSocket', function() { done(); } }); - srv.on('pong', function(data) { + srv.on('pong', function (data) { assert.equal('foobar', data); if (++receivedIndex == 2) { srv.close(); @@ -1377,19 +1376,19 @@ describe('WebSocket', function() { }); }); - it('will cause intermittent close to be delivered', function(done) { - server.createServer(++port, function(srv) { + it('will cause intermittent close to be delivered', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var payload = 'HelloWorld'; var errorGiven = false; - ws.on('open', function() { + ws.on('open', function () { var i = 0; - ws.stream(function(error, send) { + ws.stream(function (error, send) { if (++i == 1) { send(payload.substr(0, 5)); ws.close(1000, 'foobar'); } - else if(i == 2) { + else if (i == 2) { send(payload.substr(5, 5), true); } else if (i == 3) { @@ -1398,17 +1397,17 @@ describe('WebSocket', function() { } }); }); - ws.on('close', function() { + ws.on('close', function () { assert.ok(errorGiven); srv.close(); ws.terminate(); done(); }); - srv.on('message', function(data, flags) { + srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.equal(payload, data); }); - srv.on('close', function(code, data) { + srv.on('close', function (code, data) { assert.equal(1000, code); assert.equal('foobar', data); }); @@ -1416,20 +1415,20 @@ describe('WebSocket', function() { }); }); - describe('#close', function() { - it('will raise error callback, if any, if called during send stream', function(done) { - server.createServer(++port, function(srv) { + describe('#close', function () { + it('will raise error callback, if any, if called during send stream', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var errorGiven = false; - ws.on('open', function() { + ws.on('open', function () { var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); - ws.send(fileStream, function(error) { + ws.send(fileStream, function (error) { errorGiven = error != null; }); ws.close(1000, 'foobar'); }); - ws.on('close', function() { - setTimeout(function() { + ws.on('close', function () { + setTimeout(function () { assert.ok(errorGiven); srv.close(); ws.terminate(); @@ -1439,10 +1438,10 @@ describe('WebSocket', function() { }); }); - it('without invalid first argument throws exception', function(done) { - server.createServer(++port, function(srv) { + it('without invalid first argument throws exception', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { try { ws.close('error'); } @@ -1455,10 +1454,10 @@ describe('WebSocket', function() { }); }); - it('without reserved error code 1004 throws exception', function(done) { - server.createServer(++port, function(srv) { + it('without reserved error code 1004 throws exception', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { try { ws.close(1004); } @@ -1471,13 +1470,13 @@ describe('WebSocket', function() { }); }); - it('without message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('without message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.close(1000); }); - srv.on('close', function(code, message, flags) { + srv.on('close', function (code, message, flags) { assert.equal('', message); srv.close(); ws.terminate(); @@ -1486,13 +1485,13 @@ describe('WebSocket', function() { }); }); - it('with message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.close(1000, 'some reason'); }); - srv.on('close', function(code, message, flags) { + srv.on('close', function (code, message, flags) { assert.ok(flags.masked); assert.equal('some reason', message); srv.close(); @@ -1502,13 +1501,13 @@ describe('WebSocket', function() { }); }); - it('with encoded message is successfully transmitted to the server', function(done) { - server.createServer(++port, function(srv) { + it('with encoded message is successfully transmitted to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.close(1000, 'some reason', {mask: true}); }); - srv.on('close', function(code, message, flags) { + srv.on('close', function (code, message, flags) { assert.ok(flags.masked); assert.equal('some reason', message); srv.close(); @@ -1518,15 +1517,15 @@ describe('WebSocket', function() { }); }); - it('ends connection to the server', function(done) { - server.createServer(++port, function(srv) { + it('ends connection to the server', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var connectedOnce = false; - ws.on('open', function() { + ws.on('open', function () { connectedOnce = true; ws.close(1000, 'some reason', {mask: true}); }); - ws.on('close', function() { + ws.on('close', function () { assert.ok(connectedOnce); srv.close(); ws.terminate(); @@ -1535,9 +1534,9 @@ describe('WebSocket', function() { }); }); - it('consumes all data when the server socket closed', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { - wss.on('connection', function(conn) { + it('consumes all data when the server socket closed', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { + wss.on('connection', function (conn) { conn.send('foo'); conn.send('bar'); conn.send('baz'); @@ -1572,9 +1571,9 @@ describe('WebSocket', function() { }); }); - describe('W3C API emulation', function() { - it('should not throw errors when getting and setting', function(done) { - server.createServer(++port, function(srv) { + describe('W3C API emulation', function () { + it('should not throw errors when getting and setting', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var listener = function () {}; @@ -1598,34 +1597,34 @@ describe('WebSocket', function() { }); }); - it('should work the same as the EventEmitter api', function(done) { - server.createServer(++port, function(srv) { + it('should work the same as the EventEmitter api', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - var listener = function() {}; + var listener = function () {}; var message = 0; var close = 0; var open = 0; - ws.onmessage = function(messageEvent) { + ws.onmessage = function (messageEvent) { assert.ok(!!messageEvent.data); ++message; ws.close(); }; - ws.onopen = function() { + ws.onopen = function () { ++open; - } + }; - ws.onclose = function() { + ws.onclose = function () { ++close; - } + }; - ws.on('open', function() { + ws.on('open', function () { ws.send('foo'); }); - ws.on('close', function() { - process.nextTick(function() { + ws.on('close', function () { + process.nextTick(function () { assert.ok(message === 1); assert.ok(open === 1); assert.ok(close === 1); @@ -1638,13 +1637,13 @@ describe('WebSocket', function() { }); }); - it('should receive text data wrapped in a MessageEvent when using addEventListener', function(done) { - server.createServer(++port, function(srv) { + it('should receive text data wrapped in a MessageEvent when using addEventListener', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - ws.addEventListener('open', function() { + ws.addEventListener('open', function () { ws.send('hi'); }); - ws.addEventListener('message', function(messageEvent) { + ws.addEventListener('message', function (messageEvent) { assert.equal('hi', messageEvent.data); ws.terminate(); srv.close(); @@ -1653,10 +1652,10 @@ describe('WebSocket', function() { }); }); - it('should receive valid CloseEvent when server closes with code 1000', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('should receive valid CloseEvent when server closes with code 1000', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); - ws.addEventListener('close', function(closeEvent) { + ws.addEventListener('close', function (closeEvent) { assert.equal(true, closeEvent.wasClean); assert.equal(1000, closeEvent.code); ws.terminate(); @@ -1664,15 +1663,15 @@ describe('WebSocket', function() { done(); }); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { client.close(1000); }); }); - it('should receive valid CloseEvent when server closes with code 1001', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('should receive valid CloseEvent when server closes with code 1001', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); - ws.addEventListener('close', function(closeEvent) { + ws.addEventListener('close', function (closeEvent) { assert.equal(false, closeEvent.wasClean); assert.equal(1001, closeEvent.code); assert.equal('some daft reason', closeEvent.reason); @@ -1681,72 +1680,72 @@ describe('WebSocket', function() { done(); }); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { client.close(1001, 'some daft reason'); }); }); - it('should have target set on Events', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('should have target set on Events', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); - ws.addEventListener('open', function(openEvent) { + ws.addEventListener('open', function (openEvent) { assert.equal(ws, openEvent.target); }); - ws.addEventListener('message', function(messageEvent) { + ws.addEventListener('message', function (messageEvent) { assert.equal(ws, messageEvent.target); wss.close(); }); - ws.addEventListener('close', function(closeEvent) { + ws.addEventListener('close', function (closeEvent) { assert.equal(ws, closeEvent.target); ws.emit('error', new Error('forced')); }); - ws.addEventListener('error', function(errorEvent) { + ws.addEventListener('error', function (errorEvent) { assert.equal(errorEvent.message, 'forced'); assert.equal(ws, errorEvent.target); ws.terminate(); done(); }); }); - wss.on('connection', function(client) { - client.send('hi') + wss.on('connection', function (client) { + client.send('hi'); }); }); - it('should have type set on Events', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('should have type set on Events', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); - ws.addEventListener('open', function(openEvent) { + ws.addEventListener('open', function (openEvent) { assert.equal('open', openEvent.type); }); - ws.addEventListener('message', function(messageEvent) { + ws.addEventListener('message', function (messageEvent) { assert.equal('message', messageEvent.type); wss.close(); }); - ws.addEventListener('close', function(closeEvent) { + ws.addEventListener('close', function (closeEvent) { assert.equal('close', closeEvent.type); ws.emit('error', new Error('forced')); }); - ws.addEventListener('error', function(errorEvent) { + ws.addEventListener('error', function (errorEvent) { assert.equal(errorEvent.message, 'forced'); assert.equal('error', errorEvent.type); ws.terminate(); done(); }); }); - wss.on('connection', function(client) { - client.send('hi') + wss.on('connection', function (client) { + client.send('hi'); }); }); - it('should pass binary data as a node.js Buffer by default', function(done) { - server.createServer(++port, function(srv) { + it('should pass binary data as a node.js Buffer by default', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); var array = new Uint8Array(4096); - ws.onopen = function() { + ws.onopen = function () { ws.send(array, {binary: true}); }; - ws.onmessage = function(messageEvent) { + ws.onmessage = function (messageEvent) { assert.ok(messageEvent.binary); assert.ok(ws.binaryType === 'nodebuffer'); assert.ok(messageEvent.data instanceof Buffer); @@ -1757,16 +1756,16 @@ describe('WebSocket', function() { }); }); - it('should pass an ArrayBuffer for event.data if binaryType = arraybuffer', function(done) { - server.createServer(++port, function(srv) { + it('should pass an ArrayBuffer for event.data if binaryType = arraybuffer', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); ws.binaryType = 'arraybuffer'; var array = new Uint8Array(4096); - ws.onopen = function() { + ws.onopen = function () { ws.send(array, {binary: true}); }; - ws.onmessage = function(messageEvent) { + ws.onmessage = function (messageEvent) { assert.ok(messageEvent.binary); assert.ok(messageEvent.data instanceof ArrayBuffer); ws.terminate(); @@ -1776,15 +1775,15 @@ describe('WebSocket', function() { }); }); - it('should ignore binaryType for text messages', function(done) { - server.createServer(++port, function(srv) { + it('should ignore binaryType for text messages', function (done) { + server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); ws.binaryType = 'arraybuffer'; - ws.onopen = function() { + ws.onopen = function () { ws.send('foobar'); }; - ws.onmessage = function(messageEvent) { + ws.onmessage = function (messageEvent) { assert.ok(!messageEvent.binary); assert.ok(typeof messageEvent.data === 'string'); ws.terminate(); @@ -1793,11 +1792,10 @@ describe('WebSocket', function() { }; }); }); - }); - describe('ssl', function() { - it('can connect to secure websocket server', function(done) { + describe('ssl', function () { + it('can connect to secure websocket server', function (done) { var options = { key: fs.readFileSync('test/fixtures/key.pem'), cert: fs.readFileSync('test/fixtures/certificate.pem') @@ -1807,10 +1805,10 @@ describe('WebSocket', function() { res.end(); }); var wss = new WebSocketServer({server: app}); - app.listen(++port, function() { + app.listen(++port, function () { var ws = new WebSocket('wss://localhost:' + port); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { app.close(); ws.terminate(); wss.close(); @@ -1818,7 +1816,7 @@ describe('WebSocket', function() { }); }); - it('can connect to secure websocket server with client side certificate', function(done) { + it('can connect to secure websocket server with client side certificate', function (done) { var options = { key: fs.readFileSync('test/fixtures/key.pem'), cert: fs.readFileSync('test/fixtures/certificate.pem'), @@ -1836,15 +1834,15 @@ describe('WebSocket', function() { var success = false; var wss = new WebSocketServer({ server: app, - verifyClient: function(info) { + verifyClient: function (info) { success = !!info.req.client.authorized; return true; } }); - app.listen(++port, function() { + app.listen(++port, function () { var ws = new WebSocket('wss://localhost:' + port, clientOptions); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { app.close(); ws.terminate(); wss.close(); @@ -1853,7 +1851,7 @@ describe('WebSocket', function() { }); }); - it('cannot connect to secure websocket server via ws://', function(done) { + it('cannot connect to secure websocket server via ws://', function (done) { var options = { key: fs.readFileSync('test/fixtures/key.pem'), cert: fs.readFileSync('test/fixtures/certificate.pem') @@ -1863,9 +1861,9 @@ describe('WebSocket', function() { res.end(); }); var wss = new WebSocketServer({server: app}); - app.listen(++port, function() { - var ws = new WebSocket('ws://localhost:' + port, { rejectUnauthorized :false }); - ws.on('error', function() { + app.listen(++port, function () { + var ws = new WebSocket('ws://localhost:' + port, { rejectUnauthorized: false }); + ws.on('error', function () { app.close(); ws.terminate(); wss.close(); @@ -1874,7 +1872,7 @@ describe('WebSocket', function() { }); }); - it('can send and receive text data', function(done) { + it('can send and receive text data', function (done) { var options = { key: fs.readFileSync('test/fixtures/key.pem'), cert: fs.readFileSync('test/fixtures/certificate.pem') @@ -1884,14 +1882,14 @@ describe('WebSocket', function() { res.end(); }); var wss = new WebSocketServer({server: app}); - app.listen(++port, function() { + app.listen(++port, function () { var ws = new WebSocket('wss://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.send('foobar'); }); }); - wss.on('connection', function(ws) { - ws.on('message', function(message, flags) { + wss.on('connection', function (ws) { + ws.on('message', function (message, flags) { message.should.eql('foobar'); app.close(); ws.terminate(); @@ -1901,24 +1899,24 @@ describe('WebSocket', function() { }); }); - it('can send and receive very long binary data', function(done) { + it('can send and receive very long binary data', function (done) { var options = { key: fs.readFileSync('test/fixtures/key.pem'), cert: fs.readFileSync('test/fixtures/certificate.pem') - } + }; var app = https.createServer(options, function (req, res) { res.writeHead(200); res.end(); }); - crypto.randomBytes(5 * 1024 * 1024, function(ex, buf) { + crypto.randomBytes(5 * 1024 * 1024, function (ex, buf) { if (ex) throw ex; var wss = new WebSocketServer({server: app}); - app.listen(++port, function() { + app.listen(++port, function () { var ws = new WebSocket('wss://localhost:' + port); - ws.on('open', function() { + ws.on('open', function () { ws.send(buf, {binary: true}); }); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { flags.binary.should.be.ok; areArraysEqual(buf, message).should.be.ok; app.close(); @@ -1927,8 +1925,8 @@ describe('WebSocket', function() { done(); }); }); - wss.on('connection', function(ws) { - ws.on('message', function(message, flags) { + wss.on('connection', function (ws) { + ws.on('message', function (message, flags) { ws.send(message, {binary: true}); }); }); @@ -1936,14 +1934,14 @@ describe('WebSocket', function() { }); }); - describe('protocol support discovery', function() { - describe('#supports', function() { - describe('#binary', function() { - it('returns true', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + describe('protocol support discovery', function () { + describe('#supports', function () { + describe('#binary', function () { + it('returns true', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { assert.equal(true, client.supports.binary); wss.close(); done(); @@ -1953,11 +1951,11 @@ describe('WebSocket', function() { }); }); - describe('host and origin headers', function() { - it('includes the host header with port number', function(done) { + describe('host and origin headers', function () { + it('includes the host header with port number', function (done) { var srv = http.createServer(); - srv.listen(++port, function(){ - srv.on('upgrade', function(req, socket, upgradeHeade) { + srv.listen(++port, function () { + srv.on('upgrade', function (req, socket, upgradeHeade) { assert.equal('localhost:' + port, req.headers['host']); srv.close(); done(); @@ -1966,10 +1964,10 @@ describe('WebSocket', function() { }); }); - it('lacks default origin header', function(done) { + it('lacks default origin header', function (done) { var srv = http.createServer(); - srv.listen(++port, function() { - srv.on('upgrade', function(req, socket, upgradeHeade) { + srv.listen(++port, function () { + srv.on('upgrade', function (req, socket, upgradeHeade) { should(req.headers).not.have.property('origin'); srv.close(); done(); @@ -1978,11 +1976,11 @@ describe('WebSocket', function() { }); }); - it('honors origin set in options', function(done) { + it('honors origin set in options', function (done) { var srv = http.createServer(); - srv.listen(++port, function() { - var options = {origin: 'https://example.com:8000'} - srv.on('upgrade', function(req, socket, upgradeHeade) { + srv.listen(++port, function () { + var options = {origin: 'https://example.com:8000'}; + srv.on('upgrade', function (req, socket, upgradeHeade) { assert.equal(options.origin, req.headers['origin']); srv.close(); done(); @@ -1991,32 +1989,32 @@ describe('WebSocket', function() { }); }); - it('excludes default ports from host header', function(done) { + it('excludes default ports from host header', function (done) { // can't create a server listening on ports 80 or 443 // so we need to expose the method that does this - var buildHostHeader = WebSocket.buildHostHeader - var host = buildHostHeader(false, 'localhost', 80) + var buildHostHeader = WebSocket.buildHostHeader; + var host = buildHostHeader(false, 'localhost', 80); assert.equal('localhost', host); - host = buildHostHeader(false, 'localhost', 88) + host = buildHostHeader(false, 'localhost', 88); assert.equal('localhost:88', host); - host = buildHostHeader(true, 'localhost', 443) + host = buildHostHeader(true, 'localhost', 443); assert.equal('localhost', host); - host = buildHostHeader(true, 'localhost', 8443) + host = buildHostHeader(true, 'localhost', 8443); assert.equal('localhost:8443', host); - done() + done(); }); }); - describe('permessage-deflate', function() { - it('is enabled by default', function(done) { + describe('permessage-deflate', function () { + it('is enabled by default', function (done) { var srv = http.createServer(function (req, res) {}); var wss = new WebSocketServer({server: srv, perMessageDeflate: true}); - srv.listen(++port, function() { + srv.listen(++port, function () { var ws = new WebSocket('ws://localhost:' + port); - srv.on('upgrade', function(req, socket, head) { + srv.on('upgrade', function (req, socket, head) { assert.ok(~req.headers['sec-websocket-extensions'].indexOf('permessage-deflate')); }); - ws.on('open', function() { + ws.on('open', function () { assert.ok(ws.extensions['permessage-deflate']); ws.terminate(); wss.close(); @@ -2025,12 +2023,12 @@ describe('WebSocket', function() { }); }); - it('can be disabled', function(done) { + it('can be disabled', function (done) { var srv = http.createServer(function (req, res) {}); var wss = new WebSocketServer({server: srv, perMessageDeflate: true}); - srv.listen(++port, function() { + srv.listen(++port, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: false}); - srv.on('upgrade', function(req, socket, head) { + srv.on('upgrade', function (req, socket, head) { assert.ok(!req.headers['sec-websocket-extensions']); ws.terminate(); wss.close(); @@ -2039,10 +2037,10 @@ describe('WebSocket', function() { }); }); - it('can send extension parameters', function(done) { + it('can send extension parameters', function (done) { var srv = http.createServer(function (req, res) {}); var wss = new WebSocketServer({server: srv, perMessageDeflate: true}); - srv.listen(++port, function() { + srv.listen(++port, function () { var ws = new WebSocket('ws://localhost:' + port, { perMessageDeflate: { serverNoContextTakeover: true, @@ -2051,7 +2049,7 @@ describe('WebSocket', function() { clientMaxWindowBits: true } }); - srv.on('upgrade', function(req, socket, head) { + srv.on('upgrade', function (req, socket, head) { var extensions = req.headers['sec-websocket-extensions']; assert.ok(~extensions.indexOf('permessage-deflate')); assert.ok(~extensions.indexOf('server_no_context_takeover')); @@ -2065,89 +2063,89 @@ describe('WebSocket', function() { }); }); - it('can send and receive text data', function(done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function() { + it('can send and receive text data', function (done) { + var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); - ws.on('open', function() { + ws.on('open', function () { ws.send('hi', {compress: true}); }); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { assert.equal('hi', message); ws.terminate(); wss.close(); done(); }); }); - wss.on('connection', function(ws) { - ws.on('message', function(message, flags) { + wss.on('connection', function (ws) { + ws.on('message', function (message, flags) { ws.send(message, {compress: true}); }); }); }); - it('can send and receive a typed array', function(done) { + it('can send and receive a typed array', function (done) { var array = new Float32Array(5); for (var i = 0; i < array.length; i++) array[i] = i / 2; - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function() { + var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); - ws.on('open', function() { + ws.on('open', function () { ws.send(array, {compress: true}); }); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); ws.terminate(); wss.close(); done(); }); }); - wss.on('connection', function(ws) { - ws.on('message', function(message, flags) { + wss.on('connection', function (ws) { + ws.on('message', function (message, flags) { ws.send(message, {compress: true}); }); }); }); - it('can send and receive ArrayBuffer', function(done) { + it('can send and receive ArrayBuffer', function (done) { var array = new Float32Array(5); for (var i = 0; i < array.length; i++) array[i] = i / 2; - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function() { + var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); - ws.on('open', function() { + ws.on('open', function () { ws.send(array.buffer, {compress: true}); }); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); ws.terminate(); wss.close(); done(); }); }); - wss.on('connection', function(ws) { - ws.on('message', function(message, flags) { + wss.on('connection', function (ws) { + ws.on('message', function (message, flags) { ws.send(message, {compress: true}); }); }); }); - it('with binary stream will send fragmented data', function(done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function() { + it('with binary stream will send fragmented data', function (done) { + var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); var callbackFired = false; - ws.on('open', function() { + ws.on('open', function () { var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); - ws.send(fileStream, {binary: true, compress: true}, function(error) { + ws.send(fileStream, {binary: true, compress: true}, function (error) { assert.equal(null, error); callbackFired = true; }); }); - ws.on('close', function() { + ws.on('close', function () { assert.ok(callbackFired); wss.close(); done(); }); }); - wss.on('connection', function(ws) { - ws.on('message', function(data, flags) { + wss.on('connection', function (ws) { + ws.on('message', function (data, flags) { assert.ok(flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile'), data)); ws.terminate(); @@ -2155,41 +2153,41 @@ describe('WebSocket', function() { }); }); - describe('#send', function() { - it('can set the compress option true when perMessageDeflate is disabled', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + describe('#send', function () { + it('can set the compress option true when perMessageDeflate is disabled', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: false}); - ws.on('open', function() { + ws.on('open', function () { ws.send('hi', {compress: true}); }); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { assert.equal('hi', message); ws.terminate(); wss.close(); done(); }); }); - wss.on('connection', function(ws) { - ws.on('message', function(message, flags) { + wss.on('connection', function (ws) { + ws.on('message', function (message, flags) { ws.send(message, {compress: true}); }); }); }); }); - describe('#close', function() { - it('should not raise error callback, if any, if called during send data', function(done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function() { + describe('#close', function () { + it('should not raise error callback, if any, if called during send data', function (done) { + var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); var errorGiven = false; - ws.on('open', function() { - ws.send('hi', function(error) { + ws.on('open', function () { + ws.send('hi', function (error) { errorGiven = error != null; }); ws.close(); }); - ws.on('close', function() { - setTimeout(function() { + ws.on('close', function () { + setTimeout(function () { assert.ok(!errorGiven); wss.close(); ws.terminate(); @@ -2200,19 +2198,19 @@ describe('WebSocket', function() { }); }); - describe('#terminate', function() { - it('will raise error callback, if any, if called during send data', function(done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function() { + describe('#terminate', function () { + it('will raise error callback, if any, if called during send data', function (done) { + var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: { threshold: 0 }}); var errorGiven = false; - ws.on('open', function() { - ws.send('hi', function(error) { + ws.on('open', function () { + ws.send('hi', function (error) { errorGiven = error != null; }); ws.terminate(); }); - ws.on('close', function() { - setTimeout(function() { + ws.on('close', function () { + setTimeout(function () { assert.ok(errorGiven); wss.close(); ws.terminate(); @@ -2222,19 +2220,19 @@ describe('WebSocket', function() { }); }); - it('can call during receiving data', function(done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function() { + it('can call during receiving data', function (done) { + var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); - wss.on('connection', function(client) { + wss.on('connection', function (client) { for (var i = 0; i < 10; i++) { client.send('hi'); } - client.send('hi', function() { + client.send('hi', function () { ws.terminate(); }); }); - ws.on('close', function() { - setTimeout(function() { + ws.on('close', function () { + setTimeout(function () { wss.close(); done(); }, 1000); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index e8a08d80c..347d4e110 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -1,13 +1,13 @@ -var http = require('http') - , https = require('https') - , WebSocket = require('../') - , WebSocketServer = WebSocket.Server - , fs = require('fs') - , should = require('should'); +var http = require('http'), + https = require('https'), + WebSocket = require('../'), + WebSocketServer = WebSocket.Server, + fs = require('fs'), + should = require('should'); var port = 8000; -function getArrayBuffer(buf) { +function getArrayBuffer (buf) { var l = buf.length; var arrayBuf = new ArrayBuffer(l); for (var i = 0; i < l; ++i) { @@ -16,7 +16,7 @@ function getArrayBuffer(buf) { return arrayBuf; } -function areArraysEqual(x, y) { +function areArraysEqual (x, y) { if (x.length != y.length) return false; for (var i = 0, l = x.length; i < l; ++i) { if (x[i] !== y[i]) return false; @@ -24,15 +24,15 @@ function areArraysEqual(x, y) { return true; } -describe('WebSocketServer', function() { - describe('#ctor', function() { - it('should return a new instance if called without new', function(done) { +describe('WebSocketServer', function () { + describe('#ctor', function () { + it('should return a new instance if called without new', function (done) { var ws = WebSocketServer({noServer: true}); ws.should.be.an.instanceOf(WebSocketServer); done(); }); - it('throws an error if no option object is passed', function() { + it('throws an error if no option object is passed', function () { var gotException = false; try { var wss = new WebSocketServer(); @@ -43,7 +43,7 @@ describe('WebSocketServer', function() { gotException.should.be.ok; }); - it('throws an error if no port or server is specified', function() { + it('throws an error if no port or server is specified', function () { var gotException = false; try { var wss = new WebSocketServer({}); @@ -54,7 +54,7 @@ describe('WebSocketServer', function() { gotException.should.be.ok; }); - it('does not throw an error if no port or server is specified, when the noServer option is true', function() { + it('does not throw an error if no port or server is specified, when the noServer option is true', function () { var gotException = false; try { var wss = new WebSocketServer({noServer: true}); @@ -65,20 +65,20 @@ describe('WebSocketServer', function() { gotException.should.eql(false); }); - it('emits an error if http server bind fails', function(done) { + it('emits an error if http server bind fails', function (done) { var wss1 = new WebSocketServer({port: 50003}); var wss2 = new WebSocketServer({port: 50003}); - wss2.on('error', function() { + wss2.on('error', function () { wss1.close(); done(); }); }); - it('starts a server on a given port', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('starts a server on a given port', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { wss.close(); done(); }); @@ -90,7 +90,7 @@ describe('WebSocketServer', function() { var wss = new WebSocketServer({server: srv}); var ws = new WebSocket('ws://localhost:' + port); - wss.on('connection', function(client) { + wss.on('connection', function (client) { wss.close(); srv.close(); done(); @@ -115,15 +115,15 @@ describe('WebSocketServer', function() { }); // Don't test this on Windows. It throws errors for obvious reasons. - if(!/^win/i.test(process.platform)) { + if (!/^win/i.test(process.platform)) { it('uses a precreated http server listening on unix socket', function (done) { var srv = http.createServer(); - var sockPath = '/tmp/ws_socket_'+new Date().getTime()+'.'+Math.floor(Math.random() * 1000); + var sockPath = '/tmp/ws_socket_' + new Date().getTime() + '.' + Math.floor(Math.random() * 1000); srv.listen(sockPath, function () { var wss = new WebSocketServer({server: srv}); - var ws = new WebSocket('ws+unix://'+sockPath); + var ws = new WebSocket('ws+unix://' + sockPath); - wss.on('connection', function(client) { + wss.on('connection', function (client) { wss.close(); srv.close(); done(); @@ -136,9 +136,9 @@ describe('WebSocketServer', function() { var srv = http.createServer(); srv.listen(++port, function () { var wss = new WebSocketServer({server: srv}); - var ws = new WebSocket('ws://localhost:' + port+'/endpointName'); + var ws = new WebSocket('ws://localhost:' + port + '/endpointName'); - wss.on('connection/endpointName', function(client) { + wss.on('connection/endpointName', function (client) { wss.close(); srv.close(); done(); @@ -146,20 +146,20 @@ describe('WebSocketServer', function() { }); }); - it('can have two different instances listening on the same http server with two different paths', function(done) { + it('can have two different instances listening on the same http server with two different paths', function (done) { var srv = http.createServer(); srv.listen(++port, function () { - var wss1 = new WebSocketServer({server: srv, path: '/wss1'}) - , wss2 = new WebSocketServer({server: srv, path: '/wss2'}); + var wss1 = new WebSocketServer({server: srv, path: '/wss1'}), + wss2 = new WebSocketServer({server: srv, path: '/wss2'}); var doneCount = 0; - wss1.on('connection', function(client) { + wss1.on('connection', function (client) { wss1.close(); if (++doneCount == 2) { srv.close(); done(); } }); - wss2.on('connection', function(client) { + wss2.on('connection', function (client) { wss2.close(); if (++doneCount == 2) { srv.close(); @@ -171,7 +171,7 @@ describe('WebSocketServer', function() { }); }); - it('cannot have two different instances listening on the same http server with the same path', function(done) { + it('cannot have two different instances listening on the same http server with the same path', function (done) { var srv = http.createServer(); srv.listen(++port, function () { var wss1 = new WebSocketServer({server: srv, path: '/wss1'}); @@ -185,26 +185,26 @@ describe('WebSocketServer', function() { } }); }); - it('will not crash when it receives an unhandled opcode', function(done) { + it('will not crash when it receives an unhandled opcode', function (done) { var wss = new WebSocketServer({ port: 8080 }); - wss.on('connection', function connection(ws) { - ws.onerror = function(error) { - done(); - }; + wss.on('connection', function connection (ws) { + ws.onerror = function (error) { + done(); + }; }); var socket = new WebSocket('ws://127.0.0.1:8080/'); - socket.onopen = function() { - socket._socket.write(new Buffer([5])); - socket.send(''); + socket.onopen = function () { + socket._socket.write(new Buffer([5])); + socket.send(''); }; }); }); - describe('#close', function() { - it('does not thrown when called twice', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + describe('#close', function () { + it('does not thrown when called twice', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { wss.close(); wss.close(); wss.close(); @@ -213,32 +213,32 @@ describe('WebSocketServer', function() { }); }); - it('will close all clients', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('will close all clients', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); - ws.on('close', function() { + ws.on('close', function () { if (++closes == 2) done(); }); }); var closes = 0; - wss.on('connection', function(client) { - client.on('close', function() { + wss.on('connection', function (client) { + client.on('close', function () { if (++closes == 2) done(); }); wss.close(); }); }); - it('does not close a precreated server', function(done) { + it('does not close a precreated server', function (done) { var srv = http.createServer(); var realClose = srv.close; - srv.close = function() { + srv.close = function () { should.fail('must not close pre-created server'); - } + }; srv.listen(++port, function () { var wss = new WebSocketServer({server: srv}); var ws = new WebSocket('ws://localhost:' + port); - wss.on('connection', function(client) { + wss.on('connection', function (client) { wss.close(); srv.close = realClose; srv.close(); @@ -247,23 +247,23 @@ describe('WebSocketServer', function() { }); }); - it('cleans event handlers on precreated server', function(done) { + it('cleans event handlers on precreated server', function (done) { var srv = http.createServer(); - srv.listen(++port, function() { + srv.listen(++port, function () { var wss = new WebSocketServer({server: srv}); wss.close(); srv.emit('upgrade'); - srv.on('error', function() {}); + srv.on('error', function () {}); srv.emit('error'); - done() + done(); }); }); - it('cleans up websocket data on a precreated server', function(done) { + it('cleans up websocket data on a precreated server', function (done) { var srv = http.createServer(); srv.listen(++port, function () { - var wss1 = new WebSocketServer({server: srv, path: '/wss1'}) - , wss2 = new WebSocketServer({server: srv, path: '/wss2'}); + var wss1 = new WebSocketServer({server: srv, path: '/wss1'}), + wss2 = new WebSocketServer({server: srv, path: '/wss2'}); (typeof srv._webSocketPaths).should.eql('object'); Object.keys(srv._webSocketPaths).length.should.eql(2); wss1.close(); @@ -276,38 +276,38 @@ describe('WebSocketServer', function() { }); }); - describe('#clients', function() { - it('returns a list of connected clients', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + describe('#clients', function () { + it('returns a list of connected clients', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { wss.clients.size.should.eql(1); wss.close(); done(); }); }); - it('can be disabled', function(done) { - var wss = new WebSocketServer({port: ++port, clientTracking: false}, function() { + it('can be disabled', function (done) { + var wss = new WebSocketServer({port: ++port, clientTracking: false}, function () { wss.should.not.have.property('clients'); var ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { wss.should.not.have.property('clients'); wss.close(); done(); }); }); - it('is updated when client terminates the connection', function(done) { + it('is updated when client terminates the connection', function (done) { var ws; - var wss = new WebSocketServer({port: ++port}, function() { + var wss = new WebSocketServer({port: ++port}, function () { ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(client) { - client.on('close', function() { + wss.on('connection', function (client) { + client.on('close', function () { wss.clients.size.should.eql(0); wss.close(); done(); @@ -316,13 +316,13 @@ describe('WebSocketServer', function() { }); }); - it('is updated when client closes the connection', function(done) { + it('is updated when client closes the connection', function (done) { var ws; - var wss = new WebSocketServer({port: ++port}, function() { + var wss = new WebSocketServer({port: ++port}, function () { ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(client) { - client.on('close', function() { + wss.on('connection', function (client) { + client.on('close', function () { wss.clients.size.should.eql(0); wss.close(); done(); @@ -332,9 +332,9 @@ describe('WebSocketServer', function() { }); }); - describe('#options', function() { - it('exposes options passed to constructor', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + describe('#options', function () { + it('exposes options passed to constructor', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { wss.options.port.should.eql(port); wss.close(); done(); @@ -342,41 +342,41 @@ describe('WebSocketServer', function() { }); }); - describe('#maxpayload', function() { - it('maxpayload is passed on to clients,', function(done) { + describe('#maxpayload', function () { + it('maxpayload is passed on to clients,', function (done) { var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload}, function() { + var wss = new WebSocketServer({port: ++port, maxPayload: _maxPayload}, function () { wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { wss.clients.size.should.eql(1); client.maxPayload.should.eql(_maxPayload); wss.close(); done(); }); }); - it('maxpayload is passed on to hybi receivers', function(done) { + it('maxpayload is passed on to hybi receivers', function (done) { var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload}, function() { + var wss = new WebSocketServer({port: ++port, maxPayload: _maxPayload}, function () { wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { wss.clients.size.should.eql(1); client._receiver.maxPayload.should.eql(_maxPayload); wss.close(); done(); }); }); - it('maxpayload is passed on to permessage-deflate', function(done) { + it('maxpayload is passed on to permessage-deflate', function (done) { var PerMessageDeflate = require('../lib/PerMessageDeflate'); var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload}, function() { + var wss = new WebSocketServer({port: ++port, maxPayload: _maxPayload}, function () { wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { wss.clients.size.should.eql(1); client._receiver.extensions[PerMessageDeflate.extensionName]._maxPayload.should.eql(_maxPayload); wss.close(); @@ -385,18 +385,18 @@ describe('WebSocketServer', function() { }); }); - describe('#handleUpgrade', function() { + describe('#handleUpgrade', function () { it('can be used for a pre-existing server', function (done) { var srv = http.createServer(); srv.listen(++port, function () { var wss = new WebSocketServer({noServer: true}); - srv.on('upgrade', function(req, socket, upgradeHead) { - wss.handleUpgrade(req, socket, upgradeHead, function(client) { + srv.on('upgrade', function (req, socket, upgradeHead) { + wss.handleUpgrade(req, socket, upgradeHead, function (client) { client.send('hello'); }); }); var ws = new WebSocket('ws://localhost:' + port); - ws.on('message', function(message) { + ws.on('message', function (message) { message.should.eql('hello'); wss.close(); srv.close(); @@ -406,7 +406,7 @@ describe('WebSocketServer', function() { }); it('closes the connection when path does not match', function (done) { - var wss = new WebSocketServer({port: ++port, path: '/ws'}, function() { + var wss = new WebSocketServer({port: ++port, path: '/ws'}, function () { var options = { port: port, host: '127.0.0.1', @@ -417,7 +417,7 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(400); wss.close(); done(); @@ -448,10 +448,10 @@ describe('WebSocketServer', function() { }); }); - describe('hybi mode', function() { - describe('connection establishing', function() { - it('does not accept connections with no sec-websocket-key', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + describe('hybi mode', function () { + describe('connection establishing', function () { + it('does not accept connections with no sec-websocket-key', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var options = { port: port, host: '127.0.0.1', @@ -462,20 +462,20 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(400); wss.close(); done(); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('does not accept connections with no sec-websocket-version', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('does not accept connections with no sec-websocket-version', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var options = { port: port, host: '127.0.0.1', @@ -487,20 +487,20 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(400); wss.close(); done(); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('does not accept connections with invalid sec-websocket-version', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('does not accept connections with invalid sec-websocket-version', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var options = { port: port, host: '127.0.0.1', @@ -513,22 +513,22 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(400); wss.close(); done(); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('client can be denied', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { + it('client can be denied', function (done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function (o) { return false; - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -542,24 +542,24 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(401); - process.nextTick(function() { + process.nextTick(function () { wss.close(); done(); }); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('client can be accepted', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { + it('client can be accepted', function (done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function (o) { return true; - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -574,21 +574,21 @@ describe('WebSocketServer', function() { var req = http.request(options); req.end(); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { ws.terminate(); wss.close(); done(); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('verifyClient gets client origin', function(done) { + it('verifyClient gets client origin', function (done) { var verifyClientCalled = false; - var wss = new WebSocketServer({port: ++port, verifyClient: function(info) { + var wss = new WebSocketServer({port: ++port, verifyClient: function (info) { info.origin.should.eql('http://foobarbaz.com'); verifyClientCalled = true; return false; - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -602,22 +602,22 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { verifyClientCalled.should.be.ok; wss.close(); done(); }); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('verifyClient gets original request', function(done) { + it('verifyClient gets original request', function (done) { var verifyClientCalled = false; - var wss = new WebSocketServer({port: ++port, verifyClient: function(info) { + var wss = new WebSocketServer({port: ++port, verifyClient: function (info) { info.req.headers['sec-websocket-key'].should.eql('dGhlIHNhbXBsZSBub25jZQ=='); verifyClientCalled = true; return false; - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -631,16 +631,16 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { verifyClientCalled.should.be.ok; wss.close(); done(); }); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('verifyClient has secure:true for ssl connections', function(done) { + it('verifyClient has secure:true for ssl connections', function (done) { var options = { key: fs.readFileSync('test/fixtures/key.pem'), cert: fs.readFileSync('test/fixtures/certificate.pem') @@ -652,15 +652,15 @@ describe('WebSocketServer', function() { var success = false; var wss = new WebSocketServer({ server: app, - verifyClient: function(info) { + verifyClient: function (info) { success = info.secure === true; return true; } }); - app.listen(++port, function() { + app.listen(++port, function () { var ws = new WebSocket('wss://localhost:' + port); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { app.close(); ws.terminate(); wss.close(); @@ -669,7 +669,7 @@ describe('WebSocketServer', function() { }); }); - it('verifyClient has secure:false for non-ssl connections', function(done) { + it('verifyClient has secure:false for non-ssl connections', function (done) { var app = http.createServer(function (req, res) { res.writeHead(200); res.end(); @@ -677,15 +677,15 @@ describe('WebSocketServer', function() { var success = false; var wss = new WebSocketServer({ server: app, - verifyClient: function(info) { + verifyClient: function (info) { success = info.secure === false; return true; } }); - app.listen(++port, function() { + app.listen(++port, function () { var ws = new WebSocket('ws://localhost:' + port); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { app.close(); ws.terminate(); wss.close(); @@ -694,12 +694,12 @@ describe('WebSocketServer', function() { }); }); - it('client can be denied asynchronously', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { - process.nextTick(function() { + it('client can be denied asynchronously', function (done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function (o, cb) { + process.nextTick(function () { cb(false); }); - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -713,26 +713,26 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(401); - process.nextTick(function() { + process.nextTick(function () { wss.close(); done(); }); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('client can be denied asynchronously with custom response code', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { - process.nextTick(function() { + it('client can be denied asynchronously with custom response code', function (done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function (o, cb) { + process.nextTick(function () { cb(false, 404); }); - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -746,26 +746,26 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(404); - process.nextTick(function() { + process.nextTick(function () { wss.close(); done(); }); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('client can be accepted asynchronously', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { - process.nextTick(function() { + it('client can be accepted asynchronously', function (done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function (o, cb) { + process.nextTick(function () { cb(true); }); - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -780,18 +780,18 @@ describe('WebSocketServer', function() { var req = http.request(options); req.end(); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { ws.terminate(); wss.close(); done(); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('handles messages passed along with the upgrade request (upgrade head)', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { + it('handles messages passed along with the upgrade request (upgrade head)', function (done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function (o) { return true; - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -807,83 +807,83 @@ describe('WebSocketServer', function() { req.write(new Buffer([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f], 'binary')); req.end(); }); - wss.on('connection', function(ws) { - ws.on('message', function(data) { + wss.on('connection', function (ws) { + ws.on('message', function (data) { data.should.eql('Hello'); ws.terminate(); wss.close(); done(); }); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('selects the first protocol by default', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('selects the first protocol by default', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function(client) { - ws.protocol.should.eql('prot1'); - wss.close(); - done(); + ws.on('open', function (client) { + ws.protocol.should.eql('prot1'); + wss.close(); + done(); }); }); }); - it('selects the last protocol via protocol handler', function(done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function(ps, cb) { - cb(true, ps[ps.length-1]); }}, function() { - var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function(client) { + it('selects the last protocol via protocol handler', function (done) { + var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { + cb(true, ps[ps.length - 1]); }}, function () { + var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); + ws.on('open', function (client) { ws.protocol.should.eql('prot2'); wss.close(); done(); + }); }); - }); }); - it('client detects invalid server protocol', function(done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function(ps, cb) { - cb(true, 'prot3'); }}, function() { - var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function(client) { + it('client detects invalid server protocol', function (done) { + var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { + cb(true, 'prot3'); }}, function () { + var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); + ws.on('open', function (client) { done(new Error('connection must not be established')); - }); - ws.on('error', function() { + }); + ws.on('error', function () { done(); + }); }); - }); }); - it('client detects no server protocol', function(done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function(ps, cb) { - cb(true); }}, function() { - var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function(client) { + it('client detects no server protocol', function (done) { + var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { + cb(true); }}, function () { + var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); + ws.on('open', function (client) { done(new Error('connection must not be established')); - }); - ws.on('error', function() { + }); + ws.on('error', function () { done(); + }); }); - }); }); - it('client refuses server protocols', function(done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function(ps, cb) { - cb(false); }}, function() { - var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function(client) { + it('client refuses server protocols', function (done) { + var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { + cb(false); }}, function () { + var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); + ws.on('open', function (client) { done(new Error('connection must not be established')); - }); - ws.on('error', function() { + }); + ws.on('error', function () { done(); + }); }); - }); }); - it('server detects unauthorized protocol handler', function(done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function(ps, cb) { + it('server detects unauthorized protocol handler', function (done) { + var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { cb(false); - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -898,22 +898,22 @@ describe('WebSocketServer', function() { options.port = port; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(401); wss.close(); done(); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('server detects invalid protocol handler', function(done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function(ps, cb) { + it('server detects invalid protocol handler', function (done) { + var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { // not calling callback is an error and shouldn't timeout - }}, function() { + }}, function () { var options = { port: port, host: '127.0.0.1', @@ -928,20 +928,20 @@ describe('WebSocketServer', function() { options.port = port; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(501); wss.close(); done(); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('accept connections with sec-websocket-extensions', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('accept connections with sec-websocket-extensions', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var options = { port: port, host: '127.0.0.1', @@ -956,30 +956,30 @@ describe('WebSocketServer', function() { var req = http.request(options); req.end(); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { ws.terminate(); wss.close(); done(); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); }); - describe('messaging', function() { - it('can send and receive data', function(done) { - var data = new Array(65*1024); + describe('messaging', function () { + it('can send and receive data', function (done) { + var data = new Array(65 * 1024); for (var i = 0; i < data.length; ++i) { data[i] = String.fromCharCode(65 + ~~(25 * Math.random())); } data = data.join(''); - var wss = new WebSocketServer({port: ++port}, function() { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); - ws.on('message', function(message, flags) { + ws.on('message', function (message, flags) { ws.send(message); }); }); - wss.on('connection', function(client) { - client.on('message', function(message) { + wss.on('connection', function (client) { + client.on('message', function (message) { message.should.eql(data); wss.close(); done(); @@ -990,34 +990,34 @@ describe('WebSocketServer', function() { }); }); - describe('client properties', function() { - it('protocol is exposed', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + describe('client properties', function () { + it('protocol is exposed', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port, 'hi'); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { client.protocol.should.eql('hi'); wss.close(); done(); }); }); - it('protocolVersion is exposed', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('protocolVersion is exposed', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port, {protocolVersion: 8}); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { client.protocolVersion.should.eql(8); wss.close(); done(); }); }); - it('upgradeReq is the original request object', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('upgradeReq is the original request object', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port, {protocolVersion: 8}); }); - wss.on('connection', function(client) { + wss.on('connection', function (client) { client.upgradeReq.httpVersion.should.eql('1.1'); wss.close(); done(); @@ -1025,9 +1025,9 @@ describe('WebSocketServer', function() { }); }); - describe('permessage-deflate', function() { - it('accept connections with permessage-deflate extension', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + describe('permessage-deflate', function () { + it('accept connections with permessage-deflate extension', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var options = { port: port, host: '127.0.0.1', @@ -1042,16 +1042,16 @@ describe('WebSocketServer', function() { var req = http.request(options); req.end(); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { ws.terminate(); wss.close(); done(); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('does not accept connections with not defined extension parameter', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('does not accept connections with not defined extension parameter', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var options = { port: port, host: '127.0.0.1', @@ -1065,20 +1065,20 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(400); wss.close(); done(); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); - it('does not accept connections with invalid extension parameter', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { + it('does not accept connections with invalid extension parameter', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { var options = { port: port, host: '127.0.0.1', @@ -1092,16 +1092,16 @@ describe('WebSocketServer', function() { }; var req = http.request(options); req.end(); - req.on('response', function(res) { + req.on('response', function (res) { res.statusCode.should.eql(400); wss.close(); done(); }); }); - wss.on('connection', function(ws) { + wss.on('connection', function (ws) { done(new Error('connection must not be established')); }); - wss.on('error', function() {}); + wss.on('error', function () {}); }); }); }); diff --git a/test/autobahn-server.js b/test/autobahn-server.js index 36fe0c246..74aab5aa3 100644 --- a/test/autobahn-server.js +++ b/test/autobahn-server.js @@ -1,6 +1,6 @@ var WebSocketServer = require('../').Server; -process.on('uncaughtException', function(err) { +process.on('uncaughtException', function (err) { console.log('Caught exception: ', err, err.stack); }); @@ -8,22 +8,22 @@ process.on('SIGINT', function () { try { console.log('Updating reports and shutting down'); var ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); - ws.on('close', function() { + ws.on('close', function () { process.exit(); }); } - catch(e) { + catch (e) { process.exit(); } }); var wss = new WebSocketServer({port: 8181}); -wss.on('connection', function(ws) { +wss.on('connection', function (ws) { console.log('new connection'); - ws.on('message', function(data, flags) { + ws.on('message', function (data, flags) { ws.send(flags.buffer, {binary: flags.binary === true}); }); - ws.on('error', function() { + ws.on('error', function () { console.log('error', arguments); }); }); diff --git a/test/autobahn.js b/test/autobahn.js index 048cc9041..2ea7f1c78 100644 --- a/test/autobahn.js +++ b/test/autobahn.js @@ -3,7 +3,7 @@ var currentTest = 1; var lastTest = -1; var testCount = null; -process.on('uncaughtException', function(err) { +process.on('uncaughtException', function (err) { console.log('Caught exception: ', err, err.stack); }); @@ -11,42 +11,42 @@ process.on('SIGINT', function () { try { console.log('Updating reports and shutting down'); var ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); - ws.on('close', function() { + ws.on('close', function () { process.exit(); }); } - catch(e) { + catch (e) { process.exit(); } }); -function nextTest() { +function nextTest () { if (currentTest > testCount || (lastTest != -1 && currentTest > lastTest)) { console.log('Updating reports and shutting down'); var ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); - ws.on('close', function() { + ws.on('close', function () { process.exit(); }); return; - }; + } console.log('Running test case ' + currentTest + '/' + testCount); var ws = new WebSocket('ws://localhost:9001/runCase?case=' + currentTest + '&agent=ws'); - ws.on('message', function(data, flags) { + ws.on('message', function (data, flags) { ws.send(flags.buffer, {binary: flags.binary === true, mask: true}); }); - ws.on('close', function(data) { + ws.on('close', function (data) { currentTest += 1; process.nextTick(nextTest); }); - ws.on('error', function(e) {}); + ws.on('error', function (e) {}); } var ws = new WebSocket('ws://localhost:9001/getCaseCount'); -ws.on('message', function(data, flags) { +ws.on('message', function (data, flags) { testCount = parseInt(data); }); -ws.on('close', function() { +ws.on('close', function () { if (testCount > 0) { nextTest(); } -}); \ No newline at end of file +}); diff --git a/test/testserver.js b/test/testserver.js index be158b5c4..356509fd7 100644 --- a/test/testserver.js +++ b/test/testserver.js @@ -1,9 +1,9 @@ -var http = require('http') - , util = require('util') - , crypto = require('crypto') - , events = require('events') - , Sender = require('../lib/Sender') - , Receiver = require('../lib/Receiver'); +var http = require('http'), + util = require('util'), + crypto = require('crypto'), + events = require('events'), + Sender = require('../lib/Sender'), + Receiver = require('../lib/Receiver'); module.exports = { handlers: { @@ -12,7 +12,7 @@ module.exports = { closeAfterConnect: closeAfterConnectHandler, return401: return401 }, - createServer: function(port, handler, cb) { + createServer: function (port, handler, cb) { if (handler && !cb) { cb = handler; handler = null; @@ -22,11 +22,11 @@ module.exports = { res.end('okay'); }); var srv = new Server(webServer); - webServer.on('upgrade', function(req, socket) { + webServer.on('upgrade', function (req, socket) { webServer._socket = socket; (handler || validServer)(srv, req, socket); }); - webServer.listen(port, '127.0.0.1', function() { cb(srv); }); + webServer.listen(port, '127.0.0.1', function () { cb(srv); }); } }; @@ -34,7 +34,7 @@ module.exports = { * Test strategies */ -function validServer(server, req, socket) { +function validServer (server, req, socket) { if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { throw new Error('invalid headers'); @@ -49,14 +49,14 @@ function validServer(server, req, socket) { // calc key var key = req.headers['sec-websocket-key']; var shasum = crypto.createHash('sha1'); - shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 'binary'); + shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); key = shasum.digest('base64'); var headers = [ - 'HTTP/1.1 101 Switching Protocols' - , 'Upgrade: websocket' - , 'Connection: Upgrade' - , 'Sec-WebSocket-Accept: ' + key + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: websocket', + 'Connection: Upgrade', + 'Sec-WebSocket-Accept: ' + key ]; socket.write(headers.concat('', '').join('\r\n')); @@ -85,7 +85,7 @@ function validServer(server, req, socket) { }; receiver.onclose = function (code, message, flags) { flags = flags || {}; - sender.close(code, message, false, function(err) { + sender.close(code, message, false, function (err) { server.emit('close', code, message, flags); socket.end(); }); @@ -93,12 +93,12 @@ function validServer(server, req, socket) { socket.on('data', function (data) { receiver.add(data); }); - socket.on('end', function() { + socket.on('end', function () { socket.end(); }); } -function invalidRequestHandler(server, req, socket) { +function invalidRequestHandler (server, req, socket) { if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { throw new Error('invalid headers'); @@ -113,21 +113,21 @@ function invalidRequestHandler(server, req, socket) { // calc key var key = req.headers['sec-websocket-key']; var shasum = crypto.createHash('sha1'); - shasum.update(key + "bogus", 'binary'); + shasum.update(key + 'bogus', 'binary'); key = shasum.digest('base64'); var headers = [ - 'HTTP/1.1 101 Switching Protocols' - , 'Upgrade: websocket' - , 'Connection: Upgrade' - , 'Sec-WebSocket-Accept: ' + key + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: websocket', + 'Connection: Upgrade', + 'Sec-WebSocket-Accept: ' + key ]; socket.write(headers.concat('', '').join('\r\n')); socket.end(); } -function closeAfterConnectHandler(server, req, socket) { +function closeAfterConnectHandler (server, req, socket) { if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { throw new Error('invalid headers'); @@ -142,25 +142,24 @@ function closeAfterConnectHandler(server, req, socket) { // calc key var key = req.headers['sec-websocket-key']; var shasum = crypto.createHash('sha1'); - shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 'binary'); + shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); key = shasum.digest('base64'); var headers = [ - 'HTTP/1.1 101 Switching Protocols' - , 'Upgrade: websocket' - , 'Connection: Upgrade' - , 'Sec-WebSocket-Accept: ' + key + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: websocket', + 'Connection: Upgrade', + 'Sec-WebSocket-Accept: ' + key ]; socket.write(headers.concat('', '').join('\r\n')); socket.end(); } - -function return401(server, req, socket) { +function return401 (server, req, socket) { var headers = [ - 'HTTP/1.1 401 Unauthorized' - , 'Content-type: text/html' + 'HTTP/1.1 401 Unauthorized', + 'Content-type: text/html' ]; socket.write(headers.concat('', '').join('\r\n')); @@ -172,13 +171,13 @@ function return401(server, req, socket) { * Server object, which will do the actual emitting */ -function Server(webServer) { +function Server (webServer) { this.webServer = webServer; } util.inherits(Server, events.EventEmitter); -Server.prototype.close = function() { +Server.prototype.close = function () { this.webServer.close(); if (this._socket) this._socket.end(); -} +}; From fa66b98d562dca70575395e2fb912b3bf27611db Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 26 Oct 2016 18:01:25 +0200 Subject: [PATCH 135/669] [list] Fix remaining lint issues on test/* --- test/PerMessageDeflate.test.js | 13 +- test/Sender.test.js | 11 +- test/WebSocket.integration.js | 31 +- test/WebSocket.test.js | 498 +++++++++++++---------------- test/WebSocketServer.test.js | 566 +++++++++++++++------------------ test/autobahn-server.js | 8 +- test/autobahn.js | 14 +- test/testserver.js | 19 +- 8 files changed, 522 insertions(+), 638 deletions(-) diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index c9608d40a..73e324c16 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -1,14 +1,15 @@ -var PerMessageDeflate = require('../lib/PerMessageDeflate'); -var Extensions = require('../lib/Extensions'); +'use strict'; + +const PerMessageDeflate = require('../lib/PerMessageDeflate'); +const Extensions = require('../lib/Extensions'); require('should'); describe('PerMessageDeflate', function () { describe('#ctor', function () { it('throws TypeError when called without new', function (done) { try { - var perMessageDeflate = PerMessageDeflate(); - } - catch (e) { + PerMessageDeflate(); + } catch (e) { e.should.be.instanceof(TypeError); done(); } @@ -232,7 +233,7 @@ describe('PerMessageDeflate', function () { if (err) return done(err); perMessageDeflate.decompress(compressed2, true, function (err, data2) { if (err) return done(err); - new Buffer.concat([data1, data2]).should.eql(new Buffer([1, 2, 3, 4])); + Buffer.concat([data1, data2]).should.eql(new Buffer([1, 2, 3, 4])); done(); }); }); diff --git a/test/Sender.test.js b/test/Sender.test.js index 862071c04..f81b96d64 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -1,14 +1,15 @@ -var Sender = require('../lib/Sender'), - PerMessageDeflate = require('../lib/PerMessageDeflate'); +'use strict'; + +const Sender = require('../lib/Sender'); +const PerMessageDeflate = require('../lib/PerMessageDeflate'); require('should'); describe('Sender', function () { describe('#ctor', function () { it('throws TypeError when called without new', function (done) { try { - var sender = Sender({ write: function () {} }); - } - catch (e) { + Sender({ write: function () {} }); + } catch (e) { e.should.be.instanceof(TypeError); done(); } diff --git a/test/WebSocket.integration.js b/test/WebSocket.integration.js index c21821566..228ffcb93 100644 --- a/test/WebSocket.integration.js +++ b/test/WebSocket.integration.js @@ -1,31 +1,14 @@ -var assert = require('assert'), - WebSocket = require('../'), - server = require('./testserver'); +'use strict'; -var port = 20000; - -function getArrayBuffer (buf) { - var l = buf.length; - var arrayBuf = new ArrayBuffer(l); - var uint8View = new Uint8Array(arrayBuf); - - for (var i = 0; i < l; i++) { - uint8View[i] = buf[i]; - } - return uint8View.buffer; -} - -function areArraysEqual (x, y) { - if (x.length != y.length) return false; - for (var i = 0, l = x.length; i < l; ++i) { - if (x[i] !== y[i]) return false; - } - return true; -} +const assert = require('assert'); +const WebSocket = require('../'); describe('WebSocket', function () { it('communicates successfully with echo service', function (done) { - var ws = new WebSocket('ws://echo.websocket.org/', {protocolVersion: 13, origin: 'http://websocket.org'}); + var ws = new WebSocket('ws://echo.websocket.org/', { + origin: 'http://websocket.org', + protocolVersion: 13 + }); var str = Date.now().toString(); var dataReceived = false; ws.on('open', function () { diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 6fbbc7801..425689e73 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1,15 +1,19 @@ -var assert = require('assert'), - https = require('https'), - http = require('http'), - should = require('should'), - WebSocket = require('../'), - WebSocketServer = require('../').Server, - fs = require('fs'), - os = require('os'), - server = require('./testserver'), - crypto = require('crypto'); - -var port = 20000; +/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^ws$", "args": "none" }] */ + +'use strict'; + +const assert = require('assert'); +const https = require('https'); +const http = require('http'); +const WebSocket = require('../'); +const fs = require('fs'); +const os = require('os'); +const server = require('./testserver'); +const crypto = require('crypto'); + +const WebSocketServer = WebSocket.Server; + +let port = 20000; function getArrayBuffer (buf) { var l = buf.length; @@ -22,7 +26,7 @@ function getArrayBuffer (buf) { } function areArraysEqual (x, y) { - if (x.length != y.length) return false; + if (x.length !== y.length) return false; for (var i = 0, l = x.length; i < l; ++i) { if (x[i] !== y[i]) return false; } @@ -31,87 +35,58 @@ function areArraysEqual (x, y) { describe('WebSocket', function () { describe('#ctor', function () { - it('throws exception for invalid url', function (done) { - try { - var ws = new WebSocket('echo.websocket.org'); - } - catch (e) { - done(); - } + it('should return a new instance if called without new', function (done) { + var ws = WebSocket('ws://localhost'); + + assert.ok(ws instanceof WebSocket); + ws.on('error', () => done()); }); - it('should return a new instance if called without new', function (done) { - var ws = WebSocket('ws://localhost:' + port); - ws.should.be.an.instanceOf(WebSocket); - done(); + it('throws exception for invalid url', function () { + assert.throws(() => new WebSocket('echo.websocket.org')); }); }); describe('options', function () { it('should accept an `agent` option', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var agent = { - addRequest: function () { - wss.close(); - done(); - } - }; - var ws = new WebSocket('ws://localhost:' + port, { agent: agent }); - }); + const agent = { addRequest: () => done() }; + const ws = new WebSocket('ws://localhost', { agent }); }); + // GH-227 it('should accept the `options` object as the 3rd argument', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var agent = { - addRequest: function () { - wss.close(); - done(); - } - }; - var ws = new WebSocket('ws://localhost:' + port, [], { agent: agent }); - }); + const agent = { addRequest: () => done() }; + const ws = new WebSocket('ws://localhost', [], { agent }); }); it('should accept the localAddress option', function (done) { // explore existing interfaces - var devs = os.networkInterfaces(), - localAddresses = [], - j, ifc, dev, devname; - for (devname in devs) { - dev = devs[devname]; - for (j = 0; j < dev.length; j++) { - ifc = dev[j]; + const devs = os.networkInterfaces(); + const localAddresses = []; + + Object.keys(devs).forEach((name) => { + devs[name].forEach((ifc) => { if (!ifc.internal && ifc.family === 'IPv4') { localAddresses.push(ifc.address); } - } - } - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port, { localAddress: localAddresses[0] }); - ws.on('open', function () { - done(); }); }); - }); - it('should accept the localAddress option whether it was wrong interface', function (done) { - if (process.platform === 'linux' && process.version.match(/^v0\.([0-9]\.|10)/)) { - return done(); - } - var wss = new WebSocketServer({port: ++port}, function () { - try { - var ws = new WebSocket('ws://localhost:' + port, { localAddress: '123.456.789.428' }); - ws.on('error', function (error) { - error.code.should.eql('EADDRNOTAVAIL'); - done(); - }); - } - catch (e) { - e.should.match(/localAddress must be a valid IP/); - done(); - } + const wss = new WebSocketServer({ port: ++port }, function () { + const ws = new WebSocket(`ws://localhost:${port}`, { + localAddress: localAddresses[0] + }); + + ws.on('open', () => wss.close(done)); }); }); + + it('should accept the localAddress option whether it was wrong interface', function () { + assert.throws( + () => new WebSocket(`ws://localhost:${port}`, { localAddress: '123.456.789.428' }), + /must be a valid IP: 123.456.789.428/ + ); + }); }); describe('properties', function () { @@ -119,7 +94,7 @@ describe('WebSocket', function () { var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port, { perMessageDeflate: false }); ws.on('message', function () { - ws.bytesReceived.should.eql(8); + assert.strictEqual(ws.bytesReceived, 8); wss.close(); done(); }); @@ -185,19 +160,16 @@ describe('WebSocket', function () { }); it('stress kernel write buffer', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port, { perMessageDeflate: false }); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: false }); }); - wss.on('connection', function (ws) { + + wss.on('connection', (ws) => { while (true) { if (ws.bufferedAmount > 0) break; - ws.send((new Array(10000)).join('hello')); + ws.send('hello'.repeat(1e4)); } - ws.terminate(); - ws.on('close', function () { - wss.close(); - done(); - }); + wss.close(done); }); }); }); @@ -205,7 +177,7 @@ describe('WebSocket', function () { describe('Custom headers', function () { it('request has an authorization header', function (done) { var auth = 'test:testpass'; - var srv = http.createServer(function (req, res) {}); + var srv = http.createServer(); var wss = new WebSocketServer({server: srv}); srv.listen(++port); var ws = new WebSocket('ws://' + auth + '@localhost:' + port); @@ -222,7 +194,7 @@ describe('WebSocket', function () { }); it('accepts custom headers', function (done) { - var srv = http.createServer(function (req, res) {}); + var srv = http.createServer(); var wss = new WebSocketServer({server: srv}); srv.listen(++port); @@ -523,10 +495,10 @@ describe('WebSocket', function () { var serverClient; var openCount = 0; function onOpen () { - if (++openCount == 2) { + if (++openCount === 2) { var paused = true; serverClient.on('message', function () { - paused.should.not.be.ok; + assert.ok(!paused); wss.close(); done(); }); @@ -557,8 +529,7 @@ describe('WebSocket', function () { ws.on('error', function () {}); try { ws.ping(); - } - catch (e) { + } catch (e) { srv.close(); ws.terminate(); done(); @@ -583,7 +554,7 @@ describe('WebSocket', function () { ws.on('open', function () { ws.ping(); }); - srv.on('ping', function (message) { + srv.on('ping', function () { srv.close(); ws.terminate(); done(); @@ -647,8 +618,7 @@ describe('WebSocket', function () { ws.on('error', function () {}); try { ws.pong(); - } - catch (e) { + } catch (e) { srv.close(); ws.terminate(); done(); @@ -673,7 +643,7 @@ describe('WebSocket', function () { ws.on('open', function () { ws.pong(); }); - srv.on('pong', function (message) { + srv.on('pong', function () { srv.close(); ws.terminate(); done(); @@ -842,8 +812,7 @@ describe('WebSocket', function () { ws.on('error', function () {}); try { ws.send('hi'); - } - catch (e) { + } catch (e) { ws.terminate(); srv.close(); done(); @@ -1020,15 +989,13 @@ describe('WebSocket', function () { var receivedIndex = 0; srv.on('message', function (data, flags) { ++receivedIndex; - if (receivedIndex == 1) { + if (receivedIndex === 1) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); - } - else if (receivedIndex == 2) { + } else if (receivedIndex === 2) { assert.ok(!flags.binary); assert.equal('foobar', data); - } - else { + } else { assert.ok(!flags.binary); assert.equal('baz', data); srv.close(); @@ -1048,18 +1015,17 @@ describe('WebSocket', function () { var i = 0; ws.stream(function (error, send) { assert.ok(!error); - if (++i == 1) send('foo'); + if (++i === 1) send('foo'); else send('bar', true); }); }); var receivedIndex = 0; srv.on('message', function (data, flags) { ++receivedIndex; - if (receivedIndex == 1) { + if (receivedIndex === 1) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); - } - else if (receivedIndex == 2) { + } else if (receivedIndex === 2) { assert.ok(!flags.binary); assert.equal('foobar', data); srv.close(); @@ -1082,7 +1048,7 @@ describe('WebSocket', function () { srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); - if (++receivedIndex == 2) { + if (++receivedIndex === 2) { srv.close(); ws.terminate(); done(); @@ -1090,7 +1056,7 @@ describe('WebSocket', function () { }); srv.on('ping', function (data) { assert.equal('foobar', data); - if (++receivedIndex == 2) { + if (++receivedIndex === 2) { srv.close(); ws.terminate(); done(); @@ -1111,7 +1077,7 @@ describe('WebSocket', function () { srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); - if (++receivedIndex == 2) { + if (++receivedIndex === 2) { srv.close(); ws.terminate(); done(); @@ -1119,7 +1085,7 @@ describe('WebSocket', function () { }); srv.on('pong', function (data) { assert.equal('foobar', data); - if (++receivedIndex == 2) { + if (++receivedIndex === 2) { srv.close(); ws.terminate(); done(); @@ -1200,12 +1166,10 @@ describe('WebSocket', function () { it('without callback should fail', function (done) { server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - var payload = 'HelloWorld'; ws.on('open', function () { try { ws.stream(); - } - catch (e) { + } catch (e) { srv.close(); ws.terminate(); done(); @@ -1222,12 +1186,11 @@ describe('WebSocket', function () { var i = 0; ws.stream(function (error, send) { assert.ok(!error); - if (++i == 1) { + if (++i === 1) { send(payload.substr(0, 5)); ws.send('foobar'); ws.send('baz'); - } - else { + } else { send(payload.substr(5, 5), true); } }); @@ -1235,15 +1198,13 @@ describe('WebSocket', function () { var receivedIndex = 0; srv.on('message', function (data, flags) { ++receivedIndex; - if (receivedIndex == 1) { + if (receivedIndex === 1) { assert.ok(!flags.binary); assert.equal(payload, data); - } - else if (receivedIndex == 2) { + } else if (receivedIndex === 2) { assert.ok(!flags.binary); assert.equal('foobar', data); - } - else { + } else { assert.ok(!flags.binary); assert.equal('baz', data); srv.close(); @@ -1262,31 +1223,30 @@ describe('WebSocket', function () { var i = 0; ws.stream(function (error, send) { assert.ok(!error); - if (++i == 1) { + if (++i === 1) { send(payload.substr(0, 5)); var i2 = 0; ws.stream(function (error, send) { assert.ok(!error); - if (++i2 == 1) send('foo'); + if (++i2 === 1) send('foo'); else send('bar', true); }); ws.send('baz'); + } else { + send(payload.substr(5, 5), true); } - else send(payload.substr(5, 5), true); }); }); var receivedIndex = 0; srv.on('message', function (data, flags) { ++receivedIndex; - if (receivedIndex == 1) { + if (receivedIndex === 1) { assert.ok(!flags.binary); assert.equal(payload, data); - } - else if (receivedIndex == 2) { + } else if (receivedIndex === 2) { assert.ok(!flags.binary); assert.equal('foobar', data); - } - else if (receivedIndex == 3) { + } else if (receivedIndex === 3) { assert.ok(!flags.binary); assert.equal('baz', data); setTimeout(function () { @@ -1294,8 +1254,9 @@ describe('WebSocket', function () { ws.terminate(); done(); }, 1000); + } else { + throw new Error('more messages than we actually sent just arrived'); } - else throw new Error('more messages than we actually sent just arrived'); }); }); }); @@ -1308,11 +1269,10 @@ describe('WebSocket', function () { var i = 0; ws.stream(function (error, send) { assert.ok(!error); - if (++i == 1) { + if (++i === 1) { send(payload.substr(0, 5)); ws.ping('foobar'); - } - else { + } else { send(payload.substr(5, 5), true); } }); @@ -1321,7 +1281,7 @@ describe('WebSocket', function () { srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.equal(payload, data); - if (++receivedIndex == 2) { + if (++receivedIndex === 2) { srv.close(); ws.terminate(); done(); @@ -1329,7 +1289,7 @@ describe('WebSocket', function () { }); srv.on('ping', function (data) { assert.equal('foobar', data); - if (++receivedIndex == 2) { + if (++receivedIndex === 2) { srv.close(); ws.terminate(); done(); @@ -1346,11 +1306,10 @@ describe('WebSocket', function () { var i = 0; ws.stream(function (error, send) { assert.ok(!error); - if (++i == 1) { + if (++i === 1) { send(payload.substr(0, 5)); ws.pong('foobar'); - } - else { + } else { send(payload.substr(5, 5), true); } }); @@ -1359,7 +1318,7 @@ describe('WebSocket', function () { srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.equal(payload, data); - if (++receivedIndex == 2) { + if (++receivedIndex === 2) { srv.close(); ws.terminate(); done(); @@ -1367,7 +1326,7 @@ describe('WebSocket', function () { }); srv.on('pong', function (data) { assert.equal('foobar', data); - if (++receivedIndex == 2) { + if (++receivedIndex === 2) { srv.close(); ws.terminate(); done(); @@ -1384,14 +1343,12 @@ describe('WebSocket', function () { ws.on('open', function () { var i = 0; ws.stream(function (error, send) { - if (++i == 1) { + if (++i === 1) { send(payload.substr(0, 5)); ws.close(1000, 'foobar'); - } - else if (i == 2) { + } else if (i === 2) { send(payload.substr(5, 5), true); - } - else if (i == 3) { + } else if (i === 3) { assert.ok(error); errorGiven = true; } @@ -1444,8 +1401,7 @@ describe('WebSocket', function () { ws.on('open', function () { try { ws.close('error'); - } - catch (e) { + } catch (e) { srv.close(); ws.terminate(); done(); @@ -1460,8 +1416,7 @@ describe('WebSocket', function () { ws.on('open', function () { try { ws.close(1004); - } - catch (e) { + } catch (e) { srv.close(); ws.terminate(); done(); @@ -1476,7 +1431,7 @@ describe('WebSocket', function () { ws.on('open', function () { ws.close(1000); }); - srv.on('close', function (code, message, flags) { + srv.on('close', function (code, message) { assert.equal('', message); srv.close(); ws.terminate(); @@ -1600,7 +1555,6 @@ describe('WebSocket', function () { it('should work the same as the EventEmitter api', function (done) { server.createServer(++port, function (srv) { var ws = new WebSocket('ws://localhost:' + port); - var listener = function () {}; var message = 0; var close = 0; var open = 0; @@ -1796,139 +1750,113 @@ describe('WebSocket', function () { describe('ssl', function () { it('can connect to secure websocket server', function (done) { - var options = { - key: fs.readFileSync('test/fixtures/key.pem'), - cert: fs.readFileSync('test/fixtures/certificate.pem') - }; - var app = https.createServer(options, function (req, res) { - res.writeHead(200); - res.end(); - }); - var wss = new WebSocketServer({server: app}); - app.listen(++port, function () { - var ws = new WebSocket('wss://localhost:' + port); + const server = https.createServer({ + cert: fs.readFileSync('test/fixtures/certificate.pem'), + key: fs.readFileSync('test/fixtures/key.pem') }); - wss.on('connection', function (ws) { - app.close(); - ws.terminate(); + const wss = new WebSocketServer({ server }); + + wss.on('connection', (ws) => { wss.close(); - done(); + server.close(done); }); + + server.listen(++port, () => new WebSocket('wss://localhost:' + port)); }); it('can connect to secure websocket server with client side certificate', function (done) { - var options = { - key: fs.readFileSync('test/fixtures/key.pem'), + const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), ca: [fs.readFileSync('test/fixtures/ca1-cert.pem')], + key: fs.readFileSync('test/fixtures/key.pem'), requestCert: true - }; - var clientOptions = { - key: fs.readFileSync('test/fixtures/agent1-key.pem'), - cert: fs.readFileSync('test/fixtures/agent1-cert.pem') - }; - var app = https.createServer(options, function (req, res) { - res.writeHead(200); - res.end(); - }); - var success = false; - var wss = new WebSocketServer({ - server: app, - verifyClient: function (info) { + }); + + let success = false; + const wss = new WebSocketServer({ + verifyClient: (info) => { success = !!info.req.client.authorized; return true; - } - }); - app.listen(++port, function () { - var ws = new WebSocket('wss://localhost:' + port, clientOptions); + }, + server }); - wss.on('connection', function (ws) { - app.close(); - ws.terminate(); + + wss.on('connection', (ws) => { + assert.ok(success); + server.close(done); wss.close(); - success.should.be.ok; - done(); + }); + + server.listen(++port, () => { + const ws = new WebSocket(`wss://localhost:${port}`, { + cert: fs.readFileSync('test/fixtures/agent1-cert.pem'), + key: fs.readFileSync('test/fixtures/agent1-key.pem') + }); }); }); it('cannot connect to secure websocket server via ws://', function (done) { - var options = { - key: fs.readFileSync('test/fixtures/key.pem'), - cert: fs.readFileSync('test/fixtures/certificate.pem') - }; - var app = https.createServer(options, function (req, res) { - res.writeHead(200); - res.end(); - }); - var wss = new WebSocketServer({server: app}); - app.listen(++port, function () { - var ws = new WebSocket('ws://localhost:' + port, { rejectUnauthorized: false }); - ws.on('error', function () { - app.close(); - ws.terminate(); + const server = https.createServer({ + cert: fs.readFileSync('test/fixtures/certificate.pem'), + key: fs.readFileSync('test/fixtures/key.pem') + }); + const wss = new WebSocketServer({ server }); + + server.listen(++port, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + rejectUnauthorized: false + }); + + ws.on('error', () => { + server.close(done); wss.close(); - done(); }); }); }); it('can send and receive text data', function (done) { - var options = { - key: fs.readFileSync('test/fixtures/key.pem'), - cert: fs.readFileSync('test/fixtures/certificate.pem') - }; - var app = https.createServer(options, function (req, res) { - res.writeHead(200); - res.end(); - }); - var wss = new WebSocketServer({server: app}); - app.listen(++port, function () { - var ws = new WebSocket('wss://localhost:' + port); - ws.on('open', function () { - ws.send('foobar'); - }); + const server = https.createServer({ + cert: fs.readFileSync('test/fixtures/certificate.pem'), + key: fs.readFileSync('test/fixtures/key.pem') }); - wss.on('connection', function (ws) { - ws.on('message', function (message, flags) { - message.should.eql('foobar'); - app.close(); - ws.terminate(); + const wss = new WebSocketServer({ server }); + + wss.on('connection', (ws) => { + ws.on('message', (message, flags) => { + assert.strictEqual(message, 'foobar'); + server.close(done); wss.close(); - done(); }); }); + + server.listen(++port, () => { + const ws = new WebSocket(`wss://localhost:${port}`); + + ws.on('open', () => ws.send('foobar')); + }); }); it('can send and receive very long binary data', function (done) { - var options = { - key: fs.readFileSync('test/fixtures/key.pem'), - cert: fs.readFileSync('test/fixtures/certificate.pem') - }; - var app = https.createServer(options, function (req, res) { - res.writeHead(200); - res.end(); - }); - crypto.randomBytes(5 * 1024 * 1024, function (ex, buf) { - if (ex) throw ex; - var wss = new WebSocketServer({server: app}); - app.listen(++port, function () { - var ws = new WebSocket('wss://localhost:' + port); - ws.on('open', function () { - ws.send(buf, {binary: true}); - }); - ws.on('message', function (message, flags) { - flags.binary.should.be.ok; - areArraysEqual(buf, message).should.be.ok; - app.close(); - ws.terminate(); - wss.close(); - done(); - }); - }); - wss.on('connection', function (ws) { - ws.on('message', function (message, flags) { - ws.send(message, {binary: true}); - }); + const buf = crypto.randomBytes(5 * 1024 * 1024); + const server = https.createServer({ + cert: fs.readFileSync('test/fixtures/certificate.pem'), + key: fs.readFileSync('test/fixtures/key.pem') + }); + const wss = new WebSocketServer({ server }); + + wss.on('connection', (ws) => { + ws.on('message', (message) => ws.send(message)); + }); + + server.listen(++port, () => { + const ws = new WebSocket('wss://localhost:' + port); + + ws.on('open', () => ws.send(buf)); + ws.on('message', (message, flags) => { + assert.strictEqual(flags.binary, true); + assert.ok(buf.equals(message)); + server.close(done); + wss.close(); }); }); }); @@ -1938,11 +1866,11 @@ describe('WebSocket', function () { describe('#supports', function () { describe('#binary', function () { it('returns true', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); }); - wss.on('connection', function (client) { - assert.equal(true, client.supports.binary); + wss.on('connection', (client) => { + assert.strictEqual(client.supports.binary, true); wss.close(); done(); }); @@ -1953,43 +1881,50 @@ describe('WebSocket', function () { describe('host and origin headers', function () { it('includes the host header with port number', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - srv.on('upgrade', function (req, socket, upgradeHeade) { - assert.equal('localhost:' + port, req.headers['host']); - srv.close(); - done(); + const server = http.createServer(); + + server.listen(++port, () => { + server.on('upgrade', (req, socket, head) => { + assert.strictEqual(req.headers['host'], `localhost:${port}`); + server.close(done); + socket.destroy(); }); - var ws = new WebSocket('ws://localhost:' + port); + + const ws = new WebSocket(`ws://localhost:${port}`); }); }); it('lacks default origin header', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - srv.on('upgrade', function (req, socket, upgradeHeade) { - should(req.headers).not.have.property('origin'); - srv.close(); - done(); + const server = http.createServer(); + + server.listen(++port, () => { + server.on('upgrade', (req, socket, head) => { + assert.strictEqual(req.headers['origin'], undefined); + server.close(done); + socket.destroy(); }); - var ws = new WebSocket('ws://localhost:' + port); + + const ws = new WebSocket(`ws://localhost:${port}`); }); }); it('honors origin set in options', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - var options = {origin: 'https://example.com:8000'}; - srv.on('upgrade', function (req, socket, upgradeHeade) { - assert.equal(options.origin, req.headers['origin']); - srv.close(); - done(); + const server = http.createServer(); + + server.listen(++port, function () { + const options = { origin: 'https://example.com:8000' }; + + server.on('upgrade', function (req, socket, head) { + assert.strictEqual(req.headers['origin'], options.origin); + server.close(done); + socket.destroy(); }); - var ws = new WebSocket('ws://localhost:' + port, options); + + const ws = new WebSocket(`ws://localhost:${port}`, options); }); }); - it('excludes default ports from host header', function (done) { + it('excludes default ports from host header', function () { // can't create a server listening on ports 80 or 443 // so we need to expose the method that does this var buildHostHeader = WebSocket.buildHostHeader; @@ -2001,13 +1936,12 @@ describe('WebSocket', function () { assert.equal('localhost', host); host = buildHostHeader(true, 'localhost', 8443); assert.equal('localhost:8443', host); - done(); }); }); describe('permessage-deflate', function () { it('is enabled by default', function (done) { - var srv = http.createServer(function (req, res) {}); + var srv = http.createServer(); var wss = new WebSocketServer({server: srv, perMessageDeflate: true}); srv.listen(++port, function () { var ws = new WebSocket('ws://localhost:' + port); @@ -2024,7 +1958,7 @@ describe('WebSocket', function () { }); it('can be disabled', function (done) { - var srv = http.createServer(function (req, res) {}); + var srv = http.createServer(); var wss = new WebSocketServer({server: srv, perMessageDeflate: true}); srv.listen(++port, function () { var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: false}); @@ -2038,7 +1972,7 @@ describe('WebSocket', function () { }); it('can send extension parameters', function (done) { - var srv = http.createServer(function (req, res) {}); + var srv = http.createServer(); var wss = new WebSocketServer({server: srv, perMessageDeflate: true}); srv.listen(++port, function () { var ws = new WebSocket('ws://localhost:' + port, { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 347d4e110..35f4d9c5b 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -1,73 +1,37 @@ -var http = require('http'), - https = require('https'), - WebSocket = require('../'), - WebSocketServer = WebSocket.Server, - fs = require('fs'), - should = require('should'); - -var port = 8000; - -function getArrayBuffer (buf) { - var l = buf.length; - var arrayBuf = new ArrayBuffer(l); - for (var i = 0; i < l; ++i) { - arrayBuf[i] = buf[i]; - } - return arrayBuf; -} - -function areArraysEqual (x, y) { - if (x.length != y.length) return false; - for (var i = 0, l = x.length; i < l; ++i) { - if (x[i] !== y[i]) return false; - } - return true; -} +/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^ws$", "args": "none" }] */ + +'use strict'; + +const assert = require('assert'); +const WebSocket = require('../'); +const https = require('https'); +const http = require('http'); +const fs = require('fs'); +require('should'); + +const WebSocketServer = WebSocket.Server; + +let port = 8000; describe('WebSocketServer', function () { describe('#ctor', function () { - it('should return a new instance if called without new', function (done) { - var ws = WebSocketServer({noServer: true}); - ws.should.be.an.instanceOf(WebSocketServer); - done(); - }); - it('throws an error if no option object is passed', function () { - var gotException = false; - try { - var wss = new WebSocketServer(); - } - catch (e) { - gotException = true; - } - gotException.should.be.ok; + assert.throws(() => new WebSocketServer()); }); it('throws an error if no port or server is specified', function () { - var gotException = false; - try { - var wss = new WebSocketServer({}); - } - catch (e) { - gotException = true; - } - gotException.should.be.ok; + assert.throws(() => new WebSocketServer({})); }); - it('does not throw an error if no port or server is specified, when the noServer option is true', function () { - var gotException = false; - try { - var wss = new WebSocketServer({noServer: true}); - } - catch (e) { - gotException = true; - } - gotException.should.eql(false); + it('should return a new instance if called without new', function () { + var wss = WebSocketServer({ noServer: true }); + + assert.ok(wss instanceof WebSocketServer); }); it('emits an error if http server bind fails', function (done) { - var wss1 = new WebSocketServer({port: 50003}); - var wss2 = new WebSocketServer({port: 50003}); + var wss1 = new WebSocketServer({ port: 50003 }); + var wss2 = new WebSocketServer({ port: 50003 }); wss2.on('error', function () { wss1.close(); done(); @@ -85,15 +49,15 @@ describe('WebSocketServer', function () { }); it('uses a precreated http server', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - var wss = new WebSocketServer({server: srv}); - var ws = new WebSocket('ws://localhost:' + port); + const server = http.createServer(); + + server.listen(++port, () => { + const wss = new WebSocketServer({ server }); + const ws = new WebSocket(`ws://localhost:${port}`); wss.on('connection', function (client) { wss.close(); - srv.close(); - done(); + server.close(done); }); }); }); @@ -103,10 +67,10 @@ describe('WebSocketServer', function () { http.get('http://localhost:' + port, function (res) { var body = ''; - res.statusCode.should.equal(426); + assert.strictEqual(res.statusCode, 426); res.on('data', function (chunk) { body += chunk; }); res.on('end', function () { - body.should.equal(http.STATUS_CODES[426]); + assert.strictEqual(body, http.STATUS_CODES[426]); wss.close(); done(); }); @@ -117,87 +81,89 @@ describe('WebSocketServer', function () { // Don't test this on Windows. It throws errors for obvious reasons. if (!/^win/i.test(process.platform)) { it('uses a precreated http server listening on unix socket', function (done) { - var srv = http.createServer(); - var sockPath = '/tmp/ws_socket_' + new Date().getTime() + '.' + Math.floor(Math.random() * 1000); - srv.listen(sockPath, function () { - var wss = new WebSocketServer({server: srv}); - var ws = new WebSocket('ws+unix://' + sockPath); + const server = http.createServer(); + const sockPath = `/tmp/ws_socket_${new Date().getTime()}.${Math.floor(Math.random() * 1000)}`; + + server.listen(sockPath, () => { + const wss = new WebSocketServer({ server }); + const ws = new WebSocket(`ws+unix://${sockPath}`); - wss.on('connection', function (client) { + wss.on('connection', (ws) => { wss.close(); - srv.close(); - done(); + server.close(done); }); }); }); } it('emits path specific connection event', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - var wss = new WebSocketServer({server: srv}); - var ws = new WebSocket('ws://localhost:' + port + '/endpointName'); + const server = http.createServer(); + + server.listen(++port, () => { + const wss = new WebSocketServer({ server }); + const ws = new WebSocket(`ws://localhost:${port}/endpointName`); - wss.on('connection/endpointName', function (client) { + wss.on('connection/endpointName', (ws) => { wss.close(); - srv.close(); - done(); + server.close(done); }); }); }); it('can have two different instances listening on the same http server with two different paths', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - var wss1 = new WebSocketServer({server: srv, path: '/wss1'}), - wss2 = new WebSocketServer({server: srv, path: '/wss2'}); - var doneCount = 0; - wss1.on('connection', function (client) { + const server = http.createServer(); + + server.listen(++port, () => { + const wss1 = new WebSocketServer({ server, path: '/wss1' }); + const wss2 = new WebSocketServer({ server, path: '/wss2' }); + let doneCount = 0; + + wss1.on('connection', (client) => { wss1.close(); - if (++doneCount == 2) { - srv.close(); - done(); + if (++doneCount === 2) { + server.close(done); } }); - wss2.on('connection', function (client) { + + wss2.on('connection', (client) => { wss2.close(); - if (++doneCount == 2) { - srv.close(); - done(); + if (++doneCount === 2) { + server.close(done); } }); - var ws1 = new WebSocket('ws://localhost:' + port + '/wss1'); - var ws2 = new WebSocket('ws://localhost:' + port + '/wss2?foo=1'); + + /* eslint-disable no-unused-vars */ + const ws1 = new WebSocket(`ws://localhost:${port}/wss1`); + const ws2 = new WebSocket(`ws://localhost:${port}/wss2?foo=1`); + /* eslint-enable no-unused-vars */ }); }); it('cannot have two different instances listening on the same http server with the same path', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - var wss1 = new WebSocketServer({server: srv, path: '/wss1'}); - try { - var wss2 = new WebSocketServer({server: srv, path: '/wss1'}); - } - catch (e) { - wss1.close(); - srv.close(); - done(); - } - }); + const server = http.createServer(); + const wss1 = new WebSocketServer({ server: server, path: '/wss1' }); + + try { + // eslint-disable-next-line no-unused-vars + const wss2 = new WebSocketServer({ server: server, path: '/wss1' }); + } catch (e) { + wss1.close(); + done(); + } }); + it('will not crash when it receives an unhandled opcode', function (done) { - var wss = new WebSocketServer({ port: 8080 }); - wss.on('connection', function connection (ws) { - ws.onerror = function (error) { - done(); - }; + const wss = new WebSocketServer({ port: 8080 }); + + wss.on('connection', (ws) => { + ws.onerror = () => done(); }); - var socket = new WebSocket('ws://127.0.0.1:8080/'); + const ws = new WebSocket('ws://localhost:8080/'); - socket.onopen = function () { - socket._socket.write(new Buffer([5])); - socket.send(''); + ws.onopen = () => { + ws._socket.write(new Buffer([5])); + ws.send(''); }; }); }); @@ -217,61 +183,66 @@ describe('WebSocketServer', function () { var wss = new WebSocketServer({port: ++port}, function () { var ws = new WebSocket('ws://localhost:' + port); ws.on('close', function () { - if (++closes == 2) done(); + if (++closes === 2) done(); }); }); var closes = 0; wss.on('connection', function (client) { client.on('close', function () { - if (++closes == 2) done(); + if (++closes === 2) done(); }); wss.close(); }); }); it('does not close a precreated server', function (done) { - var srv = http.createServer(); - var realClose = srv.close; - srv.close = function () { - should.fail('must not close pre-created server'); + const server = http.createServer(); + const realClose = server.close; + + server.close = () => { + throw new Error('must not close pre-created server'); }; - srv.listen(++port, function () { - var wss = new WebSocketServer({server: srv}); - var ws = new WebSocket('ws://localhost:' + port); - wss.on('connection', function (client) { - wss.close(); - srv.close = realClose; - srv.close(); - done(); - }); + + const wss = new WebSocketServer({ server }); + + wss.on('connection', function (ws) { + wss.close(); + server.close = realClose; + server.close(done); + }); + + server.listen(++port, () => { + const ws = new WebSocket(`ws://localhost:${port}`); }); }); it('cleans event handlers on precreated server', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - var wss = new WebSocketServer({server: srv}); + const server = http.createServer(); + + server.listen(++port, () => { + const wss = new WebSocketServer({ server }); wss.close(); - srv.emit('upgrade'); - srv.on('error', function () {}); - srv.emit('error'); - done(); + + assert.strictEqual(server.listeners('listening').length, 0); + assert.strictEqual(server.listeners('upgrade').length, 0); + assert.strictEqual(server.listeners('error').length, 0); + + server.close(done); }); }); it('cleans up websocket data on a precreated server', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - var wss1 = new WebSocketServer({server: srv, path: '/wss1'}), - wss2 = new WebSocketServer({server: srv, path: '/wss2'}); + const srv = http.createServer(); + srv.listen(++port, () => { + const wss1 = new WebSocketServer({server: srv, path: '/wss1'}); + const wss2 = new WebSocketServer({server: srv, path: '/wss2'}); (typeof srv._webSocketPaths).should.eql('object'); Object.keys(srv._webSocketPaths).length.should.eql(2); wss1.close(); Object.keys(srv._webSocketPaths).length.should.eql(1); wss2.close(); (typeof srv._webSocketPaths).should.eql('undefined'); - srv.close(); - done(); + srv.close(done); }); }); }); @@ -526,10 +497,11 @@ describe('WebSocketServer', function () { }); it('client can be denied', function (done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function (o) { - return false; - }}, function () { - var options = { + const wss = new WebSocketServer({ + verifyClient: (o) => false, + port: ++port + }, () => { + const req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -539,28 +511,28 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 8, 'Sec-WebSocket-Origin': 'http://foobar.com' } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { - res.statusCode.should.eql(401); - process.nextTick(function () { - wss.close(); - done(); - }); }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 401); + wss.close(); + done(); + }); + + req.end(); }); - wss.on('connection', function (ws) { + + wss.on('connection', (ws) => { done(new Error('connection must not be established')); }); - wss.on('error', function () {}); }); it('client can be accepted', function (done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function (o) { - return true; - }}, function () { - var options = { + var wss = new WebSocketServer({ + port: ++port, + verifyClient: (o) => true + }, () => { + var req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -570,26 +542,28 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 13, 'Origin': 'http://foobar.com' } - }; - var req = http.request(options); + }); req.end(); }); + wss.on('connection', function (ws) { ws.terminate(); wss.close(); done(); }); - wss.on('error', function () {}); }); it('verifyClient gets client origin', function (done) { var verifyClientCalled = false; - var wss = new WebSocketServer({port: ++port, verifyClient: function (info) { - info.origin.should.eql('http://foobarbaz.com'); - verifyClientCalled = true; - return false; - }}, function () { - var options = { + var wss = new WebSocketServer({ + verifyClient: (info) => { + info.origin.should.eql('http://foobarbaz.com'); + verifyClientCalled = true; + return false; + }, + port: ++port + }, () => { + var req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -599,26 +573,27 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 13, 'Origin': 'http://foobarbaz.com' } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { + }); + req.on('response', (res) => { verifyClientCalled.should.be.ok; wss.close(); done(); }); + req.end(); }); - wss.on('error', function () {}); }); it('verifyClient gets original request', function (done) { var verifyClientCalled = false; - var wss = new WebSocketServer({port: ++port, verifyClient: function (info) { - info.req.headers['sec-websocket-key'].should.eql('dGhlIHNhbXBsZSBub25jZQ=='); - verifyClientCalled = true; - return false; - }}, function () { - var options = { + var wss = new WebSocketServer({ + verifyClient: (info) => { + info.req.headers['sec-websocket-key'].should.eql('dGhlIHNhbXBsZSBub25jZQ=='); + verifyClientCalled = true; + return false; + }, + port: ++port + }, () => { + var req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -628,16 +603,14 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 13, 'Origin': 'http://foobarbaz.com' } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { + }); + req.on('response', (res) => { verifyClientCalled.should.be.ok; wss.close(); done(); }); + req.end(); }); - wss.on('error', function () {}); }); it('verifyClient has secure:true for ssl connections', function (done) { @@ -695,12 +668,11 @@ describe('WebSocketServer', function () { }); it('client can be denied asynchronously', function (done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function (o, cb) { - process.nextTick(function () { - cb(false); - }); - }}, function () { - var options = { + const wss = new WebSocketServer({ + verifyClient: (o, cb) => process.nextTick(cb, false), + port: ++port + }, () => { + const req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -710,30 +682,26 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 8, 'Sec-WebSocket-Origin': 'http://foobar.com' } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { + }); + req.on('response', (res) => { res.statusCode.should.eql(401); - process.nextTick(function () { - wss.close(); - done(); - }); + wss.close(); + done(); }); + req.end(); }); - wss.on('connection', function (ws) { + + wss.on('connection', (ws) => { done(new Error('connection must not be established')); }); - wss.on('error', function () {}); }); it('client can be denied asynchronously with custom response code', function (done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function (o, cb) { - process.nextTick(function () { - cb(false, 404); - }); - }}, function () { - var options = { + const wss = new WebSocketServer({ + verifyClient: (o, cb) => process.nextTick(cb, false, 404), + port: ++port + }, () => { + const req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -743,30 +711,26 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 8, 'Sec-WebSocket-Origin': 'http://foobar.com' } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { + }); + req.on('response', (res) => { res.statusCode.should.eql(404); - process.nextTick(function () { - wss.close(); - done(); - }); + wss.close(); + done(); }); + req.end(); }); - wss.on('connection', function (ws) { + + wss.on('connection', (ws) => { done(new Error('connection must not be established')); }); - wss.on('error', function () {}); }); it('client can be accepted asynchronously', function (done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function (o, cb) { - process.nextTick(function () { - cb(true); - }); - }}, function () { - var options = { + const wss = new WebSocketServer({ + verifyClient: (o, cb) => process.nextTick(cb, true), + port: ++port + }, () => { + const req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -776,23 +740,19 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 13, 'Origin': 'http://foobar.com' } - }; - var req = http.request(options); + }); req.end(); }); - wss.on('connection', function (ws) { + wss.on('connection', (ws) => { ws.terminate(); wss.close(); done(); }); - wss.on('error', function () {}); }); it('handles messages passed along with the upgrade request (upgrade head)', function (done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function (o) { - return true; - }}, function () { - var options = { + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -802,20 +762,19 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 13, 'Origin': 'http://foobar.com' } - }; - var req = http.request(options); - req.write(new Buffer([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f], 'binary')); + }); + req.write(Buffer.from([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f])); req.end(); }); - wss.on('connection', function (ws) { - ws.on('message', function (data) { + + wss.on('connection', (ws) => { + ws.on('message', (data) => { data.should.eql('Hello'); ws.terminate(); wss.close(); done(); }); }); - wss.on('error', function () {}); }); it('selects the first protocol by default', function (done) { @@ -830,61 +789,71 @@ describe('WebSocketServer', function () { }); it('selects the last protocol via protocol handler', function (done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { - cb(true, ps[ps.length - 1]); }}, function () { - var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function (client) { - ws.protocol.should.eql('prot2'); - wss.close(); - done(); - }); + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(true, ps[ps.length - 1]), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); + + ws.on('open', () => { + ws.protocol.should.eql('prot2'); + wss.close(); + done(); }); + }); }); it('client detects invalid server protocol', function (done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { - cb(true, 'prot3'); }}, function () { - var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function (client) { - done(new Error('connection must not be established')); - }); - ws.on('error', function () { - done(); - }); + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(true, 'prot3'), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); + + ws.on('open', () => done(new Error('connection must not be established'))); + ws.on('error', () => { + wss.close(); + done(); }); + }); }); it('client detects no server protocol', function (done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { - cb(true); }}, function () { - var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function (client) { - done(new Error('connection must not be established')); - }); - ws.on('error', function () { - done(); - }); + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(true), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); + + ws.on('open', () => done(new Error('connection must not be established'))); + ws.on('error', () => { + wss.close(); + done(); }); + }); }); it('client refuses server protocols', function (done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { - cb(false); }}, function () { - var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function (client) { - done(new Error('connection must not be established')); - }); - ws.on('error', function () { - done(); - }); + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(false), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); + + ws.on('open', () => done(new Error('connection must not be established'))); + ws.on('error', () => { + wss.close(); + done(); }); + }); }); it('server detects unauthorized protocol handler', function (done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { - cb(false); - }}, function () { - var options = { + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(false), + port: ++port + }, () => { + const req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -894,27 +863,24 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 13, 'Sec-WebSocket-Origin': 'http://foobar.com' } - }; - options.port = port; - var req = http.request(options); - req.end(); - req.on('response', function (res) { - res.statusCode.should.eql(401); + }); + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 401); wss.close(); done(); }); + req.end(); }); - wss.on('connection', function (ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function () {}); }); it('server detects invalid protocol handler', function (done) { - var wss = new WebSocketServer({port: ++port, handleProtocols: function (ps, cb) { + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => { // not calling callback is an error and shouldn't timeout - }}, function () { - var options = { + }, + port: ++port + }, () => { + const req = http.request({ port: port, host: '127.0.0.1', headers: { @@ -924,20 +890,14 @@ describe('WebSocketServer', function () { 'Sec-WebSocket-Version': 13, 'Sec-WebSocket-Origin': 'http://foobar.com' } - }; - options.port = port; - var req = http.request(options); - req.end(); - req.on('response', function (res) { - res.statusCode.should.eql(501); + }); + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 501); wss.close(); done(); }); + req.end(); }); - wss.on('connection', function (ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function () {}); }); it('accept connections with sec-websocket-extensions', function (done) { diff --git a/test/autobahn-server.js b/test/autobahn-server.js index 74aab5aa3..73655d8b3 100644 --- a/test/autobahn-server.js +++ b/test/autobahn-server.js @@ -1,4 +1,7 @@ -var WebSocketServer = require('../').Server; +'use strict'; + +var WebSocket = require('../'); +var WebSocketServer = WebSocket.Server; process.on('uncaughtException', function (err) { console.log('Caught exception: ', err, err.stack); @@ -11,8 +14,7 @@ process.on('SIGINT', function () { ws.on('close', function () { process.exit(); }); - } - catch (e) { + } catch (e) { process.exit(); } }); diff --git a/test/autobahn.js b/test/autobahn.js index 2ea7f1c78..dbe3fefd5 100644 --- a/test/autobahn.js +++ b/test/autobahn.js @@ -1,3 +1,5 @@ +'use strict'; + var WebSocket = require('../'); var currentTest = 1; var lastTest = -1; @@ -14,23 +16,25 @@ process.on('SIGINT', function () { ws.on('close', function () { process.exit(); }); - } - catch (e) { + } catch (e) { process.exit(); } }); function nextTest () { - if (currentTest > testCount || (lastTest != -1 && currentTest > lastTest)) { + var ws; + + if (currentTest > testCount || (lastTest !== -1 && currentTest > lastTest)) { console.log('Updating reports and shutting down'); - var ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); + ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); ws.on('close', function () { process.exit(); }); return; } + console.log('Running test case ' + currentTest + '/' + testCount); - var ws = new WebSocket('ws://localhost:9001/runCase?case=' + currentTest + '&agent=ws'); + ws = new WebSocket('ws://localhost:9001/runCase?case=' + currentTest + '&agent=ws'); ws.on('message', function (data, flags) { ws.send(flags.buffer, {binary: flags.binary === true, mask: true}); }); diff --git a/test/testserver.js b/test/testserver.js index 356509fd7..3f24964c4 100644 --- a/test/testserver.js +++ b/test/testserver.js @@ -1,9 +1,11 @@ -var http = require('http'), - util = require('util'), - crypto = require('crypto'), - events = require('events'), - Sender = require('../lib/Sender'), - Receiver = require('../lib/Receiver'); +'use strict'; + +const http = require('http'); +const util = require('util'); +const crypto = require('crypto'); +const events = require('events'); +const Sender = require('../lib/Sender'); +const Receiver = require('../lib/Receiver'); module.exports = { handlers: { @@ -38,7 +40,6 @@ function validServer (server, req, socket) { if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { throw new Error('invalid headers'); - return; } if (!req.headers['sec-websocket-key']) { @@ -85,7 +86,7 @@ function validServer (server, req, socket) { }; receiver.onclose = function (code, message, flags) { flags = flags || {}; - sender.close(code, message, false, function (err) { + sender.close(code, message, false, function () { server.emit('close', code, message, flags); socket.end(); }); @@ -102,7 +103,6 @@ function invalidRequestHandler (server, req, socket) { if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { throw new Error('invalid headers'); - return; } if (!req.headers['sec-websocket-key']) { @@ -131,7 +131,6 @@ function closeAfterConnectHandler (server, req, socket) { if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { throw new Error('invalid headers'); - return; } if (!req.headers['sec-websocket-key']) { From 7214fc0b7273bb97a36d6d2738cdd6e1c4e39b8f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 26 Oct 2016 18:09:55 +0200 Subject: [PATCH 136/669] [lint] Run eslint --fix on examples/* --- examples/fileapi/public/app.js | 16 +++--- examples/fileapi/public/uploader.js | 16 +++--- examples/fileapi/server.js | 50 ++++++++-------- examples/serverstats-express_3/server.js | 16 +++--- examples/serverstats/server.js | 16 +++--- examples/ssl.js | 72 ++++++++++-------------- 6 files changed, 87 insertions(+), 99 deletions(-) diff --git a/examples/fileapi/public/app.js b/examples/fileapi/public/app.js index e812cc3ea..db5f5c6d6 100644 --- a/examples/fileapi/public/app.js +++ b/examples/fileapi/public/app.js @@ -1,4 +1,4 @@ -function onFilesSelected(e) { +function onFilesSelected (e) { var button = e.srcElement; button.disabled = true; var progress = document.querySelector('div#progress'); @@ -8,12 +8,12 @@ function onFilesSelected(e) { var filesSent = 0; if (totalFiles) { var uploader = new Uploader('ws://localhost:8080', function () { - Array.prototype.slice.call(files, 0).forEach(function(file) { + Array.prototype.slice.call(files, 0).forEach(function (file) { if (file.name == '.') { --totalFiles; return; } - uploader.sendFile(file, function(error) { + uploader.sendFile(file, function (error) { if (error) { console.log(error); return; @@ -25,15 +25,15 @@ function onFilesSelected(e) { }); }); } - uploader.ondone = function() { + uploader.ondone = function () { uploader.close(); progress.innerHTML = '100% done, ' + totalFiles + ' files sent.'; - } + }; } -window.onload = function() { +window.onload = function () { var importButtons = document.querySelectorAll('[type="file"]'); - Array.prototype.slice.call(importButtons, 0).forEach(function(importButton) { + Array.prototype.slice.call(importButtons, 0).forEach(function (importButton) { importButton.addEventListener('change', onFilesSelected, false); }); -} +}; diff --git a/examples/fileapi/public/uploader.js b/examples/fileapi/public/uploader.js index 0c34a7fae..09d9850e1 100644 --- a/examples/fileapi/public/uploader.js +++ b/examples/fileapi/public/uploader.js @@ -1,4 +1,4 @@ -function Uploader(url, cb) { +function Uploader (url, cb) { this.ws = new WebSocket(url); if (cb) this.ws.onopen = cb; this.sendQueue = []; @@ -6,7 +6,7 @@ function Uploader(url, cb) { this.sendCallback = null; this.ondone = null; var self = this; - this.ws.onmessage = function(event) { + this.ws.onmessage = function (event) { var data = JSON.parse(event.data); if (data.event == 'complete') { if (data.path != self.sending.path) { @@ -22,7 +22,7 @@ function Uploader(url, cb) { if (self.sendQueue.length === 0 && self.ondone) self.ondone(null); if (self.sendQueue.length > 0) { var args = self.sendQueue.pop(); - setTimeout(function() { self.sendFile.apply(self, args); }, 0); + setTimeout(function () { self.sendFile.apply(self, args); }, 0); } } else if (data.event == 'error') { @@ -34,10 +34,10 @@ function Uploader(url, cb) { if (callback) callback(error); if (self.ondone) self.ondone(error); } - } + }; } -Uploader.prototype.sendFile = function(file, cb) { +Uploader.prototype.sendFile = function (file, cb) { if (this.ws.readyState != WebSocket.OPEN) throw new Error('Not connected'); if (this.sending) { this.sendQueue.push(arguments); @@ -48,8 +48,8 @@ Uploader.prototype.sendFile = function(file, cb) { this.sendCallback = cb; this.ws.send(JSON.stringify(fileData)); this.ws.send(file); -} +}; -Uploader.prototype.close = function() { +Uploader.prototype.close = function () { this.ws.close(); -} +}; diff --git a/examples/fileapi/server.js b/examples/fileapi/server.js index badfeba7a..4fcf789bc 100644 --- a/examples/fileapi/server.js +++ b/examples/fileapi/server.js @@ -1,40 +1,40 @@ -var WebSocketServer = require('../../').Server - , express = require('express') - , fs = require('fs') - , http = require('http') - , util = require('util') - , path = require('path') - , app = express.createServer() - , events = require('events') - , ansi = require('ansi') - , cursor = ansi(process.stdout); +var WebSocketServer = require('../../').Server, + express = require('express'), + fs = require('fs'), + http = require('http'), + util = require('util'), + path = require('path'), + app = express.createServer(), + events = require('events'), + ansi = require('ansi'), + cursor = ansi(process.stdout); -function BandwidthSampler(ws, interval) { +function BandwidthSampler (ws, interval) { interval = interval || 2000; var previousByteCount = 0; var self = this; - var intervalId = setInterval(function() { + var intervalId = setInterval(function () { var byteCount = ws.bytesReceived; var bytesPerSec = (byteCount - previousByteCount) / (interval / 1000); previousByteCount = byteCount; self.emit('sample', bytesPerSec); }, interval); - ws.on('close', function() { + ws.on('close', function () { clearInterval(intervalId); }); } util.inherits(BandwidthSampler, events.EventEmitter); -function makePathForFile(filePath, prefix, cb) { +function makePathForFile (filePath, prefix, cb) { if (typeof cb !== 'function') throw new Error('callback is required'); filePath = path.dirname(path.normalize(filePath)).replace(/^(\/|\\)+/, ''); var pieces = filePath.split(/(\\|\/)/); var incrementalPath = prefix; - function step(error) { + function step (error) { if (error) return cb(error); if (pieces.length == 0) return cb(null, incrementalPath); incrementalPath += '/' + pieces.shift(); - fs.exists(incrementalPath, function(exists) { + fs.exists(incrementalPath, function (exists) { if (!exists) fs.mkdir(incrementalPath, step); else process.nextTick(step); }); @@ -47,33 +47,33 @@ app.use(express.static(__dirname + '/public')); var clientId = 0; var wss = new WebSocketServer({server: app}); -wss.on('connection', function(ws) { +wss.on('connection', function (ws) { var thisId = ++clientId; cursor.goto(1, 4 + thisId).eraseLine(); console.log('Client #%d connected', thisId); var sampler = new BandwidthSampler(ws); - sampler.on('sample', function(bps) { + sampler.on('sample', function (bps) { cursor.goto(1, 4 + thisId).eraseLine(); - console.log('WebSocket #%d incoming bandwidth: %d MB/s', thisId, Math.round(bps / (1024*1024))); + console.log('WebSocket #%d incoming bandwidth: %d MB/s', thisId, Math.round(bps / (1024 * 1024))); }); var filesReceived = 0; var currentFile = null; - ws.on('message', function(data, flags) { + ws.on('message', function (data, flags) { if (!flags.binary) { currentFile = JSON.parse(data); // note: a real-world app would want to sanity check the data } else { if (currentFile == null) return; - makePathForFile(currentFile.path, __dirname + '/uploaded', function(error, path) { + makePathForFile(currentFile.path, __dirname + '/uploaded', function (error, path) { if (error) { console.log(error); ws.send(JSON.stringify({event: 'error', path: currentFile.path, message: error.message})); return; } - fs.writeFile(path + '/' + currentFile.name, data, function(error) { + fs.writeFile(path + '/' + currentFile.name, data, function (error) { ++filesReceived; // console.log('received %d bytes long file, %s', data.length, currentFile.path); ws.send(JSON.stringify({event: 'complete', path: currentFile.path})); @@ -83,18 +83,18 @@ wss.on('connection', function(ws) { } }); - ws.on('close', function() { + ws.on('close', function () { cursor.goto(1, 4 + thisId).eraseLine(); console.log('Client #%d disconnected. %d files received.', thisId, filesReceived); }); - ws.on('error', function(e) { + ws.on('error', function (e) { cursor.goto(1, 4 + thisId).eraseLine(); console.log('Client #%d error: %s', thisId, e.message); }); }); -fs.mkdir(__dirname + '/uploaded', function(error) { +fs.mkdir(__dirname + '/uploaded', function (error) { // ignore errors, most likely means directory exists console.log('Uploaded files will be saved to %s/uploaded.', __dirname); console.log('Remember to wipe this directory if you upload lots and lots.'); diff --git a/examples/serverstats-express_3/server.js b/examples/serverstats-express_3/server.js index 88bbc9ebf..c69db84fc 100644 --- a/examples/serverstats-express_3/server.js +++ b/examples/serverstats-express_3/server.js @@ -1,7 +1,7 @@ -var WebSocketServer = require('../../').Server - , http = require('http') - , express = require('express') - , app = express(); +var WebSocketServer = require('../../').Server, + http = require('http'), + express = require('express'), + app = express(); app.use(express.static(__dirname + '/public')); @@ -9,12 +9,12 @@ var server = http.createServer(app); server.listen(8080); var wss = new WebSocketServer({server: server}); -wss.on('connection', function(ws) { - var id = setInterval(function() { - ws.send(JSON.stringify(process.memoryUsage()), function() { /* ignore errors */ }); +wss.on('connection', function (ws) { + var id = setInterval(function () { + ws.send(JSON.stringify(process.memoryUsage()), function () { /* ignore errors */ }); }, 100); console.log('started client interval'); - ws.on('close', function() { + ws.on('close', function () { console.log('stopping client interval'); clearInterval(id); }); diff --git a/examples/serverstats/server.js b/examples/serverstats/server.js index d7845e0cb..9b7819e4c 100644 --- a/examples/serverstats/server.js +++ b/examples/serverstats/server.js @@ -1,18 +1,18 @@ -var WebSocketServer = require('../../').Server - , http = require('http') - , express = require('express') - , app = express.createServer(); +var WebSocketServer = require('../../').Server, + http = require('http'), + express = require('express'), + app = express.createServer(); app.use(express.static(__dirname + '/public')); app.listen(8080); var wss = new WebSocketServer({server: app}); -wss.on('connection', function(ws) { - var id = setInterval(function() { - ws.send(JSON.stringify(process.memoryUsage()), function() { /* ignore errors */ }); +wss.on('connection', function (ws) { + var id = setInterval(function () { + ws.send(JSON.stringify(process.memoryUsage()), function () { /* ignore errors */ }); }, 100); console.log('started client interval'); - ws.on('close', function() { + ws.on('close', function () { console.log('stopping client interval'); clearInterval(id); }); diff --git a/examples/ssl.js b/examples/ssl.js index bf1bf5303..f1f1dee44 100644 --- a/examples/ssl.js +++ b/examples/ssl.js @@ -1,59 +1,47 @@ -(function(){ +(function () { + 'use strict'; - "use strict"; - - var fs = require('fs'); + var fs = require('fs'); // you'll probably load configuration from config - var cfg = { - ssl: true, - port: 8080, - ssl_key: '/path/to/you/ssl.key', - ssl_cert: '/path/to/you/ssl.crt' - }; + var cfg = { + ssl: true, + port: 8080, + ssl_key: '/path/to/you/ssl.key', + ssl_cert: '/path/to/you/ssl.crt' + }; - var httpServ = ( cfg.ssl ) ? require('https') : require('http'); + var httpServ = (cfg.ssl) ? require('https') : require('http'); - var WebSocketServer = require('../').Server; + var WebSocketServer = require('../').Server; - var app = null; + var app = null; // dummy request processing - var processRequest = function( req, res ) { - - res.writeHead(200); - res.end("All glory to WebSockets!\n"); - }; - - if ( cfg.ssl ) { + var processRequest = function (req, res) { + res.writeHead(200); + res.end('All glory to WebSockets!\n'); + }; - app = httpServ.createServer({ + if (cfg.ssl) { + app = httpServ.createServer({ // providing server with SSL key/cert - key: fs.readFileSync( cfg.ssl_key ), - cert: fs.readFileSync( cfg.ssl_cert ) + key: fs.readFileSync(cfg.ssl_key), + cert: fs.readFileSync(cfg.ssl_cert) - }, processRequest ).listen( cfg.port ); - - } else { - - app = httpServ.createServer( processRequest ).listen( cfg.port ); - } + }, processRequest).listen(cfg.port); + } else { + app = httpServ.createServer(processRequest).listen(cfg.port); + } // passing or reference to web server so WS would knew port and SSL capabilities - var wss = new WebSocketServer( { server: app } ); - - - wss.on( 'connection', function ( wsConnect ) { - - wsConnect.on( 'message', function ( message ) { - - console.log( message ); - - }); + var wss = new WebSocketServer({ server: app }); + wss.on('connection', function (wsConnect) { + wsConnect.on('message', function (message) { + console.log(message); }); - - -}()); \ No newline at end of file + }); +}()); From bf8ab467fe391888a2ebed9b44cac66d94b2f4fa Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 26 Oct 2016 18:33:39 +0200 Subject: [PATCH 137/669] [list] Fix remaining lint issues on examples/* --- Makefile | 2 +- examples/fileapi/public/app.js | 3 +- examples/fileapi/public/uploader.js | 15 +++++----- examples/fileapi/server.js | 35 +++++++++++++----------- examples/serverstats-express_3/server.js | 11 ++++---- examples/serverstats/server.js | 10 +++---- 6 files changed, 41 insertions(+), 35 deletions(-) diff --git a/Makefile b/Makefile index 920bc1ff1..2ae3d0fbe 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ ALL_TESTS = $(shell find test -name '*.test.js') ALL_INTEGRATION = $(shell find test -name '*.integration.js') lint: - @./node_modules/.bin/eslint lib index.js + @./node_modules/.bin/eslint . run-tests: @./node_modules/.bin/mocha \ diff --git a/examples/fileapi/public/app.js b/examples/fileapi/public/app.js index db5f5c6d6..f1a9ee32c 100644 --- a/examples/fileapi/public/app.js +++ b/examples/fileapi/public/app.js @@ -1,3 +1,4 @@ +/* global Uploader */ function onFilesSelected (e) { var button = e.srcElement; button.disabled = true; @@ -9,7 +10,7 @@ function onFilesSelected (e) { if (totalFiles) { var uploader = new Uploader('ws://localhost:8080', function () { Array.prototype.slice.call(files, 0).forEach(function (file) { - if (file.name == '.') { + if (file.name === '.') { --totalFiles; return; } diff --git a/examples/fileapi/public/uploader.js b/examples/fileapi/public/uploader.js index 09d9850e1..9b4f98e2f 100644 --- a/examples/fileapi/public/uploader.js +++ b/examples/fileapi/public/uploader.js @@ -1,3 +1,4 @@ +/* global WebSocket */ function Uploader (url, cb) { this.ws = new WebSocket(url); if (cb) this.ws.onopen = cb; @@ -8,15 +9,16 @@ function Uploader (url, cb) { var self = this; this.ws.onmessage = function (event) { var data = JSON.parse(event.data); - if (data.event == 'complete') { - if (data.path != self.sending.path) { + var callback; + if (data.event === 'complete') { + if (data.path !== self.sending.path) { self.sendQueue = []; self.sending = null; self.sendCallback = null; throw new Error('Got message for wrong file!'); } self.sending = null; - var callback = self.sendCallback; + callback = self.sendCallback; self.sendCallback = null; if (callback) callback(); if (self.sendQueue.length === 0 && self.ondone) self.ondone(null); @@ -24,11 +26,10 @@ function Uploader (url, cb) { var args = self.sendQueue.pop(); setTimeout(function () { self.sendFile.apply(self, args); }, 0); } - } - else if (data.event == 'error') { + } else if (data.event === 'error') { self.sendQueue = []; self.sending = null; - var callback = self.sendCallback; + callback = self.sendCallback; self.sendCallback = null; var error = new Error('Server reported send error for file ' + data.path); if (callback) callback(error); @@ -38,7 +39,7 @@ function Uploader (url, cb) { } Uploader.prototype.sendFile = function (file, cb) { - if (this.ws.readyState != WebSocket.OPEN) throw new Error('Not connected'); + if (this.ws.readyState !== WebSocket.OPEN) throw new Error('Not connected'); if (this.sending) { this.sendQueue.push(arguments); return; diff --git a/examples/fileapi/server.js b/examples/fileapi/server.js index 4fcf789bc..e7d80f009 100644 --- a/examples/fileapi/server.js +++ b/examples/fileapi/server.js @@ -1,13 +1,12 @@ -var WebSocketServer = require('../../').Server, - express = require('express'), - fs = require('fs'), - http = require('http'), - util = require('util'), - path = require('path'), - app = express.createServer(), - events = require('events'), - ansi = require('ansi'), - cursor = ansi(process.stdout); +var WebSocketServer = require('../../').Server; +var express = require('express'); +var fs = require('fs'); +var util = require('util'); +var path = require('path'); +var app = express.createServer(); +var events = require('events'); +var ansi = require('ansi'); +var cursor = ansi(process.stdout); function BandwidthSampler (ws, interval) { interval = interval || 2000; @@ -32,7 +31,7 @@ function makePathForFile (filePath, prefix, cb) { var incrementalPath = prefix; function step (error) { if (error) return cb(error); - if (pieces.length == 0) return cb(null, incrementalPath); + if (pieces.length === 0) return cb(null, incrementalPath); incrementalPath += '/' + pieces.shift(); fs.exists(incrementalPath, function (exists) { if (!exists) fs.mkdir(incrementalPath, step); @@ -43,7 +42,7 @@ function makePathForFile (filePath, prefix, cb) { } cursor.eraseData(2).goto(1, 1); -app.use(express.static(__dirname + '/public')); +app.use(express.static(path.join(__dirname, '/public'))); var clientId = 0; var wss = new WebSocketServer({server: app}); @@ -64,16 +63,20 @@ wss.on('connection', function (ws) { if (!flags.binary) { currentFile = JSON.parse(data); // note: a real-world app would want to sanity check the data - } - else { + } else { if (currentFile == null) return; - makePathForFile(currentFile.path, __dirname + '/uploaded', function (error, path) { + makePathForFile(currentFile.path, path.join(__dirname, '/uploaded'), function (error, path) { if (error) { console.log(error); ws.send(JSON.stringify({event: 'error', path: currentFile.path, message: error.message})); return; } fs.writeFile(path + '/' + currentFile.name, data, function (error) { + if (error) { + console.log(error); + ws.send(JSON.stringify({event: 'error', path: currentFile.path, message: error.message})); + return; + } ++filesReceived; // console.log('received %d bytes long file, %s', data.length, currentFile.path); ws.send(JSON.stringify({event: 'complete', path: currentFile.path})); @@ -94,7 +97,7 @@ wss.on('connection', function (ws) { }); }); -fs.mkdir(__dirname + '/uploaded', function (error) { +fs.mkdir(path.join(__dirname, '/uploaded'), function () { // ignore errors, most likely means directory exists console.log('Uploaded files will be saved to %s/uploaded.', __dirname); console.log('Remember to wipe this directory if you upload lots and lots.'); diff --git a/examples/serverstats-express_3/server.js b/examples/serverstats-express_3/server.js index c69db84fc..16e584f95 100644 --- a/examples/serverstats-express_3/server.js +++ b/examples/serverstats-express_3/server.js @@ -1,9 +1,10 @@ -var WebSocketServer = require('../../').Server, - http = require('http'), - express = require('express'), - app = express(); +var WebSocketServer = require('../../').Server; +var http = require('http'); +var express = require('express'); +var path = require('path'); +var app = express(); -app.use(express.static(__dirname + '/public')); +app.use(express.static(path.join(__dirname, '/public'))); var server = http.createServer(app); server.listen(8080); diff --git a/examples/serverstats/server.js b/examples/serverstats/server.js index 9b7819e4c..73e4fcf8f 100644 --- a/examples/serverstats/server.js +++ b/examples/serverstats/server.js @@ -1,9 +1,9 @@ -var WebSocketServer = require('../../').Server, - http = require('http'), - express = require('express'), - app = express.createServer(); +var WebSocketServer = require('../../').Server; +var express = require('express'); +var path = require('path'); +var app = express.createServer(); -app.use(express.static(__dirname + '/public')); +app.use(express.static(path.join(__dirname, '/public'))); app.listen(8080); var wss = new WebSocketServer({server: app}); From 47f8b9b8eb992519a23fe50ead62d86e01973f6e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 27 Oct 2016 12:40:12 +0200 Subject: [PATCH 138/669] [test] Clean up tests and remove should dependency --- package.json | 1 - test/Extensions.test.js | 42 +- test/PerMessageDeflate.test.js | 271 ++++--- test/Receiver.test.js | 8 +- test/Sender.test.js | 106 +-- test/Validation.test.js | 49 +- test/WebSocket.integration.js | 25 +- test/WebSocket.test.js | 442 ++++++------ test/WebSocketServer.test.js | 1239 ++++++++++++++++---------------- test/hybi-util.js | 4 +- test/testserver.js | 35 +- 11 files changed, 1166 insertions(+), 1056 deletions(-) diff --git a/package.json b/package.json index 1b0a893a1..8d33af457 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "eslint-plugin-standard": "2.0.x", "istanbul": "0.4.x", "mocha": "3.1.x", - "should": "8.0.x", "utf-8-validate": "1.2.x" } } diff --git a/test/Extensions.test.js b/test/Extensions.test.js index ce0af8381..8b8fe2fe9 100644 --- a/test/Extensions.test.js +++ b/test/Extensions.test.js @@ -1,31 +1,38 @@ -var Extensions = require('../lib/Extensions'); -require('should'); +'use strict'; + +const assert = require('assert'); + +const Extensions = require('../lib/Extensions'); describe('Extensions', function () { describe('parse', function () { it('should parse', function () { - var extensions = Extensions.parse('foo'); - extensions.should.eql({ foo: [{}] }); + const extensions = Extensions.parse('foo'); + + assert.deepStrictEqual(extensions, { foo: [{}] }); }); it('should parse params', function () { - var extensions = Extensions.parse('foo; bar; baz=1; bar=2'); - extensions.should.eql({ + const extensions = Extensions.parse('foo; bar; baz=1; bar=2'); + + assert.deepStrictEqual(extensions, { foo: [{ bar: [true, '2'], baz: ['1'] }] }); }); it('should parse multiple extensions', function () { - var extensions = Extensions.parse('foo, bar; baz, foo; baz'); - extensions.should.eql({ + const extensions = Extensions.parse('foo, bar; baz, foo; baz'); + + assert.deepStrictEqual(extensions, { foo: [{}, { baz: [true] }], bar: [{ baz: [true] }] }); }); it('should parse quoted params', function () { - var extensions = Extensions.parse('foo; bar="hi"'); - extensions.should.eql({ + const extensions = Extensions.parse('foo; bar="hi"'); + + assert.deepStrictEqual(extensions, { foo: [{ bar: ['hi'] }] }); }); @@ -33,21 +40,24 @@ describe('Extensions', function () { describe('format', function () { it('should format', function () { - var extensions = Extensions.format({ foo: {} }); - extensions.should.eql('foo'); + const extensions = Extensions.format({ foo: {} }); + + assert.strictEqual(extensions, 'foo'); }); it('should format params', function () { - var extensions = Extensions.format({ foo: { bar: [true, 2], baz: 1 } }); - extensions.should.eql('foo; bar; bar=2; baz=1'); + const extensions = Extensions.format({ foo: { bar: [true, 2], baz: 1 } }); + + assert.strictEqual(extensions, 'foo; bar; bar=2; baz=1'); }); it('should format multiple extensions', function () { - var extensions = Extensions.format({ + const extensions = Extensions.format({ foo: [{}, { baz: true }], bar: { baz: true } }); - extensions.should.eql('foo, foo; baz, bar; baz'); + + assert.strictEqual(extensions, 'foo, foo; baz, bar; baz'); }); }); }); diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index 73e324c16..fe570f4b2 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -1,35 +1,36 @@ 'use strict'; +const assert = require('assert'); + const PerMessageDeflate = require('../lib/PerMessageDeflate'); const Extensions = require('../lib/Extensions'); -require('should'); describe('PerMessageDeflate', function () { describe('#ctor', function () { - it('throws TypeError when called without new', function (done) { - try { - PerMessageDeflate(); - } catch (e) { - e.should.be.instanceof(TypeError); - done(); - } + it('throws TypeError when called without new', function () { + assert.throws(PerMessageDeflate, TypeError); }); }); describe('#offer', function () { it('should create default params', function () { - var perMessageDeflate = new PerMessageDeflate(); - perMessageDeflate.offer().should.eql({ client_max_window_bits: true }); + const perMessageDeflate = new PerMessageDeflate(); + + assert.deepStrictEqual( + perMessageDeflate.offer(), + { client_max_window_bits: true } + ); }); it('should create params from options', function () { - var perMessageDeflate = new PerMessageDeflate({ + const perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: true, clientNoContextTakeover: true, serverMaxWindowBits: 10, clientMaxWindowBits: 11 }); - perMessageDeflate.offer().should.eql({ + + assert.deepStrictEqual(perMessageDeflate.offer(), { server_no_context_takeover: true, client_no_context_takeover: true, server_max_window_bits: 10, @@ -41,14 +42,20 @@ describe('PerMessageDeflate', function () { describe('#accept', function () { describe('as server', function () { it('should accept empty offer', function () { - var perMessageDeflate = new PerMessageDeflate({}, true); - perMessageDeflate.accept([{}]).should.eql({}); + const perMessageDeflate = new PerMessageDeflate({}, true); + + assert.deepStrictEqual(perMessageDeflate.accept([{}]), {}); }); it('should accept offer', function () { - var perMessageDeflate = new PerMessageDeflate({}, true); - var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; client_no_context_takeover; server_max_window_bits=10; client_max_window_bits=11'); - perMessageDeflate.accept(extensions['permessage-deflate']).should.eql({ + const perMessageDeflate = new PerMessageDeflate({}, true); + const extensions = Extensions.parse( + 'permessage-deflate; server_no_context_takeover; ' + + 'client_no_context_takeover; server_max_window_bits=10; ' + + 'client_max_window_bits=11' + ); + + assert.deepStrictEqual(perMessageDeflate.accept(extensions['permessage-deflate']), { server_no_context_takeover: true, client_no_context_takeover: true, server_max_window_bits: 10, @@ -57,14 +64,17 @@ describe('PerMessageDeflate', function () { }); it('should prefer configuration than offer', function () { - var perMessageDeflate = new PerMessageDeflate({ + const perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: true, clientNoContextTakeover: true, serverMaxWindowBits: 12, clientMaxWindowBits: 11 }, true); - var extensions = Extensions.parse('permessage-deflate; server_max_window_bits=14; client_max_window_bits=13'); - perMessageDeflate.accept(extensions['permessage-deflate']).should.eql({ + const extensions = Extensions.parse( + 'permessage-deflate; server_max_window_bits=14; client_max_window_bits=13' + ); + + assert.deepStrictEqual(perMessageDeflate.accept(extensions['permessage-deflate']), { server_no_context_takeover: true, client_no_context_takeover: true, server_max_window_bits: 12, @@ -73,56 +83,61 @@ describe('PerMessageDeflate', function () { }); it('should fallback', function () { - var perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); - var extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10, permessage-deflate'); - perMessageDeflate.accept(extensions['permessage-deflate']).should.eql({ + const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); + const extensions = Extensions.parse( + 'permessage-deflate; server_max_window_bits=10, permessage-deflate' + ); + + assert.deepStrictEqual(perMessageDeflate.accept(extensions['permessage-deflate']), { server_max_window_bits: 11 }); }); it('should throw an error if server_no_context_takeover is unsupported', function () { - var perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: false }, true); - var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: false }, true); + const extensions = Extensions.parse('permessage-deflate; server_no_context_takeover'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if server_max_window_bits is unsupported', function () { - var perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: false }, true); - var extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: false }, true); + const extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if server_max_window_bits is less than configuration', function () { - var perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); - var extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); + const extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_max_window_bits is unsupported on client', function () { - var perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }, true); - var extensions = Extensions.parse('permessage-deflate'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }, true); + const extensions = Extensions.parse('permessage-deflate'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); }); describe('as client', function () { it('should accept empty response', function () { - var perMessageDeflate = new PerMessageDeflate({}); - perMessageDeflate.accept([{}]).should.eql({}); + const perMessageDeflate = new PerMessageDeflate({}); + + assert.deepStrictEqual(perMessageDeflate.accept([{}]), {}); }); it('should accept response parameter', function () { - var perMessageDeflate = new PerMessageDeflate({}); - var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; client_no_context_takeover; server_max_window_bits=10; client_max_window_bits=11'); - perMessageDeflate.accept(extensions['permessage-deflate']).should.eql({ + const perMessageDeflate = new PerMessageDeflate({}); + const extensions = Extensions.parse( + 'permessage-deflate; server_no_context_takeover; ' + + 'client_no_context_takeover; server_max_window_bits=10; ' + + 'client_max_window_bits=11' + ); + + assert.deepStrictEqual(perMessageDeflate.accept(extensions['permessage-deflate']), { server_no_context_takeover: true, client_no_context_takeover: true, server_max_window_bits: 10, @@ -131,109 +146,110 @@ describe('PerMessageDeflate', function () { }); it('should throw an error if client_no_context_takeover is unsupported', function () { - var perMessageDeflate = new PerMessageDeflate({ clientNoContextTakeover: false }); - var extensions = Extensions.parse('permessage-deflate; client_no_context_takeover'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate({ clientNoContextTakeover: false }); + const extensions = Extensions.parse('permessage-deflate; client_no_context_takeover'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_max_window_bits is unsupported', function () { - var perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: false }); - var extensions = Extensions.parse('permessage-deflate; client_max_window_bits=10'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: false }); + const extensions = Extensions.parse('permessage-deflate; client_max_window_bits=10'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_max_window_bits is greater than configuration', function () { - var perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }); - var extensions = Extensions.parse('permessage-deflate; client_max_window_bits=11'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }); + const extensions = Extensions.parse('permessage-deflate; client_max_window_bits=11'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); }); describe('validate parameters', function () { it('should throw an error if a parameter has multiple values', function () { - var perMessageDeflate = new PerMessageDeflate(); - var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; server_no_context_takeover'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate(); + const extensions = Extensions.parse( + 'permessage-deflate; server_no_context_takeover; server_no_context_takeover' + ); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if a parameter is undefined', function () { - var perMessageDeflate = new PerMessageDeflate(); - var extensions = Extensions.parse('permessage-deflate; foo;'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate(); + const extensions = Extensions.parse('permessage-deflate; foo;'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if server_no_context_takeover has a value', function () { - var perMessageDeflate = new PerMessageDeflate(); - var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover=10'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate(); + const extensions = Extensions.parse('permessage-deflate; server_no_context_takeover=10'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_no_context_takeover has a value', function () { - var perMessageDeflate = new PerMessageDeflate(); - var extensions = Extensions.parse('permessage-deflate; client_no_context_takeover=10'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate(); + const extensions = Extensions.parse('permessage-deflate; client_no_context_takeover=10'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if server_max_window_bits has an invalid value', function () { - var perMessageDeflate = new PerMessageDeflate(); - var extensions = Extensions.parse('permessage-deflate; server_max_window_bits=7'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate(); + const extensions = Extensions.parse('permessage-deflate; server_max_window_bits=7'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_max_window_bits has an invalid value', function () { - var perMessageDeflate = new PerMessageDeflate(); - var extensions = Extensions.parse('permessage-deflate; client_max_window_bits=16'); - (function () { - perMessageDeflate.accept(extensions['permessage-deflate']); - }).should.throw(); + const perMessageDeflate = new PerMessageDeflate(); + const extensions = Extensions.parse('permessage-deflate; client_max_window_bits=16'); + + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); }); }); describe('#compress/#decompress', function () { it('should compress/decompress data', function (done) { - var perMessageDeflate = new PerMessageDeflate(); + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + perMessageDeflate.accept([{}]); - perMessageDeflate.compress(new Buffer([1, 2, 3]), true, function (err, compressed) { + perMessageDeflate.compress(Buffer.from([1, 2, 3]), true, (err, compressed) => { if (err) return done(err); - perMessageDeflate.decompress(compressed, true, function (err, data) { + + perMessageDeflate.decompress(compressed, true, (err, data) => { if (err) return done(err); - data.should.eql(new Buffer([1, 2, 3])); + + assert.ok(data.equals(Buffer.from([1, 2, 3]))); done(); }); }); }); it('should compress/decompress fragments', function (done) { - var perMessageDeflate = new PerMessageDeflate(); + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const buf = Buffer.from([1, 2, 3, 4]); + perMessageDeflate.accept([{}]); - var buf = new Buffer([1, 2, 3, 4]); - perMessageDeflate.compress(buf.slice(0, 2), false, function (err, compressed1) { + perMessageDeflate.compress(buf.slice(0, 2), false, (err, compressed1) => { if (err) return done(err); - perMessageDeflate.compress(buf.slice(2), true, function (err, compressed2) { + + perMessageDeflate.compress(buf.slice(2), true, (err, compressed2) => { if (err) return done(err); - perMessageDeflate.decompress(compressed1, false, function (err, data1) { + + perMessageDeflate.decompress(compressed1, false, (err, data1) => { if (err) return done(err); - perMessageDeflate.decompress(compressed2, true, function (err, data2) { + + perMessageDeflate.decompress(compressed2, true, (err, data2) => { if (err) return done(err); - Buffer.concat([data1, data2]).should.eql(new Buffer([1, 2, 3, 4])); + + assert.ok(Buffer.concat([data1, data2]).equals(Buffer.from([1, 2, 3, 4]))); done(); }); }); @@ -242,34 +258,53 @@ describe('PerMessageDeflate', function () { }); it('should compress/decompress data with parameters', function (done) { - var perMessageDeflate = new PerMessageDeflate({ memLevel: 5 }); - var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; client_no_context_takeover; server_max_window_bits=10; client_max_window_bits=11'); + const perMessageDeflate = new PerMessageDeflate({ + threshold: 0, + memLevel: 5 + }); + const extensions = Extensions.parse( + 'permessage-deflate; server_no_context_takeover; ' + + 'client_no_context_takeover; server_max_window_bits=10; ' + + 'client_max_window_bits=11' + ); + perMessageDeflate.accept(extensions['permessage-deflate']); - perMessageDeflate.compress(new Buffer([1, 2, 3]), true, function (err, compressed) { + + perMessageDeflate.compress(Buffer.from([1, 2, 3]), true, (err, compressed) => { if (err) return done(err); - perMessageDeflate.decompress(compressed, true, function (err, data) { + + perMessageDeflate.decompress(compressed, true, (err, data) => { if (err) return done(err); - data.should.eql(new Buffer([1, 2, 3])); + + assert.ok(data.equals(Buffer.from([1, 2, 3]))); done(); }); }); }); it('should compress/decompress data with no context takeover', function (done) { - var perMessageDeflate = new PerMessageDeflate(); - var extensions = Extensions.parse('permessage-deflate; server_no_context_takeover; client_no_context_takeover'); + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const extensions = Extensions.parse( + 'permessage-deflate; server_no_context_takeover; client_no_context_takeover' + ); + const buf = Buffer.from('foofoo'); + perMessageDeflate.accept(extensions['permessage-deflate']); - var buf = new Buffer('foofoo'); - perMessageDeflate.compress(buf, true, function (err, compressed1) { + + perMessageDeflate.compress(buf, true, (err, compressed1) => { if (err) return done(err); - perMessageDeflate.decompress(compressed1, true, function (err, data) { + + perMessageDeflate.decompress(compressed1, true, (err, data) => { if (err) return done(err); - perMessageDeflate.compress(data, true, function (err, compressed2) { + + perMessageDeflate.compress(data, true, (err, compressed2) => { if (err) return done(err); - perMessageDeflate.decompress(compressed2, true, function (err, data) { + + perMessageDeflate.decompress(compressed2, true, (err, data) => { if (err) return done(err); - compressed2.length.should.equal(compressed1.length); - data.should.eql(buf); + + assert.strictEqual(compressed2.length, compressed1.length); + assert.ok(data.equals(buf)); done(); }); }); diff --git a/test/Receiver.test.js b/test/Receiver.test.js index f9f017d2b..0f7b1b077 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -165,10 +165,10 @@ describe('Receiver', function () { it('can parse a fragmented masked text message of 300 B with a ping in the middle (2/2)', function (done) { const p = new Receiver(); const msg = 'A'.repeat(300); - var pingMessage = 'Hello'; + const pingMessage = 'Hello'; - var fragment1 = msg.substr(0, 150); - var fragment2 = msg.substr(150); + const fragment1 = msg.substr(0, 150); + const fragment2 = msg.substr(150); const mask = '3483a868'; const frame1 = '01FE' + util.pack(4, fragment1.length) + mask + @@ -196,7 +196,7 @@ describe('Receiver', function () { assert.strictEqual(data.toString(), pingMessage); }; - for (var i = 0; i < buffers.length; ++i) { + for (let i = 0; i < buffers.length; ++i) { p.add(buffers[i]); } }); diff --git a/test/Sender.test.js b/test/Sender.test.js index f81b96d64..acb16918a 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -1,96 +1,97 @@ 'use strict'; -const Sender = require('../lib/Sender'); +const assert = require('assert'); + const PerMessageDeflate = require('../lib/PerMessageDeflate'); -require('should'); +const Sender = require('../lib/Sender'); describe('Sender', function () { describe('#ctor', function () { - it('throws TypeError when called without new', function (done) { - try { - Sender({ write: function () {} }); - } catch (e) { - e.should.be.instanceof(TypeError); - done(); - } + it('throws TypeError when called without new', function () { + assert.throws(Sender, TypeError); }); }); describe('#frameAndSend', function () { it('does not modify a masked binary buffer', function () { - var sender = new Sender({ write: function () {} }); - var buf = new Buffer([1, 2, 3, 4, 5]); + const sender = new Sender({ write: () => {} }); + const buf = Buffer.from([1, 2, 3, 4, 5]); + sender.frameAndSend(2, buf, true, true); - buf[0].should.eql(1); - buf[1].should.eql(2); - buf[2].should.eql(3); - buf[3].should.eql(4); - buf[4].should.eql(5); + + assert.ok(buf.equals(Buffer.from([1, 2, 3, 4, 5]))); }); it('does not modify a masked text buffer', function () { - var sender = new Sender({ write: function () {} }); - var text = 'hi there'; - sender.frameAndSend(1, Buffer.from(text), true, true); - text.should.eql('hi there'); + const sender = new Sender({ write: () => {} }); + const text = Buffer.from('hi there'); + + sender.frameAndSend(1, text, true, true); + + assert.ok(text.equals(Buffer.from('hi there'))); }); it('sets rsv1 flag if compressed', function (done) { - var sender = new Sender({ - write: function (data) { - (data[0] & 0x40).should.equal(0x40); + const sender = new Sender({ + write: (data) => { + assert.strictEqual(data[0] & 0x40, 0x40); done(); } }); + sender.frameAndSend(1, Buffer.from('hi'), true, false, true); }); }); describe('#send', function () { it('compresses data if compress option is enabled', function (done) { - var perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); - perMessageDeflate.accept([{}]); - - var sender = new Sender({ - write: function (data) { - (data[0] & 0x40).should.equal(0x40); + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const sender = new Sender({ + write: (data) => { + assert.strictEqual(data[0] & 0x40, 0x40); done(); } }, { 'permessage-deflate': perMessageDeflate }); + + perMessageDeflate.accept([{}]); + sender.send('hi', { compress: true }); }); it('does not compress data for small payloads', function (done) { - var perMessageDeflate = new PerMessageDeflate(); - perMessageDeflate.accept([{}]); - - var sender = new Sender({ - write: function (data) { - (data[0] & 0x40).should.not.equal(0x40); + const perMessageDeflate = new PerMessageDeflate(); + const sender = new Sender({ + write: (data) => { + assert.notStrictEqual(data[0] & 0x40, 0x40); done(); } }, { 'permessage-deflate': perMessageDeflate }); + + perMessageDeflate.accept([{}]); + sender.send('hi', { compress: true }); }); it('Should be able to handle many send calls while processing without crashing on flush', function (done) { - var messageCount = 0; - var maxMessages = 5000; + const maxMessages = 5000; + let messageCount = 0; - var sender = new Sender({ - write: function (data) { + const sender = new Sender({ + write: (data) => { messageCount++; if (messageCount > maxMessages) return done(); } }); - for (var i = 0; i < maxMessages; i++) { + + for (let i = 0; i < maxMessages; i++) { sender.processing = true; sender.send('hi', { compress: false, fin: true, binary: false, mask: false }); } + sender.processing = false; sender.send('hi', { compress: false, fin: true, binary: false, mask: false }); }); @@ -98,22 +99,23 @@ describe('Sender', function () { describe('#close', function () { it('should consume all data before closing', function (done) { - var perMessageDeflate = new PerMessageDeflate(); - perMessageDeflate.accept([{}]); + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); - var count = 0; - var sender = new Sender({ - write: function (data) { - count++; - } + let count = 0; + const sender = new Sender({ + write: (data) => count++ }, { 'permessage-deflate': perMessageDeflate }); - sender.send('foo', {compress: true}); - sender.send('bar', {compress: true}); - sender.send('baz', {compress: true}); - sender.close(1000, null, false, function (err) { - count.should.be.equal(4); + + perMessageDeflate.accept([{}]); + + sender.send('foo', { compress: true }); + sender.send('bar', { compress: true }); + sender.send('baz', { compress: true }); + + sender.close(1000, null, false, (err) => { + assert.strictEqual(count, 4); done(err); }); }); diff --git a/test/Validation.test.js b/test/Validation.test.js index 8038a7c63..d09b6631d 100644 --- a/test/Validation.test.js +++ b/test/Validation.test.js @@ -1,23 +1,52 @@ -var Validation = require('../lib/Validation').Validation; -require('should'); +'use strict'; + +const assert = require('assert'); + +const validation = require('../lib/Validation'); + +const Validation = validation.Validation; describe('Validation', function () { describe('isValidUTF8', function () { it('should return true for a valid utf8 string', function () { - var validBuffer = new Buffer('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque gravida mattis rhoncus. Donec iaculis, metus quis varius accumsan, erat mauris condimentum diam, et egestas erat enim ut ligula. Praesent sollicitudin tellus eget dolor euismod euismod. Nullam ac augue nec neque varius luctus. Curabitur elit mi, consequat ultricies adipiscing mollis, scelerisque in erat. Phasellus facilisis fermentum ullamcorper. Nulla et sem eu arcu pharetra pellentesque. Praesent consectetur tempor justo, vel iaculis dui ullamcorper sit amet. Integer tristique viverra ullamcorper. Vivamus laoreet, nulla eget suscipit eleifend, lacus lectus feugiat libero, non fermentum erat nisi at risus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut pulvinar dignissim tellus, eu dignissim lorem vulputate quis. Morbi ut pulvinar augue.'); - Validation.isValidUTF8(validBuffer).should.be.ok; + const validBuffer = Buffer.from( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' + + 'Quisque gravida mattis rhoncus. Donec iaculis, metus ' + + 'quis varius accumsan, erat mauris condimentum diam, et ' + + 'egestas erat enim ut ligula. Praesent sollicitudin tellus ' + + 'eget dolor euismod euismod. Nullam ac augue nec neque ' + + 'varius luctus. Curabitur elit mi, consequat ultricies ' + + 'adipiscing mollis, scelerisque in erat. Phasellus facilisis ' + + ' fermentum ullamcorper. Nulla et sem eu arcu pharetra ' + + 'pellentesque. Praesent consectetur tempor justo, vel ' + + 'iaculis dui ullamcorper sit amet. Integer tristique viverra ' + + 'ullamcorper. Vivamus laoreet, nulla eget suscipit eleifend, ' + + 'lacus lectus feugiat libero, non fermentum erat nisi at ' + + 'risus. Lorem ipsum dolor sit amet, consectetur adipiscing ' + + 'elit. Ut pulvinar dignissim tellus, eu dignissim lorem ' + + 'vulputate quis. Morbi ut pulvinar augue.' + ); + + assert.ok(Validation.isValidUTF8(validBuffer)); }); + it('should return false for an erroneous string', function () { - var invalidBuffer = new Buffer([0xce, 0xba, 0xe1, 0xbd, 0xb9, 0xcf, 0x83, 0xce, 0xbc, 0xce, 0xb5, 0xed, 0xa0, 0x80, 0x65, 0x64, 0x69, 0x74, 0x65, 0x64]); - Validation.isValidUTF8(invalidBuffer).should.not.be.ok; + const invalidBuffer = Buffer.from([ + 0xce, 0xba, 0xe1, 0xbd, 0xb9, 0xcf, 0x83, + 0xce, 0xbc, 0xce, 0xb5, 0xed, 0xa0, 0x80, + 0x65, 0x64, 0x69, 0x74, 0x65, 0x64 + ]); + + assert.ok(!Validation.isValidUTF8(invalidBuffer)); }); + it('should return true for valid cases from the autobahn test suite', function () { - Validation.isValidUTF8(new Buffer('\xf0\x90\x80\x80')).should.be.ok; - Validation.isValidUTF8(new Buffer([0xf0, 0x90, 0x80, 0x80])).should.be.ok; + assert.ok(Validation.isValidUTF8(Buffer.from([0xf0, 0x90, 0x80, 0x80]))); + assert.ok(Validation.isValidUTF8(Buffer.from('\xf0\x90\x80\x80'))); }); + it('should return false for erroneous autobahn strings', function () { - Validation.isValidUTF8(new Buffer([0xce, 0xba, 0xe1, 0xbd])).should.not.be.ok; + assert.ok(!Validation.isValidUTF8(Buffer.from([0xce, 0xba, 0xe1, 0xbd]))); }); }); }); - diff --git a/test/WebSocket.integration.js b/test/WebSocket.integration.js index 228ffcb93..237ac5cfc 100644 --- a/test/WebSocket.integration.js +++ b/test/WebSocket.integration.js @@ -1,27 +1,28 @@ 'use strict'; const assert = require('assert'); -const WebSocket = require('../'); + +const WebSocket = require('..'); describe('WebSocket', function () { it('communicates successfully with echo service', function (done) { - var ws = new WebSocket('ws://echo.websocket.org/', { + const ws = new WebSocket('ws://echo.websocket.org/', { origin: 'http://websocket.org', protocolVersion: 13 }); - var str = Date.now().toString(); - var dataReceived = false; - ws.on('open', function () { - ws.send(str, {mask: true}); - }); - ws.on('close', function () { - assert.equal(true, dataReceived); + const str = Date.now().toString(); + + let dataReceived = false; + + ws.on('open', () => ws.send(str, { mask: true })); + ws.on('close', () => { + assert.ok(dataReceived); done(); }); - ws.on('message', function (data, flags) { - assert.equal(str, data); - ws.terminate(); + ws.on('message', (data) => { dataReceived = true; + assert.strictEqual(data, str); + ws.close(); }); }); }); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 425689e73..ad05a9848 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -3,23 +3,23 @@ 'use strict'; const assert = require('assert'); +const crypto = require('crypto'); const https = require('https'); const http = require('http'); -const WebSocket = require('../'); const fs = require('fs'); const os = require('os'); + const server = require('./testserver'); -const crypto = require('crypto'); +const WebSocket = require('..'); const WebSocketServer = WebSocket.Server; - let port = 20000; function getArrayBuffer (buf) { - var l = buf.length; - var arrayBuf = new ArrayBuffer(l); - var uint8View = new Uint8Array(arrayBuf); - for (var i = 0; i < l; i++) { + const l = buf.length; + const arrayBuf = new ArrayBuffer(l); + const uint8View = new Uint8Array(arrayBuf); + for (let i = 0; i < l; i++) { uint8View[i] = buf[i]; } return uint8View.buffer; @@ -27,7 +27,7 @@ function getArrayBuffer (buf) { function areArraysEqual (x, y) { if (x.length !== y.length) return false; - for (var i = 0, l = x.length; i < l; ++i) { + for (let i = 0, l = x.length; i < l; ++i) { if (x[i] !== y[i]) return false; } return true; @@ -36,7 +36,7 @@ function areArraysEqual (x, y) { describe('WebSocket', function () { describe('#ctor', function () { it('should return a new instance if called without new', function (done) { - var ws = WebSocket('ws://localhost'); + const ws = WebSocket('ws://localhost'); assert.ok(ws instanceof WebSocket); ws.on('error', () => done()); @@ -91,8 +91,8 @@ describe('WebSocket', function () { describe('properties', function () { it('#bytesReceived exposes number of bytes received', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port, { perMessageDeflate: false }); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: false }); ws.on('message', function () { assert.strictEqual(ws.bytesReceived, 8); wss.close(); @@ -106,8 +106,8 @@ describe('WebSocket', function () { it('#url exposes the server url', function (done) { server.createServer(++port, function (srv) { - var url = 'ws://localhost:' + port; - var ws = new WebSocket(url); + const url = `ws://localhost:${port}`; + const ws = new WebSocket(url); assert.equal(url, ws.url); ws.terminate(); ws.on('close', function () { @@ -119,8 +119,8 @@ describe('WebSocket', function () { it('#protocolVersion exposes the protocol version', function (done) { server.createServer(++port, function (srv) { - var url = 'ws://localhost:' + port; - var ws = new WebSocket(url); + const url = `ws://localhost:${port}`; + const ws = new WebSocket(url); assert.equal(13, ws.protocolVersion); ws.terminate(); ws.on('close', function () { @@ -133,8 +133,8 @@ describe('WebSocket', function () { describe('#bufferedAmount', function () { it('defaults to zero', function (done) { server.createServer(++port, function (srv) { - var url = 'ws://localhost:' + port; - var ws = new WebSocket(url); + const url = `ws://localhost:${port}`; + const ws = new WebSocket(url); assert.equal(0, ws.bufferedAmount); ws.terminate(); ws.on('close', function () { @@ -146,8 +146,8 @@ describe('WebSocket', function () { it('defaults to zero upon "open"', function (done) { server.createServer(++port, function (srv) { - var url = 'ws://localhost:' + port; - var ws = new WebSocket(url); + const url = `ws://localhost:${port}`; + const ws = new WebSocket(url); ws.onopen = function () { assert.equal(0, ws.bufferedAmount); ws.terminate(); @@ -176,11 +176,11 @@ describe('WebSocket', function () { describe('Custom headers', function () { it('request has an authorization header', function (done) { - var auth = 'test:testpass'; - var srv = http.createServer(); - var wss = new WebSocketServer({server: srv}); + const auth = 'test:testpass'; + const srv = http.createServer(); + const wss = new WebSocketServer({server: srv}); srv.listen(++port); - var ws = new WebSocket('ws://' + auth + '@localhost:' + port); + const ws = new WebSocket('ws://' + auth + '@localhost:' + port); srv.on('upgrade', function (req, socket, head) { assert(req.headers.authorization, 'auth header exists'); assert.equal(req.headers.authorization, 'Basic ' + new Buffer(auth).toString('base64')); @@ -194,11 +194,11 @@ describe('WebSocket', function () { }); it('accepts custom headers', function (done) { - var srv = http.createServer(); - var wss = new WebSocketServer({server: srv}); + const srv = http.createServer(); + const wss = new WebSocketServer({server: srv}); srv.listen(++port); - var ws = new WebSocket('ws://localhost:' + port, { + const ws = new WebSocket(`ws://localhost:${port}`, { headers: { 'Cookie': 'foo=bar' } @@ -221,7 +221,7 @@ describe('WebSocket', function () { describe('#readyState', function () { it('defaults to connecting', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); assert.equal(WebSocket.CONNECTING, ws.readyState); ws.terminate(); ws.on('close', function () { @@ -233,7 +233,7 @@ describe('WebSocket', function () { it('set to open once connection is established', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { assert.equal(WebSocket.OPEN, ws.readyState); srv.close(); @@ -244,7 +244,7 @@ describe('WebSocket', function () { it('set to closed once connection is closed', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.close(1001); ws.on('close', function () { assert.equal(WebSocket.CLOSED, ws.readyState); @@ -256,7 +256,7 @@ describe('WebSocket', function () { it('set to closed once connection is terminated', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.terminate(); ws.on('close', function () { assert.equal(WebSocket.CLOSED, ws.readyState); @@ -271,7 +271,7 @@ describe('WebSocket', function () { * Ready state constants */ - var readyStates = { + const readyStates = { CONNECTING: 0, OPEN: 1, CLOSING: 2, @@ -285,7 +285,7 @@ describe('WebSocket', function () { Object.keys(readyStates).forEach(function (state) { describe('.' + state, function () { it('is enumerable property of class', function () { - var propertyDescripter = Object.getOwnPropertyDescriptor(WebSocket, state); + const propertyDescripter = Object.getOwnPropertyDescriptor(WebSocket, state); assert.equal(readyStates[state], propertyDescripter.value); assert.equal(true, propertyDescripter.enumerable); }); @@ -293,7 +293,7 @@ describe('WebSocket', function () { }); server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); Object.keys(readyStates).forEach(function (state) { describe('.' + state, function () { it('is property of instance', function () { @@ -306,11 +306,11 @@ describe('WebSocket', function () { describe('events', function () { it('emits a ping event', function (done) { - var wss = new WebSocketServer({port: ++port}); + const wss = new WebSocketServer({port: ++port}); wss.on('connection', function (client) { client.ping(); }); - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('ping', function () { ws.terminate(); wss.close(); @@ -319,11 +319,11 @@ describe('WebSocket', function () { }); it('emits a pong event', function (done) { - var wss = new WebSocketServer({port: ++port}); + const wss = new WebSocketServer({port: ++port}); wss.on('connection', function (client) { client.pong(); }); - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('pong', function () { ws.terminate(); wss.close(); @@ -335,7 +335,7 @@ describe('WebSocket', function () { describe('connection establishing', function () { it('can disconnect before connection is established', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.terminate(); ws.on('open', function () { assert.fail('connect shouldnt be raised here'); @@ -349,7 +349,7 @@ describe('WebSocket', function () { it('can close before connection is established', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.close(1001); ws.on('open', function () { assert.fail('connect shouldnt be raised here'); @@ -365,11 +365,11 @@ describe('WebSocket', function () { // Here, we don't create a server, to guarantee that the connection will // fail before the request is upgraded ++port; - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { assert.fail('connect shouldnt be raised here'); }); - var errorCallBackFired = false; + let errorCallBackFired = false; ws.on('error', function () { errorCallBackFired = true; }); @@ -384,7 +384,7 @@ describe('WebSocket', function () { it('invalid server key is denied', function (done) { server.createServer(++port, server.handlers.invalidKey, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', function () { srv.close(); done(); @@ -394,7 +394,7 @@ describe('WebSocket', function () { it('close event is raised when server closes connection', function (done) { server.createServer(++port, server.handlers.closeAfterConnect, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', function () { srv.close(); done(); @@ -404,7 +404,7 @@ describe('WebSocket', function () { it('error is emitted if server aborts connection', function (done) { server.createServer(++port, server.handlers.return401, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { assert.fail('connect shouldnt be raised here'); }); @@ -417,14 +417,14 @@ describe('WebSocket', function () { it('unexpected response can be read when sent by server', function (done) { server.createServer(++port, server.handlers.return401, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { assert.fail('connect shouldnt be raised here'); }); ws.on('unexpected-response', function (req, res) { assert.equal(res.statusCode, 401); - var data = ''; + let data = ''; res.on('data', function (v) { data += v; @@ -444,7 +444,7 @@ describe('WebSocket', function () { it('request can be aborted when unexpected response is sent by server', function (done) { server.createServer(++port, server.handlers.return401, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { assert.fail('connect shouldnt be raised here'); }); @@ -467,8 +467,8 @@ describe('WebSocket', function () { describe('connection with query string', function () { it('connects when pathname is not null', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket(`ws://localhost:${port}/?token=qwerty`); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}/?token=qwerty`); ws.on('open', function () { wss.close(done); }); @@ -476,8 +476,8 @@ describe('WebSocket', function () { }); it('connects when pathname is null', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket(`ws://localhost:${port}?token=qwerty`); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}?token=qwerty`); ws.on('open', function () { wss.close(done); }); @@ -491,12 +491,12 @@ describe('WebSocket', function () { // to localhost can cause the test to succeed even when the stream pausing // isn't working as intended. that is an extremely unlikely scenario, though // and an acceptable risk for the test. - var client; - var serverClient; - var openCount = 0; + let client; + let serverClient; + let openCount = 0; function onOpen () { if (++openCount === 2) { - var paused = true; + let paused = true; serverClient.on('message', function () { assert.ok(!paused); wss.close(); @@ -510,8 +510,8 @@ describe('WebSocket', function () { client.send('foo'); } } - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}`); serverClient = ws; serverClient.on('open', onOpen); }); @@ -525,7 +525,7 @@ describe('WebSocket', function () { describe('#ping', function () { it('before connect should fail', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', function () {}); try { ws.ping(); @@ -539,7 +539,7 @@ describe('WebSocket', function () { it('before connect can silently fail', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', function () {}); ws.ping('', {}, true); srv.close(); @@ -550,7 +550,7 @@ describe('WebSocket', function () { it('without message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.ping(); }); @@ -564,7 +564,7 @@ describe('WebSocket', function () { it('with message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.ping('hi'); }); @@ -579,7 +579,7 @@ describe('WebSocket', function () { it('can send safely receive numbers as ping payload', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.ping(200); @@ -596,7 +596,7 @@ describe('WebSocket', function () { it('with encoded message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.ping('hi', {mask: true}); }); @@ -614,7 +614,7 @@ describe('WebSocket', function () { describe('#pong', function () { it('before connect should fail', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', function () {}); try { ws.pong(); @@ -628,7 +628,7 @@ describe('WebSocket', function () { it('before connect can silently fail', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', function () {}); ws.pong('', {}, true); srv.close(); @@ -639,7 +639,7 @@ describe('WebSocket', function () { it('without message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.pong(); }); @@ -653,7 +653,7 @@ describe('WebSocket', function () { it('with message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.pong('hi'); }); @@ -668,7 +668,7 @@ describe('WebSocket', function () { it('with encoded message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.pong('hi', {mask: true}); }); @@ -686,9 +686,9 @@ describe('WebSocket', function () { describe('#send', function () { it('very long binary data can be sent and received (with echoing server)', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var array = new Float32Array(5 * 1024 * 1024); - for (var i = 0; i < array.length; ++i) array[i] = i / 5; + const ws = new WebSocket(`ws://localhost:${port}`); + const array = new Float32Array(5 * 1024 * 1024); + for (let i = 0; i < array.length; ++i) array[i] = i / 5; ws.on('open', function () { ws.send(array, {binary: true}); }); @@ -704,7 +704,7 @@ describe('WebSocket', function () { it('can send and receive text data', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.send('hi'); }); @@ -737,10 +737,10 @@ describe('WebSocket', function () { it('send and receive binary data as an array', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var array = new Float32Array(6); - for (var i = 0; i < array.length; ++i) array[i] = i / 2; - var partial = array.subarray(2, 5); + const ws = new WebSocket(`ws://localhost:${port}`); + const array = new Float32Array(6); + for (let i = 0; i < array.length; ++i) array[i] = i / 2; + const partial = array.subarray(2, 5); ws.on('open', function () { ws.send(partial, {binary: true}); }); @@ -756,8 +756,8 @@ describe('WebSocket', function () { it('binary data can be sent and received as buffer', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var buf = new Buffer('foobar'); + const ws = new WebSocket(`ws://localhost:${port}`); + const buf = new Buffer('foobar'); ws.on('open', function () { ws.send(buf, {binary: true}); }); @@ -773,9 +773,9 @@ describe('WebSocket', function () { it('ArrayBuffer is auto-detected without binary flag', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var array = new Float32Array(5); - for (var i = 0; i < array.length; ++i) array[i] = i / 2; + const ws = new WebSocket(`ws://localhost:${port}`); + const array = new Float32Array(5); + for (let i = 0; i < array.length; ++i) array[i] = i / 2; ws.on('open', function () { ws.send(array.buffer); }); @@ -791,8 +791,8 @@ describe('WebSocket', function () { it('Buffer is auto-detected without binary flag', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var buf = new Buffer('foobar'); + const ws = new WebSocket(`ws://localhost:${port}`); + const buf = new Buffer('foobar'); ws.on('open', function () { ws.send(buf); }); @@ -808,7 +808,7 @@ describe('WebSocket', function () { it('before connect should fail', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', function () {}); try { ws.send('hi'); @@ -822,7 +822,7 @@ describe('WebSocket', function () { it('before connect should pass error through callback, if present', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', function () {}); ws.send('hi', function (error) { assert.ok(error instanceof Error); @@ -835,7 +835,7 @@ describe('WebSocket', function () { it('without data should be successful', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.send(); }); @@ -850,7 +850,7 @@ describe('WebSocket', function () { it('calls optional callback when flushed', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.send('hi', function () { srv.close(); @@ -863,7 +863,7 @@ describe('WebSocket', function () { it('with unencoded message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.send('hi'); }); @@ -878,7 +878,7 @@ describe('WebSocket', function () { it('with encoded message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.send('hi', {mask: true}); }); @@ -894,9 +894,9 @@ describe('WebSocket', function () { it('with unencoded binary message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var array = new Float32Array(5); - for (var i = 0; i < array.length; ++i) array[i] = i / 2; + const ws = new WebSocket(`ws://localhost:${port}`); + const array = new Float32Array(5); + for (let i = 0; i < array.length; ++i) array[i] = i / 2; ws.on('open', function () { ws.send(array, {binary: true}); }); @@ -912,9 +912,9 @@ describe('WebSocket', function () { it('with encoded binary message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var array = new Float32Array(5); - for (var i = 0; i < array.length; ++i) array[i] = i / 2; + const ws = new WebSocket(`ws://localhost:${port}`); + const array = new Float32Array(5); + for (let i = 0; i < array.length; ++i) array[i] = i / 2; ws.on('open', function () { ws.send(array, {mask: true, binary: true}); }); @@ -931,10 +931,10 @@ describe('WebSocket', function () { it('with binary stream will send fragmented data', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var callbackFired = false; + const ws = new WebSocket(`ws://localhost:${port}`); + let callbackFired = false; ws.on('open', function () { - var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); + const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); ws.send(fileStream, {binary: true}, function (error) { assert.equal(null, error); callbackFired = true; @@ -955,10 +955,10 @@ describe('WebSocket', function () { it('with text stream will send fragmented data', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var callbackFired = false; + const ws = new WebSocket(`ws://localhost:${port}`); + let callbackFired = false; ws.on('open', function () { - var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream, {binary: false}, function (error) { assert.equal(null, error); callbackFired = true; @@ -979,14 +979,14 @@ describe('WebSocket', function () { it('will cause intermittent send to be delayed in order', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { - var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.send('foobar'); ws.send('baz'); }); - var receivedIndex = 0; + let receivedIndex = 0; srv.on('message', function (data, flags) { ++receivedIndex; if (receivedIndex === 1) { @@ -1008,18 +1008,18 @@ describe('WebSocket', function () { it('will cause intermittent stream to be delayed in order', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { - var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); - var i = 0; + let i = 0; ws.stream(function (error, send) { assert.ok(!error); if (++i === 1) send('foo'); else send('bar', true); }); }); - var receivedIndex = 0; + let receivedIndex = 0; srv.on('message', function (data, flags) { ++receivedIndex; if (receivedIndex === 1) { @@ -1038,13 +1038,13 @@ describe('WebSocket', function () { it('will cause intermittent ping to be delivered', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { - var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.ping('foobar'); }); - var receivedIndex = 0; + let receivedIndex = 0; srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); @@ -1067,13 +1067,13 @@ describe('WebSocket', function () { it('will cause intermittent pong to be delivered', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { - var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.pong('foobar'); }); - var receivedIndex = 0; + let receivedIndex = 0; srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); @@ -1096,9 +1096,9 @@ describe('WebSocket', function () { it('will cause intermittent close to be delivered', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { - var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream); ws.close(1000, 'foobar'); }); @@ -1123,19 +1123,19 @@ describe('WebSocket', function () { describe('#stream', function () { it('very long binary data can be streamed', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var buffer = new Buffer(10 * 1024); - for (var i = 0; i < buffer.length; ++i) buffer[i] = i % 0xff; + const ws = new WebSocket(`ws://localhost:${port}`); + const buffer = new Buffer(10 * 1024); + for (let i = 0; i < buffer.length; ++i) buffer[i] = i % 0xff; ws.on('open', function () { - var i = 0; - var blockSize = 800; - var bufLen = buffer.length; + let i = 0; + const blockSize = 800; + const bufLen = buffer.length; ws.stream({binary: true}, function (error, send) { assert.ok(!error); - var start = i * blockSize; - var toSend = Math.min(blockSize, bufLen - (i * blockSize)); - var end = start + toSend; - var isFinal = toSend < blockSize; + const start = i * blockSize; + const toSend = Math.min(blockSize, bufLen - (i * blockSize)); + const end = start + toSend; + const isFinal = toSend < blockSize; send(buffer.slice(start, end), isFinal); i += 1; }); @@ -1152,7 +1152,7 @@ describe('WebSocket', function () { it('before connect should pass error through callback', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', function () {}); ws.stream(function (error) { assert.ok(error instanceof Error); @@ -1165,7 +1165,7 @@ describe('WebSocket', function () { it('without callback should fail', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { try { ws.stream(); @@ -1180,10 +1180,10 @@ describe('WebSocket', function () { it('will cause intermittent send to be delayed in order', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var payload = 'HelloWorld'; + const ws = new WebSocket(`ws://localhost:${port}`); + const payload = 'HelloWorld'; ws.on('open', function () { - var i = 0; + let i = 0; ws.stream(function (error, send) { assert.ok(!error); if (++i === 1) { @@ -1195,7 +1195,7 @@ describe('WebSocket', function () { } }); }); - var receivedIndex = 0; + let receivedIndex = 0; srv.on('message', function (data, flags) { ++receivedIndex; if (receivedIndex === 1) { @@ -1217,15 +1217,15 @@ describe('WebSocket', function () { it('will cause intermittent stream to be delayed in order', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var payload = 'HelloWorld'; + const ws = new WebSocket(`ws://localhost:${port}`); + const payload = 'HelloWorld'; ws.on('open', function () { - var i = 0; + let i = 0; ws.stream(function (error, send) { assert.ok(!error); if (++i === 1) { send(payload.substr(0, 5)); - var i2 = 0; + let i2 = 0; ws.stream(function (error, send) { assert.ok(!error); if (++i2 === 1) send('foo'); @@ -1237,7 +1237,7 @@ describe('WebSocket', function () { } }); }); - var receivedIndex = 0; + let receivedIndex = 0; srv.on('message', function (data, flags) { ++receivedIndex; if (receivedIndex === 1) { @@ -1263,10 +1263,10 @@ describe('WebSocket', function () { it('will cause intermittent ping to be delivered', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var payload = 'HelloWorld'; + const ws = new WebSocket(`ws://localhost:${port}`); + const payload = 'HelloWorld'; ws.on('open', function () { - var i = 0; + let i = 0; ws.stream(function (error, send) { assert.ok(!error); if (++i === 1) { @@ -1277,7 +1277,7 @@ describe('WebSocket', function () { } }); }); - var receivedIndex = 0; + let receivedIndex = 0; srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.equal(payload, data); @@ -1300,10 +1300,10 @@ describe('WebSocket', function () { it('will cause intermittent pong to be delivered', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var payload = 'HelloWorld'; + const ws = new WebSocket(`ws://localhost:${port}`); + const payload = 'HelloWorld'; ws.on('open', function () { - var i = 0; + let i = 0; ws.stream(function (error, send) { assert.ok(!error); if (++i === 1) { @@ -1314,7 +1314,7 @@ describe('WebSocket', function () { } }); }); - var receivedIndex = 0; + let receivedIndex = 0; srv.on('message', function (data, flags) { assert.ok(!flags.binary); assert.equal(payload, data); @@ -1337,11 +1337,11 @@ describe('WebSocket', function () { it('will cause intermittent close to be delivered', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var payload = 'HelloWorld'; - var errorGiven = false; + const ws = new WebSocket(`ws://localhost:${port}`); + const payload = 'HelloWorld'; + let errorGiven = false; ws.on('open', function () { - var i = 0; + let i = 0; ws.stream(function (error, send) { if (++i === 1) { send(payload.substr(0, 5)); @@ -1375,10 +1375,10 @@ describe('WebSocket', function () { describe('#close', function () { it('will raise error callback, if any, if called during send stream', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var errorGiven = false; + const ws = new WebSocket(`ws://localhost:${port}`); + let errorGiven = false; ws.on('open', function () { - var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); ws.send(fileStream, function (error) { errorGiven = error != null; }); @@ -1397,7 +1397,7 @@ describe('WebSocket', function () { it('without invalid first argument throws exception', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { try { ws.close('error'); @@ -1412,7 +1412,7 @@ describe('WebSocket', function () { it('without reserved error code 1004 throws exception', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { try { ws.close(1004); @@ -1427,7 +1427,7 @@ describe('WebSocket', function () { it('without message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.close(1000); }); @@ -1442,7 +1442,7 @@ describe('WebSocket', function () { it('with message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.close(1000, 'some reason'); }); @@ -1458,7 +1458,7 @@ describe('WebSocket', function () { it('with encoded message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { ws.close(1000, 'some reason', {mask: true}); }); @@ -1474,8 +1474,8 @@ describe('WebSocket', function () { it('ends connection to the server', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var connectedOnce = false; + const ws = new WebSocket(`ws://localhost:${port}`); + let connectedOnce = false; ws.on('open', function () { connectedOnce = true; ws.close(1000, 'some reason', {mask: true}); @@ -1490,15 +1490,15 @@ describe('WebSocket', function () { }); it('consumes all data when the server socket closed', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({port: ++port}, function () { wss.on('connection', function (conn) { conn.send('foo'); conn.send('bar'); conn.send('baz'); conn.close(); }); - var ws = new WebSocket('ws://localhost:' + port); - var messages = []; + const ws = new WebSocket(`ws://localhost:${port}`); + const messages = []; ws.on('message', function (message) { messages.push(message); if (messages.length === 3) { @@ -1512,8 +1512,8 @@ describe('WebSocket', function () { }); it('allows close code 1013', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket(`ws://localhost:${port}`); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', function (code) { assert.strictEqual(code, 1013); wss.close(done); @@ -1529,8 +1529,8 @@ describe('WebSocket', function () { describe('W3C API emulation', function () { it('should not throw errors when getting and setting', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var listener = function () {}; + const ws = new WebSocket(`ws://localhost:${port}`); + const listener = function () {}; ws.onmessage = listener; ws.onerror = listener; @@ -1554,10 +1554,10 @@ describe('WebSocket', function () { it('should work the same as the EventEmitter api', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var message = 0; - var close = 0; - var open = 0; + const ws = new WebSocket(`ws://localhost:${port}`); + let message = 0; + let close = 0; + let open = 0; ws.onmessage = function (messageEvent) { assert.ok(!!messageEvent.data); @@ -1593,7 +1593,7 @@ describe('WebSocket', function () { it('should receive text data wrapped in a MessageEvent when using addEventListener', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('open', function () { ws.send('hi'); }); @@ -1607,8 +1607,8 @@ describe('WebSocket', function () { }); it('should receive valid CloseEvent when server closes with code 1000', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('close', function (closeEvent) { assert.equal(true, closeEvent.wasClean); assert.equal(1000, closeEvent.code); @@ -1623,8 +1623,8 @@ describe('WebSocket', function () { }); it('should receive valid CloseEvent when server closes with code 1001', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('close', function (closeEvent) { assert.equal(false, closeEvent.wasClean); assert.equal(1001, closeEvent.code); @@ -1640,8 +1640,8 @@ describe('WebSocket', function () { }); it('should have target set on Events', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('open', function (openEvent) { assert.equal(ws, openEvent.target); }); @@ -1666,8 +1666,8 @@ describe('WebSocket', function () { }); it('should have type set on Events', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('open', function (openEvent) { assert.equal('open', openEvent.type); }); @@ -1693,8 +1693,8 @@ describe('WebSocket', function () { it('should pass binary data as a node.js Buffer by default', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); - var array = new Uint8Array(4096); + const ws = new WebSocket(`ws://localhost:${port}`); + const array = new Uint8Array(4096); ws.onopen = function () { ws.send(array, {binary: true}); @@ -1712,9 +1712,9 @@ describe('WebSocket', function () { it('should pass an ArrayBuffer for event.data if binaryType = arraybuffer', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.binaryType = 'arraybuffer'; - var array = new Uint8Array(4096); + const array = new Uint8Array(4096); ws.onopen = function () { ws.send(array, {binary: true}); @@ -1731,7 +1731,7 @@ describe('WebSocket', function () { it('should ignore binaryType for text messages', function (done) { server.createServer(++port, function (srv) { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); ws.binaryType = 'arraybuffer'; ws.onopen = function () { @@ -1927,8 +1927,8 @@ describe('WebSocket', function () { it('excludes default ports from host header', function () { // can't create a server listening on ports 80 or 443 // so we need to expose the method that does this - var buildHostHeader = WebSocket.buildHostHeader; - var host = buildHostHeader(false, 'localhost', 80); + const buildHostHeader = WebSocket.buildHostHeader; + let host = buildHostHeader(false, 'localhost', 80); assert.equal('localhost', host); host = buildHostHeader(false, 'localhost', 88); assert.equal('localhost:88', host); @@ -1941,10 +1941,10 @@ describe('WebSocket', function () { describe('permessage-deflate', function () { it('is enabled by default', function (done) { - var srv = http.createServer(); - var wss = new WebSocketServer({server: srv, perMessageDeflate: true}); + const srv = http.createServer(); + const wss = new WebSocketServer({server: srv, perMessageDeflate: true}); srv.listen(++port, function () { - var ws = new WebSocket('ws://localhost:' + port); + const ws = new WebSocket(`ws://localhost:${port}`); srv.on('upgrade', function (req, socket, head) { assert.ok(~req.headers['sec-websocket-extensions'].indexOf('permessage-deflate')); }); @@ -1958,10 +1958,10 @@ describe('WebSocket', function () { }); it('can be disabled', function (done) { - var srv = http.createServer(); - var wss = new WebSocketServer({server: srv, perMessageDeflate: true}); + const srv = http.createServer(); + const wss = new WebSocketServer({server: srv, perMessageDeflate: true}); srv.listen(++port, function () { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: false}); + const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: false}); srv.on('upgrade', function (req, socket, head) { assert.ok(!req.headers['sec-websocket-extensions']); ws.terminate(); @@ -1972,10 +1972,10 @@ describe('WebSocket', function () { }); it('can send extension parameters', function (done) { - var srv = http.createServer(); - var wss = new WebSocketServer({server: srv, perMessageDeflate: true}); + const srv = http.createServer(); + const wss = new WebSocketServer({server: srv, perMessageDeflate: true}); srv.listen(++port, function () { - var ws = new WebSocket('ws://localhost:' + port, { + const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { serverNoContextTakeover: true, clientNoContextTakeover: true, @@ -1984,7 +1984,7 @@ describe('WebSocket', function () { } }); srv.on('upgrade', function (req, socket, head) { - var extensions = req.headers['sec-websocket-extensions']; + const extensions = req.headers['sec-websocket-extensions']; assert.ok(~extensions.indexOf('permessage-deflate')); assert.ok(~extensions.indexOf('server_no_context_takeover')); assert.ok(~extensions.indexOf('client_no_context_takeover')); @@ -1998,8 +1998,8 @@ describe('WebSocket', function () { }); it('can send and receive text data', function (done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); + const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { + const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); ws.on('open', function () { ws.send('hi', {compress: true}); }); @@ -2018,10 +2018,10 @@ describe('WebSocket', function () { }); it('can send and receive a typed array', function (done) { - var array = new Float32Array(5); - for (var i = 0; i < array.length; i++) array[i] = i / 2; - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); + const array = new Float32Array(5); + for (let i = 0; i < array.length; i++) array[i] = i / 2; + const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { + const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); ws.on('open', function () { ws.send(array, {compress: true}); }); @@ -2040,10 +2040,10 @@ describe('WebSocket', function () { }); it('can send and receive ArrayBuffer', function (done) { - var array = new Float32Array(5); - for (var i = 0; i < array.length; i++) array[i] = i / 2; - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); + const array = new Float32Array(5); + for (let i = 0; i < array.length; i++) array[i] = i / 2; + const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { + const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); ws.on('open', function () { ws.send(array.buffer, {compress: true}); }); @@ -2062,11 +2062,11 @@ describe('WebSocket', function () { }); it('with binary stream will send fragmented data', function (done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); - var callbackFired = false; + const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { + const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); + let callbackFired = false; ws.on('open', function () { - var fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); + const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); ws.send(fileStream, {binary: true, compress: true}, function (error) { assert.equal(null, error); callbackFired = true; @@ -2089,8 +2089,8 @@ describe('WebSocket', function () { describe('#send', function () { it('can set the compress option true when perMessageDeflate is disabled', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: false}); + const wss = new WebSocketServer({port: ++port}, function () { + const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: false}); ws.on('open', function () { ws.send('hi', {compress: true}); }); @@ -2111,9 +2111,9 @@ describe('WebSocket', function () { describe('#close', function () { it('should not raise error callback, if any, if called during send data', function (done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); - var errorGiven = false; + const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { + const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); + let errorGiven = false; ws.on('open', function () { ws.send('hi', function (error) { errorGiven = error != null; @@ -2134,9 +2134,9 @@ describe('WebSocket', function () { describe('#terminate', function () { it('will raise error callback, if any, if called during send data', function (done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: { threshold: 0 }}); - var errorGiven = false; + const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { + const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: { threshold: 0 }}); + let errorGiven = false; ws.on('open', function () { ws.send('hi', function (error) { errorGiven = error != null; @@ -2155,10 +2155,10 @@ describe('WebSocket', function () { }); it('can call during receiving data', function (done) { - var wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - var ws = new WebSocket('ws://localhost:' + port, {perMessageDeflate: true}); + const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { + const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); wss.on('connection', function (client) { - for (var i = 0; i < 10; i++) { + for (let i = 0; i < 10; i++) { client.send('hi'); } client.send('hi', function () { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 35f4d9c5b..9d035c375 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -3,14 +3,13 @@ 'use strict'; const assert = require('assert'); -const WebSocket = require('../'); const https = require('https'); const http = require('http'); const fs = require('fs'); -require('should'); -const WebSocketServer = WebSocket.Server; +const WebSocket = require('..'); +const WebSocketServer = WebSocket.Server; let port = 8000; describe('WebSocketServer', function () { @@ -24,25 +23,25 @@ describe('WebSocketServer', function () { }); it('should return a new instance if called without new', function () { - var wss = WebSocketServer({ noServer: true }); + const wss = WebSocketServer({ noServer: true }); assert.ok(wss instanceof WebSocketServer); }); it('emits an error if http server bind fails', function (done) { - var wss1 = new WebSocketServer({ port: 50003 }); - var wss2 = new WebSocketServer({ port: 50003 }); - wss2.on('error', function () { + const wss1 = new WebSocketServer({ port: 50003 }); + const wss2 = new WebSocketServer({ port: 50003 }); + wss2.on('error', () => { wss1.close(); done(); }); }); it('starts a server on a given port', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); }); - wss.on('connection', function (client) { + wss.on('connection', (client) => { wss.close(); done(); }); @@ -55,7 +54,7 @@ describe('WebSocketServer', function () { const wss = new WebSocketServer({ server }); const ws = new WebSocket(`ws://localhost:${port}`); - wss.on('connection', function (client) { + wss.on('connection', (client) => { wss.close(); server.close(done); }); @@ -63,13 +62,13 @@ describe('WebSocketServer', function () { }); it('426s for non-Upgrade requests', function (done) { - var wss = new WebSocketServer({ port: ++port }, function () { - http.get('http://localhost:' + port, function (res) { - var body = ''; + const wss = new WebSocketServer({ port: ++port }, () => { + http.get(`http://localhost:${port}`, (res) => { + let body = ''; assert.strictEqual(res.statusCode, 426); - res.on('data', function (chunk) { body += chunk; }); - res.on('end', function () { + res.on('data', (chunk) => { body += chunk; }); + res.on('end', () => { assert.strictEqual(body, http.STATUS_CODES[426]); wss.close(); done(); @@ -170,7 +169,7 @@ describe('WebSocketServer', function () { describe('#close', function () { it('does not thrown when called twice', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({ port: ++port }, () => { wss.close(); wss.close(); wss.close(); @@ -180,15 +179,15 @@ describe('WebSocketServer', function () { }); it('will close all clients', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port); - ws.on('close', function () { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + ws.on('close', () => { if (++closes === 2) done(); }); }); - var closes = 0; - wss.on('connection', function (client) { - client.on('close', function () { + let closes = 0; + wss.on('connection', (client) => { + client.on('close', () => { if (++closes === 2) done(); }); wss.close(); @@ -205,7 +204,7 @@ describe('WebSocketServer', function () { const wss = new WebSocketServer({ server }); - wss.on('connection', function (ws) { + wss.on('connection', (ws) => { wss.close(); server.close = realClose; server.close(done); @@ -232,81 +231,91 @@ describe('WebSocketServer', function () { }); it('cleans up websocket data on a precreated server', function (done) { - const srv = http.createServer(); - srv.listen(++port, () => { - const wss1 = new WebSocketServer({server: srv, path: '/wss1'}); - const wss2 = new WebSocketServer({server: srv, path: '/wss2'}); - (typeof srv._webSocketPaths).should.eql('object'); - Object.keys(srv._webSocketPaths).length.should.eql(2); + const server = http.createServer(); + + server.listen(++port, () => { + const wss1 = new WebSocketServer({ server, path: '/wss1' }); + const wss2 = new WebSocketServer({ server, path: '/wss2' }); + + assert.strictEqual(typeof server._webSocketPaths, 'object'); + assert.strictEqual(Object.keys(server._webSocketPaths).length, 2); + wss1.close(); - Object.keys(srv._webSocketPaths).length.should.eql(1); + + assert.strictEqual(Object.keys(server._webSocketPaths).length, 1); + wss2.close(); - (typeof srv._webSocketPaths).should.eql('undefined'); - srv.close(done); + + assert.strictEqual(typeof server._webSocketPaths, 'undefined'); + server.close(done); }); }); }); describe('#clients', function () { it('returns a list of connected clients', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - wss.clients.size.should.eql(0); - var ws = new WebSocket('ws://localhost:' + port); + const wss = new WebSocketServer({ port: ++port }, () => { + assert.strictEqual(wss.clients.size, 0); + const ws = new WebSocket(`ws://localhost:${port}`); }); - wss.on('connection', function (client) { - wss.clients.size.should.eql(1); + + wss.on('connection', (client) => { + assert.strictEqual(wss.clients.size, 1); wss.close(); done(); }); }); it('can be disabled', function (done) { - var wss = new WebSocketServer({port: ++port, clientTracking: false}, function () { - wss.should.not.have.property('clients'); - var ws = new WebSocket('ws://localhost:' + port); + const wss = new WebSocketServer({ port: ++port, clientTracking: false }, () => { + assert.strictEqual(wss.clients, undefined); + const ws = new WebSocket(`ws://localhost:${port}`); }); - wss.on('connection', function (client) { - wss.should.not.have.property('clients'); + + wss.on('connection', (client) => { + assert.strictEqual(wss.clients, undefined); wss.close(); done(); }); }); it('is updated when client terminates the connection', function (done) { - var ws; - var wss = new WebSocketServer({port: ++port}, function () { - ws = new WebSocket('ws://localhost:' + port); - }); - wss.on('connection', function (client) { - client.on('close', function () { - wss.clients.size.should.eql(0); - wss.close(); - done(); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + wss.on('connection', (client) => { + client.on('close', () => { + assert.strictEqual(wss.clients.size, 0); + wss.close(); + done(); + }); + + ws.close(); }); - ws.terminate(); }); }); it('is updated when client closes the connection', function (done) { - var ws; - var wss = new WebSocketServer({port: ++port}, function () { - ws = new WebSocket('ws://localhost:' + port); - }); - wss.on('connection', function (client) { - client.on('close', function () { - wss.clients.size.should.eql(0); - wss.close(); - done(); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + wss.on('connection', (client) => { + client.on('close', () => { + assert.strictEqual(wss.clients.size, 0); + wss.close(); + done(); + }); + + ws.close(); }); - ws.close(); }); }); }); describe('#options', function () { it('exposes options passed to constructor', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - wss.options.port.should.eql(port); + const wss = new WebSocketServer({ port: ++port }, () => { + assert.strictEqual(wss.options.port, port); wss.close(); done(); }); @@ -315,41 +324,43 @@ describe('WebSocketServer', function () { describe('#maxpayload', function () { it('maxpayload is passed on to clients,', function (done) { - var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port, maxPayload: _maxPayload}, function () { - wss.clients.size.should.eql(0); - var ws = new WebSocket('ws://localhost:' + port); - }); - wss.on('connection', function (client) { - wss.clients.size.should.eql(1); - client.maxPayload.should.eql(_maxPayload); + const maxPayload = 20480; + const wss = new WebSocketServer({ port: ++port, maxPayload }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + }); + + wss.on('connection', (client) => { + assert.strictEqual(client.maxPayload, maxPayload); wss.close(); done(); }); }); + it('maxpayload is passed on to hybi receivers', function (done) { - var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port, maxPayload: _maxPayload}, function () { - wss.clients.size.should.eql(0); - var ws = new WebSocket('ws://localhost:' + port); - }); - wss.on('connection', function (client) { - wss.clients.size.should.eql(1); - client._receiver.maxPayload.should.eql(_maxPayload); + const maxPayload = 20480; + const wss = new WebSocketServer({ port: ++port, maxPayload }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + }); + + wss.on('connection', (client) => { + assert.strictEqual(client._receiver.maxPayload, maxPayload); wss.close(); done(); }); }); + it('maxpayload is passed on to permessage-deflate', function (done) { - var PerMessageDeflate = require('../lib/PerMessageDeflate'); - var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port, maxPayload: _maxPayload}, function () { - wss.clients.size.should.eql(0); - var ws = new WebSocket('ws://localhost:' + port); - }); - wss.on('connection', function (client) { - wss.clients.size.should.eql(1); - client._receiver.extensions[PerMessageDeflate.extensionName]._maxPayload.should.eql(_maxPayload); + const PerMessageDeflate = require('../lib/PerMessageDeflate'); + const maxPayload = 20480; + const wss = new WebSocketServer({ port: ++port, maxPayload }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + }); + + wss.on('connection', (client) => { + assert.strictEqual( + client._receiver.extensions[PerMessageDeflate.extensionName]._maxPayload, + maxPayload + ); wss.close(); done(); }); @@ -358,627 +369,648 @@ describe('WebSocketServer', function () { describe('#handleUpgrade', function () { it('can be used for a pre-existing server', function (done) { - var srv = http.createServer(); - srv.listen(++port, function () { - var wss = new WebSocketServer({noServer: true}); - srv.on('upgrade', function (req, socket, upgradeHead) { - wss.handleUpgrade(req, socket, upgradeHead, function (client) { - client.send('hello'); - }); + const server = http.createServer(); + + server.listen(++port, () => { + const wss = new WebSocketServer({ noServer: true }); + + server.on('upgrade', (req, socket, head) => { + wss.handleUpgrade(req, socket, head, (client) => client.send('hello')); }); - var ws = new WebSocket('ws://localhost:' + port); - ws.on('message', function (message) { - message.should.eql('hello'); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('message', (message) => { + assert.strictEqual(message, 'hello'); wss.close(); - srv.close(); - done(); + server.close(done); }); }); }); it('closes the connection when path does not match', function (done) { - var wss = new WebSocketServer({port: ++port, path: '/ws'}, function () { - var options = { - port: port, - host: '127.0.0.1', + const wss = new WebSocketServer({ port: ++port, path: '/ws' }, () => { + const req = http.request({ headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket' - } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { - res.statusCode.should.eql(400); + }, + host: '127.0.0.1', + port + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); wss.close(); done(); }); + + req.end(); }); }); it('closes the connection when protocol version is Hixie-76', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var options = { - port: port, + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ headers: { 'Connection': 'Upgrade', 'Upgrade': 'WebSocket', 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', 'Sec-WebSocket-Protocol': 'sample' - } - }; - var req = http.request(options); - req.on('response', function (res) { - res.statusCode.should.eql(400); + }, + port + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); wss.close(); done(); }); + req.end(); }); }); }); - describe('hybi mode', function () { - describe('connection establishing', function () { - it('does not accept connections with no sec-websocket-key', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket' - } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { - res.statusCode.should.eql(400); - wss.close(); - done(); - }); - }); - wss.on('connection', function (ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function () {}); - }); - - it('does not accept connections with no sec-websocket-version', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==' - } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { - res.statusCode.should.eql(400); - wss.close(); - done(); - }); - }); - wss.on('connection', function (ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function () {}); - }); - - it('does not accept connections with invalid sec-websocket-version', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 12 - } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { - res.statusCode.should.eql(400); - wss.close(); - done(); - }); + describe('connection establishing', function () { + it('does not accept connections with no sec-websocket-key', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket' + }, + host: '127.0.0.1', + port }); - wss.on('connection', function (ws) { - done(new Error('connection must not be established')); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); + wss.close(); + done(); }); - wss.on('error', function () {}); - }); - - it('client can be denied', function (done) { - const wss = new WebSocketServer({ - verifyClient: (o) => false, - port: ++port - }, () => { - const req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8, - 'Sec-WebSocket-Origin': 'http://foobar.com' - } - }); - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 401); - wss.close(); - done(); - }); + req.end(); + }); - req.end(); + wss.on('connection', (ws) => { + done(new Error('connection must not be established')); + }); + }); + + it('does not accept connections with no sec-websocket-version', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==' + }, + host: '127.0.0.1', + port }); - wss.on('connection', (ws) => { - done(new Error('connection must not be established')); + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); + wss.close(); + done(); }); + + req.end(); }); - it('client can be accepted', function (done) { - var wss = new WebSocketServer({ - port: ++port, - verifyClient: (o) => true - }, () => { - var req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobar.com' - } - }); - req.end(); + wss.on('connection', (ws) => { + done(new Error('connection must not be established')); + }); + }); + + it('does not accept connections with invalid sec-websocket-version', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 12 + }, + host: '127.0.0.1', + port }); - wss.on('connection', function (ws) { - ws.terminate(); + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); wss.close(); done(); }); + + req.end(); }); - it('verifyClient gets client origin', function (done) { - var verifyClientCalled = false; - var wss = new WebSocketServer({ - verifyClient: (info) => { - info.origin.should.eql('http://foobarbaz.com'); - verifyClientCalled = true; - return false; + wss.on('connection', (ws) => { + done(new Error('connection must not be established')); + }); + }); + + it('client can be denied', function (done) { + const wss = new WebSocketServer({ + verifyClient: (o) => false, + port: ++port + }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8, + 'Sec-WebSocket-Origin': 'http://foobar.com' }, - port: ++port - }, () => { - var req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobarbaz.com' - } - }); - req.on('response', (res) => { - verifyClientCalled.should.be.ok; - wss.close(); - done(); - }); - req.end(); + host: '127.0.0.1', + port + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 401); + wss.close(); + done(); }); + + req.end(); }); - it('verifyClient gets original request', function (done) { - var verifyClientCalled = false; - var wss = new WebSocketServer({ - verifyClient: (info) => { - info.req.headers['sec-websocket-key'].should.eql('dGhlIHNhbXBsZSBub25jZQ=='); - verifyClientCalled = true; - return false; + wss.on('connection', (ws) => { + done(new Error('connection must not be established')); + }); + }); + + it('client can be accepted', function (done) { + const wss = new WebSocketServer({ + port: ++port, + verifyClient: (o) => true + }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Origin': 'http://foobar.com' }, - port: ++port - }, () => { - var req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobarbaz.com' - } - }); - req.on('response', (res) => { - verifyClientCalled.should.be.ok; - wss.close(); - done(); - }); - req.end(); + host: '127.0.0.1', + port }); + + req.end(); }); - it('verifyClient has secure:true for ssl connections', function (done) { - var options = { - key: fs.readFileSync('test/fixtures/key.pem'), - cert: fs.readFileSync('test/fixtures/certificate.pem') - }; - var app = https.createServer(options, function (req, res) { - res.writeHead(200); - res.end(); - }); - var success = false; - var wss = new WebSocketServer({ - server: app, - verifyClient: function (info) { - success = info.secure === true; - return true; - } - }); - app.listen(++port, function () { - var ws = new WebSocket('wss://localhost:' + port); + wss.on('connection', (ws) => { + wss.close(); + done(); + }); + }); + + it('verifyClient gets client origin', function (done) { + let verifyClientCalled = false; + const wss = new WebSocketServer({ + verifyClient: (info) => { + assert.strictEqual(info.origin, 'http://foobarbaz.com'); + verifyClientCalled = true; + return false; + }, + port: ++port + }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Origin': 'http://foobarbaz.com' + }, + host: '127.0.0.1', + port }); - wss.on('connection', function (ws) { - app.close(); - ws.terminate(); + + req.on('response', (res) => { + assert.ok(verifyClientCalled); wss.close(); - success.should.be.ok; done(); }); + + req.end(); }); + }); - it('verifyClient has secure:false for non-ssl connections', function (done) { - var app = http.createServer(function (req, res) { - res.writeHead(200); - res.end(); - }); - var success = false; - var wss = new WebSocketServer({ - server: app, - verifyClient: function (info) { - success = info.secure === false; - return true; - } - }); - app.listen(++port, function () { - var ws = new WebSocket('ws://localhost:' + port); + it('verifyClient gets original request', function (done) { + let verifyClientCalled = false; + const wss = new WebSocketServer({ + verifyClient: (info) => { + assert.strictEqual( + info.req.headers['sec-websocket-key'], + 'dGhlIHNhbXBsZSBub25jZQ==' + ); + verifyClientCalled = true; + return false; + }, + port: ++port + }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Origin': 'http://foobarbaz.com' + }, + host: '127.0.0.1', + port }); - wss.on('connection', function (ws) { - app.close(); - ws.terminate(); + + req.on('response', (res) => { + assert.ok(verifyClientCalled); wss.close(); - success.should.be.ok; done(); }); + + req.end(); }); + }); - it('client can be denied asynchronously', function (done) { - const wss = new WebSocketServer({ - verifyClient: (o, cb) => process.nextTick(cb, false), - port: ++port - }, () => { - const req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8, - 'Sec-WebSocket-Origin': 'http://foobar.com' - } - }); - req.on('response', (res) => { - res.statusCode.should.eql(401); - wss.close(); - done(); - }); - req.end(); - }); + it('verifyClient has secure:true for ssl connections', function (done) { + const server = https.createServer({ + cert: fs.readFileSync('test/fixtures/certificate.pem'), + key: fs.readFileSync('test/fixtures/key.pem') + }); - wss.on('connection', (ws) => { - done(new Error('connection must not be established')); - }); + let success = false; + const wss = new WebSocketServer({ + verifyClient: (info) => { + success = info.secure === true; + return true; + }, + server }); - it('client can be denied asynchronously with custom response code', function (done) { - const wss = new WebSocketServer({ - verifyClient: (o, cb) => process.nextTick(cb, false, 404), - port: ++port - }, () => { - const req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8, - 'Sec-WebSocket-Origin': 'http://foobar.com' - } - }); - req.on('response', (res) => { - res.statusCode.should.eql(404); - wss.close(); - done(); - }); - req.end(); + wss.on('connection', (ws) => { + assert.ok(success); + wss.close(); + server.close(done); + }); + + server.listen(++port, () => { + const ws = new WebSocket('wss://localhost:' + port); + }); + }); + + it('verifyClient has secure:false for non-ssl connections', function (done) { + const server = http.createServer(); + + let success = false; + const wss = new WebSocketServer({ + server: server, + verifyClient: (info) => { + success = info.secure === false; + return true; + } + }); + + wss.on('connection', (ws) => { + assert.ok(success); + wss.close(); + server.close(done); + }); + + server.listen(++port, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + }); + }); + + it('client can be denied asynchronously', function (done) { + const wss = new WebSocketServer({ + verifyClient: (o, cb) => process.nextTick(cb, false), + port: ++port + }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8, + 'Sec-WebSocket-Origin': 'http://foobar.com' + }, + host: '127.0.0.1', + port }); - wss.on('connection', (ws) => { - done(new Error('connection must not be established')); + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 401); + wss.close(); + done(); }); + + req.end(); }); - it('client can be accepted asynchronously', function (done) { - const wss = new WebSocketServer({ - verifyClient: (o, cb) => process.nextTick(cb, true), - port: ++port - }, () => { - const req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobar.com' - } - }); - req.end(); + wss.on('connection', (ws) => { + done(new Error('connection must not be established')); + }); + }); + + it('client can be denied asynchronously with custom response code', function (done) { + const wss = new WebSocketServer({ + verifyClient: (o, cb) => process.nextTick(cb, false, 404), + port: ++port + }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8, + 'Sec-WebSocket-Origin': 'http://foobar.com' + }, + host: '127.0.0.1', + port }); - wss.on('connection', (ws) => { - ws.terminate(); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 404); wss.close(); done(); }); + + req.end(); }); - it('handles messages passed along with the upgrade request (upgrade head)', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobar.com' - } - }); - req.write(Buffer.from([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f])); - req.end(); + wss.on('connection', (ws) => { + done(new Error('connection must not be established')); + }); + }); + + it('client can be accepted asynchronously', function (done) { + const wss = new WebSocketServer({ + verifyClient: (o, cb) => process.nextTick(cb, true), + port: ++port + }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Origin': 'http://foobar.com' + }, + host: '127.0.0.1', + port }); - wss.on('connection', (ws) => { - ws.on('message', (data) => { - data.should.eql('Hello'); - ws.terminate(); - wss.close(); - done(); - }); + req.end(); + }); + + wss.on('connection', (ws) => { + wss.close(); + done(); + }); + }); + + it('handles messages passed along with the upgrade request (upgrade head)', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Origin': 'http://foobar.com' + }, + host: '127.0.0.1', + port }); + + req.write(Buffer.from([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f])); + req.end(); }); - it('selects the first protocol by default', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port, ['prot1', 'prot2']); - ws.on('open', function (client) { - ws.protocol.should.eql('prot1'); - wss.close(); - done(); - }); + wss.on('connection', (ws) => { + ws.on('message', (data) => { + assert.strictEqual(data, 'Hello'); + wss.close(); + done(); }); }); + }); - it('selects the last protocol via protocol handler', function (done) { - const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(true, ps[ps.length - 1]), - port: ++port - }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); + it('selects the first protocol by default', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); - ws.on('open', () => { - ws.protocol.should.eql('prot2'); - wss.close(); - done(); - }); + ws.on('open', (client) => { + assert.strictEqual(ws.protocol, 'prot1'); + wss.close(); + done(); }); }); + }); - it('client detects invalid server protocol', function (done) { - const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(true, 'prot3'), - port: ++port - }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); + it('selects the last protocol via protocol handler', function (done) { + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(true, ps[ps.length - 1]), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); - ws.on('open', () => done(new Error('connection must not be established'))); - ws.on('error', () => { - wss.close(); - done(); - }); + ws.on('open', () => { + assert.strictEqual(ws.protocol, 'prot2'); + wss.close(); + done(); }); }); + }); - it('client detects no server protocol', function (done) { - const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(true), - port: ++port - }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); + it('client detects invalid server protocol', function (done) { + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(true, 'prot3'), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); - ws.on('open', () => done(new Error('connection must not be established'))); - ws.on('error', () => { - wss.close(); - done(); - }); + ws.on('open', () => done(new Error('connection must not be established'))); + ws.on('error', () => { + wss.close(); + done(); }); }); + }); - it('client refuses server protocols', function (done) { - const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(false), - port: ++port - }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); + it('client detects no server protocol', function (done) { + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(true), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); - ws.on('open', () => done(new Error('connection must not be established'))); - ws.on('error', () => { - wss.close(); - done(); - }); + ws.on('open', () => done(new Error('connection must not be established'))); + ws.on('error', () => { + wss.close(); + done(); }); }); + }); - it('server detects unauthorized protocol handler', function (done) { - const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(false), - port: ++port - }, () => { - const req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Origin': 'http://foobar.com' - } - }); - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 401); - wss.close(); - done(); - }); - req.end(); + it('client refuses server protocols', function (done) { + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(false), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); + + ws.on('open', () => done(new Error('connection must not be established'))); + ws.on('error', () => { + wss.close(); + done(); }); }); + }); - it('server detects invalid protocol handler', function (done) { - const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => { - // not calling callback is an error and shouldn't timeout + it('server detects unauthorized protocol handler', function (done) { + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => cb(false), + port: ++port + }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Sec-WebSocket-Origin': 'http://foobar.com' }, - port: ++port - }, () => { - const req = http.request({ - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Origin': 'http://foobar.com' - } - }); - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 501); - wss.close(); - done(); - }); - req.end(); + host: '127.0.0.1', + port + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 401); + wss.close(); + done(); }); + + req.end(); }); + }); - it('accept connections with sec-websocket-extensions', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Extensions': 'permessage-foo; x=10' - } - }; - var req = http.request(options); - req.end(); + it('server detects invalid protocol handler', function (done) { + const wss = new WebSocketServer({ + handleProtocols: (ps, cb) => { + // not calling callback is an error and shouldn't timeout + }, + port: ++port + }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Sec-WebSocket-Origin': 'http://foobar.com' + }, + host: '127.0.0.1', + port }); - wss.on('connection', function (ws) { - ws.terminate(); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 501); wss.close(); done(); }); - wss.on('error', function () {}); + + req.end(); }); }); - describe('messaging', function () { - it('can send and receive data', function (done) { - var data = new Array(65 * 1024); - for (var i = 0; i < data.length; ++i) { - data[i] = String.fromCharCode(65 + ~~(25 * Math.random())); - } - data = data.join(''); - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port); - ws.on('message', function (message, flags) { - ws.send(message); - }); + it('accept connections with sec-websocket-extensions', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Sec-WebSocket-Extensions': 'permessage-foo; x=10' + }, + host: '127.0.0.1', + port }); - wss.on('connection', function (client) { - client.on('message', function (message) { - message.should.eql(data); - wss.close(); - done(); - }); - client.send(data); + + req.end(); + }); + + wss.on('connection', (ws) => { + wss.close(); + done(); + }); + }); + }); + + describe('messaging', function () { + it('can send and receive data', function (done) { + let data = new Array(65 * 1024); + + for (let i = 0; i < data.length; ++i) { + data[i] = String.fromCharCode(65 + ~~(25 * Math.random())); + } + data = data.join(''); + + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('message', (message, flags) => ws.send(message)); + }); + + wss.on('connection', (client) => { + client.on('message', (message) => { + assert.strictEqual(message, data); + wss.close(); + done(); }); + + client.send(data); }); }); }); describe('client properties', function () { it('protocol is exposed', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port, 'hi'); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, 'hi'); }); - wss.on('connection', function (client) { - client.protocol.should.eql('hi'); + + wss.on('connection', (client) => { + assert.strictEqual(client.protocol, 'hi'); wss.close(); done(); }); }); it('protocolVersion is exposed', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port, {protocolVersion: 8}); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { protocolVersion: 8 }); }); - wss.on('connection', function (client) { - client.protocolVersion.should.eql(8); + + wss.on('connection', (client) => { + assert.strictEqual(client.protocolVersion, 8); wss.close(); done(); }); }); it('upgradeReq is the original request object', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var ws = new WebSocket('ws://localhost:' + port, {protocolVersion: 8}); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { protocolVersion: 8 }); }); - wss.on('connection', function (client) { - client.upgradeReq.httpVersion.should.eql('1.1'); + + wss.on('connection', (client) => { + assert.strictEqual(client.upgradeReq.httpVersion, '1.1'); wss.close(); done(); }); @@ -987,81 +1019,82 @@ describe('WebSocketServer', function () { describe('permessage-deflate', function () { it('accept connections with permessage-deflate extension', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var options = { - port: port, - host: '127.0.0.1', + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13, 'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits=8; server_max_window_bits=8; client_no_context_takeover; server_no_context_takeover' - } - }; - var req = http.request(options); + }, + host: '127.0.0.1', + port + }); + req.end(); }); - wss.on('connection', function (ws) { - ws.terminate(); + + wss.on('connection', (ws) => { wss.close(); done(); }); - wss.on('error', function () {}); }); it('does not accept connections with not defined extension parameter', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var options = { - port: port, - host: '127.0.0.1', + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13, 'Sec-WebSocket-Extensions': 'permessage-deflate; foo=15' - } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { - res.statusCode.should.eql(400); + }, + host: '127.0.0.1', + port + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); wss.close(); done(); }); + + req.end(); }); - wss.on('connection', function (ws) { + + wss.on('connection', (ws) => { done(new Error('connection must not be established')); }); - wss.on('error', function () {}); }); it('does not accept connections with invalid extension parameter', function (done) { - var wss = new WebSocketServer({port: ++port}, function () { - var options = { - port: port, - host: '127.0.0.1', + const wss = new WebSocketServer({ port: ++port }, () => { + const req = http.request({ headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13, 'Sec-WebSocket-Extensions': 'permessage-deflate; server_max_window_bits=foo' - } - }; - var req = http.request(options); - req.end(); - req.on('response', function (res) { - res.statusCode.should.eql(400); + }, + host: '127.0.0.1', + port + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); wss.close(); done(); }); + + req.end(); }); - wss.on('connection', function (ws) { + + wss.on('connection', (ws) => { done(new Error('connection must not be established')); }); - wss.on('error', function () {}); }); }); }); diff --git a/test/hybi-util.js b/test/hybi-util.js index 47446b0de..cdff71f5a 100644 --- a/test/hybi-util.js +++ b/test/hybi-util.js @@ -14,7 +14,7 @@ function mask (buf, maskString) { buf = Buffer.from(buf); - for (var i = 0; i < buf.length; ++i) { + for (let i = 0; i < buf.length; ++i) { buf[i] ^= _mask[i % 4]; } @@ -39,7 +39,7 @@ function pack (length, number) { * Returns a hex string representing the length of a message. */ function getHybiLengthAsHexString (len, masked) { - var s; + let s; masked = masked ? 0x80 : 0; diff --git a/test/testserver.js b/test/testserver.js index 3f24964c4..4f03b79c8 100644 --- a/test/testserver.js +++ b/test/testserver.js @@ -1,11 +1,12 @@ 'use strict'; -const http = require('http'); -const util = require('util'); const crypto = require('crypto'); const events = require('events'); -const Sender = require('../lib/Sender'); +const http = require('http'); +const util = require('util'); + const Receiver = require('../lib/Receiver'); +const Sender = require('../lib/Sender'); module.exports = { handlers: { @@ -19,11 +20,11 @@ module.exports = { cb = handler; handler = null; } - var webServer = http.createServer(function (req, res) { + const webServer = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('okay'); }); - var srv = new Server(webServer); + const srv = new Server(webServer); webServer.on('upgrade', function (req, socket) { webServer._socket = socket; (handler || validServer)(srv, req, socket); @@ -48,12 +49,12 @@ function validServer (server, req, socket) { } // calc key - var key = req.headers['sec-websocket-key']; - var shasum = crypto.createHash('sha1'); + let key = req.headers['sec-websocket-key']; + const shasum = crypto.createHash('sha1'); shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); key = shasum.digest('base64'); - var headers = [ + const headers = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', @@ -64,8 +65,8 @@ function validServer (server, req, socket) { socket.setTimeout(0); socket.setNoDelay(true); - var sender = new Sender(socket); - var receiver = new Receiver(); + const sender = new Sender(socket); + const receiver = new Receiver(); receiver.ontext = function (message, flags) { server.emit('message', message, flags); sender.send(message); @@ -111,12 +112,12 @@ function invalidRequestHandler (server, req, socket) { } // calc key - var key = req.headers['sec-websocket-key']; - var shasum = crypto.createHash('sha1'); + let key = req.headers['sec-websocket-key']; + const shasum = crypto.createHash('sha1'); shasum.update(key + 'bogus', 'binary'); key = shasum.digest('base64'); - var headers = [ + const headers = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', @@ -139,12 +140,12 @@ function closeAfterConnectHandler (server, req, socket) { } // calc key - var key = req.headers['sec-websocket-key']; - var shasum = crypto.createHash('sha1'); + let key = req.headers['sec-websocket-key']; + const shasum = crypto.createHash('sha1'); shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); key = shasum.digest('base64'); - var headers = [ + const headers = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', @@ -156,7 +157,7 @@ function closeAfterConnectHandler (server, req, socket) { } function return401 (server, req, socket) { - var headers = [ + const headers = [ 'HTTP/1.1 401 Unauthorized', 'Content-type: text/html' ]; From 32750aa86445eb6d7efa6a57498c5cc754521f5a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 27 Oct 2016 20:57:05 +0200 Subject: [PATCH 139/669] [test] Clean up tests further --- test/WebSocket.test.js | 1875 +++++++++++++++++++++------------------- test/testserver.js | 168 ++-- 2 files changed, 1047 insertions(+), 996 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index ad05a9848..888e35d60 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -15,24 +15,6 @@ const WebSocket = require('..'); const WebSocketServer = WebSocket.Server; let port = 20000; -function getArrayBuffer (buf) { - const l = buf.length; - const arrayBuf = new ArrayBuffer(l); - const uint8View = new Uint8Array(arrayBuf); - for (let i = 0; i < l; i++) { - uint8View[i] = buf[i]; - } - return uint8View.buffer; -} - -function areArraysEqual (x, y) { - if (x.length !== y.length) return false; - for (let i = 0, l = x.length; i < l; ++i) { - if (x[i] !== y[i]) return false; - } - return true; -} - describe('WebSocket', function () { describe('#ctor', function () { it('should return a new instance if called without new', function (done) { @@ -72,7 +54,7 @@ describe('WebSocket', function () { }); }); - const wss = new WebSocketServer({ port: ++port }, function () { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { localAddress: localAddresses[0] }); @@ -91,77 +73,70 @@ describe('WebSocket', function () { describe('properties', function () { it('#bytesReceived exposes number of bytes received', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: false }); - ws.on('message', function () { + ws.on('message', () => { assert.strictEqual(ws.bytesReceived, 8); wss.close(); done(); }); }); - wss.on('connection', function (ws) { - ws.send('foobar'); - }); + wss.on('connection', (ws) => ws.send('foobar')); }); it('#url exposes the server url', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const url = `ws://localhost:${port}`; const ws = new WebSocket(url); - assert.equal(url, ws.url); - ws.terminate(); - ws.on('close', function () { - srv.close(); - done(); - }); + + assert.strictEqual(ws.url, url); + + ws.on('close', () => srv.close(done)); + ws.close(); }); }); it('#protocolVersion exposes the protocol version', function (done) { - server.createServer(++port, function (srv) { - const url = `ws://localhost:${port}`; - const ws = new WebSocket(url); - assert.equal(13, ws.protocolVersion); - ws.terminate(); - ws.on('close', function () { - srv.close(); - done(); - }); + server.createServer(++port, (srv) => { + const ws = new WebSocket(`ws://localhost:${port}`); + + assert.strictEqual(ws.protocolVersion, 13); + + ws.on('close', () => srv.close(done)); + ws.close(); }); }); describe('#bufferedAmount', function () { it('defaults to zero', function (done) { - server.createServer(++port, function (srv) { - const url = `ws://localhost:${port}`; - const ws = new WebSocket(url); - assert.equal(0, ws.bufferedAmount); - ws.terminate(); - ws.on('close', function () { - srv.close(); - done(); - }); + server.createServer(++port, (srv) => { + const ws = new WebSocket(`ws://localhost:${port}`); + + assert.strictEqual(ws.bufferedAmount, 0); + + ws.on('close', () => srv.close(done)); + ws.close(); }); }); it('defaults to zero upon "open"', function (done) { - server.createServer(++port, function (srv) { - const url = `ws://localhost:${port}`; - const ws = new WebSocket(url); - ws.onopen = function () { - assert.equal(0, ws.bufferedAmount); - ws.terminate(); - ws.on('close', function () { - srv.close(); - done(); - }); + server.createServer(++port, (srv) => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.onopen = () => { + assert.strictEqual(ws.bufferedAmount, 0); + + ws.on('close', () => srv.close(done)); + ws.close(); }; }); }); it('stress kernel write buffer', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: false }); + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: false + }); }); wss.on('connection', (ws) => { @@ -176,43 +151,41 @@ describe('WebSocket', function () { describe('Custom headers', function () { it('request has an authorization header', function (done) { + const server = http.createServer(); + const wss = new WebSocketServer({ server }); const auth = 'test:testpass'; - const srv = http.createServer(); - const wss = new WebSocketServer({server: srv}); - srv.listen(++port); - const ws = new WebSocket('ws://' + auth + '@localhost:' + port); - srv.on('upgrade', function (req, socket, head) { - assert(req.headers.authorization, 'auth header exists'); - assert.equal(req.headers.authorization, 'Basic ' + new Buffer(auth).toString('base64')); - ws.terminate(); - ws.on('close', function () { - srv.close(); - wss.close(); - done(); - }); + + server.listen(++port, () => { + const ws = new WebSocket(`ws://${auth}@localhost:${port}`); + }); + + server.on('upgrade', (req, socket, head) => { + assert.ok(req.headers.authorization); + assert.strictEqual( + req.headers.authorization, + `Basic ${new Buffer(auth).toString('base64')}` + ); + + wss.close(); + server.close(done); }); }); it('accepts custom headers', function (done) { - const srv = http.createServer(); - const wss = new WebSocketServer({server: srv}); - srv.listen(++port); + const server = http.createServer(); + const wss = new WebSocketServer({ server }); - const ws = new WebSocket(`ws://localhost:${port}`, { - headers: { - 'Cookie': 'foo=bar' - } - }); + server.on('upgrade', (req, socket, head) => { + assert.ok(req.headers.cookie); + assert.strictEqual(req.headers.cookie, 'foo=bar'); - srv.on('upgrade', function (req, socket, head) { - assert(req.headers.cookie, 'auth header exists'); - assert.equal(req.headers.cookie, 'foo=bar'); + wss.close(); + server.close(done); + }); - ws.terminate(); - ws.on('close', function () { - srv.close(); - wss.close(); - done(); + server.listen(++port, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + headers: { 'Cookie': 'foo=bar' } }); }); }); @@ -220,49 +193,52 @@ describe('WebSocket', function () { describe('#readyState', function () { it('defaults to connecting', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - assert.equal(WebSocket.CONNECTING, ws.readyState); - ws.terminate(); - ws.on('close', function () { - srv.close(); - done(); - }); + + assert.strictEqual(ws.readyState, WebSocket.CONNECTING); + + ws.on('close', () => srv.close(done)); + ws.close(); }); }); it('set to open once connection is established', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - assert.equal(WebSocket.OPEN, ws.readyState); - srv.close(); - done(); + + ws.on('open', () => { + assert.strictEqual(ws.readyState, WebSocket.OPEN); + ws.close(); }); + + ws.on('close', () => srv.close(done)); }); }); it('set to closed once connection is closed', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.close(1001); - ws.on('close', function () { - assert.equal(WebSocket.CLOSED, ws.readyState); - srv.close(); - done(); + + ws.on('close', () => { + assert.strictEqual(ws.readyState, WebSocket.CLOSED); + srv.close(done); }); + + ws.close(1001); }); }); it('set to closed once connection is terminated', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.terminate(); - ws.on('close', function () { - assert.equal(WebSocket.CLOSED, ws.readyState); - srv.close(); - done(); + + ws.on('close', () => { + assert.strictEqual(ws.readyState, WebSocket.CLOSED); + srv.close(done); }); + + ws.terminate(); }); }); }); @@ -282,23 +258,20 @@ describe('WebSocket', function () { * Ready state constant tests */ - Object.keys(readyStates).forEach(function (state) { - describe('.' + state, function () { + Object.keys(readyStates).forEach((state) => { + describe(`.${state}`, function () { it('is enumerable property of class', function () { const propertyDescripter = Object.getOwnPropertyDescriptor(WebSocket, state); - assert.equal(readyStates[state], propertyDescripter.value); - assert.equal(true, propertyDescripter.enumerable); + + assert.strictEqual(propertyDescripter.value, readyStates[state]); + assert.strictEqual(propertyDescripter.enumerable, true); }); - }); - }); - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); - Object.keys(readyStates).forEach(function (state) { - describe('.' + state, function () { - it('is property of instance', function () { - assert.equal(readyStates[state], ws[state]); - }); + it('is property of instance', function () { + const ws = new WebSocket('ws://localhost'); + ws.on('error', () => {}); + + assert.strictEqual(ws[state], readyStates[state]); }); }); }); @@ -306,181 +279,138 @@ describe('WebSocket', function () { describe('events', function () { it('emits a ping event', function (done) { - const wss = new WebSocketServer({port: ++port}); - wss.on('connection', function (client) { - client.ping(); - }); - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('ping', function () { - ws.terminate(); - wss.close(); - done(); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + ws.on('ping', function () { + wss.close(); + done(); + }); }); + + wss.on('connection', (client) => client.ping()); }); it('emits a pong event', function (done) { - const wss = new WebSocketServer({port: ++port}); - wss.on('connection', function (client) { - client.pong(); - }); - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('pong', function () { - ws.terminate(); - wss.close(); - done(); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + ws.on('pong', () => { + wss.close(); + done(); + }); }); + + wss.on('connection', (client) => client.pong()); }); }); describe('connection establishing', function () { it('can disconnect before connection is established', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('close', () => srv.close(done)); ws.terminate(); - ws.on('open', function () { - assert.fail('connect shouldnt be raised here'); - }); - ws.on('close', function () { - srv.close(); - done(); - }); }); }); it('can close before connection is established', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('close', () => srv.close(done)); ws.close(1001); - ws.on('open', function () { - assert.fail('connect shouldnt be raised here'); - }); - ws.on('close', function () { - srv.close(); - done(); - }); }); }); it('can handle error before request is upgraded', function (done) { // Here, we don't create a server, to guarantee that the connection will // fail before the request is upgraded - ++port; - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - assert.fail('connect shouldnt be raised here'); - }); - let errorCallBackFired = false; - ws.on('error', function () { - errorCallBackFired = true; - }); - ws.on('close', function () { - setTimeout(function () { - assert.equal(true, errorCallBackFired); - assert.equal(ws.readyState, WebSocket.CLOSED); - done(); - }, 50); - }); + const ws = new WebSocket(`ws://localhost:${++port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', () => done()); }); it('invalid server key is denied', function (done) { - server.createServer(++port, server.handlers.invalidKey, function (srv) { + server.createServer(++port, server.handlers.invalidKey, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('error', function () { - srv.close(); - done(); - }); + + ws.on('error', () => srv.close(done)); }); }); it('close event is raised when server closes connection', function (done) { - server.createServer(++port, server.handlers.closeAfterConnect, function (srv) { + server.createServer(++port, server.handlers.closeAfterConnect, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('close', function () { - srv.close(); - done(); - }); + + ws.on('close', () => srv.close(done)); }); }); it('error is emitted if server aborts connection', function (done) { - server.createServer(++port, server.handlers.return401, function (srv) { + server.createServer(++port, server.handlers.return401, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - assert.fail('connect shouldnt be raised here'); - }); - ws.on('error', function () { - srv.close(); - done(); - }); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', () => srv.close(done)); }); }); it('unexpected response can be read when sent by server', function (done) { - server.createServer(++port, server.handlers.return401, function (srv) { + server.createServer(++port, server.handlers.return401, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - assert.fail('connect shouldnt be raised here'); - }); - ws.on('unexpected-response', function (req, res) { - assert.equal(res.statusCode, 401); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', () => assert.fail(null, null, 'error shouldnt be raised here')); + ws.on('unexpected-response', (req, res) => { + assert.strictEqual(res.statusCode, 401); let data = ''; - res.on('data', function (v) { + res.on('data', (v) => { data += v; }); - res.on('end', function () { - assert.equal(data, 'Not allowed!'); - srv.close(); - done(); + res.on('end', () => { + assert.strictEqual(data, 'Not allowed!'); + srv.close(done); }); }); - ws.on('error', function () { - assert.fail('error shouldnt be raised here'); - }); }); }); it('request can be aborted when unexpected response is sent by server', function (done) { - server.createServer(++port, server.handlers.return401, function (srv) { + server.createServer(++port, server.handlers.return401, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - assert.fail('connect shouldnt be raised here'); - }); - ws.on('unexpected-response', function (req, res) { - assert.equal(res.statusCode, 401); - res.on('end', function () { - srv.close(); - done(); - }); + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', () => assert.fail(null, null, 'error shouldnt be raised here')); + ws.on('unexpected-response', (req, res) => { + assert.strictEqual(res.statusCode, 401); + res.on('end', () => srv.close(done)); req.abort(); }); - ws.on('error', function () { - assert.fail('error shouldnt be raised here'); - }); }); }); }); describe('connection with query string', function () { it('connects when pathname is not null', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}/?token=qwerty`); - ws.on('open', function () { - wss.close(done); - }); + + ws.on('open', () => wss.close(done)); }); }); it('connects when pathname is null', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}?token=qwerty`); - ws.on('open', function () { - wss.close(done); - }); + + ws.on('open', () => wss.close(done)); }); }); }); @@ -491,31 +421,37 @@ describe('WebSocket', function () { // to localhost can cause the test to succeed even when the stream pausing // isn't working as intended. that is an extremely unlikely scenario, though // and an acceptable risk for the test. - let client; - let serverClient; let openCount = 0; - function onOpen () { - if (++openCount === 2) { - let paused = true; - serverClient.on('message', function () { - assert.ok(!paused); - wss.close(); - done(); - }); - serverClient.pause(); - setTimeout(function () { - paused = false; - serverClient.resume(); - }, 200); - client.send('foo'); - } - } - const wss = new WebSocketServer({port: ++port}, function () { + let serverClient; + let client; + + const onOpen = () => { + if (++openCount !== 2) return; + + let paused = true; + serverClient.on('message', () => { + assert.ok(!paused); + wss.close(); + done(); + }); + serverClient.pause(); + + setTimeout(() => { + paused = false; + serverClient.resume(); + }, 200); + + client.send('foo'); + }; + + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); + serverClient = ws; serverClient.on('open', onOpen); }); - wss.on('connection', function (ws) { + + wss.on('connection', (ws) => { client = ws; onOpen(); }); @@ -524,195 +460,190 @@ describe('WebSocket', function () { describe('#ping', function () { it('before connect should fail', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('error', function () {}); + + ws.on('error', () => {}); + try { ws.ping(); } catch (e) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); }); it('before connect can silently fail', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('error', function () {}); + + ws.on('error', () => {}); ws.ping('', {}, true); - srv.close(); + + srv.close(done); ws.terminate(); - done(); }); }); it('without message is successfully transmitted to the server', function (done) { server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.ping(); - }); - srv.on('ping', function () { - srv.close(); + srv.on('ping', () => { + srv.close(done); ws.terminate(); - done(); }); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.ping()); }); }); it('with message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.ping('hi'); - }); - srv.on('ping', function (message) { - assert.equal('hi', message); - srv.close(); + server.createServer(++port, (srv) => { + srv.on('ping', (message) => { + assert.strictEqual(message.toString(), 'hi'); + srv.close(done); ws.terminate(); - done(); }); - }); - }); - it('can send safely receive numbers as ping payload', function (done) { - server.createServer(++port, function (srv) { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.ping(200); - }); + ws.on('open', () => ws.ping('hi')); + }); + }); - srv.on('ping', function (message) { - assert.equal('200', message); - srv.close(); + it('can send safely receive numbers as ping payload', function (done) { + server.createServer(++port, (srv) => { + srv.on('ping', (message) => { + assert.strictEqual(message.toString(), '200'); + srv.close(done); ws.terminate(); - done(); }); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.ping(200)); }); }); it('with encoded message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.ping('hi', {mask: true}); - }); - srv.on('ping', function (message, flags) { + server.createServer(++port, (srv) => { + srv.on('ping', (message, flags) => { assert.ok(flags.masked); - assert.equal('hi', message); - srv.close(); + assert.strictEqual(message.toString(), 'hi'); + srv.close(done); ws.terminate(); - done(); }); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.ping('hi', { mask: true })); }); }); }); describe('#pong', function () { - it('before connect should fail', function (done) { - server.createServer(++port, function (srv) { + it('before connect should fail', (done) => { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('error', function () {}); + + ws.on('error', () => {}); + try { ws.pong(); } catch (e) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); }); it('before connect can silently fail', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('error', function () {}); + + ws.on('error', () => {}); ws.pong('', {}, true); - srv.close(); + + srv.close(done); ws.terminate(); - done(); }); }); it('without message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.pong(); - }); - srv.on('pong', function () { - srv.close(); + server.createServer(++port, (srv) => { + srv.on('pong', () => { + srv.close(done); ws.terminate(); - done(); }); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.pong()); }); }); it('with message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.pong('hi'); - }); - srv.on('pong', function (message) { - assert.equal('hi', message); - srv.close(); + server.createServer(++port, (srv) => { + srv.on('pong', (message) => { + assert.strictEqual(message.toString(), 'hi'); + srv.close(done); ws.terminate(); - done(); }); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.pong('hi')); }); }); it('with encoded message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.pong('hi', {mask: true}); - }); - srv.on('pong', function (message, flags) { + server.createServer(++port, (srv) => { + srv.on('pong', (message, flags) => { assert.ok(flags.masked); - assert.equal('hi', message); - srv.close(); + assert.strictEqual(message.toString(), 'hi'); + srv.close(done); ws.terminate(); - done(); }); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.pong('hi', { mask: true })); }); }); }); describe('#send', function () { - it('very long binary data can be sent and received (with echoing server)', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + it('very long binary data can be sent and received (with echoing server)', (done) => { + server.createServer(++port, (srv) => { const array = new Float32Array(5 * 1024 * 1024); - for (let i = 0; i < array.length; ++i) array[i] = i / 5; - ws.on('open', function () { - ws.send(array, {binary: true}); - }); - ws.on('message', function (message, flags) { + + for (let i = 0; i < array.length; ++i) { + array[i] = i / 5; + } + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.send(array, { binary: true })); + ws.on('message', (message, flags) => { assert.ok(flags.binary); - assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); + assert.ok(message.equals(Buffer.from(array.buffer))); + srv.close(done); ws.terminate(); - srv.close(); - done(); }); }); }); it('can send and receive text data', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.send('hi'); - }); - ws.on('message', function (message, flags) { - assert.equal('hi', message); + + ws.on('open', () => ws.send('hi')); + ws.on('message', (message, flags) => { + assert.strictEqual(message, 'hi'); + srv.close(done); ws.terminate(); - srv.close(); - done(); }); }); }); @@ -736,17 +667,23 @@ describe('WebSocket', function () { }); it('send and receive binary data as an array', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const array = new Float32Array(6); - for (let i = 0; i < array.length; ++i) array[i] = i / 2; + + for (let i = 0; i < array.length; ++i) { + array[i] = i / 2; + } + const partial = array.subarray(2, 5); - ws.on('open', function () { - ws.send(partial, {binary: true}); - }); - ws.on('message', function (message, flags) { + const buf = Buffer.from(partial.buffer) + .slice(partial.byteOffset, partial.byteOffset + partial.byteLength); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.send(partial, { binary: true })); + ws.on('message', (message, flags) => { assert.ok(flags.binary); - assert.ok(areArraysEqual(partial, new Float32Array(getArrayBuffer(message)))); + assert.ok(message.equals(buf)); ws.terminate(); srv.close(); done(); @@ -755,366 +692,428 @@ describe('WebSocket', function () { }); it('binary data can be sent and received as buffer', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { + const buf = Buffer.from('foobar'); const ws = new WebSocket(`ws://localhost:${port}`); - const buf = new Buffer('foobar'); - ws.on('open', function () { - ws.send(buf, {binary: true}); - }); - ws.on('message', function (message, flags) { + + ws.on('open', () => ws.send(buf, { binary: true })); + ws.on('message', (message, flags) => { assert.ok(flags.binary); - assert.ok(areArraysEqual(buf, message)); + assert.ok(message.equals(buf)); + srv.close(done); ws.terminate(); - srv.close(); - done(); }); }); }); it('ArrayBuffer is auto-detected without binary flag', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const array = new Float32Array(5); - for (let i = 0; i < array.length; ++i) array[i] = i / 2; - ws.on('open', function () { - ws.send(array.buffer); - }); - ws.onmessage = function (event) { + + for (let i = 0; i < array.length; ++i) { + array[i] = i / 2; + } + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.send(array.buffer)); + ws.onmessage = (event) => { assert.ok(event.binary); - assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(event.data)))); + assert.ok(event.data.equals(Buffer.from(array.buffer))); + srv.close(done); ws.terminate(); - srv.close(); - done(); }; }); }); it('Buffer is auto-detected without binary flag', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { + const buf = Buffer.from('foobar'); const ws = new WebSocket(`ws://localhost:${port}`); - const buf = new Buffer('foobar'); - ws.on('open', function () { - ws.send(buf); - }); - ws.onmessage = function (event) { + + ws.on('open', () => ws.send(buf)); + + ws.onmessage = (event) => { assert.ok(event.binary); - assert.ok(areArraysEqual(event.data, buf)); + assert.ok(event.data.equals(buf)); + srv.close(done); ws.terminate(); - srv.close(); - done(); }; }); }); it('before connect should fail', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('error', function () {}); + + ws.on('error', () => {}); + try { ws.send('hi'); } catch (e) { + srv.close(done); ws.terminate(); - srv.close(); - done(); } }); }); it('before connect should pass error through callback, if present', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('error', function () {}); - ws.send('hi', function (error) { + + ws.send('hi', (error) => { assert.ok(error instanceof Error); + srv.close(done); ws.terminate(); - srv.close(); - done(); }); }); }); it('without data should be successful', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.send(); - }); - srv.on('message', function (message, flags) { - assert.equal('', message); - srv.close(); + + ws.on('open', () => ws.send()); + + srv.on('message', (message, flags) => { + assert.strictEqual(message, ''); + srv.close(done); ws.terminate(); - done(); }); }); }); it('calls optional callback when flushed', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.send('hi', function () { - srv.close(); + + ws.on('open', () => { + ws.send('hi', () => { + srv.close(done); ws.terminate(); - done(); }); }); }); }); it('with unencoded message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.send('hi'); - }); - srv.on('message', function (message, flags) { - assert.equal('hi', message); - srv.close(); + + ws.on('open', () => ws.send('hi')); + + srv.on('message', (message, flags) => { + assert.strictEqual(message, 'hi'); + srv.close(done); ws.terminate(); - done(); }); }); }); it('with encoded message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.send('hi', {mask: true}); - }); - srv.on('message', function (message, flags) { + + ws.on('open', () => ws.send('hi', { mask: true })); + + srv.on('message', (message, flags) => { assert.ok(flags.masked); - assert.equal('hi', message); - srv.close(); + assert.strictEqual(message, 'hi'); + srv.close(done); ws.terminate(); - done(); }); }); }); it('with unencoded binary message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const array = new Float32Array(5); - for (let i = 0; i < array.length; ++i) array[i] = i / 2; - ws.on('open', function () { - ws.send(array, {binary: true}); - }); - srv.on('message', function (message, flags) { + + for (let i = 0; i < array.length; ++i) { + array[i] = i / 2; + } + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.send(array, { binary: true })); + + srv.on('message', (message, flags) => { assert.ok(flags.binary); - assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); - srv.close(); + assert.ok(message.equals(Buffer.from(array.buffer))); + srv.close(done); ws.terminate(); - done(); }); }); }); it('with encoded binary message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const array = new Float32Array(5); - for (let i = 0; i < array.length; ++i) array[i] = i / 2; - ws.on('open', function () { - ws.send(array, {mask: true, binary: true}); - }); - srv.on('message', function (message, flags) { + + for (let i = 0; i < array.length; ++i) { + array[i] = i / 2; + } + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.send(array, { mask: true, binary: true })); + + srv.on('message', (message, flags) => { assert.ok(flags.binary); assert.ok(flags.masked); - assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); - srv.close(); + assert.ok(message.equals(Buffer.from(array.buffer))); + srv.close(done); ws.terminate(); - done(); }); }); }); it('with binary stream will send fragmented data', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); let callbackFired = false; - ws.on('open', function () { - const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); - ws.send(fileStream, {binary: true}, function (error) { - assert.equal(null, error); + + ws.on('open', () => { + const fileStream = fs.createReadStream('test/fixtures/textfile', { + highWaterMark: 100 + }); + + ws.send(fileStream, { binary: true }, (error) => { + assert.ifError(error); callbackFired = true; }); }); - srv.on('message', function (data, flags) { - assert.ok(flags.binary); - assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile'), data)); - ws.terminate(); - }); - ws.on('close', function () { + + ws.on('close', () => { assert.ok(callbackFired); - srv.close(); - done(); + srv.close(done); + }); + + srv.on('message', (data, flags) => { + assert.ok(flags.binary); + assert.ok(data.equals(fs.readFileSync('test/fixtures/textfile'))); + + ws.close(); }); }); }); it('with text stream will send fragmented data', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); let callbackFired = false; - ws.on('open', function () { - const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); - ws.send(fileStream, {binary: false}, function (error) { - assert.equal(null, error); + + ws.on('open', () => { + const fileStream = fs.createReadStream('test/fixtures/textfile', { + highWaterMark: 100, + encoding: 'utf8' + }); + + ws.send(fileStream, { binary: false }, (error) => { + assert.ifError(error); callbackFired = true; }); }); - srv.on('message', function (data, flags) { - assert.ok(!flags.binary); - assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); - ws.terminate(); - }); - ws.on('close', function () { + + ws.on('close', () => { assert.ok(callbackFired); - srv.close(); - done(); + srv.close(done); + }); + + srv.on('message', (data, flags) => { + assert.ok(!flags.binary); + assert.strictEqual( + data, + fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) + ); + + ws.close(); }); }); }); it('will cause intermittent send to be delayed in order', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + + ws.on('open', () => { + const fileStream = fs.createReadStream('test/fixtures/textfile', { + highWaterMark: 100, + encoding: 'utf8' + }); + ws.send(fileStream); ws.send('foobar'); ws.send('baz'); }); + let receivedIndex = 0; - srv.on('message', function (data, flags) { - ++receivedIndex; - if (receivedIndex === 1) { + + srv.on('message', (data, flags) => { + if (++receivedIndex === 1) { assert.ok(!flags.binary); - assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); + assert.strictEqual( + data, + fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) + ); } else if (receivedIndex === 2) { assert.ok(!flags.binary); - assert.equal('foobar', data); + assert.strictEqual(data, 'foobar'); } else { assert.ok(!flags.binary); - assert.equal('baz', data); - srv.close(); + assert.strictEqual(data, 'baz'); + srv.close(done); ws.terminate(); - done(); } }); }); }); it('will cause intermittent stream to be delayed in order', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + + ws.on('open', () => { + const fileStream = fs.createReadStream('test/fixtures/textfile', { + highWaterMark: 100, + encoding: 'utf8' + }); + ws.send(fileStream); + let i = 0; - ws.stream(function (error, send) { - assert.ok(!error); + ws.stream((error, send) => { + assert.ifError(error); + if (++i === 1) send('foo'); else send('bar', true); }); }); + let receivedIndex = 0; - srv.on('message', function (data, flags) { - ++receivedIndex; - if (receivedIndex === 1) { + + srv.on('message', (data, flags) => { + if (++receivedIndex === 1) { assert.ok(!flags.binary); - assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); + assert.strictEqual( + data, + fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) + ); } else if (receivedIndex === 2) { assert.ok(!flags.binary); - assert.equal('foobar', data); - srv.close(); + assert.strictEqual(data, 'foobar'); + srv.close(done); ws.terminate(); - done(); } }); }); }); it('will cause intermittent ping to be delivered', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + + ws.on('open', () => { + const fileStream = fs.createReadStream('test/fixtures/textfile', { + highWaterMark: 100, + encoding: 'utf8' + }); + ws.send(fileStream); ws.ping('foobar'); }); + let receivedIndex = 0; - srv.on('message', function (data, flags) { + + srv.on('message', (data, flags) => { assert.ok(!flags.binary); - assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); + assert.strictEqual( + data, + fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) + ); if (++receivedIndex === 2) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); - srv.on('ping', function (data) { - assert.equal('foobar', data); + + srv.on('ping', (data) => { + assert.strictEqual(data.toString(), 'foobar'); if (++receivedIndex === 2) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); }); }); it('will cause intermittent pong to be delivered', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + + ws.on('open', () => { + const fileStream = fs.createReadStream('test/fixtures/textfile', { + highWaterMark: 100, + encoding: 'utf8' + }); + ws.send(fileStream); ws.pong('foobar'); }); + let receivedIndex = 0; - srv.on('message', function (data, flags) { + + srv.on('message', (data, flags) => { assert.ok(!flags.binary); - assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); + assert.strictEqual( + data, + fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) + ); if (++receivedIndex === 2) { - srv.close(); - ws.terminate(); - done(); + srv.close(done); + ws.close(); } }); - srv.on('pong', function (data) { - assert.equal('foobar', data); + + srv.on('pong', (data) => { + assert.strictEqual(data.toString(), 'foobar'); if (++receivedIndex === 2) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); }); }); it('will cause intermittent close to be delivered', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); + + ws.on('open', () => { + const fileStream = fs.createReadStream('test/fixtures/textfile', { + highWaterMark: 100, + encoding: 'utf8' + }); ws.send(fileStream); ws.close(1000, 'foobar'); }); - ws.on('close', function () { - srv.close(); - ws.terminate(); - done(); + + ws.on('close', () => srv.close(done)); + ws.on('error', () => { + // That's quite alright -- a send was attempted after close }); - ws.on('error', function () { /* That's quite alright -- a send was attempted after close */ }); - srv.on('message', function (data, flags) { + + srv.on('message', (data, flags) => { assert.ok(!flags.binary); - assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); + assert.strictEqual( + data, + fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) + ); }); - srv.on('close', function (code, data) { - assert.equal(1000, code); - assert.equal('foobar', data); + + srv.on('close', (code, data) => { + assert.strictEqual(code, 1000); + assert.strictEqual(data, 'foobar'); }); }); }); @@ -1122,70 +1121,79 @@ describe('WebSocket', function () { describe('#stream', function () { it('very long binary data can be streamed', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const buffer = new Buffer(10 * 1024); - for (let i = 0; i < buffer.length; ++i) buffer[i] = i % 0xff; - ws.on('open', function () { - let i = 0; - const blockSize = 800; + + for (let i = 0; i < buffer.length; ++i) { + buffer[i] = i % 0xff; + } + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => { const bufLen = buffer.length; - ws.stream({binary: true}, function (error, send) { - assert.ok(!error); + const blockSize = 800; + let i = 0; + + ws.stream({ binary: true }, (error, send) => { + assert.ifError(error); + const start = i * blockSize; const toSend = Math.min(blockSize, bufLen - (i * blockSize)); const end = start + toSend; const isFinal = toSend < blockSize; + send(buffer.slice(start, end), isFinal); i += 1; }); }); - srv.on('message', function (data, flags) { + + srv.on('message', (data, flags) => { assert.ok(flags.binary); - assert.ok(areArraysEqual(buffer, data)); + assert.ok(data.equals(buffer)); + srv.close(done); ws.terminate(); - srv.close(); - done(); }); }); }); it('before connect should pass error through callback', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('error', function () {}); - ws.stream(function (error) { + + ws.stream((error) => { assert.ok(error instanceof Error); + srv.close(done); ws.terminate(); - srv.close(); - done(); }); }); }); it('without callback should fail', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { + + ws.on('open', () => { try { ws.stream(); } catch (e) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); }); }); it('will cause intermittent send to be delayed in order', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const payload = 'HelloWorld'; - ws.on('open', function () { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => { let i = 0; - ws.stream(function (error, send) { - assert.ok(!error); + + ws.stream((error, send) => { + assert.ifError(error); if (++i === 1) { send(payload.substr(0, 5)); ws.send('foobar'); @@ -1195,64 +1203,69 @@ describe('WebSocket', function () { } }); }); + let receivedIndex = 0; - srv.on('message', function (data, flags) { - ++receivedIndex; - if (receivedIndex === 1) { + + srv.on('message', (data, flags) => { + if (++receivedIndex === 1) { assert.ok(!flags.binary); - assert.equal(payload, data); + assert.strictEqual(data, payload); } else if (receivedIndex === 2) { assert.ok(!flags.binary); - assert.equal('foobar', data); + assert.strictEqual(data, 'foobar'); } else { assert.ok(!flags.binary); - assert.equal('baz', data); - srv.close(); + assert.strictEqual(data, 'baz'); + srv.close(done); ws.terminate(); - done(); } }); }); }); - it('will cause intermittent stream to be delayed in order', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + it('will cause intermittent stream to be delayed in order', function (done) { + server.createServer(++port, (srv) => { const payload = 'HelloWorld'; - ws.on('open', function () { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => { let i = 0; - ws.stream(function (error, send) { - assert.ok(!error); + + ws.stream((error, send) => { + assert.ifError(error); if (++i === 1) { send(payload.substr(0, 5)); + let i2 = 0; - ws.stream(function (error, send) { - assert.ok(!error); + + ws.stream((error, send) => { + assert.ifError(error); if (++i2 === 1) send('foo'); else send('bar', true); }); + ws.send('baz'); } else { send(payload.substr(5, 5), true); } }); }); + let receivedIndex = 0; - srv.on('message', function (data, flags) { - ++receivedIndex; - if (receivedIndex === 1) { + + srv.on('message', (data, flags) => { + if (++receivedIndex === 1) { assert.ok(!flags.binary); - assert.equal(payload, data); + assert.strictEqual(data, payload); } else if (receivedIndex === 2) { assert.ok(!flags.binary); - assert.equal('foobar', data); + assert.strictEqual(data, 'foobar'); } else if (receivedIndex === 3) { assert.ok(!flags.binary); - assert.equal('baz', data); - setTimeout(function () { - srv.close(); + assert.strictEqual(data, 'baz'); + setTimeout(() => { + srv.close(done); ws.terminate(); - done(); }, 1000); } else { throw new Error('more messages than we actually sent just arrived'); @@ -1262,13 +1275,15 @@ describe('WebSocket', function () { }); it('will cause intermittent ping to be delivered', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const payload = 'HelloWorld'; - ws.on('open', function () { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => { let i = 0; - ws.stream(function (error, send) { - assert.ok(!error); + + ws.stream((error, send) => { + assert.ifError(error); if (++i === 1) { send(payload.substr(0, 5)); ws.ping('foobar'); @@ -1277,35 +1292,38 @@ describe('WebSocket', function () { } }); }); + let receivedIndex = 0; - srv.on('message', function (data, flags) { + + srv.on('message', (data, flags) => { assert.ok(!flags.binary); - assert.equal(payload, data); + assert.strictEqual(data, payload); if (++receivedIndex === 2) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); - srv.on('ping', function (data) { - assert.equal('foobar', data); + + srv.on('ping', (data) => { + assert.strictEqual(data.toString(), 'foobar'); if (++receivedIndex === 2) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); }); }); it('will cause intermittent pong to be delivered', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const payload = 'HelloWorld'; - ws.on('open', function () { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => { let i = 0; - ws.stream(function (error, send) { - assert.ok(!error); + + ws.stream((error, send) => { + assert.ifError(error); if (++i === 1) { send(payload.substr(0, 5)); ws.pong('foobar'); @@ -1314,35 +1332,38 @@ describe('WebSocket', function () { } }); }); + let receivedIndex = 0; - srv.on('message', function (data, flags) { + + srv.on('message', (data, flags) => { assert.ok(!flags.binary); - assert.equal(payload, data); + assert.strictEqual(data, payload); if (++receivedIndex === 2) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); - srv.on('pong', function (data) { - assert.equal('foobar', data); + + srv.on('pong', (data) => { + assert.strictEqual(data.toString(), 'foobar'); if (++receivedIndex === 2) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); }); }); it('will cause intermittent close to be delivered', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const payload = 'HelloWorld'; + const ws = new WebSocket(`ws://localhost:${port}`); let errorGiven = false; - ws.on('open', function () { + + ws.on('open', () => { let i = 0; - ws.stream(function (error, send) { + + ws.stream((error, send) => { if (++i === 1) { send(payload.substr(0, 5)); ws.close(1000, 'foobar'); @@ -1354,19 +1375,21 @@ describe('WebSocket', function () { } }); }); - ws.on('close', function () { + + ws.on('close', () => { assert.ok(errorGiven); - srv.close(); + srv.close(done); ws.terminate(); - done(); }); - srv.on('message', function (data, flags) { + + srv.on('message', (data, flags) => { assert.ok(!flags.binary); - assert.equal(payload, data); + assert.strictEqual(data, payload); }); - srv.on('close', function (code, data) { - assert.equal(1000, code); - assert.equal('foobar', data); + + srv.on('close', (code, data) => { + assert.strictEqual(code, 1000); + assert.strictEqual(data.toString(), 'foobar'); }); }); }); @@ -1374,375 +1397,356 @@ describe('WebSocket', function () { describe('#close', function () { it('will raise error callback, if any, if called during send stream', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); let errorGiven = false; - ws.on('open', function () { - const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100, encoding: 'utf8' }); - ws.send(fileStream, function (error) { - errorGiven = error != null; + + ws.on('open', () => { + const fileStream = fs.createReadStream('test/fixtures/textfile', { + highWaterMark: 100, + encoding: 'utf8' + }); + + ws.send(fileStream, (error) => { + errorGiven = !!error; }); ws.close(1000, 'foobar'); }); - ws.on('close', function () { - setTimeout(function () { + + ws.on('close', () => { + setTimeout(() => { assert.ok(errorGiven); - srv.close(); - ws.terminate(); - done(); + srv.close(done); }, 1000); }); }); }); it('without invalid first argument throws exception', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { + + ws.on('open', () => { try { ws.close('error'); } catch (e) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); }); }); it('without reserved error code 1004 throws exception', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { + + ws.on('open', () => { try { ws.close(1004); } catch (e) { - srv.close(); + srv.close(done); ws.terminate(); - done(); } }); }); }); it('without message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.close(1000); - }); - srv.on('close', function (code, message) { - assert.equal('', message); - srv.close(); + + ws.on('open', () => ws.close(1000)); + + srv.on('close', (code, message) => { + assert.strictEqual(message, ''); + srv.close(done); ws.terminate(); - done(); }); }); }); it('with message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.close(1000, 'some reason'); - }); - srv.on('close', function (code, message, flags) { + + ws.on('open', () => ws.close(1000, 'some reason')); + + srv.on('close', (code, message, flags) => { assert.ok(flags.masked); - assert.equal('some reason', message); - srv.close(); + assert.strictEqual(message, 'some reason'); + srv.close(done); ws.terminate(); - done(); }); }); }); it('with encoded message is successfully transmitted to the server', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', function () { - ws.close(1000, 'some reason', {mask: true}); - }); - srv.on('close', function (code, message, flags) { + + ws.on('open', () => ws.close(1000, 'some reason', { mask: true })); + + srv.on('close', (code, message, flags) => { assert.ok(flags.masked); - assert.equal('some reason', message); - srv.close(); + assert.strictEqual(message, 'some reason'); + srv.close(done); ws.terminate(); - done(); }); }); }); it('ends connection to the server', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); let connectedOnce = false; - ws.on('open', function () { + + ws.on('open', () => { connectedOnce = true; ws.close(1000, 'some reason', {mask: true}); }); - ws.on('close', function () { + + ws.on('close', () => { assert.ok(connectedOnce); - srv.close(); + srv.close(done); ws.terminate(); - done(); }); }); }); it('consumes all data when the server socket closed', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { - wss.on('connection', function (conn) { + const wss = new WebSocketServer({ port: ++port }, () => { + wss.on('connection', (conn) => { conn.send('foo'); conn.send('bar'); conn.send('baz'); conn.close(); }); + const ws = new WebSocket(`ws://localhost:${port}`); const messages = []; - ws.on('message', function (message) { + + ws.on('message', (message) => { messages.push(message); if (messages.length === 3) { - assert.deepEqual(messages, ['foo', 'bar', 'baz']); - wss.close(); + assert.deepStrictEqual(messages, ['foo', 'bar', 'baz']); + + wss.close(done); ws.terminate(); - done(); } }); }); }); it('allows close code 1013', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('close', function (code) { + + ws.on('close', (code) => { assert.strictEqual(code, 1013); wss.close(done); }); }); - wss.on('connection', function (ws) { - ws.close(1013); - }); + wss.on('connection', (ws) => ws.close(1013)); }); }); describe('W3C API emulation', function () { it('should not throw errors when getting and setting', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - const listener = function () {}; + const listener = () => {}; ws.onmessage = listener; ws.onerror = listener; ws.onclose = listener; ws.onopen = listener; - assert.ok(ws.binaryType === 'nodebuffer'); + assert.strictEqual(ws.binaryType, 'nodebuffer'); ws.binaryType = 'arraybuffer'; - assert.ok(ws.binaryType === 'arraybuffer'); + assert.strictEqual(ws.binaryType, 'arraybuffer'); - assert.ok(ws.onopen === listener); - assert.ok(ws.onmessage === listener); - assert.ok(ws.onclose === listener); - assert.ok(ws.onerror === listener); + assert.strictEqual(ws.onopen, listener); + assert.strictEqual(ws.onmessage, listener); + assert.strictEqual(ws.onclose, listener); + assert.strictEqual(ws.onerror, listener); - srv.close(); + srv.close(done); ws.terminate(); - done(); }); }); it('should work the same as the EventEmitter api', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); let message = 0; let close = 0; let open = 0; - ws.onmessage = function (messageEvent) { - assert.ok(!!messageEvent.data); + ws.onmessage = (messageEvent) => { + assert.strictEqual(messageEvent.data, 'foo'); ++message; ws.close(); }; - ws.onopen = function () { - ++open; - }; - - ws.onclose = function () { - ++close; - }; - - ws.on('open', function () { - ws.send('foo'); - }); + ws.onopen = () => ++open; + ws.onclose = () => ++close; - ws.on('close', function () { - process.nextTick(function () { - assert.ok(message === 1); - assert.ok(open === 1); - assert.ok(close === 1); + ws.on('open', () => ws.send('foo')); - srv.close(); - ws.terminate(); - done(); - }); + ws.on('close', () => { + assert.strictEqual(message, 1); + assert.strictEqual(open, 1); + assert.strictEqual(close, 1); + srv.close(done); + ws.terminate(); }); }); }); it('should receive text data wrapped in a MessageEvent when using addEventListener', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.addEventListener('open', function () { - ws.send('hi'); - }); - ws.addEventListener('message', function (messageEvent) { - assert.equal('hi', messageEvent.data); + + ws.addEventListener('open', () => ws.send('hi')); + ws.addEventListener('message', (messageEvent) => { + assert.strictEqual(messageEvent.data, 'hi'); + srv.close(done); ws.terminate(); - srv.close(); - done(); }); }); }); it('should receive valid CloseEvent when server closes with code 1000', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.addEventListener('close', function (closeEvent) { - assert.equal(true, closeEvent.wasClean); - assert.equal(1000, closeEvent.code); - ws.terminate(); + + ws.addEventListener('close', (closeEvent) => { + assert.ok(closeEvent.wasClean); + assert.strictEqual(closeEvent.code, 1000); + wss.close(); done(); }); }); - wss.on('connection', function (client) { - client.close(1000); - }); + + wss.on('connection', (client) => client.close(1000)); }); it('should receive valid CloseEvent when server closes with code 1001', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.addEventListener('close', function (closeEvent) { - assert.equal(false, closeEvent.wasClean); - assert.equal(1001, closeEvent.code); - assert.equal('some daft reason', closeEvent.reason); - ws.terminate(); + + ws.addEventListener('close', (closeEvent) => { + assert.ok(!closeEvent.wasClean); + assert.strictEqual(closeEvent.code, 1001); + assert.strictEqual(closeEvent.reason, 'some daft reason'); + wss.close(); done(); }); }); - wss.on('connection', function (client) { - client.close(1001, 'some daft reason'); - }); + + wss.on('connection', (client) => client.close(1001, 'some daft reason')); }); it('should have target set on Events', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.addEventListener('open', function (openEvent) { - assert.equal(ws, openEvent.target); + + ws.addEventListener('open', (openEvent) => { + assert.strictEqual(openEvent.target, ws); }); - ws.addEventListener('message', function (messageEvent) { - assert.equal(ws, messageEvent.target); + ws.addEventListener('message', (messageEvent) => { + assert.strictEqual(messageEvent.target, ws); wss.close(); }); - ws.addEventListener('close', function (closeEvent) { - assert.equal(ws, closeEvent.target); + ws.addEventListener('close', (closeEvent) => { + assert.strictEqual(closeEvent.target, ws); ws.emit('error', new Error('forced')); }); - ws.addEventListener('error', function (errorEvent) { - assert.equal(errorEvent.message, 'forced'); - assert.equal(ws, errorEvent.target); - ws.terminate(); + ws.addEventListener('error', (errorEvent) => { + assert.strictEqual(errorEvent.message, 'forced'); + assert.strictEqual(errorEvent.target, ws); + done(); }); }); - wss.on('connection', function (client) { - client.send('hi'); - }); + + wss.on('connection', (client) => client.send('hi')); }); it('should have type set on Events', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.addEventListener('open', function (openEvent) { - assert.equal('open', openEvent.type); + + ws.addEventListener('open', (openEvent) => { + assert.strictEqual(openEvent.type, 'open'); }); - ws.addEventListener('message', function (messageEvent) { - assert.equal('message', messageEvent.type); + ws.addEventListener('message', (messageEvent) => { + assert.strictEqual(messageEvent.type, 'message'); wss.close(); }); - ws.addEventListener('close', function (closeEvent) { - assert.equal('close', closeEvent.type); + ws.addEventListener('close', (closeEvent) => { + assert.strictEqual(closeEvent.type, 'close'); ws.emit('error', new Error('forced')); }); - ws.addEventListener('error', function (errorEvent) { - assert.equal(errorEvent.message, 'forced'); - assert.equal('error', errorEvent.type); - ws.terminate(); + ws.addEventListener('error', (errorEvent) => { + assert.strictEqual(errorEvent.message, 'forced'); + assert.strictEqual(errorEvent.type, 'error'); + done(); }); }); - wss.on('connection', function (client) { - client.send('hi'); - }); + + wss.on('connection', (client) => client.send('hi')); }); it('should pass binary data as a node.js Buffer by default', function (done) { - server.createServer(++port, function (srv) { - const ws = new WebSocket(`ws://localhost:${port}`); + server.createServer(++port, (srv) => { const array = new Uint8Array(4096); + const ws = new WebSocket(`ws://localhost:${port}`); - ws.onopen = function () { - ws.send(array, {binary: true}); - }; - ws.onmessage = function (messageEvent) { + ws.onopen = () => ws.send(array, { binary: true }); + ws.onmessage = (messageEvent) => { assert.ok(messageEvent.binary); - assert.ok(ws.binaryType === 'nodebuffer'); + assert.strictEqual(ws.binaryType, 'nodebuffer'); assert.ok(messageEvent.data instanceof Buffer); + srv.close(done); ws.terminate(); - srv.close(); - done(); }; }); }); it('should pass an ArrayBuffer for event.data if binaryType = arraybuffer', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { + const array = new Uint8Array(4096); const ws = new WebSocket(`ws://localhost:${port}`); + ws.binaryType = 'arraybuffer'; - const array = new Uint8Array(4096); - ws.onopen = function () { - ws.send(array, {binary: true}); - }; - ws.onmessage = function (messageEvent) { + ws.onopen = () => ws.send(array, { binary: true }); + ws.onmessage = (messageEvent) => { assert.ok(messageEvent.binary); assert.ok(messageEvent.data instanceof ArrayBuffer); + srv.close(done); ws.terminate(); - srv.close(); - done(); }; }); }); it('should ignore binaryType for text messages', function (done) { - server.createServer(++port, function (srv) { + server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); ws.binaryType = 'arraybuffer'; - ws.onopen = function () { - ws.send('foobar'); - }; - ws.onmessage = function (messageEvent) { + ws.onopen = () => ws.send('foobar'); + ws.onmessage = (messageEvent) => { assert.ok(!messageEvent.binary); - assert.ok(typeof messageEvent.data === 'string'); + assert.strictEqual(typeof messageEvent.data, 'string'); + srv.close(done); ws.terminate(); - srv.close(); - done(); }; }); }); @@ -1855,6 +1859,7 @@ describe('WebSocket', function () { ws.on('message', (message, flags) => { assert.strictEqual(flags.binary, true); assert.ok(buf.equals(message)); + server.close(done); wss.close(); }); @@ -1869,6 +1874,7 @@ describe('WebSocket', function () { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); }); + wss.on('connection', (client) => { assert.strictEqual(client.supports.binary, true); wss.close(); @@ -1911,10 +1917,10 @@ describe('WebSocket', function () { it('honors origin set in options', function (done) { const server = http.createServer(); - server.listen(++port, function () { + server.listen(++port, () => { const options = { origin: 'https://example.com:8000' }; - server.on('upgrade', function (req, socket, head) { + server.on('upgrade', (req, socket, head) => { assert.strictEqual(req.headers['origin'], options.origin); server.close(done); socket.destroy(); @@ -1929,52 +1935,71 @@ describe('WebSocket', function () { // so we need to expose the method that does this const buildHostHeader = WebSocket.buildHostHeader; let host = buildHostHeader(false, 'localhost', 80); - assert.equal('localhost', host); + assert.strictEqual(host, 'localhost'); host = buildHostHeader(false, 'localhost', 88); - assert.equal('localhost:88', host); + assert.strictEqual(host, 'localhost:88'); host = buildHostHeader(true, 'localhost', 443); - assert.equal('localhost', host); + assert.strictEqual(host, 'localhost'); host = buildHostHeader(true, 'localhost', 8443); - assert.equal('localhost:8443', host); + assert.strictEqual(host, 'localhost:8443'); }); }); describe('permessage-deflate', function () { - it('is enabled by default', function (done) { - const srv = http.createServer(); - const wss = new WebSocketServer({server: srv, perMessageDeflate: true}); - srv.listen(++port, function () { + it('is enabled by default', (done) => { + const server = http.createServer(); + const wss = new WebSocketServer({ server, perMessageDeflate: true }); + + server.on('upgrade', (req, socket, head) => { + assert.ok(req.headers['sec-websocket-extensions'].includes('permessage-deflate')); + }); + + server.listen(++port, () => { const ws = new WebSocket(`ws://localhost:${port}`); - srv.on('upgrade', function (req, socket, head) { - assert.ok(~req.headers['sec-websocket-extensions'].indexOf('permessage-deflate')); - }); - ws.on('open', function () { + + ws.on('open', () => { assert.ok(ws.extensions['permessage-deflate']); - ws.terminate(); + server.close(done); wss.close(); - done(); }); }); }); it('can be disabled', function (done) { - const srv = http.createServer(); - const wss = new WebSocketServer({server: srv, perMessageDeflate: true}); - srv.listen(++port, function () { - const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: false}); - srv.on('upgrade', function (req, socket, head) { - assert.ok(!req.headers['sec-websocket-extensions']); - ws.terminate(); + const server = http.createServer(); + const wss = new WebSocketServer({ server, perMessageDeflate: true }); + + server.on('upgrade', (req, socket, head) => { + assert.strictEqual(req.headers['sec-websocket-extensions'], undefined); + }); + + server.listen(++port, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: false + }); + + ws.on('open', () => { + server.close(done); wss.close(); - done(); }); }); }); it('can send extension parameters', function (done) { - const srv = http.createServer(); - const wss = new WebSocketServer({server: srv, perMessageDeflate: true}); - srv.listen(++port, function () { + const server = http.createServer(); + const wss = new WebSocketServer({ server, perMessageDeflate: true }); + + server.on('upgrade', (req, socket, head) => { + const extensions = req.headers['sec-websocket-extensions']; + + assert.notStrictEqual(extensions.indexOf('permessage-deflate'), -1); + assert.notStrictEqual(extensions.indexOf('server_no_context_takeover'), -1); + assert.notStrictEqual(extensions.indexOf('client_no_context_takeover'), -1); + assert.notStrictEqual(extensions.indexOf('server_max_window_bits=10'), -1); + assert.notStrictEqual(extensions.indexOf('client_max_window_bits'), -1); + }); + + server.listen(++port, () => { const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { serverNoContextTakeover: true, @@ -1983,148 +2008,181 @@ describe('WebSocket', function () { clientMaxWindowBits: true } }); - srv.on('upgrade', function (req, socket, head) { - const extensions = req.headers['sec-websocket-extensions']; - assert.ok(~extensions.indexOf('permessage-deflate')); - assert.ok(~extensions.indexOf('server_no_context_takeover')); - assert.ok(~extensions.indexOf('client_no_context_takeover')); - assert.ok(~extensions.indexOf('server_max_window_bits=10')); - assert.ok(~extensions.indexOf('client_max_window_bits')); - ws.terminate(); + + ws.on('open', () => { + server.close(done); wss.close(); - done(); }); }); }); it('can send and receive text data', function (done) { - const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); - ws.on('open', function () { - ws.send('hi', {compress: true}); + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: true }); - ws.on('message', function (message, flags) { - assert.equal('hi', message); - ws.terminate(); + + ws.on('open', () => ws.send('hi', { compress: true })); + ws.on('message', (message, flags) => { + assert.strictEqual(message, 'hi'); wss.close(); done(); }); }); - wss.on('connection', function (ws) { - ws.on('message', function (message, flags) { - ws.send(message, {compress: true}); - }); + + wss.on('connection', (ws) => { + ws.on('message', (message, flags) => ws.send(message, { + compress: true + })); }); }); it('can send and receive a typed array', function (done) { const array = new Float32Array(5); - for (let i = 0; i < array.length; i++) array[i] = i / 2; - const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); - ws.on('open', function () { - ws.send(array, {compress: true}); - }); - ws.on('message', function (message, flags) { - assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); - ws.terminate(); + + for (let i = 0; i < array.length; i++) { + array[i] = i / 2; + } + + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: true + }); + + ws.on('open', () => ws.send(array, { compress: true })); + ws.on('message', (message, flags) => { + assert.ok(message.equals(Buffer.from(array.buffer))); wss.close(); done(); }); }); - wss.on('connection', function (ws) { - ws.on('message', function (message, flags) { - ws.send(message, {compress: true}); - }); + + wss.on('connection', (ws) => { + ws.on('message', (message, flags) => ws.send(message, { + compress: true + })); }); }); it('can send and receive ArrayBuffer', function (done) { const array = new Float32Array(5); - for (let i = 0; i < array.length; i++) array[i] = i / 2; - const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); - ws.on('open', function () { - ws.send(array.buffer, {compress: true}); - }); - ws.on('message', function (message, flags) { - assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); - ws.terminate(); + + for (let i = 0; i < array.length; i++) { + array[i] = i / 2; + } + + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: true + }); + + ws.on('open', () => ws.send(array.buffer, { compress: true })); + ws.on('message', (message, flags) => { + assert.ok(message.equals(Buffer.from(array.buffer))); wss.close(); done(); }); }); - wss.on('connection', function (ws) { - ws.on('message', function (message, flags) { - ws.send(message, {compress: true}); - }); + + wss.on('connection', (ws) => { + ws.on('message', (message, flags) => ws.send(message, { + compress: true + })); }); }); it('with binary stream will send fragmented data', function (done) { - const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: true + }); + let callbackFired = false; - ws.on('open', function () { - const fileStream = fs.createReadStream('test/fixtures/textfile', { highWaterMark: 100 }); - ws.send(fileStream, {binary: true, compress: true}, function (error) { - assert.equal(null, error); + + ws.on('open', () => { + const fileStream = fs.createReadStream('test/fixtures/textfile', { + highWaterMark: 100 + }); + + ws.send(fileStream, { binary: true, compress: true }, (error) => { + assert.ifError(error); callbackFired = true; }); }); - ws.on('close', function () { + + ws.on('close', () => { assert.ok(callbackFired); wss.close(); done(); }); }); - wss.on('connection', function (ws) { - ws.on('message', function (data, flags) { + + wss.on('connection', (ws) => { + ws.on('message', (data, flags) => { assert.ok(flags.binary); - assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile'), data)); - ws.terminate(); + assert.ok(data.equals(fs.readFileSync('test/fixtures/textfile'))); + ws.close(); }); }); }); describe('#send', function () { it('can set the compress option true when perMessageDeflate is disabled', function (done) { - const wss = new WebSocketServer({port: ++port}, function () { - const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: false}); - ws.on('open', function () { - ws.send('hi', {compress: true}); + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: false }); - ws.on('message', function (message, flags) { - assert.equal('hi', message); - ws.terminate(); + + ws.on('open', () => ws.send('hi', { compress: true })); + ws.on('message', (message, flags) => { + assert.strictEqual(message, 'hi'); wss.close(); done(); }); }); - wss.on('connection', function (ws) { - ws.on('message', function (message, flags) { - ws.send(message, {compress: true}); - }); + + wss.on('connection', (ws) => { + ws.on('message', (message, flags) => ws.send(message, { + compress: true + })); }); }); }); describe('#close', function () { it('should not raise error callback, if any, if called during send data', function (done) { - const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: true + }); let errorGiven = false; - ws.on('open', function () { - ws.send('hi', function (error) { - errorGiven = error != null; + + ws.on('open', () => { + ws.send('hi', (error) => { + errorGiven = !!error; }); ws.close(); }); - ws.on('close', function () { - setTimeout(function () { + + ws.on('close', () => { + setTimeout(() => { assert.ok(!errorGiven); wss.close(); - ws.terminate(); done(); }, 1000); }); @@ -2134,20 +2192,26 @@ describe('WebSocket', function () { describe('#terminate', function () { it('will raise error callback, if any, if called during send data', function (done) { - const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: { threshold: 0 }}); + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: { threshold: 0 } + }); let errorGiven = false; - ws.on('open', function () { - ws.send('hi', function (error) { - errorGiven = error != null; + + ws.on('open', () => { + ws.send('hi', (error) => { + errorGiven = !!error; }); ws.terminate(); }); - ws.on('close', function () { - setTimeout(function () { + + ws.on('close', () => { + setTimeout(() => { assert.ok(errorGiven); wss.close(); - ws.terminate(); done(); }, 1000); }); @@ -2155,18 +2219,23 @@ describe('WebSocket', function () { }); it('can call during receiving data', function (done) { - const wss = new WebSocketServer({port: ++port, perMessageDeflate: true}, function () { - const ws = new WebSocket(`ws://localhost:${port}`, {perMessageDeflate: true}); - wss.on('connection', function (client) { + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { + perMessageDeflate: true + }); + + wss.on('connection', (client) => { for (let i = 0; i < 10; i++) { client.send('hi'); } - client.send('hi', function () { - ws.terminate(); - }); + client.send('hi', () => ws.terminate()); }); - ws.on('close', function () { - setTimeout(function () { + + ws.on('close', () => { + setTimeout(() => { wss.close(); done(); }, 1000); diff --git a/test/testserver.js b/test/testserver.js index 4f03b79c8..bc94071ca 100644 --- a/test/testserver.js +++ b/test/testserver.js @@ -1,183 +1,165 @@ 'use strict'; +const EventEmitter = require('events'); const crypto = require('crypto'); -const events = require('events'); const http = require('http'); -const util = require('util'); const Receiver = require('../lib/Receiver'); const Sender = require('../lib/Sender'); module.exports = { handlers: { - valid: validServer, - invalidKey: invalidRequestHandler, closeAfterConnect: closeAfterConnectHandler, - return401: return401 + invalidKey: invalidRequestHandler, + return401: return401, + valid: validServer }, - createServer: function (port, handler, cb) { + createServer: (port, handler, cb) => { if (handler && !cb) { cb = handler; handler = null; } - const webServer = http.createServer(function (req, res) { - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end('okay'); - }); + + const webServer = http.createServer(); const srv = new Server(webServer); - webServer.on('upgrade', function (req, socket) { + + webServer.on('upgrade', (req, socket) => { webServer._socket = socket; (handler || validServer)(srv, req, socket); }); - webServer.listen(port, '127.0.0.1', function () { cb(srv); }); + + webServer.listen(port, '127.0.0.1', () => cb(srv)); } }; -/** - * Test strategies - */ - function validServer (server, req, socket) { - if (typeof req.headers.upgrade === 'undefined' || - req.headers.upgrade.toLowerCase() !== 'websocket') { + if (!req.headers.upgrade || req.headers.upgrade !== 'websocket') { throw new Error('invalid headers'); } if (!req.headers['sec-websocket-key']) { - socket.end(); throw new Error('websocket key is missing'); } // calc key - let key = req.headers['sec-websocket-key']; - const shasum = crypto.createHash('sha1'); - shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); - key = shasum.digest('base64'); - - const headers = [ - 'HTTP/1.1 101 Switching Protocols', - 'Upgrade: websocket', - 'Connection: Upgrade', - 'Sec-WebSocket-Accept: ' + key - ]; - - socket.write(headers.concat('', '').join('\r\n')); + const key = crypto.createHash('sha1') + .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'latin1') + .digest('base64'); + socket.setTimeout(0); socket.setNoDelay(true); + socket.write( + 'HTTP/1.1 101 Switching Protocols\r\n' + + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + `Sec-WebSocket-Accept:${key}\r\n` + + '\r\n' + ); + const sender = new Sender(socket); const receiver = new Receiver(); - receiver.ontext = function (message, flags) { + + receiver.ontext = (message, flags) => { server.emit('message', message, flags); sender.send(message); }; - receiver.onbinary = function (message, flags) { + receiver.onbinary = (message, flags) => { flags = flags || {}; flags.binary = true; server.emit('message', message, flags); - sender.send(message, {binary: true}); + sender.send(message, { binary: true }); }; - receiver.onping = function (message, flags) { + receiver.onping = (message, flags) => { flags = flags || {}; server.emit('ping', message, flags); }; - receiver.onpong = function (message, flags) { + receiver.onpong = (message, flags) => { flags = flags || {}; server.emit('pong', message, flags); }; - receiver.onclose = function (code, message, flags) { + receiver.onclose = (code, message, flags) => { flags = flags || {}; - sender.close(code, message, false, function () { + sender.close(code, message, false, () => { server.emit('close', code, message, flags); socket.end(); }); }; - socket.on('data', function (data) { - receiver.add(data); - }); - socket.on('end', function () { - socket.end(); - }); + socket.on('data', (data) => receiver.add(data)); + socket.on('end', () => socket.end()); } function invalidRequestHandler (server, req, socket) { - if (typeof req.headers.upgrade === 'undefined' || - req.headers.upgrade.toLowerCase() !== 'websocket') { + if (!req.headers.upgrade || req.headers.upgrade !== 'websocket') { throw new Error('invalid headers'); } if (!req.headers['sec-websocket-key']) { - socket.end(); throw new Error('websocket key is missing'); } // calc key - let key = req.headers['sec-websocket-key']; - const shasum = crypto.createHash('sha1'); - shasum.update(key + 'bogus', 'binary'); - key = shasum.digest('base64'); - - const headers = [ - 'HTTP/1.1 101 Switching Protocols', - 'Upgrade: websocket', - 'Connection: Upgrade', - 'Sec-WebSocket-Accept: ' + key - ]; - - socket.write(headers.concat('', '').join('\r\n')); + const key = crypto.createHash('sha1') + .update(`${req.headers['sec-websocket-key']}bogus`, 'latin1') + .digest('base64'); + + socket.write( + 'HTTP/1.1 101 Switching Protocols\r\n' + + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + `Sec-WebSocket-Accept:${key}\r\n` + + '\r\n' + ); socket.end(); } function closeAfterConnectHandler (server, req, socket) { - if (typeof req.headers.upgrade === 'undefined' || - req.headers.upgrade.toLowerCase() !== 'websocket') { + if (!req.headers.upgrade || req.headers.upgrade !== 'websocket') { throw new Error('invalid headers'); } if (!req.headers['sec-websocket-key']) { - socket.end(); throw new Error('websocket key is missing'); } // calc key - let key = req.headers['sec-websocket-key']; - const shasum = crypto.createHash('sha1'); - shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); - key = shasum.digest('base64'); - - const headers = [ - 'HTTP/1.1 101 Switching Protocols', - 'Upgrade: websocket', - 'Connection: Upgrade', - 'Sec-WebSocket-Accept: ' + key - ]; - - socket.write(headers.concat('', '').join('\r\n')); + const key = crypto.createHash('sha1') + .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'latin1') + .digest('base64'); + + socket.write( + 'HTTP/1.1 101 Switching Protocols\r\n' + + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + `Sec-WebSocket-Accept:${key}\r\n` + + '\r\n' + ); socket.end(); } function return401 (server, req, socket) { - const headers = [ - 'HTTP/1.1 401 Unauthorized', - 'Content-type: text/html' - ]; - - socket.write(headers.concat('', '').join('\r\n')); - socket.write('Not allowed!'); + socket.write( + `HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` + + 'Connection: close\r\n' + + 'Content-type: text/html\r\n' + + 'Content-Length: 12\r\n' + + '\r\n' + + 'Not allowed!' + ); socket.end(); } /** * Server object, which will do the actual emitting */ +class Server extends EventEmitter { + constructor (webServer) { + super(); + this.webServer = webServer; + } -function Server (webServer) { - this.webServer = webServer; + close (cb) { + this.webServer.close(cb); + if (this._socket) this._socket.end(); + } } - -util.inherits(Server, events.EventEmitter); - -Server.prototype.close = function () { - this.webServer.close(); - if (this._socket) this._socket.end(); -}; From 36e60fcc93b524369cdb0bc5d8d1a780e7e01f64 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 28 Oct 2016 10:33:45 +0200 Subject: [PATCH 140/669] [test] Don't use the NODE_TLS_REJECT_UNAUTHORIZED env variable --- test/WebSocket.test.js | 15 +++++++++++---- test/WebSocketServer.test.js | 6 +++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 888e35d60..52612c533 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1765,7 +1765,9 @@ describe('WebSocket', function () { server.close(done); }); - server.listen(++port, () => new WebSocket('wss://localhost:' + port)); + server.listen(++port, () => new WebSocket(`wss://localhost:${port}`, { + rejectUnauthorized: false + })); }); it('can connect to secure websocket server with client side certificate', function (done) { @@ -1794,7 +1796,8 @@ describe('WebSocket', function () { server.listen(++port, () => { const ws = new WebSocket(`wss://localhost:${port}`, { cert: fs.readFileSync('test/fixtures/agent1-cert.pem'), - key: fs.readFileSync('test/fixtures/agent1-key.pem') + key: fs.readFileSync('test/fixtures/agent1-key.pem'), + rejectUnauthorized: false }); }); }); @@ -1834,7 +1837,9 @@ describe('WebSocket', function () { }); server.listen(++port, () => { - const ws = new WebSocket(`wss://localhost:${port}`); + const ws = new WebSocket(`wss://localhost:${port}`, { + rejectUnauthorized: false + }); ws.on('open', () => ws.send('foobar')); }); @@ -1853,7 +1858,9 @@ describe('WebSocket', function () { }); server.listen(++port, () => { - const ws = new WebSocket('wss://localhost:' + port); + const ws = new WebSocket(`wss://localhost:${port}`, { + rejectUnauthorized: false + }); ws.on('open', () => ws.send(buf)); ws.on('message', (message, flags) => { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 9d035c375..b74a28afd 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -657,9 +657,9 @@ describe('WebSocketServer', function () { server.close(done); }); - server.listen(++port, () => { - const ws = new WebSocket('wss://localhost:' + port); - }); + server.listen(++port, () => new WebSocket(`wss://localhost:${port}`, { + rejectUnauthorized: false + })); }); it('verifyClient has secure:false for non-ssl connections', function (done) { From 53f033acea1ff6fe5b836f9a9f5ddc1b41b067de Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 28 Oct 2016 10:38:32 +0200 Subject: [PATCH 141/669] [minor] Remove Makefile in favor of npm scripts --- .travis.yml | 2 ++ Makefile | 48 ------------------------------------------------ package.json | 6 +++++- 3 files changed, 7 insertions(+), 49 deletions(-) delete mode 100644 Makefile diff --git a/.travis.yml b/.travis.yml index 4b92d55ae..1891cb08d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,3 +5,5 @@ node_js: - "7" - "6" - "4" +script: + - "npm run test-travis" diff --git a/Makefile b/Makefile deleted file mode 100644 index 2ae3d0fbe..000000000 --- a/Makefile +++ /dev/null @@ -1,48 +0,0 @@ -ALL_TESTS = $(shell find test -name '*.test.js') -ALL_INTEGRATION = $(shell find test -name '*.integration.js') - -lint: - @./node_modules/.bin/eslint . - -run-tests: - @./node_modules/.bin/mocha \ - -t 5000 \ - -s 2400 \ - $(TESTFLAGS) \ - $(TESTS) - -run-integrationtests: - @./node_modules/.bin/mocha \ - -t 5000 \ - -s 6000 \ - $(TESTFLAGS) \ - $(TESTS) - -run-coverage: - @./node_modules/.bin/istanbul cover --report html \ - ./node_modules/.bin/_mocha -- \ - -t 5000 \ - -s 6000 \ - $(TESTFLAGS) \ - $(TESTS) - -test: lint - @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 TESTS="$(ALL_TESTS)" run-tests - -integrationtest: - @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 TESTS="$(ALL_INTEGRATION)" run-integrationtests - -coverage: - @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 TESTS="$(ALL_TESTS)" run-coverage - -benchmark: - @node bench/sender.benchmark.js - @node bench/parser.benchmark.js - -autobahn: - @NODE_PATH=lib node test/autobahn.js - -autobahn-server: - @NODE_PATH=lib node test/autobahn-server.js - -.PHONY: test coverage diff --git a/package.json b/package.json index 8d33af457..939c7de8c 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,11 @@ "url": "git://github.com/websockets/ws.git" }, "scripts": { - "test": "make test" + "test-travis": "npm run lint && istanbul cover _mocha --report lcovonly -- test/*.test.js", + "coverage": "istanbul cover _mocha --report html -- test/*.test.js", + "integration": "npm run lint && mocha test/*.integration.js", + "test": "npm run lint && mocha test/*.test.js", + "lint": "eslint ." }, "dependencies": { "ultron": "1.0.x" From 31c05b01cc57d4e688b7b79a59ef22931c5b0b42 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 28 Oct 2016 12:21:00 +0200 Subject: [PATCH 142/669] [test] Add secure WebSocket (wss) integration test --- test/WebSocket.integration.js | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/test/WebSocket.integration.js b/test/WebSocket.integration.js index 237ac5cfc..90d5adcfb 100644 --- a/test/WebSocket.integration.js +++ b/test/WebSocket.integration.js @@ -5,16 +5,37 @@ const assert = require('assert'); const WebSocket = require('..'); describe('WebSocket', function () { - it('communicates successfully with echo service', function (done) { + it('communicates successfully with echo service (ws)', function (done) { const ws = new WebSocket('ws://echo.websocket.org/', { - origin: 'http://websocket.org', + origin: 'ws://echo.websocket.org', protocolVersion: 13 }); const str = Date.now().toString(); let dataReceived = false; - ws.on('open', () => ws.send(str, { mask: true })); + ws.on('open', () => ws.send(str)); + ws.on('close', () => { + assert.ok(dataReceived); + done(); + }); + ws.on('message', (data) => { + dataReceived = true; + assert.strictEqual(data, str); + ws.close(); + }); + }); + + it('communicates successfully with echo service (wss)', function (done) { + const ws = new WebSocket('wss://echo.websocket.org/', { + origin: 'wss://echo.websocket.org', + protocolVersion: 13 + }); + const str = Date.now().toString(); + + let dataReceived = false; + + ws.on('open', () => ws.send(str)); ws.on('close', () => { assert.ok(dataReceived); done(); From d4ac814f8613e2931f39bd4fbd4516d8d56118b5 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 28 Oct 2016 12:31:38 +0200 Subject: [PATCH 143/669] [ignore] Clean up .gitignore and .npmignore --- .gitignore | 9 ++------- .npmignore | 17 ++++++----------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 182c7b910..4b173c692 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,3 @@ +node_modules/ +coverage/ npm-debug.log -node_modules -.*.swp -.lock-* -build -coverage - -builderror.log diff --git a/.npmignore b/.npmignore index 1eba800f8..e0e4dee67 100644 --- a/.npmignore +++ b/.npmignore @@ -1,11 +1,6 @@ -npm-debug.log -node_modules -.*.swp -.lock-* -build - -bench -doc -examples -test - +coverage/ +examples/ +bench/ +test/ +doc/ +.* From 5a025dcb730427be1d696718003784b33babd696 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 28 Oct 2016 15:14:24 +0200 Subject: [PATCH 144/669] [minor] Remove unnecessary `typeof` checks --- lib/Extensions.js | 2 +- lib/Receiver.js | 2 +- lib/WebSocket.js | 44 +++++++++++++++++++----------------------- lib/WebSocketServer.js | 10 ++++------ 4 files changed, 26 insertions(+), 32 deletions(-) diff --git a/lib/Extensions.js b/lib/Extensions.js index 574677129..036c71382 100644 --- a/lib/Extensions.js +++ b/lib/Extensions.js @@ -26,7 +26,7 @@ function parse (value) { var parts = param.trim().split('='); var key = parts[0]; var value = parts[1]; - if (typeof value === 'undefined') { + if (value === undefined) { value = true; } else { // unquote value diff --git a/lib/Receiver.js b/lib/Receiver.js index c5c527729..604ea8fc6 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -201,7 +201,7 @@ class Receiver { } } const handler = opcodes[this.state.opcode]; - if (typeof handler === 'undefined') { + if (handler === undefined) { this.error(new Error(`no handler for opcode ${this.state.opcode}`), 1002); } else { handler.start(this, data); diff --git a/lib/WebSocket.js b/lib/WebSocket.js index e0eead112..511a63f9d 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -162,7 +162,7 @@ WebSocket.prototype.ping = function ping (data, options, dontFailWhenClosed) { options = options || {}; - if (typeof options.mask === 'undefined') options.mask = !this._isServer; + if (options.mask === undefined) options.mask = !this._isServer; this._sender.ping(data, options); }; @@ -183,7 +183,7 @@ WebSocket.prototype.pong = function (data, options, dontFailWhenClosed) { options = options || {}; - if (typeof options.mask === 'undefined') options.mask = !this._isServer; + if (options.mask === undefined) options.mask = !this._isServer; this._sender.pong(data, options); }; @@ -215,7 +215,7 @@ WebSocket.prototype.send = function send (data, options, cb) { } if (this.readyState !== WebSocket.OPEN) { - if (typeof cb === 'function') cb(new Error('not opened')); + if (cb) cb(new Error('not opened')); else throw new Error('not opened'); return; } @@ -230,27 +230,23 @@ WebSocket.prototype.send = function send (data, options, cb) { options = options || {}; if (options.fin !== false) options.fin = true; - if (typeof options.binary === 'undefined') { - options.binary = (data instanceof ArrayBuffer || data instanceof Buffer || - ArrayBuffer.isView(data)); + if (options.binary === undefined) { + options.binary = data instanceof Buffer || data instanceof ArrayBuffer || + ArrayBuffer.isView(data); } - if (typeof options.mask === 'undefined') options.mask = !this._isServer; - if (typeof options.compress === 'undefined') options.compress = true; + if (options.mask === undefined) options.mask = !this._isServer; + if (options.compress === undefined) options.compress = true; if (!this.extensions[PerMessageDeflate.extensionName]) { options.compress = false; } - var readable = typeof stream.Readable === 'function' - ? stream.Readable - : stream.Stream; - - if (data instanceof readable) { + if (data instanceof stream.Readable) { startQueue(this); sendStream(this, data, options, (error) => { process.nextTick(() => executeQueueSends(this)); - if (typeof cb === 'function') cb(error); + if (cb) cb(error); }); } else { this._sender.send(data, options, cb); @@ -271,10 +267,10 @@ WebSocket.prototype.stream = function stream (options, cb) { options = {}; } - if (typeof cb !== 'function') throw new Error('callback must be provided'); + if (!cb) throw new Error('callback must be provided'); if (this.readyState !== WebSocket.OPEN) { - if (typeof cb === 'function') cb(new Error('not opened')); + if (cb) cb(new Error('not opened')); else throw new Error('not opened'); return; } @@ -286,8 +282,8 @@ WebSocket.prototype.stream = function stream (options, cb) { options = options || {}; - if (typeof options.mask === 'undefined') options.mask = !this._isServer; - if (typeof options.compress === 'undefined') options.compress = true; + if (options.mask === undefined) options.mask = !this._isServer; + if (options.compress === undefined) options.compress = true; if (!this.extensions[PerMessageDeflate.extensionName]) { options.compress = false; } @@ -500,7 +496,7 @@ function MessageEvent (dataArg, isBinary, target) { */ function CloseEvent (code, reason, target) { this.type = 'close'; - this.wasClean = (typeof code === 'undefined' || code === 1000); + this.wasClean = code === undefined || code === 1000; this.code = code; this.reason = reason; this.target = target; @@ -723,7 +719,7 @@ function initAsClient (address, protocols, options) { } var serverKey = res.headers['sec-websocket-accept']; - if (typeof serverKey === 'undefined' || serverKey !== expectedServerKey) { + if (serverKey !== expectedServerKey) { this.emit('error', new Error('invalid server key')); this.removeAllListeners(); socket.end(); @@ -855,7 +851,7 @@ function startQueue (instance) { function executeQueueSends (instance) { var queue = instance._queue; - if (typeof queue === 'undefined') return; + if (queue === undefined) return; delete instance._queue; for (var i = 0, l = queue.length; i < l; ++i) { @@ -866,7 +862,7 @@ function executeQueueSends (instance) { function sendStream (instance, stream, options, cb) { stream.on('data', function incoming (data) { if (instance.readyState !== WebSocket.OPEN) { - if (typeof cb === 'function') cb(new Error('not opened')); + if (cb) cb(new Error('not opened')); else { delete instance._queue; instance.emit('error', new Error('not opened')); @@ -880,7 +876,7 @@ function sendStream (instance, stream, options, cb) { stream.on('end', function end () { if (instance.readyState !== WebSocket.OPEN) { - if (typeof cb === 'function') cb(new Error('not opened')); + if (cb) cb(new Error('not opened')); else { delete instance._queue; instance.emit('error', new Error('not opened')); @@ -891,7 +887,7 @@ function sendStream (instance, stream, options, cb) { options.fin = true; instance._sender.send(null, options); - if (typeof cb === 'function') cb(null); + if (cb) cb(null); }); } diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index e51c2e1c5..6bd06f908 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -73,9 +73,7 @@ function WebSocketServer (options, callback) { if (this._server._webSocketPaths && options.server._webSocketPaths[options.path]) { throw new Error('two instances of WebSocketServer cannot listen on the same http server path'); } - if (typeof this._server._webSocketPaths !== 'object') { - this._server._webSocketPaths = {}; - } + if (!this._server._webSocketPaths) this._server._webSocketPaths = {}; this._server._webSocketPaths[options.path] = 1; } } @@ -139,7 +137,7 @@ WebSocketServer.prototype.close = function (callback) { // close the http server if it was internally created try { - if (typeof this._closeServer !== 'undefined') { + if (this._closeServer !== undefined) { this._closeServer(); } } finally { @@ -283,7 +281,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { // calling completeHybiUpgrade2 var completeHybiUpgrade1 = () => { // choose from the sub-protocols - if (typeof this.options.handleProtocols === 'function') { + if (this.options.handleProtocols) { var protList = (protocols || '').split(/, */); var callbackCalled = false; this.options.handleProtocols(protList, (result, protocol) => { @@ -302,7 +300,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { }; // optionally call external client verification handler - if (typeof this.options.verifyClient === 'function') { + if (this.options.verifyClient) { var info = { secure: req.connection.authorized !== undefined || req.connection.encrypted !== undefined, origin: origin, From 95001508cdec29dfe49b9e87a493f9d9f559bcb7 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Mon, 31 Oct 2016 19:27:16 +0100 Subject: [PATCH 145/669] Use `binary` instead of `latin1` encoding for node 4 compatibility --- lib/WebSocketServer.js | 2 +- test/testserver.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 6bd06f908..fe7978a9d 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -217,7 +217,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { var completeHybiUpgrade2 = (protocol) => { // calc key var key = crypto.createHash('sha1') - .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'latin1') + .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'binary') .digest('base64'); var headers = [ diff --git a/test/testserver.js b/test/testserver.js index bc94071ca..f68c4c32e 100644 --- a/test/testserver.js +++ b/test/testserver.js @@ -43,7 +43,7 @@ function validServer (server, req, socket) { // calc key const key = crypto.createHash('sha1') - .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'latin1') + .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'binary') .digest('base64'); socket.setTimeout(0); From c8f3ed4f4d33610b33bf48ae0a6f58cbcd1d87e0 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 2 Nov 2016 14:58:41 +0100 Subject: [PATCH 146/669] [fix] Ignore the case of the Upgrade header value as per spec --- lib/WebSocketServer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index fe7978a9d..10eae03f2 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -170,7 +170,7 @@ WebSocketServer.prototype.handleUpgrade = function (req, socket, upgradeHead, cb } } - if (!req.headers.upgrade || req.headers.upgrade !== 'websocket') { + if (!req.headers.upgrade || req.headers.upgrade.toLowerCase() !== 'websocket') { return abortConnection(socket, 400); } From 060b6cad09eeb1b01d6d073d829e976d7c3be1a9 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 3 Nov 2016 11:25:06 +0100 Subject: [PATCH 147/669] [fix] Prevent data corruption when sending ping/pong messages (#881) --- lib/Sender.js | 50 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index f1acac557..ba4fa76c5 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -64,6 +64,7 @@ class Sender { * @api public */ ping (data, options) { + if (data) data = toBuffer(data); if (this.extensions[PerMessageDeflate.extensionName]) { this.enqueue([this.doPing, [data, options]]); } else { @@ -77,8 +78,7 @@ class Sender { * @api private */ doPing (data, options) { - var mask = options && options.mask; - this.frameAndSend(0x9, data ? Buffer.from(data.toString()) : null, true, mask); + this.frameAndSend(0x9, data, true, options.mask); if (this.extensions[PerMessageDeflate.extensionName]) { this.messageHandlerCallback(); } @@ -90,6 +90,7 @@ class Sender { * @api public */ pong (data, options) { + if (data) data = toBuffer(data); if (this.extensions[PerMessageDeflate.extensionName]) { this.enqueue([this.doPong, [data, options]]); } else { @@ -103,8 +104,7 @@ class Sender { * @api private */ doPong (data, options) { - var mask = options && options.mask; - this.frameAndSend(0xa, data ? Buffer.from(data.toString()) : null, true, mask); + this.frameAndSend(0xa, data, true, options.mask); if (this.extensions[PerMessageDeflate.extensionName]) { this.messageHandlerCallback(); } @@ -120,6 +120,7 @@ class Sender { var mask = options && options.mask; var compress = options && options.compress; var opcode = options && options.binary ? 2 : 1; + if (this.firstFragment === false) { opcode = 0; compress = false; @@ -127,18 +128,9 @@ class Sender { this.firstFragment = false; this.compress = compress; } - if (finalFragment) this.firstFragment = true; - if (data && !Buffer.isBuffer(data)) { - if ((data.buffer || data) instanceof ArrayBuffer) { - data = getBufferFromNative(data); - } else { - if (typeof data === 'number') { - data = data.toString(); - } - data = Buffer.from(data); - } - } + if (finalFragment) this.firstFragment = true; + if (data) data = toBuffer(data); if (this.extensions[PerMessageDeflate.extensionName]) { this.enqueue([this.sendCompressed, [opcode, data, finalFragment, mask, compress, cb]]); @@ -283,11 +275,29 @@ class Sender { module.exports = Sender; -function getBufferFromNative (data) { - // data is either an ArrayBuffer or ArrayBufferView. - return !data.buffer - ? new Buffer(data) - : new Buffer(data.buffer).slice(data.byteOffset, data.byteOffset + data.byteLength); +/** + * Converts `data` into a buffer. + * + * @param {*} data Data to convert + * @return {Buffer} Converted data + * @private + */ +function toBuffer (data) { + if (Buffer.isBuffer(data)) return data; + + if (data instanceof ArrayBuffer) return Buffer.from(data); + + if (ArrayBuffer.isView(data)) { + const buf = Buffer.from(data.buffer); + + if (data.byteLength !== data.buffer.byteLength) { + return buf.slice(data.byteOffset, data.byteOffset + data.byteLength); + } + + return buf; + } + + return Buffer.from(typeof data === 'number' ? data.toString() : data); } function getRandomMask () { From 73d6880b3dea0e3627653077456241c5ba6b7d5b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 31 Oct 2016 14:54:02 +0100 Subject: [PATCH 148/669] [major] Rewrite the parser --- bench/parser.benchmark.js | 39 +- lib/Receiver.js | 723 +++++++++++++++++--------------------- test/Receiver.test.js | 26 +- 3 files changed, 363 insertions(+), 425 deletions(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index f0b9cae72..63883f8ea 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -7,48 +7,55 @@ 'use strict'; const benchmark = require('benchmark'); +const crypto = require('crypto'); const util = require('../test/hybi-util'); const Receiver = require('../').Receiver; -function createBinaryPacket (length) { - const message = Buffer.alloc(length); +// +// Override the `cleanup` method to make the "close message" test work as +// expected. +// +Receiver.prototype.cleanup = function () { + this.state = 0; + this.start(); +}; - for (var i = 0; i < length; ++i) message[i] = i % 10; +function createBinaryPacket (length) { + const message = crypto.randomBytes(length); - return Buffer.from('82' + util.getHybiLengthAsHexString(length, true) + '3483a868' + - util.mask(message, '3483a868').toString('hex'), 'hex'); + return Buffer.from('82' + util.getHybiLengthAsHexString(length, true) + + '3483a868' + util.mask(message, '3483a868').toString('hex'), 'hex'); } const pingMessage = 'Hello'; const pingPacket1 = Buffer.from('89' + util.pack(2, 0x80 | pingMessage.length) + '3483a868' + util.mask(pingMessage, '3483a868').toString('hex'), 'hex'); + +const textMessage = 'a'.repeat(20); +const maskedTextPacket = Buffer.from('81' + util.pack(2, 0x80 | textMessage.length) + + '61616161' + util.mask(textMessage, '61616161').toString('hex'), 'hex'); + const pingPacket2 = Buffer.from('8900', 'hex'); const closePacket = Buffer.from('8800', 'hex'); -const maskedTextPacket = Buffer.from('81933483a86801b992524fa1c60959e68a5216e6cb005ba1d5', 'hex'); const binaryDataPacket = createBinaryPacket(125); const binaryDataPacket2 = createBinaryPacket(65535); const binaryDataPacket3 = createBinaryPacket(200 * 1024); const binaryDataPacket4 = createBinaryPacket(1024 * 1024); -var receiver = new Receiver({}, 1024 * 1024); +const receiver = new Receiver(); const suite = new benchmark.Suite(); suite.add('ping message', () => receiver.add(pingPacket1)); suite.add('ping with no data', () => receiver.add(pingPacket2)); -suite.add('close message', () => { - receiver.add(closePacket); - receiver.endPacket(); -}); -suite.add('masked text message', () => receiver.add(maskedTextPacket)); +suite.add('close message', () => receiver.add(closePacket)); +suite.add('masked text message (20 bytes)', () => receiver.add(maskedTextPacket)); suite.add('binary data (125 bytes)', () => receiver.add(binaryDataPacket)); suite.add('binary data (65535 bytes)', () => receiver.add(binaryDataPacket2)); suite.add('binary data (200 KiB)', () => receiver.add(binaryDataPacket3)); suite.add('binary data (1 MiB)', () => receiver.add(binaryDataPacket4)); -suite.on('cycle', (e) => { - console.log(e.target.toString()); - receiver = new Receiver(); -}); + +suite.on('cycle', (e) => console.log(e.target.toString())); if (require.main === module) { suite.run({ async: true }); diff --git a/lib/Receiver.js b/lib/Receiver.js index 604ea8fc6..cc7b2be30 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -6,10 +6,19 @@ 'use strict'; +const PerMessageDeflate = require('./PerMessageDeflate'); +const bufferUtil = require('./BufferUtil').BufferUtil; const Validation = require('./Validation').Validation; const ErrorCodes = require('./ErrorCodes'); -const bufferUtil = require('./BufferUtil').BufferUtil; -const PerMessageDeflate = require('./PerMessageDeflate'); + +const EMPTY_BUFFER = Buffer.alloc(0); + +const START = 0; +const GET_PAYLOAD_LENGTH_16 = 1; +const GET_PAYLOAD_LENGTH_64 = 2; +const GET_MASK = 3; +const GET_DATA = 4; +const HANDLE_DATA = 5; const noop = () => {}; @@ -17,70 +26,53 @@ const noop = () => {}; * HyBi Receiver implementation. */ class Receiver { + /** + * Creates a Receiver instance. + * + * @param {Object} extensions An object containing the negotiated extensions + * @param {Number} maxPayload The maximum allowed message length + */ constructor (extensions, maxPayload) { this.extensions = extensions || {}; this.maxPayload = maxPayload | 0; - this.state = { - activeFragmentedOperation: null, - lastFragment: false, - masked: false, - opcode: 0 - }; - this.expectBytes = 0; - this.expectHandler = null; - this.currentMessage = []; - this.currentMessageLength = 0; - this.currentPayloadLength = 0; - this.expectData(2, this.processPacket); + + this.bufferedBytes = 0; + this.buffers = []; + + this.compressed = false; + this.payloadLength = 0; + this.fragmented = 0; + this.masked = false; + this.fin = false; + this.mask = null; + this.opcode = 0; + + this.totalPayloadLength = 0; + this.messageLength = 0; + this.fragments = []; + this.dead = false; - this.onerror = noop; - this.ontext = noop; this.onbinary = noop; this.onclose = noop; + this.onerror = noop; + this.ontext = noop; this.onping = noop; this.onpong = noop; - this.buffers = []; - this.bufferedBytes = 0; - } - - /** - * Add new data to the parser. - * - * @api public - */ - add (data) { - if (this.dead) return; - - this.buffers.push(data); - this.bufferedBytes += data.length; - - this.process(); - } - - /** - * Check buffer for data. - * - * @api private - */ - process () { - if (this.expectBytes && this.expectBytes <= this.bufferedBytes) { - var bufferForHandler = this.readBuffer(this.expectBytes); - this.expectBytes = 0; - this.expectHandler(bufferForHandler); - } + this.state = START; } /** - * Consume bytes from the available buffered data. + * Consumes bytes from the available buffered data. * - * @api private + * @param {Number} bytes The number of bytes to consume + * @private */ readBuffer (bytes) { + var bufoff = 0; var dst; var l; - var bufoff = 0; if (bytes === this.buffers[0].length) { this.bufferedBytes -= bytes; @@ -117,441 +109,380 @@ class Receiver { } /** - * Releases all resources used by the receiver. + * Adds new data to the parser. * - * @api public + * @public */ - cleanup () { - this.dead = true; - this.expectBytes = 0; - this.expectHandler = null; - this.buffers = []; - this.bufferedBytes = 0; - this.state = null; - this.currentMessage = null; - this.onerror = null; - this.ontext = null; - this.onbinary = null; - this.onclose = null; - this.onping = null; - this.onpong = null; + add (data) { + if (this.dead) return; + + this.bufferedBytes += data.length; + this.buffers.push(data); + + switch (this.state) { + case START: + this.start(); + break; + case GET_PAYLOAD_LENGTH_16: + this.getPayloadLength16(); + break; + case GET_PAYLOAD_LENGTH_64: + this.getPayloadLength64(); + break; + case GET_MASK: + this.getMask(); + break; + case GET_DATA: + this.getData(); + } } /** - * Waits for a certain amount of data bytes to be available, then fires a callback. + * Reads the first two bytes of a frame. * - * @api private + * @private */ - expectData (length, handler) { - if (length === 0) { - handler(null); + start () { + if (this.bufferedBytes < 2) return; + + const buf = this.readBuffer(2); + + if ((buf[0] & 0x30) !== 0x00) { + this.error(new Error('RSV2 and RSV3 must be clear'), 1002); return; } - this.expectBytes = length; - this.expectHandler = handler; - this.process(); - } + const compressed = (buf[0] & 0x40) === 0x40; - /** - * Start processing a new packet. - * - * @api private - */ - processPacket (data) { - if (this.extensions[PerMessageDeflate.extensionName]) { - if ((data[0] & 0x30) !== 0) { - this.error(new Error('reserved fields (2, 3) must be empty'), 1002); + if (compressed && !this.extensions[PerMessageDeflate.extensionName]) { + this.error(new Error('RSV1 must be clear'), 1002); + return; + } + + this.fin = (buf[0] & 0x80) === 0x80; + this.opcode = buf[0] & 0x0f; + this.payloadLength = buf[1] & 0x7f; + + if (this.opcode === 0x00) { + if (compressed) { + this.error(new Error('RSV1 must be clear'), 1002); return; } - } else { - if ((data[0] & 0x70) !== 0) { - this.error(new Error('reserved fields must be empty'), 1002); + + if (!this.fragmented) { + this.error(new Error(`invalid opcode: ${this.opcode}`), 1002); return; + } else { + this.opcode = this.fragmented; } - } - this.state.lastFragment = (data[0] & 0x80) === 0x80; - this.state.masked = (data[1] & 0x80) === 0x80; - const compressed = (data[0] & 0x40) === 0x40; - const opcode = data[0] & 0xf; - if (opcode === 0) { - if (compressed) { - this.error(new Error('continuation frame cannot have the Per-message Compressed bits'), 1002); + } else if (this.opcode === 0x01 || this.opcode === 0x02) { + if (this.fragmented) { + this.error(new Error(`invalid opcode: ${this.opcode}`), 1002); return; } - // continuation frame - this.state.opcode = this.state.activeFragmentedOperation; - if (!(this.state.opcode === 1 || this.state.opcode === 2)) { - this.error(new Error('continuation frame cannot follow current opcode'), 1002); + + this.compressed = compressed; + } else if (this.opcode > 0x07 && this.opcode < 0x0b) { + if (!this.fin) { + this.error(new Error('FIN must be set'), 1002); return; } - } else { - if (opcode < 3 && this.state.activeFragmentedOperation != null) { - this.error(new Error('data frames after the initial data frame must have opcode 0'), 1002); + + if (compressed) { + this.error(new Error('RSV1 must be clear'), 1002); return; } - if (opcode >= 8 && compressed) { - this.error(new Error('control frames cannot have the Per-message Compressed bits'), 1002); + + if (this.payloadLength > 0x7d) { + this.error(new Error('invalid payload length'), 1002); return; } - this.state.compressed = compressed; - this.state.opcode = opcode; - if (this.state.lastFragment === false) { - this.state.activeFragmentedOperation = opcode; - } + } else { + this.error(new Error(`invalid opcode: ${this.opcode}`), 1002); + return; } - const handler = opcodes[this.state.opcode]; - if (handler === undefined) { - this.error(new Error(`no handler for opcode ${this.state.opcode}`), 1002); + + if (!this.fin && !this.fragmented) this.fragmented = this.opcode; + + this.masked = (buf[1] & 0x80) === 0x80; + + if (this.payloadLength === 126) { + this.state = GET_PAYLOAD_LENGTH_16; + this.getPayloadLength16(); + } else if (this.payloadLength === 127) { + this.state = GET_PAYLOAD_LENGTH_64; + this.getPayloadLength64(); } else { - handler.start(this, data); + this.haveLength(); } } /** - * Endprocessing a packet. + * Gets extended payload length (7+16). * - * @api private + * @private */ - endPacket () { - if (this.dead) return; - if (this.state.lastFragment && this.state.opcode === this.state.activeFragmentedOperation) { - // end current fragmented operation - this.state.activeFragmentedOperation = null; - } - if (this.state.activeFragmentedOperation !== null) { - this.state.opcode = this.state.activeFragmentedOperation; - } else { - this.currentPayloadLength = this.state.opcode = 0; - } - this.state.lastFragment = false; - this.state.masked = false; - this.expectData(2, this.processPacket); + getPayloadLength16 () { + if (this.bufferedBytes < 2) return; + + this.payloadLength = this.readBuffer(2).readUInt16BE(0, true); + this.haveLength(); } /** - * Reset the parser state. + * Gets extended payload length (7+64). * - * @api private + * @private */ - reset () { - if (this.dead) return; - this.state = { - activeFragmentedOperation: null, - lastFragment: false, - masked: false, - opcode: 0 - }; - this.expectBytes = 0; - this.expectHandler = null; - this.buffers = []; - this.bufferedBytes = 0; - this.currentMessage = []; - this.currentMessageLength = 0; - this.currentPayloadLength = 0; + getPayloadLength64 () { + if (this.bufferedBytes < 8) return; + + const buf = this.readBuffer(8); + const num = buf.readUInt32BE(0, true); + + // + // The maximum safe integer in JavaScript is 2^53 - 1. An error is returned + // if payload length is greater than this number. + // + if (num > Math.pow(2, 53 - 32) - 1) { + this.error(new Error('max payload size exceeded'), 1009); + return; + } + + this.payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4, true); + this.haveLength(); } /** - * Unmask received data. + * Payload length has been read. * - * @api private + * @private */ - unmask (mask, buf) { - if (mask != null && buf != null) bufferUtil.unmask(buf, mask); - return buf; + haveLength () { + if (this.opcode < 0x08 && this.maxPayloadExceeded(this.payloadLength)) { + return; + } + + if (this.masked) { + this.state = GET_MASK; + this.getMask(); + } else { + this.state = GET_DATA; + this.getData(); + } } /** - * Handles an error. + * Reads mask bytes. * - * @api private + * @private */ - error (err, protocolErrorCode) { - this.reset(); - this.onerror(err, protocolErrorCode); - return this; + getMask () { + if (this.bufferedBytes < 4) return; + + this.mask = this.readBuffer(4); + this.state = GET_DATA; + this.getData(); } /** - * Checks payload size, disconnects socket when it exceeds `maxPayload`. + * Reads data bytes. * - * @api private + * @private */ - maxPayloadExceeded (length) { - if (this.maxPayload < 1) return false; - - const fullLength = this.currentPayloadLength + length; - if (fullLength <= this.maxPayload) { - this.currentPayloadLength = fullLength; - return false; + getData () { + if (this.payloadLength === 0) { + if (this.opcode > 0x07) this.controlMessage(EMPTY_BUFFER); + else this.dataMessage(); + return; } - this.error(new Error(`payload cannot exceed ${this.maxPayload} bytes`), 1009); - this.cleanup(); - return true; + if (this.bufferedBytes < this.payloadLength) return; + + const data = this.readBuffer(this.payloadLength); + + if (this.masked) bufferUtil.unmask(data, this.mask); + + if (this.opcode > 0x07) { + this.controlMessage(data); + } else if (this.compressed) { + this.state = HANDLE_DATA; + this.decompress(data); + } else if (this.pushFragment(data)) { + this.dataMessage(); + } } /** - * Handles compressed data. + * Decompresses data. * - * @api private + * @param {Buffer} data Compressed data + * @private */ - handleDataCompressed (packet) { + decompress (data) { const extension = this.extensions[PerMessageDeflate.extensionName]; - extension.decompress(packet, this.state.lastFragment, (err, buffer) => { + + extension.decompress(data, this.fin, (err, buf) => { if (this.dead) return; + if (err) { this.error(err, err.closeCode === 1009 ? 1009 : 1007); return; } - this.handleData(buffer); - this.endPacket(); + if (this.pushFragment(buf)) this.dataMessage(); }); } /** - * Handles uncompressed data. + * Handles a data message. * - * @api private + * @private */ - handleData (buffer) { - if (buffer != null) { - if (this.maxPayload < 1 || this.currentMessageLength + buffer.length <= this.maxPayload) { - this.currentMessageLength += buffer.length; - this.currentMessage.push(buffer); + dataMessage () { + if (this.fin) { + const buf = this.fragments.length > 1 + ? Buffer.concat(this.fragments, this.messageLength) + : this.fragments.length === 1 + ? this.fragments[0] + : EMPTY_BUFFER; + + this.totalPayloadLength = 0; + this.fragments.length = 0; + this.messageLength = 0; + this.fragmented = 0; + + if (this.opcode === 2) { + this.onbinary(buf, { masked: this.masked }); } else { - this.error(new Error(`payload cannot exceed ${this.maxPayload} bytes`), 1009); - return; - } - } - if (this.state.lastFragment) { - const messageBuffer = this.currentMessage.length === 1 - ? this.currentMessage[0] - : Buffer.concat(this.currentMessage, this.currentMessageLength); - this.currentMessage = []; - this.currentMessageLength = 0; - - if (this.state.opcode === 2) { - this.onbinary(messageBuffer, { masked: this.state.masked }); - } else { - if (!Validation.isValidUTF8(messageBuffer)) { + if (!Validation.isValidUTF8(buf)) { this.error(new Error('invalid utf8 sequence'), 1007); return; } - this.ontext(messageBuffer.toString(), { masked: this.state.masked }); + + this.ontext(buf.toString(), { masked: this.masked }); } } - } -} -module.exports = Receiver; + this.state = START; + this.start(); + } -// -// Opcode handlers. -// -const opcodes = { - // text - '1': { - start: (receiver, data) => { - // decode length - const firstLength = data[1] & 0x7f; - if (firstLength < 126) { - if (receiver.maxPayloadExceeded(firstLength)) return; - opcodes['1'].getData(receiver, firstLength); - } else if (firstLength === 126) { - receiver.expectData(2, (data) => { - const length = data.readUInt16BE(0, true); - if (receiver.maxPayloadExceeded(length)) return; - opcodes['1'].getData(receiver, length); - }); - } else if (firstLength === 127) { - receiver.expectData(8, (data) => { - if (data.readUInt32BE(0, true) !== 0) { - receiver.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); - return; - } - const length = data.readUInt32BE(4, true); - if (receiver.maxPayloadExceeded(length)) return; - opcodes['1'].getData(receiver, length); - }); - } - }, - getData: (receiver, length) => { - if (receiver.state.masked) { - receiver.expectData(4, (mask) => { - receiver.expectData(length, (data) => opcodes['1'].finish(receiver, mask, data)); - }); - } else { - receiver.expectData(length, (data) => opcodes['1'].finish(receiver, null, data)); - } - }, - finish: (receiver, mask, data) => { - const packet = receiver.unmask(mask, data) || new Buffer(0); - if (receiver.state.compressed) { - receiver.handleDataCompressed(packet); - } else { - receiver.handleData(packet); - receiver.endPacket(); - } - } - }, - // binary - '2': { - start: (receiver, data) => { - // decode length - const firstLength = data[1] & 0x7f; - if (firstLength < 126) { - if (receiver.maxPayloadExceeded(firstLength)) return; - opcodes['2'].getData(receiver, firstLength); - } else if (firstLength === 126) { - receiver.expectData(2, (data) => { - const length = data.readUInt16BE(0, true); - if (receiver.maxPayloadExceeded(length)) return; - opcodes['2'].getData(receiver, length); - }); - } else if (firstLength === 127) { - receiver.expectData(8, (data) => { - if (data.readUInt32BE(0, true) !== 0) { - receiver.error(new Error('packets with length spanning more than 32 bit is currently not supported'), 1008); - return; - } - const length = data.readUInt32BE(4, true); - if (receiver.maxPayloadExceeded(length)) return; - opcodes['2'].getData(receiver, length); - }); - } - }, - getData: (receiver, length) => { - if (receiver.state.masked) { - receiver.expectData(4, (mask) => { - receiver.expectData(length, (data) => opcodes['2'].finish(receiver, mask, data)); - }); - } else { - receiver.expectData(length, (data) => opcodes['2'].finish(receiver, null, data)); - } - }, - finish: (receiver, mask, data) => { - const packet = receiver.unmask(mask, data) || new Buffer(0); - if (receiver.state.compressed) { - receiver.handleDataCompressed(packet); + /** + * Handles a control message. + * + * @param {Buffer} data Data to handle + * @private + */ + controlMessage (data) { + if (this.opcode === 0x08) { + if (data.length === 0) { + this.onclose(1000, '', { masked: this.masked }); + this.cleanup(); + } else if (data.length === 1) { + this.error(new Error('invalid payload length'), 1002); } else { - receiver.handleData(packet); - receiver.endPacket(); - } - } - }, - // close - '8': { - start: (receiver, data) => { - if (receiver.state.lastFragment === false) { - receiver.error('fragmented close is not supported', 1002); - return; - } + const code = data.readUInt16BE(0, true); - // decode length - const firstLength = data[1] & 0x7f; - if (firstLength < 126) { - opcodes['8'].getData(receiver, firstLength); - } else { - receiver.error('control frames cannot have more than 125 bytes of data', 1002); - } - }, - getData: (receiver, length) => { - if (receiver.state.masked) { - receiver.expectData(4, (mask) => { - receiver.expectData(length, (data) => opcodes['8'].finish(receiver, mask, data)); - }); - } else { - receiver.expectData(length, (data) => opcodes['8'].finish(receiver, null, data)); - } - }, - finish: (receiver, mask, data) => { - const packet = receiver.unmask(mask, data); - if (packet && packet.length === 1) { - receiver.error('close packets with data must be at least two bytes long', 1002); - return; - } - const code = packet && packet.length > 1 ? packet.readUInt16BE(0, true) : 1000; - if (!ErrorCodes.isValidErrorCode(code)) { - receiver.error('invalid error code', 1002); - return; - } - var message = ''; - if (packet && packet.length > 2) { - const messageBuffer = packet.slice(2); - if (!Validation.isValidUTF8(messageBuffer)) { - receiver.error('invalid utf8 sequence', 1007); + if (!ErrorCodes.isValidErrorCode(code)) { + this.error(new Error(`invalid status code: ${code}`), 1002); return; } - message = messageBuffer.toString(); + + const buf = data.slice(2); + + if (!Validation.isValidUTF8(buf)) { + this.error(new Error('invalid utf8 sequence'), 1007); + return; + } + + this.onclose(code, buf.toString(), { masked: this.masked }); + this.cleanup(); } - receiver.onclose(code, message, { masked: receiver.state.masked }); - receiver.reset(); + + return; } - }, - // ping - '9': { - start: (receiver, data) => { - if (receiver.state.lastFragment === false) { - receiver.error('fragmented ping is not supported', 1002); - return; - } - // decode length - const firstLength = data[1] & 0x7f; - if (firstLength < 126) { - opcodes['9'].getData(receiver, firstLength); - } else { - receiver.error('control frames cannot have more than 125 bytes of data', 1002); - } - }, - getData: (receiver, length) => { - if (receiver.state.masked) { - receiver.expectData(4, (mask) => { - receiver.expectData(length, (data) => opcodes['9'].finish(receiver, mask, data)); - }); - } else { - receiver.expectData(length, (data) => opcodes['9'].finish(receiver, null, data)); - } - }, - finish: (receiver, mask, data) => { - const packet = receiver.unmask(mask, data); - const flags = { masked: receiver.state.masked, binary: true }; - receiver.onping(packet, flags); - receiver.endPacket(); + const flags = { masked: this.masked, binary: true }; + + if (this.opcode === 0x09) this.onping(data, flags); + else this.onpong(data, flags); + + this.state = START; + this.start(); + } + + /** + * Handles an error. + * + * @param {Error} err The error + * @param {Number} code Close code + * @private + */ + error (err, code) { + this.onerror(err, code); + this.cleanup(); + } + + /** + * Checks payload size, disconnects socket when it exceeds `maxPayload`. + * + * @param {Number} length Payload length + * @private + */ + maxPayloadExceeded (length) { + if (length === 0 || this.maxPayload < 1) return false; + + const fullLength = this.totalPayloadLength + length; + + if (fullLength <= this.maxPayload) { + this.totalPayloadLength = fullLength; + return false; } - }, - // pong - '10': { - start: (receiver, data) => { - if (receiver.state.lastFragment === false) { - receiver.error('fragmented pong is not supported', 1002); - return; - } - // decode length - const firstLength = data[1] & 0x7f; - if (firstLength < 126) { - opcodes['10'].getData(receiver, firstLength); - } else { - receiver.error('control frames cannot have more than 125 bytes of data', 1002); - } - }, - getData: (receiver, length) => { - if (receiver.state.masked) { - receiver.expectData(4, (mask) => { - receiver.expectData(length, (data) => opcodes['10'].finish(receiver, mask, data)); - }); - } else { - receiver.expectData(length, (data) => opcodes['10'].finish(receiver, null, data)); - } - }, - finish: (receiver, mask, data) => { - const packet = receiver.unmask(mask, data); - const flags = { masked: receiver.state.masked, binary: true }; - receiver.onpong(packet, flags); - receiver.endPacket(); + this.error(new Error('max payload size exceeded'), 1009); + return true; + } + + /** + * Appends a fragment in the fragments array after checking that the sum of + * fragment lengths does not exceed `maxPayload`. + * + * @param {Buffer} fragment The fragment to add + * @return {Boolean} `true` if `maxPayload` is not exceeded, else `false` + * @private + */ + pushFragment (fragment) { + if (this.maxPayload < 1 || this.messageLength + fragment.length <= this.maxPayload) { + this.messageLength += fragment.length; + this.fragments.push(fragment); + return true; } + + this.error(new Error('max payload size exceeded'), 1009); + return false; + } + + /** + * Releases resources used by the receiver. + * + * @public + */ + cleanup () { + this.dead = true; + + this.extensions = null; + this.fragments = null; + this.buffers = null; + this.mask = null; + + this.onbinary = null; + this.onclose = null; + this.onerror = null; + this.ontext = null; + this.onping = null; + this.onpong = null; } -}; +} + +module.exports = Receiver; diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 0f7b1b077..5717c7b49 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -122,7 +122,7 @@ describe('Receiver', function () { const p = new Receiver(); p.onping = function (data) { - assert.strictEqual(data, null); + assert.ok(data.equals(Buffer.alloc(0))); done(); }; @@ -312,41 +312,41 @@ describe('Receiver', function () { }); }); - it('resets `currentPayloadLength` only on final frame (unfragmented)', function () { + it('resets `totalPayloadLength` only on final frame (unfragmented)', function () { const p = new Receiver({}, 10); - assert.strictEqual(p.currentPayloadLength, 0); + assert.strictEqual(p.totalPayloadLength, 0); p.add(Buffer.from('810548656c6c6f', 'hex')); - assert.strictEqual(p.currentPayloadLength, 0); + assert.strictEqual(p.totalPayloadLength, 0); }); - it('resets `currentPayloadLength` only on final frame (fragmented)', function () { + it('resets `totalPayloadLength` only on final frame (fragmented)', function () { const p = new Receiver({}, 10); const frame1 = '01024865'; const frame2 = '80036c6c6f'; - assert.strictEqual(p.currentPayloadLength, 0); + assert.strictEqual(p.totalPayloadLength, 0); p.add(Buffer.from(frame1, 'hex')); - assert.strictEqual(p.currentPayloadLength, 2); + assert.strictEqual(p.totalPayloadLength, 2); p.add(Buffer.from(frame2, 'hex')); - assert.strictEqual(p.currentPayloadLength, 0); + assert.strictEqual(p.totalPayloadLength, 0); }); - it('resets `currentPayloadLength` only on final frame (fragmented + ping)', function () { + it('resets `totalPayloadLength` only on final frame (fragmented + ping)', function () { const p = new Receiver({}, 10); const frame1 = '01024865'; const frame2 = '8900'; const frame3 = '80036c6c6f'; - assert.strictEqual(p.currentPayloadLength, 0); + assert.strictEqual(p.totalPayloadLength, 0); p.add(Buffer.from(frame1, 'hex')); - assert.strictEqual(p.currentPayloadLength, 2); + assert.strictEqual(p.totalPayloadLength, 2); p.add(Buffer.from(frame2, 'hex')); - assert.strictEqual(p.currentPayloadLength, 2); + assert.strictEqual(p.totalPayloadLength, 2); p.add(Buffer.from(frame3, 'hex')); - assert.strictEqual(p.currentPayloadLength, 0); + assert.strictEqual(p.totalPayloadLength, 0); }); it('will raise an error on a 200 KiB long masked binary message when maxpayload is 20 KiB', function (done) { From 1b46f5ed97db6fb99a88e1bd4128e5c9d61e6a81 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 3 Nov 2016 17:03:45 +0100 Subject: [PATCH 149/669] [fix] Handle empty frames correctly in compressed & fragmented messages --- lib/Receiver.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index cc7b2be30..62d68c361 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -291,17 +291,14 @@ class Receiver { * @private */ getData () { - if (this.payloadLength === 0) { - if (this.opcode > 0x07) this.controlMessage(EMPTY_BUFFER); - else this.dataMessage(); - return; - } - - if (this.bufferedBytes < this.payloadLength) return; + var data = EMPTY_BUFFER; - const data = this.readBuffer(this.payloadLength); + if (this.payloadLength) { + if (this.bufferedBytes < this.payloadLength) return; - if (this.masked) bufferUtil.unmask(data, this.mask); + data = this.readBuffer(this.payloadLength); + if (this.masked) bufferUtil.unmask(data, this.mask); + } if (this.opcode > 0x07) { this.controlMessage(data); @@ -453,8 +450,12 @@ class Receiver { * @private */ pushFragment (fragment) { - if (this.maxPayload < 1 || this.messageLength + fragment.length <= this.maxPayload) { - this.messageLength += fragment.length; + if (fragment.length === 0) return true; + + const totalLength = this.messageLength + fragment.length; + + if (this.maxPayload < 1 || totalLength <= this.maxPayload) { + this.messageLength = totalLength; this.fragments.push(fragment); return true; } From d3fef3b407239171a7bcc516ff8bdb19606d36bf Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Fri, 4 Nov 2016 19:06:12 +0100 Subject: [PATCH 150/669] Fix compression threshold use with fragmented messages --- doc/ws.md | 1 + lib/Sender.js | 23 +++++++---------------- test/Sender.test.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 897f875c2..b5189594d 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -65,6 +65,7 @@ If `handleProtocols` is not set then the handshake is accepted regardless the va * `threshold` Number: Payloads smaller than this will not be compressed. Default 1024 bytes. If a property is empty then either an offered configuration or a default value is used. +When sending a fragmented message the length of the first fragment is compared to the threshold. This determines if compression is used for the entire message. ### server.close([callback]) diff --git a/lib/Sender.js b/lib/Sender.js index ba4fa76c5..ae62b9f64 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -121,16 +121,20 @@ class Sender { var compress = options && options.compress; var opcode = options && options.binary ? 2 : 1; + if (data) data = toBuffer(data); + if (this.firstFragment === false) { opcode = 0; compress = false; } else { this.firstFragment = false; + if (compress && this.extensions[PerMessageDeflate.extensionName]) { + compress = data && data.length > this.extensions[PerMessageDeflate.extensionName].threshold; + } this.compress = compress; } if (finalFragment) this.firstFragment = true; - if (data) data = toBuffer(data); if (this.extensions[PerMessageDeflate.extensionName]) { this.enqueue([this.sendCompressed, [opcode, data, finalFragment, mask, compress, cb]]); @@ -145,12 +149,12 @@ class Sender { * @api private */ sendCompressed (opcode, data, finalFragment, mask, compress, cb) { - if (data && data.length < this.extensions[PerMessageDeflate.extensionName].threshold) { + if (!this.compress) { this.frameAndSend(opcode, data, finalFragment, mask, false, cb); this.messageHandlerCallback(); return; } - this.applyExtensions(data, finalFragment, this.compress, (err, data) => { + this.extensions[PerMessageDeflate.extensionName].compress(data, finalFragment, (err, data) => { if (err) { if (cb) cb(err); else this.onerror(err); @@ -258,19 +262,6 @@ class Sender { this.messageHandlers.push(params); this.flush(); } - - /** - * Apply extensions to message - * - * @api private - */ - applyExtensions (data, fin, compress, callback) { - if (compress && data) { - this.extensions[PerMessageDeflate.extensionName].compress(data, fin, callback); - } else { - callback(null, data); - } - } } module.exports = Sender; diff --git a/test/Sender.test.js b/test/Sender.test.js index acb16918a..651824487 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -76,6 +76,48 @@ describe('Sender', function () { sender.send('hi', { compress: true }); }); + it('compresses all frames in a fragmented message', function (done) { + let messageCount = 0; + let messageLength = 16; + const perMessageDeflate = new PerMessageDeflate({ threshold: 2 }); + const sender = new Sender({ + write: (data) => { + assert.strictEqual(data.length, messageLength); + messageCount++; + if (messageCount > 1) return done(); + messageLength = 11; + } + }, { + 'permessage-deflate': perMessageDeflate + }); + + perMessageDeflate.accept([{}]); + + sender.send('123', { compress: true, fin: false }); + sender.send('12', { compress: true, fin: true }); + }); + + it('compresses no frames in a fragmented message', function (done) { + let messageCount = 0; + let messageLength = 4; + const perMessageDeflate = new PerMessageDeflate({ threshold: 2 }); + const sender = new Sender({ + write: (data) => { + assert.strictEqual(data.length, messageLength); + messageCount++; + if (messageCount > 1) return done(); + messageLength = 5; + } + }, { + 'permessage-deflate': perMessageDeflate + }); + + perMessageDeflate.accept([{}]); + + sender.send('12', { compress: true, fin: false }); + sender.send('123', { compress: true, fin: true }); + }); + it('Should be able to handle many send calls while processing without crashing on flush', function (done) { const maxMessages = 5000; let messageCount = 0; From 16bd0c1ceb71ab7507905d6ea17dbb3ba7d84eeb Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Fri, 4 Nov 2016 19:23:18 +0100 Subject: [PATCH 151/669] Fix sending empty fragment --- lib/Sender.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index ae62b9f64..f39fa97a0 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -149,8 +149,8 @@ class Sender { * @api private */ sendCompressed (opcode, data, finalFragment, mask, compress, cb) { - if (!this.compress) { - this.frameAndSend(opcode, data, finalFragment, mask, false, cb); + if (!this.compress || !data) { + this.frameAndSend(opcode, data, finalFragment, mask, compress, cb); this.messageHandlerCallback(); return; } From e4514483989c5e1706a6618dfb30f3f66a690be2 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 4 Nov 2016 17:43:24 +0100 Subject: [PATCH 152/669] [codestyle] Update JSDoc comments in lib/Sender.js --- lib/Sender.js | 106 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 82 insertions(+), 24 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index f39fa97a0..5f33e3f5e 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -11,9 +11,15 @@ const bufferUtil = require('./BufferUtil').BufferUtil; const PerMessageDeflate = require('./PerMessageDeflate'); /** - * HyBi Sender implementation, Inherits from EventEmitter. + * HyBi Sender implementation. */ class Sender { + /** + * Creates a Sender instance. + * + * @param {net.Socket} socket The connection socket + * @param {Object} extensions An object containing the negotiated extensions + */ constructor (socket, extensions) { this._socket = socket; this.extensions = extensions || {}; @@ -25,9 +31,13 @@ class Sender { } /** - * Sends a close instruction to the remote party. + * Sends a close message to the other peer. * - * @api public + * @param {(Number|undefined)} code The status code component of the body + * @param {String} data The message component of the body + * @param {Boolean} mask Specifies whether or not to mask the message + * @param {Function} cb Callback + * @public */ close (code, data, mask, cb) { if (code !== undefined && (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code))) { @@ -46,9 +56,12 @@ class Sender { } /** - * Sends a close frame. + * Frames and sends a close message. * - * @api private + * @param {Buffer} data The message to send + * @param {Boolean} mask Specifies whether or not to mask `data` + * @param {Function} cb Callback + * @private */ doClose (data, mask, cb) { this.frameAndSend(0x8, data, true, mask); @@ -59,9 +72,12 @@ class Sender { } /** - * Sends a ping message to the remote party. + * Sends a ping message to the other peer. * - * @api public + * @param {*} data The message to send + * @param {Object} options Options object + * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @public */ ping (data, options) { if (data) data = toBuffer(data); @@ -73,9 +89,12 @@ class Sender { } /** - * Sends a ping frame. + * Frames and sends a ping message. * - * @api private + * @param {Buffer} data The message to send + * @param {Object} options Options object + * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @private */ doPing (data, options) { this.frameAndSend(0x9, data, true, options.mask); @@ -85,9 +104,12 @@ class Sender { } /** - * Sends a pong message to the remote party. + * Sends a pong message to the other peer. * - * @api public + * @param {*} data The message to send + * @param {Object} options Options object + * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @public */ pong (data, options) { if (data) data = toBuffer(data); @@ -99,9 +121,12 @@ class Sender { } /** - * Sends a pong frame. + * Frames and sends a pong message. * - * @api private + * @param {Buffer} data The message to send + * @param {Object} options Options object + * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @private */ doPong (data, options) { this.frameAndSend(0xa, data, true, options.mask); @@ -111,9 +136,15 @@ class Sender { } /** - * Sends text or binary data to the remote party. + * Sends a data message to the other peer. * - * @api public + * @param {*} data The message to send + * @param {Object} options Options object + * @param {Boolean} options.compress Specifies whether or not to compress `data` + * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @param {Boolean} options.fin Specifies whether the fragment is the last one + * @param {Function} cb Callback + * @public */ send (data, options, cb) { var finalFragment = !options || options.fin !== false; @@ -144,9 +175,15 @@ class Sender { } /** - * Sends compressed data. + * Compresses, frames and sends a data message. * - * @api private + * @param {Number} opcode The opcode + * @param {Buffer} data The message to send + * @param {Boolean} finalFragment Specifies whether or not to set the FIN bit + * @param {Boolean} mask Specifies whether or not to mask `data` + * @param {Boolean} compress Specifies whether or not to set the RSV1 bit + * @param {Function} cb Callback + * @private */ sendCompressed (opcode, data, finalFragment, mask, compress, cb) { if (!this.compress || !data) { @@ -168,7 +205,13 @@ class Sender { /** * Frames and sends a piece of data according to the HyBi WebSocket protocol. * - * @api private + * @param {Number} opcode The opcode + * @param {Buffer} data The data to send + * @param {Boolean} finalFragment Specifies whether or not to set the FIN bit + * @param {Boolean} maskData Specifies whether or not to mask `data` + * @param {Boolean} compressed Specifies whether or not to set the RSV1 bit + * @param {Function} cb Callback + * @private */ frameAndSend (opcode, data, finalFragment, maskData, compressed, cb) { if (!data) { @@ -228,9 +271,9 @@ class Sender { } /** - * Execute message handler buffers + * Executes a queued send operation. * - * @api private + * @private */ flush () { if (this.processing) return; @@ -244,9 +287,9 @@ class Sender { } /** - * Callback to indicate message handler completion. + * Signals the completion of a send operation. * - * @api private + * @private */ messageHandlerCallback () { this.processing = false; @@ -254,9 +297,9 @@ class Sender { } /** - * Enqueues a send frame operation. + * Enqueues a send operation. * - * @api private + * @private */ enqueue (params) { this.messageHandlers.push(params); @@ -291,6 +334,12 @@ function toBuffer (data) { return Buffer.from(typeof data === 'number' ? data.toString() : data); } +/** + * Generates a random mask. + * + * @return {Buffer} The mask + * @private + */ function getRandomMask () { return new Buffer([ ~~(Math.random() * 255), @@ -300,6 +349,15 @@ function getRandomMask () { ]); } +/** + * Sends a frame. + * + * @param {Sender} sender Sender instance + * @param {Buffer} outputBuffer The data to send + * @param {Buffer} data Additional data to send if frame is split into two buffers + * @param {Function} cb Callback + * @private + */ function sendFramedData (sender, outputBuffer, data, cb) { try { if (data) { From 9823f710f95eea0a19ea674f74a98361b040f161 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Fri, 4 Nov 2016 19:50:07 +0100 Subject: [PATCH 153/669] Remove unneeded `encoding` argument to `socket.write` --- lib/Sender.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 5f33e3f5e..0c70ee156 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -361,10 +361,10 @@ function getRandomMask () { function sendFramedData (sender, outputBuffer, data, cb) { try { if (data) { - sender._socket.write(outputBuffer, 'binary'); - sender._socket.write(data, 'binary', cb); + sender._socket.write(outputBuffer); + sender._socket.write(data, cb); } else { - sender._socket.write(outputBuffer, 'binary', cb); + sender._socket.write(outputBuffer, cb); } } catch (e) { if (cb) cb(e); From 2dd55269bea2047ecc33829fc3a28a7ef189506e Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Fri, 4 Nov 2016 20:54:34 +0100 Subject: [PATCH 154/669] Allow compressed empty first fragment --- lib/Sender.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 0c70ee156..2f7113030 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -159,8 +159,8 @@ class Sender { compress = false; } else { this.firstFragment = false; - if (compress && this.extensions[PerMessageDeflate.extensionName]) { - compress = data && data.length > this.extensions[PerMessageDeflate.extensionName].threshold; + if (compress && data && this.extensions[PerMessageDeflate.extensionName]) { + compress = data.length > this.extensions[PerMessageDeflate.extensionName].threshold; } this.compress = compress; } From 87904d7b0362d3574b8ef21336ad3cc22ff73611 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Fri, 4 Nov 2016 21:19:46 +0100 Subject: [PATCH 155/669] Fix sending empty compressed fragments --- lib/PerMessageDeflate.js | 5 +++++ lib/Sender.js | 4 ++-- test/Sender.test.js | 42 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 397377b91..03dded810 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -6,6 +6,7 @@ const AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15]; const DEFAULT_WINDOW_BITS = 15; const DEFAULT_MEM_LEVEL = 8; const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); +const EMPTY_BLOCK = Buffer.from([0x00]); /** * Per-message Compression Extensions implementation @@ -282,6 +283,10 @@ class PerMessageDeflate { */ compress (data, fin, callback) { + if (!data) { + return callback(null, EMPTY_BLOCK); + } + var endpoint = this._isServer ? 'server' : 'client'; if (!this._deflate) { diff --git a/lib/Sender.js b/lib/Sender.js index 2f7113030..b4637d10a 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -186,8 +186,8 @@ class Sender { * @private */ sendCompressed (opcode, data, finalFragment, mask, compress, cb) { - if (!this.compress || !data) { - this.frameAndSend(opcode, data, finalFragment, mask, compress, cb); + if (!this.compress) { + this.frameAndSend(opcode, data, finalFragment, mask, false, cb); this.messageHandlerCallback(); return; } diff --git a/test/Sender.test.js b/test/Sender.test.js index 651824487..1e2fb4196 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -118,6 +118,48 @@ describe('Sender', function () { sender.send('123', { compress: true, fin: true }); }); + it('compresses empty first fragment', function (done) { + let messageCount = 0; + let messageLength = 3; + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const sender = new Sender({ + write: (data) => { + assert.strictEqual(data.length, messageLength); + messageCount++; + if (messageCount > 1) return done(); + messageLength = 13; + } + }, { + 'permessage-deflate': perMessageDeflate + }); + + perMessageDeflate.accept([{}]); + + sender.send(null, { compress: true, fin: false }); + sender.send('data', { compress: true, fin: true }); + }); + + it('compresses empty last fragment', function (done) { + let messageCount = 0; + let messageLength = 17; + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const sender = new Sender({ + write: (data) => { + assert.strictEqual(data.length, messageLength); + messageCount++; + if (messageCount > 1) return done(); + messageLength = 3; + } + }, { + 'permessage-deflate': perMessageDeflate + }); + + perMessageDeflate.accept([{}]); + + sender.send('data', { compress: true, fin: false }); + sender.send(null, { compress: true, fin: true }); + }); + it('Should be able to handle many send calls while processing without crashing on flush', function (done) { const maxMessages = 5000; let messageCount = 0; From 8d0cdb6fb269b40b40b12c5ef19f6a28e3feba4b Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Fri, 4 Nov 2016 21:44:47 +0100 Subject: [PATCH 156/669] Support sending an empty buffer as a compressed fragment --- lib/PerMessageDeflate.js | 2 +- lib/Sender.js | 2 +- test/Sender.test.js | 51 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 03dded810..83bb85065 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -283,7 +283,7 @@ class PerMessageDeflate { */ compress (data, fin, callback) { - if (!data) { + if (!data || data.length === 0) { return callback(null, EMPTY_BLOCK); } diff --git a/lib/Sender.js b/lib/Sender.js index b4637d10a..58b4a4a1b 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -160,7 +160,7 @@ class Sender { } else { this.firstFragment = false; if (compress && data && this.extensions[PerMessageDeflate.extensionName]) { - compress = data.length > this.extensions[PerMessageDeflate.extensionName].threshold; + compress = data.length >= this.extensions[PerMessageDeflate.extensionName].threshold; } this.compress = compress; } diff --git a/test/Sender.test.js b/test/Sender.test.js index 1e2fb4196..cb49a86d7 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -79,7 +79,7 @@ describe('Sender', function () { it('compresses all frames in a fragmented message', function (done) { let messageCount = 0; let messageLength = 16; - const perMessageDeflate = new PerMessageDeflate({ threshold: 2 }); + const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); const sender = new Sender({ write: (data) => { assert.strictEqual(data.length, messageLength); @@ -100,7 +100,7 @@ describe('Sender', function () { it('compresses no frames in a fragmented message', function (done) { let messageCount = 0; let messageLength = 4; - const perMessageDeflate = new PerMessageDeflate({ threshold: 2 }); + const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); const sender = new Sender({ write: (data) => { assert.strictEqual(data.length, messageLength); @@ -118,7 +118,7 @@ describe('Sender', function () { sender.send('123', { compress: true, fin: true }); }); - it('compresses empty first fragment', function (done) { + it('compresses null as first fragment', function (done) { let messageCount = 0; let messageLength = 3; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); @@ -139,7 +139,29 @@ describe('Sender', function () { sender.send('data', { compress: true, fin: true }); }); - it('compresses empty last fragment', function (done) { + it('compresses empty buffer as first fragment', function (done) { + let messageCount = 0; + // let messageLength = 3; + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const sender = new Sender({ + write: (data) => { + console.log(data); + // assert.strictEqual(data.length, messageLength); + messageCount++; + if (messageCount > 1) return done(); + // messageLength = 13; + } + }, { + 'permessage-deflate': perMessageDeflate + }); + + perMessageDeflate.accept([{}]); + + sender.send(Buffer.alloc(0), { compress: true, fin: false }); + sender.send('data', { compress: true, fin: true }); + }); + + it('compresses null last fragment', function (done) { let messageCount = 0; let messageLength = 17; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); @@ -160,6 +182,27 @@ describe('Sender', function () { sender.send(null, { compress: true, fin: true }); }); + it('compresses empty buffer as last fragment', function (done) { + let messageCount = 0; + let messageLength = 17; + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const sender = new Sender({ + write: (data) => { + assert.strictEqual(data.length, messageLength); + messageCount++; + if (messageCount > 1) return done(); + messageLength = 3; + } + }, { + 'permessage-deflate': perMessageDeflate + }); + + perMessageDeflate.accept([{}]); + + sender.send('data', { compress: true, fin: false }); + sender.send(Buffer.alloc(0), { compress: true, fin: true }); + }); + it('Should be able to handle many send calls while processing without crashing on flush', function (done) { const maxMessages = 5000; let messageCount = 0; From 158c6725f92f35cef527874273d8f3e92697def3 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Fri, 4 Nov 2016 21:46:37 +0100 Subject: [PATCH 157/669] Uncomment test --- test/Sender.test.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/Sender.test.js b/test/Sender.test.js index cb49a86d7..f2384e38c 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -141,15 +141,14 @@ describe('Sender', function () { it('compresses empty buffer as first fragment', function (done) { let messageCount = 0; - // let messageLength = 3; + let messageLength = 3; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const sender = new Sender({ write: (data) => { - console.log(data); - // assert.strictEqual(data.length, messageLength); + assert.strictEqual(data.length, messageLength); messageCount++; if (messageCount > 1) return done(); - // messageLength = 13; + messageLength = 13; } }, { 'permessage-deflate': perMessageDeflate From dd9c973e06592183fb38726f34afd5f9f3be68be Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Sat, 5 Nov 2016 03:02:01 +0100 Subject: [PATCH 158/669] Use noAssert when writing to buffers --- lib/Sender.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 58b4a4a1b..f1beff458 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -45,7 +45,7 @@ class Sender { } code = code || 1000; var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0)); - dataBuffer.writeUInt16BE(code, 0); + dataBuffer.writeUInt16BE(code, 0, true); if (dataBuffer.length > 2) dataBuffer.write(data, 2); if (this.extensions[PerMessageDeflate.extensionName]) { @@ -242,11 +242,11 @@ class Sender { switch (secondByte) { case 126: - outputBuffer.writeUInt16BE(dataLength, 2); + outputBuffer.writeUInt16BE(dataLength, 2, true); break; case 127: - outputBuffer.writeUInt32BE(0, 2); - outputBuffer.writeUInt32BE(dataLength, 6); + outputBuffer.writeUInt32BE(0, 2, true); + outputBuffer.writeUInt32BE(dataLength, 6, true); } if (maskData) { From 6696543dde0cf364e6ce3d0a7ff9f36afb29b04b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 5 Nov 2016 07:27:51 +0100 Subject: [PATCH 159/669] [deps] Bump eslint to version 3.9.x --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 939c7de8c..a2e271bee 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "devDependencies": { "benchmark": "2.1.x", "bufferutil": "1.2.x", - "eslint": "3.8.x", + "eslint": "3.9.x", "eslint-config-semistandard": "7.0.x", "eslint-config-standard": "6.2.x", "eslint-plugin-promise": "3.3.x", From 8cbb5f5f70dc0a77bf0c71cefe40812a98644f3d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 5 Nov 2016 08:25:17 +0100 Subject: [PATCH 160/669] [test] Ensure that the RSV1 bit is set as intended --- test/Sender.test.js | 94 +++++++++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 38 deletions(-) diff --git a/test/Sender.test.js b/test/Sender.test.js index f2384e38c..30eddd42a 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -31,7 +31,7 @@ describe('Sender', function () { assert.ok(text.equals(Buffer.from('hi there'))); }); - it('sets rsv1 flag if compressed', function (done) { + it('sets RSV1 bit if compressed', function (done) { const sender = new Sender({ write: (data) => { assert.strictEqual(data[0] & 0x40, 0x40); @@ -77,15 +77,18 @@ describe('Sender', function () { }); it('compresses all frames in a fragmented message', function (done) { - let messageCount = 0; - let messageLength = 16; + const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); const sender = new Sender({ write: (data) => { - assert.strictEqual(data.length, messageLength); - messageCount++; - if (messageCount > 1) return done(); - messageLength = 11; + fragments.push(data); + if (fragments.length !== 2) return; + + assert.strictEqual(fragments[0][0] & 0x40, 0x40); + assert.strictEqual(fragments[0].length, 16); + assert.strictEqual(fragments[1][0] & 0x40, 0x00); + assert.strictEqual(fragments[1].length, 11); + done(); } }, { 'permessage-deflate': perMessageDeflate @@ -98,15 +101,18 @@ describe('Sender', function () { }); it('compresses no frames in a fragmented message', function (done) { - let messageCount = 0; - let messageLength = 4; + const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); const sender = new Sender({ write: (data) => { - assert.strictEqual(data.length, messageLength); - messageCount++; - if (messageCount > 1) return done(); - messageLength = 5; + fragments.push(data); + if (fragments.length !== 2) return; + + assert.strictEqual(fragments[0][0] & 0x40, 0x00); + assert.strictEqual(fragments[0].length, 4); + assert.strictEqual(fragments[1][0] & 0x40, 0x00); + assert.strictEqual(fragments[1].length, 5); + done(); } }, { 'permessage-deflate': perMessageDeflate @@ -119,15 +125,18 @@ describe('Sender', function () { }); it('compresses null as first fragment', function (done) { - let messageCount = 0; - let messageLength = 3; + const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const sender = new Sender({ write: (data) => { - assert.strictEqual(data.length, messageLength); - messageCount++; - if (messageCount > 1) return done(); - messageLength = 13; + fragments.push(data); + if (fragments.length !== 2) return; + + assert.strictEqual(fragments[0][0] & 0x40, 0x40); + assert.strictEqual(fragments[0].length, 3); + assert.strictEqual(fragments[1][0] & 0x40, 0x00); + assert.strictEqual(fragments[1].length, 13); + done(); } }, { 'permessage-deflate': perMessageDeflate @@ -140,15 +149,18 @@ describe('Sender', function () { }); it('compresses empty buffer as first fragment', function (done) { - let messageCount = 0; - let messageLength = 3; + const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const sender = new Sender({ write: (data) => { - assert.strictEqual(data.length, messageLength); - messageCount++; - if (messageCount > 1) return done(); - messageLength = 13; + fragments.push(data); + if (fragments.length !== 2) return; + + assert.strictEqual(fragments[0][0] & 0x40, 0x40); + assert.strictEqual(fragments[0].length, 3); + assert.strictEqual(fragments[1][0] & 0x40, 0x00); + assert.strictEqual(fragments[1].length, 13); + done(); } }, { 'permessage-deflate': perMessageDeflate @@ -161,15 +173,18 @@ describe('Sender', function () { }); it('compresses null last fragment', function (done) { - let messageCount = 0; - let messageLength = 17; + const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const sender = new Sender({ write: (data) => { - assert.strictEqual(data.length, messageLength); - messageCount++; - if (messageCount > 1) return done(); - messageLength = 3; + fragments.push(data); + if (fragments.length !== 2) return; + + assert.strictEqual(fragments[0][0] & 0x40, 0x40); + assert.strictEqual(fragments[0].length, 17); + assert.strictEqual(fragments[1][0] & 0x40, 0x00); + assert.strictEqual(fragments[1].length, 3); + done(); } }, { 'permessage-deflate': perMessageDeflate @@ -182,15 +197,18 @@ describe('Sender', function () { }); it('compresses empty buffer as last fragment', function (done) { - let messageCount = 0; - let messageLength = 17; + const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const sender = new Sender({ write: (data) => { - assert.strictEqual(data.length, messageLength); - messageCount++; - if (messageCount > 1) return done(); - messageLength = 3; + fragments.push(data); + if (fragments.length !== 2) return; + + assert.strictEqual(fragments[0][0] & 0x40, 0x40); + assert.strictEqual(fragments[0].length, 17); + assert.strictEqual(fragments[1][0] & 0x40, 0x00); + assert.strictEqual(fragments[1].length, 3); + done(); } }, { 'permessage-deflate': perMessageDeflate @@ -202,7 +220,7 @@ describe('Sender', function () { sender.send(Buffer.alloc(0), { compress: true, fin: true }); }); - it('Should be able to handle many send calls while processing without crashing on flush', function (done) { + it('handles many send calls while processing without crashing on flush', function (done) { const maxMessages = 5000; let messageCount = 0; From 72a4bda5bc144b037702dfcea84dd58223f60cb0 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 5 Nov 2016 09:42:14 +0100 Subject: [PATCH 161/669] [fix] Reset the `processing` flag inside the `nextTick` callback --- lib/Sender.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index f1beff458..4b212365e 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -292,8 +292,10 @@ class Sender { * @private */ messageHandlerCallback () { - this.processing = false; - process.nextTick(() => this.flush()); + process.nextTick(() => { + this.processing = false; + this.flush(); + }); } /** From ebf86b58f1fb1e96ab96ebf95782bad7bc5ab8ca Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 6 Nov 2016 12:00:52 +0100 Subject: [PATCH 162/669] [minor] Remove redundant checks --- lib/Sender.js | 29 ++++++++++++++--------------- test/testserver.js | 17 +++++------------ 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 4b212365e..c5849f02b 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -140,6 +140,7 @@ class Sender { * * @param {*} data The message to send * @param {Object} options Options object + * @param {Boolean} options.binary Specifies whether `data` is binary or text * @param {Boolean} options.compress Specifies whether or not to compress `data` * @param {Boolean} options.mask Specifies whether or not to mask `data` * @param {Boolean} options.fin Specifies whether the fragment is the last one @@ -147,30 +148,28 @@ class Sender { * @public */ send (data, options, cb) { - var finalFragment = !options || options.fin !== false; - var mask = options && options.mask; - var compress = options && options.compress; - var opcode = options && options.binary ? 2 : 1; + const pmd = this.extensions[PerMessageDeflate.extensionName]; + var opcode = options.binary ? 2 : 1; + var compress = options.compress; if (data) data = toBuffer(data); - if (this.firstFragment === false) { - opcode = 0; - compress = false; - } else { + if (this.firstFragment) { this.firstFragment = false; - if (compress && data && this.extensions[PerMessageDeflate.extensionName]) { - compress = data.length >= this.extensions[PerMessageDeflate.extensionName].threshold; - } + if (compress && data && pmd) compress = data.length >= pmd.threshold; this.compress = compress; + } else { + compress = false; + opcode = 0; } - if (finalFragment) this.firstFragment = true; + if (options.fin) this.firstFragment = true; - if (this.extensions[PerMessageDeflate.extensionName]) { - this.enqueue([this.sendCompressed, [opcode, data, finalFragment, mask, compress, cb]]); + if (pmd) { + const args = [opcode, data, options.fin, options.mask, compress, cb]; + this.enqueue([this.sendCompressed, args]); } else { - this.frameAndSend(opcode, data, finalFragment, mask, false, cb); + this.frameAndSend(opcode, data, options.fin, options.mask, false, cb); } } diff --git a/test/testserver.js b/test/testserver.js index f68c4c32e..48e1e287d 100644 --- a/test/testserver.js +++ b/test/testserver.js @@ -60,31 +60,24 @@ function validServer (server, req, socket) { const sender = new Sender(socket); const receiver = new Receiver(); + receiver.onping = (message, flags) => server.emit('ping', message, flags); + receiver.onpong = (message, flags) => server.emit('pong', message, flags); receiver.ontext = (message, flags) => { server.emit('message', message, flags); - sender.send(message); + sender.send(message, { fin: true }); }; receiver.onbinary = (message, flags) => { - flags = flags || {}; flags.binary = true; server.emit('message', message, flags); - sender.send(message, { binary: true }); - }; - receiver.onping = (message, flags) => { - flags = flags || {}; - server.emit('ping', message, flags); - }; - receiver.onpong = (message, flags) => { - flags = flags || {}; - server.emit('pong', message, flags); + sender.send(message, { binary: true, fin: true }); }; receiver.onclose = (code, message, flags) => { - flags = flags || {}; sender.close(code, message, false, () => { server.emit('close', code, message, flags); socket.end(); }); }; + socket.on('data', (data) => receiver.add(data)); socket.on('end', () => socket.end()); } From 2fc1965e63008f0d65301ba1ee923ddc4c3d1415 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 6 Nov 2016 20:10:53 +0100 Subject: [PATCH 163/669] [major] Remove path registry --- lib/WebSocketServer.js | 17 ---------- test/WebSocketServer.test.js | 63 ------------------------------------ 2 files changed, 80 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 10eae03f2..8c9a2eeb5 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -67,15 +67,6 @@ function WebSocketServer (options, callback) { this._closeServer = () => this._server && this._server.close(); } else if (options.server) { this._server = options.server; - if (options.path) { - // take note of the path, to avoid collisions when multiple websocket servers are - // listening on the same http server - if (this._server._webSocketPaths && options.server._webSocketPaths[options.path]) { - throw new Error('two instances of WebSocketServer cannot listen on the same http server path'); - } - if (!this._server._webSocketPaths) this._server._webSocketPaths = {}; - this._server._webSocketPaths[options.path] = 1; - } } if (this._server) { @@ -127,14 +118,6 @@ WebSocketServer.prototype.close = function (callback) { } } - // remove path descriptor, if any - if (this.path && this._server._webSocketPaths) { - delete this._server._webSocketPaths[this.path]; - if (Object.keys(this._server._webSocketPaths).length === 0) { - delete this._server._webSocketPaths; - } - } - // close the http server if it was internally created try { if (this._closeServer !== undefined) { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index b74a28afd..1da15738c 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -109,48 +109,6 @@ describe('WebSocketServer', function () { }); }); - it('can have two different instances listening on the same http server with two different paths', function (done) { - const server = http.createServer(); - - server.listen(++port, () => { - const wss1 = new WebSocketServer({ server, path: '/wss1' }); - const wss2 = new WebSocketServer({ server, path: '/wss2' }); - let doneCount = 0; - - wss1.on('connection', (client) => { - wss1.close(); - if (++doneCount === 2) { - server.close(done); - } - }); - - wss2.on('connection', (client) => { - wss2.close(); - if (++doneCount === 2) { - server.close(done); - } - }); - - /* eslint-disable no-unused-vars */ - const ws1 = new WebSocket(`ws://localhost:${port}/wss1`); - const ws2 = new WebSocket(`ws://localhost:${port}/wss2?foo=1`); - /* eslint-enable no-unused-vars */ - }); - }); - - it('cannot have two different instances listening on the same http server with the same path', function (done) { - const server = http.createServer(); - const wss1 = new WebSocketServer({ server: server, path: '/wss1' }); - - try { - // eslint-disable-next-line no-unused-vars - const wss2 = new WebSocketServer({ server: server, path: '/wss1' }); - } catch (e) { - wss1.close(); - done(); - } - }); - it('will not crash when it receives an unhandled opcode', function (done) { const wss = new WebSocketServer({ port: 8080 }); @@ -229,27 +187,6 @@ describe('WebSocketServer', function () { server.close(done); }); }); - - it('cleans up websocket data on a precreated server', function (done) { - const server = http.createServer(); - - server.listen(++port, () => { - const wss1 = new WebSocketServer({ server, path: '/wss1' }); - const wss2 = new WebSocketServer({ server, path: '/wss2' }); - - assert.strictEqual(typeof server._webSocketPaths, 'object'); - assert.strictEqual(Object.keys(server._webSocketPaths).length, 2); - - wss1.close(); - - assert.strictEqual(Object.keys(server._webSocketPaths).length, 1); - - wss2.close(); - - assert.strictEqual(typeof server._webSocketPaths, 'undefined'); - server.close(done); - }); - }); }); describe('#clients', function () { From 5cfe70ea1dc656f77bd43bfccd2c48b5db839d8a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 7 Nov 2016 17:09:08 +0100 Subject: [PATCH 164/669] [minor] Avoid copying buffers when possible --- bench/sender.benchmark.js | 29 +++--- lib/Sender.js | 186 +++++++++++++++++++++----------------- test/Sender.test.js | 36 ++++---- 3 files changed, 141 insertions(+), 110 deletions(-) diff --git a/bench/sender.benchmark.js b/bench/sender.benchmark.js index 11e856a33..8473e561d 100644 --- a/bench/sender.benchmark.js +++ b/bench/sender.benchmark.js @@ -7,25 +7,32 @@ 'use strict'; const benchmark = require('benchmark'); +const crypto = require('crypto'); const Sender = require('../').Sender; -const data1 = Buffer.alloc(200 * 1024, 99); -const data2 = Buffer.alloc(1024 * 1024, 99); +const data1 = crypto.randomBytes(64); +const data2 = crypto.randomBytes(16 * 1024); +const data3 = crypto.randomBytes(64 * 1024); +const data4 = crypto.randomBytes(200 * 1024); +const data5 = crypto.randomBytes(1024 * 1024); const suite = new benchmark.Suite(); var sender = new Sender(); sender._socket = { write () {} }; -suite.add('frameAndSend, unmasked (200 KiB)', () => sender.frameAndSend(0x2, data1, true, false)); -suite.add('frameAndSend, masked (200 KiB)', () => sender.frameAndSend(0x2, data1, true, true)); -suite.add('frameAndSend, unmasked (1 MiB)', () => sender.frameAndSend(0x2, data2, true, false)); -suite.add('frameAndSend, masked (1 MiB)', () => sender.frameAndSend(0x2, data2, true, true)); -suite.on('cycle', (e) => { - console.log(e.target.toString()); - sender = new Sender(); - sender._socket = { write () {} }; -}); +suite.add('frameAndSend, unmasked (64 B)', () => sender.frameAndSend(0x2, data1, false, true, false)); +suite.add('frameAndSend, masked (64 B)', () => sender.frameAndSend(0x2, data1, true, true, true)); +suite.add('frameAndSend, unmasked (16 KiB)', () => sender.frameAndSend(0x2, data2, false, true, false)); +suite.add('frameAndSend, masked (16 KiB)', () => sender.frameAndSend(0x2, data2, true, true, true)); +suite.add('frameAndSend, unmasked (64 KiB)', () => sender.frameAndSend(0x2, data3, false, true, false)); +suite.add('frameAndSend, masked (64 KiB)', () => sender.frameAndSend(0x2, data3, true, true, true)); +suite.add('frameAndSend, unmasked (200 KiB)', () => sender.frameAndSend(0x2, data4, false, true, false)); +suite.add('frameAndSend, masked (200 KiB)', () => sender.frameAndSend(0x2, data4, true, true, true)); +suite.add('frameAndSend, unmasked (1 MiB)', () => sender.frameAndSend(0x2, data5, false, true, false)); +suite.add('frameAndSend, masked (1 MiB)', () => sender.frameAndSend(0x2, data5, true, true, true)); + +suite.on('cycle', (e) => console.log(e.target.toString())); if (require.main === module) { suite.run({ async: true }); diff --git a/lib/Sender.js b/lib/Sender.js index c5849f02b..de6fda611 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -43,15 +43,16 @@ class Sender { if (code !== undefined && (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code))) { throw new Error('first argument must be a valid error code number'); } - code = code || 1000; - var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0)); - dataBuffer.writeUInt16BE(code, 0, true); - if (dataBuffer.length > 2) dataBuffer.write(data, 2); + + const buf = Buffer.allocUnsafe(2 + (data ? Buffer.byteLength(data) : 0)); + + buf.writeUInt16BE(code || 1000, 0, true); + if (buf.length > 2) buf.write(data, 2); if (this.extensions[PerMessageDeflate.extensionName]) { - this.enqueue([this.doClose, [dataBuffer, mask, cb]]); + this.enqueue([this.doClose, [buf, mask, cb]]); } else { - this.doClose(dataBuffer, mask, cb); + this.doClose(buf, mask, cb); } } @@ -64,7 +65,7 @@ class Sender { * @private */ doClose (data, mask, cb) { - this.frameAndSend(0x8, data, true, mask); + this.frameAndSend(0x08, data, false, true, mask, false); if (this.extensions[PerMessageDeflate.extensionName]) { this.messageHandlerCallback(); } @@ -80,24 +81,22 @@ class Sender { * @public */ ping (data, options) { - if (data) data = toBuffer(data); if (this.extensions[PerMessageDeflate.extensionName]) { - this.enqueue([this.doPing, [data, options]]); + this.enqueue([this.doPing, [data, options.mask]]); } else { - this.doPing(data, options); + this.doPing(data, options.mask); } } /** * Frames and sends a ping message. * - * @param {Buffer} data The message to send - * @param {Object} options Options object - * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @param {*} data The message to send + * @param {Boolean} mask Specifies whether or not to mask `data` * @private */ - doPing (data, options) { - this.frameAndSend(0x9, data, true, options.mask); + doPing (data, mask) { + this.frameAndSend(0x09, data, true, true, mask, false); if (this.extensions[PerMessageDeflate.extensionName]) { this.messageHandlerCallback(); } @@ -112,24 +111,22 @@ class Sender { * @public */ pong (data, options) { - if (data) data = toBuffer(data); if (this.extensions[PerMessageDeflate.extensionName]) { - this.enqueue([this.doPong, [data, options]]); + this.enqueue([this.doPong, [data, options.mask]]); } else { - this.doPong(data, options); + this.doPong(data, options.mask); } } /** * Frames and sends a pong message. * - * @param {Buffer} data The message to send - * @param {Object} options Options object - * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @param {*} data The message to send + * @param {Boolean} mask Specifies whether or not to mask `data` * @private */ - doPong (data, options) { - this.frameAndSend(0xa, data, true, options.mask); + doPong (data, mask) { + this.frameAndSend(0x0a, data, true, true, mask, false); if (this.extensions[PerMessageDeflate.extensionName]) { this.messageHandlerCallback(); } @@ -140,10 +137,10 @@ class Sender { * * @param {*} data The message to send * @param {Object} options Options object - * @param {Boolean} options.binary Specifies whether `data` is binary or text * @param {Boolean} options.compress Specifies whether or not to compress `data` - * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @param {Boolean} options.binary Specifies whether `data` is binary or text * @param {Boolean} options.fin Specifies whether the fragment is the last one + * @param {Boolean} options.mask Specifies whether or not to mask `data` * @param {Function} cb Callback * @public */ @@ -152,8 +149,6 @@ class Sender { var opcode = options.binary ? 2 : 1; var compress = options.compress; - if (data) data = toBuffer(data); - if (this.firstFragment) { this.firstFragment = false; if (compress && data && pmd) compress = data.length >= pmd.threshold; @@ -169,7 +164,7 @@ class Sender { const args = [opcode, data, options.fin, options.mask, compress, cb]; this.enqueue([this.sendCompressed, args]); } else { - this.frameAndSend(opcode, data, options.fin, options.mask, false, cb); + this.frameAndSend(opcode, data, true, options.fin, options.mask, false, cb); } } @@ -177,26 +172,29 @@ class Sender { * Compresses, frames and sends a data message. * * @param {Number} opcode The opcode - * @param {Buffer} data The message to send - * @param {Boolean} finalFragment Specifies whether or not to set the FIN bit + * @param {*} data The message to send + * @param {Boolean} fin Specifies whether or not to set the FIN bit * @param {Boolean} mask Specifies whether or not to mask `data` - * @param {Boolean} compress Specifies whether or not to set the RSV1 bit + * @param {Boolean} rsv1 Specifies whether or not to set the RSV1 bit * @param {Function} cb Callback * @private */ - sendCompressed (opcode, data, finalFragment, mask, compress, cb) { + sendCompressed (opcode, data, fin, mask, rsv1, cb) { if (!this.compress) { - this.frameAndSend(opcode, data, finalFragment, mask, false, cb); + this.frameAndSend(opcode, data, true, fin, mask, false, cb); this.messageHandlerCallback(); return; } - this.extensions[PerMessageDeflate.extensionName].compress(data, finalFragment, (err, data) => { + + if (data && !Buffer.isBuffer(data)) data = toBuffer(data); + this.extensions[PerMessageDeflate.extensionName].compress(data, fin, (err, buf) => { if (err) { if (cb) cb(err); else this.onerror(err); return; } - this.frameAndSend(opcode, data, finalFragment, mask, compress, cb); + + this.frameAndSend(opcode, buf, false, fin, mask, rsv1, cb); this.messageHandlerCallback(); }); } @@ -205,67 +203,84 @@ class Sender { * Frames and sends a piece of data according to the HyBi WebSocket protocol. * * @param {Number} opcode The opcode - * @param {Buffer} data The data to send - * @param {Boolean} finalFragment Specifies whether or not to set the FIN bit + * @param {*} data The data to send + * @param {Boolean} readOnly Specifies whether `data` can be modified + * @param {Boolean} fin Specifies whether or not to set the FIN bit * @param {Boolean} maskData Specifies whether or not to mask `data` - * @param {Boolean} compressed Specifies whether or not to set the RSV1 bit + * @param {Boolean} rsv1 Specifies whether or not to set the RSV1 bit * @param {Function} cb Callback * @private */ - frameAndSend (opcode, data, finalFragment, maskData, compressed, cb) { + frameAndSend (opcode, data, readOnly, fin, maskData, rsv1, cb) { if (!data) { - var buff = [opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)] - .concat(maskData ? [0, 0, 0, 0] : []); - sendFramedData(this, new Buffer(buff), null, cb); + const bytes = [opcode, 0]; + + if (fin) bytes[0] |= 0x80; + if (maskData) { + bytes[1] |= 0x80; + bytes.push(0, 0, 0, 0); + } + + sendFramedData(this, Buffer.from(bytes), null, cb); return; } - var dataLength = data.length; + if (!Buffer.isBuffer(data)) { + if (data instanceof ArrayBuffer) { + data = Buffer.from(data); + } else if (ArrayBuffer.isView(data)) { + data = viewToBuffer(data); + } else { + data = Buffer.from(typeof data === 'number' ? data.toString() : data); + readOnly = false; + } + } + + const mergeBuffers = data.length < 1024 || maskData && readOnly; var dataOffset = maskData ? 6 : 2; - var secondByte = dataLength; + var payloadLength = data.length; - if (dataLength >= 65536) { + if (data.length >= 65536) { dataOffset += 8; - secondByte = 127; - } else if (dataLength > 125) { + payloadLength = 127; + } else if (data.length > 125) { dataOffset += 2; - secondByte = 126; + payloadLength = 126; } - var canModifyData = compressed; - var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData); - var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset; - var outputBuffer = new Buffer(totalLength); - outputBuffer[0] = finalFragment ? opcode | 0x80 : opcode; - if (compressed) outputBuffer[0] |= 0x40; - - switch (secondByte) { - case 126: - outputBuffer.writeUInt16BE(dataLength, 2, true); - break; - case 127: - outputBuffer.writeUInt32BE(0, 2, true); - outputBuffer.writeUInt32BE(dataLength, 6, true); + const outputBuffer = Buffer.allocUnsafe( + mergeBuffers ? data.length + dataOffset : dataOffset + ); + + outputBuffer[0] = fin ? opcode | 0x80 : opcode; + if (rsv1) outputBuffer[0] |= 0x40; + + if (payloadLength === 126) { + outputBuffer.writeUInt16BE(data.length, 2, true); + } else if (payloadLength === 127) { + outputBuffer.writeUInt32BE(0, 2, true); + outputBuffer.writeUInt32BE(data.length, 6, true); } if (maskData) { - outputBuffer[1] = secondByte | 0x80; - var mask = getRandomMask(); + const mask = getRandomMask(); + + outputBuffer[1] = payloadLength | 0x80; outputBuffer[dataOffset - 4] = mask[0]; outputBuffer[dataOffset - 3] = mask[1]; outputBuffer[dataOffset - 2] = mask[2]; outputBuffer[dataOffset - 1] = mask[3]; + if (mergeBuffers) { - bufferUtil.mask(data, mask, outputBuffer, dataOffset, dataLength); + bufferUtil.mask(data, mask, outputBuffer, dataOffset, data.length); } else { - bufferUtil.mask(data, mask, data, 0, dataLength); + bufferUtil.mask(data, mask, data, 0, data.length); } } else { - outputBuffer[1] = secondByte; - if (mergeBuffers) { - data.copy(outputBuffer, dataOffset); - } + outputBuffer[1] = payloadLength; + if (mergeBuffers) data.copy(outputBuffer, dataOffset); } + sendFramedData(this, outputBuffer, mergeBuffers ? null : data, cb); } @@ -300,6 +315,7 @@ class Sender { /** * Enqueues a send operation. * + * @param {Array} params Send operation parameters. * @private */ enqueue (params) { @@ -310,6 +326,23 @@ class Sender { module.exports = Sender; +/** + * Converts an `ArrayBuffer` view into a buffer. + * + * @param {(DataView|TypedArray)} view The view to convert + * @return {Buffer} Converted view + * @private + */ +function viewToBuffer (view) { + const buf = Buffer.from(view.buffer); + + if (view.byteLength !== view.buffer.byteLength) { + return buf.slice(view.byteOffset, view.byteOffset + view.byteLength); + } + + return buf; +} + /** * Converts `data` into a buffer. * @@ -318,19 +351,8 @@ module.exports = Sender; * @private */ function toBuffer (data) { - if (Buffer.isBuffer(data)) return data; - if (data instanceof ArrayBuffer) return Buffer.from(data); - - if (ArrayBuffer.isView(data)) { - const buf = Buffer.from(data.buffer); - - if (data.byteLength !== data.buffer.byteLength) { - return buf.slice(data.byteOffset, data.byteOffset + data.byteLength); - } - - return buf; - } + if (ArrayBuffer.isView(data)) return viewToBuffer(data); return Buffer.from(typeof data === 'number' ? data.toString() : data); } diff --git a/test/Sender.test.js b/test/Sender.test.js index 30eddd42a..d88f32d4f 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -17,7 +17,7 @@ describe('Sender', function () { const sender = new Sender({ write: () => {} }); const buf = Buffer.from([1, 2, 3, 4, 5]); - sender.frameAndSend(2, buf, true, true); + sender.frameAndSend(2, buf, true, true, true); assert.ok(buf.equals(Buffer.from([1, 2, 3, 4, 5]))); }); @@ -26,7 +26,7 @@ describe('Sender', function () { const sender = new Sender({ write: () => {} }); const text = Buffer.from('hi there'); - sender.frameAndSend(1, text, true, true); + sender.frameAndSend(1, text, true, true, true); assert.ok(text.equals(Buffer.from('hi there'))); }); @@ -39,7 +39,7 @@ describe('Sender', function () { } }); - sender.frameAndSend(1, Buffer.from('hi'), true, false, true); + sender.frameAndSend(1, Buffer.from('hi'), false, true, false, true); }); }); @@ -57,7 +57,7 @@ describe('Sender', function () { perMessageDeflate.accept([{}]); - sender.send('hi', { compress: true }); + sender.send('hi', { compress: true, fin: true }); }); it('does not compress data for small payloads', function (done) { @@ -73,7 +73,7 @@ describe('Sender', function () { perMessageDeflate.accept([{}]); - sender.send('hi', { compress: true }); + sender.send('hi', { compress: true, fin: true }); }); it('compresses all frames in a fragmented message', function (done) { @@ -221,23 +221,25 @@ describe('Sender', function () { }); it('handles many send calls while processing without crashing on flush', function (done) { - const maxMessages = 5000; - let messageCount = 0; - + let cnt = 0; + const perMessageDeflate = new PerMessageDeflate(); const sender = new Sender({ - write: (data) => { - messageCount++; - if (messageCount > maxMessages) return done(); + write: () => { + if (++cnt > 1e4) done(); } + }, { + 'permessage-deflate': perMessageDeflate }); - for (let i = 0; i < maxMessages; i++) { + perMessageDeflate.accept([{}]); + + for (let i = 0; i < 1e4; i++) { sender.processing = true; - sender.send('hi', { compress: false, fin: true, binary: false, mask: false }); + sender.send('hi', { compress: false, fin: true }); } sender.processing = false; - sender.send('hi', { compress: false, fin: true, binary: false, mask: false }); + sender.send('hi', { compress: false, fin: true }); }); }); @@ -254,9 +256,9 @@ describe('Sender', function () { perMessageDeflate.accept([{}]); - sender.send('foo', { compress: true }); - sender.send('bar', { compress: true }); - sender.send('baz', { compress: true }); + sender.send('foo', { compress: true, fin: true }); + sender.send('bar', { compress: true, fin: true }); + sender.send('baz', { compress: true, fin: true }); sender.close(1000, null, false, (err) => { assert.strictEqual(count, 4); From 647242518f8ea2ccafbd2bc008327d8b7fedbebd Mon Sep 17 00:00:00 2001 From: William Yaworsky Date: Tue, 14 Apr 2015 02:00:17 -0400 Subject: [PATCH 165/669] [minor] Add `shouldHandle()` method to `WebSocketServer` Moved the check to see if a `WebSocketServer` instance should handle a given request into a public method. This will allow people to override this logic with a patch if they desire. --- lib/WebSocketServer.js | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 10eae03f2..f513100fe 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -156,21 +156,35 @@ WebSocketServer.prototype.close = function (callback) { }; /** - * Handle a HTTP Upgrade request. + * See if a given request should be handled by this server instance. * - * @api public + * @param {http.IncomingMessage} req Request object to inspect + * @return {Boolean} `true` if the request is valid, else `false` + * @public */ - -WebSocketServer.prototype.handleUpgrade = function (req, socket, upgradeHead, cb) { - // check for wrong path - if (this.options.path) { - var u = url.parse(req.url); - if (u && u.pathname !== this.options.path) { - return abortConnection(socket, 400); - } +WebSocketServer.prototype.shouldHandle = function (req) { + if (this.options.path && url.parse(req.url).pathname !== this.options.path) { + return false; } - if (!req.headers.upgrade || req.headers.upgrade.toLowerCase() !== 'websocket') { + return true; +}; + +/** + * Handle a HTTP Upgrade request. + * + * @param {http.IncomingMessage} req The request object + * @param {net.Socket} socket The network socket between the server and client + * @param {Buffer} head The first packet of the upgraded stream + * @param {Function} cb Callback + * @public + */ +WebSocketServer.prototype.handleUpgrade = function (req, socket, head, cb) { + if ( + !this.shouldHandle(req) || + !req.headers.upgrade || + req.headers.upgrade.toLowerCase() !== 'websocket' + ) { return abortConnection(socket, 400); } From d83117d5fbaf2ece7e4ffe575efd486469abc29d Mon Sep 17 00:00:00 2001 From: William Yaworsky Date: Tue, 14 Apr 2015 02:17:45 -0400 Subject: [PATCH 166/669] [test] Add tests for `WebSocketServer.prototype.shouldHandle()` --- test/WebSocketServer.test.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index b74a28afd..419563d42 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -367,6 +367,20 @@ describe('WebSocketServer', function () { }); }); + describe('#shouldHandle', function () { + it('returns true when the path matches', function () { + const wss = new WebSocketServer({ noServer: true, path: '/foo' }); + + assert.strictEqual(wss.shouldHandle({ url: '/foo' }), true); + }); + + it('returns false when the path does not match', function () { + const wss = new WebSocketServer({ noServer: true, path: '/foo' }); + + assert.strictEqual(wss.shouldHandle({ url: '/bar' }), false); + }); + }); + describe('#handleUpgrade', function () { it('can be used for a pre-existing server', function (done) { const server = http.createServer(); From 442ca0abb5726d667c9ffd9296c0f92c78097721 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 28 Oct 2016 16:25:17 +0200 Subject: [PATCH 167/669] [major] Remove `stream` method and ability to send a stream --- lib/WebSocket.js | 120 +------- test/WebSocket.test.js | 602 +---------------------------------------- 2 files changed, 7 insertions(+), 715 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 511a63f9d..a89c1daac 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -11,7 +11,6 @@ const util = require('util'); const http = require('http'); const https = require('https'); const crypto = require('crypto'); -const stream = require('stream'); const Ultron = require('ultron'); const Sender = require('./Sender'); const Receiver = require('./Receiver'); @@ -222,11 +221,6 @@ WebSocket.prototype.send = function send (data, options, cb) { if (!data) data = ''; - if (this._queue) { - this._queue.push(() => this.send(data, options, cb)); - return; - } - options = options || {}; if (options.fin !== false) options.fin = true; @@ -241,72 +235,7 @@ WebSocket.prototype.send = function send (data, options, cb) { options.compress = false; } - if (data instanceof stream.Readable) { - startQueue(this); - - sendStream(this, data, options, (error) => { - process.nextTick(() => executeQueueSends(this)); - if (cb) cb(error); - }); - } else { - this._sender.send(data, options, cb); - } -}; - -/** - * Streams data through calls to a user supplied function - * - * @param {Object} Members - mask: boolean, binary: boolean, compress: boolean - * @param {function} 'function (error, send)' which is executed on successive - * ticks of which send is 'function (data, final)'. - * @api public - */ -WebSocket.prototype.stream = function stream (options, cb) { - if (typeof options === 'function') { - cb = options; - options = {}; - } - - if (!cb) throw new Error('callback must be provided'); - - if (this.readyState !== WebSocket.OPEN) { - if (cb) cb(new Error('not opened')); - else throw new Error('not opened'); - return; - } - - if (this._queue) { - this._queue.push(() => this.stream(options, cb)); - return; - } - - options = options || {}; - - if (options.mask === undefined) options.mask = !this._isServer; - if (options.compress === undefined) options.compress = true; - if (!this.extensions[PerMessageDeflate.extensionName]) { - options.compress = false; - } - - startQueue(this); - - const send = (data, final) => { - try { - if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); - options.fin = final === true; - this._sender.send(data, options); - if (!final) process.nextTick(cb, null, send); - else executeQueueSends(this); - } catch (e) { - if (typeof cb === 'function') cb(e); - else { - delete this._queue; - this.emit('error', e); - } - } - }; - - process.nextTick(cb, null, send); + this._sender.send(data, options, cb); }; /** @@ -845,52 +774,6 @@ function establishConnection (socket, upgradeHead) { this.emit('open'); } -function startQueue (instance) { - instance._queue = instance._queue || []; -} - -function executeQueueSends (instance) { - var queue = instance._queue; - if (queue === undefined) return; - - delete instance._queue; - for (var i = 0, l = queue.length; i < l; ++i) { - queue[i](); - } -} - -function sendStream (instance, stream, options, cb) { - stream.on('data', function incoming (data) { - if (instance.readyState !== WebSocket.OPEN) { - if (cb) cb(new Error('not opened')); - else { - delete instance._queue; - instance.emit('error', new Error('not opened')); - } - return; - } - - options.fin = false; - instance._sender.send(data, options); - }); - - stream.on('end', function end () { - if (instance.readyState !== WebSocket.OPEN) { - if (cb) cb(new Error('not opened')); - else { - delete instance._queue; - instance.emit('error', new Error('not opened')); - } - return; - } - - options.fin = true; - instance._sender.send(null, options); - - if (cb) cb(null); - }); -} - function cleanupWebsocketResources (error) { if (this.readyState === WebSocket.CLOSED) return; @@ -941,5 +824,4 @@ function cleanupWebsocketResources (error) { this.removeAllListeners(); this.on('error', function onerror () {}); // catch all errors after this - delete this._queue; } diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 52612c533..cafab9647 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -796,11 +796,11 @@ describe('WebSocket', function () { }); }); - it('with unencoded message is successfully transmitted to the server', function (done) { + it('with unmasked message is successfully transmitted to the server', function (done) { server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.send('hi')); + ws.on('open', () => ws.send('hi', { mask: false })); srv.on('message', (message, flags) => { assert.strictEqual(message, 'hi'); @@ -810,7 +810,7 @@ describe('WebSocket', function () { }); }); - it('with encoded message is successfully transmitted to the server', function (done) { + it('with masked message is successfully transmitted to the server', function (done) { server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -825,7 +825,7 @@ describe('WebSocket', function () { }); }); - it('with unencoded binary message is successfully transmitted to the server', function (done) { + it('with unmasked binary message is successfully transmitted to the server', function (done) { server.createServer(++port, (srv) => { const array = new Float32Array(5); @@ -835,7 +835,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.send(array, { binary: true })); + ws.on('open', () => ws.send(array, { mask: false, binary: true })); srv.on('message', (message, flags) => { assert.ok(flags.binary); @@ -846,7 +846,7 @@ describe('WebSocket', function () { }); }); - it('with encoded binary message is successfully transmitted to the server', function (done) { + it('with masked binary message is successfully transmitted to the server', function (done) { server.createServer(++port, (srv) => { const array = new Float32Array(5); @@ -867,561 +867,9 @@ describe('WebSocket', function () { }); }); }); - - it('with binary stream will send fragmented data', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - let callbackFired = false; - - ws.on('open', () => { - const fileStream = fs.createReadStream('test/fixtures/textfile', { - highWaterMark: 100 - }); - - ws.send(fileStream, { binary: true }, (error) => { - assert.ifError(error); - callbackFired = true; - }); - }); - - ws.on('close', () => { - assert.ok(callbackFired); - srv.close(done); - }); - - srv.on('message', (data, flags) => { - assert.ok(flags.binary); - assert.ok(data.equals(fs.readFileSync('test/fixtures/textfile'))); - - ws.close(); - }); - }); - }); - - it('with text stream will send fragmented data', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - let callbackFired = false; - - ws.on('open', () => { - const fileStream = fs.createReadStream('test/fixtures/textfile', { - highWaterMark: 100, - encoding: 'utf8' - }); - - ws.send(fileStream, { binary: false }, (error) => { - assert.ifError(error); - callbackFired = true; - }); - }); - - ws.on('close', () => { - assert.ok(callbackFired); - srv.close(done); - }); - - srv.on('message', (data, flags) => { - assert.ok(!flags.binary); - assert.strictEqual( - data, - fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) - ); - - ws.close(); - }); - }); - }); - - it('will cause intermittent send to be delayed in order', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - const fileStream = fs.createReadStream('test/fixtures/textfile', { - highWaterMark: 100, - encoding: 'utf8' - }); - - ws.send(fileStream); - ws.send('foobar'); - ws.send('baz'); - }); - - let receivedIndex = 0; - - srv.on('message', (data, flags) => { - if (++receivedIndex === 1) { - assert.ok(!flags.binary); - assert.strictEqual( - data, - fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) - ); - } else if (receivedIndex === 2) { - assert.ok(!flags.binary); - assert.strictEqual(data, 'foobar'); - } else { - assert.ok(!flags.binary); - assert.strictEqual(data, 'baz'); - srv.close(done); - ws.terminate(); - } - }); - }); - }); - - it('will cause intermittent stream to be delayed in order', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - const fileStream = fs.createReadStream('test/fixtures/textfile', { - highWaterMark: 100, - encoding: 'utf8' - }); - - ws.send(fileStream); - - let i = 0; - ws.stream((error, send) => { - assert.ifError(error); - - if (++i === 1) send('foo'); - else send('bar', true); - }); - }); - - let receivedIndex = 0; - - srv.on('message', (data, flags) => { - if (++receivedIndex === 1) { - assert.ok(!flags.binary); - assert.strictEqual( - data, - fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) - ); - } else if (receivedIndex === 2) { - assert.ok(!flags.binary); - assert.strictEqual(data, 'foobar'); - srv.close(done); - ws.terminate(); - } - }); - }); - }); - - it('will cause intermittent ping to be delivered', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - const fileStream = fs.createReadStream('test/fixtures/textfile', { - highWaterMark: 100, - encoding: 'utf8' - }); - - ws.send(fileStream); - ws.ping('foobar'); - }); - - let receivedIndex = 0; - - srv.on('message', (data, flags) => { - assert.ok(!flags.binary); - assert.strictEqual( - data, - fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) - ); - if (++receivedIndex === 2) { - srv.close(done); - ws.terminate(); - } - }); - - srv.on('ping', (data) => { - assert.strictEqual(data.toString(), 'foobar'); - if (++receivedIndex === 2) { - srv.close(done); - ws.terminate(); - } - }); - }); - }); - - it('will cause intermittent pong to be delivered', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - const fileStream = fs.createReadStream('test/fixtures/textfile', { - highWaterMark: 100, - encoding: 'utf8' - }); - - ws.send(fileStream); - ws.pong('foobar'); - }); - - let receivedIndex = 0; - - srv.on('message', (data, flags) => { - assert.ok(!flags.binary); - assert.strictEqual( - data, - fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) - ); - if (++receivedIndex === 2) { - srv.close(done); - ws.close(); - } - }); - - srv.on('pong', (data) => { - assert.strictEqual(data.toString(), 'foobar'); - if (++receivedIndex === 2) { - srv.close(done); - ws.terminate(); - } - }); - }); - }); - - it('will cause intermittent close to be delivered', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - const fileStream = fs.createReadStream('test/fixtures/textfile', { - highWaterMark: 100, - encoding: 'utf8' - }); - ws.send(fileStream); - ws.close(1000, 'foobar'); - }); - - ws.on('close', () => srv.close(done)); - ws.on('error', () => { - // That's quite alright -- a send was attempted after close - }); - - srv.on('message', (data, flags) => { - assert.ok(!flags.binary); - assert.strictEqual( - data, - fs.readFileSync('test/fixtures/textfile', { encoding: 'utf8' }) - ); - }); - - srv.on('close', (code, data) => { - assert.strictEqual(code, 1000); - assert.strictEqual(data, 'foobar'); - }); - }); - }); - }); - - describe('#stream', function () { - it('very long binary data can be streamed', function (done) { - server.createServer(++port, (srv) => { - const buffer = new Buffer(10 * 1024); - - for (let i = 0; i < buffer.length; ++i) { - buffer[i] = i % 0xff; - } - - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - const bufLen = buffer.length; - const blockSize = 800; - let i = 0; - - ws.stream({ binary: true }, (error, send) => { - assert.ifError(error); - - const start = i * blockSize; - const toSend = Math.min(blockSize, bufLen - (i * blockSize)); - const end = start + toSend; - const isFinal = toSend < blockSize; - - send(buffer.slice(start, end), isFinal); - i += 1; - }); - }); - - srv.on('message', (data, flags) => { - assert.ok(flags.binary); - assert.ok(data.equals(buffer)); - srv.close(done); - ws.terminate(); - }); - }); - }); - - it('before connect should pass error through callback', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.stream((error) => { - assert.ok(error instanceof Error); - srv.close(done); - ws.terminate(); - }); - }); - }); - - it('without callback should fail', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - try { - ws.stream(); - } catch (e) { - srv.close(done); - ws.terminate(); - } - }); - }); - }); - - it('will cause intermittent send to be delayed in order', function (done) { - server.createServer(++port, (srv) => { - const payload = 'HelloWorld'; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - let i = 0; - - ws.stream((error, send) => { - assert.ifError(error); - if (++i === 1) { - send(payload.substr(0, 5)); - ws.send('foobar'); - ws.send('baz'); - } else { - send(payload.substr(5, 5), true); - } - }); - }); - - let receivedIndex = 0; - - srv.on('message', (data, flags) => { - if (++receivedIndex === 1) { - assert.ok(!flags.binary); - assert.strictEqual(data, payload); - } else if (receivedIndex === 2) { - assert.ok(!flags.binary); - assert.strictEqual(data, 'foobar'); - } else { - assert.ok(!flags.binary); - assert.strictEqual(data, 'baz'); - srv.close(done); - ws.terminate(); - } - }); - }); - }); - - it('will cause intermittent stream to be delayed in order', function (done) { - server.createServer(++port, (srv) => { - const payload = 'HelloWorld'; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - let i = 0; - - ws.stream((error, send) => { - assert.ifError(error); - if (++i === 1) { - send(payload.substr(0, 5)); - - let i2 = 0; - - ws.stream((error, send) => { - assert.ifError(error); - if (++i2 === 1) send('foo'); - else send('bar', true); - }); - - ws.send('baz'); - } else { - send(payload.substr(5, 5), true); - } - }); - }); - - let receivedIndex = 0; - - srv.on('message', (data, flags) => { - if (++receivedIndex === 1) { - assert.ok(!flags.binary); - assert.strictEqual(data, payload); - } else if (receivedIndex === 2) { - assert.ok(!flags.binary); - assert.strictEqual(data, 'foobar'); - } else if (receivedIndex === 3) { - assert.ok(!flags.binary); - assert.strictEqual(data, 'baz'); - setTimeout(() => { - srv.close(done); - ws.terminate(); - }, 1000); - } else { - throw new Error('more messages than we actually sent just arrived'); - } - }); - }); - }); - - it('will cause intermittent ping to be delivered', function (done) { - server.createServer(++port, (srv) => { - const payload = 'HelloWorld'; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - let i = 0; - - ws.stream((error, send) => { - assert.ifError(error); - if (++i === 1) { - send(payload.substr(0, 5)); - ws.ping('foobar'); - } else { - send(payload.substr(5, 5), true); - } - }); - }); - - let receivedIndex = 0; - - srv.on('message', (data, flags) => { - assert.ok(!flags.binary); - assert.strictEqual(data, payload); - if (++receivedIndex === 2) { - srv.close(done); - ws.terminate(); - } - }); - - srv.on('ping', (data) => { - assert.strictEqual(data.toString(), 'foobar'); - if (++receivedIndex === 2) { - srv.close(done); - ws.terminate(); - } - }); - }); - }); - - it('will cause intermittent pong to be delivered', function (done) { - server.createServer(++port, (srv) => { - const payload = 'HelloWorld'; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => { - let i = 0; - - ws.stream((error, send) => { - assert.ifError(error); - if (++i === 1) { - send(payload.substr(0, 5)); - ws.pong('foobar'); - } else { - send(payload.substr(5, 5), true); - } - }); - }); - - let receivedIndex = 0; - - srv.on('message', (data, flags) => { - assert.ok(!flags.binary); - assert.strictEqual(data, payload); - if (++receivedIndex === 2) { - srv.close(done); - ws.terminate(); - } - }); - - srv.on('pong', (data) => { - assert.strictEqual(data.toString(), 'foobar'); - if (++receivedIndex === 2) { - srv.close(done); - ws.terminate(); - } - }); - }); - }); - - it('will cause intermittent close to be delivered', function (done) { - server.createServer(++port, (srv) => { - const payload = 'HelloWorld'; - const ws = new WebSocket(`ws://localhost:${port}`); - let errorGiven = false; - - ws.on('open', () => { - let i = 0; - - ws.stream((error, send) => { - if (++i === 1) { - send(payload.substr(0, 5)); - ws.close(1000, 'foobar'); - } else if (i === 2) { - send(payload.substr(5, 5), true); - } else if (i === 3) { - assert.ok(error); - errorGiven = true; - } - }); - }); - - ws.on('close', () => { - assert.ok(errorGiven); - srv.close(done); - ws.terminate(); - }); - - srv.on('message', (data, flags) => { - assert.ok(!flags.binary); - assert.strictEqual(data, payload); - }); - - srv.on('close', (code, data) => { - assert.strictEqual(code, 1000); - assert.strictEqual(data.toString(), 'foobar'); - }); - }); - }); }); describe('#close', function () { - it('will raise error callback, if any, if called during send stream', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - let errorGiven = false; - - ws.on('open', () => { - const fileStream = fs.createReadStream('test/fixtures/textfile', { - highWaterMark: 100, - encoding: 'utf8' - }); - - ws.send(fileStream, (error) => { - errorGiven = !!error; - }); - ws.close(1000, 'foobar'); - }); - - ws.on('close', () => { - setTimeout(() => { - assert.ok(errorGiven); - srv.close(done); - }, 1000); - }); - }); - }); - it('without invalid first argument throws exception', function (done) { server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -2107,44 +1555,6 @@ describe('WebSocket', function () { }); }); - it('with binary stream will send fragmented data', function (done) { - const wss = new WebSocketServer({ - perMessageDeflate: true, - port: ++port - }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, { - perMessageDeflate: true - }); - - let callbackFired = false; - - ws.on('open', () => { - const fileStream = fs.createReadStream('test/fixtures/textfile', { - highWaterMark: 100 - }); - - ws.send(fileStream, { binary: true, compress: true }, (error) => { - assert.ifError(error); - callbackFired = true; - }); - }); - - ws.on('close', () => { - assert.ok(callbackFired); - wss.close(); - done(); - }); - }); - - wss.on('connection', (ws) => { - ws.on('message', (data, flags) => { - assert.ok(flags.binary); - assert.ok(data.equals(fs.readFileSync('test/fixtures/textfile'))); - ws.close(); - }); - }); - }); - describe('#send', function () { it('can set the compress option true when perMessageDeflate is disabled', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { From bdcd18b97e7324dbe8effa7d06866b29172709e7 Mon Sep 17 00:00:00 2001 From: Antti Risteli Date: Sat, 12 Nov 2016 09:09:50 +0200 Subject: [PATCH 168/669] [fix] Call zlib flush with the correct flush level (#733) --- lib/PerMessageDeflate.js | 5 +++-- test/PerMessageDeflate.test.js | 27 +++++++++++++++++++++++++++ test/Sender.test.js | 12 ++++++------ 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 83bb85065..ae58689a8 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -7,6 +7,7 @@ const DEFAULT_WINDOW_BITS = 15; const DEFAULT_MEM_LEVEL = 8; const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); const EMPTY_BLOCK = Buffer.from([0x00]); +const FLUSH_LEVEL = zlib.Z_SYNC_FLUSH; /** * Per-message Compression Extensions implementation @@ -292,7 +293,7 @@ class PerMessageDeflate { if (!this._deflate) { var maxWindowBits = this.params[endpoint + '_max_window_bits']; this._deflate = zlib.createDeflateRaw({ - flush: zlib.Z_SYNC_FLUSH, + flush: FLUSH_LEVEL, windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS, memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL }); @@ -304,7 +305,7 @@ class PerMessageDeflate { this._deflate.on('error', onError).on('data', onData); this._deflate.write(data); - this._deflate.flush(function () { + this._deflate.flush(FLUSH_LEVEL, function () { cleanup(); var data = Buffer.concat(buffers); if (fin) { diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index fe570f4b2..59d202b69 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -311,5 +311,32 @@ describe('PerMessageDeflate', function () { }); }); }); + + it('should compress data between contexts when allowed', function (done) { + var perMessageDeflate = new PerMessageDeflate(); + var extensions = Extensions.parse('permessage-deflate'); + perMessageDeflate.accept(extensions['permessage-deflate']); + + var buf = new Buffer('foofoo'); + perMessageDeflate.compress(buf, true, function (err, compressed1) { + if (err) return done(err); + + perMessageDeflate.decompress(compressed1, true, function (err, data) { + if (err) return done(err); + + perMessageDeflate.compress(data, true, function (err, compressed2) { + if (err) return done(err); + + perMessageDeflate.decompress(compressed2, true, function (err, data) { + if (err) return done(err); + + assert.ok(compressed2.length < compressed1.length); + assert.deepEqual(data, buf); + done(); + }); + }); + }); + }); + }); }); }); diff --git a/test/Sender.test.js b/test/Sender.test.js index d88f32d4f..01342b54d 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -85,9 +85,9 @@ describe('Sender', function () { if (fragments.length !== 2) return; assert.strictEqual(fragments[0][0] & 0x40, 0x40); - assert.strictEqual(fragments[0].length, 16); + assert.strictEqual(fragments[0].length, 11); assert.strictEqual(fragments[1][0] & 0x40, 0x00); - assert.strictEqual(fragments[1].length, 11); + assert.strictEqual(fragments[1].length, 6); done(); } }, { @@ -135,7 +135,7 @@ describe('Sender', function () { assert.strictEqual(fragments[0][0] & 0x40, 0x40); assert.strictEqual(fragments[0].length, 3); assert.strictEqual(fragments[1][0] & 0x40, 0x00); - assert.strictEqual(fragments[1].length, 13); + assert.strictEqual(fragments[1].length, 8); done(); } }, { @@ -159,7 +159,7 @@ describe('Sender', function () { assert.strictEqual(fragments[0][0] & 0x40, 0x40); assert.strictEqual(fragments[0].length, 3); assert.strictEqual(fragments[1][0] & 0x40, 0x00); - assert.strictEqual(fragments[1].length, 13); + assert.strictEqual(fragments[1].length, 8); done(); } }, { @@ -181,7 +181,7 @@ describe('Sender', function () { if (fragments.length !== 2) return; assert.strictEqual(fragments[0][0] & 0x40, 0x40); - assert.strictEqual(fragments[0].length, 17); + assert.strictEqual(fragments[0].length, 12); assert.strictEqual(fragments[1][0] & 0x40, 0x00); assert.strictEqual(fragments[1].length, 3); done(); @@ -205,7 +205,7 @@ describe('Sender', function () { if (fragments.length !== 2) return; assert.strictEqual(fragments[0][0] & 0x40, 0x40); - assert.strictEqual(fragments[0].length, 17); + assert.strictEqual(fragments[0].length, 12); assert.strictEqual(fragments[1][0] & 0x40, 0x00); assert.strictEqual(fragments[1].length, 3); done(); From e909cf177b3566d638a1aa683640ab1dff32ef6e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 12 Nov 2016 08:31:36 +0100 Subject: [PATCH 169/669] [minor] Fix nits --- lib/PerMessageDeflate.js | 5 ++--- test/PerMessageDeflate.test.js | 17 +++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index ae58689a8..37162c0d1 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -7,7 +7,6 @@ const DEFAULT_WINDOW_BITS = 15; const DEFAULT_MEM_LEVEL = 8; const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); const EMPTY_BLOCK = Buffer.from([0x00]); -const FLUSH_LEVEL = zlib.Z_SYNC_FLUSH; /** * Per-message Compression Extensions implementation @@ -293,7 +292,7 @@ class PerMessageDeflate { if (!this._deflate) { var maxWindowBits = this.params[endpoint + '_max_window_bits']; this._deflate = zlib.createDeflateRaw({ - flush: FLUSH_LEVEL, + flush: zlib.Z_SYNC_FLUSH, windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS, memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL }); @@ -305,7 +304,7 @@ class PerMessageDeflate { this._deflate.on('error', onError).on('data', onData); this._deflate.write(data); - this._deflate.flush(FLUSH_LEVEL, function () { + this._deflate.flush(zlib.Z_SYNC_FLUSH, function () { cleanup(); var data = Buffer.concat(buffers); if (fin) { diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index 59d202b69..fb1706813 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -313,25 +313,26 @@ describe('PerMessageDeflate', function () { }); it('should compress data between contexts when allowed', function (done) { - var perMessageDeflate = new PerMessageDeflate(); - var extensions = Extensions.parse('permessage-deflate'); + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const extensions = Extensions.parse('permessage-deflate'); + const buf = Buffer.from('foofoo'); + perMessageDeflate.accept(extensions['permessage-deflate']); - var buf = new Buffer('foofoo'); - perMessageDeflate.compress(buf, true, function (err, compressed1) { + perMessageDeflate.compress(buf, true, (err, compressed1) => { if (err) return done(err); - perMessageDeflate.decompress(compressed1, true, function (err, data) { + perMessageDeflate.decompress(compressed1, true, (err, data) => { if (err) return done(err); - perMessageDeflate.compress(data, true, function (err, compressed2) { + perMessageDeflate.compress(data, true, (err, compressed2) => { if (err) return done(err); - perMessageDeflate.decompress(compressed2, true, function (err, data) { + perMessageDeflate.decompress(compressed2, true, (err, data) => { if (err) return done(err); assert.ok(compressed2.length < compressed1.length); - assert.deepEqual(data, buf); + assert.ok(data.equals(buf)); done(); }); }); From 6bd0a57f17a5ed75127ebe67b27661c25f4ba3ec Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 13 Nov 2016 11:31:52 +0100 Subject: [PATCH 170/669] [major] Remove callback from `handleProtocols` --- doc/ws.md | 11 ++- lib/WebSocketServer.js | 139 +++++++++++++++-------------------- test/WebSocketServer.test.js | 54 ++------------ 3 files changed, 69 insertions(+), 135 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index b5189594d..6ba7e2b2b 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -45,13 +45,12 @@ If `verifyClient` is not set then the handshake is automatically accepted. ### options.handleProtocols -`handleProtocols` receives two arguments: -* `protocols` Array: The list of WebSocket sub-protocols indicated by the client in the Sec-WebSocket-Protocol header. -* `cb` Function: A callback that must be called by the user upon inspection of the protocols. Arguments in this callback are: - * `result` Boolean: Whether the user accepts or not the handshake. - * `protocol` String: If `result` is `true` then this field sets the value of the Sec-WebSocket-Protocol header in the HTTP 101 response. +`handleProtocols` takes a single argument: +* `protocols` Array: The list of WebSocket sub-protocols indicated by the client in the `Sec-WebSocket-Protocol` header. + +If returned value is `false` then the handshake is rejected with the HTTP 401 status code, otherwise the returned value sets the value of the `Sec-WebSocket-Protocol` header in the HTTP 101 response. -If `handleProtocols` is not set then the handshake is accepted regardless the value of Sec-WebSocket-Protocol header. If it is set but the user does not invoke the `cb` callback then the handshake is rejected with error HTTP 501. +If `handleProtocols` is not set then the handshake is automatically accepted. ### options.perMessageDeflate diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index b2eda494a..dab4cfea1 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -166,58 +166,51 @@ WebSocketServer.prototype.handleUpgrade = function (req, socket, head, cb) { if ( !this.shouldHandle(req) || !req.headers.upgrade || - req.headers.upgrade.toLowerCase() !== 'websocket' + req.headers.upgrade.toLowerCase() !== 'websocket' || + !req.headers['sec-websocket-key'] ) { return abortConnection(socket, 400); } - handleHybiUpgrade.apply(this, arguments); + socket.on('error', socketError); + upgrade.apply(this, arguments); }; module.exports = WebSocketServer; /** - * Entirely private apis, - * which may or may not be bound to a specific WebSocket instance. + * Handle premature socket errors. + * + * @private */ +function socketError () { + this.destroy(); +} -function handleHybiUpgrade (req, socket, upgradeHead, cb) { - // handle premature socket errors - var errorHandler = () => { - try { socket.destroy(); } catch (e) {} - }; - socket.on('error', errorHandler); - - // verify key presence - if (!req.headers['sec-websocket-key']) { - return abortConnection(socket, 400); - } - - // verify version - var version = +req.headers['sec-websocket-version']; - if (version !== 8 && version !== 13) { - return abortConnection(socket, 400); - } - - // verify protocol - var protocols = req.headers['sec-websocket-protocol']; +/** + * Upgrade the connection to WebSocket. + * + * @param {http.IncomingMessage} req The request object + * @param {net.Socket} socket The network socket between the server and client + * @param {Buffer} head The first packet of the upgraded stream + * @param {Function} cb Callback + * @private + */ +function upgrade (req, socket, head, cb) { + const version = +req.headers['sec-websocket-version']; - // verify client - var origin = version !== 13 - ? req.headers['sec-websocket-origin'] - : req.headers['origin']; + if (version !== 8 && version !== 13) return abortConnection(socket, 400); - // handle extensions offer - var extensionsOffer = Extensions.parse(req.headers['sec-websocket-extensions']); + var protocol = (req.headers['sec-websocket-protocol'] || '').split(/, */); // handler to call when the connection sequence completes - var completeHybiUpgrade2 = (protocol) => { + const completeUpgrade = () => { // calc key - var key = crypto.createHash('sha1') + const key = crypto.createHash('sha1') .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'binary') .digest('base64'); - var headers = [ + const headers = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', @@ -228,40 +221,39 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { headers.push(`Sec-WebSocket-Protocol: ${protocol}`); } - var extensions = {}; + const offer = Extensions.parse(req.headers['sec-websocket-extensions']); + var extensions; + try { - extensions = acceptExtensions.call(this, extensionsOffer); + extensions = acceptExtensions.call(this, offer); } catch (err) { return abortConnection(socket, 400); } if (Object.keys(extensions).length) { - var serverExtensions = {}; - Object.keys(extensions).forEach((token) => { - serverExtensions[token] = [extensions[token].params]; - }); + const serverExtensions = Object.keys(extensions).reduce((obj, key) => { + obj[key] = [extensions[key].params]; + return obj; + }, {}); + headers.push(`Sec-WebSocket-Extensions: ${Extensions.format(serverExtensions)}`); } // allows external modification/inspection of handshake headers this.emit('headers', headers); - socket.setTimeout(0); - socket.setNoDelay(true); - - try { + if (socket.writable) { socket.write(headers.concat('', '').join('\r\n')); - } catch (e) { - // if the upgrade write fails, shut the connection down hard - try { socket.destroy(); } catch (e) {} + } else { + socket.destroy(); return; } - var client = new WebSocket([req, socket, upgradeHead], { + const client = new WebSocket([req, socket, head], { + maxPayload: this.options.maxPayload, protocolVersion: version, - protocol: protocol, - extensions: extensions, - maxPayload: this.options.maxPayload + extensions, + protocol }); if (this.clients) { @@ -270,44 +262,31 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { } // signal upgrade complete - socket.removeListener('error', errorHandler); + socket.removeListener('error', socketError); cb(client); }; - // optionally call external protocol selection handler before - // calling completeHybiUpgrade2 - var completeHybiUpgrade1 = () => { - // choose from the sub-protocols - if (this.options.handleProtocols) { - var protList = (protocols || '').split(/, */); - var callbackCalled = false; - this.options.handleProtocols(protList, (result, protocol) => { - callbackCalled = true; - if (!result) return abortConnection(socket, 401); - - completeHybiUpgrade2(protocol); - }); - if (!callbackCalled) { - // the handleProtocols handler never called our callback - abortConnection(socket, 501, 'Could not process protocols'); - } - } else { - completeHybiUpgrade2(protocols && protocols.split(/, */)[0]); - } - }; + // optionally call external protocol selection handler + if (this.options.handleProtocols) { + protocol = this.options.handleProtocols(protocol); + if (protocol === false) return abortConnection(socket, 401); + } else { + protocol = protocol[0]; + } // optionally call external client verification handler if (this.options.verifyClient) { - var info = { - secure: req.connection.authorized !== undefined || req.connection.encrypted !== undefined, - origin: origin, - req: req + const info = { + origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`], + secure: !!(req.connection.authorized || req.connection.encrypted), + req }; + if (this.options.verifyClient.length === 2) { - this.options.verifyClient(info, (result, code, message) => { - if (!result) return abortConnection(socket, code || 401, message); + this.options.verifyClient(info, (verified, code, message) => { + if (!verified) return abortConnection(socket, code || 401, message); - completeHybiUpgrade1(); + completeUpgrade(); }); return; } else if (!this.options.verifyClient(info)) { @@ -315,7 +294,7 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { } } - completeHybiUpgrade1(); + completeUpgrade(); } function acceptExtensions (offer) { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 86e52174c..2bfa6aafa 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -765,7 +765,7 @@ describe('WebSocketServer', function () { it('selects the last protocol via protocol handler', function (done) { const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(true, ps[ps.length - 1]), + handleProtocols: (ps) => ps[ps.length - 1], port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); @@ -780,7 +780,7 @@ describe('WebSocketServer', function () { it('client detects invalid server protocol', function (done) { const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(true, 'prot3'), + handleProtocols: (ps) => 'prot3', port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); @@ -795,22 +795,7 @@ describe('WebSocketServer', function () { it('client detects no server protocol', function (done) { const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(true), - port: ++port - }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); - - ws.on('open', () => done(new Error('connection must not be established'))); - ws.on('error', () => { - wss.close(); - done(); - }); - }); - }); - - it('client refuses server protocols', function (done) { - const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(false), + handleProtocols: (ps) => {}, port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); @@ -825,7 +810,7 @@ describe('WebSocketServer', function () { it('server detects unauthorized protocol handler', function (done) { const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => cb(false), + handleProtocols: (ps) => false, port: ++port }, () => { const req = http.request({ @@ -834,7 +819,7 @@ describe('WebSocketServer', function () { 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Origin': 'http://foobar.com' + 'Origin': 'http://foobar.com' }, host: '127.0.0.1', port @@ -850,35 +835,6 @@ describe('WebSocketServer', function () { }); }); - it('server detects invalid protocol handler', function (done) { - const wss = new WebSocketServer({ - handleProtocols: (ps, cb) => { - // not calling callback is an error and shouldn't timeout - }, - port: ++port - }, () => { - const req = http.request({ - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Origin': 'http://foobar.com' - }, - host: '127.0.0.1', - port - }); - - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 501); - wss.close(); - done(); - }); - - req.end(); - }); - }); - it('accept connections with sec-websocket-extensions', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const req = http.request({ From 6a8d707483f2a4ba4c754894a2c0e0c258fe3407 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 12 Nov 2016 20:26:56 +0100 Subject: [PATCH 171/669] [minor] Use ultron to handle the events listeners on the server (#889) --- lib/WebSocketServer.js | 102 ++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 57 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index b2eda494a..ea818f288 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -6,23 +6,35 @@ 'use strict'; -const util = require('util'); const EventEmitter = require('events'); -const http = require('http'); const crypto = require('crypto'); -const WebSocket = require('./WebSocket'); -const Extensions = require('./Extensions'); -const PerMessageDeflate = require('./PerMessageDeflate'); +const Ultron = require('ultron'); +const http = require('http'); +const util = require('util'); const url = require('url'); -var isDefinedAndNonNull = function (options, key) { - return options[key] !== undefined && options[key] !== null; -}; +const PerMessageDeflate = require('./PerMessageDeflate'); +const Extensions = require('./Extensions'); +const WebSocket = require('./WebSocket'); /** - * WebSocket Server implementation + * Create a `WebSocketServer` instance. + * + * @param {Object} options Configuration options + * @param {String} options.host The hostname where to bind the server + * @param {Number} options.port The port where to bind the server + * @param {http.Server} options.server A pre-created HTTP/S server to use + * @param {Function} options.verifyClient An hook to reject connections + * @param {Function} options.handleProtocols An hook to handle protocols + * @param {String} options.path Accept only connections matching this path + * @param {Boolean} options.noServer Enable no server mode + * @param {Boolean} options.clientTracking Specifies whether or not to track clients + * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate + * @param {Number} options.maxPayload The maximum allowed message size + * @param {Function} callback A listener for the `listening` event + * @constructor + * @public */ - function WebSocketServer (options, callback) { if (this instanceof WebSocketServer === false) { return new WebSocketServer(options, callback); @@ -44,13 +56,14 @@ function WebSocketServer (options, callback) { backlog: null // use default (511 as implemented in net.js) }, options); - if (!isDefinedAndNonNull(options, 'port') && !isDefinedAndNonNull(options, 'server') && !options.noServer) { - throw new TypeError('`port` or a `server` must be provided'); + if (options.port == null && !options.server && !options.noServer) { + throw new TypeError('missing or invalid options'); } - if (isDefinedAndNonNull(options, 'port')) { + if (options.port != null) { this._server = http.createServer((req, res) => { - var body = http.STATUS_CODES[426]; + const body = http.STATUS_CODES[426]; + res.writeHead(426, { 'Content-Length': body.length, 'Content-Type': 'text/plain' @@ -58,33 +71,21 @@ function WebSocketServer (options, callback) { res.end(body); }); this._server.allowHalfOpen = false; - // maybe use a generic server.listen(options[, callback]) variant here, instead of two overloaded variants? - if (isDefinedAndNonNull(options, 'backlog')) { - this._server.listen(options.port, options.host, options.backlog, callback); - } else { - this._server.listen(options.port, options.host, callback); - } - this._closeServer = () => this._server && this._server.close(); + this._server.listen(options.port, options.host, options.backlog, callback); } else if (options.server) { this._server = options.server; } if (this._server) { - this._onceServerListening = () => this.emit('listening'); - this._server.once('listening', this._onceServerListening); - this._onServerError = (error) => this.emit('error', error); - this._server.on('error', this._onServerError); - this._onServerUpgrade = (req, socket, upgradeHead) => { - // copy upgradeHead to avoid retention of large slab buffers used in node core - var head = new Buffer(upgradeHead.length); - upgradeHead.copy(head); - + this._ultron = new Ultron(this._server); + this._ultron.on('listening', () => this.emit('listening')); + this._ultron.on('error', (err) => this.emit('error', err)); + this._ultron.on('upgrade', (req, socket, head) => { this.handleUpgrade(req, socket, head, (client) => { this.emit(`connection${req.url}`, client); this.emit('connection', client); }); - }; - this._server.on('upgrade', this._onServerUpgrade); + }); } if (options.clientTracking) this.clients = new Set(); @@ -92,19 +93,15 @@ function WebSocketServer (options, callback) { this.path = options.path; } -/** - * Inherits from EventEmitter. - */ - util.inherits(WebSocketServer, EventEmitter); /** - * Immediately shuts down the connection. + * Close the server. * - * @api public + * @param {Function} cb Callback + * @public */ - -WebSocketServer.prototype.close = function (callback) { +WebSocketServer.prototype.close = function (cb) { // terminate all associated clients var error = null; @@ -118,24 +115,15 @@ WebSocketServer.prototype.close = function (callback) { } } - // close the http server if it was internally created - try { - if (this._closeServer !== undefined) { - this._closeServer(); - } - } finally { - if (this._server) { - this._server.removeListener('listening', this._onceServerListening); - this._server.removeListener('error', this._onServerError); - this._server.removeListener('upgrade', this._onServerUpgrade); - } - delete this._server; - } - if (callback) { - callback(error); - } else if (error) { - throw error; + if (this._server) { + // close the http server if it was internally created + if (this.options.port != null) this._server.close(); + this._ultron.destroy(); + this._ultron = this._server = null; } + + if (cb) cb(error); + else if (error) throw error; }; /** From 084369dde8f99af7a7e2bfc63b286648d10181b1 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 15 Nov 2016 10:52:47 +0100 Subject: [PATCH 172/669] [fix] Invoke the close callback when the close event is emitted --- lib/WebSocketServer.js | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index ea818f288..e8b25f2b7 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -103,27 +103,18 @@ util.inherits(WebSocketServer, EventEmitter); */ WebSocketServer.prototype.close = function (cb) { // terminate all associated clients - var error = null; - if (this.clients) { - for (const client of this.clients) { - try { - client.terminate(); - } catch (e) { - error = e; - } - } + for (const client of this.clients) client.terminate(); } if (this._server) { // close the http server if it was internally created - if (this.options.port != null) this._server.close(); + if (this.options.port != null) this._server.close(cb); this._ultron.destroy(); this._ultron = this._server = null; + } else if (cb) { + cb(); } - - if (cb) cb(error); - else if (error) throw error; }; /** From 4056bde03b1e123a7ce8145a1d8a8c097fc451bb Mon Sep 17 00:00:00 2001 From: Antti Risteli Date: Tue, 15 Nov 2016 23:58:23 +0200 Subject: [PATCH 173/669] [test] Fix Autobahn tests (#894) --- test/autobahn-server.js | 32 ++++++-------------------- test/autobahn.js | 51 ++++++++++++----------------------------- 2 files changed, 22 insertions(+), 61 deletions(-) diff --git a/test/autobahn-server.js b/test/autobahn-server.js index 73655d8b3..7d3e92915 100644 --- a/test/autobahn-server.js +++ b/test/autobahn-server.js @@ -1,31 +1,13 @@ 'use strict'; -var WebSocket = require('../'); -var WebSocketServer = WebSocket.Server; +const WebSocket = require('../'); -process.on('uncaughtException', function (err) { - console.log('Caught exception: ', err, err.stack); +const port = process.argv.length > 2 ? parseInt(process.argv[2]) : 9001; +const wss = new WebSocket.Server({ port }, () => { + console.log(`Listening to port ${port}. Use extra argument to define the port`); }); -process.on('SIGINT', function () { - try { - console.log('Updating reports and shutting down'); - var ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); - ws.on('close', function () { - process.exit(); - }); - } catch (e) { - process.exit(); - } -}); - -var wss = new WebSocketServer({port: 8181}); -wss.on('connection', function (ws) { - console.log('new connection'); - ws.on('message', function (data, flags) { - ws.send(flags.buffer, {binary: flags.binary === true}); - }); - ws.on('error', function () { - console.log('error', arguments); - }); +wss.on('connection', (ws) => { + ws.on('message', (data) => ws.send(data)); + ws.on('error', (e) => console.error(e)); }); diff --git a/test/autobahn.js b/test/autobahn.js index dbe3fefd5..cf2492494 100644 --- a/test/autobahn.js +++ b/test/autobahn.js @@ -1,55 +1,34 @@ 'use strict'; -var WebSocket = require('../'); -var currentTest = 1; -var lastTest = -1; -var testCount = null; +const WebSocket = require('../'); -process.on('uncaughtException', function (err) { - console.log('Caught exception: ', err, err.stack); -}); - -process.on('SIGINT', function () { - try { - console.log('Updating reports and shutting down'); - var ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); - ws.on('close', function () { - process.exit(); - }); - } catch (e) { - process.exit(); - } -}); +let currentTest = 1; +let testCount; function nextTest () { - var ws; + let ws; - if (currentTest > testCount || (lastTest !== -1 && currentTest > lastTest)) { - console.log('Updating reports and shutting down'); + if (currentTest > testCount) { ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); - ws.on('close', function () { - process.exit(); - }); return; } - console.log('Running test case ' + currentTest + '/' + testCount); - ws = new WebSocket('ws://localhost:9001/runCase?case=' + currentTest + '&agent=ws'); - ws.on('message', function (data, flags) { - ws.send(flags.buffer, {binary: flags.binary === true, mask: true}); - }); - ws.on('close', function (data) { - currentTest += 1; + console.log(`Running test case ${currentTest}/${testCount}`); + + ws = new WebSocket(`ws://localhost:9001/runCase?case=${currentTest}&agent=ws`); + ws.on('message', (data) => ws.send(data)); + ws.on('close', () => { + currentTest++; process.nextTick(nextTest); }); - ws.on('error', function (e) {}); + ws.on('error', (e) => console.error(e)); } -var ws = new WebSocket('ws://localhost:9001/getCaseCount'); -ws.on('message', function (data, flags) { +const ws = new WebSocket('ws://localhost:9001/getCaseCount'); +ws.on('message', (data) => { testCount = parseInt(data); }); -ws.on('close', function () { +ws.on('close', () => { if (testCount > 0) { nextTest(); } From dcdc652df3532389c9973c5ebe2911509ce685ed Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 16 Nov 2016 15:16:24 +0100 Subject: [PATCH 174/669] [fix] Use `null` as default value for the `host` option Fixes #588 --- lib/WebSocketServer.js | 18 +++++++++--------- test/WebSocketServer.test.js | 36 +++++++++++++++++++++++------------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 4110d7921..f5007000c 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -43,17 +43,17 @@ function WebSocketServer (options, callback) { EventEmitter.call(this); options = Object.assign({ - host: '0.0.0.0', - port: null, - server: null, - verifyClient: null, + maxPayload: 100 * 1024 * 1024, + perMessageDeflate: true, handleProtocols: null, - path: null, - noServer: false, clientTracking: true, - perMessageDeflate: true, - maxPayload: 100 * 1024 * 1024, - backlog: null // use default (511 as implemented in net.js) + verifyClient: null, + noServer: false, + backlog: null, // use default (511 as implemented in net.js) + server: null, + host: null, + path: null, + port: null }, options); if (options.port == null && !options.server && !options.noServer) { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 2bfa6aafa..282676fc0 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -31,19 +31,30 @@ describe('WebSocketServer', function () { it('emits an error if http server bind fails', function (done) { const wss1 = new WebSocketServer({ port: 50003 }); const wss2 = new WebSocketServer({ port: 50003 }); - wss2.on('error', () => { - wss1.close(); - done(); - }); + + wss2.on('error', () => wss1.close(done)); }); it('starts a server on a given port', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); }); - wss.on('connection', (client) => { - wss.close(); - done(); + + wss.on('connection', (client) => wss.close(done)); + }); + + it('binds the server on any IPv6 address when available', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const address = wss._server.address().address; + + // The Trusty build environment does not have IPv6 connectivity. + if (process.env.TRAVIS) { + assert.strictEqual(address, '0.0.0.0'); + } else { + assert.strictEqual(address, '::'); + } + + wss.close(done); }); }); @@ -70,15 +81,14 @@ describe('WebSocketServer', function () { res.on('data', (chunk) => { body += chunk; }); res.on('end', () => { assert.strictEqual(body, http.STATUS_CODES[426]); - wss.close(); - done(); + wss.close(done); }); }); }); }); // Don't test this on Windows. It throws errors for obvious reasons. - if (!/^win/i.test(process.platform)) { + if (process.platform !== 'win32') { it('uses a precreated http server listening on unix socket', function (done) { const server = http.createServer(); const sockPath = `/tmp/ws_socket_${new Date().getTime()}.${Math.floor(Math.random() * 1000)}`; @@ -110,13 +120,13 @@ describe('WebSocketServer', function () { }); it('will not crash when it receives an unhandled opcode', function (done) { - const wss = new WebSocketServer({ port: 8080 }); + const wss = new WebSocketServer({ port: ++port }); wss.on('connection', (ws) => { - ws.onerror = () => done(); + ws.onerror = () => wss.close(done); }); - const ws = new WebSocket('ws://localhost:8080/'); + const ws = new WebSocket(`ws://localhost:${port}/`); ws.onopen = () => { ws._socket.write(new Buffer([5])); From c917a9dc5f3babd787daa2692dd38279b336ed18 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 16 Nov 2016 15:30:08 +0100 Subject: [PATCH 175/669] [test] Fix failing test --- test/WebSocketServer.test.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 282676fc0..b1f1d192d 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -45,15 +45,7 @@ describe('WebSocketServer', function () { it('binds the server on any IPv6 address when available', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { - const address = wss._server.address().address; - - // The Trusty build environment does not have IPv6 connectivity. - if (process.env.TRAVIS) { - assert.strictEqual(address, '0.0.0.0'); - } else { - assert.strictEqual(address, '::'); - } - + assert.strictEqual(wss._server.address().address, '::'); wss.close(done); }); }); From 04530ad939c1fed63e2e5ef26bca250e8a4b1e7f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 16 Nov 2016 20:15:42 +0100 Subject: [PATCH 176/669] [fix] Don't emit the connection event if socket is closed prematurely Fixes #380 --- lib/WebSocketServer.js | 2 +- test/WebSocketServer.test.js | 38 ++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index f5007000c..249306585 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -221,7 +221,7 @@ function upgrade (req, socket, head, cb) { // allows external modification/inspection of handshake headers this.emit('headers', headers); - if (socket.writable) { + if (socket.readable && socket.writable) { socket.write(headers.concat('', '').join('\r\n')); } else { socket.destroy(); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index b1f1d192d..03a5ca16c 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -5,6 +5,7 @@ const assert = require('assert'); const https = require('https'); const http = require('http'); +const net = require('net'); const fs = require('fs'); const WebSocket = require('..'); @@ -726,6 +727,43 @@ describe('WebSocketServer', function () { }); }); + it('doesn\'t emit the `connection` event if socket is closed prematurely', function (done) { + const server = http.createServer(); + + server.listen(++port, () => { + const wss = new WebSocketServer({ + verifyClient: (o, cb) => setTimeout(cb, 100, true), + server + }); + + wss.on('connection', () => { + throw new Error('connection event emitted'); + }); + + const socket = net.connect({ host: 'localhost', port }, () => { + socket.write([ + 'GET / HTTP/1.1', + 'Host: localhost', + 'Upgrade: websocket', + 'Connection: Upgrade', + 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version: 13', + '', + '' + ].join('\r\n')); + }); + + socket.on('end', () => { + wss.close(); + server.close(done); + }); + + socket.setTimeout(50, () => { + socket.end(); + }); + }); + }); + it('handles messages passed along with the upgrade request (upgrade head)', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const req = http.request({ From 5583051f85f206a154631ce8deeb968437dac680 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 17 Nov 2016 07:37:49 +0100 Subject: [PATCH 177/669] chore(package): update dependencies (#896) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a2e271bee..57f171bc4 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "devDependencies": { "benchmark": "2.1.x", "bufferutil": "1.2.x", - "eslint": "3.9.x", + "eslint": "3.10.x", "eslint-config-semistandard": "7.0.x", "eslint-config-standard": "6.2.x", "eslint-plugin-promise": "3.3.x", From d93e864938c3fd84ae1fbb4b816f81ede0b825f3 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 17 Nov 2016 09:07:04 +0100 Subject: [PATCH 178/669] [doc] Add more badges --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3d7863d60..9b78bb0e5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # ws: a node.js websocket library -[![Build Status](https://travis-ci.org/websockets/ws.svg?branch=master)](https://travis-ci.org/websockets/ws) +[![Version npm](https://img.shields.io/npm/v/ws.svg)](https://www.npmjs.com/package/ws) +[![Build Status](https://img.shields.io/travis/websockets/ws/master.svg)](https://travis-ci.org/websockets/ws) +[![Coverage Status](https://img.shields.io/coveralls/websockets/ws/master.svg)](https://coveralls.io/r/websockets/ws?branch=master) `ws` is a simple to use WebSocket implementation, up-to-date against RFC-6455, and [probably the fastest WebSocket library for node.js][archive]. From 8cd6dd2704f23edfbcfe768ec4c44f41389101f1 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 17 Nov 2016 09:18:49 +0100 Subject: [PATCH 179/669] [travis] Add after-script to send coverage data to coveralls --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 1891cb08d..c9c131ecc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,3 +7,5 @@ node_js: - "4" script: - "npm run test-travis" +after_script: + - "npm install coveralls@2 && cat coverage/lcov.info | coveralls" From f2ac5f151393b9b664f5be59b0a8e06344d790be Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 17 Nov 2016 15:26:44 +0100 Subject: [PATCH 180/669] [codestyle] Fix nit --- test/WebSocketServer.test.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 03a5ca16c..cc28896b7 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -758,9 +758,7 @@ describe('WebSocketServer', function () { server.close(done); }); - socket.setTimeout(50, () => { - socket.end(); - }); + socket.setTimeout(50, () => socket.end()); }); }); From 3e1aa854dee6c1c4c9c1d820ff907356841949ce Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 17 Nov 2016 16:20:36 +0100 Subject: [PATCH 181/669] [minor] Use arrow functions for lexical this in lib/PerMessageDeflate.js --- lib/PerMessageDeflate.js | 112 ++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 59 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 37162c0d1..6c2278d7c 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -98,7 +98,7 @@ class PerMessageDeflate { acceptAsServer (paramsList) { var accepted = {}; - var result = paramsList.some(function (params) { + var result = paramsList.some((params) => { accepted = {}; if (this._options.serverNoContextTakeover === false && params.server_no_context_takeover) { return; @@ -135,7 +135,7 @@ class PerMessageDeflate { accepted.client_max_window_bits = params.client_max_window_bits; } return true; - }, this); + }); if (!result) { throw new Error(`Doesn't support the offered configuration`); @@ -176,8 +176,8 @@ class PerMessageDeflate { */ normalizeParams (paramsList) { - return paramsList.map(function (params) { - Object.keys(params).forEach(function (key) { + return paramsList.map((params) => { + Object.keys(params).forEach((key) => { var value = params[key]; if (value.length > 1) { throw new Error('Multiple extension parameters for ' + key); @@ -209,9 +209,9 @@ class PerMessageDeflate { default: throw new Error(`Not defined extension parameter (${key})`); } - }, this); + }); return params; - }, this); + }); } /** @@ -230,30 +230,14 @@ class PerMessageDeflate { } this._inflate.writeInProgress = true; - var self = this; - var buffers = []; + const buffers = []; var cumulativeBufferLength = 0; - this._inflate.on('error', onError).on('data', onData); - this._inflate.write(data); - if (fin) { - this._inflate.write(TRAILER); - } - this._inflate.flush(function () { - cleanup(); - callback(null, Buffer.concat(buffers)); - }); - - function onError (err) { - cleanup(); - callback(err); - } - - function onData (data) { - if (self._maxPayload > 0) { + const onData = (data) => { + if (this._maxPayload > 0) { cumulativeBufferLength += data.length; - if (cumulativeBufferLength > self._maxPayload) { - const err = new Error(`payload cannot exceed ${self._maxPayload} bytes`); + if (cumulativeBufferLength > this._maxPayload) { + const err = new Error(`payload cannot exceed ${this._maxPayload} bytes`); err.closeCode = 1009; buffers.length = 0; cleanup(); @@ -262,18 +246,33 @@ class PerMessageDeflate { } } buffers.push(data); - } + }; - function cleanup () { - if (!self._inflate) return; - self._inflate.removeListener('error', onError); - self._inflate.removeListener('data', onData); - self._inflate.writeInProgress = false; - if ((fin && self.params[endpoint + '_no_context_takeover']) || self._inflate.pendingClose) { - self._inflate.close(); - self._inflate = null; + const onError = (err) => { + cleanup(); + callback(err); + }; + + const cleanup = () => { + if (!this._inflate) return; + this._inflate.removeListener('error', onError); + this._inflate.removeListener('data', onData); + this._inflate.writeInProgress = false; + if ((fin && this.params[endpoint + '_no_context_takeover']) || this._inflate.pendingClose) { + this._inflate.close(); + this._inflate = null; } + }; + + this._inflate.on('error', onError).on('data', onData); + this._inflate.write(data); + if (fin) { + this._inflate.write(TRAILER); } + this._inflate.flush(() => { + cleanup(); + callback(null, Buffer.concat(buffers)); + }); } /** @@ -299,12 +298,27 @@ class PerMessageDeflate { } this._deflate.writeInProgress = true; - var self = this; - var buffers = []; + const buffers = []; + + const onData = (data) => buffers.push(data); + const onError = (err) => { + cleanup(); + callback(err); + }; + const cleanup = () => { + if (!this._deflate) return; + this._deflate.removeListener('error', onError); + this._deflate.removeListener('data', onData); + this._deflate.writeInProgress = false; + if ((fin && this.params[endpoint + '_no_context_takeover']) || this._deflate.pendingClose) { + this._deflate.close(); + this._deflate = null; + } + }; this._deflate.on('error', onError).on('data', onData); this._deflate.write(data); - this._deflate.flush(zlib.Z_SYNC_FLUSH, function () { + this._deflate.flush(zlib.Z_SYNC_FLUSH, () => { cleanup(); var data = Buffer.concat(buffers); if (fin) { @@ -312,26 +326,6 @@ class PerMessageDeflate { } callback(null, data); }); - - function onError (err) { - cleanup(); - callback(err); - } - - function onData (data) { - buffers.push(data); - } - - function cleanup () { - if (!self._deflate) return; - self._deflate.removeListener('error', onError); - self._deflate.removeListener('data', onData); - self._deflate.writeInProgress = false; - if ((fin && self.params[endpoint + '_no_context_takeover']) || self._deflate.pendingClose) { - self._deflate.close(); - self._deflate = null; - } - } } } From 44950b668fab76a1baa17f43075992c7b8abfb90 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 17 Nov 2016 17:38:46 +0100 Subject: [PATCH 182/669] [test] Fix flaky test --- test/WebSocketServer.test.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index cc28896b7..0f832f55f 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -30,10 +30,11 @@ describe('WebSocketServer', function () { }); it('emits an error if http server bind fails', function (done) { - const wss1 = new WebSocketServer({ port: 50003 }); - const wss2 = new WebSocketServer({ port: 50003 }); + const wss1 = new WebSocketServer({ port: 50003 }, () => { + const wss2 = new WebSocketServer({ port: 50003 }); - wss2.on('error', () => wss1.close(done)); + wss2.on('error', () => wss1.close(done)); + }); }); it('starts a server on a given port', function (done) { From 40291cb8de609ab58ee5d4e9003b8e2fc993f745 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 17 Nov 2016 15:00:04 +0100 Subject: [PATCH 183/669] [minor] Move `completeUpgrade` to `WebSocketServer.prototype` --- lib/WebSocketServer.js | 227 ++++++++++++++++++++++------------------- 1 file changed, 123 insertions(+), 104 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 249306585..08ae3feb9 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -17,6 +17,8 @@ const PerMessageDeflate = require('./PerMessageDeflate'); const Extensions = require('./Extensions'); const WebSocket = require('./WebSocket'); +const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; + /** * Create a `WebSocketServer` instance. * @@ -142,110 +144,25 @@ WebSocketServer.prototype.shouldHandle = function (req) { * @public */ WebSocketServer.prototype.handleUpgrade = function (req, socket, head, cb) { + socket.on('error', socketError); + + const version = +req.headers['sec-websocket-version']; + if ( !this.shouldHandle(req) || !req.headers.upgrade || req.headers.upgrade.toLowerCase() !== 'websocket' || - !req.headers['sec-websocket-key'] + !req.headers['sec-websocket-key'] || + version !== 8 && version !== 13 ) { return abortConnection(socket, 400); } - socket.on('error', socketError); - upgrade.apply(this, arguments); -}; - -module.exports = WebSocketServer; - -/** - * Handle premature socket errors. - * - * @private - */ -function socketError () { - this.destroy(); -} - -/** - * Upgrade the connection to WebSocket. - * - * @param {http.IncomingMessage} req The request object - * @param {net.Socket} socket The network socket between the server and client - * @param {Buffer} head The first packet of the upgraded stream - * @param {Function} cb Callback - * @private - */ -function upgrade (req, socket, head, cb) { - const version = +req.headers['sec-websocket-version']; - - if (version !== 8 && version !== 13) return abortConnection(socket, 400); - var protocol = (req.headers['sec-websocket-protocol'] || '').split(/, */); - // handler to call when the connection sequence completes - const completeUpgrade = () => { - // calc key - const key = crypto.createHash('sha1') - .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'binary') - .digest('base64'); - - const headers = [ - 'HTTP/1.1 101 Switching Protocols', - 'Upgrade: websocket', - 'Connection: Upgrade', - `Sec-WebSocket-Accept: ${key}` - ]; - - if (protocol) { - headers.push(`Sec-WebSocket-Protocol: ${protocol}`); - } - - const offer = Extensions.parse(req.headers['sec-websocket-extensions']); - var extensions; - - try { - extensions = acceptExtensions.call(this, offer); - } catch (err) { - return abortConnection(socket, 400); - } - - if (Object.keys(extensions).length) { - const serverExtensions = Object.keys(extensions).reduce((obj, key) => { - obj[key] = [extensions[key].params]; - return obj; - }, {}); - - headers.push(`Sec-WebSocket-Extensions: ${Extensions.format(serverExtensions)}`); - } - - // allows external modification/inspection of handshake headers - this.emit('headers', headers); - - if (socket.readable && socket.writable) { - socket.write(headers.concat('', '').join('\r\n')); - } else { - socket.destroy(); - return; - } - - const client = new WebSocket([req, socket, head], { - maxPayload: this.options.maxPayload, - protocolVersion: version, - extensions, - protocol - }); - - if (this.clients) { - this.clients.add(client); - client.on('close', () => this.clients.delete(client)); - } - - // signal upgrade complete - socket.removeListener('error', socketError); - cb(client); - }; - - // optionally call external protocol selection handler + // + // Optionally call external protocol selection handler. + // if (this.options.handleProtocols) { protocol = this.options.handleProtocols(protocol); if (protocol === false) return abortConnection(socket, 401); @@ -253,7 +170,9 @@ function upgrade (req, socket, head, cb) { protocol = protocol[0]; } - // optionally call external client verification handler + // + // Optionally call external client verification handler. + // if (this.options.verifyClient) { const info = { origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`], @@ -265,7 +184,7 @@ function upgrade (req, socket, head, cb) { this.options.verifyClient(info, (verified, code, message) => { if (!verified) return abortConnection(socket, code || 401, message); - completeUpgrade(); + this.completeUpgrade(protocol, version, req, socket, head, cb); }); return; } else if (!this.options.verifyClient(info)) { @@ -273,18 +192,116 @@ function upgrade (req, socket, head, cb) { } } - completeUpgrade(); + this.completeUpgrade(protocol, version, req, socket, head, cb); +}; + +/** + * Upgrade the connection to WebSocket. + * + * @param {String} protocol The chosen subprotocol + * @param {Number} version The WebSocket protocol version + * @param {http.IncomingMessage} req The request object + * @param {net.Socket} socket The network socket between the server and client + * @param {Buffer} head The first packet of the upgraded stream + * @param {Function} cb Callback + * @private + */ +WebSocketServer.prototype.completeUpgrade = function (protocol, version, req, socket, head, cb) { + // + // Destroy the socket if the client has already sent a FIN packet. + // + if (!socket.readable || !socket.writable) return socket.destroy(); + + const key = crypto.createHash('sha1') + .update(req.headers['sec-websocket-key'] + GUID, 'binary') + .digest('base64'); + + const headers = [ + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: websocket', + 'Connection: Upgrade', + `Sec-WebSocket-Accept: ${key}` + ]; + + if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); + + const offer = Extensions.parse(req.headers['sec-websocket-extensions']); + var extensions; + + try { + extensions = acceptExtensions(this.options, offer); + } catch (err) { + return abortConnection(socket, 400); + } + + const props = Object.keys(extensions); + + if (props.length) { + const serverExtensions = props.reduce((obj, key) => { + obj[key] = [extensions[key].params]; + return obj; + }, {}); + + headers.push(`Sec-WebSocket-Extensions: ${Extensions.format(serverExtensions)}`); + } + + // + // Allow external modification/inspection of handshake headers. + // + this.emit('headers', headers); + + socket.write(headers.concat('', '').join('\r\n')); + + const client = new WebSocket([req, socket, head], { + maxPayload: this.options.maxPayload, + protocolVersion: version, + extensions, + protocol + }); + + if (this.clients) { + this.clients.add(client); + client.on('close', () => this.clients.delete(client)); + } + + socket.removeListener('error', socketError); + cb(client); +}; + +module.exports = WebSocketServer; + +/** + * Handle premature socket errors. + * + * @private + */ +function socketError () { + this.destroy(); } -function acceptExtensions (offer) { - var extensions = {}; - var options = this.options.perMessageDeflate; - var maxPayload = this.options.maxPayload; - if (options && offer[PerMessageDeflate.extensionName]) { - var perMessageDeflate = new PerMessageDeflate(options !== true ? options : {}, true, maxPayload); +/** + * Accept WebSocket extensions. + * + * @param {Object} options The `WebSocketServer` configuration options + * @param {Object} offer The parsed value of the `sec-websocket-extensions` header + * @return {Object} Accepted extensions + * @private + */ +function acceptExtensions (options, offer) { + const pmd = options.perMessageDeflate; + const extensions = {}; + + if (pmd && offer[PerMessageDeflate.extensionName]) { + const perMessageDeflate = new PerMessageDeflate( + pmd !== true ? pmd : {}, + true, + options.maxPayload + ); + perMessageDeflate.accept(offer[PerMessageDeflate.extensionName]); extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } + return extensions; } @@ -294,7 +311,7 @@ function acceptExtensions (offer) { * @param {net.Socket} socket The socket of the upgrade request * @param {Number} code The HTTP response status code * @param {String} [message] The HTTP response body - * @api private + * @private */ function abortConnection (socket, code, message) { if (socket.writable) { @@ -308,5 +325,7 @@ function abortConnection (socket, code, message) { message ); } + + socket.removeListener('error', socketError); socket.destroy(); } From ef0a379d88dbf6d951c6880f9b8f506437a4ca84 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 17 Nov 2016 19:17:04 +0000 Subject: [PATCH 184/669] chore(package): update eslint-plugin-promise to version 3.4.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 57f171bc4..93092d2ff 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "eslint": "3.10.x", "eslint-config-semistandard": "7.0.x", "eslint-config-standard": "6.2.x", - "eslint-plugin-promise": "3.3.x", + "eslint-plugin-promise": "~3.4.0", "eslint-plugin-standard": "2.0.x", "istanbul": "0.4.x", "mocha": "3.1.x", From 199c248a4a65d175fdcb351ff1d29c87debaf855 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 18 Nov 2016 10:43:40 +0100 Subject: [PATCH 185/669] [minor] Remove redundant checks --- lib/WebSocket.js | 38 +++++++++++--------------------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 511a63f9d..092f776cb 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -773,18 +773,18 @@ function initAsClient (address, protocols, options) { } function establishConnection (socket, upgradeHead) { - var ultron = this._ultron = new Ultron(socket); - socket.setTimeout(0); - socket.setNoDelay(true); + socket.setNoDelay(); this._receiver = new Receiver(this.extensions, this.maxPayload); + this._sender = new Sender(socket, this.extensions); + this._ultron = new Ultron(socket); this._socket = socket; // socket cleanup handlers - ultron.on('end', cleanupWebsocketResources.bind(this)); - ultron.on('close', cleanupWebsocketResources.bind(this)); - ultron.on('error', cleanupWebsocketResources.bind(this)); + this._ultron.on('end', cleanupWebsocketResources.bind(this)); + this._ultron.on('close', cleanupWebsocketResources.bind(this)); + this._ultron.on('error', cleanupWebsocketResources.bind(this)); // ensure that the upgradeHead is added to the receiver if (upgradeHead && upgradeHead.length > 0) { @@ -793,49 +793,33 @@ function establishConnection (socket, upgradeHead) { } // subsequent packets are pushed to the receiver - ultron.on('data', (data) => { + this._ultron.on('data', (data) => { this.bytesReceived += data.length; this._receiver.add(data); }); // receiver event handlers - this._receiver.ontext = (data, flags) => this.emit('message', data, flags || {}); - + this._receiver.ontext = (data, flags) => this.emit('message', data, flags); this._receiver.onbinary = (data, flags) => { - flags = flags || {}; flags.binary = true; - this.emit('message', data, flags); }; - this._receiver.onping = (data, flags) => { - flags = flags || {}; - - this.pong(data, { - mask: !this._isServer, - binary: flags.binary === true - }, true); - + this.pong(data, { mask: !this._isServer }, true); this.emit('ping', data, flags); }; - - this._receiver.onpong = (data, flags) => this.emit('pong', data, flags || {}); - + this._receiver.onpong = (data, flags) => this.emit('pong', data, flags); this._receiver.onclose = (code, data, flags) => { - flags = flags || {}; - this._closeReceived = true; this.close(code, data); }; - this._receiver.onerror = (error, errorCode) => { // close the connection when the receiver reports a HyBi error code this.close(errorCode, ''); this.emit('error', error); }; - // finalize the client - this._sender = new Sender(socket, this.extensions); + // sender event handlers this._sender.onerror = (error) => { this.close(1002, ''); this.emit('error', error); From e4e6163425896e9e8a4561f01e7566de33334574 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 23 Nov 2016 09:15:57 +0100 Subject: [PATCH 186/669] [major] Refactor `WebSocketServer` to use class syntax (#903) --- lib/WebSocketServer.js | 434 +++++++++++++++++------------------ test/WebSocketServer.test.js | 6 - 2 files changed, 215 insertions(+), 225 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 08ae3feb9..a0024760e 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -10,7 +10,6 @@ const EventEmitter = require('events'); const crypto = require('crypto'); const Ultron = require('ultron'); const http = require('http'); -const util = require('util'); const url = require('url'); const PerMessageDeflate = require('./PerMessageDeflate'); @@ -20,253 +19,250 @@ const WebSocket = require('./WebSocket'); const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; /** - * Create a `WebSocketServer` instance. - * - * @param {Object} options Configuration options - * @param {String} options.host The hostname where to bind the server - * @param {Number} options.port The port where to bind the server - * @param {http.Server} options.server A pre-created HTTP/S server to use - * @param {Function} options.verifyClient An hook to reject connections - * @param {Function} options.handleProtocols An hook to handle protocols - * @param {String} options.path Accept only connections matching this path - * @param {Boolean} options.noServer Enable no server mode - * @param {Boolean} options.clientTracking Specifies whether or not to track clients - * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate - * @param {Number} options.maxPayload The maximum allowed message size - * @param {Function} callback A listener for the `listening` event - * @constructor - * @public + * Class representing a WebSocket server. */ -function WebSocketServer (options, callback) { - if (this instanceof WebSocketServer === false) { - return new WebSocketServer(options, callback); - } - - EventEmitter.call(this); - - options = Object.assign({ - maxPayload: 100 * 1024 * 1024, - perMessageDeflate: true, - handleProtocols: null, - clientTracking: true, - verifyClient: null, - noServer: false, - backlog: null, // use default (511 as implemented in net.js) - server: null, - host: null, - path: null, - port: null - }, options); - - if (options.port == null && !options.server && !options.noServer) { - throw new TypeError('missing or invalid options'); - } +class WebSocketServer extends EventEmitter { + /** + * Create a `WebSocketServer` instance. + * + * @param {Object} options Configuration options + * @param {String} options.host The hostname where to bind the server + * @param {Number} options.port The port where to bind the server + * @param {http.Server} options.server A pre-created HTTP/S server to use + * @param {Function} options.verifyClient An hook to reject connections + * @param {Function} options.handleProtocols An hook to handle protocols + * @param {String} options.path Accept only connections matching this path + * @param {Boolean} options.noServer Enable no server mode + * @param {Boolean} options.clientTracking Specifies whether or not to track clients + * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate + * @param {Number} options.maxPayload The maximum allowed message size + * @param {Function} callback A listener for the `listening` event + */ + constructor (options, callback) { + super(); + + options = Object.assign({ + maxPayload: 100 * 1024 * 1024, + perMessageDeflate: true, + handleProtocols: null, + clientTracking: true, + verifyClient: null, + noServer: false, + backlog: null, // use default (511 as implemented in net.js) + server: null, + host: null, + path: null, + port: null + }, options); + + if (options.port == null && !options.server && !options.noServer) { + throw new TypeError('missing or invalid options'); + } - if (options.port != null) { - this._server = http.createServer((req, res) => { - const body = http.STATUS_CODES[426]; + if (options.port != null) { + this._server = http.createServer((req, res) => { + const body = http.STATUS_CODES[426]; - res.writeHead(426, { - 'Content-Length': body.length, - 'Content-Type': 'text/plain' + res.writeHead(426, { + 'Content-Length': body.length, + 'Content-Type': 'text/plain' + }); + res.end(body); }); - res.end(body); - }); - this._server.allowHalfOpen = false; - this._server.listen(options.port, options.host, options.backlog, callback); - } else if (options.server) { - this._server = options.server; - } + this._server.allowHalfOpen = false; + this._server.listen(options.port, options.host, options.backlog, callback); + } else if (options.server) { + this._server = options.server; + } - if (this._server) { - this._ultron = new Ultron(this._server); - this._ultron.on('listening', () => this.emit('listening')); - this._ultron.on('error', (err) => this.emit('error', err)); - this._ultron.on('upgrade', (req, socket, head) => { - this.handleUpgrade(req, socket, head, (client) => { - this.emit(`connection${req.url}`, client); - this.emit('connection', client); + if (this._server) { + this._ultron = new Ultron(this._server); + this._ultron.on('listening', () => this.emit('listening')); + this._ultron.on('error', (err) => this.emit('error', err)); + this._ultron.on('upgrade', (req, socket, head) => { + this.handleUpgrade(req, socket, head, (client) => { + this.emit(`connection${req.url}`, client); + this.emit('connection', client); + }); }); - }); - } - - if (options.clientTracking) this.clients = new Set(); - this.options = options; - this.path = options.path; -} - -util.inherits(WebSocketServer, EventEmitter); + } -/** - * Close the server. - * - * @param {Function} cb Callback - * @public - */ -WebSocketServer.prototype.close = function (cb) { - // terminate all associated clients - if (this.clients) { - for (const client of this.clients) client.terminate(); + if (options.clientTracking) this.clients = new Set(); + this.options = options; + this.path = options.path; } - if (this._server) { - // close the http server if it was internally created - if (this.options.port != null) this._server.close(cb); - this._ultron.destroy(); - this._ultron = this._server = null; - } else if (cb) { - cb(); - } -}; + /** + * Close the server. + * + * @param {Function} cb Callback + * @public + */ + close (cb) { + // terminate all associated clients + if (this.clients) { + for (const client of this.clients) client.terminate(); + } -/** - * See if a given request should be handled by this server instance. - * - * @param {http.IncomingMessage} req Request object to inspect - * @return {Boolean} `true` if the request is valid, else `false` - * @public - */ -WebSocketServer.prototype.shouldHandle = function (req) { - if (this.options.path && url.parse(req.url).pathname !== this.options.path) { - return false; + if (this._server) { + // close the http server if it was internally created + if (this.options.port != null) this._server.close(cb); + this._ultron.destroy(); + this._ultron = this._server = null; + } else if (cb) { + cb(); + } } - return true; -}; + /** + * See if a given request should be handled by this server instance. + * + * @param {http.IncomingMessage} req Request object to inspect + * @return {Boolean} `true` if the request is valid, else `false` + * @public + */ + shouldHandle (req) { + if (this.options.path && url.parse(req.url).pathname !== this.options.path) { + return false; + } -/** - * Handle a HTTP Upgrade request. - * - * @param {http.IncomingMessage} req The request object - * @param {net.Socket} socket The network socket between the server and client - * @param {Buffer} head The first packet of the upgraded stream - * @param {Function} cb Callback - * @public - */ -WebSocketServer.prototype.handleUpgrade = function (req, socket, head, cb) { - socket.on('error', socketError); - - const version = +req.headers['sec-websocket-version']; - - if ( - !this.shouldHandle(req) || - !req.headers.upgrade || - req.headers.upgrade.toLowerCase() !== 'websocket' || - !req.headers['sec-websocket-key'] || - version !== 8 && version !== 13 - ) { - return abortConnection(socket, 400); + return true; } - var protocol = (req.headers['sec-websocket-protocol'] || '').split(/, */); + /** + * Handle a HTTP Upgrade request. + * + * @param {http.IncomingMessage} req The request object + * @param {net.Socket} socket The network socket between the server and client + * @param {Buffer} head The first packet of the upgraded stream + * @param {Function} cb Callback + * @public + */ + handleUpgrade (req, socket, head, cb) { + socket.on('error', socketError); + + const version = +req.headers['sec-websocket-version']; + + if ( + !this.shouldHandle(req) || + !req.headers.upgrade || + req.headers.upgrade.toLowerCase() !== 'websocket' || + !req.headers['sec-websocket-key'] || + version !== 8 && version !== 13 + ) { + return abortConnection(socket, 400); + } - // - // Optionally call external protocol selection handler. - // - if (this.options.handleProtocols) { - protocol = this.options.handleProtocols(protocol); - if (protocol === false) return abortConnection(socket, 401); - } else { - protocol = protocol[0]; - } + var protocol = (req.headers['sec-websocket-protocol'] || '').split(/, */); - // - // Optionally call external client verification handler. - // - if (this.options.verifyClient) { - const info = { - origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`], - secure: !!(req.connection.authorized || req.connection.encrypted), - req - }; - - if (this.options.verifyClient.length === 2) { - this.options.verifyClient(info, (verified, code, message) => { - if (!verified) return abortConnection(socket, code || 401, message); - - this.completeUpgrade(protocol, version, req, socket, head, cb); - }); - return; - } else if (!this.options.verifyClient(info)) { - return abortConnection(socket, 401); + // + // Optionally call external protocol selection handler. + // + if (this.options.handleProtocols) { + protocol = this.options.handleProtocols(protocol); + if (protocol === false) return abortConnection(socket, 401); + } else { + protocol = protocol[0]; } - } - this.completeUpgrade(protocol, version, req, socket, head, cb); -}; + // + // Optionally call external client verification handler. + // + if (this.options.verifyClient) { + const info = { + origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`], + secure: !!(req.connection.authorized || req.connection.encrypted), + req + }; + + if (this.options.verifyClient.length === 2) { + this.options.verifyClient(info, (verified, code, message) => { + if (!verified) return abortConnection(socket, code || 401, message); + + this.completeUpgrade(protocol, version, req, socket, head, cb); + }); + return; + } else if (!this.options.verifyClient(info)) { + return abortConnection(socket, 401); + } + } -/** - * Upgrade the connection to WebSocket. - * - * @param {String} protocol The chosen subprotocol - * @param {Number} version The WebSocket protocol version - * @param {http.IncomingMessage} req The request object - * @param {net.Socket} socket The network socket between the server and client - * @param {Buffer} head The first packet of the upgraded stream - * @param {Function} cb Callback - * @private - */ -WebSocketServer.prototype.completeUpgrade = function (protocol, version, req, socket, head, cb) { - // - // Destroy the socket if the client has already sent a FIN packet. - // - if (!socket.readable || !socket.writable) return socket.destroy(); - - const key = crypto.createHash('sha1') - .update(req.headers['sec-websocket-key'] + GUID, 'binary') - .digest('base64'); - - const headers = [ - 'HTTP/1.1 101 Switching Protocols', - 'Upgrade: websocket', - 'Connection: Upgrade', - `Sec-WebSocket-Accept: ${key}` - ]; - - if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); - - const offer = Extensions.parse(req.headers['sec-websocket-extensions']); - var extensions; - - try { - extensions = acceptExtensions(this.options, offer); - } catch (err) { - return abortConnection(socket, 400); + this.completeUpgrade(protocol, version, req, socket, head, cb); } - const props = Object.keys(extensions); + /** + * Upgrade the connection to WebSocket. + * + * @param {String} protocol The chosen subprotocol + * @param {Number} version The WebSocket protocol version + * @param {http.IncomingMessage} req The request object + * @param {net.Socket} socket The network socket between the server and client + * @param {Buffer} head The first packet of the upgraded stream + * @param {Function} cb Callback + * @private + */ + completeUpgrade (protocol, version, req, socket, head, cb) { + // + // Destroy the socket if the client has already sent a FIN packet. + // + if (!socket.readable || !socket.writable) return socket.destroy(); + + const key = crypto.createHash('sha1') + .update(req.headers['sec-websocket-key'] + GUID, 'binary') + .digest('base64'); + + const headers = [ + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: websocket', + 'Connection: Upgrade', + `Sec-WebSocket-Accept: ${key}` + ]; + + if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); + + const offer = Extensions.parse(req.headers['sec-websocket-extensions']); + var extensions; + + try { + extensions = acceptExtensions(this.options, offer); + } catch (err) { + return abortConnection(socket, 400); + } - if (props.length) { - const serverExtensions = props.reduce((obj, key) => { - obj[key] = [extensions[key].params]; - return obj; - }, {}); + const props = Object.keys(extensions); - headers.push(`Sec-WebSocket-Extensions: ${Extensions.format(serverExtensions)}`); - } + if (props.length) { + const serverExtensions = props.reduce((obj, key) => { + obj[key] = [extensions[key].params]; + return obj; + }, {}); - // - // Allow external modification/inspection of handshake headers. - // - this.emit('headers', headers); + headers.push(`Sec-WebSocket-Extensions: ${Extensions.format(serverExtensions)}`); + } - socket.write(headers.concat('', '').join('\r\n')); + // + // Allow external modification/inspection of handshake headers. + // + this.emit('headers', headers); - const client = new WebSocket([req, socket, head], { - maxPayload: this.options.maxPayload, - protocolVersion: version, - extensions, - protocol - }); + socket.write(headers.concat('', '').join('\r\n')); - if (this.clients) { - this.clients.add(client); - client.on('close', () => this.clients.delete(client)); - } + const client = new WebSocket([req, socket, head], { + maxPayload: this.options.maxPayload, + protocolVersion: version, + extensions, + protocol + }); - socket.removeListener('error', socketError); - cb(client); -}; + if (this.clients) { + this.clients.add(client); + client.on('close', () => this.clients.delete(client)); + } + + socket.removeListener('error', socketError); + cb(client); + } +} module.exports = WebSocketServer; diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 0f832f55f..e79aa5257 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -23,12 +23,6 @@ describe('WebSocketServer', function () { assert.throws(() => new WebSocketServer({})); }); - it('should return a new instance if called without new', function () { - const wss = WebSocketServer({ noServer: true }); - - assert.ok(wss instanceof WebSocketServer); - }); - it('emits an error if http server bind fails', function (done) { const wss1 = new WebSocketServer({ port: 50003 }, () => { const wss2 = new WebSocketServer({ port: 50003 }); From d2997e733fc7f0ce362b86d1ce49f429cf215dd1 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 23 Nov 2016 09:16:43 +0100 Subject: [PATCH 187/669] [minor] Rename some methods of the `Sender` class (#905) --- lib/Sender.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index de6fda611..792af4852 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -6,9 +6,9 @@ 'use strict'; -const ErrorCodes = require('./ErrorCodes'); -const bufferUtil = require('./BufferUtil').BufferUtil; const PerMessageDeflate = require('./PerMessageDeflate'); +const bufferUtil = require('./BufferUtil').BufferUtil; +const ErrorCodes = require('./ErrorCodes'); /** * HyBi Sender implementation. @@ -21,13 +21,13 @@ class Sender { * @param {Object} extensions An object containing the negotiated extensions */ constructor (socket, extensions) { - this._socket = socket; this.extensions = extensions || {}; this.firstFragment = true; - this.compress = false; - this.messageHandlers = []; this.processing = false; + this.compress = false; + this._socket = socket; this.onerror = null; + this.queue = []; } /** @@ -67,7 +67,7 @@ class Sender { doClose (data, mask, cb) { this.frameAndSend(0x08, data, false, true, mask, false); if (this.extensions[PerMessageDeflate.extensionName]) { - this.messageHandlerCallback(); + this.continue(); } if (cb) cb(); } @@ -98,7 +98,7 @@ class Sender { doPing (data, mask) { this.frameAndSend(0x09, data, true, true, mask, false); if (this.extensions[PerMessageDeflate.extensionName]) { - this.messageHandlerCallback(); + this.continue(); } } @@ -128,7 +128,7 @@ class Sender { doPong (data, mask) { this.frameAndSend(0x0a, data, true, true, mask, false); if (this.extensions[PerMessageDeflate.extensionName]) { - this.messageHandlerCallback(); + this.continue(); } } @@ -182,7 +182,7 @@ class Sender { sendCompressed (opcode, data, fin, mask, rsv1, cb) { if (!this.compress) { this.frameAndSend(opcode, data, true, fin, mask, false, cb); - this.messageHandlerCallback(); + this.continue(); return; } @@ -195,7 +195,7 @@ class Sender { } this.frameAndSend(opcode, buf, false, fin, mask, rsv1, cb); - this.messageHandlerCallback(); + this.continue(); }); } @@ -289,10 +289,10 @@ class Sender { * * @private */ - flush () { + dequeue () { if (this.processing) return; - var handler = this.messageHandlers.shift(); + const handler = this.queue.shift(); if (!handler) return; this.processing = true; @@ -305,10 +305,10 @@ class Sender { * * @private */ - messageHandlerCallback () { + continue () { process.nextTick(() => { this.processing = false; - this.flush(); + this.dequeue(); }); } @@ -319,8 +319,8 @@ class Sender { * @private */ enqueue (params) { - this.messageHandlers.push(params); - this.flush(); + this.queue.push(params); + this.dequeue(); } } From abc5c965b531f4ddce5c02478f941b13ec5f1996 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 25 Nov 2016 08:48:32 +0100 Subject: [PATCH 188/669] chore(package): update mocha to version 3.2.0 (#912) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 93092d2ff..a9eae337f 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "eslint-plugin-promise": "~3.4.0", "eslint-plugin-standard": "2.0.x", "istanbul": "0.4.x", - "mocha": "3.1.x", + "mocha": "~3.2.0", "utf-8-validate": "1.2.x" } } From 269dff8bfe99437c77f5ef558278acb120f929ec Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 25 Nov 2016 10:48:33 +0100 Subject: [PATCH 189/669] [deps] Use tilde ranges --- package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index a9eae337f..972d00e1b 100644 --- a/package.json +++ b/package.json @@ -25,18 +25,18 @@ "lint": "eslint ." }, "dependencies": { - "ultron": "1.0.x" + "ultron": "~1.0.2" }, "devDependencies": { - "benchmark": "2.1.x", - "bufferutil": "1.2.x", - "eslint": "3.10.x", - "eslint-config-semistandard": "7.0.x", - "eslint-config-standard": "6.2.x", + "benchmark": "~2.1.2", + "bufferutil": "~1.2.1", + "eslint": "~3.10.2", + "eslint-config-semistandard": "~7.0.0", + "eslint-config-standard": "~6.2.1", "eslint-plugin-promise": "~3.4.0", - "eslint-plugin-standard": "2.0.x", - "istanbul": "0.4.x", + "eslint-plugin-standard": "~2.0.1", + "istanbul": "~0.4.5", "mocha": "~3.2.0", - "utf-8-validate": "1.2.x" + "utf-8-validate": "~1.2.1" } } From 7253f06f5432c76f3e82e2c055fcea08b612d8b2 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 20 Sep 2016 08:59:58 -0600 Subject: [PATCH 190/669] [fix] Use `crypto.randomBytes()` to generate the masking key --- lib/Sender.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 792af4852..e0393bc65 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -6,6 +6,8 @@ 'use strict'; +const crypto = require('crypto'); + const PerMessageDeflate = require('./PerMessageDeflate'); const bufferUtil = require('./BufferUtil').BufferUtil; const ErrorCodes = require('./ErrorCodes'); @@ -364,12 +366,7 @@ function toBuffer (data) { * @private */ function getRandomMask () { - return new Buffer([ - ~~(Math.random() * 255), - ~~(Math.random() * 255), - ~~(Math.random() * 255), - ~~(Math.random() * 255) - ]); + return crypto.randomBytes(4); } /** From 078e96a31dd7c1c9fee9f51db806e91db7b5aeac Mon Sep 17 00:00:00 2001 From: Hans-Peter Herzog Date: Sat, 6 Aug 2016 17:32:09 +0200 Subject: [PATCH 191/669] [feature] Add removeEventListener method to WebSocket interface --- lib/WebSocket.js | 18 ++++++++++++++++++ test/WebSocket.test.js | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 092f776cb..bb00ff3a1 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -470,6 +470,24 @@ WebSocket.prototype.addEventListener = function (method, listener) { } }; +/** + * Removes an event listener previously registered with `addEventListener`. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener + * @param {String} method A string representing the event type to remove + * @param {Function} listener The listener to remove + * @public + */ +WebSocket.prototype.removeEventListener = function (method, listener) { + const listeners = this.listeners(method); + + for (var i = 0; i < listeners.length; i++) { + if (listeners[i]._listener === listener) { + this.removeListener(method, listeners[i]); + } + } +}; + module.exports = WebSocket; module.exports.buildHostHeader = buildHostHeader; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 52612c533..a9cefbf9c 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1618,6 +1618,41 @@ describe('WebSocket', function () { }); }); + it('should remove event listeners added with addEventListener', function (done) { + server.createServer(++port, (srv) => { + const message = () => {}; + const open = () => {}; + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.addEventListener('message', message); + ws.addEventListener('open', open); + + assert.notStrictEqual( + ws.listeners('message').find((listener) => listener._listener === message), + undefined + ); + assert.notStrictEqual( + ws.listeners('open').find((listener) => listener._listener === open), + undefined + ); + + ws.removeEventListener('message', message); + ws.removeEventListener('open', open); + + assert.strictEqual( + ws.listeners('message').find((listener) => listener._listener === message), + undefined + ); + assert.strictEqual( + ws.listeners('open').find((listener) => listener._listener === open), + undefined + ); + + srv.close(done); + ws.close(); + }); + }); + it('should receive valid CloseEvent when server closes with code 1000', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); From 2dc201d4587a2f24b37f16df2aa166879ce30367 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 25 Nov 2016 17:13:40 +0100 Subject: [PATCH 192/669] [test] Simplify `removeEventListener` test --- test/WebSocket.test.js | 53 +++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index a9cefbf9c..14bd00abb 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -325,8 +325,8 @@ describe('WebSocket', function () { }); it('can handle error before request is upgraded', function (done) { - // Here, we don't create a server, to guarantee that the connection will - // fail before the request is upgraded + // Here, we don't create a server, to guarantee that the connection will + // fail before the request is upgraded const ws = new WebSocket(`ws://localhost:${++port}`); ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); @@ -1618,39 +1618,24 @@ describe('WebSocket', function () { }); }); - it('should remove event listeners added with addEventListener', function (done) { - server.createServer(++port, (srv) => { - const message = () => {}; - const open = () => {}; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.addEventListener('message', message); - ws.addEventListener('open', open); - - assert.notStrictEqual( - ws.listeners('message').find((listener) => listener._listener === message), - undefined - ); - assert.notStrictEqual( - ws.listeners('open').find((listener) => listener._listener === open), - undefined - ); - - ws.removeEventListener('message', message); - ws.removeEventListener('open', open); - - assert.strictEqual( - ws.listeners('message').find((listener) => listener._listener === message), - undefined - ); - assert.strictEqual( - ws.listeners('open').find((listener) => listener._listener === open), - undefined - ); + it('should remove event listeners added with addEventListener', function () { + const message = () => {}; + const open = () => {}; + const ws = new WebSocket(`ws://localhost:${++port}`); - srv.close(done); - ws.close(); - }); + ws.on('error', () => {}); + + ws.addEventListener('message', message); + ws.addEventListener('open', open); + + assert.strictEqual(ws.listeners('message')[0]._listener, message); + assert.strictEqual(ws.listeners('open')[0]._listener, open); + + ws.removeEventListener('message', message); + ws.removeEventListener('open', open); + + assert.strictEqual(ws.listeners('message').length, 0); + assert.strictEqual(ws.listeners('open').length, 0); }); it('should receive valid CloseEvent when server closes with code 1000', function (done) { From 2a4e78f84946a86bcb1c2a059981470ebd420ab0 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 25 Nov 2016 22:23:48 +0000 Subject: [PATCH 193/669] chore(package): update eslint to version 3.11.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 972d00e1b..a28987fe9 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~1.2.1", - "eslint": "~3.10.2", + "eslint": "~3.11.0", "eslint-config-semistandard": "~7.0.0", "eslint-config-standard": "~6.2.1", "eslint-plugin-promise": "~3.4.0", From d3b98d4428a64401fe9f1e57a6bab95b8ab43f51 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 27 Nov 2016 08:32:28 +0100 Subject: [PATCH 194/669] chore(package): update bufferutil to version 1.3.0 (#915) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a28987fe9..162b61c5b 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "devDependencies": { "benchmark": "~2.1.2", - "bufferutil": "~1.2.1", + "bufferutil": "~1.3.0", "eslint": "~3.11.0", "eslint-config-semistandard": "~7.0.0", "eslint-config-standard": "~6.2.1", From 984d35b661b76b304257b078a13f2c5aecd4e44f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 29 Nov 2016 17:31:20 +0100 Subject: [PATCH 195/669] [major] Make error message consistent --- lib/PerMessageDeflate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 6c2278d7c..f8400712b 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -237,7 +237,7 @@ class PerMessageDeflate { if (this._maxPayload > 0) { cumulativeBufferLength += data.length; if (cumulativeBufferLength > this._maxPayload) { - const err = new Error(`payload cannot exceed ${this._maxPayload} bytes`); + const err = new Error('max payload size exceeded'); err.closeCode = 1009; buffers.length = 0; cleanup(); From fff499b704319b39c199d6b3d92fd581ff44171d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 29 Nov 2016 17:46:00 +0100 Subject: [PATCH 196/669] [test] Increase code coverage --- test/Receiver.test.js | 282 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 255 insertions(+), 27 deletions(-) diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 5717c7b49..ef5c249e0 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -53,15 +53,16 @@ describe('Receiver', function () { const msg = 'A'.repeat(200); const mask = '3483a868'; - const frame = '81FE' + util.pack(4, msg.length) + mask + - util.mask(msg, mask).toString('hex'); + const frame = Buffer.from('81FE' + util.pack(4, msg.length) + mask + + util.mask(msg, mask).toString('hex'), 'hex'); p.ontext = function (data) { assert.strictEqual(data, msg); done(); }; - p.add(Buffer.from(frame, 'hex')); + p.add(frame.slice(0, 2)); + setImmediate(() => p.add(frame.slice(2))); }); it('can parse a really long masked text message', function (done) { @@ -349,7 +350,227 @@ describe('Receiver', function () { assert.strictEqual(p.totalPayloadLength, 0); }); - it('will raise an error on a 200 KiB long masked binary message when maxpayload is 20 KiB', function (done) { + it('raises an error when RSV1 is on and permessage-deflate is disabled', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'RSV1 must be clear'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0xc2, 0x80, 0x00, 0x00, 0x00, 0x00])); + }); + + it('raises an error when RSV1 is on and opcode is 0', function (done) { + const perMessageDeflate = new PerMessageDeflate(); + perMessageDeflate.accept([{}]); + + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'RSV1 must be clear'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x40, 0x00])); + }); + + it('raises an error when RSV2 is on', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'RSV2 and RSV3 must be clear'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0xa2, 0x00])); + }); + + it('raises an error when RSV3 is on', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'RSV2 and RSV3 must be clear'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x92, 0x00])); + }); + + it('raises an error if the first frame in a fragmented message has opcode 0', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid opcode: 0'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x00, 0x00])); + }); + + it('raises an error if a frame has opcode 1 in the middle of a fragmented message', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid opcode: 1'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x01, 0x00])); + p.add(Buffer.from([0x01, 0x00])); + }); + + it('raises an error if a frame has opcode 2 in the middle of a fragmented message', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid opcode: 2'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x01, 0x00])); + p.add(Buffer.from([0x02, 0x00])); + }); + + it('raises an error when a control frame has the FIN bit off', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'FIN must be set'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x09, 0x00])); + }); + + it('raises an error when a control frame has the RSV1 bit on', function (done) { + const perMessageDeflate = new PerMessageDeflate(); + perMessageDeflate.accept([{}]); + + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'RSV1 must be clear'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0xc9, 0x00])); + }); + + it('raises an error when a control frame has the FIN bit off', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'FIN must be set'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x09, 0x00])); + }); + + it('raises an error when a control frame has a payload bigger than 125 B', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid payload length'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x89, 0x7e])); + }); + + it('raises an error when a data frame has a payload bigger than 2^53 - 1 B', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'max payload size exceeded'); + assert.strictEqual(code, 1009); + done(); + }; + + p.add(Buffer.from([0x82, 0x7f])); + setImmediate(() => p.add(Buffer.from([ + 0x00, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + ]))); + }); + + it('raises an error if a text frame contains invalid UTF-8 data', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid utf8 sequence'); + assert.strictEqual(code, 1007); + done(); + }; + + p.add(Buffer.from([0x81, 0x04, 0xce, 0xba, 0xe1, 0xbd])); + }); + + it('raises an error if a close frame has a payload of 1 B', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid payload length'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x88, 0x01, 0x00])); + }); + + it('raises an error if a close frame contains a invalid close code', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid status code: 0'); + assert.strictEqual(code, 1002); + done(); + }; + + p.add(Buffer.from([0x88, 0x02, 0x00, 0x00])); + }); + + it('raises an error if a close frame contains invalid UTF-8 data', function (done) { + const p = new Receiver(); + + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid utf8 sequence'); + assert.strictEqual(code, 1007); + done(); + }; + + p.add(Buffer.from([0x88, 0x06, 0x03, 0xef, 0xce, 0xba, 0xe1, 0xbd])); + }); + + it('raises an error on a 200 KiB long masked binary message when `maxPayload` is 20 KiB', function (done) { const p = new Receiver({}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); @@ -357,7 +578,9 @@ describe('Receiver', function () { const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + util.mask(msg, mask).toString('hex'); - p.error = function (reason, code) { + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'max payload size exceeded'); assert.strictEqual(code, 1009); done(); }; @@ -365,14 +588,16 @@ describe('Receiver', function () { p.add(Buffer.from(frame, 'hex')); }); - it('will raise an error on a 200 KiB long unmasked binary message when maxpayload is 20 KiB', function (done) { + it('raises an error on a 200 KiB long unmasked binary message when maxpayload is 20 KiB', function (done) { const p = new Receiver({}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); const frame = '82' + util.getHybiLengthAsHexString(msg.length, false) + msg.toString('hex'); - p.error = function (reason, code) { + p.error = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'max payload size exceeded'); assert.strictEqual(code, 1009); done(); }; @@ -380,50 +605,53 @@ describe('Receiver', function () { p.add(Buffer.from(frame, 'hex')); }); - it('will raise an error on a compressed message that exceeds maxpayload of 3 B', function (done) { - const perMessageDeflate = new PerMessageDeflate({}, false, 3); + it('raises an error on a compressed message that exceeds `maxPayload`', function (done) { + const perMessageDeflate = new PerMessageDeflate({}, false, 25); perMessageDeflate.accept([{}]); - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 3); - const buf = Buffer.from('Hellooooooooooooooooooooooooooooooooooooooo'); + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 25); + const buf = Buffer.from('A'.repeat(50)); - p.onerror = function (reason, code) { + p.onerror = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'max payload size exceeded'); assert.strictEqual(code, 1009); done(); }; - perMessageDeflate.compress(buf, true, function (err, compressed) { + perMessageDeflate.compress(buf, true, function (err, data) { if (err) return done(err); - p.add(Buffer.from([0xc1, compressed.length])); - p.add(compressed); + p.add(Buffer.from([0xc1, data.length])); + p.add(data); }); }); - it('will raise an error on a compressed fragment that exceeds maxpayload of 2 B', function (done) { - const perMessageDeflate = new PerMessageDeflate({}, false, 2); + it('raises an error if the sum of fragment lengths exceeds `maxPayload`', function (done) { + const perMessageDeflate = new PerMessageDeflate({}, false, 25); perMessageDeflate.accept([{}]); - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 2); - const buf1 = Buffer.from('foooooooooooooooooooooooooooooooooooooooooooooo'); - const buf2 = Buffer.from('baaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr'); + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 25); + const buf = Buffer.from('A'.repeat(15)); - p.onerror = function (reason, code) { + p.onerror = function (err, code) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'max payload size exceeded'); assert.strictEqual(code, 1009); done(); }; - perMessageDeflate.compress(buf1, false, function (err, compressed1) { + perMessageDeflate.compress(buf, false, function (err, fragment1) { if (err) return done(err); - p.add(Buffer.from([0x41, compressed1.length])); - p.add(compressed1); + p.add(Buffer.from([0x41, fragment1.length])); + p.add(fragment1); - perMessageDeflate.compress(buf2, true, function (err, compressed2) { + perMessageDeflate.compress(buf, true, function (err, fragment2) { if (err) return done(err); - p.add(Buffer.from([0x80, compressed2.length])); - p.add(compressed2); + p.add(Buffer.from([0x80, fragment2.length])); + p.add(fragment2); }); }); }); From 584b81d7bd4c280b186089c9c7d3b4af8e4d1150 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 29 Nov 2016 19:53:00 +0100 Subject: [PATCH 197/669] [minor] Clean up `WebSocket.prototype.addEventListener()` --- lib/WebSocket.js | 63 +++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 36 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index bb00ff3a1..f61b5dade 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -413,60 +413,51 @@ Object.defineProperty(WebSocket.prototype, 'binaryType', { }); /** - * Emulates the W3C Browser based WebSocket interface using addEventListener. + * Registers an event listener emulating the `EventTarget` interface. * - * @see https://developer.mozilla.org/en/DOM/element.addEventListener - * @see http://dev.w3.org/html5/websockets/#the-websocket-interface - * @api public + * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener + * @param {String} method A string representing the event type to listen for + * @param {Function} listener The listener to add + * @public */ WebSocket.prototype.addEventListener = function (method, listener) { - var target = this; + if (typeof listener !== 'function') return; function onMessage (data, flags) { if (flags.binary && this.binaryType === 'arraybuffer') { data = new Uint8Array(data).buffer; } - listener.call(target, new MessageEvent(data, !!flags.binary, target)); + listener.call(this, new MessageEvent(data, !!flags.binary, this)); } function onClose (code, message) { - listener.call(target, new CloseEvent(code, message, target)); + listener.call(this, new CloseEvent(code, message, this)); } function onError (event) { event.type = 'error'; - event.target = target; - listener.call(target, event); + event.target = this; + listener.call(this, event); } function onOpen () { - listener.call(target, new OpenEvent(target)); - } - - if (typeof listener === 'function') { - if (method === 'message') { - // store a reference so we can return the original function from the - // addEventListener hook - onMessage._listener = listener; - this.on(method, onMessage); - } else if (method === 'close') { - // store a reference so we can return the original function from the - // addEventListener hook - onClose._listener = listener; - this.on(method, onClose); - } else if (method === 'error') { - // store a reference so we can return the original function from the - // addEventListener hook - onError._listener = listener; - this.on(method, onError); - } else if (method === 'open') { - // store a reference so we can return the original function from the - // addEventListener hook - onOpen._listener = listener; - this.on(method, onOpen); - } else { - this.on(method, listener); - } + listener.call(this, new OpenEvent(this)); + } + + if (method === 'message') { + onMessage._listener = listener; + this.on(method, onMessage); + } else if (method === 'close') { + onClose._listener = listener; + this.on(method, onClose); + } else if (method === 'error') { + onError._listener = listener; + this.on(method, onError); + } else if (method === 'open') { + onOpen._listener = listener; + this.on(method, onOpen); + } else { + this.on(method, listener); } }; From 8e1b092dc94e79c548f8b2e6fb38b8fafefc6b69 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 30 Nov 2016 15:01:24 +0100 Subject: [PATCH 198/669] [minor] Clean up `initAsClient()` --- lib/WebSocket.js | 271 +++++++++++++++++++++++------------------------ 1 file changed, 135 insertions(+), 136 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index f61b5dade..8fb99f485 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -6,34 +6,23 @@ 'use strict'; -const url = require('url'); -const util = require('util'); -const http = require('http'); -const https = require('https'); +const EventEmitter = require('events'); const crypto = require('crypto'); const stream = require('stream'); const Ultron = require('ultron'); -const Sender = require('./Sender'); -const Receiver = require('./Receiver'); -const Extensions = require('./Extensions'); -const PerMessageDeflate = require('./PerMessageDeflate'); -const EventEmitter = require('events'); - -var isDefinedAndNonNull = function (options, key) { - return options[key] !== undefined && options[key] !== null; -}; - -/** - * Constants - */ - -// Default protocol version - -var protocolVersion = 13; +const https = require('https'); +const http = require('http'); +const util = require('util'); +const url = require('url'); -// Close timeout +const PerMessageDeflate = require('./PerMessageDeflate'); +const Extensions = require('./Extensions'); +const Receiver = require('./Receiver'); +const Sender = require('./Sender'); -var closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly +const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; +const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly. +const protocolVersion = 13; /** * WebSocket implementation @@ -549,131 +538,128 @@ function initAsServerClient (req, socket, upgradeHead, options) { establishConnection.call(this, socket, upgradeHead); } +/** + * Initialize a WebSocket client. + * + * @param {String} address The URL to which to connect + * @param {String[]} protocols The list of subprotocols + * @param {Object} options Configuration options + * @param {String} option.protocol Value of the `Sec-WebSocket-Protocol` header + * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate + * @param {String} options.localAddress Local interface to bind for network connections + * @param {Number} options.protocolVersion Value of the `Sec-WebSocket-Version` header + * @param {Object} options.headers An object containing request headers + * @param {String} options.origin Value of the `Sec-WebSocket-Version` header + * @param {http.Agent} options.agent Use the specified Agent + * @param {String} options.host Value of the `Host` header + * @param {Function} options.checkServerIdentity A function to validate the server hostname + * @param {Boolean} options.rejectUnauthorized Verify or not the server certificate + * @param {String} options.passphrase The passphrase for the private key or pfx + * @param {String} options.ciphers The ciphers to use or exclude + * @param {(String|String[]|Buffer|Buffer[])} options.cert The certificate key + * @param {(String|String[]|Buffer|Buffer[])} options.key The private key + * @param {(String|Buffer)} options.pfx The private key, certificate, and CA certs + * @param {(String|String[]|Buffer|Buffer[])} options.ca Trusted certificates + * @private + */ function initAsClient (address, protocols, options) { options = Object.assign({ - origin: null, - protocolVersion: protocolVersion, - host: null, - headers: null, protocol: protocols.join(','), + perMessageDeflate: true, + localAddress: null, + protocolVersion, + headers: null, + origin: null, agent: null, + host: null, - // ssl-related options - pfx: null, - key: null, + // + // SSL options. + // + checkServerIdentity: null, + rejectUnauthorized: null, passphrase: null, - cert: null, - ca: null, ciphers: null, - rejectUnauthorized: null, - checkServerIdentity: null, - perMessageDeflate: true, - localAddress: null + cert: null, + key: null, + pfx: null, + ca: null }, options); if (options.protocolVersion !== 8 && options.protocolVersion !== 13) { throw new Error('unsupported protocol version'); } - // verify URL and establish http class - var serverUrl = url.parse(address); - var isUnixSocket = serverUrl.protocol === 'ws+unix:'; + const serverUrl = url.parse(address); + const isUnixSocket = serverUrl.protocol === 'ws+unix:'; + if (!serverUrl.host && !isUnixSocket) throw new Error('invalid url'); - var isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:'; - var httpObj = isSecure ? https : http; - var port = serverUrl.port || (isSecure ? 443 : 80); - var auth = serverUrl.auth; - // prepare extensions - var extensionsOffer = {}; + const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:'; + const key = crypto.randomBytes(16).toString('base64'); + const port = serverUrl.port || (isSecure ? 443 : 80); + const httpObj = isSecure ? https : http; + + // + // Prepare extensions. + // + const extensionsOffer = {}; var perMessageDeflate; + if (options.perMessageDeflate) { - var opts = options.perMessageDeflate !== true ? options.perMessageDeflate : {}; - perMessageDeflate = new PerMessageDeflate(opts, false); + perMessageDeflate = new PerMessageDeflate( + options.perMessageDeflate !== true ? options.perMessageDeflate : {}, + false + ); extensionsOffer[PerMessageDeflate.extensionName] = perMessageDeflate.offer(); } - // expose state properties + // + // Expose state properties. + // + this.protocolVersion = options.protocolVersion; + this.readyState = WebSocket.CONNECTING; this._isServer = false; this.url = address; - this.protocolVersion = options.protocolVersion; - - // begin handshake - var key = new Buffer(options.protocolVersion + '-' + Date.now()).toString('base64'); - var shasum = crypto.createHash('sha1'); - shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary'); - var expectedServerKey = shasum.digest('base64'); var agent = options.agent; - var headerHost = buildHostHeader(isSecure, serverUrl.hostname, port); - - var requestOptions = { - port: port, + const requestOptions = { host: serverUrl.hostname, path: '/', + port, headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Host': headerHost, + 'Host': buildHostHeader(isSecure, serverUrl.hostname, port), 'Sec-WebSocket-Version': options.protocolVersion, - 'Sec-WebSocket-Key': key + 'Sec-WebSocket-Key': key, + 'Connection': 'Upgrade', + 'Upgrade': 'websocket' } }; - // If we have basic auth. - if (auth) { - requestOptions.headers.Authorization = 'Basic ' + new Buffer(auth).toString('base64'); + if (options.headers) Object.assign(requestOptions.headers, options.headers); + if (Object.keys(extensionsOffer).length) { + requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format(extensionsOffer); } - if (options.protocol) { requestOptions.headers['Sec-WebSocket-Protocol'] = options.protocol; } - - if (options.host) { - requestOptions.headers.Host = options.host; - } - - if (options.headers) { - for (var header in options.headers) { - if (options.headers.hasOwnProperty(header)) { - requestOptions.headers[header] = options.headers[header]; - } + if (options.origin) { + if (options.protocolVersion < 13) { + requestOptions.headers['Sec-WebSocket-Origin'] = options.origin; + } else { + requestOptions.headers.Origin = options.origin; } } + if (options.host) requestOptions.headers.Host = options.host; - if (Object.keys(extensionsOffer).length) { - requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format(extensionsOffer); - } - - if (isDefinedAndNonNull(options, 'pfx') || - isDefinedAndNonNull(options, 'key') || - isDefinedAndNonNull(options, 'passphrase') || - isDefinedAndNonNull(options, 'cert') || - isDefinedAndNonNull(options, 'ca') || - isDefinedAndNonNull(options, 'ciphers') || - isDefinedAndNonNull(options, 'rejectUnauthorized') || - isDefinedAndNonNull(options, 'checkServerIdentity')) { - if (isDefinedAndNonNull(options, 'pfx')) requestOptions.pfx = options.pfx; - if (isDefinedAndNonNull(options, 'key')) requestOptions.key = options.key; - if (isDefinedAndNonNull(options, 'passphrase')) requestOptions.passphrase = options.passphrase; - if (isDefinedAndNonNull(options, 'cert')) requestOptions.cert = options.cert; - if (isDefinedAndNonNull(options, 'ca')) requestOptions.ca = options.ca; - if (isDefinedAndNonNull(options, 'ciphers')) requestOptions.ciphers = options.ciphers; - if (isDefinedAndNonNull(options, 'rejectUnauthorized')) { - requestOptions.rejectUnauthorized = options.rejectUnauthorized; - } - if (isDefinedAndNonNull(options, 'checkServerIdentity')) { - requestOptions.checkServerIdentity = options.checkServerIdentity; - } + if (options.localAddress) requestOptions.localAddress = options.localAddress; + if (isUnixSocket) requestOptions.socketPath = serverUrl.pathname; + if (serverUrl.auth) requestOptions.auth = serverUrl.auth; - if (!agent) { - // global agent ignores client side certificates - agent = new httpObj.Agent(requestOptions); - } - } - - // make sure that path starts with `/` + // + // Make sure that path starts with `/`. + // if (serverUrl.path) { if (serverUrl.path.charAt(0) !== '/') { requestOptions.path = `/${serverUrl.path}`; @@ -682,31 +668,45 @@ function initAsClient (address, protocols, options) { } } - if (agent) { - requestOptions.agent = agent; - } - - if (isUnixSocket) { - requestOptions.socketPath = serverUrl.pathname; - } + // + // A custom agent is required for these options. + // + if ( + options.rejectUnauthorized != null || + options.checkServerIdentity || + options.passphrase || + options.ciphers || + options.cert || + options.key || + options.pfx || + options.ca + ) { + if (options.passphrase) requestOptions.passphrase = options.passphrase; + if (options.ciphers) requestOptions.ciphers = options.ciphers; + if (options.cert) requestOptions.cert = options.cert; + if (options.key) requestOptions.key = options.key; + if (options.pfx) requestOptions.pfx = options.pfx; + if (options.ca) requestOptions.ca = options.ca; + if (options.checkServerIdentity) { + requestOptions.checkServerIdentity = options.checkServerIdentity; + } + if (options.rejectUnauthorized != null) { + requestOptions.rejectUnauthorized = options.rejectUnauthorized; + } - if (options.localAddress) { - requestOptions.localAddress = options.localAddress; + if (!agent) agent = new httpObj.Agent(requestOptions); } - if (options.origin) { - if (options.protocolVersion < 13) requestOptions.headers['Sec-WebSocket-Origin'] = options.origin; - else requestOptions.headers.Origin = options.origin; - } + if (agent) requestOptions.agent = agent; - var req = httpObj.request(requestOptions); + const req = httpObj.get(requestOptions); req.on('error', (error) => { this.emit('error', error); cleanupWebsocketResources.call(this, error); }); - req.once('response', (res) => { + req.on('response', (res) => { var error; if (!this.emit('unexpected-response', req, res)) { @@ -718,7 +718,7 @@ function initAsClient (address, protocols, options) { cleanupWebsocketResources.call(this, error); }); - req.once('upgrade', (res, socket, upgradeHead) => { + req.on('upgrade', (res, socket, upgradeHead) => { if (this.readyState === WebSocket.CLOSED) { // client closed before server accepted connection this.emit('close'); @@ -727,17 +727,20 @@ function initAsClient (address, protocols, options) { return; } - var serverKey = res.headers['sec-websocket-accept']; - if (serverKey !== expectedServerKey) { + const digest = crypto.createHash('sha1') + .update(key + GUID, 'binary') + .digest('base64'); + + if (res.headers['sec-websocket-accept'] !== digest) { this.emit('error', new Error('invalid server key')); this.removeAllListeners(); socket.end(); return; } - var serverProt = res.headers['sec-websocket-protocol']; - var protList = (options.protocol || '').split(/, */); - var protError = null; + const serverProt = res.headers['sec-websocket-protocol']; + const protList = (options.protocol || '').split(/, */); + var protError; if (!options.protocol && serverProt) { protError = 'server sent a subprotocol even though none requested'; @@ -752,11 +755,11 @@ function initAsClient (address, protocols, options) { this.removeAllListeners(); socket.end(); return; - } else if (serverProt) { - this.protocol = serverProt; } - var serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']); + if (serverProt) this.protocol = serverProt; + + const serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']); if (perMessageDeflate && serverExtensions[PerMessageDeflate.extensionName]) { try { perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]); @@ -773,12 +776,8 @@ function initAsClient (address, protocols, options) { // perform cleanup on http resources req.removeAllListeners(); - req = null; agent = null; }); - - req.end(); - this.readyState = WebSocket.CONNECTING; } function establishConnection (socket, upgradeHead) { From c16d595a1862b5f812139de695f515aefd79c5fa Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 4 Dec 2016 19:35:00 +0100 Subject: [PATCH 199/669] [minor] Move the EventTarget methods to lib/EventTarget.js --- lib/EventTarget.js | 158 ++++++++++++++++++++++++++++++++++ lib/WebSocket.js | 206 +++++++++++++-------------------------------- 2 files changed, 217 insertions(+), 147 deletions(-) create mode 100644 lib/EventTarget.js diff --git a/lib/EventTarget.js b/lib/EventTarget.js new file mode 100644 index 000000000..1c97e0420 --- /dev/null +++ b/lib/EventTarget.js @@ -0,0 +1,158 @@ +'use strict'; + +/** + * Class representing an event. + * + * @private + */ +class Event { + /** + * Create a new `Event`. + * + * @param {String} type The name of the event + * @param {Object} target A reference to the target to which the event was dispatched + */ + constructor (type, target) { + this.target = target; + this.type = type; + } +} + +/** + * Class representing a message event. + * + * @extends Event + * @private + */ +class MessageEvent extends Event { + /** + * Create a new `MessageEvent`. + * + * @param {(String|Buffer|ArrayBuffer)} data The received data + * @param {Boolean} isBinary Specifies if `data` is binary + * @param {WebSocket} target A reference to the target to which the event was dispatched + */ + constructor (data, isBinary, target) { + super('message', target); + + this.binary = isBinary; // non-standard. + this.data = data; + } +} + +/** + * Class representing a close event. + * + * @extends Event + * @private + */ +class CloseEvent extends Event { + /** + * Create a new `CloseEvent`. + * + * @param {Number} code The status code explaining why the connection is being closed + * @param {String} reason A human-readable string explaining why the connection is closing + * @param {WebSocket} target A reference to the target to which the event was dispatched + */ + constructor (code, reason, target) { + super('close', target); + + this.wasClean = code === undefined || code === 1000; + this.reason = reason; + this.target = target; + this.type = 'close'; + this.code = code; + } +} + +/** + * Class representing an open event. + * + * @extends Event + * @private + */ +class OpenEvent extends Event { + /** + * Create a new `OpenEvent`. + * + * @param {WebSocket} target A reference to the target to which the event was dispatched + */ + constructor (target) { + super('open', target); + } +} + +/** + * This provides methods for emulating the `EventTarget` interface. It's not + * meant to be used directly. + * + * @mixin + */ +const EventTarget = { + /** + * Register an event listener. + * + * @param {String} method A string representing the event type to listen for + * @param {Function} listener The listener to add + * @public + */ + addEventListener (method, listener) { + if (typeof listener !== 'function') return; + + function onMessage (data, flags) { + if (flags.binary && this.binaryType === 'arraybuffer') { + data = new Uint8Array(data).buffer; + } + listener.call(this, new MessageEvent(data, !!flags.binary, this)); + } + + function onClose (code, message) { + listener.call(this, new CloseEvent(code, message, this)); + } + + function onError (event) { + event.type = 'error'; + event.target = this; + listener.call(this, event); + } + + function onOpen () { + listener.call(this, new OpenEvent(this)); + } + + if (method === 'message') { + onMessage._listener = listener; + this.on(method, onMessage); + } else if (method === 'close') { + onClose._listener = listener; + this.on(method, onClose); + } else if (method === 'error') { + onError._listener = listener; + this.on(method, onError); + } else if (method === 'open') { + onOpen._listener = listener; + this.on(method, onOpen); + } else { + this.on(method, listener); + } + }, + + /** + * Remove an event listener. + * + * @param {String} method A string representing the event type to remove + * @param {Function} listener The listener to remove + * @public + */ + removeEventListener (method, listener) { + const listeners = this.listeners(method); + + for (var i = 0; i < listeners.length; i++) { + if (listeners[i]._listener === listener) { + this.removeListener(method, listeners[i]); + } + } + } +}; + +module.exports = EventTarget; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 8fb99f485..af5cd1d48 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -16,6 +16,7 @@ const util = require('util'); const url = require('url'); const PerMessageDeflate = require('./PerMessageDeflate'); +const EventTarget = require('./EventTarget'); const Extensions = require('./Extensions'); const Receiver = require('./Receiver'); const Sender = require('./Sender'); @@ -332,12 +333,12 @@ WebSocket.prototype.terminate = function terminate () { }; /** - * Expose bufferedAmount + * Expose the `bufferedAmount` attribute. * - * @api public + * @public */ Object.defineProperty(WebSocket.prototype, 'bufferedAmount', { - get: function get () { + get () { var amount = 0; if (this._socket) { amount = this._socket.bufferSize || 0; @@ -347,19 +348,19 @@ Object.defineProperty(WebSocket.prototype, 'bufferedAmount', { }); /** - * Expose binaryType + * Expose the `binaryType` attribute. * - * This deviates from the W3C interface since ws doesn't support the required + * This deviates from the WHATWG interface since ws doesn't support the required * default "blob" type (instead we define a custom "nodebuffer" type). * - * @see http://dev.w3.org/html5/websockets/#the-websocket-interface - * @api public + * @see {@link https://html.spec.whatwg.org/multipage/comms.html#dom-websocket-binarytype} + * @public */ Object.defineProperty(WebSocket.prototype, 'binaryType', { - get: function get () { + get () { return this._binaryType; }, - set: function set (type) { + set (type) { if (type === 'arraybuffer' || type === 'nodebuffer') { this._binaryType = type; } else { @@ -368,174 +369,85 @@ Object.defineProperty(WebSocket.prototype, 'binaryType', { } }); -/** - * Emulates the W3C Browser based WebSocket interface using function members. - * - * @see http://dev.w3.org/html5/websockets/#the-websocket-interface - * @api public - */ -['open', 'error', 'close', 'message'].forEach(function (method) { - Object.defineProperty(WebSocket.prototype, 'on' + method, { +// +// Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes. +// See https://html.spec.whatwg.org/multipage/comms.html#the-websocket-interface +// +['open', 'error', 'close', 'message'].forEach((method) => { + Object.defineProperty(WebSocket.prototype, `on${method}`, { /** - * Returns the current listener + * Return the listener of the event. * - * @returns {Mixed} the set function or undefined - * @api public + * @return {(Function|undefined)} The event listener or `undefined` + * @public */ - get: function get () { - var listener = this.listeners(method)[0]; - return listener ? (listener._listener ? listener._listener : listener) : undefined; + get () { + const listener = this.listeners(method)[0]; + return listener ? listener._listener ? listener._listener : listener : undefined; }, - /** - * Start listening for events + * Add a listener for the event. * - * @param {Function} listener the listener - * @returns {Mixed} the set function or undefined - * @api public + * @param {Function} listener The listener to add + * @public */ - set: function set (listener) { + set (listener) { this.removeAllListeners(method); this.addEventListener(method, listener); } }); }); -/** - * Registers an event listener emulating the `EventTarget` interface. - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener - * @param {String} method A string representing the event type to listen for - * @param {Function} listener The listener to add - * @public - */ -WebSocket.prototype.addEventListener = function (method, listener) { - if (typeof listener !== 'function') return; - - function onMessage (data, flags) { - if (flags.binary && this.binaryType === 'arraybuffer') { - data = new Uint8Array(data).buffer; - } - listener.call(this, new MessageEvent(data, !!flags.binary, this)); - } - - function onClose (code, message) { - listener.call(this, new CloseEvent(code, message, this)); - } - - function onError (event) { - event.type = 'error'; - event.target = this; - listener.call(this, event); - } - - function onOpen () { - listener.call(this, new OpenEvent(this)); - } - - if (method === 'message') { - onMessage._listener = listener; - this.on(method, onMessage); - } else if (method === 'close') { - onClose._listener = listener; - this.on(method, onClose); - } else if (method === 'error') { - onError._listener = listener; - this.on(method, onError); - } else if (method === 'open') { - onOpen._listener = listener; - this.on(method, onOpen); - } else { - this.on(method, listener); - } -}; - -/** - * Removes an event listener previously registered with `addEventListener`. - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener - * @param {String} method A string representing the event type to remove - * @param {Function} listener The listener to remove - * @public - */ -WebSocket.prototype.removeEventListener = function (method, listener) { - const listeners = this.listeners(method); - - for (var i = 0; i < listeners.length; i++) { - if (listeners[i]._listener === listener) { - this.removeListener(method, listeners[i]); - } - } -}; +WebSocket.prototype.addEventListener = EventTarget.addEventListener; +WebSocket.prototype.removeEventListener = EventTarget.removeEventListener; module.exports = WebSocket; module.exports.buildHostHeader = buildHostHeader; /** - * W3C MessageEvent + * Append port number to Host header, only if specified in the URL and + * non-default. * - * @see http://www.w3.org/TR/html5/comms.html - * @constructor - * @api private - */ -function MessageEvent (dataArg, isBinary, target) { - this.type = 'message'; - this.data = dataArg; - this.target = target; - this.binary = isBinary; // non-standard. -} - -/** - * W3C CloseEvent - * - * @see http://www.w3.org/TR/html5/comms.html - * @constructor - * @api private - */ -function CloseEvent (code, reason, target) { - this.type = 'close'; - this.wasClean = code === undefined || code === 1000; - this.code = code; - this.reason = reason; - this.target = target; -} - -/** - * W3C OpenEvent - * - * @see http://www.w3.org/TR/html5/comms.html - * @constructor - * @api private + * @param {Boolean} isSecure Specifies whether or not the URL scheme is `wss` + * @param {String} hostname The hostname portion of the URL + * @param {Number} port The port portion of the URL + * @return {String} The field value of the `Host` header + * @private */ -function OpenEvent (target) { - this.type = 'open'; - this.target = target; -} - -// Append port number to Host header, only if specified in the url -// and non-default function buildHostHeader (isSecure, hostname, port) { var headerHost = hostname; - if (hostname) { - if ((isSecure && (port !== 443)) || (!isSecure && (port !== 80))) { - headerHost = headerHost + ':' + port; - } + + if (headerHost && (isSecure && port !== 443 || !isSecure && port !== 80)) { + headerHost = `${headerHost}:${port}`; } + return headerHost; } /** - * Entirely private apis, - * which may or may not be bound to a sepcific WebSocket instance. + * Initialize a WebSocket server client. + * + * @param {http.IncomingMessage} req The request object + * @param {net.Socket} socket The network socket between the server and client + * @param {Buffer} head The first packet of the upgraded stream + * @param {Object} options WebSocket attributes + * @param {Number} options.protocolVersion The WebSocket protocol version + * @param {Object} options.extensions The negotiated extensions + * @param {Number} options.maxPayload The maximum allowed message size + * @param {String} options.protocol The chosen subprotocol + * @private */ -function initAsServerClient (req, socket, upgradeHead, options) { - // expose state properties - Object.assign(this, options); +function initAsServerClient (req, socket, head, options) { + this.protocolVersion = options.protocolVersion; + this.extensions = options.extensions; + this.maxPayload = options.maxPayload; + this.protocol = options.protocol; + this.readyState = WebSocket.CONNECTING; this.upgradeReq = req; this._isServer = true; - // establish connection - establishConnection.call(this, socket, upgradeHead); + + establishConnection.call(this, socket, head); } /** @@ -543,7 +455,7 @@ function initAsServerClient (req, socket, upgradeHead, options) { * * @param {String} address The URL to which to connect * @param {String[]} protocols The list of subprotocols - * @param {Object} options Configuration options + * @param {Object} options Connection options * @param {String} option.protocol Value of the `Sec-WebSocket-Protocol` header * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate * @param {String} options.localAddress Local interface to bind for network connections From 9896d5b722901c01c7702b8777a84d5253e2a2be Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 5 Dec 2016 10:05:34 +0100 Subject: [PATCH 200/669] [ci] Add appveyor.yml --- appveyor.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..954f55608 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,18 @@ +environment: + matrix: + - nodejs_version: "7" + - nodejs_version: "6" + - nodejs_version: "4" +platform: + - x86 + - x64 +matrix: + fast_finish: true +install: + - ps: Install-Product node $env:nodejs_version $env:platform + - npm install +test_script: + - node --version + - npm --version + - npm test +build: off From 4874b5e64846abdb057568453d90ded4de37dadb Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 5 Dec 2016 10:07:59 +0100 Subject: [PATCH 201/669] [doc] Add AppVeyor badge --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b78bb0e5..654c11609 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # ws: a node.js websocket library [![Version npm](https://img.shields.io/npm/v/ws.svg)](https://www.npmjs.com/package/ws) -[![Build Status](https://img.shields.io/travis/websockets/ws/master.svg)](https://travis-ci.org/websockets/ws) +[![Linux Build](https://img.shields.io/travis/websockets/ws/master.svg)](https://travis-ci.org/websockets/ws) +[![Windows Build](https://ci.appveyor.com/api/projects/status/github/websockets/ws?branch=master&svg=true)](https://ci.appveyor.com/project/lpinca/ws) [![Coverage Status](https://img.shields.io/coveralls/websockets/ws/master.svg)](https://coveralls.io/r/websockets/ws?branch=master) `ws` is a simple to use WebSocket implementation, up-to-date against RFC-6455, From f0caae4a5f1c30024ee96e595b2d2fdd2f374980 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 5 Dec 2016 10:08:42 +0100 Subject: [PATCH 202/669] [ci] Use container-based infrastructure for faster builds --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c9c131ecc..3a58aefe7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js -sudo: required dist: trusty +sudo: false node_js: - "7" - "6" From e032dd94e88d4a865fc27957beb35687bfd51766 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 5 Dec 2016 13:00:58 +0100 Subject: [PATCH 203/669] [major] Remove `supports` attribute (#918) --- doc/ws.md | 4 ---- lib/WebSocket.js | 1 - test/WebSocket.test.js | 18 ------------------ 3 files changed, 23 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 6ba7e2b2b..c8b6a748d 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -143,10 +143,6 @@ The WebSocket protocol version used for this connection, `8`, `13`. The URL of the WebSocket server (only for clients) -### websocket.supports - -Describes the feature of the used protocol version. E.g. `supports.binary` is a boolean that describes if the connection supports binary messages. - ### websocket.upgradeReq The http request that initiated the upgrade. Useful for parsing authorty headers, cookie headers and other information to associate a specific Websocket to a specific Client. This is only available for WebSockets constructed by a Server. diff --git a/lib/WebSocket.js b/lib/WebSocket.js index af5cd1d48..63363cf41 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -60,7 +60,6 @@ function WebSocket (address, protocols, options) { this._closeReceived = false; this.bytesReceived = 0; this.readyState = null; - this.supports = { binary: true }; this.extensions = {}; this._binaryType = 'nodebuffer'; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 14bd00abb..1d4920df4 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1894,24 +1894,6 @@ describe('WebSocket', function () { }); }); - describe('protocol support discovery', function () { - describe('#supports', function () { - describe('#binary', function () { - it('returns true', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const ws = new WebSocket(`ws://localhost:${port}`); - }); - - wss.on('connection', (client) => { - assert.strictEqual(client.supports.binary, true); - wss.close(); - done(); - }); - }); - }); - }); - }); - describe('host and origin headers', function () { it('includes the host header with port number', function (done) { const server = http.createServer(); From 4ea3661e399754d1f272fc409e51747089bb3f18 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 5 Dec 2016 13:06:47 +0100 Subject: [PATCH 204/669] [fix] Call `Sender#close()` callback when data is written out (#908) --- lib/Sender.js | 3 +-- test/Sender.test.js | 13 ++++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index e0393bc65..b15b20a7e 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -67,11 +67,10 @@ class Sender { * @private */ doClose (data, mask, cb) { - this.frameAndSend(0x08, data, false, true, mask, false); + this.frameAndSend(0x08, data, false, true, mask, false, cb); if (this.extensions[PerMessageDeflate.extensionName]) { this.continue(); } - if (cb) cb(); } /** diff --git a/test/Sender.test.js b/test/Sender.test.js index 01342b54d..0600dd9b4 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -221,11 +221,11 @@ describe('Sender', function () { }); it('handles many send calls while processing without crashing on flush', function (done) { - let cnt = 0; + let count = 0; const perMessageDeflate = new PerMessageDeflate(); const sender = new Sender({ write: () => { - if (++cnt > 1e4) done(); + if (++count > 1e4) done(); } }, { 'permessage-deflate': perMessageDeflate @@ -249,7 +249,10 @@ describe('Sender', function () { let count = 0; const sender = new Sender({ - write: (data) => count++ + write: (data, cb) => { + count++; + if (cb) cb(); + } }, { 'permessage-deflate': perMessageDeflate }); @@ -260,9 +263,9 @@ describe('Sender', function () { sender.send('bar', { compress: true, fin: true }); sender.send('baz', { compress: true, fin: true }); - sender.close(1000, null, false, (err) => { + sender.close(1000, null, false, () => { assert.strictEqual(count, 4); - done(err); + done(); }); }); }); From 8303c5c759f9a049e4a8e84bbcc8dd4600b099d9 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 5 Dec 2016 21:47:26 +0100 Subject: [PATCH 205/669] [test] Fix failing test on Windows --- test/WebSocket.test.js | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 1d4920df4..0d85fda2f 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -7,7 +7,6 @@ const crypto = require('crypto'); const https = require('https'); const http = require('http'); const fs = require('fs'); -const os = require('os'); const server = require('./testserver'); const WebSocket = require('..'); @@ -42,24 +41,21 @@ describe('WebSocket', function () { }); it('should accept the localAddress option', function (done) { - // explore existing interfaces - const devs = os.networkInterfaces(); - const localAddresses = []; - - Object.keys(devs).forEach((name) => { - devs[name].forEach((ifc) => { - if (!ifc.internal && ifc.family === 'IPv4') { - localAddresses.push(ifc.address); - } - }); - }); + // + // Skip this test on macOS as by default all loopback addresses other + // than 127.0.0.1 are disabled. + // + if (process.platform === 'darwin') return done(); - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocketServer({ host: '127.0.0.1', port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { - localAddress: localAddresses[0] + localAddress: '127.0.0.2' }); + }); - ws.on('open', () => wss.close(done)); + wss.on('connection', (ws) => { + assert.strictEqual(ws.upgradeReq.connection.remoteAddress, '127.0.0.2'); + wss.close(done); }); }); From 22d3b77cabff3ff71f25594ee2376cd0975fce4d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 6 Dec 2016 11:32:10 +0100 Subject: [PATCH 206/669] [fix] Call close callback when using a precreated server --- lib/WebSocketServer.js | 21 ++++++++++----- test/WebSocketServer.test.js | 50 +++++++++++++++++++++--------------- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index a0024760e..4a0bd4e92 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -20,6 +20,8 @@ const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; /** * Class representing a WebSocket server. + * + * @extends EventEmitter */ class WebSocketServer extends EventEmitter { /** @@ -99,19 +101,26 @@ class WebSocketServer extends EventEmitter { * @public */ close (cb) { - // terminate all associated clients + // + // Terminate all associated clients. + // if (this.clients) { for (const client of this.clients) client.terminate(); } - if (this._server) { - // close the http server if it was internally created - if (this.options.port != null) this._server.close(cb); + const server = this._server; + + if (server) { this._ultron.destroy(); this._ultron = this._server = null; - } else if (cb) { - cb(); + + // + // Close the http server if it was internally created. + // + if (this.options.port != null) return server.close(cb); } + + if (cb) cb(); } /** diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index e79aa5257..cceb53f94 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -75,23 +75,25 @@ describe('WebSocketServer', function () { }); }); - // Don't test this on Windows. It throws errors for obvious reasons. - if (process.platform !== 'win32') { - it('uses a precreated http server listening on unix socket', function (done) { - const server = http.createServer(); - const sockPath = `/tmp/ws_socket_${new Date().getTime()}.${Math.floor(Math.random() * 1000)}`; + it('uses a precreated http server listening on unix socket', function (done) { + // + // Skip this test on Windows as it throws errors for obvious reasons. + // + if (process.platform === 'win32') return done(); - server.listen(sockPath, () => { - const wss = new WebSocketServer({ server }); - const ws = new WebSocket(`ws+unix://${sockPath}`); + const server = http.createServer(); + const sockPath = `/tmp/ws_socket_${new Date().getTime()}.${Math.floor(Math.random() * 1000)}`; - wss.on('connection', (ws) => { - wss.close(); - server.close(done); - }); + server.listen(sockPath, () => { + const wss = new WebSocketServer({ server }); + const ws = new WebSocket(`ws+unix://${sockPath}`); + + wss.on('connection', (ws) => { + wss.close(); + server.close(done); }); }); - } + }); it('emits path specific connection event', function (done) { const server = http.createServer(); @@ -134,7 +136,7 @@ describe('WebSocketServer', function () { }); }); - it('will close all clients', function (done) { + it('closes all clients', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', () => { @@ -171,18 +173,24 @@ describe('WebSocketServer', function () { }); }); + it('invokes the callback in noServer mode', function (done) { + const wss = new WebSocketServer({ noServer: true }); + + wss.close(done); + }); + it('cleans event handlers on precreated server', function (done) { const server = http.createServer(); + const wss = new WebSocketServer({ server }); server.listen(++port, () => { - const wss = new WebSocketServer({ server }); - wss.close(); + wss.close(() => { + assert.strictEqual(server.listeners('listening').length, 0); + assert.strictEqual(server.listeners('upgrade').length, 0); + assert.strictEqual(server.listeners('error').length, 0); - assert.strictEqual(server.listeners('listening').length, 0); - assert.strictEqual(server.listeners('upgrade').length, 0); - assert.strictEqual(server.listeners('error').length, 0); - - server.close(done); + server.close(done); + }); }); }); }); From 5375617e528359749e081a31229f3d6cb3dbfefd Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 7 Dec 2016 08:14:12 +0100 Subject: [PATCH 207/669] [major] Remove `WebSocket.createServer()` and `WebSocket.connect()` --- index.js | 46 ++++++---------------------------------------- 1 file changed, 6 insertions(+), 40 deletions(-) diff --git a/index.js b/index.js index 3cfe33714..489e16942 100644 --- a/index.js +++ b/index.js @@ -1,49 +1,15 @@ -'use strict'; - /*! * ws: a node.js websocket client * Copyright(c) 2011 Einar Otto Stangvik * MIT Licensed */ -var WS = module.exports = require('./lib/WebSocket'); - -WS.Server = require('./lib/WebSocketServer'); -WS.Sender = require('./lib/Sender'); -WS.Receiver = require('./lib/Receiver'); - -/** - * Create a new WebSocket server. - * - * @param {Object} options Server options - * @param {Function} fn Optional connection listener. - * @returns {WS.Server} - * @api public - */ -WS.createServer = function createServer (options, fn) { - var server = new WS.Server(options); - - if (typeof fn === 'function') { - server.on('connection', fn); - } - - return server; -}; +'use strict'; -/** - * Create a new WebSocket connection. - * - * @param {String} address The URL/address we need to connect to. - * @param {Function} fn Open listener. - * @returns {WS} - * @api public - */ -WS.connect = WS.createConnection = function connect (address, fn) { - var client = new WS(address); +const WebSocket = require('./lib/WebSocket'); - if (typeof fn === 'function') { - client.on('open', fn); - } +WebSocket.Server = require('./lib/WebSocketServer'); +WebSocket.Receiver = require('./lib/Receiver'); +WebSocket.Sender = require('./lib/Sender'); - return client; -}; +module.exports = WebSocket; From 54f902407c152d2710645f2ec911c54091d1e067 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 7 Dec 2016 09:55:15 +0100 Subject: [PATCH 208/669] [fix] Make `WebSocket#removeEventListener()` work with any listener --- lib/EventTarget.js | 2 +- test/WebSocket.test.js | 41 ++++++++++++++++++++++++++++++++--------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/lib/EventTarget.js b/lib/EventTarget.js index 1c97e0420..9720e4a26 100644 --- a/lib/EventTarget.js +++ b/lib/EventTarget.js @@ -148,7 +148,7 @@ const EventTarget = { const listeners = this.listeners(method); for (var i = 0; i < listeners.length; i++) { - if (listeners[i]._listener === listener) { + if (listeners[i] === listener || listeners[i]._listener === listener) { this.removeListener(method, listeners[i]); } } diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 0d85fda2f..3337d4c67 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1614,24 +1614,47 @@ describe('WebSocket', function () { }); }); - it('should remove event listeners added with addEventListener', function () { - const message = () => {}; - const open = () => {}; + it('registers listeners for custom events with addEventListener', function () { + const listener = () => {}; const ws = new WebSocket(`ws://localhost:${++port}`); ws.on('error', () => {}); - ws.addEventListener('message', message); - ws.addEventListener('open', open); + ws.addEventListener('foo', listener); + assert.strictEqual(ws.listeners('foo')[0], listener); - assert.strictEqual(ws.listeners('message')[0]._listener, message); - assert.strictEqual(ws.listeners('open')[0]._listener, open); + // + // Fails silently when the `listener` is not a function. + // + ws.addEventListener('bar', {}); + assert.strictEqual(ws.listeners('bar').length, 0); + }); + + it('removes event listeners added with addEventListener', function () { + const listener = () => {}; + const ws = new WebSocket(`ws://localhost:${++port}`); + + ws.on('error', () => {}); + + ws.addEventListener('message', listener); + ws.addEventListener('open', listener); + ws.addEventListener('foo', listener); + + assert.strictEqual(ws.listeners('message')[0]._listener, listener); + assert.strictEqual(ws.listeners('open')[0]._listener, listener); + assert.strictEqual(ws.listeners('foo')[0], listener); + + ws.removeEventListener('message', () => {}); + + assert.strictEqual(ws.listeners('message').length, 1); - ws.removeEventListener('message', message); - ws.removeEventListener('open', open); + ws.removeEventListener('message', listener); + ws.removeEventListener('open', listener); + ws.removeEventListener('foo', listener); assert.strictEqual(ws.listeners('message').length, 0); assert.strictEqual(ws.listeners('open').length, 0); + assert.strictEqual(ws.listeners('foo').length, 0); }); it('should receive valid CloseEvent when server closes with code 1000', function (done) { From 5d2ae65de38029223a7d0a3f3c426a7bc0d001c5 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 8 Dec 2016 16:37:20 +0100 Subject: [PATCH 209/669] chore(package): update utf-8-validate to version 2.0.0 (#927) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 162b61c5b..bbc84ce3c 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,6 @@ "eslint-plugin-standard": "~2.0.1", "istanbul": "~0.4.5", "mocha": "~3.2.0", - "utf-8-validate": "~1.2.1" + "utf-8-validate": "~2.0.0" } } From a720a7601cbee38ed4e4c097295d4a8a5bf5c61f Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 9 Dec 2016 18:13:57 +0100 Subject: [PATCH 210/669] chore(package): update eslint to version 3.12.0 (#931) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bbc84ce3c..c6b592113 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~1.3.0", - "eslint": "~3.11.0", + "eslint": "~3.12.0", "eslint-config-semistandard": "~7.0.0", "eslint-config-standard": "~6.2.1", "eslint-plugin-promise": "~3.4.0", From 53a67ab085a006ee5aec1c7adac72d977436c918 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 10 Dec 2016 20:05:11 +0100 Subject: [PATCH 211/669] [test] Add more tests --- test/WebSocket.test.js | 97 ++++++++++++++++++++++++++++++++---------- 1 file changed, 75 insertions(+), 22 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index e133ac145..3850f14a1 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -14,6 +14,10 @@ const WebSocket = require('..'); const WebSocketServer = WebSocket.Server; let port = 20000; +class CustomAgent extends http.Agent { + createConnection () {} +} + describe('WebSocket', function () { describe('#ctor', function () { it('should return a new instance if called without new', function (done) { @@ -23,21 +27,39 @@ describe('WebSocket', function () { ws.on('error', () => done()); }); - it('throws exception for invalid url', function () { - assert.throws(() => new WebSocket('echo.websocket.org')); + it('throws an error when using an invalid url', function () { + assert.throws( + () => new WebSocket('echo.websocket.org'), + /^Error: invalid url$/ + ); }); }); describe('options', function () { it('should accept an `agent` option', function (done) { - const agent = { addRequest: () => done() }; + const agent = new CustomAgent(); + + agent.createConnection = () => { + done(); + }; + const ws = new WebSocket('ws://localhost', { agent }); }); // GH-227 - it('should accept the `options` object as the 3rd argument', function (done) { - const agent = { addRequest: () => done() }; - const ws = new WebSocket('ws://localhost', [], { agent }); + it('should accept the `options` object as the 3rd argument', function () { + const ws = new WebSocket('ws://localhost', [], { + agent: new CustomAgent() + }); + }); + + it('throws an error when using an invalid `protocolVersion`', function () { + const options = { agent: new CustomAgent(), protocolVersion: 1000 }; + + assert.throws( + () => new WebSocket('ws://localhost', options), + /^Error: unsupported protocol version$/ + ); }); it('should accept the localAddress option', function (done) { @@ -264,8 +286,9 @@ describe('WebSocket', function () { }); it('is property of instance', function () { - const ws = new WebSocket('ws://localhost'); - ws.on('error', () => {}); + const ws = new WebSocket('ws://localhost', { + agent: new CustomAgent() + }); assert.strictEqual(ws[state], readyStates[state]); }); @@ -996,11 +1019,16 @@ describe('WebSocket', function () { }); }); - describe('W3C API emulation', function () { + describe('WHATWG API emulation', function () { it('should not throw errors when getting and setting', function (done) { server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); const listener = () => {}; + const ws = new WebSocket(`ws://localhost:${port}`); + + assert.strictEqual(ws.onmessage, undefined); + assert.strictEqual(ws.onclose, undefined); + assert.strictEqual(ws.onerror, undefined); + assert.strictEqual(ws.onopen, undefined); ws.onmessage = listener; ws.onerror = listener; @@ -1010,17 +1038,27 @@ describe('WebSocket', function () { assert.strictEqual(ws.binaryType, 'nodebuffer'); ws.binaryType = 'arraybuffer'; assert.strictEqual(ws.binaryType, 'arraybuffer'); + ws.binaryType = 'nodebuffer'; + assert.strictEqual(ws.binaryType, 'nodebuffer'); - assert.strictEqual(ws.onopen, listener); assert.strictEqual(ws.onmessage, listener); assert.strictEqual(ws.onclose, listener); assert.strictEqual(ws.onerror, listener); + assert.strictEqual(ws.onopen, listener); srv.close(done); ws.terminate(); }); }); + it('should throw an error when setting an invalid binary type', function () { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); + + assert.throws(() => { + ws.binaryType = 'foo'; + }, /^SyntaxError: unsupported binaryType: must be either "nodebuffer" or "arraybuffer"$/); + }); + it('should work the same as the EventEmitter api', function (done) { server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -1064,9 +1102,7 @@ describe('WebSocket', function () { it('registers listeners for custom events with addEventListener', function () { const listener = () => {}; - const ws = new WebSocket(`ws://localhost:${++port}`); - - ws.on('error', () => {}); + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); ws.addEventListener('foo', listener); assert.strictEqual(ws.listeners('foo')[0], listener); @@ -1080,9 +1116,7 @@ describe('WebSocket', function () { it('removes event listeners added with addEventListener', function () { const listener = () => {}; - const ws = new WebSocket(`ws://localhost:${++port}`); - - ws.on('error', () => {}); + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); ws.addEventListener('message', listener); ws.addEventListener('open', listener); @@ -1094,7 +1128,7 @@ describe('WebSocket', function () { ws.removeEventListener('message', () => {}); - assert.strictEqual(ws.listeners('message').length, 1); + assert.strictEqual(ws.listeners('message')[0]._listener, listener); ws.removeEventListener('message', listener); ws.removeEventListener('open', listener); @@ -1367,7 +1401,7 @@ describe('WebSocket', function () { server.listen(++port, () => { server.on('upgrade', (req, socket, head) => { - assert.strictEqual(req.headers['host'], `localhost:${port}`); + assert.strictEqual(req.headers.host, `localhost:${port}`); server.close(done); socket.destroy(); }); @@ -1381,7 +1415,7 @@ describe('WebSocket', function () { server.listen(++port, () => { server.on('upgrade', (req, socket, head) => { - assert.strictEqual(req.headers['origin'], undefined); + assert.strictEqual(req.headers.origin, undefined); server.close(done); socket.destroy(); }); @@ -1390,14 +1424,33 @@ describe('WebSocket', function () { }); }); - it('honors origin set in options', function (done) { + it('honors origin set in options (1/2)', function (done) { const server = http.createServer(); server.listen(++port, () => { const options = { origin: 'https://example.com:8000' }; server.on('upgrade', (req, socket, head) => { - assert.strictEqual(req.headers['origin'], options.origin); + assert.strictEqual(req.headers.origin, options.origin); + server.close(done); + socket.destroy(); + }); + + const ws = new WebSocket(`ws://localhost:${port}`, options); + }); + }); + + it('honors origin set in options (2/2)', function (done) { + const server = http.createServer(); + + server.listen(++port, () => { + const options = { + origin: 'https://example.com:8000', + protocolVersion: 8 + }; + + server.on('upgrade', (req, socket, head) => { + assert.strictEqual(req.headers['sec-websocket-origin'], options.origin); server.close(done); socket.destroy(); }); From ff7257ea7dd41ea4ca4c0a2e61054ba520ecb58c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 10 Dec 2016 20:22:22 +0100 Subject: [PATCH 212/669] [test] Fix failing tests on node 4 --- test/WebSocket.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 3850f14a1..9771978cc 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -15,7 +15,7 @@ const WebSocketServer = WebSocket.Server; let port = 20000; class CustomAgent extends http.Agent { - createConnection () {} + addRequest () {} } describe('WebSocket', function () { @@ -39,7 +39,7 @@ describe('WebSocket', function () { it('should accept an `agent` option', function (done) { const agent = new CustomAgent(); - agent.createConnection = () => { + agent.addRequest = () => { done(); }; From 838ba0971b5dcbd8d38f68caab53dc6dd23ec9fe Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 11 Dec 2016 13:38:20 +0100 Subject: [PATCH 213/669] [fix] Convert data to buffer in `Sender#send()` --- lib/Sender.js | 44 +++++++++++++++++++++----------------------- test/Sender.test.js | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index b15b20a7e..0091a849b 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -148,24 +148,36 @@ class Sender { send (data, options, cb) { const pmd = this.extensions[PerMessageDeflate.extensionName]; var opcode = options.binary ? 2 : 1; - var compress = options.compress; + var rsv1 = options.compress; + var readOnly = true; + + if (data && !Buffer.isBuffer(data)) { + if (data instanceof ArrayBuffer) { + data = Buffer.from(data); + } else if (ArrayBuffer.isView(data)) { + data = viewToBuffer(data); + } else { + data = Buffer.from(typeof data === 'number' ? data.toString() : data); + readOnly = false; + } + } if (this.firstFragment) { this.firstFragment = false; - if (compress && data && pmd) compress = data.length >= pmd.threshold; - this.compress = compress; + if (rsv1 && data && pmd) rsv1 = data.length >= pmd.threshold; + this.compress = rsv1; } else { - compress = false; + rsv1 = false; opcode = 0; } if (options.fin) this.firstFragment = true; if (pmd) { - const args = [opcode, data, options.fin, options.mask, compress, cb]; + const args = [opcode, data, readOnly, options.fin, options.mask, rsv1, cb]; this.enqueue([this.sendCompressed, args]); } else { - this.frameAndSend(opcode, data, true, options.fin, options.mask, false, cb); + this.frameAndSend(opcode, data, readOnly, options.fin, options.mask, false, cb); } } @@ -174,20 +186,20 @@ class Sender { * * @param {Number} opcode The opcode * @param {*} data The message to send + * @param {Boolean} readOnly Specifies whether `data` can be modified * @param {Boolean} fin Specifies whether or not to set the FIN bit * @param {Boolean} mask Specifies whether or not to mask `data` * @param {Boolean} rsv1 Specifies whether or not to set the RSV1 bit * @param {Function} cb Callback * @private */ - sendCompressed (opcode, data, fin, mask, rsv1, cb) { + sendCompressed (opcode, data, readOnly, fin, mask, rsv1, cb) { if (!this.compress) { - this.frameAndSend(opcode, data, true, fin, mask, false, cb); + this.frameAndSend(opcode, data, readOnly, fin, mask, false, cb); this.continue(); return; } - if (data && !Buffer.isBuffer(data)) data = toBuffer(data); this.extensions[PerMessageDeflate.extensionName].compress(data, fin, (err, buf) => { if (err) { if (cb) cb(err); @@ -344,20 +356,6 @@ function viewToBuffer (view) { return buf; } -/** - * Converts `data` into a buffer. - * - * @param {*} data Data to convert - * @return {Buffer} Converted data - * @private - */ -function toBuffer (data) { - if (data instanceof ArrayBuffer) return Buffer.from(data); - if (ArrayBuffer.isView(data)) return viewToBuffer(data); - - return Buffer.from(typeof data === 'number' ? data.toString() : data); -} - /** * Generates a random mask. * diff --git a/test/Sender.test.js b/test/Sender.test.js index 0600dd9b4..1b7ed063f 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -43,13 +43,38 @@ describe('Sender', function () { }); }); + describe('#ping', function () { + it('works with multiple types of data', function (done) { + let count = 0; + const sender = new Sender({ + write: (data) => { + if (++count < 4) { + assert.ok(data.equals(Buffer.from([0x89, 0x02, 0x68, 0x69]))); + } else { + assert.ok(data.equals(Buffer.from([0x89, 0x02, 0x31, 0x30]))); + done(); + } + } + }); + + const array = new Uint8Array([0x68, 0x69]); + const options = { mask: false }; + + sender.ping(array.buffer, options); + sender.ping(array, options); + sender.ping('hi', options); + sender.ping(10, options); + }); + }); + describe('#send', function () { it('compresses data if compress option is enabled', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + let count = 0; const sender = new Sender({ write: (data) => { assert.strictEqual(data[0] & 0x40, 0x40); - done(); + if (++count === 4) done(); } }, { 'permessage-deflate': perMessageDeflate @@ -57,7 +82,13 @@ describe('Sender', function () { perMessageDeflate.accept([{}]); - sender.send('hi', { compress: true, fin: true }); + const options = { compress: true, fin: true }; + const array = new Uint8Array([0x68, 0x69]); + + sender.send(array.buffer, options); + sender.send(array, options); + sender.send('hi', options); + sender.send(100, options); }); it('does not compress data for small payloads', function (done) { From 3d4273d95208e45af7de45cbbe48984e872b05d7 Mon Sep 17 00:00:00 2001 From: Denis Andrejew Date: Mon, 12 Dec 2016 17:21:46 +0000 Subject: [PATCH 214/669] [doc] Fix typo in ws.md (#935) --- doc/ws.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index c8b6a748d..0ca28e20a 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -22,7 +22,7 @@ Construct a new server object. Either `port` or `server` must be provided, otherwise you might enable `noServer` if you want to pass the requests directly. Please note that the -`callback` is only used when you supply the a `port` number in the options. +`callback` is only used when you supply a `port` number in the options. ### options.verifyClient From 90269e2dcc8334f98013c1e469f3bfdd9820d95e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 12 Dec 2016 19:45:12 +0100 Subject: [PATCH 215/669] [ignore] Add appveyor.yml to .npmignore --- .npmignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.npmignore b/.npmignore index e0e4dee67..7c56d8c06 100644 --- a/.npmignore +++ b/.npmignore @@ -3,4 +3,5 @@ examples/ bench/ test/ doc/ +appveyor.yml .* From d93f69f7c0bbb6a11c6f1289d4d7a1add9173eda Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 12 Dec 2016 21:21:07 +0100 Subject: [PATCH 216/669] [license] Add LICENSE file --- LICENSE | 21 +++++++++++++++++++++ README.md | 29 +---------------------------- 2 files changed, 22 insertions(+), 28 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..a145cd1df --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2011 Einar Otto Stangvik + +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. diff --git a/README.md b/README.md index 654c11609..53a1a2526 100644 --- a/README.md +++ b/README.md @@ -203,12 +203,6 @@ Note that the usage together with Express 3.0 is quite different from Express Otherwise, see the test cases. -### Running the tests - -``` -make test -``` - ## API Docs See [`/doc/ws.md`](https://github.com/websockets/ws/blob/master/doc/ws.md) for Node.js-like docs for the ws classes. @@ -219,27 +213,6 @@ We're using the GitHub [`releases`](https://github.com/websockets/ws/releases) f ## License -(The MIT License) - -Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com> - -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. +[MIT](LICENSE) [archive]: http://web.archive.org/web/20130314230536/http://hobbycoding.posterous.com/the-fastest-websocket-module-for-nodejs From 5a8ead573f1fb2b7c061431a85b3b79c91013f6c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 13 Dec 2016 18:53:45 +0100 Subject: [PATCH 217/669] [doc] Improve documentation --- doc/ws.md | 455 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 310 insertions(+), 145 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 0ca28e20a..f25035623 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -2,242 +2,407 @@ ## Class: WebSocket.Server -This class is a WebSocket server. It is an `EventEmitter`. +This class represents a WebSocket server. It extends the `EventEmitter`. + +### new WebSocket.Server(options[, callback]) + +- `options` {Object} + - `host` {String} The hostname where to bind the server. + - `port` {Number} The port where to bind the server. + - `backlog` {Number} The maximum length of the queue of pending connections. + - `server` {http.Server|https.Server} A pre-created Node.js HTTP server. + - `verifyClient` {Function} A function which can be used to validate incoming + connections. See description below. + - `handleProtocols` {Function} A function which can be used to handle the + WebSocket subprotocols. See description below. + - `path` {String} Accept only connections matching this path. + - `noServer` {Boolean} Enable no server mode. + - `clientTracking` {Boolean} Specifies whether or not to track clients. + - `perMessageDeflate` {Boolean|Object} Enable/disable permessage-deflate. + - `maxPayload` {Number} The maximum allowed message size in bytes. +- `callback` {Function} + +Create a new server instance. One of `port`, `server` or `noServer` must be +provided or an error is thrown. + + +If `verifyClient` is not set then the handshake is automatically accepted. If +it is is provided with a single argument then that is: + +- `info` {Object} + - `origin` {String} The value in the Origin header indicated by the client. + - `req` {http.IncomingMessage} The client HTTP GET request. + - `secure` {Boolean} `true` if `req.connection.authorized` or + `req.connection.encrypted` is set. + +The return value (Boolean) of the function determines whether or not to accept +the handshake. + +if `verifyClient` is provided with two arguments then those are: + +- `info` {Object} Same as above. +- `cb` {Function} A callback that must be called by the user upon inspection + of the `info` fields. Arguments in this callback are: + - `result` {Boolean} Whether or not to accept the handshake. + - `code` {Number} When `result` is `false` this field determines the HTTP + error status code to be sent to the client. + - `name` {String} When `result` is `false` this field determines the HTTP + reason phrase. + + +If `handleProtocols` is not set then the handshake is automatically accepted, +otherwise the function takes a single argument: + +- `protocols` {Array} The list of WebSocket subprotocols indicated by the + client in the `Sec-WebSocket-Protocol` header. + +If returned value is `false` then the handshake is rejected with the HTTP 401 +status code, otherwise the returned value sets the value of the +`Sec-WebSocket-Protocol` header in the HTTP 101 response. + +`perMessageDeflate` can be used to control the behavior of +[permessage-deflate extension][permessage-deflate]. +The extension is disabled when `false`. Defaults to `true`. If an object is +provided then that is extension parameters: + +- `serverNoContextTakeover` {Boolean} Whether to use context take over or not. +- `clientNoContextTakeover` {Boolean} The value to be requested to clients + whether to use context take over or not. +- `serverMaxWindowBits` {Number} The value of windowBits. +- `clientMaxWindowBits` {Number} The value of max windowBits to be requested + to clients. +- `memLevel` {Number} The value of memLevel. +- `threshold` {Number} Payloads smaller than this will not be compressed. + Defaults to 1024 bytes. + +If a property is empty then either an offered configuration or a default value +is used. +When sending a fragmented message the length of the first fragment is compared +to the threshold. This determines if compression is used for the entire message. + + +`callback` will be added as a listener for the `listening` event when the +HTTP server is created internally and that is when the `port` option is +provided. -### new WebSocket.Server([options], [callback]) +### Event: 'connection' -* `options` Object - * `host` String - * `port` Number - * `server` http.Server - * `verifyClient` Function - * `handleProtocols` Function - * `path` String - * `noServer` Boolean - * `clientTracking` Boolean - * `perMessageDeflate` Boolean|Object -* `callback` Function +- `socket` {WebSocket} -Construct a new server object. +Emitted when the handshake is complete. `socket` is an instance of `WebSocket`. -Either `port` or `server` must be provided, otherwise you might enable -`noServer` if you want to pass the requests directly. Please note that the -`callback` is only used when you supply a `port` number in the options. +### Event: 'error' -### options.verifyClient +- `error` {Error} -`verifyClient` can be used in two different ways. If it is provided with two arguments then those are: -* `info` Object: - * `origin` String: The value in the Origin header indicated by the client. - * `req` http.ClientRequest: The client HTTP GET request. - * `secure` Boolean: `true` if `req.connection.authorized` or `req.connection.encrypted` is set. -* `cb` Function: A callback that must be called by the user upon inspection of the `info` fields. Arguments in this callback are: - * `result` Boolean: Whether the user accepts or not the handshake. - * `code` Number: If `result` is `false` this field determines the HTTP error status code to be sent to the client. - * `name` String: If `result` is `false` this field determines the HTTP reason phrase. +Emitted when an error occurs on the underlying server. -If `verifyClient` is provided with a single argument then that is: -* `info` Object: Same as above. +### Event: 'headers' -In this case the return code (Boolean) of the function determines whether the handshake is accepted or not. +- `headers` {Array} -If `verifyClient` is not set then the handshake is automatically accepted. +Emitted before the response headers are written to the socket as part of the +handshake. This allows you to inspect/modify the headers before they are sent. -### options.handleProtocols +### Event: 'listening' -`handleProtocols` takes a single argument: -* `protocols` Array: The list of WebSocket sub-protocols indicated by the client in the `Sec-WebSocket-Protocol` header. +Emitted when the underlying server has been bound. -If returned value is `false` then the handshake is rejected with the HTTP 401 status code, otherwise the returned value sets the value of the `Sec-WebSocket-Protocol` header in the HTTP 101 response. +### server.clients -If `handleProtocols` is not set then the handshake is automatically accepted. +- {Set} -### options.perMessageDeflate +A set that stores all connected clients. Please note that this property is only +added when the `clientTracking` is truthy. -`perMessageDeflate` can be used to control the behavior of [permessage-deflate extension](https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19). The extension is disabled when `false`. Defaults to `true`. If an object is provided then that is extension parameters: +### server.close([callback]) -* `serverNoContextTakeover` Boolean: Whether to use context take over or not. -* `clientNoContextTakeover` Boolean: The value to be requested to clients whether to use context take over or not. -* `serverMaxWindowBits` Number: The value of windowBits. -* `clientMaxWindowBits` Number: The value of max windowBits to be requested to clients. -* `memLevel` Number: The value of memLevel. -* `threshold` Number: Payloads smaller than this will not be compressed. Default 1024 bytes. +Close the server and terminate all clients, calls callback when done. -If a property is empty then either an offered configuration or a default value is used. -When sending a fragmented message the length of the first fragment is compared to the threshold. This determines if compression is used for the entire message. +### server.handleUpgrade(request, socket, head, callback) -### server.close([callback]) +- `request` {http.IncomingMessage} The client HTTP GET request. +- `socket` {net.Socket} The network socket between the server and client. +- `head` {Buffer} The first packet of the upgraded stream. +- `callback` {Function}. -Close the server and terminate all clients, calls callback when done with an error if one occurred. +Handle a HTTP upgrade request. When the HTTP server is created internally or +when the HTTP server is passed via the `server` option, this method is called +automatically. When operating in "noServer" mode, this method must be called +manually. -### server.handleUpgrade(request, socket, upgradeHead, callback) +If the upgrade is successfull, the `callback` is called with a `WebSocket` +object as parameter. -Handles a HTTP Upgrade request. `request` is an instance of `http.ServerRequest`, `socket` is an instance of `net.Socket`. +### server.shouldHandle(request) + +- `request` {http.IncomingMessage} The client HTTP GET request. + +See if a given request should be handled by this server. +By default this method validates the pathname of the request, matching it +against the `path` option if provided. +The return value, `true` or `false`, determines whether or not to accept the +handshake. + +This method can be overriden when a custom handling logic is required. + +## Class: WebSocket -When the Upgrade was successfully, the `callback` will be called with a `WebSocket` object as parameter. +This class represents a WebSocket. It extends the `EventEmitter`. + +### Ready state constants + +|Constant | Value | Description | +|-----------|-------|--------------------------------------------------| +|CONNECTING | 0 | The connection is not yet open. | +|OPEN | 1 | The connection is open and ready to communicate. | +|CLOSING | 2 | The connection is in the process of closing. | +|CLOSED | 3 | The connection is closed. | + +### new WebSocket(address[, protocols][, options]) + +- `address` {String} The URL to which to connect. +- `protocols` {String|Array} The list of subprotocols. +- `options` {Object} + - `protocol` {String} Value of the `Sec-WebSocket-Protocol` header. + - `perMessageDeflate` {Boolean|Object} Enable/disable permessage-deflate. + - `localAddress` {String} Local interface to bind for network connections. + - `protocolVersion` {Number} Value of the `Sec-WebSocket-Version` header. + - `headers` {Object} An object with custom headers to send along with the + request. + - `origin` {String} Value of the `Origin` or `Sec-WebSocket-Origin` header + depending on the `protocolVersion`. + - `agent` {http.Agent|https.Agent} Use the specified Agent, + - `host` {String} Value of the `Host` header. + - `checkServerIdentity` {Function} A function to validate the server hostname. + - `rejectUnauthorized` {Boolean} Verify or not the server certificate. + - `passphrase` {String} The passphrase for the private key or pfx. + - `ciphers` {String} The ciphers to use or exclude + - `cert` {String|Array|Buffer} The certificate key. + - `key` {String|Array|Buffer} The private key. + - `pfx` {String|Buffer} The private key, certificate, and CA certs. + - `ca` {Array} Trusted certificates. + +`perMessageDeflate` parameters are the same of the server, the only difference +is the direction of requests (e.g. `serverNoContextTakeover` is the value to be +requested to the server). + +Create a new WebSocket instance. + +### Event: 'close' + +- `code` {Number} +- `reason` {String} + +Emitted when the connection is closed. `code` is a numeric value indicating the +status code explaining why the connection has been closed. `reason` is a +human-readable string explaining why the connection has been closed. ### Event: 'error' -`function (error) { }` +- `error` {Error} -If the underlying server emits an error, it will be forwarded here. +Emitted when an error occurs. Errors from the underlying `net.Socket` are +forwarded here. -### Event: 'headers' +### Event: 'message' -`function (headers) { }` +- `data` {String|Buffer} +- `flags` {Object} + - `binary` {Boolean} Specifies if `data` is binary. + - `masked` {Boolean} Specifies if `data` was masked. -Emitted with the object of HTTP headers that are going to be written to the `Stream` as part of the handshake. +Emitted when a message is received from the server. -### Event: 'connection' +### Event: 'open' -`function (socket) { }` +Emitted when the connection is established. -When a new WebSocket connection is established. `socket` is an object of type `WebSocket`. +### Event: 'ping' +- `data` {Buffer} +- `flags` {Object} + - `binary` {Boolean} Specifies if `data` is binary. + - `masked` {Boolean} Specifies if `data` was masked. -## Class: WebSocket +Emitted when a ping is received from the server. + +### Event: 'pong' + +- `data` {Buffer} +- `flags` {Object} + - `binary` {Boolean} Specifies if `data` is binary. + - `masked` {Boolean} Specifies if `data` was masked. + +Emitted when a pong is received from the server. + +### Event: 'unexpected-response' + +- `request` {http.ClientRequest} +- `response` {http.IncomingMessage} + +Emitted when the server response is not the expected one, for example a 401 +response. This event gives the ability to read the response in order to extract +useful information. If the server sends an invalid response and there isn't a +listener for this event, an error is emitted. -This class represents a WebSocket connection. It is an `EventEmitter`. +### websocket.addEventListener(type, listener) -### new WebSocket(address, [protocols], [options]) +- `type` {String} A string representing the event type to listen for. +- `listener` {Function} The listener to add. -* `address` String -* `protocols` String|Array -* `options` Object - * `protocol` String - * `agent` Agent - * `headers` Object - * `protocolVersion` Number - -- the following only apply if `address` is a String - * `host` String - * `origin` String - * `pfx` String|Buffer - * `key` String|Buffer - * `passphrase` String - * `cert` String|Buffer - * `ca` Array - * `ciphers` String - * `rejectUnauthorized` Boolean - * `perMessageDeflate` Boolean|Object - * `localAddress` String +Register an event listener emulating the `EventTarget` interface. -Instantiating with an `address` creates a new WebSocket client object. If `address` is an Array (request, socket, rest), it is instantiated as a Server client (e.g. called from the `WebSocket.Server`). +### websocket.binaryType -### options.perMessageDeflate +- {String} -Parameters of permessage-deflate extension which have the same form with the one for `WebSocket.Server` except the direction of requests. (e.g. `serverNoContextTakeover` is the value to be requested to the server) +A string indicating the type of binary data being transmitted by the connection. +This should be either "nodebuffer" or "arraybuffer". Defaults to "nodebuffer". + +### websocket.bufferedAmount + +- {Number} + +The number of bytes of data that have been queued using calls to `send()` but +not yet transmitted to the network. ### websocket.bytesReceived +- {Number} + Received bytes count. -### websocket.readyState +### websocket.close([code][, reason]) -Possible states are `WebSocket.CONNECTING`, `WebSocket.OPEN`, `WebSocket.CLOSING`, `WebSocket.CLOSED`. +- `code` {Number} A numeric value indicating the status code explaining why + the connection is being closed. +- `reason` {String} A human-readable string explaining why the connection is + closing. -### websocket.protocolVersion +Initiate a closing handshake. -The WebSocket protocol version used for this connection, `8`, `13`. +### websocket.extensions -### websocket.url +- {Object} -The URL of the WebSocket server (only for clients) +An object containing the negotiated extensions. -### websocket.upgradeReq +### websocket.onclose -The http request that initiated the upgrade. Useful for parsing authorty headers, cookie headers and other information to associate a specific Websocket to a specific Client. This is only available for WebSockets constructed by a Server. +- {Function} -### websocket.close([code], [data]) +An event listener to be called when connection is closed. The listener receives +a `CloseEvent` named "close". -Gracefully closes the connection, after sending a description message +### websocket.onerror -### websocket.pause() +- {Function} -Pause the client stream +An event listener to be called when an error occurs. The listener receives +an `Error` instance. -### websocket.ping([data], [options], [dontFailWhenClosed]) +### websocket.onmessage -Sends a ping. `data` is sent, `options` is an object with members `mask` and `binary`. `dontFailWhenClosed` indicates whether or not to throw if the connection isnt open. +- {Function} -### websocket.pong([data], [options], [dontFailWhenClosed]) +An event listener to be called when a message is received from the server. The +listener receives a `MessageEvent` named "message". -Sends a pong. `data` is sent, `options` is an object with members `mask` and `binary`. `dontFailWhenClosed` indicates whether or not to throw if the connection isnt open. +### websocket.onopen +- {Function} -### websocket.resume() +An event listener to be called when the connection is established. The listener +receives an `OpenEvent` named "open". -Resume the client stream +### websocket.pause() -### websocket.send(data, [options], [callback]) +Pause the socket. -* `data` Any The data to send. -* `options` Object An options object. - * `compress` Boolean Specifies whether `data` should be compressed or not. - Defaults to `true` when permessage-deflate is enabled. - * `binary` Boolean Specifies whether `data` should be sent as a binary or not. - Default is autodetected. - * `mask` Boolean Specifies whether `data` should be masked or not. Defaults +### websocket.ping([data[, options[, dontFailWhenClosed]]]) + +- `data` {Any} The data to send in the ping frame. +- `options` {Object} + - `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults to `true` when `websocket` is not a server client. - * `fin` Boolean Specifies whether `data` is the last fragment of a message or - not. Defaults to `true`. -* `callback` Function An optional callback which is invoked when the send - completes. +- `dontFailWhenClosed` {Boolean} Specifies whether or not to throw an error if + the connection is not open. -Sends `data` through the connection. +Send a ping. -### websocket.stream([options], callback) +### websocket.pong([data[, options[, dontFailWhenClosed]]]) -Streams data through calls to a user supplied function. `options` can be an object with members `mask` and `binary`. `callback` is executed on successive ticks of which send is `function (data, final)`. +- `data` {Any} The data to send in the ping frame. +- `options` {Object} + - `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults + to `true` when `websocket` is not a server client. +- `dontFailWhenClosed` {Boolean} Specifies whether or not to throw an error if + the connection is not open. -### websocket.terminate() +Send a pong. -Immediately shuts down the connection +### websocket.protocol -### websocket.onopen -### websocket.onerror -### websocket.onclose -### websocket.onmessage +- {String} -Emulates the W3C Browser based WebSocket interface using function members. +The subprotocol selected by the server. -### websocket.addEventListener(method, listener) +### websocket.protocolVersion -Emulates the W3C Browser based WebSocket interface using addEventListener. +- {Number} -### Event: 'error' +The WebSocket protocol version used for this connection, 8 or 13. -`function (error) { }` +### websocket.readyState -If the client emits an error, this event is emitted (errors from the underlying `net.Socket` are forwarded here). +- {Number} -### Event: 'close' +The current state of the connection. This is one of the ready state constants. -`function (code, message) { }` +### websocket.removeEventListener(type, listener) -Is emitted when the connection is closed. `code` is defined in the WebSocket specification. +- `type` {String} A string representing the event type to remove. +- `listener` {Function} The listener to remove. -The `close` event is also emitted when then underlying `net.Socket` closes the connection (`end` or `close`). +Removes an event listener emulating the `EventTarget` interface. -### Event: 'message' +### websocket.resume() -`function (data, flags) { }` +Resume the socket -Is emitted when data is received. `flags` is an object with member `binary`. +### websocket.send(data, [options][, callback]) -### Event: 'ping' +- `data` {Any} The data to send. +- `options` {Object} + - `compress` {Boolean} Specifies whether `data` should be compressed or not. + Defaults to `true` when permessage-deflate is enabled. + - `binary` {Boolean} Specifies whether `data` should be sent as a binary or not. + Default is autodetected. + - `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults + to `true` when `websocket` is not a server client. + - `fin` {Boolean} Specifies whether `data` is the last fragment of a message or + not. Defaults to `true`. +- `callback` {Function} An optional callback which is invoked when `data` is + written out. -`function (data, flags) { }` +Sends `data` through the connection. -Is emitted when a ping is received. `flags` is an object with member `binary`. +### websocket.terminate() -### Event: 'pong' +Send a FIN packet to the other peer. -`function (data, flags) { }` +### websocket.upgradeReq -Is emitted when a pong is received. `flags` is an object with member `binary`. +- {http.IncomingMessage} -### Event: 'open' +The http GET request sent by the client. Useful for parsing authorty headers, +cookie headers, and other information. This is only available for server clients. -`function () { }` +### websocket.url -Emitted when the connection is established. +- {String} + +The URL of the WebSocket server. Server clients don't have this attribute. + +[permessage-deflate]: https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19 From 099c63647fd95249e8e9f3fc980802169fc16729 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 14 Dec 2016 18:00:29 +0100 Subject: [PATCH 218/669] [major] Refactor `WebSocket` to use class syntax (#930) --- lib/WebSocket.js | 667 +++++++++++++++++++++-------------------- test/WebSocket.test.js | 7 - 2 files changed, 335 insertions(+), 339 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 5899c3a1a..794e74692 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -11,7 +11,6 @@ const crypto = require('crypto'); const Ultron = require('ultron'); const https = require('https'); const http = require('http'); -const util = require('util'); const url = require('url'); const PerMessageDeflate = require('./PerMessageDeflate'); @@ -25,277 +24,391 @@ const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection const protocolVersion = 13; /** - * WebSocket implementation + * Class representing a WebSocket. * - * @constructor - * @param {String} address Connection address. - * @param {String|Array} protocols WebSocket protocols. - * @param {Object} options Additional connection options. - * @api public + * @extends EventEmitter */ -function WebSocket (address, protocols, options) { - if (this instanceof WebSocket === false) { - return new WebSocket(address, protocols, options); - } +class WebSocket extends EventEmitter { + /** + * Create a new `WebSocket`. + * + * @param {String} address The URL to which to connect + * @param {(String|String[])} protocols The subprotocols + * @param {Object} options Connection options + */ + constructor (address, protocols, options) { + super(); + + if (typeof protocols === 'object' && !Array.isArray(protocols)) { + // + // Accept the `options` object as the 2nd argument. + // + options = protocols; + protocols = null; + } - EventEmitter.call(this); + if (typeof protocols === 'string') protocols = [protocols]; + if (!Array.isArray(protocols)) protocols = []; - if (protocols && !Array.isArray(protocols) && typeof protocols === 'object') { - // accept the "options" Object as the 2nd argument - options = protocols; - protocols = null; - } - - if (typeof protocols === 'string') { - protocols = [ protocols ]; - } + this._finalize = this.finalize.bind(this); - if (!Array.isArray(protocols)) { - protocols = []; - } + this._binaryType = 'nodebuffer'; + this._closeReceived = false; + this.bytesReceived = 0; + this.readyState = null; + this.extensions = {}; + this._socket = null; + this._ultron = null; - this._socket = null; - this._ultron = null; - this._closeReceived = false; - this.bytesReceived = 0; - this.readyState = null; - this.extensions = {}; - this._binaryType = 'nodebuffer'; - - if (Array.isArray(address)) { - initAsServerClient.apply(this, address.concat(options)); - } else { - initAsClient.apply(this, [address, protocols, options]); + if (Array.isArray(address)) { + initAsServerClient.call(this, address[0], address[1], address[2], options); + } else { + initAsClient.call(this, address, protocols, options); + } } -} -/** - * Inherits from EventEmitter. - */ -util.inherits(WebSocket, EventEmitter); + get CONNECTING () { return WebSocket.CONNECTING; } + get CLOSING () { return WebSocket.CLOSING; } + get CLOSED () { return WebSocket.CLOSED; } + get OPEN () { return WebSocket.OPEN; } -/** - * Ready States - */ -['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'].forEach(function each (state, index) { - WebSocket.prototype[state] = WebSocket[state] = index; -}); + /** + * @type {Number} + */ + get bufferedAmount () { + var amount = 0; -/** - * Gracefully closes the connection, after sending a description message to the server - * - * @param {Object} data to be sent to the server - * @api public - */ -WebSocket.prototype.close = function close (code, data) { - if (this.readyState === WebSocket.CLOSED) return; + if (this._socket) amount = this._socket.bufferSize || 0; + return amount; + } - if (this.readyState === WebSocket.CONNECTING) { - this.readyState = WebSocket.CLOSED; - return; + /** + * This deviates from the WHATWG interface since ws doesn't support the required + * default "blob" type (instead we define a custom "nodebuffer" type). + * + * @type {String} + */ + get binaryType () { + return this._binaryType; } - if (this.readyState === WebSocket.CLOSING) { - if (this._closeReceived && this._isServer) { - this.terminate(); + set binaryType (type) { + if (type === 'arraybuffer' || type === 'nodebuffer') { + this._binaryType = type; + } else { + throw new SyntaxError('unsupported binaryType: must be either "nodebuffer" or "arraybuffer"'); } - return; } - try { - this.readyState = WebSocket.CLOSING; - this._closeCode = code; - this._closeMessage = data; - var mask = !this._isServer; - this._sender.close(code, data, mask, (err) => { - if (err) this.emit('error', err); + /** + * Set up the socket and the internal resources. + * + * @param {net.Socket} socket The network socket between the server and client + * @param {Buffer} head The first packet of the upgraded stream + * @private + */ + setSocket (socket, head) { + socket.setTimeout(0); + socket.setNoDelay(); + + this._receiver = new Receiver(this.extensions, this.maxPayload); + this._sender = new Sender(socket, this.extensions); + this._ultron = new Ultron(socket); + this._socket = socket; + + // socket cleanup handlers + this._ultron.on('close', this._finalize); + this._ultron.on('error', this._finalize); + this._ultron.on('end', this._finalize); + + // ensure that the head is added to the receiver + if (head && head.length > 0) { + socket.unshift(head); + head = null; + } - if (this._closeReceived && this._isServer) { - this.terminate(); - } else { - // ensure that the connection is cleaned up even when no response of closing handshake. - clearTimeout(this._closeTimer); - this._closeTimer = setTimeout(cleanupWebsocketResources.bind(this, true), closeTimeout); - } + // subsequent packets are pushed to the receiver + this._ultron.on('data', (data) => { + this.bytesReceived += data.length; + this._receiver.add(data); }); - } catch (e) { - this.emit('error', e); - } -}; -/** - * Pause the client stream - * - * @api public - */ -WebSocket.prototype.pause = function pauser () { - if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); + // receiver event handlers + this._receiver.ontext = (data, flags) => this.emit('message', data, flags); + this._receiver.onbinary = (data, flags) => { + flags.binary = true; + this.emit('message', data, flags); + }; + this._receiver.onping = (data, flags) => { + this.pong(data, { mask: !this._isServer }, true); + this.emit('ping', data, flags); + }; + this._receiver.onpong = (data, flags) => this.emit('pong', data, flags); + this._receiver.onclose = (code, data, flags) => { + this._closeReceived = true; + this.close(code, data); + }; + this._receiver.onerror = (error, errorCode) => { + // close the connection when the receiver reports a HyBi error code + this.close(errorCode, ''); + this.emit('error', error); + }; - return this._socket.pause(); -}; + // sender event handlers + this._sender.onerror = (error) => { + this.close(1002, ''); + this.emit('error', error); + }; -/** - * Sends a ping - * - * @param {Object} data to be sent to the server - * @param {Object} Members - mask: boolean, binary: boolean - * @param {boolean} dontFailWhenClosed indicates whether or not to throw if the connection isnt open - * @api public - */ -WebSocket.prototype.ping = function ping (data, options, dontFailWhenClosed) { - if (this.readyState !== WebSocket.OPEN) { - if (dontFailWhenClosed === true) return; - throw new Error('not opened'); + this.readyState = WebSocket.OPEN; + this.emit('open'); } - options = options || {}; + /** + * Clean up and release internal resources and emit the `close` event. + * + * @param {(Boolean|Error)} Indicates whether or not an error occurred + * @private + */ + finalize (error) { + if (this.readyState === WebSocket.CLOSED) return; - if (options.mask === undefined) options.mask = !this._isServer; + this.readyState = WebSocket.CLOSED; - this._sender.ping(data, options); -}; + clearTimeout(this._closeTimer); + this._closeTimer = null; -/** - * Sends a pong - * - * @param {Object} data to be sent to the server - * @param {Object} Members - mask: boolean, binary: boolean - * @param {boolean} dontFailWhenClosed indicates whether or not to throw if the connection isnt open - * @api public - */ -WebSocket.prototype.pong = function (data, options, dontFailWhenClosed) { - if (this.readyState !== WebSocket.OPEN) { - if (dontFailWhenClosed === true) return; - throw new Error('not opened'); - } + // If the connection was closed abnormally (with an error), or if + // the close control frame was not received then the close code + // must default to 1006. + if (error || !this._closeReceived) { + this._closeCode = 1006; + } + this.emit('close', this._closeCode || 1000, this._closeMessage || ''); - options = options || {}; + if (this._socket) { + if (this._ultron) this._ultron.destroy(); + this._socket.on('error', function onerror () { + try { + this.destroy(); + } catch (e) {} + }); + + try { + if (!error) this._socket.end(); + else this._socket.destroy(); + } catch (e) { /* Ignore termination errors */ } - if (options.mask === undefined) options.mask = !this._isServer; + this._socket = null; + this._ultron = null; + } - this._sender.pong(data, options); -}; + if (this._sender) { + this._sender = this._sender.onerror = null; + } -/** - * Resume the client stream - * - * @api public - */ -WebSocket.prototype.resume = function resume () { - if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); + if (this._receiver) { + this._receiver.cleanup(); + this._receiver = null; + } - return this._socket.resume(); -}; + if (this.extensions[PerMessageDeflate.extensionName]) { + this.extensions[PerMessageDeflate.extensionName].cleanup(); + } -/** - * Sends a piece of data - * - * @param {Object} data to be sent to the server - * @param {Object} Members - mask: boolean, binary: boolean, compress: boolean - * @param {function} Optional callback which is executed after the send completes - * @api public - */ + this.extensions = null; -WebSocket.prototype.send = function send (data, options, cb) { - if (typeof options === 'function') { - cb = options; - options = {}; + this.removeAllListeners(); + this.on('error', function onerror () {}); // catch all errors after this } - if (this.readyState !== WebSocket.OPEN) { - if (cb) cb(new Error('not opened')); - else throw new Error('not opened'); - return; + /** + * Pause the socket stream. + * + * @public + */ + pause () { + if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); + + this._socket.pause(); } - if (!data) data = ''; + /** + * Resume the socket stream + * + * @public + */ + resume () { + if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); + + this._socket.resume(); + } - options = options || {}; - if (options.fin !== false) options.fin = true; + /** + * Start a closing handshake. + * + * @param {Number} code Status code explaining why the connection is closing + * @param {String} data A string explaining why the connection is closing + * @public + */ + close (code, data) { + if (this.readyState === WebSocket.CLOSED) return; + + if (this.readyState === WebSocket.CONNECTING) { + this.readyState = WebSocket.CLOSED; + return; + } - if (options.binary === undefined) { - options.binary = data instanceof Buffer || data instanceof ArrayBuffer || - ArrayBuffer.isView(data); + if (this.readyState === WebSocket.CLOSING) { + if (this._closeReceived && this._isServer) { + this.terminate(); + } + return; + } + + try { + this.readyState = WebSocket.CLOSING; + this._closeCode = code; + this._closeMessage = data; + var mask = !this._isServer; + this._sender.close(code, data, mask, (err) => { + if (err) this.emit('error', err); + + if (this._closeReceived && this._isServer) { + this.terminate(); + } else { + // + // Ensure that the connection is cleaned up even when the closing + // handshake fails. + // + clearTimeout(this._closeTimer); + this._closeTimer = setTimeout(this._finalize, closeTimeout, true); + } + }); + } catch (e) { + this.emit('error', e); + } } - if (options.mask === undefined) options.mask = !this._isServer; - if (options.compress === undefined) options.compress = true; - if (!this.extensions[PerMessageDeflate.extensionName]) { - options.compress = false; + /** + * Send a ping message. + * + * @param {*} data The message to send + * @param {Object} options Options object + * @param {Boolean} options.mask Indicates whether or not to mask `data` + * @param {Boolean} dontFailWhenClosed Indicates whether or not to throw an if the connection isn't open + * @public + */ + ping (data, options, dontFailWhenClosed) { + if (this.readyState !== WebSocket.OPEN) { + if (dontFailWhenClosed) return; + throw new Error('not opened'); + } + + options = options || {}; + if (options.mask === undefined) options.mask = !this._isServer; + + this._sender.ping(data, options); } - this._sender.send(data, options, cb); -}; + /** + * Send a pong message. + * + * @param {*} data The message to send + * @param {Object} options Options object + * @param {Boolean} options.mask Indicates whether or not to mask `data` + * @param {Boolean} dontFailWhenClosed Indicates whether or not to throw an if the connection isn't open + * @public + */ + pong (data, options, dontFailWhenClosed) { + if (this.readyState !== WebSocket.OPEN) { + if (dontFailWhenClosed) return; + throw new Error('not opened'); + } -/** - * Immediately shuts down the connection - * - * @api public - */ -WebSocket.prototype.terminate = function terminate () { - if (this.readyState === WebSocket.CLOSED) return; + options = options || {}; + if (options.mask === undefined) options.mask = !this._isServer; - if (this._socket) { - this.readyState = WebSocket.CLOSING; + this._sender.pong(data, options); + } - // End the connection - try { - this._socket.end(); - } catch (e) { - // Socket error during end() call, so just destroy it right now - cleanupWebsocketResources.call(this, true); + /** + * Send a data message. + * + * @param {*} data The message to send + * @param {Object} options Options object + * @param {Boolean} options.compress Specifies whether or not to compress `data` + * @param {Boolean} options.binary Specifies whether `data` is binary or text + * @param {Boolean} options.fin Specifies whether the fragment is the last one + * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @param {Function} cb Callback which is executed when data is written out + * @public + */ + send (data, options, cb) { + if (typeof options === 'function') { + cb = options; + options = {}; + } + + if (this.readyState !== WebSocket.OPEN) { + if (cb) cb(new Error('not opened')); + else throw new Error('not opened'); return; } - // Add a timeout to ensure that the connection is completely - // cleaned up within 30 seconds, even if the clean close procedure - // fails for whatever reason - // First cleanup any pre-existing timeout from an earlier "terminate" call, - // if one exists. Otherwise terminate calls in quick succession will leak timeouts - // and hold the program open for `closeTimout` time. - if (this._closeTimer) { clearTimeout(this._closeTimer); } - this._closeTimer = setTimeout(cleanupWebsocketResources.bind(this, true), closeTimeout); - } else if (this.readyState === WebSocket.CONNECTING) { - cleanupWebsocketResources.call(this, true); - } -}; + if (!data) data = ''; -/** - * Expose the `bufferedAmount` attribute. - * - * @public - */ -Object.defineProperty(WebSocket.prototype, 'bufferedAmount', { - get () { - var amount = 0; - if (this._socket) { - amount = this._socket.bufferSize || 0; + options = options || {}; + if (options.fin !== false) options.fin = true; + + if (options.binary === undefined) { + options.binary = data instanceof Buffer || data instanceof ArrayBuffer || + ArrayBuffer.isView(data); } - return amount; + + if (options.mask === undefined) options.mask = !this._isServer; + if (options.compress === undefined) options.compress = true; + if (!this.extensions[PerMessageDeflate.extensionName]) { + options.compress = false; + } + + this._sender.send(data, options, cb); } -}); -/** - * Expose the `binaryType` attribute. - * - * This deviates from the WHATWG interface since ws doesn't support the required - * default "blob" type (instead we define a custom "nodebuffer" type). - * - * @see {@link https://html.spec.whatwg.org/multipage/comms.html#dom-websocket-binarytype} - * @public - */ -Object.defineProperty(WebSocket.prototype, 'binaryType', { - get () { - return this._binaryType; - }, - set (type) { - if (type === 'arraybuffer' || type === 'nodebuffer') { - this._binaryType = type; - } else { - throw new SyntaxError('unsupported binaryType: must be either "nodebuffer" or "arraybuffer"'); + /** + * Half-close the socket sending a FIN packet. + * + * @public + */ + terminate () { + if (this.readyState === WebSocket.CLOSED) return; + + if (this._socket) { + this.readyState = WebSocket.CLOSING; + + try { + this._socket.end(); + } catch (e) { + this.finalize(true); + return; + } + + // + // Add a timeout to ensure that the connection is completely cleaned up + // within 30 seconds, even if the other peer does not send a FIN packet. + // + if (this._closeTimer) clearTimeout(this._closeTimer); + this._closeTimer = setTimeout(this._finalize, closeTimeout, true); + } else if (this.readyState === WebSocket.CONNECTING) { + this.finalize(true); } } -}); +} + +WebSocket.CONNECTING = 0; +WebSocket.OPEN = 1; +WebSocket.CLOSING = 2; +WebSocket.CLOSED = 3; // // Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes. @@ -375,7 +488,7 @@ function initAsServerClient (req, socket, head, options) { this.upgradeReq = req; this._isServer = true; - establishConnection.call(this, socket, head); + this.setSocket(socket, head); } /** @@ -543,7 +656,7 @@ function initAsClient (address, protocols, options) { req.on('error', (error) => { this.emit('error', error); - cleanupWebsocketResources.call(this, error); + this.finalize(error); }); req.on('response', (res) => { @@ -555,10 +668,10 @@ function initAsClient (address, protocols, options) { this.emit('error', error); } - cleanupWebsocketResources.call(this, error); + this.finalize(error); }); - req.on('upgrade', (res, socket, upgradeHead) => { + req.on('upgrade', (res, socket, head) => { if (this.readyState === WebSocket.CLOSED) { // client closed before server accepted connection this.emit('close'); @@ -612,119 +725,9 @@ function initAsClient (address, protocols, options) { this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } - establishConnection.call(this, socket, upgradeHead); + this.setSocket(socket, head); - // perform cleanup on http resources req.removeAllListeners(); agent = null; }); } - -function establishConnection (socket, upgradeHead) { - socket.setTimeout(0); - socket.setNoDelay(); - - this._receiver = new Receiver(this.extensions, this.maxPayload); - this._sender = new Sender(socket, this.extensions); - this._ultron = new Ultron(socket); - this._socket = socket; - - // socket cleanup handlers - this._ultron.on('end', cleanupWebsocketResources.bind(this)); - this._ultron.on('close', cleanupWebsocketResources.bind(this)); - this._ultron.on('error', cleanupWebsocketResources.bind(this)); - - // ensure that the upgradeHead is added to the receiver - if (upgradeHead && upgradeHead.length > 0) { - socket.unshift(upgradeHead); - upgradeHead = null; - } - - // subsequent packets are pushed to the receiver - this._ultron.on('data', (data) => { - this.bytesReceived += data.length; - this._receiver.add(data); - }); - - // receiver event handlers - this._receiver.ontext = (data, flags) => this.emit('message', data, flags); - this._receiver.onbinary = (data, flags) => { - flags.binary = true; - this.emit('message', data, flags); - }; - this._receiver.onping = (data, flags) => { - this.pong(data, { mask: !this._isServer }, true); - this.emit('ping', data, flags); - }; - this._receiver.onpong = (data, flags) => this.emit('pong', data, flags); - this._receiver.onclose = (code, data, flags) => { - this._closeReceived = true; - this.close(code, data); - }; - this._receiver.onerror = (error, errorCode) => { - // close the connection when the receiver reports a HyBi error code - this.close(errorCode, ''); - this.emit('error', error); - }; - - // sender event handlers - this._sender.onerror = (error) => { - this.close(1002, ''); - this.emit('error', error); - }; - - this.readyState = WebSocket.OPEN; - this.emit('open'); -} - -function cleanupWebsocketResources (error) { - if (this.readyState === WebSocket.CLOSED) return; - - this.readyState = WebSocket.CLOSED; - - clearTimeout(this._closeTimer); - this._closeTimer = null; - - // If the connection was closed abnormally (with an error), or if - // the close control frame was not received then the close code - // must default to 1006. - if (error || !this._closeReceived) { - this._closeCode = 1006; - } - this.emit('close', this._closeCode || 1000, this._closeMessage || ''); - - if (this._socket) { - if (this._ultron) this._ultron.destroy(); - this._socket.on('error', function onerror () { - try { - this.destroy(); - } catch (e) {} - }); - - try { - if (!error) this._socket.end(); - else this._socket.destroy(); - } catch (e) { /* Ignore termination errors */ } - - this._socket = null; - this._ultron = null; - } - - if (this._sender) { - this._sender = this._sender.onerror = null; - } - - if (this._receiver) { - this._receiver.cleanup(); - this._receiver = null; - } - - if (this.extensions[PerMessageDeflate.extensionName]) { - this.extensions[PerMessageDeflate.extensionName].cleanup(); - } - - this.extensions = null; - - this.removeAllListeners(); - this.on('error', function onerror () {}); // catch all errors after this -} diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 9771978cc..cd4846557 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -20,13 +20,6 @@ class CustomAgent extends http.Agent { describe('WebSocket', function () { describe('#ctor', function () { - it('should return a new instance if called without new', function (done) { - const ws = WebSocket('ws://localhost'); - - assert.ok(ws instanceof WebSocket); - ws.on('error', () => done()); - }); - it('throws an error when using an invalid url', function () { assert.throws( () => new WebSocket('echo.websocket.org'), From 68994638178123e4fd7ad798d5286e257bda71cb Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 14 Dec 2016 19:35:46 +0100 Subject: [PATCH 219/669] [minor] Set `readyState` to `CONNECTING` in the constructor --- lib/WebSocket.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 794e74692..309106743 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -52,10 +52,10 @@ class WebSocket extends EventEmitter { this._finalize = this.finalize.bind(this); + this.readyState = WebSocket.CONNECTING; this._binaryType = 'nodebuffer'; this._closeReceived = false; this.bytesReceived = 0; - this.readyState = null; this.extensions = {}; this._socket = null; this._ultron = null; @@ -484,7 +484,6 @@ function initAsServerClient (req, socket, head, options) { this.maxPayload = options.maxPayload; this.protocol = options.protocol; - this.readyState = WebSocket.CONNECTING; this.upgradeReq = req; this._isServer = true; @@ -571,7 +570,6 @@ function initAsClient (address, protocols, options) { // Expose state properties. // this.protocolVersion = options.protocolVersion; - this.readyState = WebSocket.CONNECTING; this._isServer = false; this.url = address; From cf40f959c3c5bd5baee51432cfeaedd4c3ddb165 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 15 Dec 2016 09:54:02 +0100 Subject: [PATCH 220/669] [minor] Initialize more properties in the constructor --- lib/WebSocket.js | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 309106743..4877e2e3f 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -40,9 +40,6 @@ class WebSocket extends EventEmitter { super(); if (typeof protocols === 'object' && !Array.isArray(protocols)) { - // - // Accept the `options` object as the 2nd argument. - // options = protocols; protocols = null; } @@ -50,13 +47,17 @@ class WebSocket extends EventEmitter { if (typeof protocols === 'string') protocols = [protocols]; if (!Array.isArray(protocols)) protocols = []; - this._finalize = this.finalize.bind(this); - this.readyState = WebSocket.CONNECTING; - this._binaryType = 'nodebuffer'; - this._closeReceived = false; this.bytesReceived = 0; this.extensions = {}; + this.protocol = ''; + + this._finalize = this.finalize.bind(this); + this._binaryType = 'nodebuffer'; + this._closeReceived = false; + this._closeTimer = null; + this._receiver = null; + this._sender = null; this._socket = null; this._ultron = null; @@ -501,7 +502,7 @@ function initAsServerClient (req, socket, head, options) { * @param {String} options.localAddress Local interface to bind for network connections * @param {Number} options.protocolVersion Value of the `Sec-WebSocket-Version` header * @param {Object} options.headers An object containing request headers - * @param {String} options.origin Value of the `Sec-WebSocket-Version` header + * @param {String} options.origin Value of the `Origin` or `Sec-WebSocket-Origin` header * @param {http.Agent} options.agent Use the specified Agent * @param {String} options.host Value of the `Host` header * @param {Function} options.checkServerIdentity A function to validate the server hostname @@ -542,6 +543,10 @@ function initAsClient (address, protocols, options) { throw new Error('unsupported protocol version'); } + this.protocolVersion = options.protocolVersion; + this._isServer = false; + this.url = address; + const serverUrl = url.parse(address); const isUnixSocket = serverUrl.protocol === 'ws+unix:'; @@ -566,15 +571,6 @@ function initAsClient (address, protocols, options) { extensionsOffer[PerMessageDeflate.extensionName] = perMessageDeflate.offer(); } - // - // Expose state properties. - // - this.protocolVersion = options.protocolVersion; - this._isServer = false; - this.url = address; - - var agent = options.agent; - const requestOptions = { host: serverUrl.hostname, path: '/', @@ -619,6 +615,8 @@ function initAsClient (address, protocols, options) { } } + var agent = options.agent; + // // A custom agent is required for these options. // From 76143e9909f1dc6d4630ad2e3c3c8e9681402eec Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 15 Dec 2016 10:11:16 +0100 Subject: [PATCH 221/669] [minor] Remove unnecessary `buildHostHeader` function (#932) --- lib/WebSocket.js | 25 +------------------------ test/WebSocket.test.js | 31 ++++++++++++++++++++----------- 2 files changed, 21 insertions(+), 35 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 4877e2e3f..fc45b08e3 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -444,27 +444,6 @@ WebSocket.prototype.addEventListener = EventTarget.addEventListener; WebSocket.prototype.removeEventListener = EventTarget.removeEventListener; module.exports = WebSocket; -module.exports.buildHostHeader = buildHostHeader; - -/** - * Append port number to Host header, only if specified in the URL and - * non-default. - * - * @param {Boolean} isSecure Specifies whether or not the URL scheme is `wss` - * @param {String} hostname The hostname portion of the URL - * @param {Number} port The port portion of the URL - * @return {String} The field value of the `Host` header - * @private - */ -function buildHostHeader (isSecure, hostname, port) { - var headerHost = hostname; - - if (headerHost && (isSecure && port !== 443 || !isSecure && port !== 80)) { - headerHost = `${headerHost}:${port}`; - } - - return headerHost; -} /** * Initialize a WebSocket server client. @@ -554,7 +533,6 @@ function initAsClient (address, protocols, options) { const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:'; const key = crypto.randomBytes(16).toString('base64'); - const port = serverUrl.port || (isSecure ? 443 : 80); const httpObj = isSecure ? https : http; // @@ -573,10 +551,9 @@ function initAsClient (address, protocols, options) { const requestOptions = { host: serverUrl.hostname, + port: serverUrl.port, path: '/', - port, headers: { - 'Host': buildHostHeader(isSecure, serverUrl.hostname, port), 'Sec-WebSocket-Version': options.protocolVersion, 'Sec-WebSocket-Key': key, 'Connection': 'Upgrade', diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index cd4846557..ffea6767d 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1453,17 +1453,26 @@ describe('WebSocket', function () { }); it('excludes default ports from host header', function () { - // can't create a server listening on ports 80 or 443 - // so we need to expose the method that does this - const buildHostHeader = WebSocket.buildHostHeader; - let host = buildHostHeader(false, 'localhost', 80); - assert.strictEqual(host, 'localhost'); - host = buildHostHeader(false, 'localhost', 88); - assert.strictEqual(host, 'localhost:88'); - host = buildHostHeader(true, 'localhost', 443); - assert.strictEqual(host, 'localhost'); - host = buildHostHeader(true, 'localhost', 8443); - assert.strictEqual(host, 'localhost:8443'); + const httpsAgent = new https.Agent(); + const httpAgent = new http.Agent(); + const values = []; + let ws; + + httpsAgent.addRequest = httpAgent.addRequest = (req) => { + values.push(req._headers.host); + }; + + ws = new WebSocket('wss://localhost:8443', { agent: httpsAgent }); + ws = new WebSocket('wss://localhost:443', { agent: httpsAgent }); + ws = new WebSocket('ws://localhost:88', { agent: httpAgent }); + ws = new WebSocket('ws://localhost:80', { agent: httpAgent }); + + assert.deepStrictEqual(values, [ + 'localhost:8443', + 'localhost', + 'localhost:88', + 'localhost' + ]); }); }); From 9c116af775bf37ac1a47c093a2376b2cbe66fdeb Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 16 Dec 2016 18:12:30 +0100 Subject: [PATCH 222/669] [minor] Change `Sender.prototype.frameAndSend()` signature (#933) --- bench/sender.benchmark.js | 35 +++++++--- lib/Sender.js | 137 +++++++++++++++++++++++--------------- test/Sender.test.js | 24 ++++++- 3 files changed, 131 insertions(+), 65 deletions(-) diff --git a/bench/sender.benchmark.js b/bench/sender.benchmark.js index 8473e561d..a0f19a870 100644 --- a/bench/sender.benchmark.js +++ b/bench/sender.benchmark.js @@ -17,20 +17,35 @@ const data3 = crypto.randomBytes(64 * 1024); const data4 = crypto.randomBytes(200 * 1024); const data5 = crypto.randomBytes(1024 * 1024); +const opts1 = { + readOnly: false, + mask: false, + rsv1: false, + opcode: 2, + fin: true +}; +const opts2 = { + readOnly: true, + rsv1: false, + mask: true, + opcode: 2, + fin: true +}; + const suite = new benchmark.Suite(); var sender = new Sender(); sender._socket = { write () {} }; -suite.add('frameAndSend, unmasked (64 B)', () => sender.frameAndSend(0x2, data1, false, true, false)); -suite.add('frameAndSend, masked (64 B)', () => sender.frameAndSend(0x2, data1, true, true, true)); -suite.add('frameAndSend, unmasked (16 KiB)', () => sender.frameAndSend(0x2, data2, false, true, false)); -suite.add('frameAndSend, masked (16 KiB)', () => sender.frameAndSend(0x2, data2, true, true, true)); -suite.add('frameAndSend, unmasked (64 KiB)', () => sender.frameAndSend(0x2, data3, false, true, false)); -suite.add('frameAndSend, masked (64 KiB)', () => sender.frameAndSend(0x2, data3, true, true, true)); -suite.add('frameAndSend, unmasked (200 KiB)', () => sender.frameAndSend(0x2, data4, false, true, false)); -suite.add('frameAndSend, masked (200 KiB)', () => sender.frameAndSend(0x2, data4, true, true, true)); -suite.add('frameAndSend, unmasked (1 MiB)', () => sender.frameAndSend(0x2, data5, false, true, false)); -suite.add('frameAndSend, masked (1 MiB)', () => sender.frameAndSend(0x2, data5, true, true, true)); +suite.add('frameAndSend, unmasked (64 B)', () => sender.frameAndSend(data1, opts1)); +suite.add('frameAndSend, masked (64 B)', () => sender.frameAndSend(data1, opts2)); +suite.add('frameAndSend, unmasked (16 KiB)', () => sender.frameAndSend(data2, opts1)); +suite.add('frameAndSend, masked (16 KiB)', () => sender.frameAndSend(data2, opts2)); +suite.add('frameAndSend, unmasked (64 KiB)', () => sender.frameAndSend(data3, opts1)); +suite.add('frameAndSend, masked (64 KiB)', () => sender.frameAndSend(data3, opts2)); +suite.add('frameAndSend, unmasked (200 KiB)', () => sender.frameAndSend(data4, opts1)); +suite.add('frameAndSend, masked (200 KiB)', () => sender.frameAndSend(data4, opts2)); +suite.add('frameAndSend, unmasked (1 MiB)', () => sender.frameAndSend(data5, opts1)); +suite.add('frameAndSend, masked (1 MiB)', () => sender.frameAndSend(data5, opts2)); suite.on('cycle', (e) => console.log(e.target.toString())); diff --git a/lib/Sender.js b/lib/Sender.js index 0091a849b..7476524a2 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -12,6 +12,8 @@ const PerMessageDeflate = require('./PerMessageDeflate'); const bufferUtil = require('./BufferUtil').BufferUtil; const ErrorCodes = require('./ErrorCodes'); +const noop = () => {}; + /** * HyBi Sender implementation. */ @@ -23,12 +25,12 @@ class Sender { * @param {Object} extensions An object containing the negotiated extensions */ constructor (socket, extensions) { - this.extensions = extensions || {}; + this.perMessageDeflate = (extensions || {})[PerMessageDeflate.extensionName]; this.firstFragment = true; this.processing = false; this.compress = false; this._socket = socket; - this.onerror = null; + this.onerror = noop; this.queue = []; } @@ -51,8 +53,8 @@ class Sender { buf.writeUInt16BE(code || 1000, 0, true); if (buf.length > 2) buf.write(data, 2); - if (this.extensions[PerMessageDeflate.extensionName]) { - this.enqueue([this.doClose, [buf, mask, cb]]); + if (this.perMessageDeflate) { + this.enqueue([this.doClose, buf, mask, cb]); } else { this.doClose(buf, mask, cb); } @@ -67,10 +69,15 @@ class Sender { * @private */ doClose (data, mask, cb) { - this.frameAndSend(0x08, data, false, true, mask, false, cb); - if (this.extensions[PerMessageDeflate.extensionName]) { - this.continue(); - } + this.frameAndSend(data, { + readOnly: false, + opcode: 0x08, + rsv1: false, + fin: true, + mask + }, cb); + + if (this.perMessageDeflate) this.continue(); } /** @@ -82,8 +89,8 @@ class Sender { * @public */ ping (data, options) { - if (this.extensions[PerMessageDeflate.extensionName]) { - this.enqueue([this.doPing, [data, options.mask]]); + if (this.perMessageDeflate) { + this.enqueue([this.doPing, data, options.mask]); } else { this.doPing(data, options.mask); } @@ -97,10 +104,15 @@ class Sender { * @private */ doPing (data, mask) { - this.frameAndSend(0x09, data, true, true, mask, false); - if (this.extensions[PerMessageDeflate.extensionName]) { - this.continue(); - } + this.frameAndSend(data, { + readOnly: true, + opcode: 0x09, + rsv1: false, + fin: true, + mask + }); + + if (this.perMessageDeflate) this.continue(); } /** @@ -112,8 +124,8 @@ class Sender { * @public */ pong (data, options) { - if (this.extensions[PerMessageDeflate.extensionName]) { - this.enqueue([this.doPong, [data, options.mask]]); + if (this.perMessageDeflate) { + this.enqueue([this.doPong, data, options.mask]); } else { this.doPong(data, options.mask); } @@ -127,10 +139,15 @@ class Sender { * @private */ doPong (data, mask) { - this.frameAndSend(0x0a, data, true, true, mask, false); - if (this.extensions[PerMessageDeflate.extensionName]) { - this.continue(); - } + this.frameAndSend(data, { + readOnly: true, + opcode: 0x0a, + rsv1: false, + fin: true, + mask + }); + + if (this.perMessageDeflate) this.continue(); } /** @@ -146,7 +163,6 @@ class Sender { * @public */ send (data, options, cb) { - const pmd = this.extensions[PerMessageDeflate.extensionName]; var opcode = options.binary ? 2 : 1; var rsv1 = options.compress; var readOnly = true; @@ -164,7 +180,9 @@ class Sender { if (this.firstFragment) { this.firstFragment = false; - if (rsv1 && data && pmd) rsv1 = data.length >= pmd.threshold; + if (rsv1 && data && this.perMessageDeflate) { + rsv1 = data.length >= this.perMessageDeflate.threshold; + } this.compress = rsv1; } else { rsv1 = false; @@ -173,41 +191,55 @@ class Sender { if (options.fin) this.firstFragment = true; - if (pmd) { - const args = [opcode, data, readOnly, options.fin, options.mask, rsv1, cb]; - this.enqueue([this.sendCompressed, args]); + if (this.perMessageDeflate) { + this.enqueue([this.sendCompressed, data, { + mask: options.mask, + fin: options.fin, + readOnly, + opcode, + rsv1 + }, cb]); } else { - this.frameAndSend(opcode, data, readOnly, options.fin, options.mask, false, cb); + this.frameAndSend(data, { + mask: options.mask, + fin: options.fin, + rsv1: false, + readOnly, + opcode + }, cb); } } /** * Compresses, frames and sends a data message. * - * @param {Number} opcode The opcode - * @param {*} data The message to send - * @param {Boolean} readOnly Specifies whether `data` can be modified - * @param {Boolean} fin Specifies whether or not to set the FIN bit - * @param {Boolean} mask Specifies whether or not to mask `data` - * @param {Boolean} rsv1 Specifies whether or not to set the RSV1 bit + * @param {Buffer} data The message to send + * @param {Object} options Options object + * @param {Number} options.opcode The opcode + * @param {Boolean} options.readOnly Specifies whether `data` can be modified + * @param {Boolean} options.fin Specifies whether or not to set the FIN bit + * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit * @param {Function} cb Callback * @private */ - sendCompressed (opcode, data, readOnly, fin, mask, rsv1, cb) { + sendCompressed (data, options, cb) { if (!this.compress) { - this.frameAndSend(opcode, data, readOnly, fin, mask, false, cb); + options.rsv1 = false; + this.frameAndSend(data, options, cb); this.continue(); return; } - this.extensions[PerMessageDeflate.extensionName].compress(data, fin, (err, buf) => { + this.perMessageDeflate.compress(data, options.fin, (err, buf) => { if (err) { if (cb) cb(err); else this.onerror(err); return; } - this.frameAndSend(opcode, buf, false, fin, mask, rsv1, cb); + options.readOnly = false; + this.frameAndSend(buf, options, cb); this.continue(); }); } @@ -215,21 +247,22 @@ class Sender { /** * Frames and sends a piece of data according to the HyBi WebSocket protocol. * - * @param {Number} opcode The opcode * @param {*} data The data to send - * @param {Boolean} readOnly Specifies whether `data` can be modified - * @param {Boolean} fin Specifies whether or not to set the FIN bit - * @param {Boolean} maskData Specifies whether or not to mask `data` - * @param {Boolean} rsv1 Specifies whether or not to set the RSV1 bit + * @param {Object} options Options object + * @param {Number} options.opcode The opcode + * @param {Boolean} options.readOnly Specifies whether `data` can be modified + * @param {Boolean} options.fin Specifies whether or not to set the FIN bit + * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit * @param {Function} cb Callback * @private */ - frameAndSend (opcode, data, readOnly, fin, maskData, rsv1, cb) { + frameAndSend (data, options, cb) { if (!data) { - const bytes = [opcode, 0]; + const bytes = [options.opcode, 0]; - if (fin) bytes[0] |= 0x80; - if (maskData) { + if (options.fin) bytes[0] |= 0x80; + if (options.mask) { bytes[1] |= 0x80; bytes.push(0, 0, 0, 0); } @@ -245,12 +278,12 @@ class Sender { data = viewToBuffer(data); } else { data = Buffer.from(typeof data === 'number' ? data.toString() : data); - readOnly = false; + options.readOnly = false; } } - const mergeBuffers = data.length < 1024 || maskData && readOnly; - var dataOffset = maskData ? 6 : 2; + const mergeBuffers = data.length < 1024 || options.mask && options.readOnly; + var dataOffset = options.mask ? 6 : 2; var payloadLength = data.length; if (data.length >= 65536) { @@ -265,8 +298,8 @@ class Sender { mergeBuffers ? data.length + dataOffset : dataOffset ); - outputBuffer[0] = fin ? opcode | 0x80 : opcode; - if (rsv1) outputBuffer[0] |= 0x40; + outputBuffer[0] = options.fin ? options.opcode | 0x80 : options.opcode; + if (options.rsv1) outputBuffer[0] |= 0x40; if (payloadLength === 126) { outputBuffer.writeUInt16BE(data.length, 2, true); @@ -275,7 +308,7 @@ class Sender { outputBuffer.writeUInt32BE(data.length, 6, true); } - if (maskData) { + if (options.mask) { const mask = getRandomMask(); outputBuffer[1] = payloadLength | 0x80; @@ -310,7 +343,7 @@ class Sender { this.processing = true; - handler[0].apply(this, handler[1]); + handler[0].apply(this, handler.slice(1)); } /** diff --git a/test/Sender.test.js b/test/Sender.test.js index 1b7ed063f..06840ce01 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -17,7 +17,13 @@ describe('Sender', function () { const sender = new Sender({ write: () => {} }); const buf = Buffer.from([1, 2, 3, 4, 5]); - sender.frameAndSend(2, buf, true, true, true); + sender.frameAndSend(buf, { + readOnly: true, + rsv1: false, + mask: true, + opcode: 2, + fin: true + }); assert.ok(buf.equals(Buffer.from([1, 2, 3, 4, 5]))); }); @@ -26,7 +32,13 @@ describe('Sender', function () { const sender = new Sender({ write: () => {} }); const text = Buffer.from('hi there'); - sender.frameAndSend(1, text, true, true, true); + sender.frameAndSend(text, { + readOnly: true, + rsv1: false, + mask: true, + opcode: 1, + fin: true + }); assert.ok(text.equals(Buffer.from('hi there'))); }); @@ -39,7 +51,13 @@ describe('Sender', function () { } }); - sender.frameAndSend(1, Buffer.from('hi'), false, true, false, true); + sender.frameAndSend(Buffer.from('hi'), { + readOnly: false, + mask: false, + rsv1: true, + opcode: 1, + fin: true + }); }); }); From 103fddbeafee3a349646908071b6d44bf10dd3ef Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 19 Dec 2016 10:28:21 +0100 Subject: [PATCH 223/669] [test] Clean up some permessage-deflate tests --- test/Receiver.test.js | 10 ++-- test/WebSocket.test.js | 113 ++++++++++++++++------------------------- 2 files changed, 52 insertions(+), 71 deletions(-) diff --git a/test/Receiver.test.js b/test/Receiver.test.js index ef5c249e0..d54cbb65d 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -692,7 +692,7 @@ describe('Receiver', function () { it('can cleanup when consuming data', function (done) { const perMessageDeflate = new PerMessageDeflate(); - perMessageDeflate.accept([{}]); + perMessageDeflate.accept([{ server_no_context_takeover: [true] }]); const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); const buf = Buffer.from('Hello'); @@ -701,11 +701,15 @@ describe('Receiver', function () { if (err) return done(err); const data = Buffer.concat([Buffer.from([0xc1, compressed.length]), compressed]); + p.add(data); p.add(data); - p.add(data); + + assert.strictEqual(p.state, 5); + assert.strictEqual(p.bufferedBytes, data.length); + + perMessageDeflate._inflate.on('close', done); p.cleanup(); - setTimeout(done, 1000); }); }); }); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index ffea6767d..35b004e49 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1523,11 +1523,11 @@ describe('WebSocket', function () { server.on('upgrade', (req, socket, head) => { const extensions = req.headers['sec-websocket-extensions']; - assert.notStrictEqual(extensions.indexOf('permessage-deflate'), -1); - assert.notStrictEqual(extensions.indexOf('server_no_context_takeover'), -1); - assert.notStrictEqual(extensions.indexOf('client_no_context_takeover'), -1); - assert.notStrictEqual(extensions.indexOf('server_max_window_bits=10'), -1); - assert.notStrictEqual(extensions.indexOf('client_max_window_bits'), -1); + assert.ok(extensions.includes('permessage-deflate')); + assert.ok(extensions.includes('server_no_context_takeover')); + assert.ok(extensions.includes('client_no_context_takeover')); + assert.ok(extensions.includes('server_max_window_bits=10')); + assert.ok(extensions.includes('client_max_window_bits')); }); server.listen(++port, () => { @@ -1549,25 +1549,22 @@ describe('WebSocket', function () { it('can send and receive text data', function (done) { const wss = new WebSocketServer({ - perMessageDeflate: true, + perMessageDeflate: { threshold: 0 }, port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { - perMessageDeflate: true + perMessageDeflate: { threshold: 0 } }); ws.on('open', () => ws.send('hi', { compress: true })); - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.strictEqual(message, 'hi'); - wss.close(); - done(); + wss.close(done); }); }); wss.on('connection', (ws) => { - ws.on('message', (message, flags) => ws.send(message, { - compress: true - })); + ws.on('message', (message) => ws.send(message, { compress: true })); }); }); @@ -1579,25 +1576,22 @@ describe('WebSocket', function () { } const wss = new WebSocketServer({ - perMessageDeflate: true, + perMessageDeflate: { threshold: 0 }, port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { - perMessageDeflate: true + perMessageDeflate: { threshold: 0 } }); ws.on('open', () => ws.send(array, { compress: true })); - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.ok(message.equals(Buffer.from(array.buffer))); - wss.close(); - done(); + wss.close(done); }); }); wss.on('connection', (ws) => { - ws.on('message', (message, flags) => ws.send(message, { - compress: true - })); + ws.on('message', (message) => ws.send(message, { compress: true })); }); }); @@ -1609,25 +1603,22 @@ describe('WebSocket', function () { } const wss = new WebSocketServer({ - perMessageDeflate: true, + perMessageDeflate: { threshold: 0 }, port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { - perMessageDeflate: true + perMessageDeflate: { threshold: 0 } }); ws.on('open', () => ws.send(array.buffer, { compress: true })); - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.ok(message.equals(Buffer.from(array.buffer))); - wss.close(); - done(); + wss.close(done); }); }); wss.on('connection', (ws) => { - ws.on('message', (message, flags) => ws.send(message, { - compress: true - })); + ws.on('message', (message) => ws.send(message, { compress: true })); }); }); @@ -1639,17 +1630,14 @@ describe('WebSocket', function () { }); ws.on('open', () => ws.send('hi', { compress: true })); - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.strictEqual(message, 'hi'); - wss.close(); - done(); + wss.close(done); }); }); wss.on('connection', (ws) => { - ws.on('message', (message, flags) => ws.send(message, { - compress: true - })); + ws.on('message', (message) => ws.send(message, { compress: true })); }); }); }); @@ -1657,27 +1645,26 @@ describe('WebSocket', function () { describe('#close', function () { it('should not raise error callback, if any, if called during send data', function (done) { const wss = new WebSocketServer({ - perMessageDeflate: true, + perMessageDeflate: { threshold: 0 }, port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { - perMessageDeflate: true + perMessageDeflate: { threshold: 0 } }); - let errorGiven = false; ws.on('open', () => { - ws.send('hi', (error) => { - errorGiven = !!error; - }); + ws.send('hi', (error) => assert.ifError(error)); ws.close(); }); + }); - ws.on('close', () => { - setTimeout(() => { - assert.ok(!errorGiven); - wss.close(); - done(); - }, 1000); + wss.on('connection', (ws) => { + ws.on('message', (message) => { + assert.strictEqual(message, 'hi'); + ws.on('close', (code) => { + assert.strictEqual(code, 1000); + wss.close(done); + }); }); }); }); @@ -1686,52 +1673,42 @@ describe('WebSocket', function () { describe('#terminate', function () { it('will raise error callback, if any, if called during send data', function (done) { const wss = new WebSocketServer({ - perMessageDeflate: true, + perMessageDeflate: { threshold: 0 }, port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { threshold: 0 } }); - let errorGiven = false; ws.on('open', () => { ws.send('hi', (error) => { - errorGiven = !!error; + assert.ok(error instanceof Error); + wss.close(done); }); ws.terminate(); }); - - ws.on('close', () => { - setTimeout(() => { - assert.ok(errorGiven); - wss.close(); - done(); - }, 1000); - }); }); }); it('can call during receiving data', function (done) { const wss = new WebSocketServer({ - perMessageDeflate: true, + perMessageDeflate: { threshold: 0 }, port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { - perMessageDeflate: true + perMessageDeflate: { threshold: 0 } }); wss.on('connection', (client) => { for (let i = 0; i < 10; i++) { client.send('hi'); } - client.send('hi', () => ws.terminate()); - }); - - ws.on('close', () => { - setTimeout(() => { - wss.close(); - done(); - }, 1000); + client.send('hi', () => { + ws.extensions['permessage-deflate']._inflate.on('close', () => { + wss.close(done); + }); + ws.terminate(); + }); }); }); }); From 5816e33059cfad4bf9fa533fa2474d6e60ed2ca0 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 20 Dec 2016 10:32:09 +0100 Subject: [PATCH 224/669] [minor] Remove unnecessary `try...catch` statements (#937) --- lib/Sender.js | 15 ++++-------- lib/WebSocket.js | 61 ++++++++++++++++++------------------------------ 2 files changed, 28 insertions(+), 48 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 7476524a2..eab04dbfd 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -409,15 +409,10 @@ function getRandomMask () { * @private */ function sendFramedData (sender, outputBuffer, data, cb) { - try { - if (data) { - sender._socket.write(outputBuffer); - sender._socket.write(data, cb); - } else { - sender._socket.write(outputBuffer, cb); - } - } catch (e) { - if (cb) cb(e); - else sender.onerror(e); + if (data) { + sender._socket.write(outputBuffer); + sender._socket.write(data, cb); + } else { + sender._socket.write(outputBuffer, cb); } } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index fc45b08e3..f1f1cd548 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -188,17 +188,13 @@ class WebSocket extends EventEmitter { this.emit('close', this._closeCode || 1000, this._closeMessage || ''); if (this._socket) { - if (this._ultron) this._ultron.destroy(); + this._ultron.destroy(); this._socket.on('error', function onerror () { - try { - this.destroy(); - } catch (e) {} + this.destroy(); }); - try { - if (!error) this._socket.end(); - else this._socket.destroy(); - } catch (e) { /* Ignore termination errors */ } + if (!error) this._socket.end(); + else this._socket.destroy(); this._socket = null; this._ultron = null; @@ -267,28 +263,23 @@ class WebSocket extends EventEmitter { return; } - try { - this.readyState = WebSocket.CLOSING; - this._closeCode = code; - this._closeMessage = data; - var mask = !this._isServer; - this._sender.close(code, data, mask, (err) => { - if (err) this.emit('error', err); - - if (this._closeReceived && this._isServer) { - this.terminate(); - } else { - // - // Ensure that the connection is cleaned up even when the closing - // handshake fails. - // - clearTimeout(this._closeTimer); - this._closeTimer = setTimeout(this._finalize, closeTimeout, true); - } - }); - } catch (e) { - this.emit('error', e); - } + this.readyState = WebSocket.CLOSING; + this._closeCode = code; + this._closeMessage = data; + this._sender.close(code, data, !this._isServer, (err) => { + if (err) this.emit('error', err); + + if (this._closeReceived && this._isServer) { + this.terminate(); + } else { + // + // Ensure that the connection is cleaned up even when the closing + // handshake fails. + // + clearTimeout(this._closeTimer); + this._closeTimer = setTimeout(this._finalize, closeTimeout, true); + } + }); } /** @@ -386,19 +377,13 @@ class WebSocket extends EventEmitter { if (this._socket) { this.readyState = WebSocket.CLOSING; - - try { - this._socket.end(); - } catch (e) { - this.finalize(true); - return; - } + this._socket.end(); // // Add a timeout to ensure that the connection is completely cleaned up // within 30 seconds, even if the other peer does not send a FIN packet. // - if (this._closeTimer) clearTimeout(this._closeTimer); + clearTimeout(this._closeTimer); this._closeTimer = setTimeout(this._finalize, closeTimeout, true); } else if (this.readyState === WebSocket.CONNECTING) { this.finalize(true); From fc956f8071e3930de432676ea2ea9cc075ee9fde Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 20 Dec 2016 12:37:05 +0100 Subject: [PATCH 225/669] [minor] Remove unnecessary `_closeReceived` property --- lib/WebSocket.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index f1f1cd548..c1061bc84 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -54,8 +54,9 @@ class WebSocket extends EventEmitter { this._finalize = this.finalize.bind(this); this._binaryType = 'nodebuffer'; - this._closeReceived = false; + this._closeMessage = null; this._closeTimer = null; + this._closeCode = null; this._receiver = null; this._sender = null; this._socket = null; @@ -145,9 +146,10 @@ class WebSocket extends EventEmitter { this.emit('ping', data, flags); }; this._receiver.onpong = (data, flags) => this.emit('pong', data, flags); - this._receiver.onclose = (code, data, flags) => { - this._closeReceived = true; - this.close(code, data); + this._receiver.onclose = (code, reason) => { + this._closeMessage = reason; + this._closeCode = code; + this.close(code, reason); }; this._receiver.onerror = (error, errorCode) => { // close the connection when the receiver reports a HyBi error code @@ -179,13 +181,13 @@ class WebSocket extends EventEmitter { clearTimeout(this._closeTimer); this._closeTimer = null; - // If the connection was closed abnormally (with an error), or if - // the close control frame was not received then the close code - // must default to 1006. - if (error || !this._closeReceived) { - this._closeCode = 1006; - } - this.emit('close', this._closeCode || 1000, this._closeMessage || ''); + // + // If the connection was closed abnormally (with an error), or if the close + // control frame was malformed or not received then the close code must be + // 1006. + // + if (error) this._closeCode = 1006; + this.emit('close', this._closeCode || 1006, this._closeMessage || ''); if (this._socket) { this._ultron.destroy(); @@ -257,19 +259,17 @@ class WebSocket extends EventEmitter { } if (this.readyState === WebSocket.CLOSING) { - if (this._closeReceived && this._isServer) { + if (this._closeCode && this._isServer) { this.terminate(); } return; } this.readyState = WebSocket.CLOSING; - this._closeCode = code; - this._closeMessage = data; this._sender.close(code, data, !this._isServer, (err) => { if (err) this.emit('error', err); - if (this._closeReceived && this._isServer) { + if (this._closeCode && this._isServer) { this.terminate(); } else { // From 097f39fdd0ea2b4029aa1df05783966aa636fe5a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 20 Dec 2016 15:08:15 +0100 Subject: [PATCH 226/669] [major] Use the same handler for binary and text messages --- lib/Receiver.js | 10 ++++------ lib/WebSocket.js | 10 +++------- test/Receiver.test.js | 34 +++++++++++++++++----------------- test/testserver.js | 9 ++------- 4 files changed, 26 insertions(+), 37 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 62d68c361..07cbb9b66 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -53,10 +53,9 @@ class Receiver { this.dead = false; - this.onbinary = noop; + this.onmessage = noop; this.onclose = noop; this.onerror = noop; - this.ontext = noop; this.onping = noop; this.onpong = noop; @@ -350,14 +349,14 @@ class Receiver { this.fragmented = 0; if (this.opcode === 2) { - this.onbinary(buf, { masked: this.masked }); + this.onmessage(buf, { masked: this.masked, binary: true }); } else { if (!Validation.isValidUTF8(buf)) { this.error(new Error('invalid utf8 sequence'), 1007); return; } - this.ontext(buf.toString(), { masked: this.masked }); + this.onmessage(buf.toString(), { masked: this.masked }); } } @@ -477,10 +476,9 @@ class Receiver { this.buffers = null; this.mask = null; - this.onbinary = null; + this.onmessage = null; this.onclose = null; this.onerror = null; - this.ontext = null; this.onping = null; this.onpong = null; } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index c1061bc84..98b5eeec9 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -136,11 +136,7 @@ class WebSocket extends EventEmitter { }); // receiver event handlers - this._receiver.ontext = (data, flags) => this.emit('message', data, flags); - this._receiver.onbinary = (data, flags) => { - flags.binary = true; - this.emit('message', data, flags); - }; + this._receiver.onmessage = (data, flags) => this.emit('message', data, flags); this._receiver.onping = (data, flags) => { this.pong(data, { mask: !this._isServer }, true); this.emit('ping', data, flags); @@ -151,9 +147,9 @@ class WebSocket extends EventEmitter { this._closeCode = code; this.close(code, reason); }; - this._receiver.onerror = (error, errorCode) => { + this._receiver.onerror = (error, code) => { // close the connection when the receiver reports a HyBi error code - this.close(errorCode, ''); + this.close(code, ''); this.emit('error', error); }; diff --git a/test/Receiver.test.js b/test/Receiver.test.js index d54cbb65d..c139b5f3f 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -17,7 +17,7 @@ describe('Receiver', function () { it('can parse unmasked text message', function (done) { const p = new Receiver(); - p.ontext = function (data) { + p.onmessage = function (data) { assert.strictEqual(data, 'Hello'); done(); }; @@ -40,7 +40,7 @@ describe('Receiver', function () { it('can parse masked text message', function (done) { const p = new Receiver(); - p.ontext = function (data) { + p.onmessage = function (data) { assert.strictEqual(data, '5:::{"name":"echo"}'); done(); }; @@ -56,7 +56,7 @@ describe('Receiver', function () { const frame = Buffer.from('81FE' + util.pack(4, msg.length) + mask + util.mask(msg, mask).toString('hex'), 'hex'); - p.ontext = function (data) { + p.onmessage = function (data) { assert.strictEqual(data, msg); done(); }; @@ -73,7 +73,7 @@ describe('Receiver', function () { const frame = '81FF' + util.pack(16, msg.length) + mask + util.mask(msg, mask).toString('hex'); - p.ontext = function (data) { + p.onmessage = function (data) { assert.strictEqual(data, msg); done(); }; @@ -94,7 +94,7 @@ describe('Receiver', function () { const frame2 = '80FE' + util.pack(4, fragment2.length) + mask + util.mask(fragment2, mask).toString('hex'); - p.ontext = function (data) { + p.onmessage = function (data) { assert.strictEqual(data, msg); done(); }; @@ -148,7 +148,7 @@ describe('Receiver', function () { let gotPing = false; - p.ontext = function (data) { + p.onmessage = function (data) { assert.strictEqual(data, msg); assert.ok(gotPing); done(); @@ -187,7 +187,7 @@ describe('Receiver', function () { let gotPing = false; - p.ontext = function (data) { + p.onmessage = function (data) { assert.strictEqual(data, msg); assert.ok(gotPing); done(); @@ -210,8 +210,8 @@ describe('Receiver', function () { const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + util.mask(msg, mask).toString('hex'); - p.onbinary = function (data) { - assert.deepStrictEqual(data.toString('hex'), msg.toString('hex')); + p.onmessage = function (data) { + assert.ok(data.equals(msg)); done(); }; @@ -226,8 +226,8 @@ describe('Receiver', function () { const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + util.mask(msg, mask).toString('hex'); - p.onbinary = function (data) { - assert.deepStrictEqual(data, msg); + p.onmessage = function (data) { + assert.ok(data.equals(msg)); done(); }; @@ -242,8 +242,8 @@ describe('Receiver', function () { const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + util.mask(msg, mask).toString('hex'); - p.onbinary = function (data) { - assert.deepStrictEqual(data, msg); + p.onmessage = function (data) { + assert.ok(data.equals(msg)); done(); }; @@ -257,8 +257,8 @@ describe('Receiver', function () { const frame = '82' + util.getHybiLengthAsHexString(msg.length, false) + msg.toString('hex'); - p.onbinary = function (data) { - assert.deepStrictEqual(data, msg); + p.onmessage = function (data) { + assert.ok(data.equals(msg)); done(); }; @@ -272,7 +272,7 @@ describe('Receiver', function () { const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); const buf = Buffer.from('Hello'); - p.ontext = function (data) { + p.onmessage = function (data) { assert.strictEqual(data, 'Hello'); done(); }; @@ -293,7 +293,7 @@ describe('Receiver', function () { const buf1 = Buffer.from('foo'); const buf2 = Buffer.from('bar'); - p.ontext = function (data) { + p.onmessage = function (data) { assert.strictEqual(data, 'foobar'); done(); }; diff --git a/test/testserver.js b/test/testserver.js index 48e1e287d..8214650e8 100644 --- a/test/testserver.js +++ b/test/testserver.js @@ -62,14 +62,9 @@ function validServer (server, req, socket) { receiver.onping = (message, flags) => server.emit('ping', message, flags); receiver.onpong = (message, flags) => server.emit('pong', message, flags); - receiver.ontext = (message, flags) => { + receiver.onmessage = (message, flags) => { server.emit('message', message, flags); - sender.send(message, { fin: true }); - }; - receiver.onbinary = (message, flags) => { - flags.binary = true; - server.emit('message', message, flags); - sender.send(message, { binary: true, fin: true }); + sender.send(message, { binary: flags.binary, fin: true }); }; receiver.onclose = (code, message, flags) => { sender.close(code, message, false, () => { From 4c3586c7f81734417a863190eea7e7c72d2e1558 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 22 Dec 2016 18:26:32 +0100 Subject: [PATCH 227/669] [fix] Prevent callback from being called twice This prevents the callback of `PerMessageDeflate.prototype.decompress()` from being called twice when `maxPayload` is exceeded. --- lib/PerMessageDeflate.js | 47 +++++++++++++++++++--------------- test/PerMessageDeflate.test.js | 31 ++++++++++++++++++++++ 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index f8400712b..d1569546f 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -215,37 +215,37 @@ class PerMessageDeflate { } /** - * Decompress message + * Decompress data. * - * @api public + * @param {Buffer} data Compressed data + * @param {Boolean} fin Specifies whether or not this is the last fragment + * @param {Function} callback Callback + * @public */ decompress (data, fin, callback) { - var endpoint = this._isServer ? 'client' : 'server'; + const endpoint = this._isServer ? 'client' : 'server'; if (!this._inflate) { - var maxWindowBits = this.params[endpoint + '_max_window_bits']; + const maxWindowBits = this.params[`${endpoint}_max_window_bits`]; this._inflate = zlib.createInflateRaw({ windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS }); } this._inflate.writeInProgress = true; + var totalLength = 0; const buffers = []; - var cumulativeBufferLength = 0; + var err; const onData = (data) => { - if (this._maxPayload > 0) { - cumulativeBufferLength += data.length; - if (cumulativeBufferLength > this._maxPayload) { - const err = new Error('max payload size exceeded'); - err.closeCode = 1009; - buffers.length = 0; - cleanup(); - callback(err); - return; - } + totalLength += data.length; + if (this._maxPayload < 1 || totalLength <= this._maxPayload) { + return buffers.push(data); } - buffers.push(data); + + err = new Error('max payload size exceeded'); + err.closeCode = 1009; + this._inflate.reset(); }; const onError = (err) => { @@ -255,10 +255,15 @@ class PerMessageDeflate { const cleanup = () => { if (!this._inflate) return; + this._inflate.removeListener('error', onError); this._inflate.removeListener('data', onData); this._inflate.writeInProgress = false; - if ((fin && this.params[endpoint + '_no_context_takeover']) || this._inflate.pendingClose) { + + if ( + fin && this.params[`${endpoint}_no_context_takeover`] || + this._inflate.pendingClose + ) { this._inflate.close(); this._inflate = null; } @@ -266,12 +271,12 @@ class PerMessageDeflate { this._inflate.on('error', onError).on('data', onData); this._inflate.write(data); - if (fin) { - this._inflate.write(TRAILER); - } + if (fin) this._inflate.write(TRAILER); + this._inflate.flush(() => { cleanup(); - callback(null, Buffer.concat(buffers)); + if (err) callback(err); + else callback(null, Buffer.concat(buffers, totalLength)); }); } diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index fb1706813..b95a162c8 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -339,5 +339,36 @@ describe('PerMessageDeflate', function () { }); }); }); + + it('should call the callback when an error occurs (inflate)', function (done) { + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const data = Buffer.from('something invalid'); + + perMessageDeflate.accept([{}]); + perMessageDeflate.decompress(data, true, (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.errno, -3); + done(); + }); + }); + + it('should not call the callback twice when `maxPayload` is exceeded', function (done) { + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }, false, 25); + const buf = Buffer.from('A'.repeat(50)); + const errors = []; + + perMessageDeflate.accept([{}]); + perMessageDeflate.compress(buf, true, (err, data) => { + if (err) return done(err); + + perMessageDeflate.decompress(data, true, (err) => errors.push(err)); + perMessageDeflate._inflate.flush(() => { + assert.strictEqual(errors.length, 1); + assert.ok(errors[0] instanceof Error); + assert.strictEqual(errors[0].message, 'max payload size exceeded'); + done(); + }); + }); + }); }); }); From 9b708e82279081082f027469be9b0befffacff14 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 22 Dec 2016 18:50:36 +0100 Subject: [PATCH 228/669] [pkg] Fix package description --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c6b592113..8c1033663 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", - "description": "simple to use, blazing fast and thoroughly tested websocket client, server and console for node.js, up-to-date against RFC-6455", + "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "version": "1.1.0", "license": "MIT", "main": "index.js", From 51993d41d4dd63cea51525d3c683fd0d7c031625 Mon Sep 17 00:00:00 2001 From: Kevin Vu Date: Thu, 22 Dec 2016 22:50:16 -0800 Subject: [PATCH 229/669] [doc] Fix typo in ws.md (#942) --- doc/ws.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index f25035623..63f786b5f 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -396,7 +396,7 @@ Send a FIN packet to the other peer. - {http.IncomingMessage} -The http GET request sent by the client. Useful for parsing authorty headers, +The http GET request sent by the client. Useful for parsing authority headers, cookie headers, and other information. This is only available for server clients. ### websocket.url From fa03221b70d5e0864e7b836ded740e950cc7786f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 23 Dec 2016 09:28:09 +0100 Subject: [PATCH 230/669] [test] Remove redundant tests --- test/PerMessageDeflate.test.js | 6 ------ test/Receiver.test.js | 11 +++-------- test/Sender.test.js | 6 ------ 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index b95a162c8..3da9de4bc 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -6,12 +6,6 @@ const PerMessageDeflate = require('../lib/PerMessageDeflate'); const Extensions = require('../lib/Extensions'); describe('PerMessageDeflate', function () { - describe('#ctor', function () { - it('throws TypeError when called without new', function () { - assert.throws(PerMessageDeflate, TypeError); - }); - }); - describe('#offer', function () { it('should create default params', function () { const perMessageDeflate = new PerMessageDeflate(); diff --git a/test/Receiver.test.js b/test/Receiver.test.js index c139b5f3f..18dc711ac 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -8,12 +8,6 @@ const Receiver = require('../lib/Receiver'); const util = require('./hybi-util'); describe('Receiver', function () { - describe('#ctor', function () { - it('throws TypeError when called without new', function () { - assert.throws(Receiver, TypeError); - }); - }); - it('can parse unmasked text message', function (done) { const p = new Receiver(); @@ -692,7 +686,7 @@ describe('Receiver', function () { it('can cleanup when consuming data', function (done) { const perMessageDeflate = new PerMessageDeflate(); - perMessageDeflate.accept([{ server_no_context_takeover: [true] }]); + perMessageDeflate.accept([{}]); const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); const buf = Buffer.from('Hello'); @@ -708,8 +702,9 @@ describe('Receiver', function () { assert.strictEqual(p.state, 5); assert.strictEqual(p.bufferedBytes, data.length); - perMessageDeflate._inflate.on('close', done); p.cleanup(); + + perMessageDeflate._inflate.flush(done); }); }); }); diff --git a/test/Sender.test.js b/test/Sender.test.js index 06840ce01..5f33cb955 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -6,12 +6,6 @@ const PerMessageDeflate = require('../lib/PerMessageDeflate'); const Sender = require('../lib/Sender'); describe('Sender', function () { - describe('#ctor', function () { - it('throws TypeError when called without new', function () { - assert.throws(Sender, TypeError); - }); - }); - describe('#frameAndSend', function () { it('does not modify a masked binary buffer', function () { const sender = new Sender({ write: () => {} }); From 549cbe11527320f123e423bb13a1b20bc1807874 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 23 Dec 2016 10:03:04 +0100 Subject: [PATCH 231/669] [test] Simplify some tests --- test/WebSocket.test.js | 73 ++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 41 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 35b004e49..24732207d 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1390,65 +1390,56 @@ describe('WebSocket', function () { describe('host and origin headers', function () { it('includes the host header with port number', function (done) { - const server = http.createServer(); + const agent = new CustomAgent(); - server.listen(++port, () => { - server.on('upgrade', (req, socket, head) => { - assert.strictEqual(req.headers.host, `localhost:${port}`); - server.close(done); - socket.destroy(); - }); + agent.addRequest = (req) => { + assert.strictEqual(req._headers.host, `localhost:${port}`); + done(); + }; - const ws = new WebSocket(`ws://localhost:${port}`); - }); + const ws = new WebSocket(`ws://localhost:${port}`, { agent }); }); it('lacks default origin header', function (done) { - const server = http.createServer(); + const agent = new CustomAgent(); - server.listen(++port, () => { - server.on('upgrade', (req, socket, head) => { - assert.strictEqual(req.headers.origin, undefined); - server.close(done); - socket.destroy(); - }); + agent.addRequest = (req) => { + assert.strictEqual(req._headers.origin, undefined); + done(); + }; - const ws = new WebSocket(`ws://localhost:${port}`); - }); + const ws = new WebSocket(`ws://localhost:${port}`, { agent }); }); it('honors origin set in options (1/2)', function (done) { - const server = http.createServer(); - - server.listen(++port, () => { - const options = { origin: 'https://example.com:8000' }; + const agent = new CustomAgent(); - server.on('upgrade', (req, socket, head) => { - assert.strictEqual(req.headers.origin, options.origin); - server.close(done); - socket.destroy(); - }); + agent.addRequest = (req) => { + assert.strictEqual(req._headers.origin, 'https://example.com:8000'); + done(); + }; - const ws = new WebSocket(`ws://localhost:${port}`, options); + const ws = new WebSocket(`ws://localhost:${port}`, { + origin: 'https://example.com:8000', + agent }); }); it('honors origin set in options (2/2)', function (done) { - const server = http.createServer(); - - server.listen(++port, () => { - const options = { - origin: 'https://example.com:8000', - protocolVersion: 8 - }; + const agent = new CustomAgent(); - server.on('upgrade', (req, socket, head) => { - assert.strictEqual(req.headers['sec-websocket-origin'], options.origin); - server.close(done); - socket.destroy(); - }); + agent.addRequest = (req) => { + assert.strictEqual( + req._headers['sec-websocket-origin'], + 'https://example.com:8000' + ); + done(); + }; - const ws = new WebSocket(`ws://localhost:${port}`, options); + const ws = new WebSocket(`ws://localhost:${port}`, { + origin: 'https://example.com:8000', + protocolVersion: 8, + agent }); }); From cb58cc97808ef90031c49031f9b2a31c9cd7213b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 1 Jan 2017 11:49:16 +0100 Subject: [PATCH 232/669] [minor] Initialize `Receiver` handlers to `null` --- lib/Receiver.js | 12 +++++------- test/Receiver.test.js | 31 +++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 07cbb9b66..389c173a6 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -20,8 +20,6 @@ const GET_MASK = 3; const GET_DATA = 4; const HANDLE_DATA = 5; -const noop = () => {}; - /** * HyBi Receiver implementation. */ @@ -53,11 +51,11 @@ class Receiver { this.dead = false; - this.onmessage = noop; - this.onclose = noop; - this.onerror = noop; - this.onping = noop; - this.onpong = noop; + this.onmessage = null; + this.onclose = null; + this.onerror = null; + this.onping = null; + this.onpong = null; this.state = START; } diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 18dc711ac..1ee811cea 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -309,39 +309,50 @@ describe('Receiver', function () { it('resets `totalPayloadLength` only on final frame (unfragmented)', function () { const p = new Receiver({}, 10); + let message; + + p.onmessage = function (msg) { + message = msg; + }; assert.strictEqual(p.totalPayloadLength, 0); p.add(Buffer.from('810548656c6c6f', 'hex')); assert.strictEqual(p.totalPayloadLength, 0); + assert.strictEqual(message, 'Hello'); }); it('resets `totalPayloadLength` only on final frame (fragmented)', function () { const p = new Receiver({}, 10); + let message; - const frame1 = '01024865'; - const frame2 = '80036c6c6f'; + p.onmessage = function (msg) { + message = msg; + }; assert.strictEqual(p.totalPayloadLength, 0); - p.add(Buffer.from(frame1, 'hex')); + p.add(Buffer.from('01024865', 'hex')); assert.strictEqual(p.totalPayloadLength, 2); - p.add(Buffer.from(frame2, 'hex')); + p.add(Buffer.from('80036c6c6f', 'hex')); assert.strictEqual(p.totalPayloadLength, 0); + assert.strictEqual(message, 'Hello'); }); it('resets `totalPayloadLength` only on final frame (fragmented + ping)', function () { const p = new Receiver({}, 10); + const data = []; - const frame1 = '01024865'; - const frame2 = '8900'; - const frame3 = '80036c6c6f'; + p.onmessage = p.onping = function (buf) { + data.push(buf.toString()); + }; assert.strictEqual(p.totalPayloadLength, 0); - p.add(Buffer.from(frame1, 'hex')); + p.add(Buffer.from('02024865', 'hex')); assert.strictEqual(p.totalPayloadLength, 2); - p.add(Buffer.from(frame2, 'hex')); + p.add(Buffer.from('8900', 'hex')); assert.strictEqual(p.totalPayloadLength, 2); - p.add(Buffer.from(frame3, 'hex')); + p.add(Buffer.from('80036c6c6f', 'hex')); assert.strictEqual(p.totalPayloadLength, 0); + assert.deepStrictEqual(data, ['', 'Hello']); }); it('raises an error when RSV1 is on and permessage-deflate is disabled', function (done) { From 2ffd470e9408d086c0e8fe5ddedc11bd19acb9ea Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 1 Jan 2017 17:30:28 +0100 Subject: [PATCH 233/669] [minor] Prevent out-of-range slices in `Receiver#readBuffer()` --- lib/Receiver.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 389c173a6..07aa05967 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -67,19 +67,17 @@ class Receiver { * @private */ readBuffer (bytes) { - var bufoff = 0; + var offset = 0; var dst; var l; - if (bytes === this.buffers[0].length) { - this.bufferedBytes -= bytes; - return this.buffers.shift(); - } + this.bufferedBytes -= bytes; + + if (bytes === this.buffers[0].length) return this.buffers.shift(); if (bytes < this.buffers[0].length) { dst = this.buffers[0].slice(0, bytes); this.buffers[0] = this.buffers[0].slice(bytes); - this.bufferedBytes -= bytes; return dst; } @@ -88,15 +86,13 @@ class Receiver { while (bytes > 0) { l = this.buffers[0].length; - if (bytes > l) { - this.buffers[0].copy(dst, bufoff); - bufoff += l; + if (bytes >= l) { + this.buffers[0].copy(dst, offset); + offset += l; this.buffers.shift(); - this.bufferedBytes -= l; } else { - this.buffers[0].copy(dst, bufoff, 0, bytes); + this.buffers[0].copy(dst, offset, 0, bytes); this.buffers[0] = this.buffers[0].slice(bytes); - this.bufferedBytes -= bytes; } bytes -= l; From 8846f14396e4cb0b52d3c4146e00478754d209f8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 1 Jan 2017 22:12:31 +0100 Subject: [PATCH 234/669] [benchmark] Fix parser benchmark --- bench/parser.benchmark.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index 63883f8ea..2924b5449 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -43,8 +43,10 @@ const binaryDataPacket2 = createBinaryPacket(65535); const binaryDataPacket3 = createBinaryPacket(200 * 1024); const binaryDataPacket4 = createBinaryPacket(1024 * 1024); -const receiver = new Receiver(); const suite = new benchmark.Suite(); +const receiver = new Receiver(); + +receiver.onmessage = receiver.onclose = receiver.onping = () => {}; suite.add('ping message', () => receiver.add(pingPacket1)); suite.add('ping with no data', () => receiver.add(pingPacket2)); From 338327c8ef834fe751609550163896517e6ebd6c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 2 Jan 2017 15:39:19 +0100 Subject: [PATCH 235/669] [major] Use a boolean instead of an options object --- doc/ws.md | 22 ++++++++++------------ lib/Sender.js | 22 +++++++++------------- lib/WebSocket.js | 32 +++++++++++++------------------- test/Sender.test.js | 9 ++++----- test/WebSocket.test.js | 8 ++++---- 5 files changed, 40 insertions(+), 53 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 63f786b5f..7839ff433 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -320,25 +320,23 @@ receives an `OpenEvent` named "open". Pause the socket. -### websocket.ping([data[, options[, dontFailWhenClosed]]]) +### websocket.ping([data[, mask[, failSilently]]]) - `data` {Any} The data to send in the ping frame. -- `options` {Object} - - `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults - to `true` when `websocket` is not a server client. -- `dontFailWhenClosed` {Boolean} Specifies whether or not to throw an error if - the connection is not open. +- `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults + to `true` when `websocket` is not a server client. +- `failSilently` {Boolean} Specifies whether or not to throw an error if the + connection is not open. Send a ping. -### websocket.pong([data[, options[, dontFailWhenClosed]]]) +### websocket.pong([data[, mask[, failSilently]]]) - `data` {Any} The data to send in the ping frame. -- `options` {Object} - - `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults - to `true` when `websocket` is not a server client. -- `dontFailWhenClosed` {Boolean} Specifies whether or not to throw an error if - the connection is not open. +- `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults + to `true` when `websocket` is not a server client. +- `failSilently` {Boolean} Specifies whether or not to throw an error if the + connection is not open. Send a pong. diff --git a/lib/Sender.js b/lib/Sender.js index eab04dbfd..c1f4eaae3 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -12,8 +12,6 @@ const PerMessageDeflate = require('./PerMessageDeflate'); const bufferUtil = require('./BufferUtil').BufferUtil; const ErrorCodes = require('./ErrorCodes'); -const noop = () => {}; - /** * HyBi Sender implementation. */ @@ -30,7 +28,7 @@ class Sender { this.processing = false; this.compress = false; this._socket = socket; - this.onerror = noop; + this.onerror = null; this.queue = []; } @@ -84,15 +82,14 @@ class Sender { * Sends a ping message to the other peer. * * @param {*} data The message to send - * @param {Object} options Options object - * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @param {Boolean} mask Specifies whether or not to mask `data` * @public */ - ping (data, options) { + ping (data, mask) { if (this.perMessageDeflate) { - this.enqueue([this.doPing, data, options.mask]); + this.enqueue([this.doPing, data, mask]); } else { - this.doPing(data, options.mask); + this.doPing(data, mask); } } @@ -119,15 +116,14 @@ class Sender { * Sends a pong message to the other peer. * * @param {*} data The message to send - * @param {Object} options Options object - * @param {Boolean} options.mask Specifies whether or not to mask `data` + * @param {Boolean} mask Specifies whether or not to mask `data` * @public */ - pong (data, options) { + pong (data, mask) { if (this.perMessageDeflate) { - this.enqueue([this.doPong, data, options.mask]); + this.enqueue([this.doPong, data, mask]); } else { - this.doPong(data, options.mask); + this.doPong(data, mask); } } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 98b5eeec9..8565b39c3 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -138,7 +138,7 @@ class WebSocket extends EventEmitter { // receiver event handlers this._receiver.onmessage = (data, flags) => this.emit('message', data, flags); this._receiver.onping = (data, flags) => { - this.pong(data, { mask: !this._isServer }, true); + this.pong(data, !this._isServer, true); this.emit('ping', data, flags); }; this._receiver.onpong = (data, flags) => this.emit('pong', data, flags); @@ -282,42 +282,36 @@ class WebSocket extends EventEmitter { * Send a ping message. * * @param {*} data The message to send - * @param {Object} options Options object - * @param {Boolean} options.mask Indicates whether or not to mask `data` - * @param {Boolean} dontFailWhenClosed Indicates whether or not to throw an if the connection isn't open + * @param {Boolean} mask Indicates whether or not to mask `data` + * @param {Boolean} failSilently Indicates whether or not to throw if `readyState` isn't `OPEN` * @public */ - ping (data, options, dontFailWhenClosed) { + ping (data, mask, failSilently) { if (this.readyState !== WebSocket.OPEN) { - if (dontFailWhenClosed) return; + if (failSilently) return; throw new Error('not opened'); } - options = options || {}; - if (options.mask === undefined) options.mask = !this._isServer; - - this._sender.ping(data, options); + if (mask === undefined) mask = !this._isServer; + this._sender.ping(data, mask); } /** * Send a pong message. * * @param {*} data The message to send - * @param {Object} options Options object - * @param {Boolean} options.mask Indicates whether or not to mask `data` - * @param {Boolean} dontFailWhenClosed Indicates whether or not to throw an if the connection isn't open + * @param {Boolean} mask Indicates whether or not to mask `data` + * @param {Boolean} failSilently Indicates whether or not to throw if `readyState` isn't `OPEN` * @public */ - pong (data, options, dontFailWhenClosed) { + pong (data, mask, failSilently) { if (this.readyState !== WebSocket.OPEN) { - if (dontFailWhenClosed) return; + if (failSilently) return; throw new Error('not opened'); } - options = options || {}; - if (options.mask === undefined) options.mask = !this._isServer; - - this._sender.pong(data, options); + if (mask === undefined) mask = !this._isServer; + this._sender.pong(data, mask); } /** diff --git a/test/Sender.test.js b/test/Sender.test.js index 5f33cb955..b0ce4598f 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -70,12 +70,11 @@ describe('Sender', function () { }); const array = new Uint8Array([0x68, 0x69]); - const options = { mask: false }; - sender.ping(array.buffer, options); - sender.ping(array, options); - sender.ping('hi', options); - sender.ping(10, options); + sender.ping(array.buffer, false); + sender.ping(array, false); + sender.ping('hi', false); + sender.ping(10, false); }); }); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 24732207d..751d1c5b2 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -491,7 +491,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', () => {}); - ws.ping('', {}, true); + ws.ping('', true, true); srv.close(done); ws.terminate(); @@ -550,7 +550,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.ping('hi', { mask: true })); + ws.on('open', () => ws.ping('hi', true)); }); }); }); @@ -576,7 +576,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('error', () => {}); - ws.pong('', {}, true); + ws.pong('', true, true); srv.close(done); ws.terminate(); @@ -621,7 +621,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.pong('hi', { mask: true })); + ws.on('open', () => ws.pong('hi', true)); }); }); }); From 623872f13f6ed5aa14dcb2c0ee54fc427e80740a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 3 Jan 2017 08:24:07 +0100 Subject: [PATCH 236/669] [minor] Simplify closing handshake (#914) Fixes #784 --- lib/WebSocket.js | 6 ++---- test/WebSocket.test.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 98b5eeec9..ecede686e 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -255,9 +255,7 @@ class WebSocket extends EventEmitter { } if (this.readyState === WebSocket.CLOSING) { - if (this._closeCode && this._isServer) { - this.terminate(); - } + if (this._closeCode) this.terminate(); return; } @@ -265,7 +263,7 @@ class WebSocket extends EventEmitter { this._sender.close(code, data, !this._isServer, (err) => { if (err) this.emit('error', err); - if (this._closeCode && this._isServer) { + if (this._closeCode) { this.terminate(); } else { // diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 24732207d..cc7004316 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1010,6 +1010,39 @@ describe('WebSocket', function () { wss.on('connection', (ws) => ws.close(1013)); }); + + it('closes the connection when an error occurs', function (done) { + const server = http.createServer(); + const wss = new WebSocketServer({ server }); + let closed = false; + + wss.on('connection', (ws) => { + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'RSV2 and RSV3 must be clear'); + + ws.on('close', (code, reason) => { + assert.strictEqual(code, 1006); + assert.strictEqual(reason, ''); + + closed = true; + }); + }); + }); + + server.listen(++port, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws._socket.write(Buffer.from([0xa1, 0x00]))); + ws.on('close', (code, reason) => { + assert.strictEqual(code, 1002); + assert.strictEqual(reason, ''); + assert.ok(closed); + + server.close(done); + }); + }); + }); }); describe('WHATWG API emulation', function () { From 44f9e89a501026b0459bad725216dc9500355d63 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 3 Jan 2017 08:27:42 +0100 Subject: [PATCH 237/669] [fix] Consume all data before emitting the `close` event (#945) Fixes #799 Fixes #923 --- lib/Receiver.js | 66 +++++++++++++------ lib/WebSocket.js | 26 ++++++-- test/Receiver.test.js | 142 +++++++++++++++++++++++++++++++++-------- test/WebSocket.test.js | 62 ++++++++++++------ 4 files changed, 224 insertions(+), 72 deletions(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 07aa05967..b79b19370 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -49,6 +49,8 @@ class Receiver { this.messageLength = 0; this.fragments = []; + this.cleanupCallback = null; + this.hadError = false; this.dead = false; this.onmessage = null; @@ -101,6 +103,21 @@ class Receiver { return dst; } + /** + * Checks if the number of buffered bytes is bigger or equal than `n` and + * calls `cleanup` if necessary. + * + * @param {Number} n The number of bytes to check against + * @return {Boolean} `true` if `bufferedBytes >= n`, else `false` + * @private + */ + hasBufferedBytes (n) { + if (this.bufferedBytes >= n) return true; + + if (this.dead) this.cleanup(this.cleanupCallback); + return false; + } + /** * Adds new data to the parser. * @@ -136,7 +153,7 @@ class Receiver { * @private */ start () { - if (this.bufferedBytes < 2) return; + if (!this.hasBufferedBytes(2)) return; const buf = this.readBuffer(2); @@ -216,7 +233,7 @@ class Receiver { * @private */ getPayloadLength16 () { - if (this.bufferedBytes < 2) return; + if (!this.hasBufferedBytes(2)) return; this.payloadLength = this.readBuffer(2).readUInt16BE(0, true); this.haveLength(); @@ -228,7 +245,7 @@ class Receiver { * @private */ getPayloadLength64 () { - if (this.bufferedBytes < 8) return; + if (!this.hasBufferedBytes(8)) return; const buf = this.readBuffer(8); const num = buf.readUInt32BE(0, true); @@ -271,7 +288,7 @@ class Receiver { * @private */ getMask () { - if (this.bufferedBytes < 4) return; + if (!this.hasBufferedBytes(4)) return; this.mask = this.readBuffer(4); this.state = GET_DATA; @@ -287,7 +304,7 @@ class Receiver { var data = EMPTY_BUFFER; if (this.payloadLength) { - if (this.bufferedBytes < this.payloadLength) return; + if (!this.hasBufferedBytes(this.payloadLength)) return; data = this.readBuffer(this.payloadLength); if (this.masked) bufferUtil.unmask(data, this.mask); @@ -313,8 +330,6 @@ class Receiver { const extension = this.extensions[PerMessageDeflate.extensionName]; extension.decompress(data, this.fin, (err, buf) => { - if (this.dead) return; - if (err) { this.error(err, err.closeCode === 1009 ? 1009 : 1007); return; @@ -368,7 +383,7 @@ class Receiver { if (this.opcode === 0x08) { if (data.length === 0) { this.onclose(1000, '', { masked: this.masked }); - this.cleanup(); + this.cleanup(this.cleanupCallback); } else if (data.length === 1) { this.error(new Error('invalid payload length'), 1002); } else { @@ -387,7 +402,7 @@ class Receiver { } this.onclose(code, buf.toString(), { masked: this.masked }); - this.cleanup(); + this.cleanup(this.cleanupCallback); } return; @@ -411,7 +426,8 @@ class Receiver { */ error (err, code) { this.onerror(err, code); - this.cleanup(); + this.hadError = true; + this.cleanup(this.cleanupCallback); } /** @@ -460,21 +476,29 @@ class Receiver { /** * Releases resources used by the receiver. * + * @param {Function} cb Callback * @public */ - cleanup () { + cleanup (cb) { this.dead = true; - this.extensions = null; - this.fragments = null; - this.buffers = null; - this.mask = null; - - this.onmessage = null; - this.onclose = null; - this.onerror = null; - this.onping = null; - this.onpong = null; + if (!this.hadError && this.state === HANDLE_DATA) { + this.cleanupCallback = cb; + } else { + this.extensions = null; + this.fragments = null; + this.buffers = null; + this.mask = null; + + this.cleanupCallback = null; + this.onmessage = null; + this.onclose = null; + this.onerror = null; + this.onping = null; + this.onpong = null; + + if (cb) cb(); + } } } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index ecede686e..b2cc91966 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -22,6 +22,7 @@ const Sender = require('./Sender'); const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly. const protocolVersion = 13; +const noop = () => {}; /** * Class representing a WebSocket. @@ -54,6 +55,7 @@ class WebSocket extends EventEmitter { this._finalize = this.finalize.bind(this); this._binaryType = 'nodebuffer'; + this._finalizeCalled = false; this._closeMessage = null; this._closeTimer = null; this._closeCode = null; @@ -164,15 +166,16 @@ class WebSocket extends EventEmitter { } /** - * Clean up and release internal resources and emit the `close` event. + * Clean up and release internal resources. * * @param {(Boolean|Error)} Indicates whether or not an error occurred * @private */ finalize (error) { - if (this.readyState === WebSocket.CLOSED) return; + if (this._finalizeCalled) return; - this.readyState = WebSocket.CLOSED; + this.readyState = WebSocket.CLOSING; + this._finalizeCalled = true; clearTimeout(this._closeTimer); this._closeTimer = null; @@ -183,7 +186,6 @@ class WebSocket extends EventEmitter { // 1006. // if (error) this._closeCode = 1006; - this.emit('close', this._closeCode || 1006, this._closeMessage || ''); if (this._socket) { this._ultron.destroy(); @@ -203,9 +205,21 @@ class WebSocket extends EventEmitter { } if (this._receiver) { - this._receiver.cleanup(); + this._receiver.cleanup(() => this.emitClose()); this._receiver = null; + } else { + this.emitClose(); } + } + + /** + * Emit the `close` event. + * + * @private + */ + emitClose () { + this.readyState = WebSocket.CLOSED; + this.emit('close', this._closeCode || 1006, this._closeMessage || ''); if (this.extensions[PerMessageDeflate.extensionName]) { this.extensions[PerMessageDeflate.extensionName].cleanup(); @@ -214,7 +228,7 @@ class WebSocket extends EventEmitter { this.extensions = null; this.removeAllListeners(); - this.on('error', function onerror () {}); // catch all errors after this + this.on('error', noop); // Catch all errors after this. } /** diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 1ee811cea..7129228be 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -549,7 +549,7 @@ describe('Receiver', function () { p.add(Buffer.from([0x88, 0x01, 0x00])); }); - it('raises an error if a close frame contains a invalid close code', function (done) { + it('raises an error if a close frame contains an invalid close code', function (done) { const p = new Receiver(); p.error = function (err, code) { @@ -593,7 +593,7 @@ describe('Receiver', function () { p.add(Buffer.from(frame, 'hex')); }); - it('raises an error on a 200 KiB long unmasked binary message when maxpayload is 20 KiB', function (done) { + it('raises an error on a 200 KiB long unmasked binary message when `maxPayload` is 20 KiB', function (done) { const p = new Receiver({}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); @@ -661,13 +661,9 @@ describe('Receiver', function () { }); }); - it('will not crash if another message is received after receiving a message that exceeds maxpayload', function (done) { - const perMessageDeflate = new PerMessageDeflate({}, false, 2); - perMessageDeflate.accept([{}]); - - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 2); - const buf1 = Buffer.from('foooooooooooooooooooooooooooooooooooooooooooooo'); - const buf2 = Buffer.from('baaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr'); + it('doesn\'t crash if data is received after `maxPayload` is exceeded', function (done) { + const p = new Receiver({}, 5); + const buf = crypto.randomBytes(10); let gotError = false; @@ -676,46 +672,140 @@ describe('Receiver', function () { assert.strictEqual(code, 1009); }; - perMessageDeflate.compress(buf1, false, function (err, compressed1) { + p.add(Buffer.from([0x82, buf.length])); + + assert.ok(gotError); + assert.strictEqual(p.onerror, null); + + p.add(buf); + done(); + }); + + it('consumes all data before calling `cleanup` callback (1/4)', function (done) { + const perMessageDeflate = new PerMessageDeflate(); + perMessageDeflate.accept([{}]); + + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const buf = Buffer.from('Hello'); + const results = []; + + p.onmessage = (message) => results.push(message); + + perMessageDeflate.compress(buf, true, (err, data) => { if (err) return done(err); - p.add(Buffer.from([0x41, compressed1.length])); - p.add(compressed1); + const frame = Buffer.concat([Buffer.from([0xc1, data.length]), data]); - assert.ok(gotError); - assert.strictEqual(p.onerror, null); + p.add(frame); + p.add(frame); - perMessageDeflate.compress(buf2, true, function (err, compressed2) { - if (err) return done(err); + assert.strictEqual(p.state, 5); + assert.strictEqual(p.bufferedBytes, frame.length); - p.add(Buffer.from([0x80, compressed2.length])); - p.add(compressed2); + p.cleanup(() => { + assert.deepStrictEqual(results, ['Hello', 'Hello']); + assert.strictEqual(p.onmessage, null); done(); }); }); }); - it('can cleanup when consuming data', function (done) { + it('consumes all data before calling `cleanup` callback (2/4)', function (done) { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); const buf = Buffer.from('Hello'); + const results = []; - perMessageDeflate.compress(buf, true, function (err, compressed) { + p.onclose = (code, reason) => results.push(code, reason); + p.onmessage = (message) => results.push(message); + + perMessageDeflate.compress(buf, true, (err, data) => { if (err) return done(err); - const data = Buffer.concat([Buffer.from([0xc1, compressed.length]), compressed]); + const textFrame = Buffer.concat([Buffer.from([0xc1, data.length]), data]); + const closeFrame = Buffer.from([0x88, 0x00]); - p.add(data); - p.add(data); + p.add(textFrame); + p.add(textFrame); + p.add(closeFrame); assert.strictEqual(p.state, 5); - assert.strictEqual(p.bufferedBytes, data.length); + assert.strictEqual(p.bufferedBytes, textFrame.length + closeFrame.length); - p.cleanup(); + p.cleanup(() => { + assert.deepStrictEqual(results, ['Hello', 'Hello', 1000, '']); + assert.strictEqual(p.onmessage, null); + done(); + }); + }); + }); - perMessageDeflate._inflate.flush(done); + it('consumes all data before calling `cleanup` callback (3/4)', function (done) { + const perMessageDeflate = new PerMessageDeflate(); + perMessageDeflate.accept([{}]); + + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const buf = Buffer.from('Hello'); + const results = []; + + p.onerror = (err, code) => results.push(err.message, code); + p.onmessage = (message) => results.push(message); + + perMessageDeflate.compress(buf, true, (err, data) => { + if (err) return done(err); + + const textFrame = Buffer.concat([Buffer.from([0xc1, data.length]), data]); + const invalidFrame = Buffer.from([0xa0, 0x00]); + + p.add(textFrame); + p.add(textFrame); + p.add(invalidFrame); + + assert.strictEqual(p.state, 5); + assert.strictEqual(p.bufferedBytes, textFrame.length + invalidFrame.length); + + p.cleanup(() => { + assert.deepStrictEqual(results, [ + 'Hello', + 'Hello', + 'RSV2 and RSV3 must be clear', + 1002 + ]); + assert.strictEqual(p.onmessage, null); + done(); + }); + }); + }); + + it('consumes all data before calling `cleanup` callback (4/4)', function (done) { + const perMessageDeflate = new PerMessageDeflate(); + perMessageDeflate.accept([{}]); + + const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const buf = Buffer.from('Hello'); + const results = []; + + p.onmessage = (message) => results.push(message); + + perMessageDeflate.compress(buf, true, (err, data) => { + if (err) return done(err); + + const textFrame = Buffer.concat([Buffer.from([0xc1, data.length]), data]); + const incompleteFrame = Buffer.from([0x82, 0x0a, 0x00, 0x00]); + + p.add(textFrame); + p.add(incompleteFrame); + + assert.strictEqual(p.state, 5); + assert.strictEqual(p.bufferedBytes, incompleteFrame.length); + + p.cleanup(() => { + assert.deepStrictEqual(results, ['Hello']); + assert.strictEqual(p.onmessage, null); + done(); + }); }); }); }); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index cc7004316..7d938880a 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -945,7 +945,7 @@ describe('WebSocket', function () { server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.close(1000, 'some reason', { mask: true })); + ws.on('open', () => ws.close(1000, 'some reason')); srv.on('close', (code, message, flags) => { assert.ok(flags.masked); @@ -963,7 +963,7 @@ describe('WebSocket', function () { ws.on('open', () => { connectedOnce = true; - ws.close(1000, 'some reason', {mask: true}); + ws.close(1000, 'some reason'); }); ws.on('close', () => { @@ -974,28 +974,28 @@ describe('WebSocket', function () { }); }); - it('consumes all data when the server socket closed', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - wss.on('connection', (conn) => { - conn.send('foo'); - conn.send('bar'); - conn.send('baz'); - conn.close(); - }); - + it('permits all buffered data to be delivered', function (done) { + const wss = new WebSocketServer({ + perMessageDeflate: { threshold: 0 }, + port: ++port + }, () => { const ws = new WebSocket(`ws://localhost:${port}`); const messages = []; - ws.on('message', (message) => { - messages.push(message); - if (messages.length === 3) { - assert.deepStrictEqual(messages, ['foo', 'bar', 'baz']); - - wss.close(done); - ws.terminate(); - } + ws.on('message', (message) => messages.push(message)); + ws.on('close', (code) => { + assert.strictEqual(code, 1000); + assert.deepStrictEqual(messages, ['foo', 'bar', 'baz']); + wss.close(done); }); }); + + wss.on('connection', (ws) => { + ws.send('foo'); + ws.send('bar'); + ws.send('baz'); + ws.close(); + }); }); it('allows close code 1013', function (done) { @@ -1646,6 +1646,30 @@ describe('WebSocket', function () { }); }); + it('consumes all received data when connection is closed abnormally', function (done) { + const wss = new WebSocketServer({ + perMessageDeflate: { threshold: 0 }, + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + const messages = []; + + ws.on('message', (message) => messages.push(message)); + ws.on('close', (code) => { + assert.strictEqual(code, 1006); + assert.deepStrictEqual(messages, ['foo', 'bar', 'baz', 'qux']); + wss.close(done); + }); + }); + + wss.on('connection', (ws) => { + ws.send('foo'); + ws.send('bar'); + ws.send('baz'); + ws.send('qux', () => ws._socket.end()); + }); + }); + describe('#send', function () { it('can set the compress option true when perMessageDeflate is disabled', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { From aabbffc956a6360cf740cdd7a049235be2392260 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 3 Jan 2017 08:42:55 +0100 Subject: [PATCH 238/669] [test] Increase test timeout --- test/WebSocket.test.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 7d938880a..be523dc03 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -254,10 +254,6 @@ describe('WebSocket', function () { }); }); - /* - * Ready state constants - */ - const readyStates = { CONNECTING: 0, OPEN: 1, @@ -265,10 +261,6 @@ describe('WebSocket', function () { CLOSED: 3 }; - /* - * Ready state constant tests - */ - Object.keys(readyStates).forEach((state) => { describe(`.${state}`, function () { it('is enumerable property of class', function () { @@ -1393,6 +1385,8 @@ describe('WebSocket', function () { }); it('can send and receive very long binary data', function (done) { + this.timeout(4000); + const buf = crypto.randomBytes(5 * 1024 * 1024); const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), From 45d5c389ca54fa8fa111dc103540680fb532c051 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 3 Jan 2017 15:14:48 +0100 Subject: [PATCH 239/669] [fix] Treat 0 like any other number --- lib/Sender.js | 4 ++-- lib/WebSocket.js | 12 +++++------- test/Sender.test.js | 12 +++--------- test/WebSocket.test.js | 43 +++++++++++++++++++++++++++++++++++------- 4 files changed, 46 insertions(+), 25 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index c1f4eaae3..74f6e85d1 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -169,7 +169,7 @@ class Sender { } else if (ArrayBuffer.isView(data)) { data = viewToBuffer(data); } else { - data = Buffer.from(typeof data === 'number' ? data.toString() : data); + data = Buffer.from(data); readOnly = false; } } @@ -273,7 +273,7 @@ class Sender { } else if (ArrayBuffer.isView(data)) { data = viewToBuffer(data); } else { - data = Buffer.from(typeof data === 'number' ? data.toString() : data); + data = Buffer.from(data); options.readOnly = false; } } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 4a773ac13..4baf5a8eb 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -304,6 +304,7 @@ class WebSocket extends EventEmitter { throw new Error('not opened'); } + if (typeof data === 'number') data = data.toString(); if (mask === undefined) mask = !this._isServer; this._sender.ping(data, mask); } @@ -322,6 +323,7 @@ class WebSocket extends EventEmitter { throw new Error('not opened'); } + if (typeof data === 'number') data = data.toString(); if (mask === undefined) mask = !this._isServer; this._sender.pong(data, mask); } @@ -350,16 +352,12 @@ class WebSocket extends EventEmitter { return; } - if (!data) data = ''; + if (typeof data === 'number') data = data.toString(); + else if (!data) data = ''; options = options || {}; if (options.fin !== false) options.fin = true; - - if (options.binary === undefined) { - options.binary = data instanceof Buffer || data instanceof ArrayBuffer || - ArrayBuffer.isView(data); - } - + if (options.binary === undefined) options.binary = typeof data !== 'string'; if (options.mask === undefined) options.mask = !this._isServer; if (options.compress === undefined) options.compress = true; if (!this.extensions[PerMessageDeflate.extensionName]) { diff --git a/test/Sender.test.js b/test/Sender.test.js index b0ce4598f..48c126963 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -60,12 +60,8 @@ describe('Sender', function () { let count = 0; const sender = new Sender({ write: (data) => { - if (++count < 4) { - assert.ok(data.equals(Buffer.from([0x89, 0x02, 0x68, 0x69]))); - } else { - assert.ok(data.equals(Buffer.from([0x89, 0x02, 0x31, 0x30]))); - done(); - } + assert.ok(data.equals(Buffer.from([0x89, 0x02, 0x68, 0x69]))); + if (++count === 3) done(); } }); @@ -74,7 +70,6 @@ describe('Sender', function () { sender.ping(array.buffer, false); sender.ping(array, false); sender.ping('hi', false); - sender.ping(10, false); }); }); @@ -85,7 +80,7 @@ describe('Sender', function () { const sender = new Sender({ write: (data) => { assert.strictEqual(data[0] & 0x40, 0x40); - if (++count === 4) done(); + if (++count === 3) done(); } }, { 'permessage-deflate': perMessageDeflate @@ -99,7 +94,6 @@ describe('Sender', function () { sender.send(array.buffer, options); sender.send(array, options); sender.send('hi', options); - sender.send(100, options); }); it('does not compress data for small payloads', function (done) { diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 85d445c66..318feed30 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -517,17 +517,17 @@ describe('WebSocket', function () { }); }); - it('can send safely receive numbers as ping payload', function (done) { + it('can send numbers as ping payload', function (done) { server.createServer(++port, (srv) => { srv.on('ping', (message) => { - assert.strictEqual(message.toString(), '200'); + assert.strictEqual(message.toString(), '0'); srv.close(done); ws.terminate(); }); const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.ping(200)); + ws.on('open', () => ws.ping(0)); }); }); @@ -602,6 +602,21 @@ describe('WebSocket', function () { }); }); + it('can send numbers as pong payload', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.pong(0)); + }); + + wss.on('connection', (ws) => { + ws.on('pong', (message) => { + assert.strictEqual(message.toString(), '0'); + wss.close(done); + }); + }); + }); + it('with encoded message is successfully transmitted to the server', function (done) { server.createServer(++port, (srv) => { srv.on('pong', (message, flags) => { @@ -670,7 +685,22 @@ describe('WebSocket', function () { }); }); - it('send and receive binary data as an array', function (done) { + it('sends numbers as strings', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.send(0)); + }); + + wss.on('connection', (ws) => { + ws.on('message', (msg) => { + assert.strictEqual(msg, '0'); + wss.close(done); + }); + }); + }); + + it('can send binary data as an array', function (done) { server.createServer(++port, (srv) => { const array = new Float32Array(6); @@ -688,14 +718,13 @@ describe('WebSocket', function () { ws.on('message', (message, flags) => { assert.ok(flags.binary); assert.ok(message.equals(buf)); + srv.close(done); ws.terminate(); - srv.close(); - done(); }); }); }); - it('binary data can be sent and received as buffer', function (done) { + it('can send binary data as a buffer', function (done) { server.createServer(++port, (srv) => { const buf = Buffer.from('foobar'); const ws = new WebSocket(`ws://localhost:${port}`); From 55843a9c0ad68658d120de2f2dbf38e478bca601 Mon Sep 17 00:00:00 2001 From: Danny Ho Date: Wed, 4 Jan 2017 03:36:29 -0800 Subject: [PATCH 240/669] [example] Update fileapi example to work with Express 4 (#953) --- examples/fileapi/package.json | 2 +- examples/fileapi/server.js | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/fileapi/package.json b/examples/fileapi/package.json index 7816f2737..1a374d4ac 100644 --- a/examples/fileapi/package.json +++ b/examples/fileapi/package.json @@ -10,7 +10,7 @@ "node": "~0.6.8" }, "dependencies": { - "express": "latest", + "express": "~4.0.0", "ansi": "https://github.com/einaros/ansi.js/tarball/master" }, "devDependencies": {}, diff --git a/examples/fileapi/server.js b/examples/fileapi/server.js index e7d80f009..380ce152d 100644 --- a/examples/fileapi/server.js +++ b/examples/fileapi/server.js @@ -3,7 +3,8 @@ var express = require('express'); var fs = require('fs'); var util = require('util'); var path = require('path'); -var app = express.createServer(); +var app = express(); +var server = require('http').Server(app); var events = require('events'); var ansi = require('ansi'); var cursor = ansi(process.stdout); @@ -45,7 +46,7 @@ cursor.eraseData(2).goto(1, 1); app.use(express.static(path.join(__dirname, '/public'))); var clientId = 0; -var wss = new WebSocketServer({server: app}); +var wss = new WebSocketServer({server: server}); wss.on('connection', function (ws) { var thisId = ++clientId; cursor.goto(1, 4 + thisId).eraseLine(); @@ -101,6 +102,7 @@ fs.mkdir(path.join(__dirname, '/uploaded'), function () { // ignore errors, most likely means directory exists console.log('Uploaded files will be saved to %s/uploaded.', __dirname); console.log('Remember to wipe this directory if you upload lots and lots.'); - app.listen(8080); - console.log('Listening on http://localhost:8080'); + server.listen(8080, function () { + console.log('Listening on http://localhost:8080'); + }); }); From 323db27415a3d35ee168756118e86fa10a985bcf Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 4 Jan 2017 14:59:51 +0100 Subject: [PATCH 241/669] [example] Update express to version 4.14.0 --- examples/fileapi/package.json | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/examples/fileapi/package.json b/examples/fileapi/package.json index 1a374d4ac..1341f59e9 100644 --- a/examples/fileapi/package.json +++ b/examples/fileapi/package.json @@ -2,17 +2,9 @@ "author": "", "name": "fileapi", "version": "0.0.0", - "repository": { - "type": "git", - "url": "git://github.com/einaros/ws.git" - }, - "engines": { - "node": "~0.6.8" - }, + "repository": "websockets/ws", "dependencies": { - "express": "~4.0.0", + "express": "~4.14.0", "ansi": "https://github.com/einaros/ansi.js/tarball/master" - }, - "devDependencies": {}, - "optionalDependencies": {} + } } From 8aa65e2f4da33083d1c20e46240aacc4fe40822c Mon Sep 17 00:00:00 2001 From: Danny Ho Date: Wed, 4 Jan 2017 22:31:41 -0800 Subject: [PATCH 242/669] [example] Update serverstats example to work with Express 4 (#954) --- examples/serverstats-express_3/package.json | 17 ---------- .../serverstats-express_3/public/index.html | 33 ------------------- examples/serverstats-express_3/server.js | 22 ------------- examples/serverstats/package.json | 14 ++------ examples/serverstats/server.js | 11 +++++-- 5 files changed, 11 insertions(+), 86 deletions(-) delete mode 100644 examples/serverstats-express_3/package.json delete mode 100644 examples/serverstats-express_3/public/index.html delete mode 100644 examples/serverstats-express_3/server.js diff --git a/examples/serverstats-express_3/package.json b/examples/serverstats-express_3/package.json deleted file mode 100644 index 99722c422..000000000 --- a/examples/serverstats-express_3/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "author": "", - "name": "serverstats", - "version": "0.0.0", - "repository": { - "type": "git", - "url": "git://github.com/einaros/ws.git" - }, - "engines": { - "node": ">0.4.0" - }, - "dependencies": { - "express": "~3.0.0" - }, - "devDependencies": {}, - "optionalDependencies": {} -} diff --git a/examples/serverstats-express_3/public/index.html b/examples/serverstats-express_3/public/index.html deleted file mode 100644 index 24d84e120..000000000 --- a/examples/serverstats-express_3/public/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - Server Stats
- RSS:

- Heap total:

- Heap used:

- - diff --git a/examples/serverstats-express_3/server.js b/examples/serverstats-express_3/server.js deleted file mode 100644 index 16e584f95..000000000 --- a/examples/serverstats-express_3/server.js +++ /dev/null @@ -1,22 +0,0 @@ -var WebSocketServer = require('../../').Server; -var http = require('http'); -var express = require('express'); -var path = require('path'); -var app = express(); - -app.use(express.static(path.join(__dirname, '/public'))); - -var server = http.createServer(app); -server.listen(8080); - -var wss = new WebSocketServer({server: server}); -wss.on('connection', function (ws) { - var id = setInterval(function () { - ws.send(JSON.stringify(process.memoryUsage()), function () { /* ignore errors */ }); - }, 100); - console.log('started client interval'); - ws.on('close', function () { - console.log('stopping client interval'); - clearInterval(id); - }); -}); diff --git a/examples/serverstats/package.json b/examples/serverstats/package.json index 65c900ab1..321049ab5 100644 --- a/examples/serverstats/package.json +++ b/examples/serverstats/package.json @@ -2,16 +2,8 @@ "author": "", "name": "serverstats", "version": "0.0.0", - "repository": { - "type": "git", - "url": "git://github.com/einaros/ws.git" - }, - "engines": { - "node": ">0.4.0" - }, + "repository": "websockets/ws", "dependencies": { - "express": "2.x" - }, - "devDependencies": {}, - "optionalDependencies": {} + "express": "~4.14.0" + } } diff --git a/examples/serverstats/server.js b/examples/serverstats/server.js index 73e4fcf8f..b1e167806 100644 --- a/examples/serverstats/server.js +++ b/examples/serverstats/server.js @@ -1,12 +1,12 @@ var WebSocketServer = require('../../').Server; var express = require('express'); var path = require('path'); -var app = express.createServer(); +var app = express(); +var server = require('http').createServer(); app.use(express.static(path.join(__dirname, '/public'))); -app.listen(8080); -var wss = new WebSocketServer({server: app}); +var wss = new WebSocketServer({server: server}); wss.on('connection', function (ws) { var id = setInterval(function () { ws.send(JSON.stringify(process.memoryUsage()), function () { /* ignore errors */ }); @@ -17,3 +17,8 @@ wss.on('connection', function (ws) { clearInterval(id); }); }); + +server.on('request', app); +server.listen(8080, function () { + console.log('Listening on http://localhost:8080'); +}); From 758e6a06f0ef8ed092b699fcfc1d0f52067ed425 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 6 Jan 2017 22:12:54 +0100 Subject: [PATCH 243/669] chore(package): update eslint to version 3.13.0 (#957) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8c1033663..e8a12c227 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~1.3.0", - "eslint": "~3.12.0", + "eslint": "~3.13.0", "eslint-config-semistandard": "~7.0.0", "eslint-config-standard": "~6.2.1", "eslint-plugin-promise": "~3.4.0", From c843e6ac9db14bec7c6271d6faa246ac9015d5b9 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Mon, 9 Jan 2017 17:53:37 +0800 Subject: [PATCH 244/669] [doc] Update coding style for README.md (#960) --- README.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 53a1a2526..2076c3b63 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,8 @@ compiler is installed on the host system. ### Sending and receiving text data ```js -var WebSocket = require('ws'); -var ws = new WebSocket('ws://www.host.com/path'); +const WebSocket = require('ws'); +const ws = new WebSocket('ws://www.host.com/path'); ws.on('open', function open() { ws.send('something'); @@ -58,8 +58,8 @@ ws.on('message', function(data, flags) { ### Sending binary data ```js -var WebSocket = require('ws'); -var ws = new WebSocket('ws://www.host.com/path'); +const WebSocket = require('ws'); +const ws = new WebSocket('ws://www.host.com/path'); ws.on('open', function open() { var array = new Float32Array(5); @@ -79,8 +79,8 @@ data. ### Server example ```js -var WebSocketServer = require('ws').Server - , wss = new WebSocketServer({ port: 8080 }); +const WebSocketServer = require('ws').Server; +const wss = new WebSocketServer({ port: 8080 }); wss.on('connection', function connection(ws) { ws.on('message', function incoming(message) { @@ -94,13 +94,13 @@ wss.on('connection', function connection(ws) { ### ExpressJS example ```js -var server = require('http').createServer() - , url = require('url') - , WebSocketServer = require('ws').Server - , wss = new WebSocketServer({ server: server }) - , express = require('express') - , app = express() - , port = 4080; +const server = require('http').createServer(); +const url = require('url'); +const WebSocketServer = require('ws').Server; +const wss = new WebSocketServer({ server: server }); +const express = require('express'); +const app = express(); +const port = 4080; app.use(function (req, res) { res.send({ msg: "hello" }); @@ -125,8 +125,8 @@ server.listen(port, function () { console.log('Listening on ' + server.address() ### Server sending broadcast data ```js -var WebSocketServer = require('ws').Server - , wss = new WebSocketServer({ port: 8080 }); +const WebSocketServer = require('ws').Server; +const wss = new WebSocketServer({ port: 8080 }); // Broadcast to all. wss.broadcast = function broadcast(data) { @@ -169,8 +169,8 @@ catch (e) { /* handle error */ } ### echo.websocket.org demo ```js -var WebSocket = require('ws'); -var ws = new WebSocket('ws://echo.websocket.org/', { +const WebSocket = require('ws'); +const ws = new WebSocket('ws://echo.websocket.org/', { protocolVersion: 8, origin: 'http://websocket.org' }); From 20585ee8de2740e14c85b4e27db33ad038eabe14 Mon Sep 17 00:00:00 2001 From: Jacob Bogers Date: Mon, 9 Jan 2017 11:39:10 +0100 Subject: [PATCH 245/669] [fix] Prevent `on` setters from removing all listeners (#955) --- lib/WebSocket.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 4baf5a8eb..1abbb7407 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -409,8 +409,10 @@ WebSocket.CLOSED = 3; * @public */ get () { - const listener = this.listeners(method)[0]; - return listener ? listener._listener ? listener._listener : listener : undefined; + const listeners = this.listeners(method); + for (var i = 0; i < listeners.length; i++) { + if (listeners[i]._listener) return listeners[i]._listener; + } }, /** * Add a listener for the event. @@ -419,7 +421,13 @@ WebSocket.CLOSED = 3; * @public */ set (listener) { - this.removeAllListeners(method); + const listeners = this.listeners(method); + for (var i = 0; i < listeners.length; i++) { + // + // Remove only the listeners added via `addEventListener`. + // + if (listeners[i]._listener) this.removeListener(method, listeners[i]); + } this.addEventListener(method, listener); } }); From f0cc971bcf5067c43c010835e599c043b4b159c6 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 9 Jan 2017 12:57:37 +0100 Subject: [PATCH 246/669] [test] Add test for 20585ee --- test/WebSocketServer.test.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index cceb53f94..78c0d7768 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -238,7 +238,7 @@ describe('WebSocketServer', function () { }); }); - it('is updated when client closes the connection', function (done) { + it('is updated when client closes the connection (1/2)', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -253,6 +253,21 @@ describe('WebSocketServer', function () { }); }); }); + + it('is updated when client closes the connection (2/2)', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.onopen = () => ws.close(); + }); + + wss.on('connection', (ws) => { + ws.onclose = () => { + assert.strictEqual(wss.clients.size, 0); + wss.close(done); + }; + }); + }); }); describe('#options', function () { From 07974eefb21bd375e8ecd1cd14fb008c36f7a1d6 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 9 Jan 2017 13:49:19 +0000 Subject: [PATCH 247/669] fix(package): update ultron to version 1.1.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e8a12c227..57f3e7baa 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "lint": "eslint ." }, "dependencies": { - "ultron": "~1.0.2" + "ultron": "~1.1.0" }, "devDependencies": { "benchmark": "~2.1.2", From 443d0baa814f6dae4e6f1fb7c5aec40676f04d4b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 9 Jan 2017 14:41:47 +0100 Subject: [PATCH 248/669] [test] Increase code coverage --- test/WebSocket.test.js | 109 ++++++++++++++++++++++++----------- test/WebSocketServer.test.js | 17 +----- 2 files changed, 75 insertions(+), 51 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 318feed30..242fb294a 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -420,6 +420,18 @@ describe('WebSocket', function () { }); describe('#pause and #resume', function () { + it('throws an error when `readyState` is not `OPEN` (pause)', function () { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); + + assert.throws(() => ws.pause(), /^Error: not opened$/); + }); + + it('throws an error when `readyState` is not `OPEN` (resume)', function () { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); + + assert.throws(() => ws.resume(), /^Error: not opened$/); + }); + it('pauses the underlying stream', function (done) { // this test is sort-of racecondition'y, since an unlikely slow connection // to localhost can cause the test to succeed even when the stream pausing @@ -1067,35 +1079,30 @@ describe('WebSocket', function () { }); describe('WHATWG API emulation', function () { - it('should not throw errors when getting and setting', function (done) { - server.createServer(++port, (srv) => { - const listener = () => {}; - const ws = new WebSocket(`ws://localhost:${port}`); - - assert.strictEqual(ws.onmessage, undefined); - assert.strictEqual(ws.onclose, undefined); - assert.strictEqual(ws.onerror, undefined); - assert.strictEqual(ws.onopen, undefined); + it('should not throw errors when getting and setting', function () { + const listener = () => {}; + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - ws.onmessage = listener; - ws.onerror = listener; - ws.onclose = listener; - ws.onopen = listener; + assert.strictEqual(ws.onmessage, undefined); + assert.strictEqual(ws.onclose, undefined); + assert.strictEqual(ws.onerror, undefined); + assert.strictEqual(ws.onopen, undefined); - assert.strictEqual(ws.binaryType, 'nodebuffer'); - ws.binaryType = 'arraybuffer'; - assert.strictEqual(ws.binaryType, 'arraybuffer'); - ws.binaryType = 'nodebuffer'; - assert.strictEqual(ws.binaryType, 'nodebuffer'); + ws.onmessage = listener; + ws.onerror = listener; + ws.onclose = listener; + ws.onopen = listener; - assert.strictEqual(ws.onmessage, listener); - assert.strictEqual(ws.onclose, listener); - assert.strictEqual(ws.onerror, listener); - assert.strictEqual(ws.onopen, listener); + assert.strictEqual(ws.binaryType, 'nodebuffer'); + ws.binaryType = 'arraybuffer'; + assert.strictEqual(ws.binaryType, 'arraybuffer'); + ws.binaryType = 'nodebuffer'; + assert.strictEqual(ws.binaryType, 'nodebuffer'); - srv.close(done); - ws.terminate(); - }); + assert.strictEqual(ws.onmessage, listener); + assert.strictEqual(ws.onclose, listener); + assert.strictEqual(ws.onerror, listener); + assert.strictEqual(ws.onopen, listener); }); it('should throw an error when setting an invalid binary type', function () { @@ -1134,17 +1141,36 @@ describe('WebSocket', function () { }); }); - it('should receive text data wrapped in a MessageEvent when using addEventListener', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); + it('doesn\'t return event listeners added with `on`', function () { + const listener = () => {}; + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - ws.addEventListener('open', () => ws.send('hi')); - ws.addEventListener('message', (messageEvent) => { - assert.strictEqual(messageEvent.data, 'hi'); - srv.close(done); - ws.terminate(); - }); - }); + ws.on('open', listener); + + assert.deepStrictEqual(ws.listeners('open'), [listener]); + assert.strictEqual(ws.onopen, undefined); + }); + + it('doesn\'t remove event listeners added with `on`', function () { + const listener = () => {}; + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); + + ws.on('close', listener); + ws.onclose = listener; + + let listeners = ws.listeners('close'); + + assert.strictEqual(listeners.length, 2); + assert.strictEqual(listeners[0], listener); + assert.strictEqual(listeners[1]._listener, listener); + + ws.onclose = listener; + + listeners = ws.listeners('close'); + + assert.strictEqual(listeners.length, 2); + assert.strictEqual(listeners[0], listener); + assert.strictEqual(listeners[1]._listener, listener); }); it('registers listeners for custom events with addEventListener', function () { @@ -1186,6 +1212,19 @@ describe('WebSocket', function () { assert.strictEqual(ws.listeners('foo').length, 0); }); + it('should receive text data wrapped in a MessageEvent when using addEventListener', function (done) { + server.createServer(++port, (srv) => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.addEventListener('open', () => ws.send('hi')); + ws.addEventListener('message', (messageEvent) => { + assert.strictEqual(messageEvent.data, 'hi'); + srv.close(done); + ws.terminate(); + }); + }); + }); + it('should receive valid CloseEvent when server closes with code 1000', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 78c0d7768..cceb53f94 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -238,7 +238,7 @@ describe('WebSocketServer', function () { }); }); - it('is updated when client closes the connection (1/2)', function (done) { + it('is updated when client closes the connection', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -253,21 +253,6 @@ describe('WebSocketServer', function () { }); }); }); - - it('is updated when client closes the connection (2/2)', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.onopen = () => ws.close(); - }); - - wss.on('connection', (ws) => { - ws.onclose = () => { - assert.strictEqual(wss.clients.size, 0); - wss.close(done); - }; - }); - }); }); describe('#options', function () { From 772a814fbe999b41dae0bf8ae6b09a5c079c4a40 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 10 Jan 2017 14:43:50 +0100 Subject: [PATCH 249/669] [fix] Abort the request if `close` is called while connecting (#956) Fixes #388 --- lib/WebSocket.js | 66 ++++++++----------- test/WebSocket.test.js | 122 ++++++++++++++++++++++------------- test/WebSocketServer.test.js | 38 ++++++----- 3 files changed, 122 insertions(+), 104 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 1abbb7407..0da5c0f78 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -262,10 +262,10 @@ class WebSocket extends EventEmitter { */ close (code, data) { if (this.readyState === WebSocket.CLOSED) return; - if (this.readyState === WebSocket.CONNECTING) { - this.readyState = WebSocket.CLOSED; - return; + this._req.abort(); + this.emit('error', new Error('closed before the connection is established')); + return this.finalize(true); } if (this.readyState === WebSocket.CLOSING) { @@ -374,6 +374,11 @@ class WebSocket extends EventEmitter { */ terminate () { if (this.readyState === WebSocket.CLOSED) return; + if (this.readyState === WebSocket.CONNECTING) { + this._req.abort(); + this.emit('error', new Error('closed before the connection is established')); + return this.finalize(true); + } if (this._socket) { this.readyState = WebSocket.CLOSING; @@ -385,8 +390,6 @@ class WebSocket extends EventEmitter { // clearTimeout(this._closeTimer); this._closeTimer = setTimeout(this._finalize, closeTimeout, true); - } else if (this.readyState === WebSocket.CONNECTING) { - this.finalize(true); } } } @@ -618,43 +621,32 @@ function initAsClient (address, protocols, options) { if (agent) requestOptions.agent = agent; - const req = httpObj.get(requestOptions); + this._req = httpObj.get(requestOptions); + + this._req.on('error', (error) => { + if (this._req.aborted) return; - req.on('error', (error) => { this.emit('error', error); - this.finalize(error); + this.finalize(true); }); - req.on('response', (res) => { - var error; - - if (!this.emit('unexpected-response', req, res)) { - error = new Error(`unexpected server response (${res.statusCode})`); - req.abort(); - this.emit('error', error); + this._req.on('response', (res) => { + if (!this.emit('unexpected-response', this._req, res)) { + this._req.abort(); + this.emit('error', new Error(`unexpected server response (${res.statusCode})`)); + this.finalize(true); } - - this.finalize(error); }); - req.on('upgrade', (res, socket, head) => { - if (this.readyState === WebSocket.CLOSED) { - // client closed before server accepted connection - this.emit('close'); - this.removeAllListeners(); - socket.end(); - return; - } - + this._req.on('upgrade', (res, socket, head) => { const digest = crypto.createHash('sha1') .update(key + GUID, 'binary') .digest('base64'); if (res.headers['sec-websocket-accept'] !== digest) { + socket.destroy(); this.emit('error', new Error('invalid server key')); - this.removeAllListeners(); - socket.end(); - return; + return this.finalize(true); } const serverProt = res.headers['sec-websocket-protocol']; @@ -670,30 +662,28 @@ function initAsClient (address, protocols, options) { } if (protError) { + socket.destroy(); this.emit('error', new Error(protError)); - this.removeAllListeners(); - socket.end(); - return; + return this.finalize(true); } if (serverProt) this.protocol = serverProt; const serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']); + if (perMessageDeflate && serverExtensions[PerMessageDeflate.extensionName]) { try { perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]); } catch (err) { + socket.destroy(); this.emit('error', new Error('invalid extension parameter')); - this.removeAllListeners(); - socket.end(); - return; + return this.finalize(true); } + this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } + this._req = null; this.setSocket(socket, head); - - req.removeAllListeners(); - agent = null; }); } diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 242fb294a..debaf5d0b 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -95,39 +95,28 @@ describe('WebSocket', function () { wss.on('connection', (ws) => ws.send('foobar')); }); - it('#url exposes the server url', function (done) { - server.createServer(++port, (srv) => { - const url = `ws://localhost:${port}`; - const ws = new WebSocket(url); - - assert.strictEqual(ws.url, url); + it('#url exposes the server url', function () { + const url = `ws://localhost:${port}`; + const ws = new WebSocket(url, { agent: new CustomAgent() }); - ws.on('close', () => srv.close(done)); - ws.close(); - }); + assert.strictEqual(ws.url, url); }); - it('#protocolVersion exposes the protocol version', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - assert.strictEqual(ws.protocolVersion, 13); - - ws.on('close', () => srv.close(done)); - ws.close(); + it('#protocolVersion exposes the protocol version', function () { + const ws = new WebSocket(`ws://localhost:${port}`, { + agent: new CustomAgent() }); + + assert.strictEqual(ws.protocolVersion, 13); }); describe('#bufferedAmount', function () { - it('defaults to zero', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - assert.strictEqual(ws.bufferedAmount, 0); - - ws.on('close', () => srv.close(done)); - ws.close(); + it('defaults to zero', function () { + const ws = new WebSocket(`ws://localhost:${port}`, { + agent: new CustomAgent() }); + + assert.strictEqual(ws.bufferedAmount, 0); }); it('defaults to zero upon "open"', function (done) { @@ -203,15 +192,12 @@ describe('WebSocket', function () { }); describe('#readyState', function () { - it('defaults to connecting', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - assert.strictEqual(ws.readyState, WebSocket.CONNECTING); - - ws.on('close', () => srv.close(done)); - ws.close(); + it('defaults to connecting', function () { + const ws = new WebSocket(`ws://localhost:${port}`, { + agent: new CustomAgent() }); + + assert.strictEqual(ws.readyState, WebSocket.CONNECTING); }); it('set to open once connection is established', function (done) { @@ -236,7 +222,7 @@ describe('WebSocket', function () { srv.close(done); }); - ws.close(1001); + ws.on('open', () => ws.close(1001)); }); }); @@ -249,7 +235,7 @@ describe('WebSocket', function () { srv.close(done); }); - ws.terminate(); + ws.on('open', () => ws.terminate()); }); }); }); @@ -308,26 +294,68 @@ describe('WebSocket', function () { }); describe('connection establishing', function () { - it('can disconnect before connection is established', function (done) { - server.createServer(++port, (srv) => { + it('can terminate before connection is established (1/2)', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); - ws.on('close', () => srv.close(done)); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'closed before the connection is established'); + ws.on('close', () => wss.close(done)); + }); ws.terminate(); }); }); - it('can close before connection is established', function (done) { - server.createServer(++port, (srv) => { + it('can terminate before connection is established (2/2)', function (done) { + const wss = new WebSocketServer({ + verifyClient: (info, cb) => setTimeout(cb, 300, true), + port: ++port + }, () => { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); - ws.on('close', () => srv.close(done)); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'closed before the connection is established'); + ws.on('close', () => wss.close(done)); + }); + setTimeout(() => ws.terminate(), 150); + }); + }); + + it('can close before connection is established (1/2)', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'closed before the connection is established'); + ws.on('close', () => wss.close(done)); + }); ws.close(1001); }); }); + it('can close before connection is established (2/2)', function (done) { + const wss = new WebSocketServer({ + verifyClient: (info, cb) => setTimeout(cb, 300, true), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'closed before the connection is established'); + ws.on('close', () => wss.close(done)); + }); + setTimeout(() => ws.close(1001), 150); + }); + }); + it('can handle error before request is upgraded', function (done) { // Here, we don't create a server, to guarantee that the connection will // fail before the request is upgraded @@ -803,15 +831,17 @@ describe('WebSocket', function () { }); it('before connect should pass error through callback, if present', function (done) { - server.createServer(++port, (srv) => { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.send('hi', (error) => { - assert.ok(error instanceof Error); - srv.close(done); - ws.terminate(); + ws.send('hi', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'not opened'); + ws.on('close', () => wss.close(done)); }); }); + + wss.on('connection', (ws) => ws.close()); }); it('without data should be successful', function (done) { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index cceb53f94..3579dcf8f 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -202,10 +202,9 @@ describe('WebSocketServer', function () { const ws = new WebSocket(`ws://localhost:${port}`); }); - wss.on('connection', (client) => { + wss.on('connection', (ws) => { assert.strictEqual(wss.clients.size, 1); - wss.close(); - done(); + wss.close(done); }); }); @@ -213,12 +212,13 @@ describe('WebSocketServer', function () { const wss = new WebSocketServer({ port: ++port, clientTracking: false }, () => { assert.strictEqual(wss.clients, undefined); const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => ws.close()); }); - wss.on('connection', (client) => { + wss.on('connection', (ws) => { assert.strictEqual(wss.clients, undefined); - wss.close(); - done(); + ws.on('close', () => wss.close(done)); }); }); @@ -226,14 +226,13 @@ describe('WebSocketServer', function () { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - wss.on('connection', (client) => { - client.on('close', () => { - assert.strictEqual(wss.clients.size, 0); - wss.close(); - done(); - }); + ws.on('open', () => ws.terminate()); + }); - ws.close(); + wss.on('connection', (ws) => { + ws.on('close', () => { + assert.strictEqual(wss.clients.size, 0); + wss.close(done); }); }); }); @@ -242,14 +241,13 @@ describe('WebSocketServer', function () { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - wss.on('connection', (client) => { - client.on('close', () => { - assert.strictEqual(wss.clients.size, 0); - wss.close(); - done(); - }); + ws.on('open', () => ws.close()); + }); - ws.close(); + wss.on('connection', (ws) => { + ws.on('close', () => { + assert.strictEqual(wss.clients.size, 0); + wss.close(done); }); }); }); From 19ce183fad0e826a025bf9709eef48e279a1cb75 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 10 Jan 2017 15:05:23 +0100 Subject: [PATCH 250/669] [dist] 2.0.0-beta.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 57f3e7baa..6ccf4657e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", - "version": "1.1.0", + "version": "2.0.0-beta.0", "license": "MIT", "main": "index.js", "keywords": [ From fd910f1460d12bc669afde6ca80a4ba9969e8b99 Mon Sep 17 00:00:00 2001 From: Hanson Wang Date: Wed, 11 Jan 2017 01:41:34 -0800 Subject: [PATCH 251/669] [feature] Accept hostname lookup `family` option (#962) --- doc/ws.md | 1 + lib/WebSocket.js | 5 ++++- test/WebSocket.test.js | 11 +++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index 7839ff433..39d1b9126 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -174,6 +174,7 @@ This class represents a WebSocket. It extends the `EventEmitter`. depending on the `protocolVersion`. - `agent` {http.Agent|https.Agent} Use the specified Agent, - `host` {String} Value of the `Host` header. + - `family` {Number} IP address family to use during hostname lookup (4 or 6). - `checkServerIdentity` {Function} A function to validate the server hostname. - `rejectUnauthorized` {Boolean} Verify or not the server certificate. - `passphrase` {String} The passphrase for the private key or pfx. diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 0da5c0f78..741a05066 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -472,7 +472,7 @@ function initAsServerClient (req, socket, head, options) { * @param {String} address The URL to which to connect * @param {String[]} protocols The list of subprotocols * @param {Object} options Connection options - * @param {String} option.protocol Value of the `Sec-WebSocket-Protocol` header + * @param {String} options.protocol Value of the `Sec-WebSocket-Protocol` header * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate * @param {String} options.localAddress Local interface to bind for network connections * @param {Number} options.protocolVersion Value of the `Sec-WebSocket-Version` header @@ -480,6 +480,7 @@ function initAsServerClient (req, socket, head, options) { * @param {String} options.origin Value of the `Origin` or `Sec-WebSocket-Origin` header * @param {http.Agent} options.agent Use the specified Agent * @param {String} options.host Value of the `Host` header + * @param {Number} options.family IP address family to use during hostname lookup (4 or 6). * @param {Function} options.checkServerIdentity A function to validate the server hostname * @param {Boolean} options.rejectUnauthorized Verify or not the server certificate * @param {String} options.passphrase The passphrase for the private key or pfx @@ -500,6 +501,7 @@ function initAsClient (address, protocols, options) { origin: null, agent: null, host: null, + family: null, // // SSL options. @@ -572,6 +574,7 @@ function initAsClient (address, protocols, options) { } } if (options.host) requestOptions.headers.Host = options.host; + if (options.family) requestOptions.family = options.family; if (options.localAddress) requestOptions.localAddress = options.localAddress; if (isUnixSocket) requestOptions.socketPath = serverUrl.pathname; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index debaf5d0b..cf44a69fb 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -80,6 +80,17 @@ describe('WebSocket', function () { /must be a valid IP: 123.456.789.428/ ); }); + + it('should accept the family option', function (done) { + const wss = new WebSocketServer({ host: '::1', port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 }); + }); + + wss.on('connection', (ws) => { + assert.strictEqual(ws.upgradeReq.connection.remoteAddress, '::1'); + wss.close(done); + }); + }); }); describe('properties', function () { From 7bec220f9e3650afc5d92c17fb04a949dce0e7fd Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 11 Jan 2017 16:20:42 +0100 Subject: [PATCH 252/669] [doc] Modernize examples --- README.md | 82 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 2076c3b63..15f4ab00b 100644 --- a/README.md +++ b/README.md @@ -43,13 +43,14 @@ compiler is installed on the host system. ```js const WebSocket = require('ws'); + const ws = new WebSocket('ws://www.host.com/path'); ws.on('open', function open() { ws.send('something'); }); -ws.on('message', function(data, flags) { +ws.on('message', function incoming(data, flags) { // flags.binary will be set if a binary data is received. // flags.masked will be set if the data was masked. }); @@ -59,28 +60,26 @@ ws.on('message', function(data, flags) { ```js const WebSocket = require('ws'); + const ws = new WebSocket('ws://www.host.com/path'); ws.on('open', function open() { - var array = new Float32Array(5); + const array = new Float32Array(5); for (var i = 0; i < array.length; ++i) { array[i] = i / 2; } - ws.send(array, { binary: true, mask: true }); + ws.send(array); }); ``` -Setting `mask`, as done for the send options above, will cause the data to be -masked according to the WebSocket protocol. The same option applies for text -data. - ### Server example ```js -const WebSocketServer = require('ws').Server; -const wss = new WebSocketServer({ port: 8080 }); +const WebSocket = require('ws'); + +const wss = new WebSocket.Server({ port: 8080 }); wss.on('connection', function connection(ws) { ws.on('message', function incoming(message) { @@ -94,21 +93,23 @@ wss.on('connection', function connection(ws) { ### ExpressJS example ```js -const server = require('http').createServer(); -const url = require('url'); -const WebSocketServer = require('ws').Server; -const wss = new WebSocketServer({ server: server }); const express = require('express'); +const http = require('http'); +const url = require('url'); +const WebSocket = require('ws'); + const app = express(); -const port = 4080; app.use(function (req, res) { res.send({ msg: "hello" }); }); +const server = http.createServer(app); +const wss = new WebSocket.Server({ server }); + wss.on('connection', function connection(ws) { - var location = url.parse(ws.upgradeReq.url, true); - // you might use location.query.access_token to authenticate or share sessions + const location = url.parse(ws.upgradeReq.url, true); + // You might use location.query.access_token to authenticate or share sessions // or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312) ws.on('message', function incoming(message) { @@ -118,28 +119,34 @@ wss.on('connection', function connection(ws) { ws.send('something'); }); -server.on('request', app); -server.listen(port, function () { console.log('Listening on ' + server.address().port) }); +server.listen(8080, function listening() { + console.log('Listening on %d', server.address().port); +}); ``` ### Server sending broadcast data ```js -const WebSocketServer = require('ws').Server; -const wss = new WebSocketServer({ port: 8080 }); +const WebSocket = require('ws'); + +const wss = new WebSocket.Server({ port: 8080 }); // Broadcast to all. wss.broadcast = function broadcast(data) { wss.clients.forEach(function each(client) { - client.send(data); + if (client.readyState === WebSocket.OPEN) { + client.send(data); + } }); }; wss.on('connection', function connection(ws) { - ws.on('message', function message(data) { + ws.on('message', function incoming(data) { // Broadcast to everyone else. wss.clients.forEach(function each(client) { - if (client !== ws) client.send(data); + if (client !== ws && client.readyState === WebSocket.OPEN) { + client.send(data); + } }); }); }); @@ -155,11 +162,11 @@ ws.send('something'); // callback. The callback is also the only way of being notified that data has // actually been sent. ws.send('something', function ack(error) { - // if error is not defined, the send has been completed, - // otherwise the error object will indicate what failed. + // If error is not defined, the send has been completed, otherwise the error + // object will indicate what failed. }); -// Immediate errors can also be handled with try/catch-blocks, but **note** that +// Immediate errors can also be handled with `try...catch`, but **note** that // since sends are inherently asynchronous, socket write failures will *not* be // captured when this technique is used. try { ws.send('something'); } @@ -170,25 +177,25 @@ catch (e) { /* handle error */ } ```js const WebSocket = require('ws'); -const ws = new WebSocket('ws://echo.websocket.org/', { - protocolVersion: 8, - origin: 'http://websocket.org' + +const ws = new WebSocket('wss://echo.websocket.org/', { + origin: 'https://websocket.org' }); ws.on('open', function open() { console.log('connected'); - ws.send(Date.now().toString(), {mask: true}); + ws.send(Date.now()); }); ws.on('close', function close() { console.log('disconnected'); }); -ws.on('message', function message(data, flags) { - console.log('Roundtrip time: ' + (Date.now() - parseInt(data)) + 'ms', flags); +ws.on('message', function incoming(data, flags) { + console.log(`Roundtrip time: ${Date.now() - data} ms`, flags); setTimeout(function timeout() { - ws.send(Date.now().toString(), {mask: true}); + ws.send(Date.now()); }, 500); }); ``` @@ -198,18 +205,17 @@ ws.on('message', function message(data, flags) { For a full example with a browser client communicating with a ws server, see the examples folder. -Note that the usage together with Express 3.0 is quite different from Express -2.x. The difference is expressed in the two different serverstats-examples. - Otherwise, see the test cases. ## API Docs -See [`/doc/ws.md`](https://github.com/websockets/ws/blob/master/doc/ws.md) for Node.js-like docs for the ws classes. +See [`/doc/ws.md`](https://github.com/websockets/ws/blob/master/doc/ws.md) +for Node.js-like docs for the ws classes. ## Changelog -We're using the GitHub [`releases`](https://github.com/websockets/ws/releases) for changelog entries. +We're using the GitHub [`releases`](https://github.com/websockets/ws/releases) +for changelog entries. ## License From 4d9209246dc9b748b4226d4158fbcc0dff523a2e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 12 Jan 2017 12:50:09 +0100 Subject: [PATCH 253/669] [doc] Add WebSocket compression section --- README.md | 144 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 90 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 15f4ab00b..2573f1f79 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ws: a node.js websocket library +# ws: a node.js WebSocket library [![Version npm](https://img.shields.io/npm/v/ws.svg)](https://www.npmjs.com/package/ws) [![Linux Build](https://img.shields.io/travis/websockets/ws/master.svg)](https://travis-ci.org/websockets/ws) @@ -16,7 +16,7 @@ for the full reports. * **HyBi drafts 07-12** (Use the option `protocolVersion: 8`) * **HyBi drafts 13-17** (Current default, alternatively option `protocolVersion: 13`) -### Installing +## Installing ``` npm install --save ws @@ -39,6 +39,46 @@ compiler is installed on the host system. validation. But if you want to be 100% spec-conforming and have fast validation of UTF-8 then this module is a must. +## API Docs + +See [`/doc/ws.md`](https://github.com/websockets/ws/blob/master/doc/ws.md) +for Node.js-like docs for the ws classes. + +## WebSocket compression + +`ws` supports the [permessage-deflate extension][permessage-deflate] extension +which enables the client and server to negotiate a compression algorithm and +its parameters, and then selectively apply it to the data payloads of each +WebSocket message. + +The extension is enabled by default but adds a significant overhead in terms of +performance and memory comsumption. We suggest to use WebSocket compression +only if it is really needed. + +To disable the extension you can set the `perMessageDeflate` option to `false`. +On the server: + +```js +const WebSocket = require('ws'); + +const wss = new WebSocket.Server({ + perMessageDeflate: false, + port: 8080 +}); +``` + +On the client: + +```js +const WebSocket = require('ws'); + +const ws = new WebSocket('ws://www.host.com/path', { + perMessageDeflate: false +}); +``` + +## Usage examples + ### Sending and receiving text data ```js @@ -90,41 +130,7 @@ wss.on('connection', function connection(ws) { }); ``` -### ExpressJS example - -```js -const express = require('express'); -const http = require('http'); -const url = require('url'); -const WebSocket = require('ws'); - -const app = express(); - -app.use(function (req, res) { - res.send({ msg: "hello" }); -}); - -const server = http.createServer(app); -const wss = new WebSocket.Server({ server }); - -wss.on('connection', function connection(ws) { - const location = url.parse(ws.upgradeReq.url, true); - // You might use location.query.access_token to authenticate or share sessions - // or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312) - - ws.on('message', function incoming(message) { - console.log('received: %s', message); - }); - - ws.send('something'); -}); - -server.listen(8080, function listening() { - console.log('Listening on %d', server.address().port); -}); -``` - -### Server sending broadcast data +### Broadcast example ```js const WebSocket = require('ws'); @@ -152,25 +158,38 @@ wss.on('connection', function connection(ws) { }); ``` -### Error handling best practices +### ExpressJS example ```js -// If the WebSocket is closed before the following send is attempted -ws.send('something'); +const express = require('express'); +const http = require('http'); +const url = require('url'); +const WebSocket = require('ws'); -// Errors (both immediate and async write errors) can be detected in an optional -// callback. The callback is also the only way of being notified that data has -// actually been sent. -ws.send('something', function ack(error) { - // If error is not defined, the send has been completed, otherwise the error - // object will indicate what failed. +const app = express(); + +app.use(function (req, res) { + res.send({ msg: "hello" }); }); -// Immediate errors can also be handled with `try...catch`, but **note** that -// since sends are inherently asynchronous, socket write failures will *not* be -// captured when this technique is used. -try { ws.send('something'); } -catch (e) { /* handle error */ } +const server = http.createServer(app); +const wss = new WebSocket.Server({ server }); + +wss.on('connection', function connection(ws) { + const location = url.parse(ws.upgradeReq.url, true); + // You might use location.query.access_token to authenticate or share sessions + // or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312) + + ws.on('message', function incoming(message) { + console.log('received: %s', message); + }); + + ws.send('something'); +}); + +server.listen(8080, function listening() { + console.log('Listening on %d', server.address().port); +}); ``` ### echo.websocket.org demo @@ -207,10 +226,26 @@ examples folder. Otherwise, see the test cases. -## API Docs +## Error handling best practices -See [`/doc/ws.md`](https://github.com/websockets/ws/blob/master/doc/ws.md) -for Node.js-like docs for the ws classes. +```js +// If the WebSocket is closed before the following send is attempted +ws.send('something'); + +// Errors (both immediate and async write errors) can be detected in an optional +// callback. The callback is also the only way of being notified that data has +// actually been sent. +ws.send('something', function ack(error) { + // If error is not defined, the send has been completed, otherwise the error + // object will indicate what failed. +}); + +// Immediate errors can also be handled with `try...catch`, but **note** that +// since sends are inherently asynchronous, socket write failures will *not* be +// captured when this technique is used. +try { ws.send('something'); } +catch (e) { /* handle error */ } +``` ## Changelog @@ -222,3 +257,4 @@ for changelog entries. [MIT](LICENSE) [archive]: http://web.archive.org/web/20130314230536/http://hobbycoding.posterous.com/the-fastest-websocket-module-for-nodejs +[permessage-deflate]: https://tools.ietf.org/html/rfc7692 From bd41a05db9aae496615c5cb3a7fbeb3ef9e47e0b Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Sat, 14 Jan 2017 15:05:12 +0100 Subject: [PATCH 254/669] [fix] Use `Object.assign()` for `send()` options (#968) This avoids mutating the passed in arguments. --- lib/WebSocket.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 741a05066..c93ed05ba 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -355,16 +355,18 @@ class WebSocket extends EventEmitter { if (typeof data === 'number') data = data.toString(); else if (!data) data = ''; - options = options || {}; - if (options.fin !== false) options.fin = true; - if (options.binary === undefined) options.binary = typeof data !== 'string'; - if (options.mask === undefined) options.mask = !this._isServer; - if (options.compress === undefined) options.compress = true; + const opts = Object.assign({ + fin: true, + binary: typeof data !== 'string', + mask: !this._isServer, + compress: true + }, options); + if (!this.extensions[PerMessageDeflate.extensionName]) { - options.compress = false; + opts.compress = false; } - this._sender.send(data, options, cb); + this._sender.send(data, opts, cb); } /** From ac2dade21a553a0d2c3f529f935f9fbfd5487c78 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 14 Jan 2017 15:06:01 +0100 Subject: [PATCH 255/669] [fix] Prevent WebSocket#close() from triggering an infinite loop (#969) This prevents `WebSocket.prototype.close()` from triggering an infinite loop if called from an error listener while connecting. --- lib/WebSocket.js | 22 +++-- test/WebSocket.test.js | 182 +++++++++++++++++++++-------------------- 2 files changed, 107 insertions(+), 97 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index c93ed05ba..ea3513559 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -263,9 +263,12 @@ class WebSocket extends EventEmitter { close (code, data) { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { - this._req.abort(); - this.emit('error', new Error('closed before the connection is established')); - return this.finalize(true); + if (this._req && !this._req.aborted) { + this._req.abort(); + this.emit('error', new Error('closed before the connection is established')); + this.finalize(true); + } + return; } if (this.readyState === WebSocket.CLOSING) { @@ -377,9 +380,12 @@ class WebSocket extends EventEmitter { terminate () { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { - this._req.abort(); - this.emit('error', new Error('closed before the connection is established')); - return this.finalize(true); + if (this._req && !this._req.aborted) { + this._req.abort(); + this.emit('error', new Error('closed before the connection is established')); + this.finalize(true); + } + return; } if (this._socket) { @@ -631,6 +637,7 @@ function initAsClient (address, protocols, options) { this._req.on('error', (error) => { if (this._req.aborted) return; + this._req = null; this.emit('error', error); this.finalize(true); }); @@ -644,6 +651,8 @@ function initAsClient (address, protocols, options) { }); this._req.on('upgrade', (res, socket, head) => { + this._req = null; + const digest = crypto.createHash('sha1') .update(key + GUID, 'binary') .digest('base64'); @@ -688,7 +697,6 @@ function initAsClient (address, protocols, options) { this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } - this._req = null; this.setSocket(socket, head); }); } diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index cf44a69fb..7ac77dd96 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -305,77 +305,6 @@ describe('WebSocket', function () { }); describe('connection establishing', function () { - it('can terminate before connection is established (1/2)', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); - ws.on('close', () => wss.close(done)); - }); - ws.terminate(); - }); - }); - - it('can terminate before connection is established (2/2)', function (done) { - const wss = new WebSocketServer({ - verifyClient: (info, cb) => setTimeout(cb, 300, true), - port: ++port - }, () => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); - ws.on('close', () => wss.close(done)); - }); - setTimeout(() => ws.terminate(), 150); - }); - }); - - it('can close before connection is established (1/2)', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); - ws.on('close', () => wss.close(done)); - }); - ws.close(1001); - }); - }); - - it('can close before connection is established (2/2)', function (done) { - const wss = new WebSocketServer({ - verifyClient: (info, cb) => setTimeout(cb, 300, true), - port: ++port - }, () => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); - ws.on('close', () => wss.close(done)); - }); - setTimeout(() => ws.close(1001), 150); - }); - }); - - it('can handle error before request is upgraded', function (done) { - // Here, we don't create a server, to guarantee that the connection will - // fail before the request is upgraded - const ws = new WebSocket(`ws://localhost:${++port}`); - - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); - ws.on('error', () => done()); - }); - it('invalid server key is denied', function (done) { server.createServer(++port, server.handlers.invalidKey, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -956,7 +885,50 @@ describe('WebSocket', function () { }); describe('#close', function () { - it('without invalid first argument throws exception', function (done) { + it('closes the connection if called while connecting (1/2)', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'closed before the connection is established'); + ws.on('close', () => wss.close(done)); + }); + ws.close(1001); + }); + }); + + it('closes the connection if called while connecting (2/2)', function (done) { + const wss = new WebSocketServer({ + verifyClient: (info, cb) => setTimeout(cb, 300, true), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'closed before the connection is established'); + ws.on('close', () => wss.close(done)); + }); + setTimeout(() => ws.close(1001), 150); + }); + }); + + it('can be called from an error listener while connecting', function (done) { + const ws = new WebSocket(`ws://localhost:${++port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.code, 'ECONNREFUSED'); + ws.close(); + ws.on('close', () => done()); + }); + }); + + it('throws an error if the first argument is invalid (1/2)', function (done) { server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -971,7 +943,7 @@ describe('WebSocket', function () { }); }); - it('without reserved error code 1004 throws exception', function (done) { + it('throws an error if the first argument is invalid (2/2)', function (done) { server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -986,7 +958,7 @@ describe('WebSocket', function () { }); }); - it('without message is successfully transmitted to the server', function (done) { + it('works when close reason is not specified', function (done) { server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -1000,22 +972,7 @@ describe('WebSocket', function () { }); }); - it('with message is successfully transmitted to the server', function (done) { - server.createServer(++port, (srv) => { - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => ws.close(1000, 'some reason')); - - srv.on('close', (code, message, flags) => { - assert.ok(flags.masked); - assert.strictEqual(message, 'some reason'); - srv.close(done); - ws.terminate(); - }); - }); - }); - - it('with encoded message is successfully transmitted to the server', function (done) { + it('works when close reason is specified', function (done) { server.createServer(++port, (srv) => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -1119,6 +1076,51 @@ describe('WebSocket', function () { }); }); + describe('#terminate', function () { + it('closes the connection if called while connecting (1/2)', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'closed before the connection is established'); + ws.on('close', () => wss.close(done)); + }); + ws.terminate(); + }); + }); + + it('closes the connection if called while connecting (2/2)', function (done) { + const wss = new WebSocketServer({ + verifyClient: (info, cb) => setTimeout(cb, 300, true), + port: ++port + }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'closed before the connection is established'); + ws.on('close', () => wss.close(done)); + }); + setTimeout(() => ws.terminate(), 150); + }); + }); + + it('can be called from an error listener while connecting', function (done) { + const ws = new WebSocket(`ws://localhost:${++port}`); + + ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.code, 'ECONNREFUSED'); + ws.terminate(); + ws.on('close', () => done()); + }); + }); + }); + describe('WHATWG API emulation', function () { it('should not throw errors when getting and setting', function () { const listener = () => {}; From e62b9ba0c89aaf7b3a2d17084c23f4f983acf339 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 14 Jan 2017 17:20:23 +0100 Subject: [PATCH 256/669] [dist] 2.0.0-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6ccf4657e..628cd215d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", - "version": "2.0.0-beta.0", + "version": "2.0.0-beta.1", "license": "MIT", "main": "index.js", "keywords": [ From 42f364e91b85644306d8864f9b26cd95a308fdaa Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 12 Jan 2017 12:04:14 +0100 Subject: [PATCH 257/669] [doc] Remove outdated link and rephrase sentence accordingly --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2573f1f79..b30735d5c 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ [![Windows Build](https://ci.appveyor.com/api/projects/status/github/websockets/ws?branch=master&svg=true)](https://ci.appveyor.com/project/lpinca/ws) [![Coverage Status](https://img.shields.io/coveralls/websockets/ws/master.svg)](https://coveralls.io/r/websockets/ws?branch=master) -`ws` is a simple to use WebSocket implementation, up-to-date against RFC-6455, -and [probably the fastest WebSocket library for node.js][archive]. +`ws` is a simple to use, blazing fast, and thoroughly tested WebSocket client +and server implementation. Passes the quite extensive Autobahn test suite. See http://websockets.github.com/ws for the full reports. @@ -256,5 +256,4 @@ for changelog entries. [MIT](LICENSE) -[archive]: http://web.archive.org/web/20130314230536/http://hobbycoding.posterous.com/the-fastest-websocket-module-for-nodejs [permessage-deflate]: https://tools.ietf.org/html/rfc7692 From bc35fa4eee26c4dfea20b0998893515487b2f0a7 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 21 Jan 2017 16:12:25 +0100 Subject: [PATCH 258/669] chore(package): update eslint to version 3.14.0 (#974) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 628cd215d..597951a65 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~1.3.0", - "eslint": "~3.13.0", + "eslint": "~3.14.0", "eslint-config-semistandard": "~7.0.0", "eslint-config-standard": "~6.2.1", "eslint-plugin-promise": "~3.4.0", From d74a32e1761a4016764b44f617d57180e9e3af5b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 25 Jan 2017 09:36:26 +0100 Subject: [PATCH 259/669] [fix] Take into account the data queued in the sender (#971) This makes the `bufferedAmount` getter take into account the data queued in the sender. --- lib/Sender.js | 75 +++++++++++++++++++++++++++--------------- lib/WebSocket.js | 4 ++- test/Sender.test.js | 18 ++++++++++ test/WebSocket.test.js | 30 +++++++++++++---- 4 files changed, 94 insertions(+), 33 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 74f6e85d1..3913a8139 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -24,12 +24,16 @@ class Sender { */ constructor (socket, extensions) { this.perMessageDeflate = (extensions || {})[PerMessageDeflate.extensionName]; + this._socket = socket; + this.firstFragment = true; - this.processing = false; this.compress = false; - this._socket = socket; - this.onerror = null; + + this.processing = false; + this.bufferedBytes = 0; this.queue = []; + + this.onerror = null; } /** @@ -86,10 +90,23 @@ class Sender { * @public */ ping (data, mask) { + var readOnly = true; + + if (data && !Buffer.isBuffer(data)) { + if (data instanceof ArrayBuffer) { + data = Buffer.from(data); + } else if (ArrayBuffer.isView(data)) { + data = viewToBuffer(data); + } else { + data = Buffer.from(data); + readOnly = false; + } + } + if (this.perMessageDeflate) { - this.enqueue([this.doPing, data, mask]); + this.enqueue([this.doPing, data, mask, readOnly]); } else { - this.doPing(data, mask); + this.doPing(data, mask, readOnly); } } @@ -98,14 +115,15 @@ class Sender { * * @param {*} data The message to send * @param {Boolean} mask Specifies whether or not to mask `data` + * @param {Boolean} readOnly Specifies whether `data` can be modified * @private */ - doPing (data, mask) { + doPing (data, mask, readOnly) { this.frameAndSend(data, { - readOnly: true, opcode: 0x09, rsv1: false, fin: true, + readOnly, mask }); @@ -120,10 +138,23 @@ class Sender { * @public */ pong (data, mask) { + var readOnly = true; + + if (data && !Buffer.isBuffer(data)) { + if (data instanceof ArrayBuffer) { + data = Buffer.from(data); + } else if (ArrayBuffer.isView(data)) { + data = viewToBuffer(data); + } else { + data = Buffer.from(data); + readOnly = false; + } + } + if (this.perMessageDeflate) { - this.enqueue([this.doPong, data, mask]); + this.enqueue([this.doPong, data, mask, readOnly]); } else { - this.doPong(data, mask); + this.doPong(data, mask, readOnly); } } @@ -132,14 +163,15 @@ class Sender { * * @param {*} data The message to send * @param {Boolean} mask Specifies whether or not to mask `data` + * @param {Boolean} readOnly Specifies whether `data` can be modified * @private */ - doPong (data, mask) { + doPong (data, mask, readOnly) { this.frameAndSend(data, { - readOnly: true, opcode: 0x0a, rsv1: false, fin: true, + readOnly, mask }); @@ -243,7 +275,7 @@ class Sender { /** * Frames and sends a piece of data according to the HyBi WebSocket protocol. * - * @param {*} data The data to send + * @param {Buffer} data The data to send * @param {Object} options Options object * @param {Number} options.opcode The opcode * @param {Boolean} options.readOnly Specifies whether `data` can be modified @@ -267,17 +299,6 @@ class Sender { return; } - if (!Buffer.isBuffer(data)) { - if (data instanceof ArrayBuffer) { - data = Buffer.from(data); - } else if (ArrayBuffer.isView(data)) { - data = viewToBuffer(data); - } else { - data = Buffer.from(data); - options.readOnly = false; - } - } - const mergeBuffers = data.length < 1024 || options.mask && options.readOnly; var dataOffset = options.mask ? 6 : 2; var payloadLength = data.length; @@ -334,12 +355,13 @@ class Sender { dequeue () { if (this.processing) return; - const handler = this.queue.shift(); - if (!handler) return; + const params = this.queue.shift(); + if (!params) return; + if (params[1]) this.bufferedBytes -= params[1].length; this.processing = true; - handler[0].apply(this, handler.slice(1)); + params[0].apply(this, params.slice(1)); } /** @@ -361,6 +383,7 @@ class Sender { * @private */ enqueue (params) { + if (params[1]) this.bufferedBytes += params[1].length; this.queue.push(params); this.dequeue(); } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index ea3513559..ec7490bd0 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -82,7 +82,9 @@ class WebSocket extends EventEmitter { get bufferedAmount () { var amount = 0; - if (this._socket) amount = this._socket.bufferSize || 0; + if (this._socket) { + amount = this._socket.bufferSize + this._sender.bufferedBytes; + } return amount; } diff --git a/test/Sender.test.js b/test/Sender.test.js index 48c126963..d6454b47a 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -73,6 +73,24 @@ describe('Sender', function () { }); }); + describe('#pong', function () { + it('works with multiple types of data', function (done) { + let count = 0; + const sender = new Sender({ + write: (data) => { + assert.ok(data.equals(Buffer.from([0x8a, 0x02, 0x68, 0x69]))); + if (++count === 3) done(); + } + }); + + const array = new Uint8Array([0x68, 0x69]); + + sender.pong(array.buffer, false); + sender.pong(array, false); + sender.pong('hi', false); + }); + }); + describe('#send', function () { it('compresses data if compress option is enabled', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 7ac77dd96..13b9d4bb4 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -131,19 +131,34 @@ describe('WebSocket', function () { }); it('defaults to zero upon "open"', function (done) { - server.createServer(++port, (srv) => { + const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); ws.onopen = () => { assert.strictEqual(ws.bufferedAmount, 0); - - ws.on('close', () => srv.close(done)); - ws.close(); + wss.close(done); }; }); }); - it('stress kernel write buffer', function (done) { + it('takes into account the data in the sender queue', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => { + ws.send('foo'); + ws.send('bar', (err) => { + assert.ifError(err); + assert.strictEqual(ws.bufferedAmount, 0); + wss.close(done); + }); + + assert.strictEqual(ws.bufferedAmount, 3); + }); + }); + }); + + it('takes into account the data in the socket queue', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: false @@ -152,7 +167,10 @@ describe('WebSocket', function () { wss.on('connection', (ws) => { while (true) { - if (ws.bufferedAmount > 0) break; + if (ws._socket.bufferSize > 0) { + assert.strictEqual(ws.bufferedAmount, ws._socket.bufferSize); + break; + } ws.send('hello'.repeat(1e4)); } wss.close(done); From 236ea222f8ddde18fbac0e234ec7297cfd66f4ab Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 25 Jan 2017 09:45:08 +0100 Subject: [PATCH 260/669] [dist] 2.0.0-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 597951a65..ed02968f5 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", - "version": "2.0.0-beta.1", + "version": "2.0.0-beta.2", "license": "MIT", "main": "index.js", "keywords": [ From cb50a2958523770735be0a4118027ef6d1262328 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 30 Jan 2017 12:45:05 +0100 Subject: [PATCH 261/669] [dist] 2.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ed02968f5..2cdc1c9ac 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", - "version": "2.0.0-beta.2", + "version": "2.0.0", "license": "MIT", "main": "index.js", "keywords": [ From d856dcb3b698c6184b393b93fe9e8e382fa971b8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 1 Feb 2017 10:44:13 +0100 Subject: [PATCH 262/669] [fix] Save the value of the `compress` flag Fixes #983 --- lib/Sender.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 3913a8139..92cdfb7f4 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -220,7 +220,8 @@ class Sender { if (options.fin) this.firstFragment = true; if (this.perMessageDeflate) { - this.enqueue([this.sendCompressed, data, { + this.enqueue([this.dispatch, data, { + compress: this.compress, mask: options.mask, fin: options.fin, readOnly, @@ -239,21 +240,21 @@ class Sender { } /** - * Compresses, frames and sends a data message. + * Dispatches a data message. * * @param {Buffer} data The message to send * @param {Object} options Options object * @param {Number} options.opcode The opcode * @param {Boolean} options.readOnly Specifies whether `data` can be modified * @param {Boolean} options.fin Specifies whether or not to set the FIN bit + * @param {Boolean} options.compress Specifies whether or not to compress `data` * @param {Boolean} options.mask Specifies whether or not to mask `data` * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit * @param {Function} cb Callback * @private */ - sendCompressed (data, options, cb) { - if (!this.compress) { - options.rsv1 = false; + dispatch (data, options, cb) { + if (!options.compress) { this.frameAndSend(data, options, cb); this.continue(); return; From 715c1da2b2114e1cb9538d5b3ab6c4a664ab3adf Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 1 Feb 2017 11:17:02 +0100 Subject: [PATCH 263/669] [test] Remove unused fixture --- test/fixtures/textfile | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 test/fixtures/textfile diff --git a/test/fixtures/textfile b/test/fixtures/textfile deleted file mode 100644 index a10483b0e..000000000 --- a/test/fixtures/textfile +++ /dev/null @@ -1,9 +0,0 @@ -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam egestas, massa at aliquam luctus, sapien erat viverra elit, nec pulvinar turpis eros sagittis urna. Pellentesque imperdiet tempor varius. Pellentesque blandit, ipsum in imperdiet venenatis, mi elit faucibus odio, id condimentum ante enim sed lectus. Aliquam et odio non odio pellentesque pulvinar. Vestibulum a erat dolor. Integer pretium risus sit amet nisl volutpat nec venenatis magna egestas. Ut bibendum felis eu tellus laoreet eleifend. Nam pulvinar auctor tortor, eu iaculis leo vestibulum quis. In euismod risus ac purus vehicula et fermentum ligula consectetur. Vivamus condimentum tempus lacinia. - -Curabitur sodales condimentum urna id dictum. Sed quis justo sit amet quam ultrices tincidunt vel laoreet nulla. Nullam quis ipsum sed nisi mollis bibendum at sit amet nisi. Donec laoreet consequat velit sit amet mollis. Nam sed sapien a massa iaculis dapibus. Sed dui nunc, ultricies et pellentesque ullamcorper, aliquet vitae ligula. Integer eu velit in neque iaculis venenatis. Ut rhoncus cursus est, ac dignissim leo vehicula a. Nulla ullamcorper vulputate mauris id blandit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque eleifend, nisi a tempor sollicitudin, odio massa pretium urna, quis congue sapien elit at tortor. Curabitur ipsum orci, vehicula non commodo molestie, laoreet id enim. Pellentesque convallis ultrices congue. Pellentesque nec iaculis lorem. In sagittis pharetra ipsum eget sodales. - -Fusce id nulla odio. Nunc nibh justo, placerat vel tincidunt sed, ornare et enim. Nulla vel urna vel ante commodo bibendum in vitae metus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis erat nunc, semper eget sagittis sit amet, ullamcorper eget lacus. Donec hendrerit ipsum vitae eros vestibulum eu gravida neque tincidunt. Ut molestie lacinia nulla. Donec mattis odio at magna egestas at pellentesque eros accumsan. Praesent interdum sem sit amet nibh commodo dignissim. Duis laoreet, enim ultricies fringilla suscipit, enim libero cursus nulla, sollicitudin adipiscing erat velit ut dui. Nulla eleifend mauris at velit fringilla a molestie lorem venenatis. - -Donec sit amet scelerisque metus. Cras ac felis a nulla venenatis vulputate. Duis porttitor eros ac neque rhoncus eget aliquet neque egestas. Quisque sed nunc est, vitae dapibus quam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In vehicula, est vitae posuere ultricies, diam purus pretium sapien, nec rhoncus dolor nisl eget arcu. Aliquam et nisi vitae risus tincidunt auctor. In vehicula, erat a cursus adipiscing, lorem orci congue est, nec ultricies elit dui in nunc. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Lorem ipsum dolor sit amet, consectetur adipiscing elit. - -Duis congue tempus elit sit amet auctor. Duis dignissim, risus ut sollicitudin ultricies, dolor ligula gravida odio, nec congue orci purus ut ligula. Fusce pretium dictum lectus at volutpat. Sed non auctor mauris. Etiam placerat vestibulum massa id blandit. Quisque consequat lacus ut nulla euismod facilisis. Sed aliquet ipsum nec mi imperdiet viverra. Pellentesque ullamcorper, lectus nec varius gravida, odio justo cursus risus, eu sagittis metus arcu quis felis. Phasellus consectetur vehicula libero, at condimentum orci euismod vel. Nunc purus tortor, suscipit nec fringilla nec, vulputate et nibh. Nam porta vehicula neque. Praesent porttitor, sapien eu auctor euismod, arcu quam elementum urna, sed hendrerit magna augue sed quam. \ No newline at end of file From 6a49182a9078cc3758c0a7c7aebcfc165d13e13f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 1 Feb 2017 11:24:27 +0100 Subject: [PATCH 264/669] [doc] Update URLs based on HTTP redirects --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b30735d5c..714f1a8d1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ws: a node.js WebSocket library +# ws: a Node.js WebSocket library [![Version npm](https://img.shields.io/npm/v/ws.svg)](https://www.npmjs.com/package/ws) [![Linux Build](https://img.shields.io/travis/websockets/ws/master.svg)](https://travis-ci.org/websockets/ws) @@ -8,7 +8,7 @@ `ws` is a simple to use, blazing fast, and thoroughly tested WebSocket client and server implementation. -Passes the quite extensive Autobahn test suite. See http://websockets.github.com/ws +Passes the quite extensive Autobahn test suite. See http://websockets.github.io/ws/ for the full reports. ## Protocol support From a8d21d40b3852f49e8a902ddc95055e9a1957130 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 1 Feb 2017 11:25:59 +0100 Subject: [PATCH 265/669] [dist] 2.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2cdc1c9ac..487199922 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", - "version": "2.0.0", + "version": "2.0.1", "license": "MIT", "main": "index.js", "keywords": [ From 466e210b7eb81a5428e33ae8f605944216168b26 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 3 Feb 2017 17:53:56 +0100 Subject: [PATCH 266/669] [minor] Add support for bufferutil@2 and utf-8-validate@3 --- lib/BufferUtil.fallback.js | 66 ++++++++++++++++++++++++++------------ lib/BufferUtil.js | 4 ++- lib/Receiver.js | 8 ++--- lib/Sender.js | 2 +- lib/Validation.fallback.js | 6 +--- lib/Validation.js | 6 +++- test/Validation.test.js | 14 ++++---- 7 files changed, 66 insertions(+), 40 deletions(-) diff --git a/lib/BufferUtil.fallback.js b/lib/BufferUtil.fallback.js index a9607e7a0..5fcde1c3f 100644 --- a/lib/BufferUtil.fallback.js +++ b/lib/BufferUtil.fallback.js @@ -6,25 +6,51 @@ 'use strict'; -exports.BufferUtil = { - merge: function (mergedBuffer, buffers) { - var offset = 0; - for (var i = 0, l = buffers.length; i < l; ++i) { - var buf = buffers[i]; - buf.copy(mergedBuffer, offset); - offset += buf.length; - } - }, - mask: function (source, mask, output, offset, length) { - for (var i = 0; i < length; i++) { - output[offset + i] = source[i] ^ mask[i & 3]; - } - }, - unmask: function (data, mask) { - // required until https://github.com/nodejs/node/issues/9006 is resolved - var length = data.length; - for (var i = 0; i < length; i++) { - data[i] ^= mask[i & 3]; - } +/** + * Merges an array of buffers into a target buffer. + * + * @param {Buffer} target The target buffer + * @param {Buffer[]} buffers The array of buffers to merge + * @public + */ +const merge = (target, buffers) => { + var offset = 0; + for (var i = 0; i < buffers.length; i++) { + const buf = buffers[i]; + buf.copy(target, offset); + offset += buf.length; + } +}; + +/** + * Masks a buffer using the given mask. + * + * @param {Buffer} source The buffer to mask + * @param {Buffer} mask The mask to use + * @param {Buffer} output The buffer where to store the result + * @param {Number} offset The offset at which to start writing + * @param {Number} length The number of bytes to mask. + * @public + */ +const mask = (source, mask, output, offset, length) => { + for (var i = 0; i < length; i++) { + output[offset + i] = source[i] ^ mask[i & 3]; } }; + +/** + * Unmasks a buffer using the given mask. + * + * @param {Buffer} buffer The buffer to unmask + * @param {Buffer} mask The mask to use + * @public + */ +const unmask = (buffer, mask) => { + // Required until https://github.com/nodejs/node/issues/9006 is resolved. + const length = buffer.length; + for (var i = 0; i < length; i++) { + buffer[i] ^= mask[i & 3]; + } +}; + +module.exports = { merge, mask, unmask }; diff --git a/lib/BufferUtil.js b/lib/BufferUtil.js index 18c699894..7220fbc86 100644 --- a/lib/BufferUtil.js +++ b/lib/BufferUtil.js @@ -7,7 +7,9 @@ */ try { - module.exports = require('bufferutil'); + const bufferUtil = require('bufferutil'); + + module.exports = bufferUtil.BufferUtil || bufferUtil; } catch (e) { module.exports = require('./BufferUtil.fallback'); } diff --git a/lib/Receiver.js b/lib/Receiver.js index b79b19370..4a23311d6 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -7,8 +7,8 @@ 'use strict'; const PerMessageDeflate = require('./PerMessageDeflate'); -const bufferUtil = require('./BufferUtil').BufferUtil; -const Validation = require('./Validation').Validation; +const isValidUTF8 = require('./Validation'); +const bufferUtil = require('./BufferUtil'); const ErrorCodes = require('./ErrorCodes'); const EMPTY_BUFFER = Buffer.alloc(0); @@ -360,7 +360,7 @@ class Receiver { if (this.opcode === 2) { this.onmessage(buf, { masked: this.masked, binary: true }); } else { - if (!Validation.isValidUTF8(buf)) { + if (!isValidUTF8(buf)) { this.error(new Error('invalid utf8 sequence'), 1007); return; } @@ -396,7 +396,7 @@ class Receiver { const buf = data.slice(2); - if (!Validation.isValidUTF8(buf)) { + if (!isValidUTF8(buf)) { this.error(new Error('invalid utf8 sequence'), 1007); return; } diff --git a/lib/Sender.js b/lib/Sender.js index 92cdfb7f4..b36d60138 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -9,7 +9,7 @@ const crypto = require('crypto'); const PerMessageDeflate = require('./PerMessageDeflate'); -const bufferUtil = require('./BufferUtil').BufferUtil; +const bufferUtil = require('./BufferUtil'); const ErrorCodes = require('./ErrorCodes'); /** diff --git a/lib/Validation.fallback.js b/lib/Validation.fallback.js index 1b1432463..60663149a 100644 --- a/lib/Validation.fallback.js +++ b/lib/Validation.fallback.js @@ -6,8 +6,4 @@ 'use strict'; -exports.Validation = { - isValidUTF8: function (buffer) { - return true; - } -}; +module.exports = () => true; diff --git a/lib/Validation.js b/lib/Validation.js index 284ab2fb1..7fa5f5084 100644 --- a/lib/Validation.js +++ b/lib/Validation.js @@ -7,7 +7,11 @@ 'use strict'; try { - module.exports = require('utf-8-validate'); + const isValidUTF8 = require('utf-8-validate'); + + module.exports = typeof isValidUTF8 === 'object' + ? isValidUTF8.Validation.isValidUTF8 // utf-8-validate@<3.0.0 + : isValidUTF8; } catch (e) { module.exports = require('./Validation.fallback'); } diff --git a/test/Validation.test.js b/test/Validation.test.js index d09b6631d..d13cf460a 100644 --- a/test/Validation.test.js +++ b/test/Validation.test.js @@ -2,9 +2,7 @@ const assert = require('assert'); -const validation = require('../lib/Validation'); - -const Validation = validation.Validation; +const isValidUTF8 = require('../lib/Validation'); describe('Validation', function () { describe('isValidUTF8', function () { @@ -27,7 +25,7 @@ describe('Validation', function () { 'vulputate quis. Morbi ut pulvinar augue.' ); - assert.ok(Validation.isValidUTF8(validBuffer)); + assert.ok(isValidUTF8(validBuffer)); }); it('should return false for an erroneous string', function () { @@ -37,16 +35,16 @@ describe('Validation', function () { 0x65, 0x64, 0x69, 0x74, 0x65, 0x64 ]); - assert.ok(!Validation.isValidUTF8(invalidBuffer)); + assert.ok(!isValidUTF8(invalidBuffer)); }); it('should return true for valid cases from the autobahn test suite', function () { - assert.ok(Validation.isValidUTF8(Buffer.from([0xf0, 0x90, 0x80, 0x80]))); - assert.ok(Validation.isValidUTF8(Buffer.from('\xf0\x90\x80\x80'))); + assert.ok(isValidUTF8(Buffer.from([0xf0, 0x90, 0x80, 0x80]))); + assert.ok(isValidUTF8(Buffer.from('\xf0\x90\x80\x80'))); }); it('should return false for erroneous autobahn strings', function () { - assert.ok(!Validation.isValidUTF8(Buffer.from([0xce, 0xba, 0xe1, 0xbd]))); + assert.ok(!isValidUTF8(Buffer.from([0xce, 0xba, 0xe1, 0xbd]))); }); }); }); From f0d03cc79fb55d2df438120dedab01e016ba67b2 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 3 Feb 2017 18:10:46 +0100 Subject: [PATCH 267/669] [dist] 2.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 487199922..95eb72d4d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", - "version": "2.0.1", + "version": "2.0.2", "license": "MIT", "main": "index.js", "keywords": [ From 04f9ebcdbd3391ffc126e243a93f7b8624e7f6dd Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 3 Feb 2017 20:57:08 +0100 Subject: [PATCH 268/669] chore(package): update bufferutil to version 2.0.0 (#986) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 95eb72d4d..5b78cdfa4 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "devDependencies": { "benchmark": "~2.1.2", - "bufferutil": "~1.3.0", + "bufferutil": "~2.0.0", "eslint": "~3.14.0", "eslint-config-semistandard": "~7.0.0", "eslint-config-standard": "~6.2.1", From ed18a8f2c9daa59df8511ec0ef1594ca052b892f Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 3 Feb 2017 20:57:26 +0100 Subject: [PATCH 269/669] chore(package): update utf-8-validate to version 3.0.0 (#987) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5b78cdfa4..452031298 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,6 @@ "eslint-plugin-standard": "~2.0.1", "istanbul": "~0.4.5", "mocha": "~3.2.0", - "utf-8-validate": "~2.0.0" + "utf-8-validate": "~3.0.0" } } From 66917d04d18e1e6b13d0102dc4d4914886e03943 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 4 Feb 2017 07:39:03 +0100 Subject: [PATCH 270/669] chore(package): update eslint to version 3.15.0 (#988) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 452031298..a990b0de4 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~2.0.0", - "eslint": "~3.14.0", + "eslint": "~3.15.0", "eslint-config-semistandard": "~7.0.0", "eslint-config-standard": "~6.2.1", "eslint-plugin-promise": "~3.4.0", From f043b52aea2d8e739fe5b3a9d6b776e7d7500739 Mon Sep 17 00:00:00 2001 From: Tom Atkinson Date: Mon, 6 Feb 2017 18:54:04 +0100 Subject: [PATCH 271/669] Restore support for default port numbers --- lib/WebSocket.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index ec7490bd0..a655a134f 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -541,6 +541,7 @@ function initAsClient (address, protocols, options) { const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:'; const key = crypto.randomBytes(16).toString('base64'); + const port = serverUrl.port || (isSecure ? 443 : 80); const httpObj = isSecure ? https : http; // @@ -559,7 +560,7 @@ function initAsClient (address, protocols, options) { const requestOptions = { host: serverUrl.hostname, - port: serverUrl.port, + port, path: '/', headers: { 'Sec-WebSocket-Version': options.protocolVersion, From d90f481426c51539309ff7f547a38f1766a27177 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 8 Feb 2017 14:24:28 +0100 Subject: [PATCH 272/669] [fix] Prevent the parser from triggering a stack overflow (#992) This makes the parser work with buffers containing thousands of frames. --- bench/parser.benchmark.js | 1 - lib/Receiver.js | 137 +++++++++++++++++++++----------------- test/Receiver.test.js | 27 ++++++-- 3 files changed, 99 insertions(+), 66 deletions(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index 2924b5449..c28e82583 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -18,7 +18,6 @@ const Receiver = require('../').Receiver; // Receiver.prototype.cleanup = function () { this.state = 0; - this.start(); }; function createBinaryPacket (length) { diff --git a/lib/Receiver.js b/lib/Receiver.js index 4a23311d6..2c2d52b03 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -13,12 +13,14 @@ const ErrorCodes = require('./ErrorCodes'); const EMPTY_BUFFER = Buffer.alloc(0); -const START = 0; +const GET_INFO = 0; const GET_PAYLOAD_LENGTH_16 = 1; const GET_PAYLOAD_LENGTH_64 = 2; -const GET_MASK = 3; -const GET_DATA = 4; -const HANDLE_DATA = 5; +const HAVE_LENGTH = 3; +const GET_MASK = 4; +const GET_DATA = 5; +const HANDLE_DATA = 6; +const INFLATING = 7; /** * HyBi Receiver implementation. @@ -59,13 +61,14 @@ class Receiver { this.onping = null; this.onpong = null; - this.state = START; + this.state = GET_INFO; } /** * Consumes bytes from the available buffered data. * * @param {Number} bytes The number of bytes to consume + * @return {Buffer} Consumed bytes * @private */ readBuffer (bytes) { @@ -128,45 +131,55 @@ class Receiver { this.bufferedBytes += data.length; this.buffers.push(data); + this.startLoop(); + } - switch (this.state) { - case START: - this.start(); - break; - case GET_PAYLOAD_LENGTH_16: - this.getPayloadLength16(); - break; - case GET_PAYLOAD_LENGTH_64: - this.getPayloadLength64(); - break; - case GET_MASK: - this.getMask(); + /** + * Starts the parsing loop. + * + * @private + */ + startLoop () { + while (true) { + if (this.state === GET_INFO) { + if (!this.getInfo()) break; + } else if (this.state === GET_PAYLOAD_LENGTH_16) { + if (!this.getPayloadLength16()) break; + } else if (this.state === GET_PAYLOAD_LENGTH_64) { + if (!this.getPayloadLength64()) break; + } else if (this.state === HAVE_LENGTH) { + if (!this.haveLength()) break; + } else if (this.state === GET_MASK) { + if (!this.getMask()) break; + } else if (this.state === GET_DATA) { + if (!this.getData()) break; + } else { // `HANDLE_DATA` or `INFLATING` break; - case GET_DATA: - this.getData(); + } } } /** * Reads the first two bytes of a frame. * + * @return {Boolean} `true` if the operation is successful, else `false` * @private */ - start () { - if (!this.hasBufferedBytes(2)) return; + getInfo () { + if (!this.hasBufferedBytes(2)) return false; const buf = this.readBuffer(2); if ((buf[0] & 0x30) !== 0x00) { this.error(new Error('RSV2 and RSV3 must be clear'), 1002); - return; + return false; } const compressed = (buf[0] & 0x40) === 0x40; if (compressed && !this.extensions[PerMessageDeflate.extensionName]) { this.error(new Error('RSV1 must be clear'), 1002); - return; + return false; } this.fin = (buf[0] & 0x80) === 0x80; @@ -176,76 +189,75 @@ class Receiver { if (this.opcode === 0x00) { if (compressed) { this.error(new Error('RSV1 must be clear'), 1002); - return; + return false; } if (!this.fragmented) { this.error(new Error(`invalid opcode: ${this.opcode}`), 1002); - return; + return false; } else { this.opcode = this.fragmented; } } else if (this.opcode === 0x01 || this.opcode === 0x02) { if (this.fragmented) { this.error(new Error(`invalid opcode: ${this.opcode}`), 1002); - return; + return false; } this.compressed = compressed; } else if (this.opcode > 0x07 && this.opcode < 0x0b) { if (!this.fin) { this.error(new Error('FIN must be set'), 1002); - return; + return false; } if (compressed) { this.error(new Error('RSV1 must be clear'), 1002); - return; + return false; } if (this.payloadLength > 0x7d) { this.error(new Error('invalid payload length'), 1002); - return; + return false; } } else { this.error(new Error(`invalid opcode: ${this.opcode}`), 1002); - return; + return false; } if (!this.fin && !this.fragmented) this.fragmented = this.opcode; this.masked = (buf[1] & 0x80) === 0x80; - if (this.payloadLength === 126) { - this.state = GET_PAYLOAD_LENGTH_16; - this.getPayloadLength16(); - } else if (this.payloadLength === 127) { - this.state = GET_PAYLOAD_LENGTH_64; - this.getPayloadLength64(); - } else { - this.haveLength(); - } + if (this.payloadLength === 126) this.state = GET_PAYLOAD_LENGTH_16; + else if (this.payloadLength === 127) this.state = GET_PAYLOAD_LENGTH_64; + else this.state = HAVE_LENGTH; + + return true; } /** * Gets extended payload length (7+16). * + * @return {Boolean} `true` if payload length has been read, else `false` * @private */ getPayloadLength16 () { - if (!this.hasBufferedBytes(2)) return; + if (!this.hasBufferedBytes(2)) return false; this.payloadLength = this.readBuffer(2).readUInt16BE(0, true); - this.haveLength(); + this.state = HAVE_LENGTH; + return true; } /** * Gets extended payload length (7+64). * + * @return {Boolean} `true` if payload length has been read, else `false` * @private */ getPayloadLength64 () { - if (!this.hasBufferedBytes(8)) return; + if (!this.hasBufferedBytes(8)) return false; const buf = this.readBuffer(8); const num = buf.readUInt32BE(0, true); @@ -256,68 +268,72 @@ class Receiver { // if (num > Math.pow(2, 53 - 32) - 1) { this.error(new Error('max payload size exceeded'), 1009); - return; + return false; } this.payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4, true); - this.haveLength(); + this.state = HAVE_LENGTH; + return true; } /** * Payload length has been read. * + * @return {Boolean} `false` if payload length exceeds `maxPayload`, else `true` * @private */ haveLength () { if (this.opcode < 0x08 && this.maxPayloadExceeded(this.payloadLength)) { - return; + return false; } - if (this.masked) { - this.state = GET_MASK; - this.getMask(); - } else { - this.state = GET_DATA; - this.getData(); - } + if (this.masked) this.state = GET_MASK; + else this.state = GET_DATA; + return true; } /** * Reads mask bytes. * + * @return {Boolean} `true` if the mask has been read, else `false` * @private */ getMask () { - if (!this.hasBufferedBytes(4)) return; + if (!this.hasBufferedBytes(4)) return false; this.mask = this.readBuffer(4); this.state = GET_DATA; - this.getData(); + return true; } /** * Reads data bytes. * + * @return {Boolean} `true` if the data bytes have been read, else `false` * @private */ getData () { var data = EMPTY_BUFFER; if (this.payloadLength) { - if (!this.hasBufferedBytes(this.payloadLength)) return; + if (!this.hasBufferedBytes(this.payloadLength)) return false; data = this.readBuffer(this.payloadLength); if (this.masked) bufferUtil.unmask(data, this.mask); } + this.state = HANDLE_DATA; + if (this.opcode > 0x07) { this.controlMessage(data); } else if (this.compressed) { - this.state = HANDLE_DATA; + this.state = INFLATING; this.decompress(data); } else if (this.pushFragment(data)) { this.dataMessage(); } + + return true; } /** @@ -336,6 +352,7 @@ class Receiver { } if (this.pushFragment(buf)) this.dataMessage(); + if (this.state === GET_INFO) this.startLoop(); }); } @@ -369,8 +386,7 @@ class Receiver { } } - this.state = START; - this.start(); + this.state = GET_INFO; } /** @@ -413,8 +429,7 @@ class Receiver { if (this.opcode === 0x09) this.onping(data, flags); else this.onpong(data, flags); - this.state = START; - this.start(); + this.state = GET_INFO; } /** @@ -482,7 +497,7 @@ class Receiver { cleanup (cb) { this.dead = true; - if (!this.hadError && this.state === HANDLE_DATA) { + if (!this.hadError && this.state === INFLATING) { this.cleanupCallback = cb; } else { this.extensions = null; diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 7129228be..db7c721bb 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -307,6 +307,25 @@ describe('Receiver', function () { }); }); + it('can parse a buffer with thousands of frames', function (done) { + const buf = Buffer.allocUnsafe(40000); + + for (let i = 0; i < buf.length; i += 2) { + buf[i] = 0x81; + buf[i + 1] = 0x00; + } + + const p = new Receiver(); + let counter = 0; + + p.onmessage = function (data) { + assert.strictEqual(data, ''); + if (++counter === 20000) done(); + }; + + p.add(buf); + }); + it('resets `totalPayloadLength` only on final frame (unfragmented)', function () { const p = new Receiver({}, 10); let message; @@ -699,7 +718,7 @@ describe('Receiver', function () { p.add(frame); p.add(frame); - assert.strictEqual(p.state, 5); + assert.strictEqual(p.state, 7); assert.strictEqual(p.bufferedBytes, frame.length); p.cleanup(() => { @@ -731,7 +750,7 @@ describe('Receiver', function () { p.add(textFrame); p.add(closeFrame); - assert.strictEqual(p.state, 5); + assert.strictEqual(p.state, 7); assert.strictEqual(p.bufferedBytes, textFrame.length + closeFrame.length); p.cleanup(() => { @@ -763,7 +782,7 @@ describe('Receiver', function () { p.add(textFrame); p.add(invalidFrame); - assert.strictEqual(p.state, 5); + assert.strictEqual(p.state, 7); assert.strictEqual(p.bufferedBytes, textFrame.length + invalidFrame.length); p.cleanup(() => { @@ -798,7 +817,7 @@ describe('Receiver', function () { p.add(textFrame); p.add(incompleteFrame); - assert.strictEqual(p.state, 5); + assert.strictEqual(p.state, 7); assert.strictEqual(p.bufferedBytes, incompleteFrame.length); p.cleanup(() => { From 3918e11d200e574beca9d5abd61fbe3020434aed Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 8 Feb 2017 14:32:12 +0100 Subject: [PATCH 273/669] [dist] 2.0.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a990b0de4..17d91b579 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Einar Otto Stangvik (http://2x.io)", "name": "ws", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", - "version": "2.0.2", + "version": "2.0.3", "license": "MIT", "main": "index.js", "keywords": [ From e86d28421bf3a4f7a0dc6a1d47608c8c5d1f117a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 9 Feb 2017 10:22:49 +0100 Subject: [PATCH 274/669] [minor] Remove unnecessary `if` statement Optimize for the most, hopefully, common case (no error). --- lib/Receiver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Receiver.js b/lib/Receiver.js index 2c2d52b03..09e9973bd 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -352,7 +352,7 @@ class Receiver { } if (this.pushFragment(buf)) this.dataMessage(); - if (this.state === GET_INFO) this.startLoop(); + this.startLoop(); }); } From 92d869695978dc73350be6590456c83ac9e80593 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 9 Feb 2017 14:38:42 +0100 Subject: [PATCH 275/669] [fix] Accept only GET requests --- lib/WebSocketServer.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 4a0bd4e92..1f812eb6e 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -153,11 +153,9 @@ class WebSocketServer extends EventEmitter { const version = +req.headers['sec-websocket-version']; if ( - !this.shouldHandle(req) || - !req.headers.upgrade || - req.headers.upgrade.toLowerCase() !== 'websocket' || - !req.headers['sec-websocket-key'] || - version !== 8 && version !== 13 + req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket' || + !req.headers['sec-websocket-key'] || version !== 8 && version !== 13 || + !this.shouldHandle(req) ) { return abortConnection(socket, 400); } From 72cf8bee2dcdbdfdc523a5117fb0d82bcfb878bc Mon Sep 17 00:00:00 2001 From: Vasily Loginov Date: Tue, 26 Jul 2016 17:56:50 +0700 Subject: [PATCH 276/669] [example] Add Express session parsing example --- examples/express-session-parse/index.js | 80 +++++++++++++++++++ examples/express-session-parse/package.json | 11 +++ examples/express-session-parse/public/app.js | 46 +++++++++++ .../express-session-parse/public/index.html | 21 +++++ 4 files changed, 158 insertions(+) create mode 100644 examples/express-session-parse/index.js create mode 100644 examples/express-session-parse/package.json create mode 100644 examples/express-session-parse/public/app.js create mode 100644 examples/express-session-parse/public/index.html diff --git a/examples/express-session-parse/index.js b/examples/express-session-parse/index.js new file mode 100644 index 000000000..bac2d8e97 --- /dev/null +++ b/examples/express-session-parse/index.js @@ -0,0 +1,80 @@ +'use strict'; + +const session = require('express-session'); +const express = require('express'); +const http = require('http'); +const uuid = require('uuid'); + +const WebSocket = require('../..'); + +const app = express(); + +// +// We need the same instance of the session parser in express and +// WebSocket server. +// +const sessionParser = session({ + saveUninitialized: false, + secret: '$eCuRiTy', + resave: false +}); + +// +// Serve static files from the 'public' folder. +// +app.use(express.static('public')); +app.use(sessionParser); + +app.post('/login', (req, res) => { + // + // "Log in" user and set userId to session. + // + const id = uuid.v4(); + + console.log(`Updating session for user ${id}`); + req.session.userId = id; + res.send({ result: 'OK', message: 'Session updated' }); +}); + +app.delete('/logout', (request, response) => { + console.log('Destroying session'); + request.session.destroy(); + response.send({ result: 'OK', message: 'Session destroyed' }); +}); + +// +// Create HTTP server by ourselves. +// +const server = http.createServer(app); + +const wss = new WebSocket.Server({ + verifyClient: (info, done) => { + console.log('Parsing session from request...'); + sessionParser(info.req, {}, () => { + console.log('Session is parsed!'); + + // + // We can reject the connection by returning false to done(). For example, + // reject here if user is unknown. + // + done(info.req.session.userId); + }); + }, + server +}); + +wss.on('connection', (ws) => { + ws.on('message', (message) => { + const session = ws.upgradeReq.session; + + // + // Here we can now use session parameters. + // + console.log(`WS message ${message} from user ${session.userId}`); + }); +}); + +// +// Start the server. +// +server.listen(8080, () => console.log('Listening on http://localhost:8080')); diff --git a/examples/express-session-parse/package.json b/examples/express-session-parse/package.json new file mode 100644 index 000000000..cf96cbc49 --- /dev/null +++ b/examples/express-session-parse/package.json @@ -0,0 +1,11 @@ +{ + "author": "", + "name": "express-session-parse", + "version": "0.0.0", + "repository": "websockets/ws", + "dependencies": { + "express": "~4.14.1", + "express-session": "~1.15.1", + "uuid": "~3.0.1" + } +} diff --git a/examples/express-session-parse/public/app.js b/examples/express-session-parse/public/app.js new file mode 100644 index 000000000..916fafd41 --- /dev/null +++ b/examples/express-session-parse/public/app.js @@ -0,0 +1,46 @@ +/* global fetch, WebSocket, location */ +(() => { + const messages = document.querySelector('#messages'); + const wsButton = document.querySelector('#wsButton'); + const logout = document.querySelector('#logout'); + const login = document.querySelector('#login'); + + const showMessage = (message) => { + messages.textContent += `\n${message}`; + messages.scrollTop = messages.scrollHeight; + }; + + const handleResponse = (response) => { + return response.ok + ? response.json().then((data) => JSON.stringify(data, null, 2)) + : Promise.reject(new Error('Unexpected response')); + }; + + login.onclick = () => { + fetch('/login', { method: 'POST', credentials: 'same-origin' }) + .then(handleResponse) + .then(showMessage) + .catch((err) => showMessage(err.message)); + }; + + logout.onclick = () => { + fetch('/logout', { method: 'DELETE', credentials: 'same-origin' }) + .then(handleResponse) + .then(showMessage) + .catch((err) => showMessage(err.message)); + }; + + let ws; + + wsButton.onclick = () => { + if (ws) { + ws.onerror = ws.onopen = ws.onclose = null; + ws.close(); + } + + ws = new WebSocket(`ws://${location.host}`); + ws.onerror = () => showMessage('WebSocket error'); + ws.onopen = () => showMessage('WebSocket connection established'); + ws.onclose = () => showMessage('WebSocket connection closed'); + }; +})(); diff --git a/examples/express-session-parse/public/index.html b/examples/express-session-parse/public/index.html new file mode 100644 index 000000000..c99949c77 --- /dev/null +++ b/examples/express-session-parse/public/index.html @@ -0,0 +1,21 @@ + + + + + Express session demo + + +

Choose an action.

+ + + +

+    
+  
+

From ac8fcce52d10dc6fbc303f61650c4c2cd211fbfe Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Wed, 15 Feb 2017 21:17:17 +0100
Subject: [PATCH 277/669] [test] Do not override `Receiver#error()`

---
 test/Receiver.test.js | 36 ++++++++++++++++++------------------
 1 file changed, 18 insertions(+), 18 deletions(-)

diff --git a/test/Receiver.test.js b/test/Receiver.test.js
index db7c721bb..5d8c3b020 100644
--- a/test/Receiver.test.js
+++ b/test/Receiver.test.js
@@ -377,7 +377,7 @@ describe('Receiver', function () {
   it('raises an error when RSV1 is on and permessage-deflate is disabled', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'RSV1 must be clear');
       assert.strictEqual(code, 1002);
@@ -393,7 +393,7 @@ describe('Receiver', function () {
 
     const p = new Receiver({ 'permessage-deflate': perMessageDeflate });
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'RSV1 must be clear');
       assert.strictEqual(code, 1002);
@@ -406,7 +406,7 @@ describe('Receiver', function () {
   it('raises an error when RSV2 is on', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'RSV2 and RSV3 must be clear');
       assert.strictEqual(code, 1002);
@@ -419,7 +419,7 @@ describe('Receiver', function () {
   it('raises an error when RSV3 is on', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'RSV2 and RSV3 must be clear');
       assert.strictEqual(code, 1002);
@@ -432,7 +432,7 @@ describe('Receiver', function () {
   it('raises an error if the first frame in a fragmented message has opcode 0', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'invalid opcode: 0');
       assert.strictEqual(code, 1002);
@@ -445,7 +445,7 @@ describe('Receiver', function () {
   it('raises an error if a frame has opcode 1 in the middle of a fragmented message', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'invalid opcode: 1');
       assert.strictEqual(code, 1002);
@@ -459,7 +459,7 @@ describe('Receiver', function () {
   it('raises an error if a frame has opcode 2 in the middle of a fragmented message', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'invalid opcode: 2');
       assert.strictEqual(code, 1002);
@@ -473,7 +473,7 @@ describe('Receiver', function () {
   it('raises an error when a control frame has the FIN bit off', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'FIN must be set');
       assert.strictEqual(code, 1002);
@@ -489,7 +489,7 @@ describe('Receiver', function () {
 
     const p = new Receiver({ 'permessage-deflate': perMessageDeflate });
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'RSV1 must be clear');
       assert.strictEqual(code, 1002);
@@ -502,7 +502,7 @@ describe('Receiver', function () {
   it('raises an error when a control frame has the FIN bit off', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'FIN must be set');
       assert.strictEqual(code, 1002);
@@ -515,7 +515,7 @@ describe('Receiver', function () {
   it('raises an error when a control frame has a payload bigger than 125 B', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'invalid payload length');
       assert.strictEqual(code, 1002);
@@ -528,7 +528,7 @@ describe('Receiver', function () {
   it('raises an error when a data frame has a payload bigger than 2^53 - 1 B', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'max payload size exceeded');
       assert.strictEqual(code, 1009);
@@ -545,7 +545,7 @@ describe('Receiver', function () {
   it('raises an error if a text frame contains invalid UTF-8 data', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'invalid utf8 sequence');
       assert.strictEqual(code, 1007);
@@ -558,7 +558,7 @@ describe('Receiver', function () {
   it('raises an error if a close frame has a payload of 1 B', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'invalid payload length');
       assert.strictEqual(code, 1002);
@@ -571,7 +571,7 @@ describe('Receiver', function () {
   it('raises an error if a close frame contains an invalid close code', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'invalid status code: 0');
       assert.strictEqual(code, 1002);
@@ -584,7 +584,7 @@ describe('Receiver', function () {
   it('raises an error if a close frame contains invalid UTF-8 data', function (done) {
     const p = new Receiver();
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'invalid utf8 sequence');
       assert.strictEqual(code, 1007);
@@ -602,7 +602,7 @@ describe('Receiver', function () {
     const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask +
       util.mask(msg, mask).toString('hex');
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'max payload size exceeded');
       assert.strictEqual(code, 1009);
@@ -619,7 +619,7 @@ describe('Receiver', function () {
     const frame = '82' + util.getHybiLengthAsHexString(msg.length, false) +
       msg.toString('hex');
 
-    p.error = function (err, code) {
+    p.onerror = function (err, code) {
       assert.ok(err instanceof Error);
       assert.strictEqual(err.message, 'max payload size exceeded');
       assert.strictEqual(code, 1009);

From 6695bd44dc2d899ab7f774222f7e07295b786000 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Wed, 15 Feb 2017 19:36:00 +0100
Subject: [PATCH 278/669] [fix] Fix parser crash on synchronous socket error

This commit fixes an issue that could happen when the parser was
processing data and an error was emitted synchronously on the socket,
for example when writing to a socket whose connection had been
abruptly closed (ECONNRESET).
In these cases the parser was cleaned up prematurely and crashed when
trying to use resources no longer available.
---
 lib/Receiver.js       | 105 ++++++++++++++++++++----------------------
 test/Receiver.test.js |   8 ++--
 2 files changed, 53 insertions(+), 60 deletions(-)

diff --git a/lib/Receiver.js b/lib/Receiver.js
index 09e9973bd..71da72c7e 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -16,11 +16,9 @@ const EMPTY_BUFFER = Buffer.alloc(0);
 const GET_INFO = 0;
 const GET_PAYLOAD_LENGTH_16 = 1;
 const GET_PAYLOAD_LENGTH_64 = 2;
-const HAVE_LENGTH = 3;
-const GET_MASK = 4;
-const GET_DATA = 5;
-const HANDLE_DATA = 6;
-const INFLATING = 7;
+const GET_MASK = 3;
+const GET_DATA = 4;
+const INFLATING = 5;
 
 /**
  * HyBi Receiver implementation.
@@ -54,6 +52,7 @@ class Receiver {
     this.cleanupCallback = null;
     this.hadError = false;
     this.dead = false;
+    this.loop = false;
 
     this.onmessage = null;
     this.onclose = null;
@@ -117,6 +116,7 @@ class Receiver {
   hasBufferedBytes (n) {
     if (this.bufferedBytes >= n) return true;
 
+    this.loop = false;
     if (this.dead) this.cleanup(this.cleanupCallback);
     return false;
   }
@@ -140,21 +140,27 @@ class Receiver {
    * @private
    */
   startLoop () {
-    while (true) {
-      if (this.state === GET_INFO) {
-        if (!this.getInfo()) break;
-      } else if (this.state === GET_PAYLOAD_LENGTH_16) {
-        if (!this.getPayloadLength16()) break;
-      } else if (this.state === GET_PAYLOAD_LENGTH_64) {
-        if (!this.getPayloadLength64()) break;
-      } else if (this.state === HAVE_LENGTH) {
-        if (!this.haveLength()) break;
-      } else if (this.state === GET_MASK) {
-        if (!this.getMask()) break;
-      } else if (this.state === GET_DATA) {
-        if (!this.getData()) break;
-      } else { // `HANDLE_DATA` or `INFLATING`
-        break;
+    this.loop = true;
+
+    while (this.loop) {
+      switch (this.state) {
+        case GET_INFO:
+          this.getInfo();
+          break;
+        case GET_PAYLOAD_LENGTH_16:
+          this.getPayloadLength16();
+          break;
+        case GET_PAYLOAD_LENGTH_64:
+          this.getPayloadLength64();
+          break;
+        case GET_MASK:
+          this.getMask();
+          break;
+        case GET_DATA:
+          this.getData();
+          break;
+        default: // `INFLATING`
+          this.loop = false;
       }
     }
   }
@@ -162,24 +168,23 @@ class Receiver {
   /**
    * Reads the first two bytes of a frame.
    *
-   * @return {Boolean} `true` if the operation is successful, else `false`
    * @private
    */
   getInfo () {
-    if (!this.hasBufferedBytes(2)) return false;
+    if (!this.hasBufferedBytes(2)) return;
 
     const buf = this.readBuffer(2);
 
     if ((buf[0] & 0x30) !== 0x00) {
       this.error(new Error('RSV2 and RSV3 must be clear'), 1002);
-      return false;
+      return;
     }
 
     const compressed = (buf[0] & 0x40) === 0x40;
 
     if (compressed && !this.extensions[PerMessageDeflate.extensionName]) {
       this.error(new Error('RSV1 must be clear'), 1002);
-      return false;
+      return;
     }
 
     this.fin = (buf[0] & 0x80) === 0x80;
@@ -189,40 +194,40 @@ class Receiver {
     if (this.opcode === 0x00) {
       if (compressed) {
         this.error(new Error('RSV1 must be clear'), 1002);
-        return false;
+        return;
       }
 
       if (!this.fragmented) {
         this.error(new Error(`invalid opcode: ${this.opcode}`), 1002);
-        return false;
+        return;
       } else {
         this.opcode = this.fragmented;
       }
     } else if (this.opcode === 0x01 || this.opcode === 0x02) {
       if (this.fragmented) {
         this.error(new Error(`invalid opcode: ${this.opcode}`), 1002);
-        return false;
+        return;
       }
 
       this.compressed = compressed;
     } else if (this.opcode > 0x07 && this.opcode < 0x0b) {
       if (!this.fin) {
         this.error(new Error('FIN must be set'), 1002);
-        return false;
+        return;
       }
 
       if (compressed) {
         this.error(new Error('RSV1 must be clear'), 1002);
-        return false;
+        return;
       }
 
       if (this.payloadLength > 0x7d) {
         this.error(new Error('invalid payload length'), 1002);
-        return false;
+        return;
       }
     } else {
       this.error(new Error(`invalid opcode: ${this.opcode}`), 1002);
-      return false;
+      return;
     }
 
     if (!this.fin && !this.fragmented) this.fragmented = this.opcode;
@@ -231,33 +236,28 @@ class Receiver {
 
     if (this.payloadLength === 126) this.state = GET_PAYLOAD_LENGTH_16;
     else if (this.payloadLength === 127) this.state = GET_PAYLOAD_LENGTH_64;
-    else this.state = HAVE_LENGTH;
-
-    return true;
+    else this.haveLength();
   }
 
   /**
    * Gets extended payload length (7+16).
    *
-   * @return {Boolean} `true` if payload length has been read, else `false`
    * @private
    */
   getPayloadLength16 () {
-    if (!this.hasBufferedBytes(2)) return false;
+    if (!this.hasBufferedBytes(2)) return;
 
     this.payloadLength = this.readBuffer(2).readUInt16BE(0, true);
-    this.state = HAVE_LENGTH;
-    return true;
+    this.haveLength();
   }
 
   /**
    * Gets extended payload length (7+64).
    *
-   * @return {Boolean} `true` if payload length has been read, else `false`
    * @private
    */
   getPayloadLength64 () {
-    if (!this.hasBufferedBytes(8)) return false;
+    if (!this.hasBufferedBytes(8)) return;
 
     const buf = this.readBuffer(8);
     const num = buf.readUInt32BE(0, true);
@@ -268,62 +268,54 @@ class Receiver {
     //
     if (num > Math.pow(2, 53 - 32) - 1) {
       this.error(new Error('max payload size exceeded'), 1009);
-      return false;
+      return;
     }
 
     this.payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4, true);
-    this.state = HAVE_LENGTH;
-    return true;
+    this.haveLength();
   }
 
   /**
    * Payload length has been read.
    *
-   * @return {Boolean} `false` if payload length exceeds `maxPayload`, else `true`
    * @private
    */
   haveLength () {
     if (this.opcode < 0x08 && this.maxPayloadExceeded(this.payloadLength)) {
-      return false;
+      return;
     }
 
     if (this.masked) this.state = GET_MASK;
     else this.state = GET_DATA;
-    return true;
   }
 
   /**
    * Reads mask bytes.
    *
-   * @return {Boolean} `true` if the mask has been read, else `false`
    * @private
    */
   getMask () {
-    if (!this.hasBufferedBytes(4)) return false;
+    if (!this.hasBufferedBytes(4)) return;
 
     this.mask = this.readBuffer(4);
     this.state = GET_DATA;
-    return true;
   }
 
   /**
    * Reads data bytes.
    *
-   * @return {Boolean} `true` if the data bytes have been read, else `false`
    * @private
    */
   getData () {
     var data = EMPTY_BUFFER;
 
     if (this.payloadLength) {
-      if (!this.hasBufferedBytes(this.payloadLength)) return false;
+      if (!this.hasBufferedBytes(this.payloadLength)) return;
 
       data = this.readBuffer(this.payloadLength);
       if (this.masked) bufferUtil.unmask(data, this.mask);
     }
 
-    this.state = HANDLE_DATA;
-
     if (this.opcode > 0x07) {
       this.controlMessage(data);
     } else if (this.compressed) {
@@ -332,8 +324,6 @@ class Receiver {
     } else if (this.pushFragment(data)) {
       this.dataMessage();
     }
-
-    return true;
   }
 
   /**
@@ -399,6 +389,7 @@ class Receiver {
     if (this.opcode === 0x08) {
       if (data.length === 0) {
         this.onclose(1000, '', { masked: this.masked });
+        this.loop = false;
         this.cleanup(this.cleanupCallback);
       } else if (data.length === 1) {
         this.error(new Error('invalid payload length'), 1002);
@@ -418,6 +409,7 @@ class Receiver {
         }
 
         this.onclose(code, buf.toString(), { masked: this.masked });
+        this.loop = false;
         this.cleanup(this.cleanupCallback);
       }
 
@@ -442,6 +434,7 @@ class Receiver {
   error (err, code) {
     this.onerror(err, code);
     this.hadError = true;
+    this.loop = false;
     this.cleanup(this.cleanupCallback);
   }
 
@@ -497,7 +490,7 @@ class Receiver {
   cleanup (cb) {
     this.dead = true;
 
-    if (!this.hadError && this.state === INFLATING) {
+    if (!this.hadError && (this.loop || this.state === INFLATING)) {
       this.cleanupCallback = cb;
     } else {
       this.extensions = null;
diff --git a/test/Receiver.test.js b/test/Receiver.test.js
index 5d8c3b020..1290388af 100644
--- a/test/Receiver.test.js
+++ b/test/Receiver.test.js
@@ -718,7 +718,7 @@ describe('Receiver', function () {
       p.add(frame);
       p.add(frame);
 
-      assert.strictEqual(p.state, 7);
+      assert.strictEqual(p.state, 5);
       assert.strictEqual(p.bufferedBytes, frame.length);
 
       p.cleanup(() => {
@@ -750,7 +750,7 @@ describe('Receiver', function () {
       p.add(textFrame);
       p.add(closeFrame);
 
-      assert.strictEqual(p.state, 7);
+      assert.strictEqual(p.state, 5);
       assert.strictEqual(p.bufferedBytes, textFrame.length + closeFrame.length);
 
       p.cleanup(() => {
@@ -782,7 +782,7 @@ describe('Receiver', function () {
       p.add(textFrame);
       p.add(invalidFrame);
 
-      assert.strictEqual(p.state, 7);
+      assert.strictEqual(p.state, 5);
       assert.strictEqual(p.bufferedBytes, textFrame.length + invalidFrame.length);
 
       p.cleanup(() => {
@@ -817,7 +817,7 @@ describe('Receiver', function () {
       p.add(textFrame);
       p.add(incompleteFrame);
 
-      assert.strictEqual(p.state, 7);
+      assert.strictEqual(p.state, 5);
       assert.strictEqual(p.bufferedBytes, incompleteFrame.length);
 
       p.cleanup(() => {

From 8ead18d00cb1bc7f7e40ab3abc9a14810a17ce3f Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Sat, 11 Feb 2017 10:24:24 +0100
Subject: [PATCH 279/669] [perf] Write to the socket as soon as possible

---
 lib/PerMessageDeflate.js |  3 ++-
 lib/Sender.js            | 40 ++++++++++------------------------------
 test/WebSocket.test.js   |  4 +++-
 3 files changed, 15 insertions(+), 32 deletions(-)

diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js
index d1569546f..92a26525f 100644
--- a/lib/PerMessageDeflate.js
+++ b/lib/PerMessageDeflate.js
@@ -288,7 +288,8 @@ class PerMessageDeflate {
 
   compress (data, fin, callback) {
     if (!data || data.length === 0) {
-      return callback(null, EMPTY_BLOCK);
+      process.nextTick(callback, null, EMPTY_BLOCK);
+      return;
     }
 
     var endpoint = this._isServer ? 'server' : 'client';
diff --git a/lib/Sender.js b/lib/Sender.js
index b36d60138..08997de95 100644
--- a/lib/Sender.js
+++ b/lib/Sender.js
@@ -29,8 +29,8 @@ class Sender {
     this.firstFragment = true;
     this.compress = false;
 
-    this.processing = false;
     this.bufferedBytes = 0;
+    this.deflating = false;
     this.queue = [];
 
     this.onerror = null;
@@ -78,8 +78,6 @@ class Sender {
       fin: true,
       mask
     }, cb);
-
-    if (this.perMessageDeflate) this.continue();
   }
 
   /**
@@ -126,8 +124,6 @@ class Sender {
       readOnly,
       mask
     });
-
-    if (this.perMessageDeflate) this.continue();
   }
 
   /**
@@ -174,8 +170,6 @@ class Sender {
       readOnly,
       mask
     });
-
-    if (this.perMessageDeflate) this.continue();
   }
 
   /**
@@ -256,10 +250,10 @@ class Sender {
   dispatch (data, options, cb) {
     if (!options.compress) {
       this.frameAndSend(data, options, cb);
-      this.continue();
       return;
     }
 
+    this.deflating = true;
     this.perMessageDeflate.compress(data, options.fin, (err, buf) => {
       if (err) {
         if (cb) cb(err);
@@ -269,7 +263,8 @@ class Sender {
 
       options.readOnly = false;
       this.frameAndSend(buf, options, cb);
-      this.continue();
+      this.deflating = false;
+      this.dequeue();
     });
   }
 
@@ -349,32 +344,17 @@ class Sender {
   }
 
   /**
-   * Executes a queued send operation.
+   * Executes queued send operations.
    *
    * @private
    */
   dequeue () {
-    if (this.processing) return;
-
-    const params = this.queue.shift();
-    if (!params) return;
-
-    if (params[1]) this.bufferedBytes -= params[1].length;
-    this.processing = true;
-
-    params[0].apply(this, params.slice(1));
-  }
+    while (!this.deflating && this.queue.length) {
+      const params = this.queue.shift();
 
-  /**
-   * Signals the completion of a send operation.
-   *
-   * @private
-   */
-  continue () {
-    process.nextTick(() => {
-      this.processing = false;
-      this.dequeue();
-    });
+      if (params[1]) this.bufferedBytes -= params[1].length;
+      params[0].apply(this, params.slice(1));
+    }
   }
 
   /**
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index 13b9d4bb4..f7ea03d90 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -143,7 +143,9 @@ describe('WebSocket', function () {
 
       it('takes into account the data in the sender queue', function (done) {
         const wss = new WebSocketServer({ port: ++port }, () => {
-          const ws = new WebSocket(`ws://localhost:${port}`);
+          const ws = new WebSocket(`ws://localhost:${port}`, {
+            perMessageDeflate: { threshold: 0 }
+          });
 
           ws.on('open', () => {
             ws.send('foo');

From 3b9f4ca282d736db79f9f903da3c03f961741ea6 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Sat, 11 Feb 2017 15:09:53 +0100
Subject: [PATCH 280/669] [perf] Enqueue send operations only when strictly
 necessary

---
 lib/Sender.js | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/lib/Sender.js b/lib/Sender.js
index 08997de95..ee520cdda 100644
--- a/lib/Sender.js
+++ b/lib/Sender.js
@@ -55,7 +55,7 @@ class Sender {
     buf.writeUInt16BE(code || 1000, 0, true);
     if (buf.length > 2) buf.write(data, 2);
 
-    if (this.perMessageDeflate) {
+    if (this.deflating) {
       this.enqueue([this.doClose, buf, mask, cb]);
     } else {
       this.doClose(buf, mask, cb);
@@ -101,7 +101,7 @@ class Sender {
       }
     }
 
-    if (this.perMessageDeflate) {
+    if (this.deflating) {
       this.enqueue([this.doPing, data, mask, readOnly]);
     } else {
       this.doPing(data, mask, readOnly);
@@ -147,7 +147,7 @@ class Sender {
       }
     }
 
-    if (this.perMessageDeflate) {
+    if (this.deflating) {
       this.enqueue([this.doPong, data, mask, readOnly]);
     } else {
       this.doPong(data, mask, readOnly);
@@ -214,14 +214,20 @@ class Sender {
     if (options.fin) this.firstFragment = true;
 
     if (this.perMessageDeflate) {
-      this.enqueue([this.dispatch, data, {
+      const opts = {
         compress: this.compress,
         mask: options.mask,
         fin: options.fin,
         readOnly,
         opcode,
         rsv1
-      }, cb]);
+      };
+
+      if (this.deflating) {
+        this.enqueue([this.dispatch, data, opts, cb]);
+      } else {
+        this.dispatch(data, opts, cb);
+      }
     } else {
       this.frameAndSend(data, {
         mask: options.mask,
@@ -366,7 +372,6 @@ class Sender {
   enqueue (params) {
     if (params[1]) this.bufferedBytes += params[1].length;
     this.queue.push(params);
-    this.dequeue();
   }
 }
 

From 060b275d978ff7d095a92863fc3a1f7bcea878ed Mon Sep 17 00:00:00 2001
From: Varun Malhotra 
Date: Wed, 8 Jul 2015 11:28:06 -0700
Subject: [PATCH 281/669] [feature] Allow path to be specified with UNIX domain
 sockets

This change allows URL path with params to be specified with
UNIX domain socket URLs.

An example URL with UNIX domain socket looks like:
  ws+unix:///absolule/path/to/uds_socket:/pathname?query_params

Note that ':' is the seprator between socket path and URL path.

Existing URLs like the following also work:
  ws+unix:///absolule/path/to/uds_socket

In the above case, path gets set to '/'.
---
 doc/ws.md                    | 18 ++++++++++++++++++
 lib/WebSocket.js             | 25 +++++++++++++++----------
 test/WebSocketServer.test.js | 16 ++++++++++++----
 3 files changed, 45 insertions(+), 14 deletions(-)

diff --git a/doc/ws.md b/doc/ws.md
index 39d1b9126..cdc6aee05 100644
--- a/doc/ws.md
+++ b/doc/ws.md
@@ -190,6 +190,24 @@ requested to the server).
 
 Create a new WebSocket instance.
 
+#### UNIX Domain Sockets
+
+`ws` supports making requests to UNIX domain sockets. To make one, use the
+following URL scheme:
+
+```
+ws+unix:///absolule/path/to/uds_socket:/pathname?search_params
+```
+
+Note that `:` is the separator between the socket path and the URL path. If
+the URL path is omitted
+
+```
+ws+unix:///absolule/path/to/uds_socket
+```
+
+it defaults to `/`.
+
 ### Event: 'close'
 
 - `code` {Number}
diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index a655a134f..2bfd1653d 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -537,11 +537,12 @@ function initAsClient (address, protocols, options) {
   const serverUrl = url.parse(address);
   const isUnixSocket = serverUrl.protocol === 'ws+unix:';
 
-  if (!serverUrl.host && !isUnixSocket) throw new Error('invalid url');
+  if (!serverUrl.host && (!isUnixSocket || !serverUrl.path)) {
+    throw new Error('invalid url');
+  }
 
   const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:';
   const key = crypto.randomBytes(16).toString('base64');
-  const port = serverUrl.port || (isSecure ? 443 : 80);
   const httpObj = isSecure ? https : http;
 
   //
@@ -559,8 +560,8 @@ function initAsClient (address, protocols, options) {
   }
 
   const requestOptions = {
+    port: serverUrl.port || (isSecure ? 443 : 80),
     host: serverUrl.hostname,
-    port,
     path: '/',
     headers: {
       'Sec-WebSocket-Version': options.protocolVersion,
@@ -585,16 +586,20 @@ function initAsClient (address, protocols, options) {
     }
   }
   if (options.host) requestOptions.headers.Host = options.host;
-  if (options.family) requestOptions.family = options.family;
+  if (serverUrl.auth) requestOptions.auth = serverUrl.auth;
 
   if (options.localAddress) requestOptions.localAddress = options.localAddress;
-  if (isUnixSocket) requestOptions.socketPath = serverUrl.pathname;
-  if (serverUrl.auth) requestOptions.auth = serverUrl.auth;
+  if (options.family) requestOptions.family = options.family;
 
-  //
-  // Make sure that path starts with `/`.
-  //
-  if (serverUrl.path) {
+  if (isUnixSocket) {
+    const parts = serverUrl.path.split(':');
+
+    requestOptions.socketPath = parts[0];
+    requestOptions.path = parts[1];
+  } else if (serverUrl.path) {
+    //
+    // Make sure that path starts with `/`.
+    //
     if (serverUrl.path.charAt(0) !== '/') {
       requestOptions.path = `/${serverUrl.path}`;
     } else {
diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js
index 3579dcf8f..72481c996 100644
--- a/test/WebSocketServer.test.js
+++ b/test/WebSocketServer.test.js
@@ -3,6 +3,7 @@
 'use strict';
 
 const assert = require('assert');
+const crypto = require('crypto');
 const https = require('https');
 const http = require('http');
 const net = require('net');
@@ -82,16 +83,23 @@ describe('WebSocketServer', function () {
       if (process.platform === 'win32') return done();
 
       const server = http.createServer();
-      const sockPath = `/tmp/ws_socket_${new Date().getTime()}.${Math.floor(Math.random() * 1000)}`;
+      const sockPath = `/tmp/ws.${crypto.randomBytes(16).toString('hex')}.socket`;
 
       server.listen(sockPath, () => {
         const wss = new WebSocketServer({ server });
-        const ws = new WebSocket(`ws+unix://${sockPath}`);
 
         wss.on('connection', (ws) => {
-          wss.close();
-          server.close(done);
+          if (wss.clients.size === 1) {
+            assert.strictEqual(ws.upgradeReq.url, '/foo?bar=bar');
+          } else {
+            assert.strictEqual(ws.upgradeReq.url, '/');
+            wss.close();
+            server.close(done);
+          }
         });
+
+        const ws = new WebSocket(`ws+unix://${sockPath}:/foo?bar=bar`);
+        ws.on('open', () => new WebSocket(`ws+unix://${sockPath}`));
       });
     });
 

From 5bccfe59252923b85e7cffd56e3ddc1bd742f378 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Sat, 18 Feb 2017 08:14:24 +0100
Subject: [PATCH 282/669] [dist] 2.1.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 17d91b579..8f510e39f 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "author": "Einar Otto Stangvik  (http://2x.io)",
   "name": "ws",
   "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
-  "version": "2.0.3",
+  "version": "2.1.0",
   "license": "MIT",
   "main": "index.js",
   "keywords": [

From ca4a6226c311c30d02344d361dce5984ec9a2a07 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Mon, 20 Feb 2017 15:48:55 +0100
Subject: [PATCH 283/669] [minor] Avoid using deprecated APIs

---
 lib/Receiver.js              | 2 +-
 test/WebSocket.test.js       | 2 +-
 test/WebSocketServer.test.js | 5 +----
 3 files changed, 3 insertions(+), 6 deletions(-)

diff --git a/lib/Receiver.js b/lib/Receiver.js
index 71da72c7e..f7dfcfda6 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -85,7 +85,7 @@ class Receiver {
       return dst;
     }
 
-    dst = new Buffer(bytes);
+    dst = Buffer.allocUnsafe(bytes);
 
     while (bytes > 0) {
       l = this.buffers[0].length;
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index f7ea03d90..b64c89283 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -194,7 +194,7 @@ describe('WebSocket', function () {
           assert.ok(req.headers.authorization);
           assert.strictEqual(
             req.headers.authorization,
-            `Basic ${new Buffer(auth).toString('base64')}`
+            `Basic ${Buffer.from(auth).toString('base64')}`
           );
 
           wss.close();
diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js
index 72481c996..afb3ded9c 100644
--- a/test/WebSocketServer.test.js
+++ b/test/WebSocketServer.test.js
@@ -126,10 +126,7 @@ describe('WebSocketServer', function () {
 
       const ws = new WebSocket(`ws://localhost:${port}/`);
 
-      ws.onopen = () => {
-        ws._socket.write(new Buffer([5]));
-        ws.send('');
-      };
+      ws.onopen = () => ws._socket.write(Buffer.from([0x85, 0x00]));
     });
   });
 

From bfa575541244405ed32094ee29c80ab0c92274df Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Mon, 20 Feb 2017 18:21:49 +0100
Subject: [PATCH 284/669] chore(package): update eslint to version 3.16.0
 (#1012)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 8f510e39f..a3dca315a 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
   "devDependencies": {
     "benchmark": "~2.1.2",
     "bufferutil": "~2.0.0",
-    "eslint": "~3.15.0",
+    "eslint": "~3.16.0",
     "eslint-config-semistandard": "~7.0.0",
     "eslint-config-standard": "~6.2.1",
     "eslint-plugin-promise": "~3.4.0",

From e94a6ab386b2ec841a7e5279d8b425509a450bc1 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 21 Feb 2017 09:36:24 +0100
Subject: [PATCH 285/669] [minor] Inline {BufferUtil,Validation}.fallback.js
 into parent modules

---
 lib/BufferUtil.fallback.js | 56 --------------------------------------
 lib/BufferUtil.js          | 49 ++++++++++++++++++++++++++++++++-
 lib/Validation.fallback.js |  9 ------
 lib/Validation.js          |  2 +-
 4 files changed, 49 insertions(+), 67 deletions(-)
 delete mode 100644 lib/BufferUtil.fallback.js
 delete mode 100644 lib/Validation.fallback.js

diff --git a/lib/BufferUtil.fallback.js b/lib/BufferUtil.fallback.js
deleted file mode 100644
index 5fcde1c3f..000000000
--- a/lib/BufferUtil.fallback.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/*!
- * ws: a node.js websocket client
- * Copyright(c) 2011 Einar Otto Stangvik 
- * MIT Licensed
- */
-
-'use strict';
-
-/**
- * Merges an array of buffers into a target buffer.
- *
- * @param {Buffer} target The target buffer
- * @param {Buffer[]} buffers The array of buffers to merge
- * @public
- */
-const merge = (target, buffers) => {
-  var offset = 0;
-  for (var i = 0; i < buffers.length; i++) {
-    const buf = buffers[i];
-    buf.copy(target, offset);
-    offset += buf.length;
-  }
-};
-
-/**
- * Masks a buffer using the given mask.
- *
- * @param {Buffer} source The buffer to mask
- * @param {Buffer} mask The mask to use
- * @param {Buffer} output The buffer where to store the result
- * @param {Number} offset The offset at which to start writing
- * @param {Number} length The number of bytes to mask.
- * @public
- */
-const mask = (source, mask, output, offset, length) => {
-  for (var i = 0; i < length; i++) {
-    output[offset + i] = source[i] ^ mask[i & 3];
-  }
-};
-
-/**
- * Unmasks a buffer using the given mask.
- *
- * @param {Buffer} buffer The buffer to unmask
- * @param {Buffer} mask The mask to use
- * @public
- */
-const unmask = (buffer, mask) => {
-  // Required until https://github.com/nodejs/node/issues/9006 is resolved.
-  const length = buffer.length;
-  for (var i = 0; i < length; i++) {
-    buffer[i] ^= mask[i & 3];
-  }
-};
-
-module.exports = { merge, mask, unmask };
diff --git a/lib/BufferUtil.js b/lib/BufferUtil.js
index 7220fbc86..526654d42 100644
--- a/lib/BufferUtil.js
+++ b/lib/BufferUtil.js
@@ -11,5 +11,52 @@ try {
 
   module.exports = bufferUtil.BufferUtil || bufferUtil;
 } catch (e) {
-  module.exports = require('./BufferUtil.fallback');
+  /**
+   * Merges an array of buffers into a target buffer.
+   *
+   * @param {Buffer} target The target buffer
+   * @param {Buffer[]} buffers The array of buffers to merge
+   * @public
+   */
+  const merge = (target, buffers) => {
+    var offset = 0;
+    for (var i = 0; i < buffers.length; i++) {
+      const buf = buffers[i];
+      buf.copy(target, offset);
+      offset += buf.length;
+    }
+  };
+
+  /**
+   * Masks a buffer using the given mask.
+   *
+   * @param {Buffer} source The buffer to mask
+   * @param {Buffer} mask The mask to use
+   * @param {Buffer} output The buffer where to store the result
+   * @param {Number} offset The offset at which to start writing
+   * @param {Number} length The number of bytes to mask.
+   * @public
+   */
+  const mask = (source, mask, output, offset, length) => {
+    for (var i = 0; i < length; i++) {
+      output[offset + i] = source[i] ^ mask[i & 3];
+    }
+  };
+
+  /**
+   * Unmasks a buffer using the given mask.
+   *
+   * @param {Buffer} buffer The buffer to unmask
+   * @param {Buffer} mask The mask to use
+   * @public
+   */
+  const unmask = (buffer, mask) => {
+    // Required until https://github.com/nodejs/node/issues/9006 is resolved.
+    const length = buffer.length;
+    for (var i = 0; i < length; i++) {
+      buffer[i] ^= mask[i & 3];
+    }
+  };
+
+  module.exports = { merge, mask, unmask };
 }
diff --git a/lib/Validation.fallback.js b/lib/Validation.fallback.js
deleted file mode 100644
index 60663149a..000000000
--- a/lib/Validation.fallback.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/*!
- * ws: a node.js websocket client
- * Copyright(c) 2011 Einar Otto Stangvik 
- * MIT Licensed
- */
-
-'use strict';
-
-module.exports = () => true;
diff --git a/lib/Validation.js b/lib/Validation.js
index 7fa5f5084..f0380555c 100644
--- a/lib/Validation.js
+++ b/lib/Validation.js
@@ -13,5 +13,5 @@ try {
     ? isValidUTF8.Validation.isValidUTF8  // utf-8-validate@<3.0.0
     : isValidUTF8;
 } catch (e) {
-  module.exports = require('./Validation.fallback');
+  module.exports = () => true;
 }

From 8127ebc7c1eb86a1938685c5b8a70f8c4046bed0 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 21 Feb 2017 10:03:07 +0100
Subject: [PATCH 286/669] [minor] Move shared constants into lib/Constants.js

---
 lib/Constants.js       | 5 +++++
 lib/Receiver.js        | 7 +++----
 lib/WebSocket.js       | 7 +++----
 lib/WebSocketServer.js | 5 ++---
 4 files changed, 13 insertions(+), 11 deletions(-)
 create mode 100644 lib/Constants.js

diff --git a/lib/Constants.js b/lib/Constants.js
new file mode 100644
index 000000000..732df7429
--- /dev/null
+++ b/lib/Constants.js
@@ -0,0 +1,5 @@
+'use strict';
+
+exports.GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
+exports.EMPTY_BUFFER = Buffer.alloc(0);
+exports.NOOP = () => {};
diff --git a/lib/Receiver.js b/lib/Receiver.js
index f7dfcfda6..b43c36022 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -10,8 +10,7 @@ const PerMessageDeflate = require('./PerMessageDeflate');
 const isValidUTF8 = require('./Validation');
 const bufferUtil = require('./BufferUtil');
 const ErrorCodes = require('./ErrorCodes');
-
-const EMPTY_BUFFER = Buffer.alloc(0);
+const constants = require('./Constants');
 
 const GET_INFO = 0;
 const GET_PAYLOAD_LENGTH_16 = 1;
@@ -307,7 +306,7 @@ class Receiver {
    * @private
    */
   getData () {
-    var data = EMPTY_BUFFER;
+    var data = constants.EMPTY_BUFFER;
 
     if (this.payloadLength) {
       if (!this.hasBufferedBytes(this.payloadLength)) return;
@@ -357,7 +356,7 @@ class Receiver {
         ? Buffer.concat(this.fragments, this.messageLength)
         : this.fragments.length === 1
           ? this.fragments[0]
-          : EMPTY_BUFFER;
+          : constants.EMPTY_BUFFER;
 
       this.totalPayloadLength = 0;
       this.fragments.length = 0;
diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index 2bfd1653d..22ef9d40c 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -16,13 +16,12 @@ const url = require('url');
 const PerMessageDeflate = require('./PerMessageDeflate');
 const EventTarget = require('./EventTarget');
 const Extensions = require('./Extensions');
+const constants = require('./Constants');
 const Receiver = require('./Receiver');
 const Sender = require('./Sender');
 
-const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
 const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly.
 const protocolVersion = 13;
-const noop = () => {};
 
 /**
  * Class representing a WebSocket.
@@ -230,7 +229,7 @@ class WebSocket extends EventEmitter {
     this.extensions = null;
 
     this.removeAllListeners();
-    this.on('error', noop); // Catch all errors after this.
+    this.on('error', constants.NOOP); // Catch all errors after this.
   }
 
   /**
@@ -662,7 +661,7 @@ function initAsClient (address, protocols, options) {
     this._req = null;
 
     const digest = crypto.createHash('sha1')
-      .update(key + GUID, 'binary')
+      .update(key + constants.GUID, 'binary')
       .digest('base64');
 
     if (res.headers['sec-websocket-accept'] !== digest) {
diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js
index 1f812eb6e..1499a2cdc 100644
--- a/lib/WebSocketServer.js
+++ b/lib/WebSocketServer.js
@@ -14,10 +14,9 @@ const url = require('url');
 
 const PerMessageDeflate = require('./PerMessageDeflate');
 const Extensions = require('./Extensions');
+const constants = require('./Constants');
 const WebSocket = require('./WebSocket');
 
-const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
-
 /**
  * Class representing a WebSocket server.
  *
@@ -215,7 +214,7 @@ class WebSocketServer extends EventEmitter {
     if (!socket.readable || !socket.writable) return socket.destroy();
 
     const key = crypto.createHash('sha1')
-      .update(req.headers['sec-websocket-key'] + GUID, 'binary')
+      .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary')
       .digest('base64');
 
     const headers = [

From 5edb4601a71202cc9bf7bf0a173145cbe62a9a1c Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 21 Feb 2017 11:08:21 +0100
Subject: [PATCH 287/669] [fix] Use a random masking key also for zero-length
 frames

---
 lib/Sender.js          | 21 ++++--------------
 lib/WebSocket.js       | 11 +++++-----
 test/Sender.test.js    | 48 ------------------------------------------
 test/WebSocket.test.js |  2 +-
 4 files changed, 10 insertions(+), 72 deletions(-)

diff --git a/lib/Sender.js b/lib/Sender.js
index ee520cdda..2dd1ec96c 100644
--- a/lib/Sender.js
+++ b/lib/Sender.js
@@ -90,7 +90,7 @@ class Sender {
   ping (data, mask) {
     var readOnly = true;
 
-    if (data && !Buffer.isBuffer(data)) {
+    if (!Buffer.isBuffer(data)) {
       if (data instanceof ArrayBuffer) {
         data = Buffer.from(data);
       } else if (ArrayBuffer.isView(data)) {
@@ -136,7 +136,7 @@ class Sender {
   pong (data, mask) {
     var readOnly = true;
 
-    if (data && !Buffer.isBuffer(data)) {
+    if (!Buffer.isBuffer(data)) {
       if (data instanceof ArrayBuffer) {
         data = Buffer.from(data);
       } else if (ArrayBuffer.isView(data)) {
@@ -189,7 +189,7 @@ class Sender {
     var rsv1 = options.compress;
     var readOnly = true;
 
-    if (data && !Buffer.isBuffer(data)) {
+    if (!Buffer.isBuffer(data)) {
       if (data instanceof ArrayBuffer) {
         data = Buffer.from(data);
       } else if (ArrayBuffer.isView(data)) {
@@ -202,7 +202,7 @@ class Sender {
 
     if (this.firstFragment) {
       this.firstFragment = false;
-      if (rsv1 && data && this.perMessageDeflate) {
+      if (rsv1 && this.perMessageDeflate) {
         rsv1 = data.length >= this.perMessageDeflate.threshold;
       }
       this.compress = rsv1;
@@ -288,19 +288,6 @@ class Sender {
    * @private
    */
   frameAndSend (data, options, cb) {
-    if (!data) {
-      const bytes = [options.opcode, 0];
-
-      if (options.fin) bytes[0] |= 0x80;
-      if (options.mask) {
-        bytes[1] |= 0x80;
-        bytes.push(0, 0, 0, 0);
-      }
-
-      sendFramedData(this, Buffer.from(bytes), null, cb);
-      return;
-    }
-
     const mergeBuffers = data.length < 1024 || options.mask && options.readOnly;
     var dataOffset = options.mask ? 6 : 2;
     var payloadLength = data.length;
diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index 22ef9d40c..c0800adfe 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -310,7 +310,7 @@ class WebSocket extends EventEmitter {
 
     if (typeof data === 'number') data = data.toString();
     if (mask === undefined) mask = !this._isServer;
-    this._sender.ping(data, mask);
+    this._sender.ping(data || constants.EMPTY_BUFFER, mask);
   }
 
   /**
@@ -329,7 +329,7 @@ class WebSocket extends EventEmitter {
 
     if (typeof data === 'number') data = data.toString();
     if (mask === undefined) mask = !this._isServer;
-    this._sender.pong(data, mask);
+    this._sender.pong(data || constants.EMPTY_BUFFER, mask);
   }
 
   /**
@@ -357,20 +357,19 @@ class WebSocket extends EventEmitter {
     }
 
     if (typeof data === 'number') data = data.toString();
-    else if (!data) data = '';
 
     const opts = Object.assign({
-      fin: true,
       binary: typeof data !== 'string',
       mask: !this._isServer,
-      compress: true
+      compress: true,
+      fin: true
     }, options);
 
     if (!this.extensions[PerMessageDeflate.extensionName]) {
       opts.compress = false;
     }
 
-    this._sender.send(data, opts, cb);
+    this._sender.send(data || constants.EMPTY_BUFFER, opts, cb);
   }
 
   /**
diff --git a/test/Sender.test.js b/test/Sender.test.js
index d6454b47a..da511051a 100644
--- a/test/Sender.test.js
+++ b/test/Sender.test.js
@@ -178,30 +178,6 @@ describe('Sender', function () {
       sender.send('123', { compress: true, fin: true });
     });
 
-    it('compresses null as first fragment', function (done) {
-      const fragments = [];
-      const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
-      const sender = new Sender({
-        write: (data) => {
-          fragments.push(data);
-          if (fragments.length !== 2) return;
-
-          assert.strictEqual(fragments[0][0] & 0x40, 0x40);
-          assert.strictEqual(fragments[0].length, 3);
-          assert.strictEqual(fragments[1][0] & 0x40, 0x00);
-          assert.strictEqual(fragments[1].length, 8);
-          done();
-        }
-      }, {
-        'permessage-deflate': perMessageDeflate
-      });
-
-      perMessageDeflate.accept([{}]);
-
-      sender.send(null, { compress: true, fin: false });
-      sender.send('data', { compress: true, fin: true });
-    });
-
     it('compresses empty buffer as first fragment', function (done) {
       const fragments = [];
       const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
@@ -226,30 +202,6 @@ describe('Sender', function () {
       sender.send('data', { compress: true, fin: true });
     });
 
-    it('compresses null last fragment', function (done) {
-      const fragments = [];
-      const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
-      const sender = new Sender({
-        write: (data) => {
-          fragments.push(data);
-          if (fragments.length !== 2) return;
-
-          assert.strictEqual(fragments[0][0] & 0x40, 0x40);
-          assert.strictEqual(fragments[0].length, 12);
-          assert.strictEqual(fragments[1][0] & 0x40, 0x00);
-          assert.strictEqual(fragments[1].length, 3);
-          done();
-        }
-      }, {
-        'permessage-deflate': perMessageDeflate
-      });
-
-      perMessageDeflate.accept([{}]);
-
-      sender.send('data', { compress: true, fin: false });
-      sender.send(null, { compress: true, fin: true });
-    });
-
     it('compresses empty buffer as last fragment', function (done) {
       const fragments = [];
       const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index b64c89283..8c336c9a3 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -811,7 +811,7 @@ describe('WebSocket', function () {
         ws.on('open', () => ws.send());
 
         srv.on('message', (message, flags) => {
-          assert.strictEqual(message, '');
+          assert.ok(message.equals(Buffer.alloc(0)));
           srv.close(done);
           ws.terminate();
         });

From eed0814853dbd4ac9b1ca3a29057121c8a0a703f Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 21 Feb 2017 11:42:01 +0100
Subject: [PATCH 288/669] [minor] Remove no longer needed `if` statements

---
 lib/Sender.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/Sender.js b/lib/Sender.js
index 2dd1ec96c..b3e6cd730 100644
--- a/lib/Sender.js
+++ b/lib/Sender.js
@@ -345,7 +345,7 @@ class Sender {
     while (!this.deflating && this.queue.length) {
       const params = this.queue.shift();
 
-      if (params[1]) this.bufferedBytes -= params[1].length;
+      this.bufferedBytes -= params[1].length;
       params[0].apply(this, params.slice(1));
     }
   }
@@ -357,7 +357,7 @@ class Sender {
    * @private
    */
   enqueue (params) {
-    if (params[1]) this.bufferedBytes += params[1].length;
+    this.bufferedBytes += params[1].length;
     this.queue.push(params);
   }
 }

From af8f00390c20bb0eaafae02be089eb268d893789 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Sun, 26 Feb 2017 16:13:39 +0100
Subject: [PATCH 289/669] [feature] Make `Sender#frameAndSend()` a static
 method (#1016)

---
 bench/sender.benchmark.js |  24 +++--
 lib/Sender.js             | 191 ++++++++++++++++++--------------------
 test/Sender.test.js       |  35 ++-----
 3 files changed, 109 insertions(+), 141 deletions(-)

diff --git a/bench/sender.benchmark.js b/bench/sender.benchmark.js
index a0f19a870..46132c190 100644
--- a/bench/sender.benchmark.js
+++ b/bench/sender.benchmark.js
@@ -33,19 +33,17 @@ const opts2 = {
 };
 
 const suite = new benchmark.Suite();
-var sender = new Sender();
-sender._socket = { write () {} };
-
-suite.add('frameAndSend, unmasked (64 B)', () => sender.frameAndSend(data1, opts1));
-suite.add('frameAndSend, masked (64 B)', () => sender.frameAndSend(data1, opts2));
-suite.add('frameAndSend, unmasked (16 KiB)', () => sender.frameAndSend(data2, opts1));
-suite.add('frameAndSend, masked (16 KiB)', () => sender.frameAndSend(data2, opts2));
-suite.add('frameAndSend, unmasked (64 KiB)', () => sender.frameAndSend(data3, opts1));
-suite.add('frameAndSend, masked (64 KiB)', () => sender.frameAndSend(data3, opts2));
-suite.add('frameAndSend, unmasked (200 KiB)', () => sender.frameAndSend(data4, opts1));
-suite.add('frameAndSend, masked (200 KiB)', () => sender.frameAndSend(data4, opts2));
-suite.add('frameAndSend, unmasked (1 MiB)', () => sender.frameAndSend(data5, opts1));
-suite.add('frameAndSend, masked (1 MiB)', () => sender.frameAndSend(data5, opts2));
+
+suite.add('frame, unmasked (64 B)', () => Sender.frame(data1, opts1));
+suite.add('frame, masked (64 B)', () => Sender.frame(data1, opts2));
+suite.add('frame, unmasked (16 KiB)', () => Sender.frame(data2, opts1));
+suite.add('frame, masked (16 KiB)', () => Sender.frame(data2, opts2));
+suite.add('frame, unmasked (64 KiB)', () => Sender.frame(data3, opts1));
+suite.add('frame, masked (64 KiB)', () => Sender.frame(data3, opts2));
+suite.add('frame, unmasked (200 KiB)', () => Sender.frame(data4, opts1));
+suite.add('frame, masked (200 KiB)', () => Sender.frame(data4, opts2));
+suite.add('frame, unmasked (1 MiB)', () => Sender.frame(data5, opts1));
+suite.add('frame, masked (1 MiB)', () => Sender.frame(data5, opts2));
 
 suite.on('cycle', (e) => console.log(e.target.toString()));
 
diff --git a/lib/Sender.js b/lib/Sender.js
index b3e6cd730..d36c7ba5a 100644
--- a/lib/Sender.js
+++ b/lib/Sender.js
@@ -36,6 +36,71 @@ class Sender {
     this.onerror = null;
   }
 
+  /**
+   * Frames a piece of data according to the HyBi WebSocket protocol.
+   *
+   * @param {Buffer} data The data to frame
+   * @param {Object} options Options object
+   * @param {Number} options.opcode The opcode
+   * @param {Boolean} options.readOnly Specifies whether `data` can be modified
+   * @param {Boolean} options.fin Specifies whether or not to set the FIN bit
+   * @param {Boolean} options.mask Specifies whether or not to mask `data`
+   * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
+   * @return {Buffer[]} The framed data as a list of `Buffer` instances
+   * @public
+   */
+  static frame (data, options) {
+    const merge = data.length < 1024 || options.mask && options.readOnly;
+    var offset = options.mask ? 6 : 2;
+    var payloadLength = data.length;
+
+    if (data.length >= 65536) {
+      offset += 8;
+      payloadLength = 127;
+    } else if (data.length > 125) {
+      offset += 2;
+      payloadLength = 126;
+    }
+
+    const target = Buffer.allocUnsafe(merge ? data.length + offset : offset);
+
+    target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
+    if (options.rsv1) target[0] |= 0x40;
+
+    if (payloadLength === 126) {
+      target.writeUInt16BE(data.length, 2, true);
+    } else if (payloadLength === 127) {
+      target.writeUInt32BE(0, 2, true);
+      target.writeUInt32BE(data.length, 6, true);
+    }
+
+    if (!options.mask) {
+      target[1] = payloadLength;
+      if (merge) {
+        data.copy(target, offset);
+        return [target];
+      }
+
+      return [target, data];
+    }
+
+    const mask = crypto.randomBytes(4);
+
+    target[1] = payloadLength | 0x80;
+    target[offset - 4] = mask[0];
+    target[offset - 3] = mask[1];
+    target[offset - 2] = mask[2];
+    target[offset - 1] = mask[3];
+
+    if (merge) {
+      bufferUtil.mask(data, mask, target, offset, data.length);
+      return [target];
+    }
+
+    bufferUtil.mask(data, mask, data, 0, data.length);
+    return [target, data];
+  }
+
   /**
    * Sends a close message to the other peer.
    *
@@ -71,13 +136,13 @@ class Sender {
    * @private
    */
   doClose (data, mask, cb) {
-    this.frameAndSend(data, {
+    this.sendFrame(Sender.frame(data, {
       readOnly: false,
       opcode: 0x08,
       rsv1: false,
       fin: true,
       mask
-    }, cb);
+    }), cb);
   }
 
   /**
@@ -117,13 +182,13 @@ class Sender {
    * @private
    */
   doPing (data, mask, readOnly) {
-    this.frameAndSend(data, {
+    this.sendFrame(Sender.frame(data, {
       opcode: 0x09,
       rsv1: false,
       fin: true,
       readOnly,
       mask
-    });
+    }));
   }
 
   /**
@@ -163,13 +228,13 @@ class Sender {
    * @private
    */
   doPong (data, mask, readOnly) {
-    this.frameAndSend(data, {
+    this.sendFrame(Sender.frame(data, {
       opcode: 0x0a,
       rsv1: false,
       fin: true,
       readOnly,
       mask
-    });
+    }));
   }
 
   /**
@@ -229,13 +294,13 @@ class Sender {
         this.dispatch(data, opts, cb);
       }
     } else {
-      this.frameAndSend(data, {
+      this.sendFrame(Sender.frame(data, {
         mask: options.mask,
         fin: options.fin,
         rsv1: false,
         readOnly,
         opcode
-      }, cb);
+      }), cb);
     }
   }
 
@@ -255,7 +320,7 @@ class Sender {
    */
   dispatch (data, options, cb) {
     if (!options.compress) {
-      this.frameAndSend(data, options, cb);
+      this.sendFrame(Sender.frame(data, options), cb);
       return;
     }
 
@@ -268,74 +333,12 @@ class Sender {
       }
 
       options.readOnly = false;
-      this.frameAndSend(buf, options, cb);
+      this.sendFrame(Sender.frame(buf, options), cb);
       this.deflating = false;
       this.dequeue();
     });
   }
 
-  /**
-   * Frames and sends a piece of data according to the HyBi WebSocket protocol.
-   *
-   * @param {Buffer} data The data to send
-   * @param {Object} options Options object
-   * @param {Number} options.opcode The opcode
-   * @param {Boolean} options.readOnly Specifies whether `data` can be modified
-   * @param {Boolean} options.fin Specifies whether or not to set the FIN bit
-   * @param {Boolean} options.mask Specifies whether or not to mask `data`
-   * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
-   * @param {Function} cb Callback
-   * @private
-   */
-  frameAndSend (data, options, cb) {
-    const mergeBuffers = data.length < 1024 || options.mask && options.readOnly;
-    var dataOffset = options.mask ? 6 : 2;
-    var payloadLength = data.length;
-
-    if (data.length >= 65536) {
-      dataOffset += 8;
-      payloadLength = 127;
-    } else if (data.length > 125) {
-      dataOffset += 2;
-      payloadLength = 126;
-    }
-
-    const outputBuffer = Buffer.allocUnsafe(
-      mergeBuffers ? data.length + dataOffset : dataOffset
-    );
-
-    outputBuffer[0] = options.fin ? options.opcode | 0x80 : options.opcode;
-    if (options.rsv1) outputBuffer[0] |= 0x40;
-
-    if (payloadLength === 126) {
-      outputBuffer.writeUInt16BE(data.length, 2, true);
-    } else if (payloadLength === 127) {
-      outputBuffer.writeUInt32BE(0, 2, true);
-      outputBuffer.writeUInt32BE(data.length, 6, true);
-    }
-
-    if (options.mask) {
-      const mask = getRandomMask();
-
-      outputBuffer[1] = payloadLength | 0x80;
-      outputBuffer[dataOffset - 4] = mask[0];
-      outputBuffer[dataOffset - 3] = mask[1];
-      outputBuffer[dataOffset - 2] = mask[2];
-      outputBuffer[dataOffset - 1] = mask[3];
-
-      if (mergeBuffers) {
-        bufferUtil.mask(data, mask, outputBuffer, dataOffset, data.length);
-      } else {
-        bufferUtil.mask(data, mask, data, 0, data.length);
-      }
-    } else {
-      outputBuffer[1] = payloadLength;
-      if (mergeBuffers) data.copy(outputBuffer, dataOffset);
-    }
-
-    sendFramedData(this, outputBuffer, mergeBuffers ? null : data, cb);
-  }
-
   /**
    * Executes queued send operations.
    *
@@ -360,6 +363,22 @@ class Sender {
     this.bufferedBytes += params[1].length;
     this.queue.push(params);
   }
+
+  /**
+   * Sends a frame.
+   *
+   * @param {Buffer[]} list The frame to send
+   * @param {Function} cb Callback
+   * @private
+   */
+  sendFrame (list, cb) {
+    if (list.length === 2) {
+      this._socket.write(list[0]);
+      this._socket.write(list[1], cb);
+    } else {
+      this._socket.write(list[0], cb);
+    }
+  }
 }
 
 module.exports = Sender;
@@ -380,31 +399,3 @@ function viewToBuffer (view) {
 
   return buf;
 }
-
-/**
- * Generates a random mask.
- *
- * @return {Buffer} The mask
- * @private
- */
-function getRandomMask () {
-  return crypto.randomBytes(4);
-}
-
-/**
- * Sends a frame.
- *
- * @param {Sender} sender Sender instance
- * @param {Buffer} outputBuffer The data to send
- * @param {Buffer} data Additional data to send if frame is split into two buffers
- * @param {Function} cb Callback
- * @private
- */
-function sendFramedData (sender, outputBuffer, data, cb) {
-  if (data) {
-    sender._socket.write(outputBuffer);
-    sender._socket.write(data, cb);
-  } else {
-    sender._socket.write(outputBuffer, cb);
-  }
-}
diff --git a/test/Sender.test.js b/test/Sender.test.js
index da511051a..7c60c1e8b 100644
--- a/test/Sender.test.js
+++ b/test/Sender.test.js
@@ -6,12 +6,11 @@ const PerMessageDeflate = require('../lib/PerMessageDeflate');
 const Sender = require('../lib/Sender');
 
 describe('Sender', function () {
-  describe('#frameAndSend', function () {
-    it('does not modify a masked binary buffer', function () {
-      const sender = new Sender({ write: () => {} });
+  describe('.frame', function () {
+    it('does not mutate the input buffer if data is `readOnly`', function () {
       const buf = Buffer.from([1, 2, 3, 4, 5]);
 
-      sender.frameAndSend(buf, {
+      Sender.frame(buf, {
         readOnly: true,
         rsv1: false,
         mask: true,
@@ -22,36 +21,16 @@ describe('Sender', function () {
       assert.ok(buf.equals(Buffer.from([1, 2, 3, 4, 5])));
     });
 
-    it('does not modify a masked text buffer', function () {
-      const sender = new Sender({ write: () => {} });
-      const text = Buffer.from('hi there');
-
-      sender.frameAndSend(text, {
-        readOnly: true,
-        rsv1: false,
-        mask: true,
-        opcode: 1,
-        fin: true
-      });
-
-      assert.ok(text.equals(Buffer.from('hi there')));
-    });
-
-    it('sets RSV1 bit if compressed', function (done) {
-      const sender = new Sender({
-        write: (data) => {
-          assert.strictEqual(data[0] & 0x40, 0x40);
-          done();
-        }
-      });
-
-      sender.frameAndSend(Buffer.from('hi'), {
+    it('sets RSV1 bit if compressed', function () {
+      const list = Sender.frame(Buffer.from('hi'), {
         readOnly: false,
         mask: false,
         rsv1: true,
         opcode: 1,
         fin: true
       });
+
+      assert.strictEqual(list[0][0] & 0x40, 0x40);
     });
   });
 

From 30eccee554f1b653239abc8a9de93dd1a1c44179 Mon Sep 17 00:00:00 2001
From: Guy Margalit 
Date: Mon, 27 Feb 2017 10:06:51 +0200
Subject: [PATCH 290/669] [feature] Add "fragments" as possible value for
 `binaryType` (#1018)

---
 .gitignore             |  1 +
 doc/ws.md              |  5 ++-
 lib/Constants.js       |  2 ++
 lib/EventTarget.js     |  5 +--
 lib/Receiver.js        | 44 ++++++++++++++++++-----
 lib/WebSocket.js       | 19 ++++++----
 test/Receiver.test.js  | 82 ++++++++++++++++++++++++++++++++++++++++++
 test/WebSocket.test.js | 57 ++++++++++++++++++++++++++---
 8 files changed, 191 insertions(+), 24 deletions(-)

diff --git a/.gitignore b/.gitignore
index 4b173c692..1e80e9875 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 node_modules/
 coverage/
 npm-debug.log
+.vscode/
diff --git a/doc/ws.md b/doc/ws.md
index cdc6aee05..038ba7651 100644
--- a/doc/ws.md
+++ b/doc/ws.md
@@ -277,7 +277,10 @@ Register an event listener emulating the `EventTarget` interface.
 - {String}
 
 A string indicating the type of binary data being transmitted by the connection.
-This should be either "nodebuffer" or "arraybuffer". Defaults to "nodebuffer".
+This should be either "nodebuffer" or "arraybuffer" or "fragments". Defaults to "nodebuffer".
+Type "fragments" will emit the array of fragments as received from the sender,
+without copyfull concatenation, which is useful for the performance of binary protocols 
+transfering large messages with multiple fragments.
 
 ### websocket.bufferedAmount
 
diff --git a/lib/Constants.js b/lib/Constants.js
index 732df7429..b1ab31fb2 100644
--- a/lib/Constants.js
+++ b/lib/Constants.js
@@ -3,3 +3,5 @@
 exports.GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
 exports.EMPTY_BUFFER = Buffer.alloc(0);
 exports.NOOP = () => {};
+
+exports.BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments'];
diff --git a/lib/EventTarget.js b/lib/EventTarget.js
index 9720e4a26..e30b1b3ed 100644
--- a/lib/EventTarget.js
+++ b/lib/EventTarget.js
@@ -28,7 +28,7 @@ class MessageEvent extends Event {
   /**
    * Create a new `MessageEvent`.
    *
-   * @param {(String|Buffer|ArrayBuffer)} data The received data
+   * @param {(String|Buffer|ArrayBuffer|Buffer[])} data The received data
    * @param {Boolean} isBinary Specifies if `data` is binary
    * @param {WebSocket} target A reference to the target to which the event was dispatched
    */
@@ -100,9 +100,6 @@ const EventTarget = {
     if (typeof listener !== 'function') return;
 
     function onMessage (data, flags) {
-      if (flags.binary && this.binaryType === 'arraybuffer') {
-        data = new Uint8Array(data).buffer;
-      }
       listener.call(this, new MessageEvent(data, !!flags.binary, this));
     }
 
diff --git a/lib/Receiver.js b/lib/Receiver.js
index b43c36022..11d9efbf9 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -28,10 +28,12 @@ class Receiver {
    *
    * @param {Object} extensions An object containing the negotiated extensions
    * @param {Number} maxPayload The maximum allowed message length
+   * @param {String} binaryType The type for binary data, see constants.BINARY_TYPES
    */
-  constructor (extensions, maxPayload) {
+  constructor (extensions, maxPayload, binaryType) {
     this.extensions = extensions || {};
     this.maxPayload = maxPayload | 0;
+    this.binaryType = binaryType || constants.BINARY_TYPES[0];
 
     this.bufferedBytes = 0;
     this.buffers = [];
@@ -352,20 +354,28 @@ class Receiver {
    */
   dataMessage () {
     if (this.fin) {
-      const buf = this.fragments.length > 1
-        ? Buffer.concat(this.fragments, this.messageLength)
-        : this.fragments.length === 1
-          ? this.fragments[0]
-          : constants.EMPTY_BUFFER;
-
+      const fragments = this.fragments;
+      const messageLength = this.messageLength;
       this.totalPayloadLength = 0;
-      this.fragments.length = 0;
+      this.fragments = [];
       this.messageLength = 0;
       this.fragmented = 0;
 
       if (this.opcode === 2) {
-        this.onmessage(buf, { masked: this.masked, binary: true });
+        var data;
+
+        if (this.binaryType === 'nodebuffer') {
+          data = bufferFromFragments(fragments, messageLength);
+        } else if (this.binaryType === 'arraybuffer') {
+          data = new Uint8Array(bufferFromFragments(fragments, messageLength)).buffer;
+        } else {
+          data = fragments;
+        }
+
+        this.onmessage(data, { masked: this.masked, binary: true });
       } else {
+        const buf = bufferFromFragments(fragments, messageLength);
+
         if (!isValidUTF8(buf)) {
           this.error(new Error('invalid utf8 sequence'), 1007);
           return;
@@ -509,4 +519,20 @@ class Receiver {
   }
 }
 
+/**
+ * Make a buffer from a list of fragments.
+ * Optimized for the common case of a single fragment in order to
+ * avoid copyfull concat and simply return the fragment buffer.
+ *
+ * @param {Buffer[]} fragments
+ * @param {Number} messageLength
+ * @return {Buffer}
+ * @private
+ */
+function bufferFromFragments (fragments, messageLength) {
+  if (fragments.length === 1) return fragments[0];
+  if (fragments.length > 1) return Buffer.concat(fragments, messageLength);
+  return constants.EMPTY_BUFFER;
+}
+
 module.exports = Receiver;
diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index c0800adfe..642330ba6 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -53,7 +53,7 @@ class WebSocket extends EventEmitter {
     this.protocol = '';
 
     this._finalize = this.finalize.bind(this);
-    this._binaryType = 'nodebuffer';
+    this._binaryType = constants.BINARY_TYPES[0];
     this._finalizeCalled = false;
     this._closeMessage = null;
     this._closeTimer = null;
@@ -98,10 +98,17 @@ class WebSocket extends EventEmitter {
   }
 
   set binaryType (type) {
-    if (type === 'arraybuffer' || type === 'nodebuffer') {
-      this._binaryType = type;
-    } else {
-      throw new SyntaxError('unsupported binaryType: must be either "nodebuffer" or "arraybuffer"');
+    // silently ignore unsupported types
+    if (constants.BINARY_TYPES.indexOf(type) < 0) {
+      return;
+    }
+
+    this._binaryType = type;
+
+    // update the receiver if already created,
+    // if not then it will take the value once created
+    if (this._receiver) {
+      this._receiver.binaryType = type;
     }
   }
 
@@ -116,7 +123,7 @@ class WebSocket extends EventEmitter {
     socket.setTimeout(0);
     socket.setNoDelay();
 
-    this._receiver = new Receiver(this.extensions, this.maxPayload);
+    this._receiver = new Receiver(this.extensions, this.maxPayload, this.binaryType);
     this._sender = new Sender(socket, this.extensions);
     this._ultron = new Ultron(socket);
     this._socket = socket;
diff --git a/test/Receiver.test.js b/test/Receiver.test.js
index 1290388af..6d4e07710 100644
--- a/test/Receiver.test.js
+++ b/test/Receiver.test.js
@@ -5,6 +5,7 @@ const crypto = require('crypto');
 
 const PerMessageDeflate = require('../lib/PerMessageDeflate');
 const Receiver = require('../lib/Receiver');
+const Sender = require('../lib/Sender');
 const util = require('./hybi-util');
 
 describe('Receiver', function () {
@@ -827,4 +828,85 @@ describe('Receiver', function () {
       });
     });
   });
+
+  it('can emit nodebuffer of fragmented binary message', function (done) {
+    const p = new Receiver();
+    const frags = [
+      crypto.randomBytes(7321),
+      crypto.randomBytes(137),
+      crypto.randomBytes(285787),
+      crypto.randomBytes(3)
+    ];
+
+    p.binaryType = 'nodebuffer';
+    p.onmessage = function (data) {
+      assert.ok(Buffer.isBuffer(data));
+      assert.ok(data.equals(Buffer.concat(frags)));
+      done();
+    };
+
+    addBinaryFragments(p, frags);
+  });
+
+  it('can emit arraybuffer of fragmented binary message', function (done) {
+    const p = new Receiver();
+    const frags = [
+      crypto.randomBytes(19221),
+      crypto.randomBytes(954),
+      crypto.randomBytes(623987)
+    ];
+
+    p.binaryType = 'arraybuffer';
+    p.onmessage = function (data) {
+      assert.ok(data instanceof ArrayBuffer);
+      assert.ok(Buffer.from(data).equals(Buffer.concat(frags)));
+      done();
+    };
+
+    addBinaryFragments(p, frags);
+  });
+
+  it('can emit fragments of fragmented binary message', function (done) {
+    const p = new Receiver();
+    const frags = [
+      crypto.randomBytes(17),
+      crypto.randomBytes(419872),
+      crypto.randomBytes(83),
+      crypto.randomBytes(9928),
+      crypto.randomBytes(1)
+    ];
+
+    p.binaryType = 'fragments';
+    p.onmessage = function (data) {
+      assert.ok(Array.isArray(data));
+      assert.ok(data.length === frags.length);
+      for (let i = 0; i < frags.length; ++i) {
+        assert.ok(Buffer.isBuffer(data[i]));
+        assert.ok(frags[i].equals(data[i]));
+      }
+      done();
+    };
+
+    addBinaryFragments(p, frags);
+  });
 });
+
+/**
+ * Adds a list of binary fragments to the receiver prefixing each one
+ * with info byte and length.
+ *
+ * @param {Receiver} receiver
+ * @param {Buffer[]} frags
+ * @private
+ */
+function addBinaryFragments (receiver, frags) {
+  for (let i = 0; i < frags.length; ++i) {
+    Sender.frame(frags[i], {
+      opcode: i === 0 ? 2 : 0,
+      fin: i + 1 === frags.length,
+      mask: false,
+      rsv1: false,
+      readOnly: true
+    }).forEach(buf => receiver.add(buf));
+  }
+}
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index 8c336c9a3..b1bcb0fe4 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -1168,12 +1168,23 @@ describe('WebSocket', function () {
       assert.strictEqual(ws.onopen, listener);
     });
 
-    it('should throw an error when setting an invalid binary type', function () {
+    it('should ignore when setting an invalid binary type', function () {
       const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() });
 
-      assert.throws(() => {
-        ws.binaryType = 'foo';
-      }, /^SyntaxError: unsupported binaryType: must be either "nodebuffer" or "arraybuffer"$/);
+      ws.binaryType = 'nodebuffer';
+      assert.ok(ws.binaryType === 'nodebuffer');
+      ws.binaryType = 'foo';
+      assert.ok(ws.binaryType === 'nodebuffer');
+      ws.binaryType = 'arraybuffer';
+      assert.ok(ws.binaryType === 'arraybuffer');
+      ws.binaryType = '';
+      assert.ok(ws.binaryType === 'arraybuffer');
+      ws.binaryType = 'fragments';
+      assert.ok(ws.binaryType === 'fragments');
+      ws.binaryType = 'buffer';
+      assert.ok(ws.binaryType === 'fragments');
+      ws.binaryType = 'nodebuffer';
+      assert.ok(ws.binaryType === 'nodebuffer');
     });
 
     it('should work the same as the EventEmitter api', function (done) {
@@ -1420,6 +1431,44 @@ describe('WebSocket', function () {
         };
       });
     });
+
+    it('should allow to update binaryType after receiver created', function (done) {
+      server.createServer(++port, (srv) => {
+        const ws = new WebSocket(`ws://localhost:${port}`);
+
+        function testType (binaryType, callback) {
+          const buf = Buffer.from(binaryType);
+          ws.binaryType = binaryType;
+          ws.onmessage = (messageEvent) => {
+            if (binaryType === 'nodebuffer') {
+              assert.ok(Buffer.isBuffer(messageEvent.data));
+              assert.ok(messageEvent.data.equals(buf));
+            } else if (binaryType === 'arraybuffer') {
+              assert.ok(messageEvent.data instanceof ArrayBuffer);
+              assert.ok(Buffer.from(messageEvent.data).equals(buf));
+            } else if (binaryType === 'fragments') {
+              assert.ok(Array.isArray(messageEvent.data));
+              assert.ok(messageEvent.data.length === 1);
+              assert.ok(Buffer.from(messageEvent.data[0]).equals(buf));
+            }
+            callback();
+          };
+          ws.send(buf);
+        }
+
+        ws.onopen =
+          () => testType('nodebuffer',
+            () => testType('arraybuffer',
+              () => testType('fragments',
+                () => {
+                  srv.close(done);
+                  ws.terminate();
+                }
+              )
+            )
+          );
+      });
+    });
   });
 
   describe('ssl', function () {

From 6a734ae17473e28a712661e54808908f8a0bc530 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Mon, 27 Feb 2017 10:41:50 +0100
Subject: [PATCH 291/669] [minor] Fix nits

---
 doc/ws.md              |  8 +++---
 lib/Constants.js       |  3 +-
 lib/Receiver.js        | 30 ++++++++++----------
 lib/WebSocket.js       | 16 ++++-------
 test/Receiver.test.js  | 63 +++++++++++++++++++++---------------------
 test/WebSocket.test.js | 47 +++++++++++++++----------------
 6 files changed, 80 insertions(+), 87 deletions(-)

diff --git a/doc/ws.md b/doc/ws.md
index 038ba7651..3cc6c0a9e 100644
--- a/doc/ws.md
+++ b/doc/ws.md
@@ -277,10 +277,10 @@ Register an event listener emulating the `EventTarget` interface.
 - {String}
 
 A string indicating the type of binary data being transmitted by the connection.
-This should be either "nodebuffer" or "arraybuffer" or "fragments". Defaults to "nodebuffer".
-Type "fragments" will emit the array of fragments as received from the sender,
-without copyfull concatenation, which is useful for the performance of binary protocols 
-transfering large messages with multiple fragments.
+This should be one of "nodebuffer", "arraybuffer" or "fragments". Defaults to
+"nodebuffer". Type "fragments" will emit the array of fragments as received from
+the sender, without copyfull concatenation, which is useful for the performance
+of binary protocols transfering large messages with multiple fragments.
 
 ### websocket.bufferedAmount
 
diff --git a/lib/Constants.js b/lib/Constants.js
index b1ab31fb2..ce7ed989f 100644
--- a/lib/Constants.js
+++ b/lib/Constants.js
@@ -1,7 +1,6 @@
 'use strict';
 
+exports.BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments'];
 exports.GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
 exports.EMPTY_BUFFER = Buffer.alloc(0);
 exports.NOOP = () => {};
-
-exports.BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments'];
diff --git a/lib/Receiver.js b/lib/Receiver.js
index 11d9efbf9..906b1b0e9 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -28,12 +28,12 @@ class Receiver {
    *
    * @param {Object} extensions An object containing the negotiated extensions
    * @param {Number} maxPayload The maximum allowed message length
-   * @param {String} binaryType The type for binary data, see constants.BINARY_TYPES
+   * @param {String} binaryType The type for binary data
    */
   constructor (extensions, maxPayload, binaryType) {
+    this.binaryType = binaryType || constants.BINARY_TYPES[0];
     this.extensions = extensions || {};
     this.maxPayload = maxPayload | 0;
-    this.binaryType = binaryType || constants.BINARY_TYPES[0];
 
     this.bufferedBytes = 0;
     this.buffers = [];
@@ -354,27 +354,29 @@ class Receiver {
    */
   dataMessage () {
     if (this.fin) {
-      const fragments = this.fragments;
       const messageLength = this.messageLength;
+      const fragments = this.fragments;
+
       this.totalPayloadLength = 0;
-      this.fragments = [];
       this.messageLength = 0;
       this.fragmented = 0;
+      this.fragments = [];
 
       if (this.opcode === 2) {
         var data;
 
         if (this.binaryType === 'nodebuffer') {
-          data = bufferFromFragments(fragments, messageLength);
+          data = toBuffer(fragments, messageLength);
         } else if (this.binaryType === 'arraybuffer') {
-          data = new Uint8Array(bufferFromFragments(fragments, messageLength)).buffer;
+          const buf = toBuffer(fragments, messageLength);
+          data = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
         } else {
           data = fragments;
         }
 
         this.onmessage(data, { masked: this.masked, binary: true });
       } else {
-        const buf = bufferFromFragments(fragments, messageLength);
+        const buf = toBuffer(fragments, messageLength);
 
         if (!isValidUTF8(buf)) {
           this.error(new Error('invalid utf8 sequence'), 1007);
@@ -519,20 +521,18 @@ class Receiver {
   }
 }
 
+module.exports = Receiver;
+
 /**
- * Make a buffer from a list of fragments.
- * Optimized for the common case of a single fragment in order to
- * avoid copyfull concat and simply return the fragment buffer.
+ * Makes a buffer from a list of fragments.
  *
- * @param {Buffer[]} fragments
- * @param {Number} messageLength
+ * @param {Buffer[]} fragments The list of fragments composing the message
+ * @param {Number} messageLength The length of the message
  * @return {Buffer}
  * @private
  */
-function bufferFromFragments (fragments, messageLength) {
+function toBuffer (fragments, messageLength) {
   if (fragments.length === 1) return fragments[0];
   if (fragments.length > 1) return Buffer.concat(fragments, messageLength);
   return constants.EMPTY_BUFFER;
 }
-
-module.exports = Receiver;
diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index 642330ba6..24bd1bff7 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -52,8 +52,8 @@ class WebSocket extends EventEmitter {
     this.extensions = {};
     this.protocol = '';
 
-    this._finalize = this.finalize.bind(this);
     this._binaryType = constants.BINARY_TYPES[0];
+    this._finalize = this.finalize.bind(this);
     this._finalizeCalled = false;
     this._closeMessage = null;
     this._closeTimer = null;
@@ -98,18 +98,14 @@ class WebSocket extends EventEmitter {
   }
 
   set binaryType (type) {
-    // silently ignore unsupported types
-    if (constants.BINARY_TYPES.indexOf(type) < 0) {
-      return;
-    }
+    if (constants.BINARY_TYPES.indexOf(type) < 0) return;
 
     this._binaryType = type;
 
-    // update the receiver if already created,
-    // if not then it will take the value once created
-    if (this._receiver) {
-      this._receiver.binaryType = type;
-    }
+    //
+    // Allow to change `binaryType` on the fly.
+    //
+    if (this._receiver) this._receiver.binaryType = type;
   }
 
   /**
diff --git a/test/Receiver.test.js b/test/Receiver.test.js
index 6d4e07710..54948a2c8 100644
--- a/test/Receiver.test.js
+++ b/test/Receiver.test.js
@@ -839,13 +839,21 @@ describe('Receiver', function () {
     ];
 
     p.binaryType = 'nodebuffer';
-    p.onmessage = function (data) {
+    p.onmessage = (data) => {
       assert.ok(Buffer.isBuffer(data));
       assert.ok(data.equals(Buffer.concat(frags)));
       done();
     };
 
-    addBinaryFragments(p, frags);
+    frags.forEach((frag, i) => {
+      Sender.frame(frag, {
+        fin: i === frags.length - 1,
+        opcode: i === 0 ? 2 : 0,
+        readOnly: true,
+        mask: false,
+        rsv1: false
+      }).forEach((buf) => p.add(buf));
+    });
   });
 
   it('can emit arraybuffer of fragmented binary message', function (done) {
@@ -857,13 +865,21 @@ describe('Receiver', function () {
     ];
 
     p.binaryType = 'arraybuffer';
-    p.onmessage = function (data) {
+    p.onmessage = (data) => {
       assert.ok(data instanceof ArrayBuffer);
       assert.ok(Buffer.from(data).equals(Buffer.concat(frags)));
       done();
     };
 
-    addBinaryFragments(p, frags);
+    frags.forEach((frag, i) => {
+      Sender.frame(frag, {
+        fin: i === frags.length - 1,
+        opcode: i === 0 ? 2 : 0,
+        readOnly: true,
+        mask: false,
+        rsv1: false
+      }).forEach((buf) => p.add(buf));
+    });
   });
 
   it('can emit fragments of fragmented binary message', function (done) {
@@ -877,36 +893,19 @@ describe('Receiver', function () {
     ];
 
     p.binaryType = 'fragments';
-    p.onmessage = function (data) {
-      assert.ok(Array.isArray(data));
-      assert.ok(data.length === frags.length);
-      for (let i = 0; i < frags.length; ++i) {
-        assert.ok(Buffer.isBuffer(data[i]));
-        assert.ok(frags[i].equals(data[i]));
-      }
+    p.onmessage = (data) => {
+      assert.deepStrictEqual(data, frags);
       done();
     };
 
-    addBinaryFragments(p, frags);
+    frags.forEach((frag, i) => {
+      Sender.frame(frag, {
+        fin: i === frags.length - 1,
+        opcode: i === 0 ? 2 : 0,
+        readOnly: true,
+        mask: false,
+        rsv1: false
+      }).forEach((buf) => p.add(buf));
+    });
   });
 });
-
-/**
- * Adds a list of binary fragments to the receiver prefixing each one
- * with info byte and length.
- *
- * @param {Receiver} receiver
- * @param {Buffer[]} frags
- * @private
- */
-function addBinaryFragments (receiver, frags) {
-  for (let i = 0; i < frags.length; ++i) {
-    Sender.frame(frags[i], {
-      opcode: i === 0 ? 2 : 0,
-      fin: i + 1 === frags.length,
-      mask: false,
-      rsv1: false,
-      readOnly: true
-    }).forEach(buf => receiver.add(buf));
-  }
-}
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index b1bcb0fe4..867629d82 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -1172,19 +1172,19 @@ describe('WebSocket', function () {
       const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() });
 
       ws.binaryType = 'nodebuffer';
-      assert.ok(ws.binaryType === 'nodebuffer');
+      assert.strictEqual(ws.binaryType, 'nodebuffer');
       ws.binaryType = 'foo';
-      assert.ok(ws.binaryType === 'nodebuffer');
+      assert.strictEqual(ws.binaryType, 'nodebuffer');
       ws.binaryType = 'arraybuffer';
-      assert.ok(ws.binaryType === 'arraybuffer');
+      assert.strictEqual(ws.binaryType, 'arraybuffer');
       ws.binaryType = '';
-      assert.ok(ws.binaryType === 'arraybuffer');
+      assert.strictEqual(ws.binaryType, 'arraybuffer');
       ws.binaryType = 'fragments';
-      assert.ok(ws.binaryType === 'fragments');
+      assert.strictEqual(ws.binaryType, 'fragments');
       ws.binaryType = 'buffer';
-      assert.ok(ws.binaryType === 'fragments');
+      assert.strictEqual(ws.binaryType, 'fragments');
       ws.binaryType = 'nodebuffer';
-      assert.ok(ws.binaryType === 'nodebuffer');
+      assert.strictEqual(ws.binaryType, 'nodebuffer');
     });
 
     it('should work the same as the EventEmitter api', function (done) {
@@ -1432,13 +1432,14 @@ describe('WebSocket', function () {
       });
     });
 
-    it('should allow to update binaryType after receiver created', function (done) {
+    it('should allow to update binaryType on the fly', function (done) {
       server.createServer(++port, (srv) => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
-        function testType (binaryType, callback) {
+        function testType (binaryType, next) {
           const buf = Buffer.from(binaryType);
           ws.binaryType = binaryType;
+
           ws.onmessage = (messageEvent) => {
             if (binaryType === 'nodebuffer') {
               assert.ok(Buffer.isBuffer(messageEvent.data));
@@ -1447,26 +1448,24 @@ describe('WebSocket', function () {
               assert.ok(messageEvent.data instanceof ArrayBuffer);
               assert.ok(Buffer.from(messageEvent.data).equals(buf));
             } else if (binaryType === 'fragments') {
-              assert.ok(Array.isArray(messageEvent.data));
-              assert.ok(messageEvent.data.length === 1);
-              assert.ok(Buffer.from(messageEvent.data[0]).equals(buf));
+              assert.deepStrictEqual(messageEvent.data, [buf]);
             }
-            callback();
+            next();
           };
+
           ws.send(buf);
         }
 
-        ws.onopen =
-          () => testType('nodebuffer',
-            () => testType('arraybuffer',
-              () => testType('fragments',
-                () => {
-                  srv.close(done);
-                  ws.terminate();
-                }
-              )
-            )
-          );
+        ws.onopen = () => {
+          testType('nodebuffer', () => {
+            testType('arraybuffer', () => {
+              testType('fragments', () => {
+                srv.close(done);
+                ws.terminate();
+              });
+            });
+          });
+        };
       });
     });
   });

From 625e9bf76acbe11bf2be13951c8fcca7733477ac Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Mon, 27 Feb 2017 14:51:23 +0100
Subject: [PATCH 292/669] [perf] Optimize for cases where buffer is not a
 subarray

---
 lib/Receiver.js | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/lib/Receiver.js b/lib/Receiver.js
index 906b1b0e9..5f74fe246 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -368,8 +368,7 @@ class Receiver {
         if (this.binaryType === 'nodebuffer') {
           data = toBuffer(fragments, messageLength);
         } else if (this.binaryType === 'arraybuffer') {
-          const buf = toBuffer(fragments, messageLength);
-          data = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
+          data = toArrayBuffer(toBuffer(fragments, messageLength));
         } else {
           data = fragments;
         }
@@ -536,3 +535,17 @@ function toBuffer (fragments, messageLength) {
   if (fragments.length > 1) return Buffer.concat(fragments, messageLength);
   return constants.EMPTY_BUFFER;
 }
+
+/**
+ * Converts a buffer to an `ArrayBuffer`.
+ *
+ * @param {Buffer} The buffer to convert
+ * @return {ArrayBuffer} Converted buffer
+ */
+function toArrayBuffer (buf) {
+  if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {
+    return buf.buffer;
+  }
+
+  return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
+}

From ae42166ec5966e4924d5995c84514d58d206a7ea Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 28 Feb 2017 10:17:19 +0100
Subject: [PATCH 293/669] [dist] 2.2.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index a3dca315a..6a4b08967 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "author": "Einar Otto Stangvik  (http://2x.io)",
   "name": "ws",
   "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
-  "version": "2.1.0",
+  "version": "2.2.0",
   "license": "MIT",
   "main": "index.js",
   "keywords": [

From 703adf85ec732d1ae8fb9bea9f4cf02436942916 Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Wed, 1 Mar 2017 07:38:05 +0100
Subject: [PATCH 294/669] chore(package): update eslint-plugin-promise to
 version 3.5.0 (#1025)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 6a4b08967..9abfbdad7 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,7 @@
     "eslint": "~3.16.0",
     "eslint-config-semistandard": "~7.0.0",
     "eslint-config-standard": "~6.2.1",
-    "eslint-plugin-promise": "~3.4.0",
+    "eslint-plugin-promise": "~3.5.0",
     "eslint-plugin-standard": "~2.0.1",
     "istanbul": "~0.4.5",
     "mocha": "~3.2.0",

From f9b2bdfb7f82c7f49b48261ab6479ee40eb7e5fc Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Wed, 1 Mar 2017 07:48:48 +0100
Subject: [PATCH 295/669] [lint] Comply with the new standard rules

---
 bench/speed.js           | 2 +-
 lib/PerMessageDeflate.js | 2 +-
 lib/Receiver.js          | 2 +-
 lib/Sender.js            | 2 +-
 lib/WebSocketServer.js   | 2 +-
 5 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/bench/speed.js b/bench/speed.js
index 40c3f30cf..55047d5d8 100644
--- a/bench/speed.js
+++ b/bench/speed.js
@@ -69,7 +69,7 @@ if (cluster.isMaster) {
       if (++roundtrip !== roundtrips) return ws.send(data, { binary: useBinary });
 
       var elapsed = process.hrtime(time);
-      elapsed = elapsed[0] * 1e9 + elapsed[1];
+      elapsed = (elapsed[0] * 1e9) + elapsed[1];
 
       console.log(
         '%d roundtrips of %s %s data:\t%ss\t%s',
diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js
index 92a26525f..218bcdeb0 100644
--- a/lib/PerMessageDeflate.js
+++ b/lib/PerMessageDeflate.js
@@ -261,7 +261,7 @@ class PerMessageDeflate {
       this._inflate.writeInProgress = false;
 
       if (
-        fin && this.params[`${endpoint}_no_context_takeover`] ||
+        (fin && this.params[`${endpoint}_no_context_takeover`]) ||
         this._inflate.pendingClose
       ) {
         this._inflate.close();
diff --git a/lib/Receiver.js b/lib/Receiver.js
index 5f74fe246..670e72e78 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -272,7 +272,7 @@ class Receiver {
       return;
     }
 
-    this.payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4, true);
+    this.payloadLength = (num * Math.pow(2, 32)) + buf.readUInt32BE(4, true);
     this.haveLength();
   }
 
diff --git a/lib/Sender.js b/lib/Sender.js
index d36c7ba5a..0df22dbb8 100644
--- a/lib/Sender.js
+++ b/lib/Sender.js
@@ -50,7 +50,7 @@ class Sender {
    * @public
    */
   static frame (data, options) {
-    const merge = data.length < 1024 || options.mask && options.readOnly;
+    const merge = data.length < 1024 || (options.mask && options.readOnly);
     var offset = options.mask ? 6 : 2;
     var payloadLength = data.length;
 
diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js
index 1499a2cdc..5170375ba 100644
--- a/lib/WebSocketServer.js
+++ b/lib/WebSocketServer.js
@@ -153,7 +153,7 @@ class WebSocketServer extends EventEmitter {
 
     if (
       req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket' ||
-      !req.headers['sec-websocket-key'] || version !== 8 && version !== 13 ||
+      !req.headers['sec-websocket-key'] || (version !== 8 && version !== 13) ||
       !this.shouldHandle(req)
     ) {
       return abortConnection(socket, 400);

From 2aec9a4abe6f62116d09ab01a873ca486b39d2ac Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Wed, 1 Mar 2017 08:56:42 +0100
Subject: [PATCH 296/669] [lint] Remove eslint-config-semistandard dev
 dependency

---
 .eslintrc.yaml | 7 ++++++-
 package.json   | 1 -
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/.eslintrc.yaml b/.eslintrc.yaml
index 5b53411e0..2524511f9 100644
--- a/.eslintrc.yaml
+++ b/.eslintrc.yaml
@@ -1,3 +1,8 @@
-extends: semistandard
+extends: standard
 env:
   mocha: true
+rules:
+  no-extra-semi: error
+  semi:
+    - error
+    - always
diff --git a/package.json b/package.json
index 9abfbdad7..95483471c 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,6 @@
     "benchmark": "~2.1.2",
     "bufferutil": "~2.0.0",
     "eslint": "~3.16.0",
-    "eslint-config-semistandard": "~7.0.0",
     "eslint-config-standard": "~6.2.1",
     "eslint-plugin-promise": "~3.5.0",
     "eslint-plugin-standard": "~2.0.1",

From 944b788c2b01ac2ea874f48fa4b143be37deeeb4 Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Wed, 1 Mar 2017 09:16:01 +0100
Subject: [PATCH 297/669] chore(package): update eslint-config-standard to
 version 7.0.0 (#1024)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 95483471c..c47da7ccb 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
     "benchmark": "~2.1.2",
     "bufferutil": "~2.0.0",
     "eslint": "~3.16.0",
-    "eslint-config-standard": "~6.2.1",
+    "eslint-config-standard": "~7.0.0",
     "eslint-plugin-promise": "~3.5.0",
     "eslint-plugin-standard": "~2.0.1",
     "istanbul": "~0.4.5",

From a70c6d0a2b0f65583f79379835cd6d952a51262d Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Thu, 2 Mar 2017 09:05:14 +0100
Subject: [PATCH 298/669] chore(package): update eslint-plugin-standard to
 version 2.1.0 (#1027)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index c47da7ccb..1aed2bd8d 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,7 @@
     "eslint": "~3.16.0",
     "eslint-config-standard": "~7.0.0",
     "eslint-plugin-promise": "~3.5.0",
-    "eslint-plugin-standard": "~2.0.1",
+    "eslint-plugin-standard": "~2.1.0",
     "istanbul": "~0.4.5",
     "mocha": "~3.2.0",
     "utf-8-validate": "~3.0.0"

From 4ae630af7409ba830a043dcf7eff24f655923aa6 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Thu, 2 Mar 2017 15:43:30 +0100
Subject: [PATCH 299/669] [perf] Avoid using `Buffer.concat()` (#1026)

---
 lib/BufferUtil.js        | 41 ++++++++++++++++-------------
 lib/PerMessageDeflate.js | 56 +++++++++++++++++++++++-----------------
 lib/Receiver.js          |  2 +-
 3 files changed, 56 insertions(+), 43 deletions(-)

diff --git a/lib/BufferUtil.js b/lib/BufferUtil.js
index 526654d42..bb1004672 100644
--- a/lib/BufferUtil.js
+++ b/lib/BufferUtil.js
@@ -6,27 +6,32 @@
  * MIT Licensed
  */
 
+/**
+ * Merges an array of buffers into a new buffer.
+ *
+ * @param {Buffer[]} list The array of buffers to concat
+ * @param {Number} totalLength The total length of buffers in the list
+ * @return {Buffer} The resulting buffer
+ * @public
+ */
+const concat = (list, totalLength) => {
+  const target = Buffer.allocUnsafe(totalLength);
+  var offset = 0;
+
+  for (var i = 0; i < list.length; i++) {
+    const buf = list[i];
+    buf.copy(target, offset);
+    offset += buf.length;
+  }
+
+  return target;
+};
+
 try {
   const bufferUtil = require('bufferutil');
 
-  module.exports = bufferUtil.BufferUtil || bufferUtil;
+  module.exports = Object.assign({ concat }, bufferUtil.BufferUtil || bufferUtil);
 } catch (e) {
-  /**
-   * Merges an array of buffers into a target buffer.
-   *
-   * @param {Buffer} target The target buffer
-   * @param {Buffer[]} buffers The array of buffers to merge
-   * @public
-   */
-  const merge = (target, buffers) => {
-    var offset = 0;
-    for (var i = 0; i < buffers.length; i++) {
-      const buf = buffers[i];
-      buf.copy(target, offset);
-      offset += buf.length;
-    }
-  };
-
   /**
    * Masks a buffer using the given mask.
    *
@@ -58,5 +63,5 @@ try {
     }
   };
 
-  module.exports = { merge, mask, unmask };
+  module.exports = { concat, mask, unmask };
 }
diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js
index 218bcdeb0..68e2ed384 100644
--- a/lib/PerMessageDeflate.js
+++ b/lib/PerMessageDeflate.js
@@ -2,6 +2,8 @@
 
 const zlib = require('zlib');
 
+const bufferUtil = require('./BufferUtil');
+
 const AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15];
 const DEFAULT_WINDOW_BITS = 15;
 const DEFAULT_MEM_LEVEL = 8;
@@ -9,7 +11,7 @@ const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
 const EMPTY_BLOCK = Buffer.from([0x00]);
 
 /**
- * Per-message Compression Extensions implementation
+ * Per-message Deflate implementation.
  */
 class PerMessageDeflate {
   constructor (options, isServer, maxPayload) {
@@ -22,12 +24,15 @@ class PerMessageDeflate {
     this.threshold = this._options.threshold === undefined ? 1024 : this._options.threshold;
   }
 
+  static get extensionName () {
+    return 'permessage-deflate';
+  }
+
   /**
    * Create extension parameters offer
    *
-   * @api public
+   * @public
    */
-
   offer () {
     var params = {};
     if (this._options.serverNoContextTakeover) {
@@ -50,7 +55,7 @@ class PerMessageDeflate {
   /**
    * Accept extension offer
    *
-   * @api public
+   * @public
    */
   accept (paramsList) {
     paramsList = this.normalizeParams(paramsList);
@@ -69,7 +74,7 @@ class PerMessageDeflate {
   /**
    * Releases all resources used by the extension
    *
-   * @api public
+   * @public
    */
   cleanup () {
     if (this._inflate) {
@@ -93,9 +98,8 @@ class PerMessageDeflate {
   /**
    * Accept extension offer from client
    *
-   * @api private
+   * @private
    */
-
   acceptAsServer (paramsList) {
     var accepted = {};
     var result = paramsList.some((params) => {
@@ -147,9 +151,8 @@ class PerMessageDeflate {
   /**
    * Accept extension response from server
    *
-   * @api privaye
+   * @private
    */
-
   acceptAsClient (paramsList) {
     var params = paramsList[0];
     if (this._options.clientNoContextTakeover != null) {
@@ -172,9 +175,8 @@ class PerMessageDeflate {
   /**
    * Normalize extensions parameters
    *
-   * @api private
+   * @private
    */
-
   normalizeParams (paramsList) {
     return paramsList.map((params) => {
       Object.keys(params).forEach((key) => {
@@ -276,26 +278,25 @@ class PerMessageDeflate {
     this._inflate.flush(() => {
       cleanup();
       if (err) callback(err);
-      else callback(null, Buffer.concat(buffers, totalLength));
+      else callback(null, bufferUtil.concat(buffers, totalLength));
     });
   }
 
   /**
    * Compress message
    *
-   * @api public
+   * @public
    */
-
   compress (data, fin, callback) {
     if (!data || data.length === 0) {
       process.nextTick(callback, null, EMPTY_BLOCK);
       return;
     }
 
-    var endpoint = this._isServer ? 'server' : 'client';
+    const endpoint = this._isServer ? 'server' : 'client';
 
     if (!this._deflate) {
-      var maxWindowBits = this.params[endpoint + '_max_window_bits'];
+      const maxWindowBits = this.params[`${endpoint}_max_window_bits`];
       this._deflate = zlib.createDeflateRaw({
         flush: zlib.Z_SYNC_FLUSH,
         windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS,
@@ -304,19 +305,30 @@ class PerMessageDeflate {
     }
     this._deflate.writeInProgress = true;
 
+    var totalLength = 0;
     const buffers = [];
 
-    const onData = (data) => buffers.push(data);
+    const onData = (data) => {
+      totalLength += data.length;
+      buffers.push(data);
+    };
+
     const onError = (err) => {
       cleanup();
       callback(err);
     };
+
     const cleanup = () => {
       if (!this._deflate) return;
+
       this._deflate.removeListener('error', onError);
       this._deflate.removeListener('data', onData);
       this._deflate.writeInProgress = false;
-      if ((fin && this.params[endpoint + '_no_context_takeover']) || this._deflate.pendingClose) {
+
+      if (
+        (fin && this.params[`${endpoint}_no_context_takeover`]) ||
+        this._deflate.pendingClose
+      ) {
         this._deflate.close();
         this._deflate = null;
       }
@@ -326,15 +338,11 @@ class PerMessageDeflate {
     this._deflate.write(data);
     this._deflate.flush(zlib.Z_SYNC_FLUSH, () => {
       cleanup();
-      var data = Buffer.concat(buffers);
-      if (fin) {
-        data = data.slice(0, data.length - 4);
-      }
+      var data = bufferUtil.concat(buffers, totalLength);
+      if (fin) data = data.slice(0, data.length - 4);
       callback(null, data);
     });
   }
 }
 
-PerMessageDeflate.extensionName = 'permessage-deflate';
-
 module.exports = PerMessageDeflate;
diff --git a/lib/Receiver.js b/lib/Receiver.js
index 670e72e78..54cb409b3 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -532,7 +532,7 @@ module.exports = Receiver;
  */
 function toBuffer (fragments, messageLength) {
   if (fragments.length === 1) return fragments[0];
-  if (fragments.length > 1) return Buffer.concat(fragments, messageLength);
+  if (fragments.length > 1) return bufferUtil.concat(fragments, messageLength);
   return constants.EMPTY_BUFFER;
 }
 

From e13ef4a603db913e02c9375e3b1393f9f76ef8ff Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Fri, 3 Mar 2017 12:34:22 +0100
Subject: [PATCH 300/669] chore(package): update bufferutil to version 3.0.0
 (#1029)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 1aed2bd8d..97d8be634 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,7 @@
   },
   "devDependencies": {
     "benchmark": "~2.1.2",
-    "bufferutil": "~2.0.0",
+    "bufferutil": "~3.0.0",
     "eslint": "~3.16.0",
     "eslint-config-standard": "~7.0.0",
     "eslint-plugin-promise": "~3.5.0",

From 4a605b9853a36ce53332f3cad87127b47d4d1030 Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Sat, 4 Mar 2017 07:20:44 +0100
Subject: [PATCH 301/669] chore(package): update eslint to version 3.17.0
 (#1030)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 97d8be634..de6bc088b 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
   "devDependencies": {
     "benchmark": "~2.1.2",
     "bufferutil": "~3.0.0",
-    "eslint": "~3.16.0",
+    "eslint": "~3.17.0",
     "eslint-config-standard": "~7.0.0",
     "eslint-plugin-promise": "~3.5.0",
     "eslint-plugin-standard": "~2.1.0",

From e057bfc8274e4b64fc6752cfddcfc634ebddd87e Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Wed, 8 Mar 2017 14:31:54 +0100
Subject: [PATCH 302/669] [example] Avoid using deprecated APIs

---
 examples/fileapi/server.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/examples/fileapi/server.js b/examples/fileapi/server.js
index 380ce152d..4202818a0 100644
--- a/examples/fileapi/server.js
+++ b/examples/fileapi/server.js
@@ -34,8 +34,8 @@ function makePathForFile (filePath, prefix, cb) {
     if (error) return cb(error);
     if (pieces.length === 0) return cb(null, incrementalPath);
     incrementalPath += '/' + pieces.shift();
-    fs.exists(incrementalPath, function (exists) {
-      if (!exists) fs.mkdir(incrementalPath, step);
+    fs.access(incrementalPath, function (err) {
+      if (err) fs.mkdir(incrementalPath, step);
       else process.nextTick(step);
     });
   }

From 05970f6a11fd153dd89559ed95a54fd8d1375c48 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Wed, 8 Mar 2017 14:42:46 +0100
Subject: [PATCH 303/669] [pkg] Add missing dependencies for
 eslint-config-standard@8

---
 package.json | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/package.json b/package.json
index de6bc088b..b2057b9be 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,8 @@
     "bufferutil": "~3.0.0",
     "eslint": "~3.17.0",
     "eslint-config-standard": "~7.0.0",
+    "eslint-plugin-import": "~2.2.0",
+    "eslint-plugin-node": "~4.2.0",
     "eslint-plugin-promise": "~3.5.0",
     "eslint-plugin-standard": "~2.1.0",
     "istanbul": "~0.4.5",

From fecc4f5b5b96bd98318d5c962eac6f0d9d3d37fc Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Wed, 8 Mar 2017 15:00:59 +0100
Subject: [PATCH 304/669] chore(package): update eslint-config-standard to
 version 8.0.0-beta.1 (#1035)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index b2057b9be..551322340 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
     "benchmark": "~2.1.2",
     "bufferutil": "~3.0.0",
     "eslint": "~3.17.0",
-    "eslint-config-standard": "~7.0.0",
+    "eslint-config-standard": "~8.0.0-beta.1",
     "eslint-plugin-import": "~2.2.0",
     "eslint-plugin-node": "~4.2.0",
     "eslint-plugin-promise": "~3.5.0",

From d29f30682e3330609c27d35053785373530aaa26 Mon Sep 17 00:00:00 2001
From: Dorian 
Date: Fri, 10 Mar 2017 00:27:42 -0800
Subject: [PATCH 305/669] [minor] Improve error message for unsupported
 protocol version (#1036)

---
 lib/WebSocket.js       | 10 +++++++---
 test/WebSocket.test.js |  2 +-
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index 24bd1bff7..f08a96bd7 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -21,7 +21,8 @@ const Receiver = require('./Receiver');
 const Sender = require('./Sender');
 
 const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly.
-const protocolVersion = 13;
+const protocolVersions = [8, 13];
+const protocolVersion = protocolVersions[1];
 
 /**
  * Class representing a WebSocket.
@@ -527,8 +528,11 @@ function initAsClient (address, protocols, options) {
     ca: null
   }, options);
 
-  if (options.protocolVersion !== 8 && options.protocolVersion !== 13) {
-    throw new Error('unsupported protocol version');
+  if (protocolVersions.indexOf(options.protocolVersion) === -1) {
+    throw new Error(
+      `unsupported protocol version: ${options.protocolVersion} ` +
+      `(supported versions: ${protocolVersions.join(', ')})`
+    );
   }
 
   this.protocolVersion = options.protocolVersion;
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index 867629d82..ff01c07be 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -51,7 +51,7 @@ describe('WebSocket', function () {
 
       assert.throws(
         () => new WebSocket('ws://localhost', options),
-        /^Error: unsupported protocol version$/
+        /^Error: unsupported protocol version: 1000 \(supported versions: 8, 13\)$/
       );
     });
 

From 72170d455c38637ef5702c5d8270e72c55313181 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Fri, 10 Mar 2017 09:34:06 +0100
Subject: [PATCH 306/669] [codestyle] Fix nits

---
 lib/WebSocket.js | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index f08a96bd7..0f8a2d3ae 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -20,9 +20,8 @@ const constants = require('./Constants');
 const Receiver = require('./Receiver');
 const Sender = require('./Sender');
 
-const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly.
 const protocolVersions = [8, 13];
-const protocolVersion = protocolVersions[1];
+const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly.
 
 /**
  * Class representing a WebSocket.
@@ -505,15 +504,15 @@ function initAsServerClient (req, socket, head, options) {
  */
 function initAsClient (address, protocols, options) {
   options = Object.assign({
+    protocolVersion: protocolVersions[1],
     protocol: protocols.join(','),
     perMessageDeflate: true,
     localAddress: null,
-    protocolVersion,
     headers: null,
+    family: null,
     origin: null,
     agent: null,
     host: null,
-    family: null,
 
     //
     // SSL options.

From 738e07f337fd601ba8ab0c0afc50f79296920fc8 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Sat, 11 Mar 2017 15:06:19 +0100
Subject: [PATCH 307/669] [fix] Make `WebSocket#terminate()` destroy the socket
 (#1033)

---
 doc/ws.md        |  4 ++--
 lib/WebSocket.js | 19 ++++---------------
 2 files changed, 6 insertions(+), 17 deletions(-)

diff --git a/doc/ws.md b/doc/ws.md
index 3cc6c0a9e..8893edf47 100644
--- a/doc/ws.md
+++ b/doc/ws.md
@@ -406,11 +406,11 @@ Resume the socket
 - `callback` {Function} An optional callback which is invoked when `data` is
   written out.
 
-Sends `data` through the connection.
+Send `data` through the connection.
 
 ### websocket.terminate()
 
-Send a FIN packet to the other peer.
+Forcibly close the connection.
 
 ### websocket.upgradeReq
 
diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index 0f8a2d3ae..c4ed744d5 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -276,7 +276,7 @@ class WebSocket extends EventEmitter {
     }
 
     if (this.readyState === WebSocket.CLOSING) {
-      if (this._closeCode) this.terminate();
+      if (this._closeCode && this._socket) this._socket.end();
       return;
     }
 
@@ -284,9 +284,8 @@ class WebSocket extends EventEmitter {
     this._sender.close(code, data, !this._isServer, (err) => {
       if (err) this.emit('error', err);
 
-      if (this._closeCode) {
-        this.terminate();
-      } else {
+      if (this._socket) {
+        if (this._closeCode) this._socket.end();
         //
         // Ensure that the connection is cleaned up even when the closing
         // handshake fails.
@@ -391,17 +390,7 @@ class WebSocket extends EventEmitter {
       return;
     }
 
-    if (this._socket) {
-      this.readyState = WebSocket.CLOSING;
-      this._socket.end();
-
-      //
-      // Add a timeout to ensure that the connection is completely cleaned up
-      // within 30 seconds, even if the other peer does not send a FIN packet.
-      //
-      clearTimeout(this._closeTimer);
-      this._closeTimer = setTimeout(this._finalize, closeTimeout, true);
-    }
+    this.finalize(true);
   }
 }
 

From 08eb82725afce71f495f82e5f24d3e375a1670d2 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Mon, 13 Mar 2017 07:56:41 +0100
Subject: [PATCH 308/669] [dist] 2.2.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 551322340..03370cf1e 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "author": "Einar Otto Stangvik  (http://2x.io)",
   "name": "ws",
   "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
-  "version": "2.2.0",
+  "version": "2.2.1",
   "license": "MIT",
   "main": "index.js",
   "keywords": [

From 1b3810ed95d2d2572314b3c361e97eaab73c41ef Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 14 Mar 2017 10:53:46 +0100
Subject: [PATCH 309/669] [minor] Add missing JSDoc comments

---
 lib/Extensions.js        |  64 ++++++++++++------------
 lib/PerMessageDeflate.js | 103 ++++++++++++++++++++++++++-------------
 lib/WebSocket.js         |   2 +-
 3 files changed, 100 insertions(+), 69 deletions(-)

diff --git a/lib/Extensions.js b/lib/Extensions.js
index 036c71382..a91910eb2 100644
--- a/lib/Extensions.js
+++ b/lib/Extensions.js
@@ -1,31 +1,28 @@
 'use strict';
 
 /**
- * Module exports.
+ * Parse the `Sec-WebSocket-Extensions` header into an object.
+ *
+ * @param {String} value field value of the header
+ * @return {Object} The parsed object
+ * @public
  */
-
-exports.parse = parse;
-exports.format = format;
-
-/**
- * Parse extensions header value
- */
-
-function parse (value) {
+const parse = (value) => {
   value = value || '';
 
-  var extensions = {};
+  const extensions = {};
 
-  value.split(',').forEach(function (v) {
-    var params = v.split(';');
-    var token = params.shift().trim();
-    var paramsList = extensions[token] = extensions[token] || [];
-    var parsedParams = {};
+  value.split(',').forEach((v) => {
+    const params = v.split(';');
+    const token = params.shift().trim();
+    const paramsList = extensions[token] = extensions[token] || [];
+    const parsedParams = {};
 
-    params.forEach(function (param) {
-      var parts = param.trim().split('=');
-      var key = parts[0];
+    params.forEach((param) => {
+      const parts = param.trim().split('=');
+      const key = parts[0];
       var value = parts[1];
+
       if (value === undefined) {
         value = true;
       } else {
@@ -44,26 +41,27 @@ function parse (value) {
   });
 
   return extensions;
-}
+};
 
 /**
- * Format extensions header value
+ * Serialize a parsed `Sec-WebSocket-Extensions` header to a string.
+ *
+ * @param {Object} value The object to format
+ * @return {String} A string representing the given value
+ * @public
  */
-
-function format (value) {
-  return Object.keys(value).map(function (token) {
+const format = (value) => {
+  return Object.keys(value).map((token) => {
     var paramsList = value[token];
-    if (!Array.isArray(paramsList)) {
-      paramsList = [paramsList];
-    }
-    return paramsList.map(function (params) {
-      return [token].concat(Object.keys(params).map(function (k) {
+    if (!Array.isArray(paramsList)) paramsList = [paramsList];
+    return paramsList.map((params) => {
+      return [token].concat(Object.keys(params).map((k) => {
         var p = params[k];
         if (!Array.isArray(p)) p = [p];
-        return p.map(function (v) {
-          return v === true ? k : k + '=' + v;
-        }).join('; ');
+        return p.map((v) => v === true ? k : `${k}=${v}`).join('; ');
       })).join('; ');
     }).join(', ');
   }).join(', ');
-}
+};
+
+module.exports = { format, parse };
diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js
index 68e2ed384..192d530ef 100644
--- a/lib/PerMessageDeflate.js
+++ b/lib/PerMessageDeflate.js
@@ -29,12 +29,14 @@ class PerMessageDeflate {
   }
 
   /**
-   * Create extension parameters offer
+   * Create extension parameters offer.
    *
+   * @return {Object} Extension parameters
    * @public
    */
   offer () {
-    var params = {};
+    const params = {};
+
     if (this._options.serverNoContextTakeover) {
       params.server_no_context_takeover = true;
     }
@@ -49,12 +51,15 @@ class PerMessageDeflate {
     } else if (this._options.clientMaxWindowBits == null) {
       params.client_max_window_bits = true;
     }
+
     return params;
   }
 
   /**
-   * Accept extension offer
+   * Accept extension offer.
    *
+   * @param {Array} paramsList Extension parameters
+   * @return {Object} Accepted configuration
    * @public
    */
   accept (paramsList) {
@@ -72,7 +77,7 @@ class PerMessageDeflate {
   }
 
   /**
-   * Releases all resources used by the extension
+   * Releases all resources used by the extension.
    *
    * @public
    */
@@ -96,36 +101,45 @@ class PerMessageDeflate {
   }
 
   /**
-   * Accept extension offer from client
+   * Accept extension offer from client.
    *
+   * @param {Array} paramsList Extension parameters
+   * @return {Object} Accepted configuration
    * @private
    */
   acceptAsServer (paramsList) {
-    var accepted = {};
-    var result = paramsList.some((params) => {
-      accepted = {};
-      if (this._options.serverNoContextTakeover === false && params.server_no_context_takeover) {
-        return;
-      }
-      if (this._options.serverMaxWindowBits === false && params.server_max_window_bits) {
-        return;
-      }
-      if (typeof this._options.serverMaxWindowBits === 'number' &&
-          typeof params.server_max_window_bits === 'number' &&
-          this._options.serverMaxWindowBits > params.server_max_window_bits) {
-        return;
-      }
-      if (typeof this._options.clientMaxWindowBits === 'number' && !params.client_max_window_bits) {
+    const accepted = {};
+    const result = paramsList.some((params) => {
+      if ((
+        this._options.serverNoContextTakeover === false &&
+        params.server_no_context_takeover
+      ) || (
+        this._options.serverMaxWindowBits === false &&
+        params.server_max_window_bits
+      ) || (
+        typeof this._options.serverMaxWindowBits === 'number' &&
+        typeof params.server_max_window_bits === 'number' &&
+        this._options.serverMaxWindowBits > params.server_max_window_bits
+      ) || (
+        typeof this._options.clientMaxWindowBits === 'number' &&
+        !params.client_max_window_bits
+      )) {
         return;
       }
 
-      if (this._options.serverNoContextTakeover || params.server_no_context_takeover) {
+      if (
+        this._options.serverNoContextTakeover ||
+        params.server_no_context_takeover
+      ) {
         accepted.server_no_context_takeover = true;
       }
       if (this._options.clientNoContextTakeover) {
         accepted.client_no_context_takeover = true;
       }
-      if (this._options.clientNoContextTakeover !== false && params.client_no_context_takeover) {
+      if (
+        this._options.clientNoContextTakeover !== false &&
+        params.client_no_context_takeover
+      ) {
         accepted.client_no_context_takeover = true;
       }
       if (typeof this._options.serverMaxWindowBits === 'number') {
@@ -135,46 +149,62 @@ class PerMessageDeflate {
       }
       if (typeof this._options.clientMaxWindowBits === 'number') {
         accepted.client_max_window_bits = this._options.clientMaxWindowBits;
-      } else if (this._options.clientMaxWindowBits !== false && typeof params.client_max_window_bits === 'number') {
+      } else if (
+        this._options.clientMaxWindowBits !== false &&
+        typeof params.client_max_window_bits === 'number'
+      ) {
         accepted.client_max_window_bits = params.client_max_window_bits;
       }
       return true;
     });
 
-    if (!result) {
-      throw new Error(`Doesn't support the offered configuration`);
-    }
+    if (!result) throw new Error(`Doesn't support the offered configuration`);
 
     return accepted;
   }
 
   /**
-   * Accept extension response from server
+   * Accept extension response from server.
    *
+   * @param {Array} paramsList Extension parameters
+   * @return {Object} Accepted configuration
    * @private
    */
   acceptAsClient (paramsList) {
-    var params = paramsList[0];
+    const params = paramsList[0];
+
     if (this._options.clientNoContextTakeover != null) {
-      if (this._options.clientNoContextTakeover === false && params.client_no_context_takeover) {
+      if (
+        this._options.clientNoContextTakeover === false &&
+        params.client_no_context_takeover
+      ) {
         throw new Error('Invalid value for "client_no_context_takeover"');
       }
     }
     if (this._options.clientMaxWindowBits != null) {
-      if (this._options.clientMaxWindowBits === false && params.client_max_window_bits) {
+      if (
+        this._options.clientMaxWindowBits === false &&
+        params.client_max_window_bits
+      ) {
         throw new Error('Invalid value for "client_max_window_bits"');
       }
-      if (typeof this._options.clientMaxWindowBits === 'number' &&
-          (!params.client_max_window_bits || params.client_max_window_bits > this._options.clientMaxWindowBits)) {
+      if (
+        typeof this._options.clientMaxWindowBits === 'number' && (
+        !params.client_max_window_bits ||
+        params.client_max_window_bits > this._options.clientMaxWindowBits
+      )) {
         throw new Error('Invalid value for "client_max_window_bits"');
       }
     }
+
     return params;
   }
 
   /**
-   * Normalize extensions parameters
+   * Normalize extensions parameters.
    *
+   * @param {Array} paramsList Extension parameters
+   * @return {Array} Normalized extensions parameters
    * @private
    */
   normalizeParams (paramsList) {
@@ -182,7 +212,7 @@ class PerMessageDeflate {
       Object.keys(params).forEach((key) => {
         var value = params[key];
         if (value.length > 1) {
-          throw new Error('Multiple extension parameters for ' + key);
+          throw new Error(`Multiple extension parameters for ${key}`);
         }
 
         value = value[0];
@@ -283,8 +313,11 @@ class PerMessageDeflate {
   }
 
   /**
-   * Compress message
+   * Compress data.
    *
+   * @param {Buffer} data Data to compress
+   * @param {Boolean} fin Specifies whether or not this is the last fragment
+   * @param {Function} callback Callback
    * @public
    */
   compress (data, fin, callback) {
diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index c4ed744d5..a66c12814 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -375,7 +375,7 @@ class WebSocket extends EventEmitter {
   }
 
   /**
-   * Half-close the socket sending a FIN packet.
+   * Forcibly close the connection.
    *
    * @public
    */

From 3e6c8d3b0d93fef8efceb75ca9b7512a581a4067 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Thu, 16 Mar 2017 20:54:05 +0100
Subject: [PATCH 310/669] [test] Fix origin header in integration tests

---
 test/WebSocket.integration.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/WebSocket.integration.js b/test/WebSocket.integration.js
index 90d5adcfb..08810cfc9 100644
--- a/test/WebSocket.integration.js
+++ b/test/WebSocket.integration.js
@@ -7,7 +7,7 @@ const WebSocket = require('..');
 describe('WebSocket', function () {
   it('communicates successfully with echo service (ws)', function (done) {
     const ws = new WebSocket('ws://echo.websocket.org/', {
-      origin: 'ws://echo.websocket.org',
+      origin: 'http://www.websocket.org',
       protocolVersion: 13
     });
     const str = Date.now().toString();
@@ -28,7 +28,7 @@ describe('WebSocket', function () {
 
   it('communicates successfully with echo service (wss)', function (done) {
     const ws = new WebSocket('wss://echo.websocket.org/', {
-      origin: 'wss://echo.websocket.org',
+      origin: 'https://www.websocket.org',
       protocolVersion: 13
     });
     const str = Date.now().toString();

From 2ace70d913a7dc0fc741147deb559dda914d1498 Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Sat, 18 Mar 2017 07:32:10 +0100
Subject: [PATCH 311/669] chore(package): update eslint to version 3.18.0
 (#1048)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 03370cf1e..bc0bf88a5 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
   "devDependencies": {
     "benchmark": "~2.1.2",
     "bufferutil": "~3.0.0",
-    "eslint": "~3.17.0",
+    "eslint": "~3.18.0",
     "eslint-config-standard": "~8.0.0-beta.1",
     "eslint-plugin-import": "~2.2.0",
     "eslint-plugin-node": "~4.2.0",

From 35119fdeeb3310e7ad8b4d34520591fa7acb53a8 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 21 Mar 2017 15:28:27 +0100
Subject: [PATCH 312/669] [test] Delete test/testserver.js

---
 test/WebSocket.test.js | 665 ++++++++++++++++++++++-------------------
 test/testserver.js     | 153 ----------
 2 files changed, 354 insertions(+), 464 deletions(-)
 delete mode 100644 test/testserver.js

diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index ff01c07be..fd065d67e 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -8,7 +8,7 @@ const https = require('https');
 const http = require('http');
 const fs = require('fs');
 
-const server = require('./testserver');
+const constants = require('../lib/Constants');
 const WebSocket = require('..');
 
 const WebSocketServer = WebSocket.Server;
@@ -181,43 +181,40 @@ describe('WebSocket', function () {
     });
 
     describe('Custom headers', function () {
+      const server = http.createServer();
+
+      beforeEach((done) => server.listen(++port, done));
+      afterEach((done) => server.close(done));
+
       it('request has an authorization header', function (done) {
-        const server = http.createServer();
         const wss = new WebSocketServer({ server });
         const auth = 'test:testpass';
 
-        server.listen(++port, () => {
-          const ws = new WebSocket(`ws://${auth}@localhost:${port}`);
-        });
-
-        server.on('upgrade', (req, socket, head) => {
+        server.once('upgrade', (req, socket, head) => {
           assert.ok(req.headers.authorization);
           assert.strictEqual(
             req.headers.authorization,
             `Basic ${Buffer.from(auth).toString('base64')}`
           );
 
-          wss.close();
-          server.close(done);
+          wss.close(done);
         });
+
+        const ws = new WebSocket(`ws://${auth}@localhost:${port}`);
       });
 
       it('accepts custom headers', function (done) {
-        const server = http.createServer();
         const wss = new WebSocketServer({ server });
 
-        server.on('upgrade', (req, socket, head) => {
+        server.once('upgrade', (req, socket, head) => {
           assert.ok(req.headers.cookie);
           assert.strictEqual(req.headers.cookie, 'foo=bar');
 
-          wss.close();
-          server.close(done);
+          wss.close(done);
         });
 
-        server.listen(++port, () => {
-          const ws = new WebSocket(`ws://localhost:${port}`, {
-            headers: { 'Cookie': 'foo=bar' }
-          });
+        const ws = new WebSocket(`ws://localhost:${port}`, {
+          headers: { 'Cookie': 'foo=bar' }
         });
       });
     });
@@ -232,7 +229,7 @@ describe('WebSocket', function () {
       });
 
       it('set to open once connection is established', function (done) {
-        server.createServer(++port, (srv) => {
+        const wss = new WebSocketServer({ port: ++port }, () => {
           const ws = new WebSocket(`ws://localhost:${port}`);
 
           ws.on('open', () => {
@@ -240,17 +237,17 @@ describe('WebSocket', function () {
             ws.close();
           });
 
-          ws.on('close', () => srv.close(done));
+          ws.on('close', () => wss.close(done));
         });
       });
 
       it('set to closed once connection is closed', function (done) {
-        server.createServer(++port, (srv) => {
+        const wss = new WebSocketServer({ port: ++port }, () => {
           const ws = new WebSocket(`ws://localhost:${port}`);
 
           ws.on('close', () => {
             assert.strictEqual(ws.readyState, WebSocket.CLOSED);
-            srv.close(done);
+            wss.close(done);
           });
 
           ws.on('open', () => ws.close(1001));
@@ -258,12 +255,12 @@ describe('WebSocket', function () {
       });
 
       it('set to closed once connection is terminated', function (done) {
-        server.createServer(++port, (srv) => {
+        const wss = new WebSocketServer({ port: ++port }, () => {
           const ws = new WebSocket(`ws://localhost:${port}`);
 
           ws.on('close', () => {
             assert.strictEqual(ws.readyState, WebSocket.CLOSED);
-            srv.close(done);
+            wss.close(done);
           });
 
           ws.on('open', () => ws.terminate());
@@ -325,66 +322,130 @@ describe('WebSocket', function () {
   });
 
   describe('connection establishing', function () {
+    const server = http.createServer();
+
+    beforeEach((done) => server.listen(++port, done));
+    afterEach((done) => server.close(done));
+
     it('invalid server key is denied', function (done) {
-      server.createServer(++port, server.handlers.invalidKey, (srv) => {
-        const ws = new WebSocket(`ws://localhost:${port}`);
+      server.once('upgrade', (req, socket) => {
+        socket.on('end', () => socket.end());
+        socket.write(
+          'HTTP/1.1 101 Switching Protocols\r\n' +
+          'Upgrade: websocket\r\n' +
+          'Connection: Upgrade\r\n' +
+          'Sec-WebSocket-Accept: CxYS6+NgJSBG74mdgLvGscRvpns=\r\n' +
+          '\r\n'
+        );
+      });
+
+      const ws = new WebSocket(`ws://localhost:${port}`);
 
-        ws.on('error', () => srv.close(done));
+      ws.on('error', (err) => {
+        assert.ok(err instanceof Error);
+        assert.strictEqual(err.message, 'invalid server key');
+        done();
       });
     });
 
     it('close event is raised when server closes connection', function (done) {
-      server.createServer(++port, server.handlers.closeAfterConnect, (srv) => {
-        const ws = new WebSocket(`ws://localhost:${port}`);
+      server.once('upgrade', (req, socket) => {
+        const key = crypto.createHash('sha1')
+          .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary')
+          .digest('base64');
+
+        socket.end(
+          'HTTP/1.1 101 Switching Protocols\r\n' +
+          'Upgrade: websocket\r\n' +
+          'Connection: Upgrade\r\n' +
+          `Sec-WebSocket-Accept: ${key}\r\n` +
+          '\r\n'
+        );
+      });
+
+      const ws = new WebSocket(`ws://localhost:${port}`);
 
-        ws.on('close', () => srv.close(done));
+      ws.on('close', (code, reason) => {
+        assert.strictEqual(code, 1006);
+        assert.strictEqual(reason, '');
+        done();
       });
     });
 
     it('error is emitted if server aborts connection', function (done) {
-      server.createServer(++port, server.handlers.return401, (srv) => {
-        const ws = new WebSocket(`ws://localhost:${port}`);
+      server.once('upgrade', (req, socket) => {
+        socket.end(
+          `HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` +
+          'Connection: close\r\n' +
+          'Content-type: text/html\r\n' +
+          `Content-Length: ${http.STATUS_CODES[401].length}\r\n` +
+          '\r\n'
+        );
+      });
 
-        ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
-        ws.on('error', () => srv.close(done));
+      const ws = new WebSocket(`ws://localhost:${port}`);
+
+      ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
+      ws.on('error', (err) => {
+        assert.ok(err instanceof Error);
+        assert.strictEqual(err.message, 'unexpected server response (401)');
+        done();
       });
     });
 
     it('unexpected response can be read when sent by server', function (done) {
-      server.createServer(++port, server.handlers.return401, (srv) => {
-        const ws = new WebSocket(`ws://localhost:${port}`);
+      server.once('upgrade', (req, socket) => {
+        socket.end(
+          `HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` +
+          'Connection: close\r\n' +
+          'Content-type: text/html\r\n' +
+          `Content-Length: ${http.STATUS_CODES[401].length}\r\n` +
+          '\r\n' +
+          'foo'
+        );
+      });
 
-        ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
-        ws.on('error', () => assert.fail(null, null, 'error shouldnt be raised here'));
-        ws.on('unexpected-response', (req, res) => {
-          assert.strictEqual(res.statusCode, 401);
+      const ws = new WebSocket(`ws://localhost:${port}`);
 
-          let data = '';
+      ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
+      ws.on('error', () => assert.fail(null, null, 'error shouldnt be raised here'));
+      ws.on('unexpected-response', (req, res) => {
+        assert.strictEqual(res.statusCode, 401);
 
-          res.on('data', (v) => {
-            data += v;
-          });
+        let data = '';
 
-          res.on('end', () => {
-            assert.strictEqual(data, 'Not allowed!');
-            srv.close(done);
-          });
+        res.on('data', (v) => {
+          data += v;
+        });
+
+        res.on('end', () => {
+          assert.strictEqual(data, 'foo');
+          done();
         });
       });
     });
 
     it('request can be aborted when unexpected response is sent by server', function (done) {
-      server.createServer(++port, server.handlers.return401, (srv) => {
-        const ws = new WebSocket(`ws://localhost:${port}`);
+      server.once('upgrade', (req, socket) => {
+        socket.end(
+          `HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` +
+          'Connection: close\r\n' +
+          'Content-type: text/html\r\n' +
+          `Content-Length: ${http.STATUS_CODES[401].length}\r\n` +
+          '\r\n' +
+          'foo'
+        );
+      });
 
-        ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
-        ws.on('error', () => assert.fail(null, null, 'error shouldnt be raised here'));
-        ws.on('unexpected-response', (req, res) => {
-          assert.strictEqual(res.statusCode, 401);
+      const ws = new WebSocket(`ws://localhost:${port}`);
 
-          res.on('end', () => srv.close(done));
-          req.abort();
-        });
+      ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
+      ws.on('error', () => assert.fail(null, null, 'error shouldnt be raised here'));
+      ws.on('unexpected-response', (req, res) => {
+        assert.strictEqual(res.statusCode, 401);
+
+        res.on('end', done);
+        req.abort();
       });
     });
   });
@@ -463,142 +524,114 @@ describe('WebSocket', function () {
   });
 
   describe('#ping', function () {
-    it('before connect should fail', function (done) {
-      server.createServer(++port, (srv) => {
-        const ws = new WebSocket(`ws://localhost:${port}`);
+    it('before connect should fail', function () {
+      const ws = new WebSocket(`ws://localhost:${port}`, {
+        agent: new CustomAgent()
+      });
 
-        ws.on('error', () => {});
+      assert.throws(() => ws.ping(), /^Error: not opened$/);
+    });
 
-        try {
-          ws.ping();
-        } catch (e) {
-          srv.close(done);
-          ws.terminate();
-        }
+    it('before connect can silently fail', function () {
+      const ws = new WebSocket(`ws://localhost:${port}`, {
+        agent: new CustomAgent()
       });
+
+      assert.doesNotThrow(() => ws.ping('', true, true));
     });
 
-    it('before connect can silently fail', function (done) {
-      server.createServer(++port, (srv) => {
+    it('without message is successfully transmitted to the server', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
-        ws.on('error', () => {});
-        ws.ping('', true, true);
+        ws.on('open', () => ws.ping());
+      });
 
-        srv.close(done);
-        ws.terminate();
+      wss.on('connection', (ws) => {
+        ws.on('ping', () => wss.close(done));
       });
     });
 
-    it('without message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, function (srv) {
-        srv.on('ping', () => {
-          srv.close(done);
-          ws.terminate();
-        });
-
+    it('with message is successfully transmitted to the server', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
-        ws.on('open', () => ws.ping());
+        ws.on('open', () => {
+          ws.ping('hi', true);
+          ws.ping('hi');
+        });
       });
-    });
 
-    it('with message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, (srv) => {
-        srv.on('ping', (message) => {
+      wss.on('connection', (ws) => {
+        let pings = 0;
+        ws.on('ping', (message) => {
           assert.strictEqual(message.toString(), 'hi');
-          srv.close(done);
-          ws.terminate();
+          if (++pings === 2) wss.close(done);
         });
-
-        const ws = new WebSocket(`ws://localhost:${port}`);
-
-        ws.on('open', () => ws.ping('hi'));
       });
     });
 
     it('can send numbers as ping payload', function (done) {
-      server.createServer(++port, (srv) => {
-        srv.on('ping', (message) => {
-          assert.strictEqual(message.toString(), '0');
-          srv.close(done);
-          ws.terminate();
-        });
-
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => ws.ping(0));
       });
-    });
 
-    it('with encoded message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, (srv) => {
-        srv.on('ping', (message, flags) => {
-          assert.ok(flags.masked);
-          assert.strictEqual(message.toString(), 'hi');
-          srv.close(done);
-          ws.terminate();
+      wss.on('connection', (ws) => {
+        ws.on('ping', (message) => {
+          assert.strictEqual(message.toString(), '0');
+          wss.close(done);
         });
-
-        const ws = new WebSocket(`ws://localhost:${port}`);
-
-        ws.on('open', () => ws.ping('hi', true));
       });
     });
   });
 
   describe('#pong', function () {
-    it('before connect should fail', (done) => {
-      server.createServer(++port, (srv) => {
-        const ws = new WebSocket(`ws://localhost:${port}`);
+    it('before connect should fail', () => {
+      const ws = new WebSocket(`ws://localhost:${port}`, {
+        agent: new CustomAgent()
+      });
 
-        ws.on('error', () => {});
+      assert.throws(() => ws.pong(), /^Error: not opened$/);
+    });
 
-        try {
-          ws.pong();
-        } catch (e) {
-          srv.close(done);
-          ws.terminate();
-        }
+    it('before connect can silently fail', function () {
+      const ws = new WebSocket(`ws://localhost:${port}`, {
+        agent: new CustomAgent()
       });
+
+      assert.doesNotThrow(() => ws.pong('', true, true));
     });
 
-    it('before connect can silently fail', function (done) {
-      server.createServer(++port, (srv) => {
+    it('without message is successfully transmitted to the server', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
-        ws.on('error', () => {});
-        ws.pong('', true, true);
+        ws.on('open', () => ws.pong());
+      });
 
-        srv.close(done);
-        ws.terminate();
+      wss.on('connection', (ws) => {
+        ws.on('pong', () => wss.close(done));
       });
     });
 
-    it('without message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, (srv) => {
-        srv.on('pong', () => {
-          srv.close(done);
-          ws.terminate();
-        });
-
+    it('with message is successfully transmitted to the server', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
-        ws.on('open', () => ws.pong());
+        ws.on('open', () => {
+          ws.pong('hi', true);
+          ws.pong('hi');
+        });
       });
-    });
 
-    it('with message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, (srv) => {
-        srv.on('pong', (message) => {
+      wss.on('connection', (ws) => {
+        let pongs = 0;
+        ws.on('pong', (message) => {
           assert.strictEqual(message.toString(), 'hi');
-          srv.close(done);
-          ws.terminate();
+          if (++pongs === 2) wss.close(done);
         });
-
-        const ws = new WebSocket(`ws://localhost:${port}`);
-
-        ws.on('open', () => ws.pong('hi'));
       });
     });
 
@@ -616,55 +649,46 @@ describe('WebSocket', function () {
         });
       });
     });
-
-    it('with encoded message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, (srv) => {
-        srv.on('pong', (message, flags) => {
-          assert.ok(flags.masked);
-          assert.strictEqual(message.toString(), 'hi');
-          srv.close(done);
-          ws.terminate();
-        });
-
-        const ws = new WebSocket(`ws://localhost:${port}`);
-
-        ws.on('open', () => ws.pong('hi', true));
-      });
-    });
   });
 
   describe('#send', function () {
-    it('very long binary data can be sent and received (with echoing server)', (done) => {
-      server.createServer(++port, (srv) => {
+    it('very long binary data can be sent and received', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const array = new Float32Array(5 * 1024 * 1024);
 
-        for (let i = 0; i < array.length; ++i) {
+        for (let i = 0; i < array.length; i++) {
           array[i] = i / 5;
         }
 
         const ws = new WebSocket(`ws://localhost:${port}`);
 
-        ws.on('open', () => ws.send(array, { binary: true }));
-        ws.on('message', (message, flags) => {
+        ws.on('open', () => ws.send(array, { compress: false }));
+        ws.on('message', (msg, flags) => {
           assert.ok(flags.binary);
-          assert.ok(message.equals(Buffer.from(array.buffer)));
-          srv.close(done);
-          ws.terminate();
+          assert.ok(msg.equals(Buffer.from(array.buffer)));
+          wss.close(done);
         });
       });
+
+      wss.on('connection', (ws) => {
+        ws.on('message', (msg) => ws.send(msg, { compress: false }));
+      });
     });
 
     it('can send and receive text data', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => ws.send('hi'));
         ws.on('message', (message, flags) => {
           assert.strictEqual(message, 'hi');
-          srv.close(done);
-          ws.terminate();
+          wss.close(done);
         });
       });
+
+      wss.on('connection', (ws) => {
+        ws.on('message', (msg) => ws.send(msg));
+      });
     });
 
     it('does not override the `fin` option', function (done) {
@@ -701,7 +725,7 @@ describe('WebSocket', function () {
     });
 
     it('can send binary data as an array', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const array = new Float32Array(6);
 
         for (let i = 0; i < array.length; ++i) {
@@ -718,14 +742,17 @@ describe('WebSocket', function () {
         ws.on('message', (message, flags) => {
           assert.ok(flags.binary);
           assert.ok(message.equals(buf));
-          srv.close(done);
-          ws.terminate();
+          wss.close(done);
         });
       });
+
+      wss.on('connection', (ws) => {
+        ws.on('message', (msg) => ws.send(msg));
+      });
     });
 
     it('can send binary data as a buffer', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const buf = Buffer.from('foobar');
         const ws = new WebSocket(`ws://localhost:${port}`);
 
@@ -733,14 +760,17 @@ describe('WebSocket', function () {
         ws.on('message', (message, flags) => {
           assert.ok(flags.binary);
           assert.ok(message.equals(buf));
-          srv.close(done);
-          ws.terminate();
+          wss.close(done);
         });
       });
+
+      wss.on('connection', (ws) => {
+        ws.on('message', (msg) => ws.send(msg));
+      });
     });
 
     it('ArrayBuffer is auto-detected without binary flag', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const array = new Float32Array(5);
 
         for (let i = 0; i < array.length; ++i) {
@@ -753,14 +783,17 @@ describe('WebSocket', function () {
         ws.onmessage = (event) => {
           assert.ok(event.binary);
           assert.ok(event.data.equals(Buffer.from(array.buffer)));
-          srv.close(done);
-          ws.terminate();
+          wss.close(done);
         };
       });
+
+      wss.on('connection', (ws) => {
+        ws.on('message', (msg) => ws.send(msg));
+      });
     });
 
     it('Buffer is auto-detected without binary flag', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const buf = Buffer.from('foobar');
         const ws = new WebSocket(`ws://localhost:${port}`);
 
@@ -769,136 +802,135 @@ describe('WebSocket', function () {
         ws.onmessage = (event) => {
           assert.ok(event.binary);
           assert.ok(event.data.equals(buf));
-          srv.close(done);
-          ws.terminate();
+          wss.close(done);
         };
       });
-    });
-
-    it('before connect should fail', function (done) {
-      server.createServer(++port, (srv) => {
-        const ws = new WebSocket(`ws://localhost:${port}`);
 
-        ws.on('error', () => {});
-
-        try {
-          ws.send('hi');
-        } catch (e) {
-          srv.close(done);
-          ws.terminate();
-        }
+      wss.on('connection', (ws) => {
+        ws.on('message', (msg) => ws.send(msg));
       });
     });
 
-    it('before connect should pass error through callback, if present', function (done) {
-      const wss = new WebSocketServer({ port: ++port }, () => {
-        const ws = new WebSocket(`ws://localhost:${port}`);
+    it('before connect should fail', function () {
+      const ws = new WebSocket(`ws://localhost:${port}`, {
+        agent: new CustomAgent()
+      });
 
-        ws.send('hi', (err) => {
-          assert.ok(err instanceof Error);
-          assert.strictEqual(err.message, 'not opened');
-          ws.on('close', () => wss.close(done));
-        });
+      assert.throws(() => ws.send('hi'), /^Error: not opened$/);
+    });
+
+    it('before connect should pass error through callback, if present', function () {
+      const ws = new WebSocket(`ws://localhost:${port}`, {
+        agent: new CustomAgent()
       });
 
-      wss.on('connection', (ws) => ws.close());
+      ws.send('hi', (err) => {
+        assert.ok(err instanceof Error);
+        assert.strictEqual(err.message, 'not opened');
+      });
     });
 
     it('without data should be successful', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => ws.send());
+      });
 
-        srv.on('message', (message, flags) => {
+      wss.on('connection', (ws) => {
+        ws.on('message', (message) => {
           assert.ok(message.equals(Buffer.alloc(0)));
-          srv.close(done);
-          ws.terminate();
+          wss.close(done);
         });
       });
     });
 
     it('calls optional callback when flushed', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => {
-          ws.send('hi', () => {
-            srv.close(done);
-            ws.terminate();
+          ws.send('hi', (err) => {
+            assert.ifError(err);
+            wss.close(done);
           });
         });
       });
     });
 
     it('with unmasked message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => ws.send('hi', { mask: false }));
+      });
 
-        srv.on('message', (message, flags) => {
+      wss.on('connection', (ws) => {
+        ws.on('message', (message, flags) => {
           assert.strictEqual(message, 'hi');
-          srv.close(done);
-          ws.terminate();
+          assert.ok(!flags.masked);
+          wss.close(done);
         });
       });
     });
 
     it('with masked message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => ws.send('hi', { mask: true }));
+      });
 
-        srv.on('message', (message, flags) => {
-          assert.ok(flags.masked);
+      wss.on('connection', (ws) => {
+        ws.on('message', (message, flags) => {
           assert.strictEqual(message, 'hi');
-          srv.close(done);
-          ws.terminate();
+          assert.ok(flags.masked);
+          wss.close(done);
         });
       });
     });
 
     it('with unmasked binary message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, (srv) => {
-        const array = new Float32Array(5);
+      const array = new Float32Array(5);
 
-        for (let i = 0; i < array.length; ++i) {
-          array[i] = i / 2;
-        }
+      for (let i = 0; i < array.length; ++i) {
+        array[i] = i / 2;
+      }
 
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => ws.send(array, { mask: false, binary: true }));
+      });
 
-        srv.on('message', (message, flags) => {
-          assert.ok(flags.binary);
+      wss.on('connection', (ws) => {
+        ws.on('message', (message, flags) => {
           assert.ok(message.equals(Buffer.from(array.buffer)));
-          srv.close(done);
-          ws.terminate();
+          assert.ok(flags.binary);
+          wss.close(done);
         });
       });
     });
 
     it('with masked binary message is successfully transmitted to the server', function (done) {
-      server.createServer(++port, (srv) => {
-        const array = new Float32Array(5);
+      const array = new Float32Array(5);
 
-        for (let i = 0; i < array.length; ++i) {
-          array[i] = i / 2;
-        }
+      for (let i = 0; i < array.length; ++i) {
+        array[i] = i / 2;
+      }
 
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => ws.send(array, { mask: true, binary: true }));
+      });
 
-        srv.on('message', (message, flags) => {
+      wss.on('connection', (ws) => {
+        ws.on('message', (message, flags) => {
+          assert.ok(message.equals(Buffer.from(array.buffer)));
           assert.ok(flags.binary);
           assert.ok(flags.masked);
-          assert.ok(message.equals(Buffer.from(array.buffer)));
-          srv.close(done);
-          ws.terminate();
+          wss.close(done);
         });
       });
     });
@@ -949,79 +981,82 @@ describe('WebSocket', function () {
     });
 
     it('throws an error if the first argument is invalid (1/2)', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => {
-          try {
-            ws.close('error');
-          } catch (e) {
-            srv.close(done);
-            ws.terminate();
-          }
+          assert.throws(
+            () => ws.close('error'),
+            /^Error: first argument must be a valid error code number$/
+          );
+
+          wss.close(done);
         });
       });
     });
 
     it('throws an error if the first argument is invalid (2/2)', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => {
-          try {
-            ws.close(1004);
-          } catch (e) {
-            srv.close(done);
-            ws.terminate();
-          }
+          assert.throws(
+            () => ws.close(1004),
+            /^Error: first argument must be a valid error code number$/
+          );
+
+          wss.close(done);
         });
       });
     });
 
     it('works when close reason is not specified', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => ws.close(1000));
+      });
 
-        srv.on('close', (code, message) => {
+      wss.on('connection', (ws) => {
+        ws.on('close', (code, message) => {
           assert.strictEqual(message, '');
-          srv.close(done);
-          ws.terminate();
+          assert.strictEqual(code, 1000);
+          wss.close(done);
         });
       });
     });
 
     it('works when close reason is specified', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.on('open', () => ws.close(1000, 'some reason'));
+      });
 
-        srv.on('close', (code, message, flags) => {
-          assert.ok(flags.masked);
+      wss.on('connection', (ws) => {
+        ws.on('close', (code, message) => {
           assert.strictEqual(message, 'some reason');
-          srv.close(done);
-          ws.terminate();
+          assert.strictEqual(code, 1000);
+          wss.close(done);
         });
       });
     });
 
     it('ends connection to the server', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({
+        clientTracking: false,
+        port: ++port
+      }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
-        let connectedOnce = false;
 
         ws.on('open', () => {
-          connectedOnce = true;
+          ws.on('close', (code, reason) => {
+            assert.strictEqual(reason, 'some reason');
+            assert.strictEqual(code, 1000);
+            wss.close(done);
+          });
           ws.close(1000, 'some reason');
         });
-
-        ws.on('close', () => {
-          assert.ok(connectedOnce);
-          srv.close(done);
-          ws.terminate();
-        });
       });
     });
 
@@ -1188,7 +1223,10 @@ describe('WebSocket', function () {
     });
 
     it('should work the same as the EventEmitter api', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({
+        clientTracking: false,
+        port: ++port
+      }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
         let message = 0;
         let close = 0;
@@ -1209,10 +1247,13 @@ describe('WebSocket', function () {
           assert.strictEqual(message, 1);
           assert.strictEqual(open, 1);
           assert.strictEqual(close, 1);
-          srv.close(done);
-          ws.terminate();
+          wss.close(done);
         });
       });
+
+      wss.on('connection', (ws) => {
+        ws.on('message', (msg) => ws.send(msg));
+      });
     });
 
     it('doesn\'t return event listeners added with `on`', function () {
@@ -1287,16 +1328,19 @@ describe('WebSocket', function () {
     });
 
     it('should receive text data wrapped in a MessageEvent when using addEventListener', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.addEventListener('open', () => ws.send('hi'));
         ws.addEventListener('message', (messageEvent) => {
           assert.strictEqual(messageEvent.data, 'hi');
-          srv.close(done);
-          ws.terminate();
+          wss.close(done);
         });
       });
+
+      wss.on('connection', (ws) => {
+        ws.on('message', (msg) => ws.send(msg));
+      });
     });
 
     it('should receive valid CloseEvent when server closes with code 1000', function (done) {
@@ -1385,70 +1429,68 @@ describe('WebSocket', function () {
     });
 
     it('should pass binary data as a node.js Buffer by default', function (done) {
-      server.createServer(++port, (srv) => {
-        const array = new Uint8Array(4096);
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
-        ws.onopen = () => ws.send(array, { binary: true });
-        ws.onmessage = (messageEvent) => {
-          assert.ok(messageEvent.binary);
-          assert.strictEqual(ws.binaryType, 'nodebuffer');
-          assert.ok(messageEvent.data instanceof Buffer);
-          srv.close(done);
-          ws.terminate();
+        ws.onmessage = (evt) => {
+          assert.ok(Buffer.isBuffer(evt.data));
+          assert.ok(evt.binary);
+          wss.close(done);
         };
       });
+
+      wss.on('connection', (ws) => ws.send(new Uint8Array(4096)));
     });
 
     it('should pass an ArrayBuffer for event.data if binaryType = arraybuffer', function (done) {
-      server.createServer(++port, (srv) => {
-        const array = new Uint8Array(4096);
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         ws.binaryType = 'arraybuffer';
 
-        ws.onopen = () => ws.send(array, { binary: true });
-        ws.onmessage = (messageEvent) => {
-          assert.ok(messageEvent.binary);
-          assert.ok(messageEvent.data instanceof ArrayBuffer);
-          srv.close(done);
-          ws.terminate();
+        ws.onmessage = (evt) => {
+          assert.ok(evt.data instanceof ArrayBuffer);
+          assert.ok(evt.binary);
+          wss.close(done);
         };
       });
+
+      wss.on('connection', (ws) => ws.send(new Uint8Array(4096)));
     });
 
     it('should ignore binaryType for text messages', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
+
         ws.binaryType = 'arraybuffer';
 
-        ws.onopen = () => ws.send('foobar');
-        ws.onmessage = (messageEvent) => {
-          assert.ok(!messageEvent.binary);
-          assert.strictEqual(typeof messageEvent.data, 'string');
-          srv.close(done);
-          ws.terminate();
+        ws.onmessage = (evt) => {
+          assert.strictEqual(evt.data, 'foo');
+          assert.ok(!evt.binary);
+          wss.close(done);
         };
       });
+
+      wss.on('connection', (ws) => ws.send('foo'));
     });
 
     it('should allow to update binaryType on the fly', function (done) {
-      server.createServer(++port, (srv) => {
+      const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
 
         function testType (binaryType, next) {
           const buf = Buffer.from(binaryType);
           ws.binaryType = binaryType;
 
-          ws.onmessage = (messageEvent) => {
+          ws.onmessage = (evt) => {
             if (binaryType === 'nodebuffer') {
-              assert.ok(Buffer.isBuffer(messageEvent.data));
-              assert.ok(messageEvent.data.equals(buf));
+              assert.ok(Buffer.isBuffer(evt.data));
+              assert.ok(evt.data.equals(buf));
             } else if (binaryType === 'arraybuffer') {
-              assert.ok(messageEvent.data instanceof ArrayBuffer);
-              assert.ok(Buffer.from(messageEvent.data).equals(buf));
+              assert.ok(evt.data instanceof ArrayBuffer);
+              assert.ok(Buffer.from(evt.data).equals(buf));
             } else if (binaryType === 'fragments') {
-              assert.deepStrictEqual(messageEvent.data, [buf]);
+              assert.deepStrictEqual(evt.data, [buf]);
             }
             next();
           };
@@ -1459,14 +1501,15 @@ describe('WebSocket', function () {
         ws.onopen = () => {
           testType('nodebuffer', () => {
             testType('arraybuffer', () => {
-              testType('fragments', () => {
-                srv.close(done);
-                ws.terminate();
-              });
+              testType('fragments', () => wss.close(done));
             });
           });
         };
       });
+
+      wss.on('connection', (ws) => {
+        ws.on('message', (msg) => ws.send(msg));
+      });
     });
   });
 
diff --git a/test/testserver.js b/test/testserver.js
deleted file mode 100644
index 8214650e8..000000000
--- a/test/testserver.js
+++ /dev/null
@@ -1,153 +0,0 @@
-'use strict';
-
-const EventEmitter = require('events');
-const crypto = require('crypto');
-const http = require('http');
-
-const Receiver = require('../lib/Receiver');
-const Sender = require('../lib/Sender');
-
-module.exports = {
-  handlers: {
-    closeAfterConnect: closeAfterConnectHandler,
-    invalidKey: invalidRequestHandler,
-    return401: return401,
-    valid: validServer
-  },
-  createServer: (port, handler, cb) => {
-    if (handler && !cb) {
-      cb = handler;
-      handler = null;
-    }
-
-    const webServer = http.createServer();
-    const srv = new Server(webServer);
-
-    webServer.on('upgrade', (req, socket) => {
-      webServer._socket = socket;
-      (handler || validServer)(srv, req, socket);
-    });
-
-    webServer.listen(port, '127.0.0.1', () => cb(srv));
-  }
-};
-
-function validServer (server, req, socket) {
-  if (!req.headers.upgrade || req.headers.upgrade !== 'websocket') {
-    throw new Error('invalid headers');
-  }
-
-  if (!req.headers['sec-websocket-key']) {
-    throw new Error('websocket key is missing');
-  }
-
-  // calc key
-  const key = crypto.createHash('sha1')
-    .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'binary')
-    .digest('base64');
-
-  socket.setTimeout(0);
-  socket.setNoDelay(true);
-
-  socket.write(
-    'HTTP/1.1 101 Switching Protocols\r\n' +
-    'Upgrade: websocket\r\n' +
-    'Connection: Upgrade\r\n' +
-    `Sec-WebSocket-Accept:${key}\r\n` +
-    '\r\n'
-  );
-
-  const sender = new Sender(socket);
-  const receiver = new Receiver();
-
-  receiver.onping = (message, flags) => server.emit('ping', message, flags);
-  receiver.onpong = (message, flags) => server.emit('pong', message, flags);
-  receiver.onmessage = (message, flags) => {
-    server.emit('message', message, flags);
-    sender.send(message, { binary: flags.binary, fin: true });
-  };
-  receiver.onclose = (code, message, flags) => {
-    sender.close(code, message, false, () => {
-      server.emit('close', code, message, flags);
-      socket.end();
-    });
-  };
-
-  socket.on('data', (data) => receiver.add(data));
-  socket.on('end', () => socket.end());
-}
-
-function invalidRequestHandler (server, req, socket) {
-  if (!req.headers.upgrade || req.headers.upgrade !== 'websocket') {
-    throw new Error('invalid headers');
-  }
-
-  if (!req.headers['sec-websocket-key']) {
-    throw new Error('websocket key is missing');
-  }
-
-  // calc key
-  const key = crypto.createHash('sha1')
-    .update(`${req.headers['sec-websocket-key']}bogus`, 'latin1')
-    .digest('base64');
-
-  socket.write(
-    'HTTP/1.1 101 Switching Protocols\r\n' +
-    'Upgrade: websocket\r\n' +
-    'Connection: Upgrade\r\n' +
-    `Sec-WebSocket-Accept:${key}\r\n` +
-    '\r\n'
-  );
-  socket.end();
-}
-
-function closeAfterConnectHandler (server, req, socket) {
-  if (!req.headers.upgrade || req.headers.upgrade !== 'websocket') {
-    throw new Error('invalid headers');
-  }
-
-  if (!req.headers['sec-websocket-key']) {
-    throw new Error('websocket key is missing');
-  }
-
-  // calc key
-  const key = crypto.createHash('sha1')
-    .update(`${req.headers['sec-websocket-key']}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`, 'latin1')
-    .digest('base64');
-
-  socket.write(
-    'HTTP/1.1 101 Switching Protocols\r\n' +
-    'Upgrade: websocket\r\n' +
-    'Connection: Upgrade\r\n' +
-    `Sec-WebSocket-Accept:${key}\r\n` +
-    '\r\n'
-  );
-  socket.end();
-}
-
-function return401 (server, req, socket) {
-  socket.write(
-    `HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` +
-    'Connection: close\r\n' +
-    'Content-type: text/html\r\n' +
-    'Content-Length: 12\r\n' +
-    '\r\n' +
-    'Not allowed!'
-  );
-  socket.end();
-}
-
-/**
- * Server object, which will do the actual emitting
- */
-class Server extends EventEmitter {
-  constructor (webServer) {
-    super();
-    this.webServer = webServer;
-  }
-
-  close (cb) {
-    this.webServer.close(cb);
-    if (this._socket) this._socket.end();
-  }
-}

From 286d513c1239bc2f1c9d23d997de1c954d3c8615 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 21 Mar 2017 21:43:25 +0100
Subject: [PATCH 313/669] [dist] 2.2.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index bc0bf88a5..81cdebf81 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "author": "Einar Otto Stangvik  (http://2x.io)",
   "name": "ws",
   "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
-  "version": "2.2.1",
+  "version": "2.2.2",
   "license": "MIT",
   "main": "index.js",
   "keywords": [

From 52d78421f29762add44c35528ab7de0da7673758 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 28 Mar 2017 07:55:32 +0200
Subject: [PATCH 314/669] [test] Use nyc for coverage

---
 .gitignore   | 3 ++-
 .travis.yml  | 6 ++----
 package.json | 8 +++-----
 3 files changed, 7 insertions(+), 10 deletions(-)

diff --git a/.gitignore b/.gitignore
index 1e80e9875..1f0951bf2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 node_modules/
+.nyc_output/
 coverage/
-npm-debug.log
 .vscode/
+npm-debug.log
diff --git a/.travis.yml b/.travis.yml
index 3a58aefe7..4627538a3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,7 +5,5 @@ node_js:
   - "7"
   - "6"
   - "4"
-script:
-  - "npm run test-travis"
-after_script:
-  - "npm install coveralls@2 && cat coverage/lcov.info | coveralls"
+after_success:
+  - "npm install coveralls@2 && nyc report --reporter=text-lcov | coveralls"
diff --git a/package.json b/package.json
index 81cdebf81..b7f8f7313 100644
--- a/package.json
+++ b/package.json
@@ -18,10 +18,8 @@
     "url": "git://github.com/websockets/ws.git"
   },
   "scripts": {
-    "test-travis": "npm run lint && istanbul cover _mocha --report lcovonly -- test/*.test.js",
-    "coverage": "istanbul cover _mocha --report html -- test/*.test.js",
-    "integration": "npm run lint && mocha test/*.integration.js",
-    "test": "npm run lint && mocha test/*.test.js",
+    "test": "eslint . && nyc --reporter=html --reporter=text mocha test/*.test.js",
+    "integration": "eslint . && mocha test/*.integration.js",
     "lint": "eslint ."
   },
   "dependencies": {
@@ -36,8 +34,8 @@
     "eslint-plugin-node": "~4.2.0",
     "eslint-plugin-promise": "~3.5.0",
     "eslint-plugin-standard": "~2.1.0",
-    "istanbul": "~0.4.5",
     "mocha": "~3.2.0",
+    "nyc": "~10.2.0",
     "utf-8-validate": "~3.0.0"
   }
 }

From 105374c94e2e08807f58ada9fe00fe0f4e0a2415 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 28 Mar 2017 10:37:06 +0200
Subject: [PATCH 315/669] [test] Increase code coverage

---
 lib/BufferUtil.js      |  2 +-
 lib/Validation.js      |  2 +-
 test/Sender.test.js    | 88 +++++++++++++++++++++++++-----------------
 test/WebSocket.test.js | 30 ++++++++++++++
 4 files changed, 84 insertions(+), 38 deletions(-)

diff --git a/lib/BufferUtil.js b/lib/BufferUtil.js
index bb1004672..b7e6d0ce4 100644
--- a/lib/BufferUtil.js
+++ b/lib/BufferUtil.js
@@ -31,7 +31,7 @@ try {
   const bufferUtil = require('bufferutil');
 
   module.exports = Object.assign({ concat }, bufferUtil.BufferUtil || bufferUtil);
-} catch (e) {
+} catch (e) /* istanbul ignore next */ {
   /**
    * Masks a buffer using the given mask.
    *
diff --git a/lib/Validation.js b/lib/Validation.js
index f0380555c..fcb170f31 100644
--- a/lib/Validation.js
+++ b/lib/Validation.js
@@ -12,6 +12,6 @@ try {
   module.exports = typeof isValidUTF8 === 'object'
     ? isValidUTF8.Validation.isValidUTF8  // utf-8-validate@<3.0.0
     : isValidUTF8;
-} catch (e) {
+} catch (e) /* istanbul ignore next */ {
   module.exports = () => true;
 }
diff --git a/test/Sender.test.js b/test/Sender.test.js
index 7c60c1e8b..e30d34332 100644
--- a/test/Sender.test.js
+++ b/test/Sender.test.js
@@ -34,42 +34,6 @@ describe('Sender', function () {
     });
   });
 
-  describe('#ping', function () {
-    it('works with multiple types of data', function (done) {
-      let count = 0;
-      const sender = new Sender({
-        write: (data) => {
-          assert.ok(data.equals(Buffer.from([0x89, 0x02, 0x68, 0x69])));
-          if (++count === 3) done();
-        }
-      });
-
-      const array = new Uint8Array([0x68, 0x69]);
-
-      sender.ping(array.buffer, false);
-      sender.ping(array, false);
-      sender.ping('hi', false);
-    });
-  });
-
-  describe('#pong', function () {
-    it('works with multiple types of data', function (done) {
-      let count = 0;
-      const sender = new Sender({
-        write: (data) => {
-          assert.ok(data.equals(Buffer.from([0x8a, 0x02, 0x68, 0x69])));
-          if (++count === 3) done();
-        }
-      });
-
-      const array = new Uint8Array([0x68, 0x69]);
-
-      sender.pong(array.buffer, false);
-      sender.pong(array, false);
-      sender.pong('hi', false);
-    });
-  });
-
   describe('#send', function () {
     it('compresses data if compress option is enabled', function (done) {
       const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
@@ -228,6 +192,58 @@ describe('Sender', function () {
     });
   });
 
+  describe('#ping', function () {
+    it('works with multiple types of data', function (done) {
+      const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
+      let count = 0;
+      const sender = new Sender({
+        write: (data) => {
+          if (++count === 1) return;
+
+          assert.ok(data.equals(Buffer.from([0x89, 0x02, 0x68, 0x69])));
+          if (count === 4) done();
+        }
+      }, {
+        'permessage-deflate': perMessageDeflate
+      });
+
+      perMessageDeflate.accept([{}]);
+
+      const array = new Uint8Array([0x68, 0x69]);
+
+      sender.send('foo', { compress: true, fin: true });
+      sender.ping(array.buffer, false);
+      sender.ping(array, false);
+      sender.ping('hi', false);
+    });
+  });
+
+  describe('#pong', function () {
+    it('works with multiple types of data', function (done) {
+      const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
+      let count = 0;
+      const sender = new Sender({
+        write: (data) => {
+          if (++count === 1) return;
+
+          assert.ok(data.equals(Buffer.from([0x8a, 0x02, 0x68, 0x69])));
+          if (count === 4) done();
+        }
+      }, {
+        'permessage-deflate': perMessageDeflate
+      });
+
+      perMessageDeflate.accept([{}]);
+
+      const array = new Uint8Array([0x68, 0x69]);
+
+      sender.send('foo', { compress: true, fin: true });
+      sender.pong(array.buffer, false);
+      sender.pong(array, false);
+      sender.pong('hi', false);
+    });
+  });
+
   describe('#close', function () {
     it('should consume all data before closing', function (done) {
       const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index fd065d67e..a78fcea69 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -1129,6 +1129,21 @@ describe('WebSocket', function () {
         });
       });
     });
+
+    it('does nothing if the connection is already CLOSED', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
+        const ws = new WebSocket(`ws://localhost:${port}`);
+
+        ws.on('close', (code) => {
+          assert.strictEqual(code, 1000);
+          assert.strictEqual(ws.readyState, WebSocket.CLOSED);
+          ws.close();
+          wss.close(done);
+        });
+      });
+
+      wss.on('connection', (ws) => ws.close());
+    });
   });
 
   describe('#terminate', function () {
@@ -1174,6 +1189,21 @@ describe('WebSocket', function () {
         ws.on('close', () => done());
       });
     });
+
+    it('does nothing if the connection is already CLOSED', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
+        const ws = new WebSocket(`ws://localhost:${port}`);
+
+        ws.on('close', (code) => {
+          assert.strictEqual(code, 1006);
+          assert.strictEqual(ws.readyState, WebSocket.CLOSED);
+          ws.terminate();
+          wss.close(done);
+        });
+      });
+
+      wss.on('connection', (ws) => ws.terminate());
+    });
   });
 
   describe('WHATWG API emulation', function () {

From c50a6b36a4a0be67416f9c9803964546fd9cd270 Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Sat, 1 Apr 2017 07:51:24 +0200
Subject: [PATCH 316/669] chore(package): update eslint to version 3.19.0
 (#1057)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index b7f8f7313..eee0fa3bf 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,7 @@
   "devDependencies": {
     "benchmark": "~2.1.2",
     "bufferutil": "~3.0.0",
-    "eslint": "~3.18.0",
+    "eslint": "~3.19.0",
     "eslint-config-standard": "~8.0.0-beta.1",
     "eslint-plugin-import": "~2.2.0",
     "eslint-plugin-node": "~4.2.0",

From 459809362a584693812277587f7e4212030bd294 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Sun, 2 Apr 2017 19:54:56 +0200
Subject: [PATCH 317/669] [minor] Use safe-buffer (#1059)

---
 .travis.yml                    | 1 +
 appveyor.yml                   | 1 +
 bench/parser.benchmark.js      | 6 +++++-
 bench/speed.js                 | 4 +++-
 lib/BufferUtil.js              | 8 ++++++--
 lib/Constants.js               | 4 ++++
 lib/PerMessageDeflate.js       | 7 +++++--
 lib/Receiver.js                | 4 ++++
 lib/Sender.js                  | 3 +++
 lib/WebSocketServer.js         | 3 +++
 package.json                   | 1 +
 test/PerMessageDeflate.test.js | 3 +++
 test/Receiver.test.js          | 3 +++
 test/Sender.test.js            | 3 +++
 test/Validation.test.js        | 3 +++
 test/WebSocket.test.js         | 2 ++
 test/WebSocketServer.test.js   | 2 ++
 test/hybi-util.js              | 4 ++++
 18 files changed, 56 insertions(+), 6 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 4627538a3..81cf3a9a2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,5 +5,6 @@ node_js:
   - "7"
   - "6"
   - "4"
+  - "4.1.0"
 after_success:
   - "npm install coveralls@2 && nyc report --reporter=text-lcov | coveralls"
diff --git a/appveyor.yml b/appveyor.yml
index 954f55608..d893ad905 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -3,6 +3,7 @@ environment:
     - nodejs_version: "7"
     - nodejs_version: "6"
     - nodejs_version: "4"
+    - nodejs_version: "4.1.0"
 platform:
   - x86
   - x64
diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js
index c28e82583..5e6259cb4 100644
--- a/bench/parser.benchmark.js
+++ b/bench/parser.benchmark.js
@@ -6,11 +6,15 @@
 
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const benchmark = require('benchmark');
 const crypto = require('crypto');
 
 const util = require('../test/hybi-util');
-const Receiver = require('../').Receiver;
+const WebSocket = require('..');
+
+const Receiver = WebSocket.Receiver;
+const Buffer = safeBuffer.Buffer;
 
 //
 // Override the `cleanup` method to make the "close message" test work as
diff --git a/bench/speed.js b/bench/speed.js
index 55047d5d8..b22e66883 100644
--- a/bench/speed.js
+++ b/bench/speed.js
@@ -1,9 +1,11 @@
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const cluster = require('cluster');
 
-const WebSocket = require('../');
+const WebSocket = require('..');
 
+const Buffer = safeBuffer.Buffer;
 const port = 8181;
 
 if (cluster.isMaster) {
diff --git a/lib/BufferUtil.js b/lib/BufferUtil.js
index b7e6d0ce4..6a35e8f43 100644
--- a/lib/BufferUtil.js
+++ b/lib/BufferUtil.js
@@ -1,11 +1,15 @@
-'use strict';
-
 /*!
  * ws: a node.js websocket client
  * Copyright(c) 2011 Einar Otto Stangvik 
  * MIT Licensed
  */
 
+'use strict';
+
+const safeBuffer = require('safe-buffer');
+
+const Buffer = safeBuffer.Buffer;
+
 /**
  * Merges an array of buffers into a new buffer.
  *
diff --git a/lib/Constants.js b/lib/Constants.js
index ce7ed989f..390441462 100644
--- a/lib/Constants.js
+++ b/lib/Constants.js
@@ -1,5 +1,9 @@
 'use strict';
 
+const safeBuffer = require('safe-buffer');
+
+const Buffer = safeBuffer.Buffer;
+
 exports.BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments'];
 exports.GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
 exports.EMPTY_BUFFER = Buffer.alloc(0);
diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js
index 192d530ef..c1a1d3c70 100644
--- a/lib/PerMessageDeflate.js
+++ b/lib/PerMessageDeflate.js
@@ -1,14 +1,17 @@
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const zlib = require('zlib');
 
 const bufferUtil = require('./BufferUtil');
 
+const Buffer = safeBuffer.Buffer;
+
 const AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15];
-const DEFAULT_WINDOW_BITS = 15;
-const DEFAULT_MEM_LEVEL = 8;
 const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
 const EMPTY_BLOCK = Buffer.from([0x00]);
+const DEFAULT_WINDOW_BITS = 15;
+const DEFAULT_MEM_LEVEL = 8;
 
 /**
  * Per-message Deflate implementation.
diff --git a/lib/Receiver.js b/lib/Receiver.js
index 54cb409b3..6c1a10e2c 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -6,12 +6,16 @@
 
 'use strict';
 
+const safeBuffer = require('safe-buffer');
+
 const PerMessageDeflate = require('./PerMessageDeflate');
 const isValidUTF8 = require('./Validation');
 const bufferUtil = require('./BufferUtil');
 const ErrorCodes = require('./ErrorCodes');
 const constants = require('./Constants');
 
+const Buffer = safeBuffer.Buffer;
+
 const GET_INFO = 0;
 const GET_PAYLOAD_LENGTH_16 = 1;
 const GET_PAYLOAD_LENGTH_64 = 2;
diff --git a/lib/Sender.js b/lib/Sender.js
index 0df22dbb8..b33bfd4d8 100644
--- a/lib/Sender.js
+++ b/lib/Sender.js
@@ -6,12 +6,15 @@
 
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const crypto = require('crypto');
 
 const PerMessageDeflate = require('./PerMessageDeflate');
 const bufferUtil = require('./BufferUtil');
 const ErrorCodes = require('./ErrorCodes');
 
+const Buffer = safeBuffer.Buffer;
+
 /**
  * HyBi Sender implementation.
  */
diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js
index 5170375ba..e78efc1aa 100644
--- a/lib/WebSocketServer.js
+++ b/lib/WebSocketServer.js
@@ -6,6 +6,7 @@
 
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const EventEmitter = require('events');
 const crypto = require('crypto');
 const Ultron = require('ultron');
@@ -17,6 +18,8 @@ const Extensions = require('./Extensions');
 const constants = require('./Constants');
 const WebSocket = require('./WebSocket');
 
+const Buffer = safeBuffer.Buffer;
+
 /**
  * Class representing a WebSocket server.
  *
diff --git a/package.json b/package.json
index eee0fa3bf..444b22b1b 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
     "lint": "eslint ."
   },
   "dependencies": {
+    "safe-buffer": "~5.0.1",
     "ultron": "~1.1.0"
   },
   "devDependencies": {
diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js
index 3da9de4bc..dbaf67e4c 100644
--- a/test/PerMessageDeflate.test.js
+++ b/test/PerMessageDeflate.test.js
@@ -1,10 +1,13 @@
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const assert = require('assert');
 
 const PerMessageDeflate = require('../lib/PerMessageDeflate');
 const Extensions = require('../lib/Extensions');
 
+const Buffer = safeBuffer.Buffer;
+
 describe('PerMessageDeflate', function () {
   describe('#offer', function () {
     it('should create default params', function () {
diff --git a/test/Receiver.test.js b/test/Receiver.test.js
index 54948a2c8..6caed81a5 100644
--- a/test/Receiver.test.js
+++ b/test/Receiver.test.js
@@ -1,5 +1,6 @@
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const assert = require('assert');
 const crypto = require('crypto');
 
@@ -8,6 +9,8 @@ const Receiver = require('../lib/Receiver');
 const Sender = require('../lib/Sender');
 const util = require('./hybi-util');
 
+const Buffer = safeBuffer.Buffer;
+
 describe('Receiver', function () {
   it('can parse unmasked text message', function (done) {
     const p = new Receiver();
diff --git a/test/Sender.test.js b/test/Sender.test.js
index e30d34332..385011476 100644
--- a/test/Sender.test.js
+++ b/test/Sender.test.js
@@ -1,10 +1,13 @@
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const assert = require('assert');
 
 const PerMessageDeflate = require('../lib/PerMessageDeflate');
 const Sender = require('../lib/Sender');
 
+const Buffer = safeBuffer.Buffer;
+
 describe('Sender', function () {
   describe('.frame', function () {
     it('does not mutate the input buffer if data is `readOnly`', function () {
diff --git a/test/Validation.test.js b/test/Validation.test.js
index d13cf460a..88dc37aa5 100644
--- a/test/Validation.test.js
+++ b/test/Validation.test.js
@@ -1,9 +1,12 @@
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const assert = require('assert');
 
 const isValidUTF8 = require('../lib/Validation');
 
+const Buffer = safeBuffer.Buffer;
+
 describe('Validation', function () {
   describe('isValidUTF8', function () {
     it('should return true for a valid utf8 string', function () {
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index a78fcea69..2f629232f 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -2,6 +2,7 @@
 
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const assert = require('assert');
 const crypto = require('crypto');
 const https = require('https');
@@ -11,6 +12,7 @@ const fs = require('fs');
 const constants = require('../lib/Constants');
 const WebSocket = require('..');
 
+const Buffer = safeBuffer.Buffer;
 const WebSocketServer = WebSocket.Server;
 let port = 20000;
 
diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js
index afb3ded9c..3930592bf 100644
--- a/test/WebSocketServer.test.js
+++ b/test/WebSocketServer.test.js
@@ -2,6 +2,7 @@
 
 'use strict';
 
+const safeBuffer = require('safe-buffer');
 const assert = require('assert');
 const crypto = require('crypto');
 const https = require('https');
@@ -11,6 +12,7 @@ const fs = require('fs');
 
 const WebSocket = require('..');
 
+const Buffer = safeBuffer.Buffer;
 const WebSocketServer = WebSocket.Server;
 let port = 8000;
 
diff --git a/test/hybi-util.js b/test/hybi-util.js
index cdff71f5a..3f0196b92 100644
--- a/test/hybi-util.js
+++ b/test/hybi-util.js
@@ -6,6 +6,10 @@
 
 'use strict';
 
+const safeBuffer = require('safe-buffer');
+
+const Buffer = safeBuffer.Buffer;
+
 /**
  * Performs hybi07+ type masking.
  */

From 20bd7c7c1a88c181008e3ce822c2035d9eec9165 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Sun, 2 Apr 2017 22:12:12 +0200
Subject: [PATCH 318/669] [fix] Don't reassign the `options` argument when
 `protocols` is `null`

---
 lib/WebSocket.js       | 11 ++++++-----
 test/WebSocket.test.js | 25 ++++++++++++++++---------
 2 files changed, 22 insertions(+), 14 deletions(-)

diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index a66c12814..21a9f105e 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -39,14 +39,15 @@ class WebSocket extends EventEmitter {
   constructor (address, protocols, options) {
     super();
 
-    if (typeof protocols === 'object' && !Array.isArray(protocols)) {
+    if (!protocols) {
+      protocols = [];
+    } else if (typeof protocols === 'string') {
+      protocols = [protocols];
+    } else if (!Array.isArray(protocols)) {
       options = protocols;
-      protocols = null;
+      protocols = [];
     }
 
-    if (typeof protocols === 'string') protocols = [protocols];
-    if (!Array.isArray(protocols)) protocols = [];
-
     this.readyState = WebSocket.CONNECTING;
     this.bytesReceived = 0;
     this.extensions = {};
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index 2f629232f..72c4343d2 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -31,7 +31,7 @@ describe('WebSocket', function () {
   });
 
   describe('options', function () {
-    it('should accept an `agent` option', function (done) {
+    it('accepts an `agent` option', function (done) {
       const agent = new CustomAgent();
 
       agent.addRequest = () => {
@@ -41,11 +41,18 @@ describe('WebSocket', function () {
       const ws = new WebSocket('ws://localhost', { agent });
     });
 
-    // GH-227
-    it('should accept the `options` object as the 3rd argument', function () {
-      const ws = new WebSocket('ws://localhost', [], {
-        agent: new CustomAgent()
-      });
+    it('accepts the `options` object as the 3rd argument', function () {
+      const agent = new CustomAgent();
+      let count = 0;
+      let ws;
+
+      agent.addRequest = (req) => count++;
+
+      ws = new WebSocket('ws://localhost', undefined, { agent });
+      ws = new WebSocket('ws://localhost', null, { agent });
+      ws = new WebSocket('ws://localhost', [], { agent });
+
+      assert.strictEqual(count, 3);
     });
 
     it('throws an error when using an invalid `protocolVersion`', function () {
@@ -57,7 +64,7 @@ describe('WebSocket', function () {
       );
     });
 
-    it('should accept the localAddress option', function (done) {
+    it('accepts the localAddress option', function (done) {
       //
       // Skip this test on macOS as by default all loopback addresses other
       // than 127.0.0.1 are disabled.
@@ -76,14 +83,14 @@ describe('WebSocket', function () {
       });
     });
 
-    it('should accept the localAddress option whether it was wrong interface', function () {
+    it('accepts the localAddress option whether it was wrong interface', function () {
       assert.throws(
         () => new WebSocket(`ws://localhost:${port}`, { localAddress: '123.456.789.428' }),
         /must be a valid IP: 123.456.789.428/
       );
     });
 
-    it('should accept the family option', function (done) {
+    it('accepts the family option', function (done) {
       const wss = new WebSocketServer({ host: '::1', port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 });
       });

From 212c7aab04a5f23d89111c1722371211efa2dd89 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Mon, 3 Apr 2017 11:58:56 +0200
Subject: [PATCH 319/669] [dist] 2.2.3

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 444b22b1b..88c7a9d14 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "author": "Einar Otto Stangvik  (http://2x.io)",
   "name": "ws",
   "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
-  "version": "2.2.2",
+  "version": "2.2.3",
   "license": "MIT",
   "main": "index.js",
   "keywords": [

From 0d7cd2b5bde5bdb0e16ae5d4155255cfa779625f Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Wed, 5 Apr 2017 07:43:14 +0200
Subject: [PATCH 320/669] chore(package): update eslint-plugin-standard to
 version 2.2.0 (#1064)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 88c7a9d14..8546a3963 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
     "eslint-plugin-import": "~2.2.0",
     "eslint-plugin-node": "~4.2.0",
     "eslint-plugin-promise": "~3.5.0",
-    "eslint-plugin-standard": "~2.1.0",
+    "eslint-plugin-standard": "~2.2.0",
     "mocha": "~3.2.0",
     "nyc": "~10.2.0",
     "utf-8-validate": "~3.0.0"

From a4cc55d1c707e1515bfdba6da357231443454b39 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Wed, 5 Apr 2017 07:59:54 +0200
Subject: [PATCH 321/669] chore(package): update eslint-config-standard to
 version 10.0.0 (#1065)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 8546a3963..4b94ab1c1 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
     "benchmark": "~2.1.2",
     "bufferutil": "~3.0.0",
     "eslint": "~3.19.0",
-    "eslint-config-standard": "~8.0.0-beta.1",
+    "eslint-config-standard": "~10.0.0",
     "eslint-plugin-import": "~2.2.0",
     "eslint-plugin-node": "~4.2.0",
     "eslint-plugin-promise": "~3.5.0",

From 617aeacaf96f2ac31f437354734ce53fc9cdc73c Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Thu, 6 Apr 2017 10:14:11 +0200
Subject: [PATCH 322/669] [minor] Make all hooks have access to the upgrade
 request

Closes #787
Fixes #783
Fixes #683
Fixes #525
---
 doc/ws.md                    |  4 +++-
 lib/WebSocketServer.js       |  6 +++---
 test/WebSocketServer.test.js | 33 ++++++++++++++++++++++++++-------
 3 files changed, 32 insertions(+), 11 deletions(-)

diff --git a/doc/ws.md b/doc/ws.md
index 8893edf47..af3b8556d 100644
--- a/doc/ws.md
+++ b/doc/ws.md
@@ -51,10 +51,11 @@ if `verifyClient` is provided with two arguments then those are:
 
 
 If `handleProtocols` is not set then the handshake is automatically accepted,
-otherwise the function takes a single argument:
+otherwise the function takes two arguments:
 
 - `protocols` {Array} The list of WebSocket subprotocols indicated by the
   client in the `Sec-WebSocket-Protocol` header.
+- `request` {http.IncomingMessage} The client HTTP GET request.
 
 If returned value is `false` then the handshake is rejected with the HTTP 401
 status code, otherwise the returned value sets the value of the
@@ -100,6 +101,7 @@ Emitted when an error occurs on the underlying server.
 ### Event: 'headers'
 
 - `headers` {Array}
+- `request` {http.IncomingMessage}
 
 Emitted before the response headers are written to the socket as part of the
 handshake. This allows you to inspect/modify the headers before they are sent.
diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js
index e78efc1aa..bd3ef24e3 100644
--- a/lib/WebSocketServer.js
+++ b/lib/WebSocketServer.js
@@ -168,7 +168,7 @@ class WebSocketServer extends EventEmitter {
     // Optionally call external protocol selection handler.
     //
     if (this.options.handleProtocols) {
-      protocol = this.options.handleProtocols(protocol);
+      protocol = this.options.handleProtocols(protocol, req);
       if (protocol === false) return abortConnection(socket, 401);
     } else {
       protocol = protocol[0];
@@ -252,11 +252,11 @@ class WebSocketServer extends EventEmitter {
     //
     // Allow external modification/inspection of handshake headers.
     //
-    this.emit('headers', headers);
+    this.emit('headers', headers, req);
 
     socket.write(headers.concat('', '').join('\r\n'));
 
-    const client = new WebSocket([req, socket, head], {
+    const client = new WebSocket([req, socket, head], null, {
       maxPayload: this.options.maxPayload,
       protocolVersion: version,
       extensions,
diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js
index 3930592bf..6092e3283 100644
--- a/test/WebSocketServer.test.js
+++ b/test/WebSocketServer.test.js
@@ -801,7 +801,7 @@ describe('WebSocketServer', function () {
       const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']);
 
-        ws.on('open', (client) => {
+        ws.on('open', () => {
           assert.strictEqual(ws.protocol, 'prot1');
           wss.close();
           done();
@@ -810,16 +810,17 @@ describe('WebSocketServer', function () {
     });
 
     it('selects the last protocol via protocol handler', function (done) {
-      const wss = new WebSocketServer({
-        handleProtocols: (ps) => ps[ps.length - 1],
-        port: ++port
-      }, () => {
+      const handleProtocols = (protocols, request) => {
+        assert.ok(request instanceof http.IncomingMessage);
+        assert.strictEqual(request.url, '/');
+        return protocols.pop();
+      };
+      const wss = new WebSocketServer({ handleProtocols, port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']);
 
         ws.on('open', () => {
           assert.strictEqual(ws.protocol, 'prot2');
-          wss.close();
-          done();
+          wss.close(done);
         });
       });
     });
@@ -903,6 +904,24 @@ describe('WebSocketServer', function () {
         done();
       });
     });
+
+    it('emits the `headers` event', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
+        const ws = new WebSocket(`ws://localhost:${port}`);
+
+        wss.on('headers', (headers, request) => {
+          assert.deepStrictEqual(headers.slice(0, 3), [
+            'HTTP/1.1 101 Switching Protocols',
+            'Upgrade: websocket',
+            'Connection: Upgrade'
+          ]);
+          assert.ok(request instanceof http.IncomingMessage);
+          assert.strictEqual(request.url, '/');
+
+          wss.on('connection', () => wss.close(done));
+        });
+      });
+    });
   });
 
   describe('messaging', function () {

From 13cff408419fef5a2ccc882ba32adfbcced5a771 Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Thu, 6 Apr 2017 20:58:53 +0200
Subject: [PATCH 323/669] chore(package): update eslint-config-standard to
 version 10.1.0 (#1072)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 4b94ab1c1..d010bd667 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
     "benchmark": "~2.1.2",
     "bufferutil": "~3.0.0",
     "eslint": "~3.19.0",
-    "eslint-config-standard": "~10.0.0",
+    "eslint-config-standard": "~10.1.0",
     "eslint-plugin-import": "~2.2.0",
     "eslint-plugin-node": "~4.2.0",
     "eslint-plugin-promise": "~3.5.0",

From 26bf7754ecd9364f55f1d4e89a3ea09bd06f4d02 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Thu, 6 Apr 2017 20:59:38 +0200
Subject: [PATCH 324/669] chore(package): update eslint-plugin-standard to
 version 3.0.0 (#1074)

Closes #1073

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index d010bd667..8c085612f 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
     "eslint-plugin-import": "~2.2.0",
     "eslint-plugin-node": "~4.2.0",
     "eslint-plugin-promise": "~3.5.0",
-    "eslint-plugin-standard": "~2.2.0",
+    "eslint-plugin-standard": "~3.0.0",
     "mocha": "~3.2.0",
     "nyc": "~10.2.0",
     "utf-8-validate": "~3.0.0"

From c4202c67427c485d0f6a4c4c0afb3bdd6f7b538c Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Fri, 7 Apr 2017 07:32:54 +0200
Subject: [PATCH 325/669] chore(package): update eslint-config-standard to
 version 10.2.0 (#1075)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 8c085612f..32e05a969 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
     "benchmark": "~2.1.2",
     "bufferutil": "~3.0.0",
     "eslint": "~3.19.0",
-    "eslint-config-standard": "~10.1.0",
+    "eslint-config-standard": "~10.2.0",
     "eslint-plugin-import": "~2.2.0",
     "eslint-plugin-node": "~4.2.0",
     "eslint-plugin-promise": "~3.5.0",

From 2efbdbc7ab3f0deb2ad8c891c969635486ae6824 Mon Sep 17 00:00:00 2001
From: Daniel Gonzalez Reina 
Date: Tue, 11 Apr 2017 16:59:01 +0200
Subject: [PATCH 326/669] Added a note to tell possible confused users that
 they should be using the native WebSocket in the browser

---
 README.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/README.md b/README.md
index 714f1a8d1..dc4863364 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,8 @@ and server implementation.
 Passes the quite extensive Autobahn test suite. See http://websockets.github.io/ws/
 for the full reports.
 
+**Note for using `ws` in the browser**: This is a node.js library, and for the browser you should use the [native WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications). The client in the docs are a reference to a back end with the role of a client in the WebSocket communication.
+
 ## Protocol support
 
 * **HyBi drafts 07-12** (Use the option `protocolVersion: 8`)

From 2bacadca97f9dbdf67a802e216f34b966f41e0a2 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Thu, 13 Apr 2017 09:58:57 +0200
Subject: [PATCH 327/669] [doc] Update optional modules section

---
 README.md | 29 ++++++++++++++---------------
 1 file changed, 14 insertions(+), 15 deletions(-)

diff --git a/README.md b/README.md
index dc4863364..f4ab1894c 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,10 @@ and server implementation.
 Passes the quite extensive Autobahn test suite. See http://websockets.github.io/ws/
 for the full reports.
 
-**Note for using `ws` in the browser**: This is a node.js library, and for the browser you should use the [native WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications). The client in the docs are a reference to a back end with the role of a client in the WebSocket communication.
+**Note**: This module does not work in the browser. The client in the docs is a
+reference to a back end with the role of a client in the WebSocket
+communication. Browser clients must use the native
+[`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) object.
 
 ## Protocol support
 
@@ -24,22 +27,18 @@ for the full reports.
 npm install --save ws
 ```
 
-### Opt-in for performance
+### Opt-in for performance and spec compliance
 
 There are 2 optional modules that can be installed along side with the `ws`
-module. These modules are binary addons which improve certain operations, but as
-they are binary addons they require compilation which can fail if no c++
-compiler is installed on the host system.
-
-- `npm install --save bufferutil`: Improves internal buffer operations which
-  allows for faster processing of masked WebSocket frames and general buffer
-  operations.
-- `npm install --save utf-8-validate`: The specification requires validation of
-  invalid UTF-8 chars, some of these validations could not be done in JavaScript
-  hence the need for a binary addon. In most cases you will already be
-  validating the input that you receive for security purposes leading to double
-  validation. But if you want to be 100% spec-conforming and have fast
-  validation of UTF-8 then this module is a must.
+module. These modules are binary addons which improve certain operations.
+Prebuilt binaries are available for the most popular platforms so you don't
+necessarily need to have a C++ compiler installed on your machine.
+
+- `npm install --save-optional bufferutil`: Allows to efficiently perform
+  operations such as masking and unmasking the data payload of the WebSocket
+  frames.
+- `npm install --save-optional utf-8-validate`: Allows to efficiently check
+  if a message contains valid UTF-8 as required by the spec.
 
 ## API Docs
 

From e182e96d2db4dadf347fd1bd04628a8b70b56ce8 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Fri, 14 Apr 2017 07:51:10 +0200
Subject: [PATCH 328/669] [doc] Fix typo in README.md

---
 README.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index f4ab1894c..1ca0bdb91 100644
--- a/README.md
+++ b/README.md
@@ -47,9 +47,9 @@ for Node.js-like docs for the ws classes.
 
 ## WebSocket compression
 
-`ws` supports the [permessage-deflate extension][permessage-deflate] extension
-which enables the client and server to negotiate a compression algorithm and
-its parameters, and then selectively apply it to the data payloads of each
+`ws` supports the [permessage-deflate extension][permessage-deflate] which
+enables the client and server to negotiate a compression algorithm and its
+parameters, and then selectively apply it to the data payloads of each
 WebSocket message.
 
 The extension is enabled by default but adds a significant overhead in terms of

From 3dbb58a82e118707fb08dc12c8c4e3f5c67f1005 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 18 Apr 2017 13:59:32 +0200
Subject: [PATCH 329/669] [perf] Prevent `Sender.frame()` from being
 deoptimized

---
 lib/Sender.js | 47 +++++++++++++++++++++++------------------------
 1 file changed, 23 insertions(+), 24 deletions(-)

diff --git a/lib/Sender.js b/lib/Sender.js
index b33bfd4d8..79e68a5a3 100644
--- a/lib/Sender.js
+++ b/lib/Sender.js
@@ -140,11 +140,11 @@ class Sender {
    */
   doClose (data, mask, cb) {
     this.sendFrame(Sender.frame(data, {
-      readOnly: false,
-      opcode: 0x08,
-      rsv1: false,
       fin: true,
-      mask
+      rsv1: false,
+      opcode: 0x08,
+      mask,
+      readOnly: false
     }), cb);
   }
 
@@ -186,11 +186,11 @@ class Sender {
    */
   doPing (data, mask, readOnly) {
     this.sendFrame(Sender.frame(data, {
-      opcode: 0x09,
-      rsv1: false,
       fin: true,
-      readOnly,
-      mask
+      rsv1: false,
+      opcode: 0x09,
+      mask,
+      readOnly
     }));
   }
 
@@ -232,11 +232,11 @@ class Sender {
    */
   doPong (data, mask, readOnly) {
     this.sendFrame(Sender.frame(data, {
-      opcode: 0x0a,
-      rsv1: false,
       fin: true,
-      readOnly,
-      mask
+      rsv1: false,
+      opcode: 0x0a,
+      mask,
+      readOnly
     }));
   }
 
@@ -283,26 +283,25 @@ class Sender {
 
     if (this.perMessageDeflate) {
       const opts = {
-        compress: this.compress,
-        mask: options.mask,
         fin: options.fin,
-        readOnly,
+        rsv1,
         opcode,
-        rsv1
+        mask: options.mask,
+        readOnly
       };
 
       if (this.deflating) {
-        this.enqueue([this.dispatch, data, opts, cb]);
+        this.enqueue([this.dispatch, data, this.compress, opts, cb]);
       } else {
-        this.dispatch(data, opts, cb);
+        this.dispatch(data, this.compress, opts, cb);
       }
     } else {
       this.sendFrame(Sender.frame(data, {
-        mask: options.mask,
         fin: options.fin,
         rsv1: false,
-        readOnly,
-        opcode
+        opcode,
+        mask: options.mask,
+        readOnly
       }), cb);
     }
   }
@@ -311,18 +310,18 @@ class Sender {
    * Dispatches a data message.
    *
    * @param {Buffer} data The message to send
+   * @param {Boolean} compress Specifies whether or not to compress `data`
    * @param {Object} options Options object
    * @param {Number} options.opcode The opcode
    * @param {Boolean} options.readOnly Specifies whether `data` can be modified
    * @param {Boolean} options.fin Specifies whether or not to set the FIN bit
-   * @param {Boolean} options.compress Specifies whether or not to compress `data`
    * @param {Boolean} options.mask Specifies whether or not to mask `data`
    * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
    * @param {Function} cb Callback
    * @private
    */
-  dispatch (data, options, cb) {
-    if (!options.compress) {
+  dispatch (data, compress, options, cb) {
+    if (!compress) {
       this.sendFrame(Sender.frame(data, options), cb);
       return;
     }

From b5b095474a4a25aad89b5cff97295c4c64c499b2 Mon Sep 17 00:00:00 2001
From: Jason Walton 
Date: Thu, 20 Apr 2017 09:37:38 -0400
Subject: [PATCH 330/669] [feature] Add headers event to WebSocket (#1082)

---
 doc/ws.md              |  9 +++++++++
 lib/WebSocket.js       |  2 ++
 test/WebSocket.test.js | 10 ++++++++++
 3 files changed, 21 insertions(+)

diff --git a/doc/ws.md b/doc/ws.md
index af3b8556d..cdb1f03e5 100644
--- a/doc/ws.md
+++ b/doc/ws.md
@@ -226,6 +226,15 @@ human-readable string explaining why the connection has been closed.
 Emitted when an error occurs. Errors from the underlying `net.Socket` are
 forwarded here.
 
+### Event: 'headers'
+
+- `headers` {Object}
+- `response` {http.IncomingMessage}
+
+Emitted when response headers are received from the server as part of the
+handshake.  This allows you to read headers from the server, for example
+'set-cookie' headers.
+
 ### Event: 'message'
 
 - `data` {String|Buffer}
diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index 21a9f105e..b835cdfe1 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -655,6 +655,8 @@ function initAsClient (address, protocols, options) {
   this._req.on('upgrade', (res, socket, head) => {
     this._req = null;
 
+    this.emit('headers', res.headers, res);
+
     const digest = crypto.createHash('sha1')
       .update(key + constants.GUID, 'binary')
       .digest('base64');
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index 72c4343d2..348e112d7 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -328,6 +328,16 @@ describe('WebSocket', function () {
 
       wss.on('connection', (client) => client.pong());
     });
+
+    it('emits a headers event', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
+        const ws = new WebSocket(`ws://localhost:${port}`);
+        ws.on('headers', (headers, res) => {
+          assert.strictEqual(headers, res.headers);
+          wss.close(done);
+        });
+      });
+    });
   });
 
   describe('connection establishing', function () {

From 2d66ec92f2dc58036725b8ff77ae13b884e48040 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Thu, 20 Apr 2017 15:52:51 +0200
Subject: [PATCH 331/669] [pkg] Remove .npmignore in favor of `files`
 package.json field

---
 .npmignore   |  7 -------
 package.json | 19 +++++++++++--------
 2 files changed, 11 insertions(+), 15 deletions(-)
 delete mode 100644 .npmignore

diff --git a/.npmignore b/.npmignore
deleted file mode 100644
index 7c56d8c06..000000000
--- a/.npmignore
+++ /dev/null
@@ -1,7 +0,0 @@
-coverage/
-examples/
-bench/
-test/
-doc/
-appveyor.yml
-.*
diff --git a/package.json b/package.json
index 32e05a969..16f8b268d 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,7 @@
 {
-  "author": "Einar Otto Stangvik  (http://2x.io)",
   "name": "ws",
-  "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
   "version": "2.2.3",
-  "license": "MIT",
-  "main": "index.js",
+  "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
   "keywords": [
     "HyBi",
     "Push",
@@ -13,10 +10,16 @@
     "WebSockets",
     "real-time"
   ],
-  "repository": {
-    "type": "git",
-    "url": "git://github.com/websockets/ws.git"
-  },
+  "homepage": "https://github.com/websockets/ws",
+  "bugs": "https://github.com/websockets/ws/issues",
+  "repository": "websockets/ws",
+  "author": "Einar Otto Stangvik  (http://2x.io)",
+  "license": "MIT",
+  "main": "index.js",
+  "files": [
+    "index.js",
+    "lib"
+  ],
   "scripts": {
     "test": "eslint . && nyc --reporter=html --reporter=text mocha test/*.test.js",
     "integration": "eslint . && mocha test/*.integration.js",

From 309d77f27ac2a505261c9d17dcfcf1a7ad0b1cae Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Thu, 20 Apr 2017 15:54:52 +0200
Subject: [PATCH 332/669] [dist] 2.3.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 16f8b268d..41892ed9e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "ws",
-  "version": "2.2.3",
+  "version": "2.3.0",
   "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
   "keywords": [
     "HyBi",

From 3633d6c5226c2fa47fcf1bcfca7047b749816cfc Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Thu, 20 Apr 2017 18:47:59 +0200
Subject: [PATCH 333/669] [fix] Make `WebSocket#close()` work when called from
 a headers listener

---
 lib/WebSocket.js       | 10 ++++++++--
 test/WebSocket.test.js | 28 ++++++++++++++++++++++++++++
 2 files changed, 36 insertions(+), 2 deletions(-)

diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index b835cdfe1..41868d832 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -653,10 +653,16 @@ function initAsClient (address, protocols, options) {
   });
 
   this._req.on('upgrade', (res, socket, head) => {
-    this._req = null;
-
     this.emit('headers', res.headers, res);
 
+    //
+    // The user may have closed the connection from a listener of the `headers`
+    // event.
+    //
+    if (this.readyState !== WebSocket.CONNECTING) return;
+
+    this._req = null;
+
     const digest = crypto.createHash('sha1')
       .update(key + constants.GUID, 'binary')
       .digest('base64');
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index 348e112d7..78d5ebaa3 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -999,6 +999,20 @@ describe('WebSocket', function () {
       });
     });
 
+    it('can be called from a listener of the headers event', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
+        const ws = new WebSocket(`ws://localhost:${port}`);
+
+        ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
+        ws.on('error', (err) => {
+          assert.ok(err instanceof Error);
+          assert.strictEqual(err.message, 'closed before the connection is established');
+          ws.on('close', () => wss.close(done));
+        });
+        ws.on('headers', () => ws.close());
+      });
+    });
+
     it('throws an error if the first argument is invalid (1/2)', function (done) {
       const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
@@ -1209,6 +1223,20 @@ describe('WebSocket', function () {
       });
     });
 
+    it('can be called from a listener of the headers event', function (done) {
+      const wss = new WebSocketServer({ port: ++port }, () => {
+        const ws = new WebSocket(`ws://localhost:${port}`);
+
+        ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here'));
+        ws.on('error', (err) => {
+          assert.ok(err instanceof Error);
+          assert.strictEqual(err.message, 'closed before the connection is established');
+          ws.on('close', () => wss.close(done));
+        });
+        ws.on('headers', () => ws.terminate());
+      });
+    });
+
     it('does nothing if the connection is already CLOSED', function (done) {
       const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);

From 732aaf06b76700f104eeff2740e1896be4e88199 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Thu, 20 Apr 2017 19:40:11 +0200
Subject: [PATCH 334/669] [dist] 2.3.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 41892ed9e..6a95c7375 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "ws",
-  "version": "2.3.0",
+  "version": "2.3.1",
   "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
   "keywords": [
     "HyBi",

From 6ade76ddd1637da28a91dd3b7f69e93daef655f8 Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Mon, 24 Apr 2017 15:27:06 +0200
Subject: [PATCH 335/669] chore(package): update mocha to version 3.3.0 (#1086)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 6a95c7375..2dc589f55 100644
--- a/package.json
+++ b/package.json
@@ -38,7 +38,7 @@
     "eslint-plugin-node": "~4.2.0",
     "eslint-plugin-promise": "~3.5.0",
     "eslint-plugin-standard": "~3.0.0",
-    "mocha": "~3.2.0",
+    "mocha": "~3.3.0",
     "nyc": "~10.2.0",
     "utf-8-validate": "~3.0.0"
   }

From d75ab9dc91e98673a9fcfdf585d5a0c8cfc48e99 Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" 
Date: Sat, 29 Apr 2017 09:51:42 +0200
Subject: [PATCH 336/669] chore(package): update nyc to version 10.3.0 (#1091)

https://greenkeeper.io/
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 2dc589f55..79d2938d9 100644
--- a/package.json
+++ b/package.json
@@ -39,7 +39,7 @@
     "eslint-plugin-promise": "~3.5.0",
     "eslint-plugin-standard": "~3.0.0",
     "mocha": "~3.3.0",
-    "nyc": "~10.2.0",
+    "nyc": "~10.3.0",
     "utf-8-validate": "~3.0.0"
   }
 }

From cce4d0f096169c4cd99592e2895da310fec19e19 Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Wed, 3 May 2017 10:34:18 +0200
Subject: [PATCH 337/669] [test] Add callback to `wss.close()`

---
 test/WebSocket.test.js       | 24 +++------
 test/WebSocketServer.test.js | 99 +++++++++++-------------------------
 2 files changed, 35 insertions(+), 88 deletions(-)

diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index 78d5ebaa3..e93bc6a83 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -108,8 +108,7 @@ describe('WebSocket', function () {
         const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: false });
         ws.on('message', () => {
           assert.strictEqual(ws.bytesReceived, 8);
-          wss.close();
-          done();
+          wss.close(done);
         });
       });
       wss.on('connection', (ws) => ws.send('foobar'));
@@ -308,10 +307,7 @@ describe('WebSocket', function () {
     it('emits a ping event', function (done) {
       const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
-        ws.on('ping', function () {
-          wss.close();
-          done();
-        });
+        ws.on('ping', () => wss.close(done));
       });
 
       wss.on('connection', (client) => client.ping());
@@ -320,10 +316,7 @@ describe('WebSocket', function () {
     it('emits a pong event', function (done) {
       const wss = new WebSocketServer({ port: ++port }, () => {
         const ws = new WebSocket(`ws://localhost:${port}`);
-        ws.on('pong', () => {
-          wss.close();
-          done();
-        });
+        ws.on('pong', () => wss.close(done));
       });
 
       wss.on('connection', (client) => client.pong());
@@ -515,8 +508,7 @@ describe('WebSocket', function () {
         let paused = true;
         serverClient.on('message', () => {
           assert.ok(!paused);
-          wss.close();
-          done();
+          wss.close(done);
         });
         serverClient.pause();
 
@@ -1427,9 +1419,7 @@ describe('WebSocket', function () {
         ws.addEventListener('close', (closeEvent) => {
           assert.ok(closeEvent.wasClean);
           assert.strictEqual(closeEvent.code, 1000);
-
-          wss.close();
-          done();
+          wss.close(done);
         });
       });
 
@@ -1444,9 +1434,7 @@ describe('WebSocket', function () {
           assert.ok(!closeEvent.wasClean);
           assert.strictEqual(closeEvent.code, 1001);
           assert.strictEqual(closeEvent.reason, 'some daft reason');
-
-          wss.close();
-          done();
+          wss.close(done);
         });
       });
 
diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js
index 6092e3283..801db7d6a 100644
--- a/test/WebSocketServer.test.js
+++ b/test/WebSocketServer.test.js
@@ -264,8 +264,7 @@ describe('WebSocketServer', function () {
     it('exposes options passed to constructor', function (done) {
       const wss = new WebSocketServer({ port: ++port }, () => {
         assert.strictEqual(wss.options.port, port);
-        wss.close();
-        done();
+        wss.close(done);
       });
     });
   });
@@ -279,8 +278,7 @@ describe('WebSocketServer', function () {
 
       wss.on('connection', (client) => {
         assert.strictEqual(client.maxPayload, maxPayload);
-        wss.close();
-        done();
+        wss.close(done);
       });
     });
 
@@ -292,8 +290,7 @@ describe('WebSocketServer', function () {
 
       wss.on('connection', (client) => {
         assert.strictEqual(client._receiver.maxPayload, maxPayload);
-        wss.close();
-        done();
+        wss.close(done);
       });
     });
 
@@ -309,8 +306,7 @@ describe('WebSocketServer', function () {
           client._receiver.extensions[PerMessageDeflate.extensionName]._maxPayload,
           maxPayload
         );
-        wss.close();
-        done();
+        wss.close(done);
       });
     });
   });
@@ -363,8 +359,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 400);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -386,8 +381,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 400);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -409,8 +403,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 400);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -435,8 +428,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 400);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -462,8 +454,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 400);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -493,8 +484,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 401);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -525,10 +515,7 @@ describe('WebSocketServer', function () {
         req.end();
       });
 
-      wss.on('connection', (ws) => {
-        wss.close();
-        done();
-      });
+      wss.on('connection', (ws) => wss.close(done));
     });
 
     it('verifyClient gets client origin', function (done) {
@@ -555,8 +542,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.ok(verifyClientCalled);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -590,8 +576,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.ok(verifyClientCalled);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -666,8 +651,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 401);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -697,8 +681,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 404);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -729,10 +712,7 @@ describe('WebSocketServer', function () {
         req.end();
       });
 
-      wss.on('connection', (ws) => {
-        wss.close();
-        done();
-      });
+      wss.on('connection', (ws) => wss.close(done));
     });
 
     it('doesn\'t emit the `connection` event if socket is closed prematurely', function (done) {
@@ -791,8 +771,7 @@ describe('WebSocketServer', function () {
       wss.on('connection', (ws) => {
         ws.on('message', (data) => {
           assert.strictEqual(data, 'Hello');
-          wss.close();
-          done();
+          wss.close(done);
         });
       });
     });
@@ -803,8 +782,7 @@ describe('WebSocketServer', function () {
 
         ws.on('open', () => {
           assert.strictEqual(ws.protocol, 'prot1');
-          wss.close();
-          done();
+          wss.close(done);
         });
       });
     });
@@ -833,10 +811,7 @@ describe('WebSocketServer', function () {
         const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']);
 
         ws.on('open', () => done(new Error('connection must not be established')));
-        ws.on('error', () => {
-          wss.close();
-          done();
-        });
+        ws.on('error', () => wss.close(done));
       });
     });
 
@@ -848,10 +823,7 @@ describe('WebSocketServer', function () {
         const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']);
 
         ws.on('open', () => done(new Error('connection must not be established')));
-        ws.on('error', () => {
-          wss.close();
-          done();
-        });
+        ws.on('error', () => wss.close(done));
       });
     });
 
@@ -874,8 +846,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 401);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -899,10 +870,7 @@ describe('WebSocketServer', function () {
         req.end();
       });
 
-      wss.on('connection', (ws) => {
-        wss.close();
-        done();
-      });
+      wss.on('connection', (ws) => wss.close(done));
     });
 
     it('emits the `headers` event', function (done) {
@@ -942,8 +910,7 @@ describe('WebSocketServer', function () {
       wss.on('connection', (client) => {
         client.on('message', (message) => {
           assert.strictEqual(message, data);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         client.send(data);
@@ -959,8 +926,7 @@ describe('WebSocketServer', function () {
 
       wss.on('connection', (client) => {
         assert.strictEqual(client.protocol, 'hi');
-        wss.close();
-        done();
+        wss.close(done);
       });
     });
 
@@ -971,8 +937,7 @@ describe('WebSocketServer', function () {
 
       wss.on('connection', (client) => {
         assert.strictEqual(client.protocolVersion, 8);
-        wss.close();
-        done();
+        wss.close(done);
       });
     });
 
@@ -983,8 +948,7 @@ describe('WebSocketServer', function () {
 
       wss.on('connection', (client) => {
         assert.strictEqual(client.upgradeReq.httpVersion, '1.1');
-        wss.close();
-        done();
+        wss.close(done);
       });
     });
   });
@@ -1007,10 +971,7 @@ describe('WebSocketServer', function () {
         req.end();
       });
 
-      wss.on('connection', (ws) => {
-        wss.close();
-        done();
-      });
+      wss.on('connection', (ws) => wss.close(done));
     });
 
     it('does not accept connections with not defined extension parameter', function (done) {
@@ -1029,8 +990,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 400);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();
@@ -1057,8 +1017,7 @@ describe('WebSocketServer', function () {
 
         req.on('response', (res) => {
           assert.strictEqual(res.statusCode, 400);
-          wss.close();
-          done();
+          wss.close(done);
         });
 
         req.end();

From 2e92c7426cbd0eee0c6dd978986b9b33e813fdbb Mon Sep 17 00:00:00 2001
From: Luigi Pinca 
Date: Tue, 9 May 2017 10:35:41 +0200
Subject: [PATCH 338/669] [doc] Add FAQ and TOC

---
 README.md | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 87 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index 1ca0bdb91..e43891614 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,26 @@ reference to a back end with the role of a client in the WebSocket
 communication. Browser clients must use the native
 [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) object.
 
+## Table of Contents
+
+* [Protocol support](#protocol-support)
+* [Installing](#installing)
+  + [Opt-in for performance and spec compliance](#opt-in-for-performance-and-spec-compliance)
+* [API docs](#api-docs)
+* [WebSocket compression](#websocket-compression)
+* [Usage examples](#usage-examples)
+  + [Sending and receiving text data](#sending-and-receiving-text-data)
+  + [Sending binary data](#sending-binary-data)
+  + [Server example](#server-example)
+  + [Broadcast example](#broadcast-example)
+  + [ExpressJS example](#expressjs-example)
+  + [echo.websocket.org demo](#echowebsocketorg-demo)
+  + [Other examples](#other-examples)
+* [Error handling best practices](#error-handling-best-practices)
+* [FAQ](#faq)
+* [Changelog](#changelog)
+* [License](#license)
+
 ## Protocol support
 
 * **HyBi drafts 07-12** (Use the option `protocolVersion: 8`)
@@ -40,10 +60,9 @@ necessarily need to have a C++ compiler installed on your machine.
 - `npm install --save-optional utf-8-validate`: Allows to efficiently check
   if a message contains valid UTF-8 as required by the spec.
 
-## API Docs
+## API docs
 
-See [`/doc/ws.md`](https://github.com/websockets/ws/blob/master/doc/ws.md)
-for Node.js-like docs for the ws classes.
+See [`/doc/ws.md`](./doc/ws.md) for Node.js-like docs for the ws classes.
 
 ## WebSocket compression
 
@@ -248,13 +267,76 @@ try { ws.send('something'); }
 catch (e) { /* handle error */ }
 ```
 
+## FAQ
+
+
+How to get the IP address of the client? +The remote IP address can be obtained from the raw socket. + +```js +const WebSocket = require('ws'); + +const wss = new WebSocket.Server({ port: 8080 }); + +wss.on('connection', function connection(ws) { + const ip = ws.upgradeReq.connection.remoteAddress; +}); +``` + +When the server runs behing a proxy like NGINX, the de-facto standard is to use +the `X-Forwarded-For` header. + +```js +wss.on('connection', function connection(ws) { + const ip = ws.upgradeReq['x-forwarded-for']; +}); +``` +
+ +
+How to detect and close broken connections? +Sometimes the link between the server and the client can be interrupted in a +way that keeps both the server and the client unware of the broken state of the +connection (e.g. when pulling the cord). + +In these cases ping messages can be used as a means to verify that the remote +endpoint is still responsive. + +```js +const WebSocket = require('ws'); + +const wss = new WebSocket.Server({ port: 8080 }); + +function heartbeat() { + this.isAlive = true; +} + +wss.on('connection', function connection(ws) { + ws.isAlive = true; + ws.on('pong', heartbeat); +}); + +const interval = setInterval(function ping() { + wss.clients.forEach(function each(ws) { + if (ws.isAlive === false) return ws.terminate(); + + ws.isAlive = false; + ws.ping('', false, true); + }); +}, 30000); +``` + +Pong messages are automatically sent in reponse to ping messages as required +by the spec. +
+ ## Changelog -We're using the GitHub [`releases`](https://github.com/websockets/ws/releases) -for changelog entries. +We're using the GitHub [releases][changelog] for changelog entries. ## License [MIT](LICENSE) [permessage-deflate]: https://tools.ietf.org/html/rfc7692 +[changelog]: https://github.com/websockets/ws/releases From 9d0efbb274518e40845ee778fc87236cf191777b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 9 May 2017 10:48:22 +0200 Subject: [PATCH 339/669] [doc] Fix typo in code snippet --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e43891614..25dac5f6c 100644 --- a/README.md +++ b/README.md @@ -288,7 +288,7 @@ the `X-Forwarded-For` header. ```js wss.on('connection', function connection(ws) { - const ip = ws.upgradeReq['x-forwarded-for']; + const ip = ws.upgradeReq.headers['x-forwarded-for']; }); ``` From abd27fe449e514c048846a43b8d51efa4317b74a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 9 May 2017 11:19:51 +0200 Subject: [PATCH 340/669] [doc] Tweak markdown formatting --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 25dac5f6c..cf3ccfbe6 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ communication. Browser clients must use the native + [Other examples](#other-examples) * [Error handling best practices](#error-handling-best-practices) * [FAQ](#faq) + + [How to get the IP address of the client?](#how-to-get-the-ip-address-of-the-client) + + [How to detect and close broken connections?](#how-to-detect-and-close-broken-connections) * [Changelog](#changelog) * [License](#license) @@ -269,8 +271,8 @@ catch (e) { /* handle error */ } ## FAQ -
-How to get the IP address of the client? +### How to get the IP address of the client? + The remote IP address can be obtained from the raw socket. ```js @@ -291,10 +293,9 @@ wss.on('connection', function connection(ws) { const ip = ws.upgradeReq.headers['x-forwarded-for']; }); ``` -
-
-How to detect and close broken connections? +### How to detect and close broken connections? + Sometimes the link between the server and the client can be interrupted in a way that keeps both the server and the client unware of the broken state of the connection (e.g. when pulling the cord). @@ -328,7 +329,6 @@ const interval = setInterval(function ping() { Pong messages are automatically sent in reponse to ping messages as required by the spec. -
## Changelog From 1849ac26ee852df3804e8b4093ab316ed787a7f5 Mon Sep 17 00:00:00 2001 From: Gibson Fahnestock Date: Sun, 14 May 2017 05:46:47 +0100 Subject: [PATCH 341/669] [test] Skip test if 127.0.0.2 unavailable (#1106) --- test/WebSocket.test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index e93bc6a83..7f93f981a 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -65,16 +65,16 @@ describe('WebSocket', function () { }); it('accepts the localAddress option', function (done) { - // - // Skip this test on macOS as by default all loopback addresses other - // than 127.0.0.1 are disabled. - // - if (process.platform === 'darwin') return done(); - const wss = new WebSocketServer({ host: '127.0.0.1', port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { localAddress: '127.0.0.2' }); + + ws.on('error', (err) => { + // Skip this test on machines where 127.0.0.2 is disabled. + if (err.code === 'EADDRNOTAVAIL') return done(); + throw err; + }); }); wss.on('connection', (ws) => { From 839eac8bc76674313f343e402f567d40abe21138 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 15 May 2017 07:24:14 +0200 Subject: [PATCH 342/669] chore(package): update mocha to version 3.4.1 (#1108) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 79d2938d9..bd59aaacf 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "eslint-plugin-node": "~4.2.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", - "mocha": "~3.3.0", + "mocha": "~3.4.1", "nyc": "~10.3.0", "utf-8-validate": "~3.0.0" } From 68f12aa0cbb4facc64a87bb0bb2ee80e99acecbd Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 10 May 2017 07:44:07 +0200 Subject: [PATCH 343/669] [major] Remove unnecessary events (#1100) --- lib/WebSocketServer.js | 1 - test/WebSocketServer.test.js | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index bd3ef24e3..b3d82df67 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -85,7 +85,6 @@ class WebSocketServer extends EventEmitter { this._ultron.on('error', (err) => this.emit('error', err)); this._ultron.on('upgrade', (req, socket, head) => { this.handleUpgrade(req, socket, head, (client) => { - this.emit(`connection${req.url}`, client); this.emit('connection', client); }); }); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 801db7d6a..de081a5bb 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -105,20 +105,6 @@ describe('WebSocketServer', function () { }); }); - it('emits path specific connection event', function (done) { - const server = http.createServer(); - - server.listen(++port, () => { - const wss = new WebSocketServer({ server }); - const ws = new WebSocket(`ws://localhost:${port}/endpointName`); - - wss.on('connection/endpointName', (ws) => { - wss.close(); - server.close(done); - }); - }); - }); - it('will not crash when it receives an unhandled opcode', function (done) { const wss = new WebSocketServer({ port: ++port }); From c15118f0636d81497db9ad34663efc98b9ddc9c3 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 11 May 2017 14:42:51 +0200 Subject: [PATCH 344/669] [major] Remove the `flags` argument (#1101) The `flags` object specifies whether the received data is binary or not and whether it was masked or not. This object is actually redundant and therefore removed in this commit as the spec requires client-sent frames to be masked and server-sent frame to be unmasked. The `typeof` operator can be used to check whether the received data is binary or not. --- README.md | 9 ++++----- bench/speed.js | 2 +- doc/ws.md | 9 --------- examples/fileapi/server.js | 4 ++-- lib/EventTarget.js | 8 +++----- lib/Receiver.js | 14 ++++++-------- lib/WebSocket.js | 8 ++++---- test/WebSocket.test.js | 36 +++++++++++------------------------- test/WebSocketServer.test.js | 2 +- 9 files changed, 32 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index cf3ccfbe6..66176150e 100644 --- a/README.md +++ b/README.md @@ -112,9 +112,8 @@ ws.on('open', function open() { ws.send('something'); }); -ws.on('message', function incoming(data, flags) { - // flags.binary will be set if a binary data is received. - // flags.masked will be set if the data was masked. +ws.on('message', function incoming(data) { + console.log(data); }); ``` @@ -232,8 +231,8 @@ ws.on('close', function close() { console.log('disconnected'); }); -ws.on('message', function incoming(data, flags) { - console.log(`Roundtrip time: ${Date.now() - data} ms`, flags); +ws.on('message', function incoming(data) { + console.log(`Roundtrip time: ${Date.now() - data} ms`); setTimeout(function timeout() { ws.send(Date.now()); diff --git a/bench/speed.js b/bench/speed.js index b22e66883..cae60a2bf 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -17,7 +17,7 @@ if (cluster.isMaster) { }, () => cluster.fork()); wss.on('connection', (ws) => { - ws.on('message', (data, flags) => ws.send(data, { binary: flags.binary || false })); + ws.on('message', (data) => ws.send(data)); }); cluster.on('exit', () => wss.close()); diff --git a/doc/ws.md b/doc/ws.md index cdb1f03e5..4a7db0fc6 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -238,9 +238,6 @@ handshake. This allows you to read headers from the server, for example ### Event: 'message' - `data` {String|Buffer} -- `flags` {Object} - - `binary` {Boolean} Specifies if `data` is binary. - - `masked` {Boolean} Specifies if `data` was masked. Emitted when a message is received from the server. @@ -251,18 +248,12 @@ Emitted when the connection is established. ### Event: 'ping' - `data` {Buffer} -- `flags` {Object} - - `binary` {Boolean} Specifies if `data` is binary. - - `masked` {Boolean} Specifies if `data` was masked. Emitted when a ping is received from the server. ### Event: 'pong' - `data` {Buffer} -- `flags` {Object} - - `binary` {Boolean} Specifies if `data` is binary. - - `masked` {Boolean} Specifies if `data` was masked. Emitted when a pong is received from the server. diff --git a/examples/fileapi/server.js b/examples/fileapi/server.js index 4202818a0..54e6128f8 100644 --- a/examples/fileapi/server.js +++ b/examples/fileapi/server.js @@ -60,8 +60,8 @@ wss.on('connection', function (ws) { var filesReceived = 0; var currentFile = null; - ws.on('message', function (data, flags) { - if (!flags.binary) { + ws.on('message', function (data) { + if (typeof data === 'string') { currentFile = JSON.parse(data); // note: a real-world app would want to sanity check the data } else { diff --git a/lib/EventTarget.js b/lib/EventTarget.js index e30b1b3ed..8f9a943a8 100644 --- a/lib/EventTarget.js +++ b/lib/EventTarget.js @@ -29,13 +29,11 @@ class MessageEvent extends Event { * Create a new `MessageEvent`. * * @param {(String|Buffer|ArrayBuffer|Buffer[])} data The received data - * @param {Boolean} isBinary Specifies if `data` is binary * @param {WebSocket} target A reference to the target to which the event was dispatched */ - constructor (data, isBinary, target) { + constructor (data, target) { super('message', target); - this.binary = isBinary; // non-standard. this.data = data; } } @@ -99,8 +97,8 @@ const EventTarget = { addEventListener (method, listener) { if (typeof listener !== 'function') return; - function onMessage (data, flags) { - listener.call(this, new MessageEvent(data, !!flags.binary, this)); + function onMessage (data) { + listener.call(this, new MessageEvent(data, this)); } function onClose (code, message) { diff --git a/lib/Receiver.js b/lib/Receiver.js index 6c1a10e2c..7054ca469 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -377,7 +377,7 @@ class Receiver { data = fragments; } - this.onmessage(data, { masked: this.masked, binary: true }); + this.onmessage(data); } else { const buf = toBuffer(fragments, messageLength); @@ -386,7 +386,7 @@ class Receiver { return; } - this.onmessage(buf.toString(), { masked: this.masked }); + this.onmessage(buf.toString()); } } @@ -402,7 +402,7 @@ class Receiver { controlMessage (data) { if (this.opcode === 0x08) { if (data.length === 0) { - this.onclose(1000, '', { masked: this.masked }); + this.onclose(1000, ''); this.loop = false; this.cleanup(this.cleanupCallback); } else if (data.length === 1) { @@ -422,7 +422,7 @@ class Receiver { return; } - this.onclose(code, buf.toString(), { masked: this.masked }); + this.onclose(code, buf.toString()); this.loop = false; this.cleanup(this.cleanupCallback); } @@ -430,10 +430,8 @@ class Receiver { return; } - const flags = { masked: this.masked, binary: true }; - - if (this.opcode === 0x09) this.onping(data, flags); - else this.onpong(data, flags); + if (this.opcode === 0x09) this.onping(data); + else this.onpong(data); this.state = GET_INFO; } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 41868d832..1c82fa438 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -143,12 +143,12 @@ class WebSocket extends EventEmitter { }); // receiver event handlers - this._receiver.onmessage = (data, flags) => this.emit('message', data, flags); - this._receiver.onping = (data, flags) => { + this._receiver.onmessage = (data) => this.emit('message', data); + this._receiver.onping = (data) => { this.pong(data, !this._isServer, true); - this.emit('ping', data, flags); + this.emit('ping', data); }; - this._receiver.onpong = (data, flags) => this.emit('pong', data, flags); + this._receiver.onpong = (data) => this.emit('pong', data); this._receiver.onclose = (code, reason) => { this._closeMessage = reason; this._closeCode = code; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 7f93f981a..8da4dec94 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -674,8 +674,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(array, { compress: false })); - ws.on('message', (msg, flags) => { - assert.ok(flags.binary); + ws.on('message', (msg) => { assert.ok(msg.equals(Buffer.from(array.buffer))); wss.close(done); }); @@ -691,7 +690,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send('hi')); - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.strictEqual(message, 'hi'); wss.close(done); }); @@ -750,8 +749,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(partial, { binary: true })); - ws.on('message', (message, flags) => { - assert.ok(flags.binary); + ws.on('message', (message) => { assert.ok(message.equals(buf)); wss.close(done); }); @@ -768,8 +766,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(buf, { binary: true })); - ws.on('message', (message, flags) => { - assert.ok(flags.binary); + ws.on('message', (message) => { assert.ok(message.equals(buf)); wss.close(done); }); @@ -792,7 +789,6 @@ describe('WebSocket', function () { ws.on('open', () => ws.send(array.buffer)); ws.onmessage = (event) => { - assert.ok(event.binary); assert.ok(event.data.equals(Buffer.from(array.buffer))); wss.close(done); }; @@ -811,7 +807,6 @@ describe('WebSocket', function () { ws.on('open', () => ws.send(buf)); ws.onmessage = (event) => { - assert.ok(event.binary); assert.ok(event.data.equals(buf)); wss.close(done); }; @@ -877,9 +872,8 @@ describe('WebSocket', function () { }); wss.on('connection', (ws) => { - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.strictEqual(message, 'hi'); - assert.ok(!flags.masked); wss.close(done); }); }); @@ -893,9 +887,8 @@ describe('WebSocket', function () { }); wss.on('connection', (ws) => { - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.strictEqual(message, 'hi'); - assert.ok(flags.masked); wss.close(done); }); }); @@ -915,9 +908,8 @@ describe('WebSocket', function () { }); wss.on('connection', (ws) => { - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.ok(message.equals(Buffer.from(array.buffer))); - assert.ok(flags.binary); wss.close(done); }); }); @@ -937,10 +929,8 @@ describe('WebSocket', function () { }); wss.on('connection', (ws) => { - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.ok(message.equals(Buffer.from(array.buffer))); - assert.ok(flags.binary); - assert.ok(flags.masked); wss.close(done); }); }); @@ -1493,13 +1483,12 @@ describe('WebSocket', function () { wss.on('connection', (client) => client.send('hi')); }); - it('should pass binary data as a node.js Buffer by default', function (done) { + it('should pass binary data as a Node.js Buffer by default', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); ws.onmessage = (evt) => { assert.ok(Buffer.isBuffer(evt.data)); - assert.ok(evt.binary); wss.close(done); }; }); @@ -1515,7 +1504,6 @@ describe('WebSocket', function () { ws.onmessage = (evt) => { assert.ok(evt.data instanceof ArrayBuffer); - assert.ok(evt.binary); wss.close(done); }; }); @@ -1531,7 +1519,6 @@ describe('WebSocket', function () { ws.onmessage = (evt) => { assert.strictEqual(evt.data, 'foo'); - assert.ok(!evt.binary); wss.close(done); }; }); @@ -1655,7 +1642,7 @@ describe('WebSocket', function () { const wss = new WebSocketServer({ server }); wss.on('connection', (ws) => { - ws.on('message', (message, flags) => { + ws.on('message', (message) => { assert.strictEqual(message, 'foobar'); server.close(done); wss.close(); @@ -1691,8 +1678,7 @@ describe('WebSocket', function () { }); ws.on('open', () => ws.send(buf)); - ws.on('message', (message, flags) => { - assert.strictEqual(flags.binary, true); + ws.on('message', (message) => { assert.ok(buf.equals(message)); server.close(done); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index de081a5bb..03cb1fb39 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -890,7 +890,7 @@ describe('WebSocketServer', function () { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('message', (message, flags) => ws.send(message)); + ws.on('message', (message) => ws.send(message)); }); wss.on('connection', (client) => { From d2c848120f4eb97913ce266df69debbb4c8eefe8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 11 May 2017 14:43:06 +0200 Subject: [PATCH 345/669] [major] Remove the `upgradeReq` property (#1104) The `http.IncomingMessage` object, instead of being attached to the `WebSocket` object, is passes as the second argument to the `connection` event. --- README.md | 14 +++++++------- doc/ws.md | 12 ++++-------- examples/express-session-parse/index.js | 6 ++---- lib/WebSocket.js | 5 ++--- lib/WebSocketServer.js | 4 ++-- test/WebSocket.test.js | 8 ++++---- test/WebSocketServer.test.js | 17 +++-------------- 7 files changed, 24 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 66176150e..bbe86e7d3 100644 --- a/README.md +++ b/README.md @@ -196,10 +196,10 @@ app.use(function (req, res) { const server = http.createServer(app); const wss = new WebSocket.Server({ server }); -wss.on('connection', function connection(ws) { - const location = url.parse(ws.upgradeReq.url, true); +wss.on('connection', function connection(ws, req) { + const location = url.parse(req.url, true); // You might use location.query.access_token to authenticate or share sessions - // or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312) + // or req.headers.cookie (see http://stackoverflow.com/a/16395220/151312) ws.on('message', function incoming(message) { console.log('received: %s', message); @@ -279,8 +279,8 @@ const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 }); -wss.on('connection', function connection(ws) { - const ip = ws.upgradeReq.connection.remoteAddress; +wss.on('connection', function connection(ws, req) { + const ip = req.connection.remoteAddress; }); ``` @@ -288,8 +288,8 @@ When the server runs behing a proxy like NGINX, the de-facto standard is to use the `X-Forwarded-For` header. ```js -wss.on('connection', function connection(ws) { - const ip = ws.upgradeReq.headers['x-forwarded-for']; +wss.on('connection', function connection(ws, req) { + const ip = req.headers['x-forwarded-for']; }); ``` diff --git a/doc/ws.md b/doc/ws.md index 4a7db0fc6..d8ca9d111 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -89,8 +89,11 @@ provided. ### Event: 'connection' - `socket` {WebSocket} +- `request` {http.IncomingMessage} -Emitted when the handshake is complete. `socket` is an instance of `WebSocket`. +Emitted when the handshake is complete. `request` is the http GET request sent +by the client. Useful for parsing authority headers, cookie headers, and other +information. ### Event: 'error' @@ -414,13 +417,6 @@ Send `data` through the connection. Forcibly close the connection. -### websocket.upgradeReq - -- {http.IncomingMessage} - -The http GET request sent by the client. Useful for parsing authority headers, -cookie headers, and other information. This is only available for server clients. - ### websocket.url - {String} diff --git a/examples/express-session-parse/index.js b/examples/express-session-parse/index.js index bac2d8e97..5250b2cb6 100644 --- a/examples/express-session-parse/index.js +++ b/examples/express-session-parse/index.js @@ -63,14 +63,12 @@ const wss = new WebSocket.Server({ server }); -wss.on('connection', (ws) => { +wss.on('connection', (ws, req) => { ws.on('message', (message) => { - const session = ws.upgradeReq.session; - // // Here we can now use session parameters. // - console.log(`WS message ${message} from user ${session.userId}`); + console.log(`WS message ${message} from user ${req.session.userId}`); }); }); diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 1c82fa438..38e6e4eb5 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -65,7 +65,7 @@ class WebSocket extends EventEmitter { this._ultron = null; if (Array.isArray(address)) { - initAsServerClient.call(this, address[0], address[1], address[2], options); + initAsServerClient.call(this, address[0], address[1], options); } else { initAsClient.call(this, address, protocols, options); } @@ -455,13 +455,12 @@ module.exports = WebSocket; * @param {String} options.protocol The chosen subprotocol * @private */ -function initAsServerClient (req, socket, head, options) { +function initAsServerClient (socket, head, options) { this.protocolVersion = options.protocolVersion; this.extensions = options.extensions; this.maxPayload = options.maxPayload; this.protocol = options.protocol; - this.upgradeReq = req; this._isServer = true; this.setSocket(socket, head); diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index b3d82df67..a76e96cbb 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -85,7 +85,7 @@ class WebSocketServer extends EventEmitter { this._ultron.on('error', (err) => this.emit('error', err)); this._ultron.on('upgrade', (req, socket, head) => { this.handleUpgrade(req, socket, head, (client) => { - this.emit('connection', client); + this.emit('connection', client, req); }); }); } @@ -255,7 +255,7 @@ class WebSocketServer extends EventEmitter { socket.write(headers.concat('', '').join('\r\n')); - const client = new WebSocket([req, socket, head], null, { + const client = new WebSocket([socket, head], null, { maxPayload: this.options.maxPayload, protocolVersion: version, extensions, diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 8da4dec94..d42f7431a 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -77,8 +77,8 @@ describe('WebSocket', function () { }); }); - wss.on('connection', (ws) => { - assert.strictEqual(ws.upgradeReq.connection.remoteAddress, '127.0.0.2'); + wss.on('connection', (ws, req) => { + assert.strictEqual(req.connection.remoteAddress, '127.0.0.2'); wss.close(done); }); }); @@ -95,8 +95,8 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 }); }); - wss.on('connection', (ws) => { - assert.strictEqual(ws.upgradeReq.connection.remoteAddress, '::1'); + wss.on('connection', (ws, req) => { + assert.strictEqual(req.connection.remoteAddress, '::1'); wss.close(done); }); }); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 03cb1fb39..4a71823ae 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -90,11 +90,11 @@ describe('WebSocketServer', function () { server.listen(sockPath, () => { const wss = new WebSocketServer({ server }); - wss.on('connection', (ws) => { + wss.on('connection', (ws, req) => { if (wss.clients.size === 1) { - assert.strictEqual(ws.upgradeReq.url, '/foo?bar=bar'); + assert.strictEqual(req.url, '/foo?bar=bar'); } else { - assert.strictEqual(ws.upgradeReq.url, '/'); + assert.strictEqual(req.url, '/'); wss.close(); server.close(done); } @@ -926,17 +926,6 @@ describe('WebSocketServer', function () { wss.close(done); }); }); - - it('upgradeReq is the original request object', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, { protocolVersion: 8 }); - }); - - wss.on('connection', (client) => { - assert.strictEqual(client.upgradeReq.httpVersion, '1.1'); - wss.close(done); - }); - }); }); describe('permessage-deflate', function () { From 439dbce25d7ef446321495bea77dbcd7646d9d81 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 14 May 2017 06:56:25 +0200 Subject: [PATCH 346/669] [major] Prefix all private properties (#1105) --- lib/PerMessageDeflate.js | 9 +- lib/Receiver.js | 234 +++++++++++++++++------------------ lib/Sender.js | 58 +++++---- lib/WebSocket.js | 8 +- lib/WebSocketServer.js | 1 - test/Receiver.test.js | 38 +++--- test/WebSocketServer.test.js | 8 +- 7 files changed, 181 insertions(+), 175 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index c1a1d3c70..17bbf75ed 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -18,13 +18,16 @@ const DEFAULT_MEM_LEVEL = 8; */ class PerMessageDeflate { constructor (options, isServer, maxPayload) { + this._maxPayload = maxPayload | 0; this._options = options || {}; + this._threshold = this._options.threshold !== undefined + ? this._options.threshold + : 1024; this._isServer = !!isServer; - this._inflate = null; this._deflate = null; + this._inflate = null; + this.params = null; - this._maxPayload = maxPayload || 0; - this.threshold = this._options.threshold === undefined ? 1024 : this._options.threshold; } static get extensionName () { diff --git a/lib/Receiver.js b/lib/Receiver.js index 7054ca469..91196706c 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -35,29 +35,29 @@ class Receiver { * @param {String} binaryType The type for binary data */ constructor (extensions, maxPayload, binaryType) { - this.binaryType = binaryType || constants.BINARY_TYPES[0]; - this.extensions = extensions || {}; - this.maxPayload = maxPayload | 0; - - this.bufferedBytes = 0; - this.buffers = []; - - this.compressed = false; - this.payloadLength = 0; - this.fragmented = 0; - this.masked = false; - this.fin = false; - this.mask = null; - this.opcode = 0; - - this.totalPayloadLength = 0; - this.messageLength = 0; - this.fragments = []; - - this.cleanupCallback = null; - this.hadError = false; - this.dead = false; - this.loop = false; + this._binaryType = binaryType || constants.BINARY_TYPES[0]; + this._extensions = extensions || {}; + this._maxPayload = maxPayload | 0; + + this._bufferedBytes = 0; + this._buffers = []; + + this._compressed = false; + this._payloadLength = 0; + this._fragmented = 0; + this._masked = false; + this._fin = false; + this._mask = null; + this._opcode = 0; + + this._totalPayloadLength = 0; + this._messageLength = 0; + this._fragments = []; + + this._cleanupCallback = null; + this._hadError = false; + this._dead = false; + this._loop = false; this.onmessage = null; this.onclose = null; @@ -65,7 +65,7 @@ class Receiver { this.onping = null; this.onpong = null; - this.state = GET_INFO; + this._state = GET_INFO; } /** @@ -80,28 +80,28 @@ class Receiver { var dst; var l; - this.bufferedBytes -= bytes; + this._bufferedBytes -= bytes; - if (bytes === this.buffers[0].length) return this.buffers.shift(); + if (bytes === this._buffers[0].length) return this._buffers.shift(); - if (bytes < this.buffers[0].length) { - dst = this.buffers[0].slice(0, bytes); - this.buffers[0] = this.buffers[0].slice(bytes); + if (bytes < this._buffers[0].length) { + dst = this._buffers[0].slice(0, bytes); + this._buffers[0] = this._buffers[0].slice(bytes); return dst; } dst = Buffer.allocUnsafe(bytes); while (bytes > 0) { - l = this.buffers[0].length; + l = this._buffers[0].length; if (bytes >= l) { - this.buffers[0].copy(dst, offset); + this._buffers[0].copy(dst, offset); offset += l; - this.buffers.shift(); + this._buffers.shift(); } else { - this.buffers[0].copy(dst, offset, 0, bytes); - this.buffers[0] = this.buffers[0].slice(bytes); + this._buffers[0].copy(dst, offset, 0, bytes); + this._buffers[0] = this._buffers[0].slice(bytes); } bytes -= l; @@ -119,10 +119,10 @@ class Receiver { * @private */ hasBufferedBytes (n) { - if (this.bufferedBytes >= n) return true; + if (this._bufferedBytes >= n) return true; - this.loop = false; - if (this.dead) this.cleanup(this.cleanupCallback); + this._loop = false; + if (this._dead) this.cleanup(this._cleanupCallback); return false; } @@ -132,10 +132,10 @@ class Receiver { * @public */ add (data) { - if (this.dead) return; + if (this._dead) return; - this.bufferedBytes += data.length; - this.buffers.push(data); + this._bufferedBytes += data.length; + this._buffers.push(data); this.startLoop(); } @@ -145,10 +145,10 @@ class Receiver { * @private */ startLoop () { - this.loop = true; + this._loop = true; - while (this.loop) { - switch (this.state) { + while (this._loop) { + switch (this._state) { case GET_INFO: this.getInfo(); break; @@ -165,7 +165,7 @@ class Receiver { this.getData(); break; default: // `INFLATING` - this.loop = false; + this._loop = false; } } } @@ -187,36 +187,36 @@ class Receiver { const compressed = (buf[0] & 0x40) === 0x40; - if (compressed && !this.extensions[PerMessageDeflate.extensionName]) { + if (compressed && !this._extensions[PerMessageDeflate.extensionName]) { this.error(new Error('RSV1 must be clear'), 1002); return; } - this.fin = (buf[0] & 0x80) === 0x80; - this.opcode = buf[0] & 0x0f; - this.payloadLength = buf[1] & 0x7f; + this._fin = (buf[0] & 0x80) === 0x80; + this._opcode = buf[0] & 0x0f; + this._payloadLength = buf[1] & 0x7f; - if (this.opcode === 0x00) { + if (this._opcode === 0x00) { if (compressed) { this.error(new Error('RSV1 must be clear'), 1002); return; } - if (!this.fragmented) { - this.error(new Error(`invalid opcode: ${this.opcode}`), 1002); + if (!this._fragmented) { + this.error(new Error(`invalid opcode: ${this._opcode}`), 1002); return; } else { - this.opcode = this.fragmented; + this._opcode = this._fragmented; } - } else if (this.opcode === 0x01 || this.opcode === 0x02) { - if (this.fragmented) { - this.error(new Error(`invalid opcode: ${this.opcode}`), 1002); + } else if (this._opcode === 0x01 || this._opcode === 0x02) { + if (this._fragmented) { + this.error(new Error(`invalid opcode: ${this._opcode}`), 1002); return; } - this.compressed = compressed; - } else if (this.opcode > 0x07 && this.opcode < 0x0b) { - if (!this.fin) { + this._compressed = compressed; + } else if (this._opcode > 0x07 && this._opcode < 0x0b) { + if (!this._fin) { this.error(new Error('FIN must be set'), 1002); return; } @@ -226,21 +226,21 @@ class Receiver { return; } - if (this.payloadLength > 0x7d) { + if (this._payloadLength > 0x7d) { this.error(new Error('invalid payload length'), 1002); return; } } else { - this.error(new Error(`invalid opcode: ${this.opcode}`), 1002); + this.error(new Error(`invalid opcode: ${this._opcode}`), 1002); return; } - if (!this.fin && !this.fragmented) this.fragmented = this.opcode; + if (!this._fin && !this._fragmented) this._fragmented = this._opcode; - this.masked = (buf[1] & 0x80) === 0x80; + this._masked = (buf[1] & 0x80) === 0x80; - if (this.payloadLength === 126) this.state = GET_PAYLOAD_LENGTH_16; - else if (this.payloadLength === 127) this.state = GET_PAYLOAD_LENGTH_64; + if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16; + else if (this._payloadLength === 127) this._state = GET_PAYLOAD_LENGTH_64; else this.haveLength(); } @@ -252,7 +252,7 @@ class Receiver { getPayloadLength16 () { if (!this.hasBufferedBytes(2)) return; - this.payloadLength = this.readBuffer(2).readUInt16BE(0, true); + this._payloadLength = this.readBuffer(2).readUInt16BE(0, true); this.haveLength(); } @@ -276,7 +276,7 @@ class Receiver { return; } - this.payloadLength = (num * Math.pow(2, 32)) + buf.readUInt32BE(4, true); + this._payloadLength = (num * Math.pow(2, 32)) + buf.readUInt32BE(4, true); this.haveLength(); } @@ -286,12 +286,12 @@ class Receiver { * @private */ haveLength () { - if (this.opcode < 0x08 && this.maxPayloadExceeded(this.payloadLength)) { + if (this._opcode < 0x08 && this.maxPayloadExceeded(this._payloadLength)) { return; } - if (this.masked) this.state = GET_MASK; - else this.state = GET_DATA; + if (this._masked) this._state = GET_MASK; + else this._state = GET_DATA; } /** @@ -302,8 +302,8 @@ class Receiver { getMask () { if (!this.hasBufferedBytes(4)) return; - this.mask = this.readBuffer(4); - this.state = GET_DATA; + this._mask = this.readBuffer(4); + this._state = GET_DATA; } /** @@ -314,17 +314,17 @@ class Receiver { getData () { var data = constants.EMPTY_BUFFER; - if (this.payloadLength) { - if (!this.hasBufferedBytes(this.payloadLength)) return; + if (this._payloadLength) { + if (!this.hasBufferedBytes(this._payloadLength)) return; - data = this.readBuffer(this.payloadLength); - if (this.masked) bufferUtil.unmask(data, this.mask); + data = this.readBuffer(this._payloadLength); + if (this._masked) bufferUtil.unmask(data, this._mask); } - if (this.opcode > 0x07) { + if (this._opcode > 0x07) { this.controlMessage(data); - } else if (this.compressed) { - this.state = INFLATING; + } else if (this._compressed) { + this._state = INFLATING; this.decompress(data); } else if (this.pushFragment(data)) { this.dataMessage(); @@ -338,9 +338,9 @@ class Receiver { * @private */ decompress (data) { - const extension = this.extensions[PerMessageDeflate.extensionName]; + const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; - extension.decompress(data, this.fin, (err, buf) => { + perMessageDeflate.decompress(data, this._fin, (err, buf) => { if (err) { this.error(err, err.closeCode === 1009 ? 1009 : 1007); return; @@ -357,21 +357,21 @@ class Receiver { * @private */ dataMessage () { - if (this.fin) { - const messageLength = this.messageLength; - const fragments = this.fragments; + if (this._fin) { + const messageLength = this._messageLength; + const fragments = this._fragments; - this.totalPayloadLength = 0; - this.messageLength = 0; - this.fragmented = 0; - this.fragments = []; + this._totalPayloadLength = 0; + this._messageLength = 0; + this._fragmented = 0; + this._fragments = []; - if (this.opcode === 2) { + if (this._opcode === 2) { var data; - if (this.binaryType === 'nodebuffer') { + if (this._binaryType === 'nodebuffer') { data = toBuffer(fragments, messageLength); - } else if (this.binaryType === 'arraybuffer') { + } else if (this._binaryType === 'arraybuffer') { data = toArrayBuffer(toBuffer(fragments, messageLength)); } else { data = fragments; @@ -390,7 +390,7 @@ class Receiver { } } - this.state = GET_INFO; + this._state = GET_INFO; } /** @@ -400,11 +400,11 @@ class Receiver { * @private */ controlMessage (data) { - if (this.opcode === 0x08) { + if (this._opcode === 0x08) { if (data.length === 0) { this.onclose(1000, ''); - this.loop = false; - this.cleanup(this.cleanupCallback); + this._loop = false; + this.cleanup(this._cleanupCallback); } else if (data.length === 1) { this.error(new Error('invalid payload length'), 1002); } else { @@ -423,17 +423,17 @@ class Receiver { } this.onclose(code, buf.toString()); - this.loop = false; - this.cleanup(this.cleanupCallback); + this._loop = false; + this.cleanup(this._cleanupCallback); } return; } - if (this.opcode === 0x09) this.onping(data); + if (this._opcode === 0x09) this.onping(data); else this.onpong(data); - this.state = GET_INFO; + this._state = GET_INFO; } /** @@ -445,9 +445,9 @@ class Receiver { */ error (err, code) { this.onerror(err, code); - this.hadError = true; - this.loop = false; - this.cleanup(this.cleanupCallback); + this._hadError = true; + this._loop = false; + this.cleanup(this._cleanupCallback); } /** @@ -457,12 +457,12 @@ class Receiver { * @private */ maxPayloadExceeded (length) { - if (length === 0 || this.maxPayload < 1) return false; + if (length === 0 || this._maxPayload < 1) return false; - const fullLength = this.totalPayloadLength + length; + const fullLength = this._totalPayloadLength + length; - if (fullLength <= this.maxPayload) { - this.totalPayloadLength = fullLength; + if (fullLength <= this._maxPayload) { + this._totalPayloadLength = fullLength; return false; } @@ -481,11 +481,11 @@ class Receiver { pushFragment (fragment) { if (fragment.length === 0) return true; - const totalLength = this.messageLength + fragment.length; + const totalLength = this._messageLength + fragment.length; - if (this.maxPayload < 1 || totalLength <= this.maxPayload) { - this.messageLength = totalLength; - this.fragments.push(fragment); + if (this._maxPayload < 1 || totalLength <= this._maxPayload) { + this._messageLength = totalLength; + this._fragments.push(fragment); return true; } @@ -500,17 +500,17 @@ class Receiver { * @public */ cleanup (cb) { - this.dead = true; + this._dead = true; - if (!this.hadError && (this.loop || this.state === INFLATING)) { - this.cleanupCallback = cb; + if (!this._hadError && (this._loop || this._state === INFLATING)) { + this._cleanupCallback = cb; } else { - this.extensions = null; - this.fragments = null; - this.buffers = null; - this.mask = null; + this._extensions = null; + this._fragments = null; + this._buffers = null; + this._mask = null; - this.cleanupCallback = null; + this._cleanupCallback = null; this.onmessage = null; this.onclose = null; this.onerror = null; diff --git a/lib/Sender.js b/lib/Sender.js index 79e68a5a3..d3502fe16 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -26,15 +26,15 @@ class Sender { * @param {Object} extensions An object containing the negotiated extensions */ constructor (socket, extensions) { - this.perMessageDeflate = (extensions || {})[PerMessageDeflate.extensionName]; + this._extensions = extensions || {}; this._socket = socket; - this.firstFragment = true; - this.compress = false; + this._firstFragment = true; + this._compress = false; - this.bufferedBytes = 0; - this.deflating = false; - this.queue = []; + this._bufferedBytes = 0; + this._deflating = false; + this._queue = []; this.onerror = null; } @@ -123,7 +123,7 @@ class Sender { buf.writeUInt16BE(code || 1000, 0, true); if (buf.length > 2) buf.write(data, 2); - if (this.deflating) { + if (this._deflating) { this.enqueue([this.doClose, buf, mask, cb]); } else { this.doClose(buf, mask, cb); @@ -169,7 +169,7 @@ class Sender { } } - if (this.deflating) { + if (this._deflating) { this.enqueue([this.doPing, data, mask, readOnly]); } else { this.doPing(data, mask, readOnly); @@ -215,7 +215,7 @@ class Sender { } } - if (this.deflating) { + if (this._deflating) { this.enqueue([this.doPong, data, mask, readOnly]); } else { this.doPong(data, mask, readOnly); @@ -268,20 +268,22 @@ class Sender { } } - if (this.firstFragment) { - this.firstFragment = false; - if (rsv1 && this.perMessageDeflate) { - rsv1 = data.length >= this.perMessageDeflate.threshold; + const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; + + if (this._firstFragment) { + this._firstFragment = false; + if (rsv1 && perMessageDeflate) { + rsv1 = data.length >= perMessageDeflate._threshold; } - this.compress = rsv1; + this._compress = rsv1; } else { rsv1 = false; opcode = 0; } - if (options.fin) this.firstFragment = true; + if (options.fin) this._firstFragment = true; - if (this.perMessageDeflate) { + if (perMessageDeflate) { const opts = { fin: options.fin, rsv1, @@ -290,10 +292,10 @@ class Sender { readOnly }; - if (this.deflating) { - this.enqueue([this.dispatch, data, this.compress, opts, cb]); + if (this._deflating) { + this.enqueue([this.dispatch, data, this._compress, opts, cb]); } else { - this.dispatch(data, this.compress, opts, cb); + this.dispatch(data, this._compress, opts, cb); } } else { this.sendFrame(Sender.frame(data, { @@ -326,8 +328,10 @@ class Sender { return; } - this.deflating = true; - this.perMessageDeflate.compress(data, options.fin, (err, buf) => { + const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; + + this._deflating = true; + perMessageDeflate.compress(data, options.fin, (err, buf) => { if (err) { if (cb) cb(err); else this.onerror(err); @@ -336,7 +340,7 @@ class Sender { options.readOnly = false; this.sendFrame(Sender.frame(buf, options), cb); - this.deflating = false; + this._deflating = false; this.dequeue(); }); } @@ -347,10 +351,10 @@ class Sender { * @private */ dequeue () { - while (!this.deflating && this.queue.length) { - const params = this.queue.shift(); + while (!this._deflating && this._queue.length) { + const params = this._queue.shift(); - this.bufferedBytes -= params[1].length; + this._bufferedBytes -= params[1].length; params[0].apply(this, params.slice(1)); } } @@ -362,8 +366,8 @@ class Sender { * @private */ enqueue (params) { - this.bufferedBytes += params[1].length; - this.queue.push(params); + this._bufferedBytes += params[1].length; + this._queue.push(params); } /** diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 38e6e4eb5..29180ea0f 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -83,7 +83,7 @@ class WebSocket extends EventEmitter { var amount = 0; if (this._socket) { - amount = this._socket.bufferSize + this._sender.bufferedBytes; + amount = this._socket.bufferSize + this._sender._bufferedBytes; } return amount; } @@ -106,7 +106,7 @@ class WebSocket extends EventEmitter { // // Allow to change `binaryType` on the fly. // - if (this._receiver) this._receiver.binaryType = type; + if (this._receiver) this._receiver._binaryType = type; } /** @@ -120,7 +120,7 @@ class WebSocket extends EventEmitter { socket.setTimeout(0); socket.setNoDelay(); - this._receiver = new Receiver(this.extensions, this.maxPayload, this.binaryType); + this._receiver = new Receiver(this.extensions, this._maxPayload, this.binaryType); this._sender = new Sender(socket, this.extensions); this._ultron = new Ultron(socket); this._socket = socket; @@ -457,8 +457,8 @@ module.exports = WebSocket; */ function initAsServerClient (socket, head, options) { this.protocolVersion = options.protocolVersion; + this._maxPayload = options.maxPayload; this.extensions = options.extensions; - this.maxPayload = options.maxPayload; this.protocol = options.protocol; this._isServer = true; diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index a76e96cbb..3735f990b 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -92,7 +92,6 @@ class WebSocketServer extends EventEmitter { if (options.clientTracking) this.clients = new Set(); this.options = options; - this.path = options.path; } /** diff --git a/test/Receiver.test.js b/test/Receiver.test.js index 6caed81a5..ffd579e00 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -338,9 +338,9 @@ describe('Receiver', function () { message = msg; }; - assert.strictEqual(p.totalPayloadLength, 0); + assert.strictEqual(p._totalPayloadLength, 0); p.add(Buffer.from('810548656c6c6f', 'hex')); - assert.strictEqual(p.totalPayloadLength, 0); + assert.strictEqual(p._totalPayloadLength, 0); assert.strictEqual(message, 'Hello'); }); @@ -352,11 +352,11 @@ describe('Receiver', function () { message = msg; }; - assert.strictEqual(p.totalPayloadLength, 0); + assert.strictEqual(p._totalPayloadLength, 0); p.add(Buffer.from('01024865', 'hex')); - assert.strictEqual(p.totalPayloadLength, 2); + assert.strictEqual(p._totalPayloadLength, 2); p.add(Buffer.from('80036c6c6f', 'hex')); - assert.strictEqual(p.totalPayloadLength, 0); + assert.strictEqual(p._totalPayloadLength, 0); assert.strictEqual(message, 'Hello'); }); @@ -368,13 +368,13 @@ describe('Receiver', function () { data.push(buf.toString()); }; - assert.strictEqual(p.totalPayloadLength, 0); + assert.strictEqual(p._totalPayloadLength, 0); p.add(Buffer.from('02024865', 'hex')); - assert.strictEqual(p.totalPayloadLength, 2); + assert.strictEqual(p._totalPayloadLength, 2); p.add(Buffer.from('8900', 'hex')); - assert.strictEqual(p.totalPayloadLength, 2); + assert.strictEqual(p._totalPayloadLength, 2); p.add(Buffer.from('80036c6c6f', 'hex')); - assert.strictEqual(p.totalPayloadLength, 0); + assert.strictEqual(p._totalPayloadLength, 0); assert.deepStrictEqual(data, ['', 'Hello']); }); @@ -722,8 +722,8 @@ describe('Receiver', function () { p.add(frame); p.add(frame); - assert.strictEqual(p.state, 5); - assert.strictEqual(p.bufferedBytes, frame.length); + assert.strictEqual(p._state, 5); + assert.strictEqual(p._bufferedBytes, frame.length); p.cleanup(() => { assert.deepStrictEqual(results, ['Hello', 'Hello']); @@ -754,8 +754,8 @@ describe('Receiver', function () { p.add(textFrame); p.add(closeFrame); - assert.strictEqual(p.state, 5); - assert.strictEqual(p.bufferedBytes, textFrame.length + closeFrame.length); + assert.strictEqual(p._state, 5); + assert.strictEqual(p._bufferedBytes, textFrame.length + closeFrame.length); p.cleanup(() => { assert.deepStrictEqual(results, ['Hello', 'Hello', 1000, '']); @@ -786,8 +786,8 @@ describe('Receiver', function () { p.add(textFrame); p.add(invalidFrame); - assert.strictEqual(p.state, 5); - assert.strictEqual(p.bufferedBytes, textFrame.length + invalidFrame.length); + assert.strictEqual(p._state, 5); + assert.strictEqual(p._bufferedBytes, textFrame.length + invalidFrame.length); p.cleanup(() => { assert.deepStrictEqual(results, [ @@ -821,8 +821,8 @@ describe('Receiver', function () { p.add(textFrame); p.add(incompleteFrame); - assert.strictEqual(p.state, 5); - assert.strictEqual(p.bufferedBytes, incompleteFrame.length); + assert.strictEqual(p._state, 5); + assert.strictEqual(p._bufferedBytes, incompleteFrame.length); p.cleanup(() => { assert.deepStrictEqual(results, ['Hello']); @@ -867,7 +867,7 @@ describe('Receiver', function () { crypto.randomBytes(623987) ]; - p.binaryType = 'arraybuffer'; + p._binaryType = 'arraybuffer'; p.onmessage = (data) => { assert.ok(data instanceof ArrayBuffer); assert.ok(Buffer.from(data).equals(Buffer.concat(frags))); @@ -895,7 +895,7 @@ describe('Receiver', function () { crypto.randomBytes(1) ]; - p.binaryType = 'fragments'; + p._binaryType = 'fragments'; p.onmessage = (data) => { assert.deepStrictEqual(data, frags); done(); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 4a71823ae..a2916e25f 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -256,14 +256,14 @@ describe('WebSocketServer', function () { }); describe('#maxpayload', function () { - it('maxpayload is passed on to clients,', function (done) { + it('maxpayload is passed on to clients', function (done) { const maxPayload = 20480; const wss = new WebSocketServer({ port: ++port, maxPayload }, () => { const ws = new WebSocket(`ws://localhost:${port}`); }); wss.on('connection', (client) => { - assert.strictEqual(client.maxPayload, maxPayload); + assert.strictEqual(client._maxPayload, maxPayload); wss.close(done); }); }); @@ -275,7 +275,7 @@ describe('WebSocketServer', function () { }); wss.on('connection', (client) => { - assert.strictEqual(client._receiver.maxPayload, maxPayload); + assert.strictEqual(client._receiver._maxPayload, maxPayload); wss.close(done); }); }); @@ -289,7 +289,7 @@ describe('WebSocketServer', function () { wss.on('connection', (client) => { assert.strictEqual( - client._receiver.extensions[PerMessageDeflate.extensionName]._maxPayload, + client._receiver._extensions[PerMessageDeflate.extensionName]._maxPayload, maxPayload ); wss.close(done); From fe44ff81cc414495f19416847ce658cbea079d06 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 16 May 2017 08:49:14 +0200 Subject: [PATCH 347/669] [major] Have permessage-deflate disabled by default (#1107) This commit disables permessage-deflate by default on the server. --- README.md | 22 ++++++---------------- doc/ws.md | 8 ++++---- lib/WebSocketServer.js | 2 +- test/WebSocket.test.js | 11 ++++++----- test/WebSocketServer.test.js | 23 +++++++++++++++++------ 5 files changed, 34 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index bbe86e7d3..1b38327d6 100644 --- a/README.md +++ b/README.md @@ -73,23 +73,13 @@ enables the client and server to negotiate a compression algorithm and its parameters, and then selectively apply it to the data payloads of each WebSocket message. -The extension is enabled by default but adds a significant overhead in terms of -performance and memory comsumption. We suggest to use WebSocket compression -only if it is really needed. +The extension is disabled by default on the server and enabled by default on +the client. It adds a significant overhead in terms of performance and memory +comsumption so we suggest to enable it only if it is really needed. -To disable the extension you can set the `perMessageDeflate` option to `false`. -On the server: - -```js -const WebSocket = require('ws'); - -const wss = new WebSocket.Server({ - perMessageDeflate: false, - port: 8080 -}); -``` - -On the client: +The client will only use the extension if it is supported and enabled on the +server. To always disable the extension on the client set the +`perMessageDeflate` option to `false`. ```js const WebSocket = require('ws'); diff --git a/doc/ws.md b/doc/ws.md index d8ca9d111..21c8c13f0 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -63,7 +63,7 @@ status code, otherwise the returned value sets the value of the `perMessageDeflate` can be used to control the behavior of [permessage-deflate extension][permessage-deflate]. -The extension is disabled when `false`. Defaults to `true`. If an object is +The extension is disabled when `false` (default value). If an object is provided then that is extension parameters: - `serverNoContextTakeover` {Boolean} Whether to use context take over or not. @@ -189,9 +189,9 @@ This class represents a WebSocket. It extends the `EventEmitter`. - `pfx` {String|Buffer} The private key, certificate, and CA certs. - `ca` {Array} Trusted certificates. -`perMessageDeflate` parameters are the same of the server, the only difference -is the direction of requests (e.g. `serverNoContextTakeover` is the value to be -requested to the server). +`perMessageDeflate` default value is `true`. When using an object, parameters +are the same of the server. The only difference is the direction of requests +(e.g. `serverNoContextTakeover` is the value to be requested to the server). Create a new WebSocket instance. diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 3735f990b..790add824 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -47,7 +47,7 @@ class WebSocketServer extends EventEmitter { options = Object.assign({ maxPayload: 100 * 1024 * 1024, - perMessageDeflate: true, + perMessageDeflate: false, handleProtocols: null, clientTracking: true, verifyClient: null, diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index d42f7431a..84fd09ea3 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -105,7 +105,7 @@ describe('WebSocket', function () { describe('properties', function () { it('#bytesReceived exposes number of bytes received', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: false }); + const ws = new WebSocket(`ws://localhost:${port}`); ws.on('message', () => { assert.strictEqual(ws.bytesReceived, 8); wss.close(done); @@ -150,7 +150,10 @@ describe('WebSocket', function () { }); it('takes into account the data in the sender queue', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { threshold: 0 } }); @@ -170,9 +173,7 @@ describe('WebSocket', function () { it('takes into account the data in the socket queue', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, { - perMessageDeflate: false - }); + const ws = new WebSocket(`ws://localhost:${port}`); }); wss.on('connection', (ws) => { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index a2916e25f..83519527e 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -283,9 +283,11 @@ describe('WebSocketServer', function () { it('maxpayload is passed on to permessage-deflate', function (done) { const PerMessageDeflate = require('../lib/PerMessageDeflate'); const maxPayload = 20480; - const wss = new WebSocketServer({ port: ++port, maxPayload }, () => { - const ws = new WebSocket(`ws://localhost:${port}`); - }); + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port, + maxPayload + }, () => new WebSocket(`ws://localhost:${port}`)); wss.on('connection', (client) => { assert.strictEqual( @@ -930,7 +932,10 @@ describe('WebSocketServer', function () { describe('permessage-deflate', function () { it('accept connections with permessage-deflate extension', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { const req = http.request({ headers: { 'Connection': 'Upgrade', @@ -950,7 +955,10 @@ describe('WebSocketServer', function () { }); it('does not accept connections with not defined extension parameter', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { const req = http.request({ headers: { 'Connection': 'Upgrade', @@ -977,7 +985,10 @@ describe('WebSocketServer', function () { }); it('does not accept connections with invalid extension parameter', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocketServer({ + perMessageDeflate: true, + port: ++port + }, () => { const req = http.request({ headers: { 'Connection': 'Upgrade', From e6f6eb1b1ab261c5058da54a7e4b79bba0648174 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 17 May 2017 07:39:17 +0200 Subject: [PATCH 348/669] [doc] Add missing argument types --- doc/ws.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index 21c8c13f0..207aa5afa 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -240,7 +240,7 @@ handshake. This allows you to read headers from the server, for example ### Event: 'message' -- `data` {String|Buffer} +- `data` {String|Buffer|ArrayBuffer|Buffer[]} Emitted when a message is received from the server. From 38df5a330aa91123851d8b49c231adf6c337ea77 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 17 May 2017 09:12:45 +0200 Subject: [PATCH 349/669] [dist] 3.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bd59aaacf..330b5d79a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "2.3.1", + "version": "3.0.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From dbab3fd0fd37d70b21639bfbde591380332e0c0e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 21 May 2017 10:18:41 +0200 Subject: [PATCH 350/669] [doc] Add FAQ about proxy support --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1b38327d6..0e8129588 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Windows Build](https://ci.appveyor.com/api/projects/status/github/websockets/ws?branch=master&svg=true)](https://ci.appveyor.com/project/lpinca/ws) [![Coverage Status](https://img.shields.io/coveralls/websockets/ws/master.svg)](https://coveralls.io/r/websockets/ws?branch=master) -`ws` is a simple to use, blazing fast, and thoroughly tested WebSocket client +ws is a simple to use, blazing fast, and thoroughly tested WebSocket client and server implementation. Passes the quite extensive Autobahn test suite. See http://websockets.github.io/ws/ @@ -35,6 +35,7 @@ communication. Browser clients must use the native * [FAQ](#faq) + [How to get the IP address of the client?](#how-to-get-the-ip-address-of-the-client) + [How to detect and close broken connections?](#how-to-detect-and-close-broken-connections) + + [How to connect via a proxy?](#how-to-connect-via-a-proxy) * [Changelog](#changelog) * [License](#license) @@ -51,7 +52,7 @@ npm install --save ws ### Opt-in for performance and spec compliance -There are 2 optional modules that can be installed along side with the `ws` +There are 2 optional modules that can be installed along side with the ws module. These modules are binary addons which improve certain operations. Prebuilt binaries are available for the most popular platforms so you don't necessarily need to have a C++ compiler installed on your machine. @@ -68,7 +69,7 @@ See [`/doc/ws.md`](./doc/ws.md) for Node.js-like docs for the ws classes. ## WebSocket compression -`ws` supports the [permessage-deflate extension][permessage-deflate] which +ws supports the [permessage-deflate extension][permessage-deflate] which enables the client and server to negotiate a compression algorithm and its parameters, and then selectively apply it to the data payloads of each WebSocket message. @@ -319,6 +320,11 @@ const interval = setInterval(function ping() { Pong messages are automatically sent in reponse to ping messages as required by the spec. +### How to connect via a proxy? + +Use a custom `http.Agent` implementation like [https-proxy-agent][] or +[socks-proxy-agent][]. + ## Changelog We're using the GitHub [releases][changelog] for changelog entries. @@ -327,5 +333,7 @@ We're using the GitHub [releases][changelog] for changelog entries. [MIT](LICENSE) +[https-proxy-agent]: https://github.com/TooTallNate/node-https-proxy-agent +[socks-proxy-agent]: https://github.com/TooTallNate/node-socks-proxy-agent [permessage-deflate]: https://tools.ietf.org/html/rfc7692 [changelog]: https://github.com/websockets/ws/releases From 62cd03ea3705123136c20eedac1b57559d8ea542 Mon Sep 17 00:00:00 2001 From: Jussi Virtanen Date: Tue, 23 May 2017 07:40:24 +0200 Subject: [PATCH 351/669] [doc] Fix typos in API documentation (#1119) --- doc/ws.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 207aa5afa..2313ea9bd 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -201,14 +201,14 @@ Create a new WebSocket instance. following URL scheme: ``` -ws+unix:///absolule/path/to/uds_socket:/pathname?search_params +ws+unix:///absolute/path/to/uds_socket:/pathname?search_params ``` Note that `:` is the separator between the socket path and the URL path. If the URL path is omitted ``` -ws+unix:///absolule/path/to/uds_socket +ws+unix:///absolute/path/to/uds_socket ``` it defaults to `/`. @@ -359,7 +359,7 @@ Send a ping. ### websocket.pong([data[, mask[, failSilently]]]) -- `data` {Any} The data to send in the ping frame. +- `data` {Any} The data to send in the pong frame. - `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults to `true` when `websocket` is not a server client. - `failSilently` {Boolean} Specifies whether or not to throw an error if the @@ -394,7 +394,7 @@ Removes an event listener emulating the `EventTarget` interface. ### websocket.resume() -Resume the socket +Resume the socket. ### websocket.send(data, [options][, callback]) From cb1c893ce94f3d800cd6e4166e6640bda24d74ab Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 24 May 2017 14:06:46 +0200 Subject: [PATCH 352/669] chore(package): update eslint-plugin-import to version 2.3.0 (#1121) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 330b5d79a..a4e764a2f 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "bufferutil": "~3.0.0", "eslint": "~3.19.0", "eslint-config-standard": "~10.2.0", - "eslint-plugin-import": "~2.2.0", + "eslint-plugin-import": "~2.3.0", "eslint-plugin-node": "~4.2.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", From 4edcc45cf001ac0f0036111b4e50d2a84b1dcffa Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 27 May 2017 09:14:35 +0200 Subject: [PATCH 353/669] [minor] Merge two consecutive if statements --- lib/PerMessageDeflate.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 17bbf75ed..8e457db4e 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -139,13 +139,10 @@ class PerMessageDeflate { ) { accepted.server_no_context_takeover = true; } - if (this._options.clientNoContextTakeover) { - accepted.client_no_context_takeover = true; - } - if ( + if (this._options.clientNoContextTakeover || ( this._options.clientNoContextTakeover !== false && params.client_no_context_takeover - ) { + )) { accepted.client_no_context_takeover = true; } if (typeof this._options.serverMaxWindowBits === 'number') { @@ -164,7 +161,7 @@ class PerMessageDeflate { return true; }); - if (!result) throw new Error(`Doesn't support the offered configuration`); + if (!result) throw new Error("Doesn't support the offered configuration"); return accepted; } From a7bd1e3b4042cf6a4a4f06a53185e9306c66cf28 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 27 May 2017 09:47:08 +0200 Subject: [PATCH 354/669] [doc] Add ISSUE_TEMPLATE.md --- ISSUE_TEMPLATE.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 ISSUE_TEMPLATE.md diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..f75647683 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,38 @@ + + +- [ ] I've searched for any related issues and avoided creating a duplicate issue. + +#### Description + + + +#### Reproducible in: + +version: +Node.js version(s): +OS version(s): + +#### Steps to reproduce: + +1. +2. +3. + +### Expected result: + + + +### Actual result: + + + +### Attachments: + + From 2e7f0b0f70393bd95f873d313423d24438198dc7 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 28 May 2017 10:09:18 +0200 Subject: [PATCH 355/669] [minor] Use zlib module constants --- lib/PerMessageDeflate.js | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 8e457db4e..c1e08f092 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -7,11 +7,8 @@ const bufferUtil = require('./BufferUtil'); const Buffer = safeBuffer.Buffer; -const AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15]; const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); const EMPTY_BLOCK = Buffer.from([0x00]); -const DEFAULT_WINDOW_BITS = 15; -const DEFAULT_MEM_LEVEL = 8; /** * Per-message Deflate implementation. @@ -232,7 +229,11 @@ class PerMessageDeflate { case 'client_max_window_bits': if (typeof value === 'string') { value = parseInt(value, 10); - if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) { + if ( + Number.isNaN(value) || + value < zlib.Z_MIN_WINDOWBITS || + value > zlib.Z_MAX_WINDOWBITS + ) { throw new Error(`invalid extension parameter value for ${key} (${value})`); } } @@ -261,10 +262,12 @@ class PerMessageDeflate { const endpoint = this._isServer ? 'client' : 'server'; if (!this._inflate) { - const maxWindowBits = this.params[`${endpoint}_max_window_bits`]; - this._inflate = zlib.createInflateRaw({ - windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS - }); + const key = `${endpoint}_max_window_bits`; + const windowBits = typeof this.params[key] !== 'number' + ? zlib.Z_DEFAULT_WINDOWBITS + : this.params[key]; + + this._inflate = zlib.createInflateRaw({ windowBits }); } this._inflate.writeInProgress = true; @@ -332,11 +335,15 @@ class PerMessageDeflate { const endpoint = this._isServer ? 'server' : 'client'; if (!this._deflate) { - const maxWindowBits = this.params[`${endpoint}_max_window_bits`]; + const key = `${endpoint}_max_window_bits`; + const windowBits = typeof this.params[key] !== 'number' + ? zlib.Z_DEFAULT_WINDOWBITS + : this.params[key]; + this._deflate = zlib.createDeflateRaw({ + memLevel: this._options.memLevel, flush: zlib.Z_SYNC_FLUSH, - windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS, - memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL + windowBits }); } this._deflate.writeInProgress = true; From 5af6f79e644800c900108ac792e367d933e1bc62 Mon Sep 17 00:00:00 2001 From: Anoop Mundathan Date: Mon, 29 May 2017 19:44:49 +0100 Subject: [PATCH 356/669] [doc] Fix typo (#1127) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e8129588..c869c207e 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ WebSocket message. The extension is disabled by default on the server and enabled by default on the client. It adds a significant overhead in terms of performance and memory -comsumption so we suggest to enable it only if it is really needed. +consumption so we suggest to enable it only if it is really needed. The client will only use the extension if it is supported and enabled on the server. To always disable the extension on the client set the From b66a6562aaf62a2b13bae942d0146adf52328bc5 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 2 Jun 2017 06:56:43 +0200 Subject: [PATCH 357/669] chore(package): update nyc to version 11.0.1 (#1130) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a4e764a2f..4c99d8708 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~3.4.1", - "nyc": "~10.3.0", + "nyc": "~11.0.1", "utf-8-validate": "~3.0.0" } } From 9c8024c41f5e2887975071c20c3ed0cf4effaf9e Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 2 Jun 2017 06:57:12 +0200 Subject: [PATCH 358/669] chore(package): update eslint-plugin-node to version 5.0.0 (#1129) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4c99d8708..5d0aa38f8 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "eslint": "~3.19.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.3.0", - "eslint-plugin-node": "~4.2.0", + "eslint-plugin-node": "~5.0.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~3.4.1", From 4e53caa723ed34e33c43cc2b562d00e2a33ab728 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 3 Jun 2017 07:15:48 +0200 Subject: [PATCH 359/669] fix(package): update safe-buffer to version 5.1.0 (#1132) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5d0aa38f8..e4559db4a 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "lint": "eslint ." }, "dependencies": { - "safe-buffer": "~5.0.1", + "safe-buffer": "~5.1.0", "ultron": "~1.1.0" }, "devDependencies": { From 942f825ff0702ce7ce66d8731a56b071269d88b4 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 3 Jun 2017 09:26:58 +0200 Subject: [PATCH 360/669] [minor] Simplify if condition --- lib/WebSocket.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 29180ea0f..320c725e1 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -131,10 +131,7 @@ class WebSocket extends EventEmitter { this._ultron.on('end', this._finalize); // ensure that the head is added to the receiver - if (head && head.length > 0) { - socket.unshift(head); - head = null; - } + if (head.length > 0) socket.unshift(head); // subsequent packets are pushed to the receiver this._ultron.on('data', (data) => { From 8c02d923413ec01afb660e98c12b583486e596be Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 10 Jun 2017 10:22:21 +0200 Subject: [PATCH 361/669] [example] Refactor SSL example --- examples/ssl.js | 70 +++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/examples/ssl.js b/examples/ssl.js index f1f1dee44..496daea80 100644 --- a/examples/ssl.js +++ b/examples/ssl.js @@ -1,47 +1,37 @@ +'use strict'; -(function () { - 'use strict'; +const https = require('https'); +const fs = require('fs'); - var fs = require('fs'); +const WebSocket = require('..'); - // you'll probably load configuration from config - var cfg = { - ssl: true, - port: 8080, - ssl_key: '/path/to/you/ssl.key', - ssl_cert: '/path/to/you/ssl.crt' - }; +const server = https.createServer({ + cert: fs.readFileSync('../test/fixtures/certificate.pem'), + key: fs.readFileSync('../test/fixtures/key.pem') +}); - var httpServ = (cfg.ssl) ? require('https') : require('http'); +const wss = new WebSocket.Server({ server }); - var WebSocketServer = require('../').Server; - - var app = null; - - // dummy request processing - var processRequest = function (req, res) { - res.writeHead(200); - res.end('All glory to WebSockets!\n'); - }; - - if (cfg.ssl) { - app = httpServ.createServer({ - - // providing server with SSL key/cert - key: fs.readFileSync(cfg.ssl_key), - cert: fs.readFileSync(cfg.ssl_cert) - - }, processRequest).listen(cfg.port); - } else { - app = httpServ.createServer(processRequest).listen(cfg.port); - } - - // passing or reference to web server so WS would knew port and SSL capabilities - var wss = new WebSocketServer({ server: app }); +wss.on('connection', function connection (ws) { + ws.on('message', function message (msg) { + console.log(msg); + }); +}); + +server.listen(function listening () { + // + // If the `rejectUnauthorized` option is not `false`, the server certificate + // is verified against a list of well-known CAs. An 'error' event is emitted + // if verification fails. + // + // The certificate used in this example is self-signed so `rejectUnauthorized` + // is set to `false`. + // + const ws = new WebSocket(`wss://localhost:${server.address().port}`, { + rejectUnauthorized: false + }); - wss.on('connection', function (wsConnect) { - wsConnect.on('message', function (message) { - console.log(message); - }); + ws.on('open', function open () { + ws.send('All glory to WebSockets!'); }); -}()); +}); From f8f149e54e70f8fc1a49dd44b674e0fc9ddcf763 Mon Sep 17 00:00:00 2001 From: Steve Date: Wed, 14 Jun 2017 17:10:26 +1200 Subject: [PATCH 362/669] [doc] Fix typo (#1141) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c869c207e..12aefb1aa 100644 --- a/README.md +++ b/README.md @@ -275,7 +275,7 @@ wss.on('connection', function connection(ws, req) { }); ``` -When the server runs behing a proxy like NGINX, the de-facto standard is to use +When the server runs behind a proxy like NGINX, the de-facto standard is to use the `X-Forwarded-For` header. ```js From bb2e952e6be9f92cd75a501935ac215b97413458 Mon Sep 17 00:00:00 2001 From: Roy Miloh Date: Mon, 19 Jun 2017 23:15:48 +0300 Subject: [PATCH 363/669] [fix] Set wasClean to true when close code is in 3000-4999 range (#1146) --- lib/EventTarget.js | 2 +- test/WebSocket.test.js | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/EventTarget.js b/lib/EventTarget.js index 8f9a943a8..fe1730255 100644 --- a/lib/EventTarget.js +++ b/lib/EventTarget.js @@ -55,7 +55,7 @@ class CloseEvent extends Event { constructor (code, reason, target) { super('close', target); - this.wasClean = code === undefined || code === 1000; + this.wasClean = code === undefined || code === 1000 || (code >= 3000 && code <= 4999); this.reason = reason; this.target = target; this.type = 'close'; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 84fd09ea3..0ebfbe52d 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1417,6 +1417,32 @@ describe('WebSocket', function () { wss.on('connection', (client) => client.close(1000)); }); + it('should assign "true" to wasClean when server closes with code 3000', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.addEventListener('close', (closeEvent) => { + assert.ok(closeEvent.wasClean); + wss.close(done); + }); + }); + + wss.on('connection', (client) => client.close(3000)); + }); + + it('should assign "true" to wasClean when server closes with code 4999', function (done) { + const wss = new WebSocketServer({ port: ++port }, () => { + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.addEventListener('close', (closeEvent) => { + assert.ok(closeEvent.wasClean); + wss.close(done); + }); + }); + + wss.on('connection', (client) => client.close(4999)); + }); + it('should receive valid CloseEvent when server closes with code 1001', function (done) { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); From cd55ced624c910469ddfe0d288fd11ad89579342 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 22 Jun 2017 22:02:59 +0200 Subject: [PATCH 364/669] chore(package): update eslint-plugin-import to version 2.5.0 (#1147) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e4559db4a..5c31c4e73 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "bufferutil": "~3.0.0", "eslint": "~3.19.0", "eslint-config-standard": "~10.2.0", - "eslint-plugin-import": "~2.3.0", + "eslint-plugin-import": "~2.5.0", "eslint-plugin-node": "~5.0.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", From 2d4dc08a624ec8fee135135f47df2c208da8e5fc Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 23 Jun 2017 20:45:27 +0200 Subject: [PATCH 365/669] chore(package): update eslint-plugin-import to version 2.6.0 (#1149) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5c31c4e73..356548731 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "bufferutil": "~3.0.0", "eslint": "~3.19.0", "eslint-config-standard": "~10.2.0", - "eslint-plugin-import": "~2.5.0", + "eslint-plugin-import": "~2.6.0", "eslint-plugin-node": "~5.0.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", From 043442855f58f84a99f1b2eb21ef76d98b7c1144 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 24 Jun 2017 07:35:57 +0200 Subject: [PATCH 366/669] chore(package): update eslint to version 4.1.0 (#1151) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 356548731..ba9cf32ae 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~3.19.0", + "eslint": "~4.1.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.6.0", "eslint-plugin-node": "~5.0.0", From c311c43bff75e961e719df6d1e41a98de4c712e0 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 24 Jun 2017 07:38:15 +0200 Subject: [PATCH 367/669] [lint] Fix lint issues --- lib/PerMessageDeflate.js | 42 +++++++++++++++++++--------------------- lib/Validation.js | 2 +- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index c1e08f092..c2c613759 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -113,20 +113,17 @@ class PerMessageDeflate { acceptAsServer (paramsList) { const accepted = {}; const result = paramsList.some((params) => { - if (( - this._options.serverNoContextTakeover === false && - params.server_no_context_takeover - ) || ( - this._options.serverMaxWindowBits === false && - params.server_max_window_bits - ) || ( - typeof this._options.serverMaxWindowBits === 'number' && - typeof params.server_max_window_bits === 'number' && - this._options.serverMaxWindowBits > params.server_max_window_bits - ) || ( - typeof this._options.clientMaxWindowBits === 'number' && - !params.client_max_window_bits - )) { + if ( + (this._options.serverNoContextTakeover === false && + params.server_no_context_takeover) || + (this._options.serverMaxWindowBits === false && + params.server_max_window_bits) || + (typeof this._options.serverMaxWindowBits === 'number' && + typeof params.server_max_window_bits === 'number' && + this._options.serverMaxWindowBits > params.server_max_window_bits) || + (typeof this._options.clientMaxWindowBits === 'number' && + !params.client_max_window_bits) + ) { return; } @@ -136,10 +133,11 @@ class PerMessageDeflate { ) { accepted.server_no_context_takeover = true; } - if (this._options.clientNoContextTakeover || ( - this._options.clientNoContextTakeover !== false && - params.client_no_context_takeover - )) { + if ( + this._options.clientNoContextTakeover || + (this._options.clientNoContextTakeover !== false && + params.client_no_context_takeover) + ) { accepted.client_no_context_takeover = true; } if (typeof this._options.serverMaxWindowBits === 'number') { @@ -189,10 +187,10 @@ class PerMessageDeflate { throw new Error('Invalid value for "client_max_window_bits"'); } if ( - typeof this._options.clientMaxWindowBits === 'number' && ( - !params.client_max_window_bits || - params.client_max_window_bits > this._options.clientMaxWindowBits - )) { + typeof this._options.clientMaxWindowBits === 'number' && + (!params.client_max_window_bits || + params.client_max_window_bits > this._options.clientMaxWindowBits) + ) { throw new Error('Invalid value for "client_max_window_bits"'); } } diff --git a/lib/Validation.js b/lib/Validation.js index fcb170f31..35c7e4f2a 100644 --- a/lib/Validation.js +++ b/lib/Validation.js @@ -10,7 +10,7 @@ try { const isValidUTF8 = require('utf-8-validate'); module.exports = typeof isValidUTF8 === 'object' - ? isValidUTF8.Validation.isValidUTF8 // utf-8-validate@<3.0.0 + ? isValidUTF8.Validation.isValidUTF8 // utf-8-validate@<3.0.0 : isValidUTF8; } catch (e) /* istanbul ignore next */ { module.exports = () => true; From 034ab89734d61c0d12a26fa033a37ca7ab398d56 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 24 Jun 2017 07:42:48 +0200 Subject: [PATCH 368/669] [ci] Test on node 8 --- .travis.yml | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 81cf3a9a2..1670a8aff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: node_js dist: trusty sudo: false node_js: - - "7" + - "8" - "6" - "4" - "4.1.0" diff --git a/appveyor.yml b/appveyor.yml index d893ad905..db5faaaff 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ environment: matrix: - - nodejs_version: "7" + - nodejs_version: "8" - nodejs_version: "6" - nodejs_version: "4" - nodejs_version: "4.1.0" From 833766fc5d9df27f5bfd5476c03e8f96f92a324d Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 26 Jun 2017 07:41:57 +0200 Subject: [PATCH 369/669] chore(package): update eslint-plugin-node to version 5.1.0 (#1155) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ba9cf32ae..6d1b11ad8 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "eslint": "~4.1.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.6.0", - "eslint-plugin-node": "~5.0.0", + "eslint-plugin-node": "~5.1.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~3.4.1", From afb36e92ca7dba7b7f70fe5b58104e8a9692cade Mon Sep 17 00:00:00 2001 From: Yaowei Date: Wed, 5 Jul 2017 22:01:30 +0800 Subject: [PATCH 370/669] [doc] Fix typos in API docs (#1165) --- doc/ws.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 2313ea9bd..4da6e4ea7 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -136,7 +136,7 @@ when the HTTP server is passed via the `server` option, this method is called automatically. When operating in "noServer" mode, this method must be called manually. -If the upgrade is successfull, the `callback` is called with a `WebSocket` +If the upgrade is successful, the `callback` is called with a `WebSocket` object as parameter. ### server.shouldHandle(request) @@ -149,7 +149,7 @@ against the `path` option if provided. The return value, `true` or `false`, determines whether or not to accept the handshake. -This method can be overriden when a custom handling logic is required. +This method can be overridden when a custom handling logic is required. ## Class: WebSocket @@ -285,7 +285,7 @@ A string indicating the type of binary data being transmitted by the connection. This should be one of "nodebuffer", "arraybuffer" or "fragments". Defaults to "nodebuffer". Type "fragments" will emit the array of fragments as received from the sender, without copyfull concatenation, which is useful for the performance -of binary protocols transfering large messages with multiple fragments. +of binary protocols transferring large messages with multiple fragments. ### websocket.bufferedAmount From 3544e3045794b011426368d3e26e0fd3f934b6a7 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 6 Jul 2017 19:04:46 +0200 Subject: [PATCH 371/669] chore(package): update eslint-plugin-import to version 2.7.0 (#1167) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6d1b11ad8..07bd5a63f 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "bufferutil": "~3.0.0", "eslint": "~4.1.0", "eslint-config-standard": "~10.2.0", - "eslint-plugin-import": "~2.6.0", + "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.1.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", From 16bbddb23adec778978d4dd9cdd6a56ce92e4786 Mon Sep 17 00:00:00 2001 From: Matt Audesse Date: Fri, 7 Jul 2017 01:32:45 -0400 Subject: [PATCH 372/669] [doc] Fix "unware" and "reponse" typos in README.md (#1168) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 12aefb1aa..88c480d78 100644 --- a/README.md +++ b/README.md @@ -287,7 +287,7 @@ wss.on('connection', function connection(ws, req) { ### How to detect and close broken connections? Sometimes the link between the server and the client can be interrupted in a -way that keeps both the server and the client unware of the broken state of the +way that keeps both the server and the client unaware of the broken state of the connection (e.g. when pulling the cord). In these cases ping messages can be used as a means to verify that the remote @@ -317,7 +317,7 @@ const interval = setInterval(function ping() { }, 30000); ``` -Pong messages are automatically sent in reponse to ping messages as required +Pong messages are automatically sent in response to ping messages as required by the spec. ### How to connect via a proxy? From d7811907ad590cbc7a6354f61dd283e36d1eee4d Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 9 Jul 2017 07:18:21 +0200 Subject: [PATCH 373/669] chore(package): update eslint to version 4.2.0 (#1169) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 07bd5a63f..b9b358406 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.1.0", + "eslint": "~4.2.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.1.0", From ab6c1c23fc2bac092d4f0ad0aaeb4322bdd48252 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 16 Jul 2017 15:21:36 +0200 Subject: [PATCH 374/669] [minor] Remove redundant assignments --- lib/EventTarget.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/EventTarget.js b/lib/EventTarget.js index fe1730255..9d0461a18 100644 --- a/lib/EventTarget.js +++ b/lib/EventTarget.js @@ -57,8 +57,6 @@ class CloseEvent extends Event { this.wasClean = code === undefined || code === 1000 || (code >= 3000 && code <= 4999); this.reason = reason; - this.target = target; - this.type = 'close'; this.code = code; } } From 5c06c8f4127b8aea4ec34cb3f2236a579901214a Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 21 Jul 2017 20:51:28 +0200 Subject: [PATCH 375/669] chore(package): update eslint to version 4.3.0 (#1176) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b9b358406..eec744f56 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.2.0", + "eslint": "~4.3.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.1.0", From b32016995e9620ed6d5826464b2c3ee931e2240a Mon Sep 17 00:00:00 2001 From: Ido schachter Date: Thu, 27 Jul 2017 08:42:35 +0300 Subject: [PATCH 376/669] [feature] Allow client to specify handshake request timeout (#1177) --- doc/ws.md | 1 + lib/WebSocket.js | 10 ++++++++++ test/WebSocket.test.js | 15 +++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/doc/ws.md b/doc/ws.md index 4da6e4ea7..9e5e6f413 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -170,6 +170,7 @@ This class represents a WebSocket. It extends the `EventEmitter`. - `protocols` {String|Array} The list of subprotocols. - `options` {Object} - `protocol` {String} Value of the `Sec-WebSocket-Protocol` header. + - `handshakeTimeout` {Number} Timeout in milliseconds for the handshake request. - `perMessageDeflate` {Boolean|Object} Enable/disable permessage-deflate. - `localAddress` {String} Local interface to bind for network connections. - `protocolVersion` {Number} Value of the `Sec-WebSocket-Version` header. diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 320c725e1..760ae34fa 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -471,6 +471,7 @@ function initAsServerClient (socket, head, options) { * @param {Object} options Connection options * @param {String} options.protocol Value of the `Sec-WebSocket-Protocol` header * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate + * @param {Number} options.handshakeTimeout Timeout in milliseconds for the handshake request * @param {String} options.localAddress Local interface to bind for network connections * @param {Number} options.protocolVersion Value of the `Sec-WebSocket-Version` header * @param {Object} options.headers An object containing request headers @@ -493,6 +494,7 @@ function initAsClient (address, protocols, options) { protocolVersion: protocolVersions[1], protocol: protocols.join(','), perMessageDeflate: true, + handshakeTimeout: null, localAddress: null, headers: null, family: null, @@ -632,6 +634,14 @@ function initAsClient (address, protocols, options) { this._req = httpObj.get(requestOptions); + if (options.handshakeTimeout) { + this._req.setTimeout(options.handshakeTimeout, () => { + this._req.abort(); + this.emit('error', new Error('opening handshake has timed out')); + this.finalize(true); + }); + } + this._req.on('error', (error) => { if (this._req.aborted) return; diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 0ebfbe52d..1aa45d12e 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -461,6 +461,21 @@ describe('WebSocket', function () { req.abort(); }); }); + + it('emits an error if the opening handshake timeout expires', function (done) { + server.once('upgrade', (req, socket) => socket.on('end', socket.end)); + + const ws = new WebSocket(`ws://localhost:${port}`, null, { + handshakeTimeout: 100 + }); + + ws.on('open', () => assert.fail(null, null, 'connect shouldn\'t be raised here')); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'opening handshake has timed out'); + done(); + }); + }); }); describe('connection with query string', function () { From 4e3ada1291b58c2a46303744835e5d466fcbc9b1 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 27 Jul 2017 07:59:12 +0200 Subject: [PATCH 377/669] [dist] 3.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eec744f56..996cdc1b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "3.0.0", + "version": "3.1.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From faea8121a0fc05561b170cca9c523a0751df1df0 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 29 Jul 2017 23:29:56 +0200 Subject: [PATCH 378/669] chore(package): update nyc to version 11.1.0 (#1178) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 996cdc1b2..ad8270469 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~3.4.1", - "nyc": "~11.0.1", + "nyc": "~11.1.0", "utf-8-validate": "~3.0.0" } } From 63b3c11f0cadcd5ba3ac897e7f1b20299cb24597 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 1 Aug 2017 07:20:07 +0200 Subject: [PATCH 379/669] chore(package): update mocha to version 3.5.0 (#1179) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ad8270469..603da662b 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "eslint-plugin-node": "~5.1.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", - "mocha": "~3.4.1", + "mocha": "~3.5.0", "nyc": "~11.1.0", "utf-8-validate": "~3.0.0" } From a1ac65994d59d46feb7b93ab1f91cd658394e301 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 5 Aug 2017 21:05:14 +0200 Subject: [PATCH 380/669] chore(package): update eslint to version 4.4.0 (#1183) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 603da662b..60562d230 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.3.0", + "eslint": "~4.4.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.1.0", From 381e7e0411fc45e05b3bb4fa9fd884ff03c6c886 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 17 Aug 2017 09:25:51 +0200 Subject: [PATCH 381/669] [benchmark] Fix parser benchmark --- bench/parser.benchmark.js | 61 ++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index 5e6259cb4..b3484b611 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -10,10 +10,10 @@ const safeBuffer = require('safe-buffer'); const benchmark = require('benchmark'); const crypto = require('crypto'); -const util = require('../test/hybi-util'); const WebSocket = require('..'); const Receiver = WebSocket.Receiver; +const Sender = WebSocket.Sender; const Buffer = safeBuffer.Buffer; // @@ -21,44 +21,51 @@ const Buffer = safeBuffer.Buffer; // expected. // Receiver.prototype.cleanup = function () { - this.state = 0; + this._state = 0; }; -function createBinaryPacket (length) { - const message = crypto.randomBytes(length); +const options = { + fin: true, + rsv1: false, + mask: true, + readOnly: false +}; - return Buffer.from('82' + util.getHybiLengthAsHexString(length, true) + - '3483a868' + util.mask(message, '3483a868').toString('hex'), 'hex'); -} +function createBinaryFrame (length) { + const list = Sender.frame( + crypto.randomBytes(length), + Object.assign({ opcode: 0x02 }, options) + ); -const pingMessage = 'Hello'; -const pingPacket1 = Buffer.from('89' + util.pack(2, 0x80 | pingMessage.length) + - '3483a868' + util.mask(pingMessage, '3483a868').toString('hex'), 'hex'); + return Buffer.concat(list); +} -const textMessage = 'a'.repeat(20); -const maskedTextPacket = Buffer.from('81' + util.pack(2, 0x80 | textMessage.length) + - '61616161' + util.mask(textMessage, '61616161').toString('hex'), 'hex'); +const pingFrame1 = Buffer.concat(Sender.frame( + crypto.randomBytes(5), + Object.assign({ opcode: 0x09 }, options) +)); -const pingPacket2 = Buffer.from('8900', 'hex'); -const closePacket = Buffer.from('8800', 'hex'); -const binaryDataPacket = createBinaryPacket(125); -const binaryDataPacket2 = createBinaryPacket(65535); -const binaryDataPacket3 = createBinaryPacket(200 * 1024); -const binaryDataPacket4 = createBinaryPacket(1024 * 1024); +const textFrame = Buffer.from('819461616161' + '61'.repeat(20), 'hex'); +const pingFrame2 = Buffer.from('8900', 'hex'); +const closeFrame = Buffer.from('8800', 'hex'); +const binaryFrame1 = createBinaryFrame(125); +const binaryFrame2 = createBinaryFrame(65535); +const binaryFrame3 = createBinaryFrame(200 * 1024); +const binaryFrame4 = createBinaryFrame(1024 * 1024); const suite = new benchmark.Suite(); const receiver = new Receiver(); receiver.onmessage = receiver.onclose = receiver.onping = () => {}; -suite.add('ping message', () => receiver.add(pingPacket1)); -suite.add('ping with no data', () => receiver.add(pingPacket2)); -suite.add('close message', () => receiver.add(closePacket)); -suite.add('masked text message (20 bytes)', () => receiver.add(maskedTextPacket)); -suite.add('binary data (125 bytes)', () => receiver.add(binaryDataPacket)); -suite.add('binary data (65535 bytes)', () => receiver.add(binaryDataPacket2)); -suite.add('binary data (200 KiB)', () => receiver.add(binaryDataPacket3)); -suite.add('binary data (1 MiB)', () => receiver.add(binaryDataPacket4)); +suite.add('ping frame (5 bytes payload)', () => receiver.add(pingFrame1)); +suite.add('ping frame (no payload)', () => receiver.add(pingFrame2)); +suite.add('close frame (no payload)', () => receiver.add(closeFrame)); +suite.add('text frame (20 bytes payload)', () => receiver.add(textFrame)); +suite.add('binary frame (125 bytes payload)', () => receiver.add(binaryFrame1)); +suite.add('binary frame (65535 bytes payload)', () => receiver.add(binaryFrame2)); +suite.add('binary frame (200 KiB payload)', () => receiver.add(binaryFrame3)); +suite.add('binary frame (1 MiB payload)', () => receiver.add(binaryFrame4)); suite.on('cycle', (e) => console.log(e.target.toString())); From ed4cf531001aa8e330daf1cec5526769963cb232 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 17 Aug 2017 12:17:10 +0200 Subject: [PATCH 382/669] [test] Remove hybi-util.js --- test/Receiver.test.js | 206 +++++++++++++++++++++++++++++------------- test/hybi-util.js | 74 --------------- 2 files changed, 142 insertions(+), 138 deletions(-) delete mode 100644 test/hybi-util.js diff --git a/test/Receiver.test.js b/test/Receiver.test.js index ffd579e00..bb75a8993 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -7,7 +7,6 @@ const crypto = require('crypto'); const PerMessageDeflate = require('../lib/PerMessageDeflate'); const Receiver = require('../lib/Receiver'); const Sender = require('../lib/Sender'); -const util = require('./hybi-util'); const Buffer = safeBuffer.Buffer; @@ -50,9 +49,15 @@ describe('Receiver', function () { const p = new Receiver(); const msg = 'A'.repeat(200); - const mask = '3483a868'; - const frame = Buffer.from('81FE' + util.pack(4, msg.length) + mask + - util.mask(msg, mask).toString('hex'), 'hex'); + const list = Sender.frame(Buffer.from(msg), { + fin: true, + rsv1: false, + opcode: 0x01, + mask: true, + readOnly: false + }); + + const frame = Buffer.concat(list); p.onmessage = function (data) { assert.strictEqual(data, msg); @@ -67,16 +72,22 @@ describe('Receiver', function () { const p = new Receiver(); const msg = 'A'.repeat(64 * 1024); - const mask = '3483a868'; - const frame = '81FF' + util.pack(16, msg.length) + mask + - util.mask(msg, mask).toString('hex'); + const list = Sender.frame(Buffer.from(msg), { + fin: true, + rsv1: false, + opcode: 0x01, + mask: true, + readOnly: false + }); + + const frame = Buffer.concat(list); p.onmessage = function (data) { assert.strictEqual(data, msg); done(); }; - p.add(Buffer.from(frame, 'hex')); + p.add(frame); }); it('can parse a fragmented masked text message of 300 B', function (done) { @@ -86,35 +97,46 @@ describe('Receiver', function () { const fragment1 = msg.substr(0, 150); const fragment2 = msg.substr(150); - const mask = '3483a868'; - const frame1 = '01FE' + util.pack(4, fragment1.length) + mask + - util.mask(fragment1, mask).toString('hex'); - const frame2 = '80FE' + util.pack(4, fragment2.length) + mask + - util.mask(fragment2, mask).toString('hex'); + const options = { rsv1: false, mask: true, readOnly: false }; + + const frame1 = Buffer.concat(Sender.frame( + Buffer.from(fragment1), + Object.assign({ fin: false, opcode: 0x01 }, options) + )); + const frame2 = Buffer.concat(Sender.frame( + Buffer.from(fragment2), + Object.assign({ fin: true, opcode: 0x00 }, options) + )); p.onmessage = function (data) { assert.strictEqual(data, msg); done(); }; - p.add(Buffer.from(frame1, 'hex')); - p.add(Buffer.from(frame2, 'hex')); + p.add(frame1); + p.add(frame2); }); it('can parse a ping message', function (done) { const p = new Receiver(); const msg = 'Hello'; - const mask = '3483a868'; - const frame = '89' + util.getHybiLengthAsHexString(msg.length, true) + mask + - util.mask(msg, mask).toString('hex'); + const list = Sender.frame(Buffer.from(msg), { + fin: true, + rsv1: false, + opcode: 0x09, + mask: true, + readOnly: false + }); + + const frame = Buffer.concat(list); p.onping = function (data) { assert.strictEqual(data.toString(), msg); done(); }; - p.add(Buffer.from(frame, 'hex')); + p.add(frame); }); it('can parse a ping with no data', function (done) { @@ -136,13 +158,20 @@ describe('Receiver', function () { const fragment1 = msg.substr(0, 150); const fragment2 = msg.substr(150); - const mask = '3483a868'; - const frame1 = '01FE' + util.pack(4, fragment1.length) + mask + - util.mask(fragment1, mask).toString('hex'); - const frame2 = '89' + util.getHybiLengthAsHexString(pingMessage.length, true) + mask + - util.mask(pingMessage, mask).toString('hex'); - const frame3 = '80FE' + util.pack(4, fragment2.length) + mask + - util.mask(fragment2, mask).toString('hex'); + const options = { rsv1: false, mask: true, readOnly: false }; + + const frame1 = Buffer.concat(Sender.frame( + Buffer.from(fragment1), + Object.assign({ fin: false, opcode: 0x01 }, options) + )); + const frame2 = Buffer.concat(Sender.frame( + Buffer.from(pingMessage), + Object.assign({ fin: true, opcode: 0x09 }, options) + )); + const frame3 = Buffer.concat(Sender.frame( + Buffer.from(fragment2), + Object.assign({ fin: true, opcode: 0x00 }, options) + )); let gotPing = false; @@ -156,9 +185,9 @@ describe('Receiver', function () { assert.strictEqual(data.toString(), pingMessage); }; - p.add(Buffer.from(frame1, 'hex')); - p.add(Buffer.from(frame2, 'hex')); - p.add(Buffer.from(frame3, 'hex')); + p.add(frame1); + p.add(frame2); + p.add(frame3); }); it('can parse a fragmented masked text message of 300 B with a ping in the middle (2/2)', function (done) { @@ -169,19 +198,30 @@ describe('Receiver', function () { const fragment1 = msg.substr(0, 150); const fragment2 = msg.substr(150); - const mask = '3483a868'; - const frame1 = '01FE' + util.pack(4, fragment1.length) + mask + - util.mask(fragment1, mask).toString('hex'); - const frame2 = '89' + util.getHybiLengthAsHexString(pingMessage.length, true) + mask + - util.mask(pingMessage, mask).toString('hex'); - const frame3 = '80FE' + util.pack(4, fragment2.length) + mask + - util.mask(fragment2, mask).toString('hex'); + const options = { rsv1: false, mask: true, readOnly: false }; + + const frame1 = Buffer.concat(Sender.frame( + Buffer.from(fragment1), + Object.assign({ fin: false, opcode: 0x01 }, options) + )); + const frame2 = Buffer.concat(Sender.frame( + Buffer.from(pingMessage), + Object.assign({ fin: true, opcode: 0x09 }, options) + )); + const frame3 = Buffer.concat(Sender.frame( + Buffer.from(fragment2), + Object.assign({ fin: true, opcode: 0x00 }, options) + )); - let buffers = []; + let chunks = []; + const splitBuffer = (buf) => { + const i = Math.floor(buf.length / 2); + return [buf.slice(0, i), buf.slice(i)]; + }; - buffers = buffers.concat(util.splitBuffer(Buffer.from(frame1, 'hex'))); - buffers = buffers.concat(util.splitBuffer(Buffer.from(frame2, 'hex'))); - buffers = buffers.concat(util.splitBuffer(Buffer.from(frame3, 'hex'))); + chunks = chunks.concat(splitBuffer(frame1)); + chunks = chunks.concat(splitBuffer(frame2)); + chunks = chunks.concat(splitBuffer(frame3)); let gotPing = false; @@ -195,8 +235,8 @@ describe('Receiver', function () { assert.strictEqual(data.toString(), pingMessage); }; - for (let i = 0; i < buffers.length; ++i) { - p.add(buffers[i]); + for (let i = 0; i < chunks.length; ++i) { + p.add(chunks[i]); } }); @@ -204,63 +244,88 @@ describe('Receiver', function () { const p = new Receiver(); const msg = crypto.randomBytes(100); - const mask = '3483a868'; - const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + - util.mask(msg, mask).toString('hex'); + const list = Sender.frame(msg, { + fin: true, + rsv1: false, + opcode: 0x02, + mask: true, + readOnly: true + }); + + const frame = Buffer.concat(list); p.onmessage = function (data) { assert.ok(data.equals(msg)); done(); }; - p.add(Buffer.from(frame, 'hex')); + p.add(frame); }); it('can parse a 256 B long masked binary message', function (done) { const p = new Receiver(); const msg = crypto.randomBytes(256); - const mask = '3483a868'; - const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + - util.mask(msg, mask).toString('hex'); + const list = Sender.frame(msg, { + fin: true, + rsv1: false, + opcode: 0x02, + mask: true, + readOnly: true + }); + + const frame = Buffer.concat(list); p.onmessage = function (data) { assert.ok(data.equals(msg)); done(); }; - p.add(Buffer.from(frame, 'hex')); + p.add(frame); }); it('can parse a 200 KiB long masked binary message', function (done) { const p = new Receiver(); const msg = crypto.randomBytes(200 * 1024); - const mask = '3483a868'; - const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + - util.mask(msg, mask).toString('hex'); + const list = Sender.frame(msg, { + fin: true, + rsv1: false, + opcode: 0x02, + mask: true, + readOnly: true + }); + + const frame = Buffer.concat(list); p.onmessage = function (data) { assert.ok(data.equals(msg)); done(); }; - p.add(Buffer.from(frame, 'hex')); + p.add(frame); }); it('can parse a 200 KiB long unmasked binary message', function (done) { const p = new Receiver(); const msg = crypto.randomBytes(200 * 1024); - const frame = '82' + util.getHybiLengthAsHexString(msg.length, false) + - msg.toString('hex'); + const list = Sender.frame(msg, { + fin: true, + rsv1: false, + opcode: 0x02, + mask: false, + readOnly: true + }); + + const frame = Buffer.concat(list); p.onmessage = function (data) { assert.ok(data.equals(msg)); done(); }; - p.add(Buffer.from(frame, 'hex')); + p.add(frame); }); it('can parse compressed message', function (done) { @@ -602,9 +667,15 @@ describe('Receiver', function () { const p = new Receiver({}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); - const mask = '3483a868'; - const frame = '82' + util.getHybiLengthAsHexString(msg.length, true) + mask + - util.mask(msg, mask).toString('hex'); + const list = Sender.frame(msg, { + fin: true, + rsv1: false, + opcode: 0x02, + mask: true, + readOnly: true + }); + + const frame = Buffer.concat(list); p.onerror = function (err, code) { assert.ok(err instanceof Error); @@ -613,15 +684,22 @@ describe('Receiver', function () { done(); }; - p.add(Buffer.from(frame, 'hex')); + p.add(frame); }); it('raises an error on a 200 KiB long unmasked binary message when `maxPayload` is 20 KiB', function (done) { const p = new Receiver({}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); - const frame = '82' + util.getHybiLengthAsHexString(msg.length, false) + - msg.toString('hex'); + const list = Sender.frame(msg, { + fin: true, + rsv1: false, + opcode: 0x02, + mask: false, + readOnly: true + }); + + const frame = Buffer.concat(list); p.onerror = function (err, code) { assert.ok(err instanceof Error); @@ -630,7 +708,7 @@ describe('Receiver', function () { done(); }; - p.add(Buffer.from(frame, 'hex')); + p.add(frame); }); it('raises an error on a compressed message that exceeds `maxPayload`', function (done) { diff --git a/test/hybi-util.js b/test/hybi-util.js deleted file mode 100644 index 3f0196b92..000000000 --- a/test/hybi-util.js +++ /dev/null @@ -1,74 +0,0 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - -'use strict'; - -const safeBuffer = require('safe-buffer'); - -const Buffer = safeBuffer.Buffer; - -/** - * Performs hybi07+ type masking. - */ -function mask (buf, maskString) { - const _mask = Buffer.from(maskString || '3483a868', 'hex'); - - buf = Buffer.from(buf); - - for (let i = 0; i < buf.length; ++i) { - buf[i] ^= _mask[i % 4]; - } - - return buf; -} - -/** - * Left pads the string `s` to a total length of `n` with char `c`. - */ -function padl (s, n, c) { - return c.repeat(n - s.length) + s; -} - -/** - * Returns a hex string, representing a specific byte count `length`, from a number. - */ -function pack (length, number) { - return padl(number.toString(16), length, '0'); -} - -/** - * Returns a hex string representing the length of a message. - */ -function getHybiLengthAsHexString (len, masked) { - let s; - - masked = masked ? 0x80 : 0; - - if (len < 126) { - s = pack(2, masked | len); - } else if (len < 65536) { - s = pack(2, masked | 126) + pack(4, len); - } else { - s = pack(2, masked | 127) + pack(16, len); - } - - return s; -} - -/** - * Split a buffer in two. - */ -function splitBuffer (buf) { - const i = Math.floor(buf.length / 2); - return [buf.slice(0, i), buf.slice(i)]; -} - -module.exports = { - getHybiLengthAsHexString, - splitBuffer, - mask, - pack -}; From 6663b89cd7c19269c6f9a527e96f3cd7db0c5fea Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 19 Aug 2017 07:23:40 +0200 Subject: [PATCH 383/669] chore(package): update eslint to version 4.5.0 (#1190) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 60562d230..3f6c0a893 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.4.0", + "eslint": "~4.5.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.1.0", From 92750e359462c732edc74234d6c7a58f94233011 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 2 Sep 2017 07:03:54 +0200 Subject: [PATCH 384/669] chore(package): update eslint to version 4.6.0 (#1197) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3f6c0a893..64cc2fd54 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.5.0", + "eslint": "~4.6.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.1.0", From 7234fd4981cd799198ddbec5d7df6b7623d4be24 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 5 Sep 2017 22:19:21 +0200 Subject: [PATCH 385/669] chore(package): update nyc to version 11.2.0 (#1198) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 64cc2fd54..cedbec7e3 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~3.5.0", - "nyc": "~11.1.0", + "nyc": "~11.2.0", "utf-8-validate": "~3.0.0" } } From 80445e7d6caf0ec021f0dcdb703d048b2f005dce Mon Sep 17 00:00:00 2001 From: Samuel Reed Date: Thu, 7 Sep 2017 15:39:03 -0500 Subject: [PATCH 386/669] [feature] Allow 'level' option (#1199) This corresponds to the zlib level as described in https://nodejs.org/api/zlib.html#zlib_class_options --- doc/ws.md | 3 +- lib/PerMessageDeflate.js | 1 + test/PerMessageDeflate.test.js | 55 ++++++++++++++++++++++++++++++++-- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 9e5e6f413..c80ca5a1e 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -72,7 +72,8 @@ provided then that is extension parameters: - `serverMaxWindowBits` {Number} The value of windowBits. - `clientMaxWindowBits` {Number} The value of max windowBits to be requested to clients. -- `memLevel` {Number} The value of memLevel. +- `level` {Number} The value of zlib's `level` param (0-9, default 8). +- `memLevel` {Number} The value of zlib's `memLevel` param (1-9, default 8). - `threshold` {Number} Payloads smaller than this will not be compressed. Defaults to 1024 bytes. diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index c2c613759..7044b31f7 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -340,6 +340,7 @@ class PerMessageDeflate { this._deflate = zlib.createDeflateRaw({ memLevel: this._options.memLevel, + level: this._options.level, flush: zlib.Z_SYNC_FLUSH, windowBits }); diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index dbaf67e4c..b66ddd082 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -257,28 +257,77 @@ describe('PerMessageDeflate', function () { it('should compress/decompress data with parameters', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0, - memLevel: 5 + memLevel: 5, + level: 9 }); const extensions = Extensions.parse( 'permessage-deflate; server_no_context_takeover; ' + 'client_no_context_takeover; server_max_window_bits=10; ' + 'client_max_window_bits=11' ); + const srcData = 'Some compressible data, it\'s compressible.'; perMessageDeflate.accept(extensions['permessage-deflate']); - perMessageDeflate.compress(Buffer.from([1, 2, 3]), true, (err, compressed) => { + perMessageDeflate.compress(Buffer.from(srcData, 'utf8'), true, (err, compressed) => { if (err) return done(err); perMessageDeflate.decompress(compressed, true, (err, data) => { if (err) return done(err); - assert.ok(data.equals(Buffer.from([1, 2, 3]))); + assert.ok(data.equals(Buffer.from(srcData, 'utf8'))); done(); }); }); }); + it('should compress/decompress with level parameter', function (done) { + const perMessageDeflateLev9 = new PerMessageDeflate({ + threshold: 0, + level: 9 + }); + const perMessageDeflateLev0 = new PerMessageDeflate({ + threshold: 0, + level: 0 + }); + const extensionStr = ( + 'permessage-deflate; server_no_context_takeover; ' + + 'client_no_context_takeover; server_max_window_bits=10; ' + + 'client_max_window_bits=11' + ); + const srcData = 'Some compressible data, it\'s compressible.'; + const srcDataBuffer = Buffer.from(srcData, 'utf8'); + + perMessageDeflateLev0.accept(Extensions.parse(extensionStr)['permessage-deflate']); + perMessageDeflateLev9.accept(Extensions.parse(extensionStr)['permessage-deflate']); + + perMessageDeflateLev0.compress(srcDataBuffer, true, (err, compressed1) => { + if (err) return done(err); + + perMessageDeflateLev0.decompress(compressed1, true, (err, data1) => { + if (err) return done(err); + + perMessageDeflateLev9.compress(srcDataBuffer, true, (err, compressed2) => { + if (err) return done(err); + + perMessageDeflateLev9.decompress(compressed2, true, (err, data2) => { + if (err) return done(err); + + // Level 0 compression actually adds a few bytes due to headers + assert.ok(compressed1.length > srcDataBuffer.length); + // Level 9 should not, of course. + assert.ok(compressed2.length < compressed1.length); + assert.ok(compressed2.length < srcDataBuffer.length); + // Ensure they both decompress back properly. + assert.ok(data1.equals(srcDataBuffer)); + assert.ok(data2.equals(srcDataBuffer)); + done(); + }); + }); + }); + }); + }); + it('should compress/decompress data with no context takeover', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const extensions = Extensions.parse( From f2d18b6a6e488290dba216e964d787fbf27b1267 Mon Sep 17 00:00:00 2001 From: Samuel Reed Date: Wed, 13 Sep 2017 00:57:06 -0500 Subject: [PATCH 387/669] [feature] zlib deflate concurrency limit (#1204) --- doc/ws.md | 5 ++++ lib/PerMessageDeflate.js | 56 +++++++++++++++++++++++++++++++++++++--- package.json | 1 + 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index c80ca5a1e..9e5e636c5 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -76,6 +76,10 @@ provided then that is extension parameters: - `memLevel` {Number} The value of zlib's `memLevel` param (1-9, default 8). - `threshold` {Number} Payloads smaller than this will not be compressed. Defaults to 1024 bytes. +- `concurrencyLimit` {Number} The number of concurrent calls to zlib. + Calls above this limit will be queued. Default 10. You usually won't + need to touch this option. See [concurrency-limit][this issue] for more + details. If a property is empty then either an offered configuration or a default value is used. @@ -425,4 +429,5 @@ Forcibly close the connection. The URL of the WebSocket server. Server clients don't have this attribute. +[concurrency-limit]: https://github.com/websockets/ws/issues/1202 [permessage-deflate]: https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19 diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 7044b31f7..27a00ca37 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -2,6 +2,7 @@ const safeBuffer = require('safe-buffer'); const zlib = require('zlib'); +const Limiter = require('async-limiter'); const bufferUtil = require('./BufferUtil'); @@ -10,6 +11,14 @@ const Buffer = safeBuffer.Buffer; const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); const EMPTY_BLOCK = Buffer.from([0x00]); +// We limit zlib concurrency, which prevents severe memory fragmentation +// as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913 +// and https://github.com/websockets/ws/issues/1202 +// +// Intentionally global; it's the global thread pool that's +// an issue. +let zlibLimiter; + /** * Per-message Deflate implementation. */ @@ -25,6 +34,13 @@ class PerMessageDeflate { this._inflate = null; this.params = null; + + if (!zlibLimiter) { + const concurrency = this._options.concurrencyLimit !== undefined + ? this._options.concurrencyLimit + : 10; + zlibLimiter = new Limiter({ concurrency }); + } } static get extensionName () { @@ -249,7 +265,7 @@ class PerMessageDeflate { } /** - * Decompress data. + * Decompress data. Concurrency limited by async-limiter. * * @param {Buffer} data Compressed data * @param {Boolean} fin Specifies whether or not this is the last fragment @@ -257,6 +273,40 @@ class PerMessageDeflate { * @public */ decompress (data, fin, callback) { + zlibLimiter.push((done) => { + this._decompress(data, fin, (err, result) => { + done(); + callback(err, result); + }); + }); + } + + /** + * Compress data. Concurrency limited by async-limiter. + * + * @param {Buffer} data Data to compress + * @param {Boolean} fin Specifies whether or not this is the last fragment + * @param {Function} callback Callback + * @public + */ + compress (data, fin, callback) { + zlibLimiter.push((done) => { + this._compress(data, fin, (err, result) => { + done(); + callback(err, result); + }); + }); + } + + /** + * Decompress data. + * + * @param {Buffer} data Compressed data + * @param {Boolean} fin Specifies whether or not this is the last fragment + * @param {Function} callback Callback + * @private + */ + _decompress (data, fin, callback) { const endpoint = this._isServer ? 'client' : 'server'; if (!this._inflate) { @@ -322,9 +372,9 @@ class PerMessageDeflate { * @param {Buffer} data Data to compress * @param {Boolean} fin Specifies whether or not this is the last fragment * @param {Function} callback Callback - * @public + * @private */ - compress (data, fin, callback) { + _compress (data, fin, callback) { if (!data || data.length === 0) { process.nextTick(callback, null, EMPTY_BLOCK); return; diff --git a/package.json b/package.json index cedbec7e3..8c3116263 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "lint": "eslint ." }, "dependencies": { + "async-limiter": "~1.0.0", "safe-buffer": "~5.1.0", "ultron": "~1.1.0" }, From 4082e695cd56cbd7a2e4bd385b77a78acd2a5b53 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 15 Sep 2017 08:02:33 +0200 Subject: [PATCH 388/669] [dist] 3.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8c3116263..01d141608 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "3.1.0", + "version": "3.2.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 36e1c7aa3654e8f7d2e71db34d0d1779fb39d669 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 16 Sep 2017 07:33:24 +0200 Subject: [PATCH 389/669] chore(package): update eslint to version 4.7.0 (#1206) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 01d141608..9fafb0612 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.6.0", + "eslint": "~4.7.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.1.0", From 9fe606e5d957fd13951803f5ae5c247720bcab8d Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 28 Sep 2017 16:29:45 +0200 Subject: [PATCH 390/669] chore(package): update eslint-plugin-node to version 5.2.0 (#1209) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9fafb0612..82a909ea1 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "eslint": "~4.7.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", - "eslint-plugin-node": "~5.1.0", + "eslint-plugin-node": "~5.2.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~3.5.0", From 6f44ee982ef7bec03cfd77f9508f3903bd9738a8 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 30 Sep 2017 09:33:32 +0200 Subject: [PATCH 391/669] chore(package): update eslint to version 4.8.0 (#1210) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 82a909ea1..7308ad237 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.7.0", + "eslint": "~4.8.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.2.0", From 71bb8f4c6412bee227034a268c29e597156c9f95 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 3 Oct 2017 10:08:08 +0200 Subject: [PATCH 392/669] chore(package): update mocha to version 4.0.0 (#1211) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7308ad237..7472670e9 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "eslint-plugin-node": "~5.2.0", "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.0", - "mocha": "~3.5.0", + "mocha": "~4.0.0", "nyc": "~11.2.0", "utf-8-validate": "~3.0.0" } From 892a84ff272ed259ea49f00feff835ad3366ae64 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 3 Oct 2017 10:40:48 +0200 Subject: [PATCH 393/669] [ci] Remove no longer needed `dist` key from .travis.yml --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1670a8aff..c94d7feab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ language: node_js -dist: trusty sudo: false node_js: - "8" From f8cb85b6d998b06ad563a1e4ab72d066b52584c8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 3 Oct 2017 10:45:22 +0200 Subject: [PATCH 394/669] [test] Fix issue that prevented process from exiting --- test/WebSocket.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 1aa45d12e..3c6ec9eaf 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -72,8 +72,8 @@ describe('WebSocket', function () { ws.on('error', (err) => { // Skip this test on machines where 127.0.0.2 is disabled. - if (err.code === 'EADDRNOTAVAIL') return done(); - throw err; + if (err.code === 'EADDRNOTAVAIL') err = undefined; + wss.close(() => done(err)); }); }); From c751a45283976374762816e75e576cca12702871 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 13 Oct 2017 21:17:53 +0200 Subject: [PATCH 395/669] chore(package): update eslint-plugin-promise to version 3.6.0 (#1215) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7472670e9..d44844353 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.2.0", - "eslint-plugin-promise": "~3.5.0", + "eslint-plugin-promise": "~3.6.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~4.0.0", "nyc": "~11.2.0", From b6a928b5119117f756b7266b194e3bd30f40248a Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 15 Oct 2017 09:05:16 +0200 Subject: [PATCH 396/669] chore(package): update eslint to version 4.9.0 (#1217) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d44844353..1ce0684af 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.8.0", + "eslint": "~4.9.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.7.0", "eslint-plugin-node": "~5.2.0", From e01fa4a3805cf1a337c6df996abe0e20babf6218 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 19 Oct 2017 14:31:34 +0200 Subject: [PATCH 397/669] chore(package): update eslint-plugin-import to version 2.8.0 (#1219) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1ce0684af..33d53602f 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "bufferutil": "~3.0.0", "eslint": "~4.9.0", "eslint-config-standard": "~10.2.0", - "eslint-plugin-import": "~2.7.0", + "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~5.2.0", "eslint-plugin-promise": "~3.6.0", "eslint-plugin-standard": "~3.0.0", From 5bb78ddc72712a62c2e2f4a4ba82c2339fa6d33f Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 28 Oct 2017 07:54:40 +0200 Subject: [PATCH 398/669] chore(package): update eslint to version 4.10.0 (#1223) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 33d53602f..6f879454b 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.9.0", + "eslint": "~4.10.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~5.2.0", From 5e0424ce627a8879a4a4995b6bcdc542c3fdd21c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 28 Oct 2017 17:52:28 +0200 Subject: [PATCH 399/669] [doc] Fix broken link --- doc/ws.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index 9e5e636c5..ba577ec7f 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -78,7 +78,7 @@ provided then that is extension parameters: Defaults to 1024 bytes. - `concurrencyLimit` {Number} The number of concurrent calls to zlib. Calls above this limit will be queued. Default 10. You usually won't - need to touch this option. See [concurrency-limit][this issue] for more + need to touch this option. See [this issue][concurrency-limit] for more details. If a property is empty then either an offered configuration or a default value From 8ea3739d62fd7b2e467ec814e31c7f65d32da98b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 30 Oct 2017 08:55:33 +0100 Subject: [PATCH 400/669] [minor] Remove unreachable code (#1224) `zlib.DeflateRaw` emits an `'error'` event only when an attempt to use it is made after it has already been closed and this cannot happen in our case. --- lib/PerMessageDeflate.js | 57 ++++++++++++++++++++++------------------ lib/Sender.js | 10 +------ lib/WebSocket.js | 19 +++----------- 3 files changed, 37 insertions(+), 49 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 27a00ca37..cee793cfa 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -394,28 +394,27 @@ class PerMessageDeflate { flush: zlib.Z_SYNC_FLUSH, windowBits }); - } - this._deflate.writeInProgress = true; - var totalLength = 0; - const buffers = []; - - const onData = (data) => { - totalLength += data.length; - buffers.push(data); - }; + this._deflate.totalLength = 0; + this._deflate.buffers = []; - const onError = (err) => { - cleanup(); - callback(err); - }; + // + // `zlib.DeflateRaw` emits an `'error'` event only when an attempt to use + // it is made after it has already been closed. This cannot happen here, + // so we only add a listener for the `'data'` event. + // + this._deflate.on('data', deflateOnData); + } - const cleanup = () => { - if (!this._deflate) return; + this._deflate.writeInProgress = true; - this._deflate.removeListener('error', onError); - this._deflate.removeListener('data', onData); - this._deflate.writeInProgress = false; + this._deflate.write(data); + this._deflate.flush(zlib.Z_SYNC_FLUSH, () => { + var data = bufferUtil.concat( + this._deflate.buffers, + this._deflate.totalLength + ); + if (fin) data = data.slice(0, data.length - 4); if ( (fin && this.params[`${endpoint}_no_context_takeover`]) || @@ -423,18 +422,26 @@ class PerMessageDeflate { ) { this._deflate.close(); this._deflate = null; + } else { + this._deflate.writeInProgress = false; + this._deflate.totalLength = 0; + this._deflate.buffers = []; } - }; - this._deflate.on('error', onError).on('data', onData); - this._deflate.write(data); - this._deflate.flush(zlib.Z_SYNC_FLUSH, () => { - cleanup(); - var data = bufferUtil.concat(buffers, totalLength); - if (fin) data = data.slice(0, data.length - 4); callback(null, data); }); } } module.exports = PerMessageDeflate; + +/** + * The listener of the `zlib.DeflateRaw` stream `'data'` event. + * + * @param {Buffer} chunk A chunk of data + * @private + */ +function deflateOnData (chunk) { + this.buffers.push(chunk); + this.totalLength += chunk.length; +} diff --git a/lib/Sender.js b/lib/Sender.js index d3502fe16..53953d8d2 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -35,8 +35,6 @@ class Sender { this._bufferedBytes = 0; this._deflating = false; this._queue = []; - - this.onerror = null; } /** @@ -331,13 +329,7 @@ class Sender { const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; this._deflating = true; - perMessageDeflate.compress(data, options.fin, (err, buf) => { - if (err) { - if (cb) cb(err); - else this.onerror(err); - return; - } - + perMessageDeflate.compress(data, options.fin, (_, buf) => { options.readOnly = false; this.sendFrame(Sender.frame(buf, options), cb); this._deflating = false; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 760ae34fa..82d335458 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -157,12 +157,6 @@ class WebSocket extends EventEmitter { this.emit('error', error); }; - // sender event handlers - this._sender.onerror = (error) => { - this.close(1002, ''); - this.emit('error', error); - }; - this.readyState = WebSocket.OPEN; this.emit('open'); } @@ -198,17 +192,12 @@ class WebSocket extends EventEmitter { if (!error) this._socket.end(); else this._socket.destroy(); - this._socket = null; - this._ultron = null; - } - - if (this._sender) { - this._sender = this._sender.onerror = null; - } - - if (this._receiver) { this._receiver.cleanup(() => this.emitClose()); + this._receiver = null; + this._sender = null; + this._socket = null; + this._ultron = null; } else { this.emitClose(); } From 1d6ba8678bf138b7fd4307cc6c97d943501292ab Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 30 Oct 2017 09:11:55 +0100 Subject: [PATCH 401/669] [minor] Prevent possible property name collisions --- lib/PerMessageDeflate.js | 43 ++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index cee793cfa..380042d37 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -1,8 +1,8 @@ 'use strict'; const safeBuffer = require('safe-buffer'); -const zlib = require('zlib'); const Limiter = require('async-limiter'); +const zlib = require('zlib'); const bufferUtil = require('./BufferUtil'); @@ -11,6 +11,11 @@ const Buffer = safeBuffer.Buffer; const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); const EMPTY_BLOCK = Buffer.from([0x00]); +const kWriteInProgress = Symbol('write-in-progress'); +const kPendingClose = Symbol('pending-close'); +const kTotalLength = Symbol('total-length'); +const kBuffers = Symbol('buffers'); + // We limit zlib concurrency, which prevents severe memory fragmentation // as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913 // and https://github.com/websockets/ws/issues/1202 @@ -102,16 +107,16 @@ class PerMessageDeflate { */ cleanup () { if (this._inflate) { - if (this._inflate.writeInProgress) { - this._inflate.pendingClose = true; + if (this._inflate[kWriteInProgress]) { + this._inflate[kPendingClose] = true; } else { this._inflate.close(); this._inflate = null; } } if (this._deflate) { - if (this._deflate.writeInProgress) { - this._deflate.pendingClose = true; + if (this._deflate[kWriteInProgress]) { + this._deflate[kPendingClose] = true; } else { this._deflate.close(); this._deflate = null; @@ -317,7 +322,7 @@ class PerMessageDeflate { this._inflate = zlib.createInflateRaw({ windowBits }); } - this._inflate.writeInProgress = true; + this._inflate[kWriteInProgress] = true; var totalLength = 0; const buffers = []; @@ -344,11 +349,11 @@ class PerMessageDeflate { this._inflate.removeListener('error', onError); this._inflate.removeListener('data', onData); - this._inflate.writeInProgress = false; + this._inflate[kWriteInProgress] = false; if ( (fin && this.params[`${endpoint}_no_context_takeover`]) || - this._inflate.pendingClose + this._inflate[kPendingClose] ) { this._inflate.close(); this._inflate = null; @@ -395,8 +400,8 @@ class PerMessageDeflate { windowBits }); - this._deflate.totalLength = 0; - this._deflate.buffers = []; + this._deflate[kTotalLength] = 0; + this._deflate[kBuffers] = []; // // `zlib.DeflateRaw` emits an `'error'` event only when an attempt to use @@ -406,26 +411,26 @@ class PerMessageDeflate { this._deflate.on('data', deflateOnData); } - this._deflate.writeInProgress = true; + this._deflate[kWriteInProgress] = true; this._deflate.write(data); this._deflate.flush(zlib.Z_SYNC_FLUSH, () => { var data = bufferUtil.concat( - this._deflate.buffers, - this._deflate.totalLength + this._deflate[kBuffers], + this._deflate[kTotalLength] ); if (fin) data = data.slice(0, data.length - 4); if ( (fin && this.params[`${endpoint}_no_context_takeover`]) || - this._deflate.pendingClose + this._deflate[kPendingClose] ) { this._deflate.close(); this._deflate = null; } else { - this._deflate.writeInProgress = false; - this._deflate.totalLength = 0; - this._deflate.buffers = []; + this._deflate[kWriteInProgress] = false; + this._deflate[kTotalLength] = 0; + this._deflate[kBuffers] = []; } callback(null, data); @@ -442,6 +447,6 @@ module.exports = PerMessageDeflate; * @private */ function deflateOnData (chunk) { - this.buffers.push(chunk); - this.totalLength += chunk.length; + this[kBuffers].push(chunk); + this[kTotalLength] += chunk.length; } From 31c81a96914c3367b52c667b211dbaf2cf773b11 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 30 Oct 2017 14:30:17 +0100 Subject: [PATCH 402/669] [minor] Refactor `PerMessageDeflate#_decompress()` If context takeover is enabled there is no reason to remove and add the listeners of the `'data'` and `'error'` events every time `PerMessageDeflate#_decompress()` is used. --- lib/PerMessageDeflate.js | 100 ++++++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 32 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 380042d37..35c687d30 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -14,7 +14,10 @@ const EMPTY_BLOCK = Buffer.from([0x00]); const kWriteInProgress = Symbol('write-in-progress'); const kPendingClose = Symbol('pending-close'); const kTotalLength = Symbol('total-length'); +const kCallback = Symbol('callback'); const kBuffers = Symbol('buffers'); +const kError = Symbol('error'); +const kOwner = Symbol('owner'); // We limit zlib concurrency, which prevents severe memory fragmentation // as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913 @@ -321,35 +324,33 @@ class PerMessageDeflate { : this.params[key]; this._inflate = zlib.createInflateRaw({ windowBits }); + this._inflate[kTotalLength] = 0; + this._inflate[kBuffers] = []; + this._inflate[kOwner] = this; + this._inflate.on('error', inflateOnError); + this._inflate.on('data', inflateOnData); } - this._inflate[kWriteInProgress] = true; - - var totalLength = 0; - const buffers = []; - var err; - const onData = (data) => { - totalLength += data.length; - if (this._maxPayload < 1 || totalLength <= this._maxPayload) { - return buffers.push(data); - } + this._inflate[kCallback] = callback; + this._inflate[kWriteInProgress] = true; - err = new Error('max payload size exceeded'); - err.closeCode = 1009; - this._inflate.reset(); - }; + this._inflate.write(data); + if (fin) this._inflate.write(TRAILER); - const onError = (err) => { - cleanup(); - callback(err); - }; + this._inflate.flush(() => { + const err = this._inflate[kError]; - const cleanup = () => { - if (!this._inflate) return; + if (err) { + this._inflate.close(); + this._inflate = null; + callback(err); + return; + } - this._inflate.removeListener('error', onError); - this._inflate.removeListener('data', onData); - this._inflate[kWriteInProgress] = false; + const data = bufferUtil.concat( + this._inflate[kBuffers], + this._inflate[kTotalLength] + ); if ( (fin && this.params[`${endpoint}_no_context_takeover`]) || @@ -357,17 +358,13 @@ class PerMessageDeflate { ) { this._inflate.close(); this._inflate = null; + } else { + this._inflate[kWriteInProgress] = false; + this._inflate[kTotalLength] = 0; + this._inflate[kBuffers] = []; } - }; - this._inflate.on('error', onError).on('data', onData); - this._inflate.write(data); - if (fin) this._inflate.write(TRAILER); - - this._inflate.flush(() => { - cleanup(); - if (err) callback(err); - else callback(null, bufferUtil.concat(buffers, totalLength)); + callback(null, data); }); } @@ -419,6 +416,7 @@ class PerMessageDeflate { this._deflate[kBuffers], this._deflate[kTotalLength] ); + if (fin) data = data.slice(0, data.length - 4); if ( @@ -450,3 +448,41 @@ function deflateOnData (chunk) { this[kBuffers].push(chunk); this[kTotalLength] += chunk.length; } + +/** + * The listener of the `zlib.InflateRaw` stream `'data'` event. + * + * @param {Buffer} chunk A chunk of data + * @private + */ +function inflateOnData (chunk) { + this[kTotalLength] += chunk.length; + + if ( + this[kOwner]._maxPayload < 1 || + this[kTotalLength] <= this[kOwner]._maxPayload + ) { + this[kBuffers].push(chunk); + return; + } + + this[kError] = new Error('max payload size exceeded'); + this[kError].closeCode = 1009; + this.removeListener('data', inflateOnData); + this.reset(); +} + +/** + * The listener of the `zlib.InflateRaw` stream `'error'` event. + * + * @param {Error} err The emitted error + * @private + */ +function inflateOnError (err) { + // + // There is no need to call `Zlib#close()` as the handle is automatically + // closed when an error is emitted. + // + this[kOwner]._inflate = null; + this[kCallback](err); +} From d6934afcf22afed25b1b9fd06bd4b1df66659aae Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 31 Oct 2017 09:00:10 +0100 Subject: [PATCH 403/669] [test] Fix error validation on node 9 --- test/WebSocket.test.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 3c6ec9eaf..db5304d88 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -84,10 +84,14 @@ describe('WebSocket', function () { }); it('accepts the localAddress option whether it was wrong interface', function () { - assert.throws( - () => new WebSocket(`ws://localhost:${port}`, { localAddress: '123.456.789.428' }), - /must be a valid IP: 123.456.789.428/ - ); + const localAddress = '123.456.789.428'; + + assert.throws(() => { + const ws = new WebSocket(`ws://localhost:${port}`, { localAddress }); + }, (err) => { + return err instanceof TypeError && (err.code === 'ERR_INVALID_IP_ADDRESS' || + err.message.includes(`must be a valid IP: ${localAddress}`)); + }); }); it('accepts the family option', function (done) { From 48b0496879899f35602856d80460926a4a6c299d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 31 Oct 2017 11:11:45 +0100 Subject: [PATCH 404/669] [ci] Do not test on node 4.1.0, use 4.2.0 instead All versions in the range >=4.0.0 <4.2.0 are bugged. --- .travis.yml | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c94d7feab..b3cd4dd25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,6 @@ node_js: - "8" - "6" - "4" - - "4.1.0" + - "4.2.0" after_success: - "npm install coveralls@2 && nyc report --reporter=text-lcov | coveralls" diff --git a/appveyor.yml b/appveyor.yml index db5faaaff..dc7e5adbc 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,7 +3,7 @@ environment: - nodejs_version: "8" - nodejs_version: "6" - nodejs_version: "4" - - nodejs_version: "4.1.0" + - nodejs_version: "4.2.0" platform: - x86 - x64 From 9303db3cfafcc1f97e27501d5d3ddc4079f15f5c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 1 Nov 2017 10:33:43 +0100 Subject: [PATCH 405/669] [ci] Test on node 9 --- .travis.yml | 3 ++- appveyor.yml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b3cd4dd25..1d63d7d33 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,10 @@ language: node_js sudo: false node_js: + - "9" - "8" - "6" - "4" - "4.2.0" after_success: - - "npm install coveralls@2 && nyc report --reporter=text-lcov | coveralls" + - "npm install coveralls@3 && nyc report --reporter=text-lcov | coveralls" diff --git a/appveyor.yml b/appveyor.yml index dc7e5adbc..2502b8c8f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,6 @@ environment: matrix: + - nodejs_version: "9" - nodejs_version: "8" - nodejs_version: "6" - nodejs_version: "4" From d0741faeec6fc6bc6db163545b3534ed822f6cf3 Mon Sep 17 00:00:00 2001 From: Hativ3 Date: Wed, 1 Nov 2017 22:21:05 +0100 Subject: [PATCH 406/669] [feature] Add ecdhCurve option (#1228) Support ecdhCurve option as discussed in #1227. --- lib/WebSocket.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 82d335458..ce1831287 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -472,6 +472,7 @@ function initAsServerClient (socket, head, options) { * @param {Boolean} options.rejectUnauthorized Verify or not the server certificate * @param {String} options.passphrase The passphrase for the private key or pfx * @param {String} options.ciphers The ciphers to use or exclude + * @param {String} options.ecdhCurve The curves for ECDH key agreement to use or exclude * @param {(String|String[]|Buffer|Buffer[])} options.cert The certificate key * @param {(String|String[]|Buffer|Buffer[])} options.key The private key * @param {(String|Buffer)} options.pfx The private key, certificate, and CA certs @@ -498,6 +499,7 @@ function initAsClient (address, protocols, options) { rejectUnauthorized: null, passphrase: null, ciphers: null, + ecdhCurve: null, cert: null, key: null, pfx: null, @@ -598,6 +600,7 @@ function initAsClient (address, protocols, options) { options.checkServerIdentity || options.passphrase || options.ciphers || + options.ecdhCurve || options.cert || options.key || options.pfx || @@ -605,6 +608,7 @@ function initAsClient (address, protocols, options) { ) { if (options.passphrase) requestOptions.passphrase = options.passphrase; if (options.ciphers) requestOptions.ciphers = options.ciphers; + if (options.ecdhCurve) requestOptions.ecdhCurve = options.ecdhCurve; if (options.cert) requestOptions.cert = options.cert; if (options.key) requestOptions.key = options.key; if (options.pfx) requestOptions.pfx = options.pfx; From db729efe920d8ebca53254c5cbf0a57f7f43744a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 2 Nov 2017 07:51:33 +0100 Subject: [PATCH 407/669] [doc] Add documentation for the `ecdhCurve` option --- doc/ws.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index ba577ec7f..14f981eec 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -183,12 +183,14 @@ This class represents a WebSocket. It extends the `EventEmitter`. request. - `origin` {String} Value of the `Origin` or `Sec-WebSocket-Origin` header depending on the `protocolVersion`. - - `agent` {http.Agent|https.Agent} Use the specified Agent, + - `agent` {http.Agent|https.Agent} Use the specified Agent. - `host` {String} Value of the `Host` header. - `family` {Number} IP address family to use during hostname lookup (4 or 6). - `checkServerIdentity` {Function} A function to validate the server hostname. - `rejectUnauthorized` {Boolean} Verify or not the server certificate. - `passphrase` {String} The passphrase for the private key or pfx. + - `ecdhCurve` {String} A named curve or a colon separated list of curve NIDs + or names to use for ECDH key agreement. - `ciphers` {String} The ciphers to use or exclude - `cert` {String|Array|Buffer} The certificate key. - `key` {String|Array|Buffer} The private key. From e5772a35f151f397f6b555ee3a947b4654c95676 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 2 Nov 2017 08:50:31 +0100 Subject: [PATCH 408/669] chore(package): update nyc to version 11.3.0 (#1230) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6f879454b..8ab261128 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "eslint-plugin-promise": "~3.6.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~4.0.0", - "nyc": "~11.2.0", + "nyc": "~11.3.0", "utf-8-validate": "~3.0.0" } } From 72751d3d72007f64f97f14f1d4472665b6354e63 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 2 Nov 2017 10:40:27 +0100 Subject: [PATCH 409/669] [test] Skip `family` option test if IPv6 is not supported --- test/WebSocket.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index db5304d88..99cd7551c 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -99,6 +99,12 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 }); }); + wss.on('error', (err) => { + // Skip this test on machines where IPv6 is not supported. + if (err.code === 'EADDRNOTAVAIL') err = undefined; + wss.close(() => done(err)); + }); + wss.on('connection', (ws, req) => { assert.strictEqual(req.connection.remoteAddress, '::1'); wss.close(done); From 56f80625399de02abfe6c0d718ea5a8939969318 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 4 Nov 2017 10:20:38 +0100 Subject: [PATCH 410/669] [dist] 3.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8ab261128..9b7c16783 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "3.2.0", + "version": "3.3.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From c4fe46608acd61fbf7397eadc47378903f95b78a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 8 Nov 2017 12:13:20 +0100 Subject: [PATCH 411/669] [security] Fix DoS vulnerability Ignore extension and parameter names that are property names of `Object.prototype` when parsing the `Sec-WebSocket-Extensions` header. --- lib/Extensions.js | 17 ++++++++++++++--- test/Extensions.test.js | 21 ++++++++++++++------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/lib/Extensions.js b/lib/Extensions.js index a91910eb2..9cb555494 100644 --- a/lib/Extensions.js +++ b/lib/Extensions.js @@ -15,7 +15,13 @@ const parse = (value) => { value.split(',').forEach((v) => { const params = v.split(';'); const token = params.shift().trim(); - const paramsList = extensions[token] = extensions[token] || []; + + if (extensions[token] === undefined) { + extensions[token] = []; + } else if (!extensions.hasOwnProperty(token)) { + return; + } + const parsedParams = {}; params.forEach((param) => { @@ -34,10 +40,15 @@ const parse = (value) => { value = value.slice(0, value.length - 1); } } - (parsedParams[key] = parsedParams[key] || []).push(value); + + if (parsedParams[key] === undefined) { + parsedParams[key] = [value]; + } else if (parsedParams.hasOwnProperty(key)) { + parsedParams[key].push(value); + } }); - paramsList.push(parsedParams); + extensions[token].push(parsedParams); }); return extensions; diff --git a/test/Extensions.test.js b/test/Extensions.test.js index 8b8fe2fe9..169f68ec5 100644 --- a/test/Extensions.test.js +++ b/test/Extensions.test.js @@ -6,13 +6,13 @@ const Extensions = require('../lib/Extensions'); describe('Extensions', function () { describe('parse', function () { - it('should parse', function () { + it('parses a single extension', function () { const extensions = Extensions.parse('foo'); assert.deepStrictEqual(extensions, { foo: [{}] }); }); - it('should parse params', function () { + it('parses params', function () { const extensions = Extensions.parse('foo; bar; baz=1; bar=2'); assert.deepStrictEqual(extensions, { @@ -20,7 +20,7 @@ describe('Extensions', function () { }); }); - it('should parse multiple extensions', function () { + it('parse multiple extensions', function () { const extensions = Extensions.parse('foo, bar; baz, foo; baz'); assert.deepStrictEqual(extensions, { @@ -29,29 +29,36 @@ describe('Extensions', function () { }); }); - it('should parse quoted params', function () { + it('parses quoted params', function () { const extensions = Extensions.parse('foo; bar="hi"'); assert.deepStrictEqual(extensions, { foo: [{ bar: ['hi'] }] }); }); + + it('ignores names that match Object.prototype properties', function () { + const parse = Extensions.parse; + + assert.deepStrictEqual(parse('hasOwnProperty, toString'), {}); + assert.deepStrictEqual(parse('foo; constructor'), { foo: [{}] }); + }); }); describe('format', function () { - it('should format', function () { + it('formats a single extension', function () { const extensions = Extensions.format({ foo: {} }); assert.strictEqual(extensions, 'foo'); }); - it('should format params', function () { + it('formats params', function () { const extensions = Extensions.format({ foo: { bar: [true, 2], baz: 1 } }); assert.strictEqual(extensions, 'foo; bar; bar=2; baz=1'); }); - it('should format multiple extensions', function () { + it('formats multiple extensions', function () { const extensions = Extensions.format({ foo: [{}, { baz: true }], bar: { baz: true } From 70eb3b2f6284a361768ea518acb072d13986dade Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 8 Nov 2017 17:02:01 +0100 Subject: [PATCH 412/669] [dist] 3.3.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9b7c16783..5deece34b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "3.3.0", + "version": "3.3.1", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From cfdecaefe68e48c97e7702fbeb393694bc40ec85 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 8 Nov 2017 18:53:11 +0100 Subject: [PATCH 413/669] [security] Add DoS vulnerablity to SECURITY.md --- SECURITY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/SECURITY.md b/SECURITY.md index fd8e07bc5..e8a5b70fb 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -31,3 +31,4 @@ all vulnerabilities to the [Node Security Project](https://nodesecurity.io/). ## History 04 Jan 2016: [Buffer vulnerablity](https://github.com/websockets/ws/releases/tag/1.0.1) +08 Nov 2017: [DoS vulnerablity](https://github.com/websockets/ws/releases/tag/3.3.1) From d96c58cda8fa2e67784cb4e37d375c14b2634fb1 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 11 Nov 2017 07:25:32 +0100 Subject: [PATCH 414/669] chore(package): update eslint to version 4.11.0 (#1234) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5deece34b..c20eb812e 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.10.0", + "eslint": "~4.11.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~5.2.0", From b7089ffc4e10b3dea9703fa1953c99db2241c22d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 16 Nov 2017 14:37:21 +0100 Subject: [PATCH 415/669] [fix] Rewrite the parser of the Sec-WebSocket-Extensions header Make the parser correctly handle quoted values and close the connection if the `Sec-WebSocket-Extensions` header is invalid. Fixes #1235 --- lib/Extensions.js | 212 +++++++++++++++++++++++++++------ lib/WebSocket.js | 17 ++- lib/WebSocketServer.js | 4 +- test/Extensions.test.js | 126 ++++++++++++++++++-- test/PerMessageDeflate.test.js | 7 -- 5 files changed, 301 insertions(+), 65 deletions(-) diff --git a/lib/Extensions.js b/lib/Extensions.js index 9cb555494..14c38ac2b 100644 --- a/lib/Extensions.js +++ b/lib/Extensions.js @@ -1,67 +1,203 @@ 'use strict'; +// +// Allowed token characters: +// +// '!', '#', '$', '%', '&', ''', '*', '+', '-', +// '.', 0-9, A-Z, '^', '_', '`', a-z, '|', '~' +// +// tokenChars[32] === 0 // ' ' +// tokenChars[33] === 1 // '!' +// tokenChars[34] === 0 // '"' +// ... +// +const tokenChars = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31 + 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 - 47 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 - 95 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0 // 112 - 127 +]; + /** - * Parse the `Sec-WebSocket-Extensions` header into an object. + * Adds an offer to the map of extension offers. * - * @param {String} value field value of the header + * @param {Object} offers The map of extension offers + * @param {String} name The extension name + * @param {Object} params The extension parameters + * @private + */ +function pushOffer (offers, name, params) { + if (Object.hasOwnProperty.call(offers, name)) offers[name].push(params); + else offers[name] = [params]; +} + +/** + * Adds a parameter to the map of parameters. + * + * @param {Object} params The map of parameters + * @param {String} name The parameter name + * @param {Object} value The parameter value + * @private + */ +function pushParam (params, name, value) { + if (Object.hasOwnProperty.call(params, name)) params[name].push(value); + else params[name] = [value]; +} + +/** + * Parses the `Sec-WebSocket-Extensions` header into an object. + * + * @param {String} header The field value of the header * @return {Object} The parsed object * @public */ -const parse = (value) => { - value = value || ''; +function parse (header) { + const offers = {}; - const extensions = {}; + if (header === undefined || header === '') return offers; - value.split(',').forEach((v) => { - const params = v.split(';'); - const token = params.shift().trim(); + var params = {}; + var mustUnescape = false; + var isEscaping = false; + var inQuotes = false; + var extensionName; + var paramName; + var start = -1; + var end = -1; - if (extensions[token] === undefined) { - extensions[token] = []; - } else if (!extensions.hasOwnProperty(token)) { - return; - } + for (var i = 0; i < header.length; i++) { + const code = header.charCodeAt(i); - const parsedParams = {}; + if (extensionName === undefined) { + if (end === -1 && tokenChars[code] === 1) { + if (start === -1) start = i; + } else if (code === 0x20/* ' ' */|| code === 0x09/* '\t' */) { + if (end === -1 && start !== -1) end = i; + } else if (code === 0x3b/* ';' */ || code === 0x2c/* ',' */) { + if (start === -1) throw new Error(`unexpected character at index ${i}`); - params.forEach((param) => { - const parts = param.trim().split('='); - const key = parts[0]; - var value = parts[1]; + if (end === -1) end = i; + const name = header.slice(start, end); + if (code === 0x2c) { + pushOffer(offers, name, params); + params = {}; + } else { + extensionName = name; + } - if (value === undefined) { - value = true; + start = end = -1; } else { - // unquote value - if (value[0] === '"') { - value = value.slice(1); - } - if (value[value.length - 1] === '"') { - value = value.slice(0, value.length - 1); + throw new Error(`unexpected character at index ${i}`); + } + } else if (paramName === undefined) { + if (end === -1 && tokenChars[code] === 1) { + if (start === -1) start = i; + } else if (code === 0x20 || code === 0x09) { + if (end === -1 && start !== -1) end = i; + } else if (code === 0x3b || code === 0x2c) { + if (start === -1) throw new Error(`unexpected character at index ${i}`); + + if (end === -1) end = i; + pushParam(params, header.slice(start, end), true); + if (code === 0x2c) { + pushOffer(offers, extensionName, params); + params = {}; + extensionName = undefined; } + + start = end = -1; + } else if (code === 0x3d/* '=' */&& start !== -1 && end === -1) { + paramName = header.slice(start, i); + start = end = -1; + } else { + throw new Error(`unexpected character at index ${i}`); } + } else { + // + // The value of a quoted-string after unescaping must conform to the + // token ABNF, so only token characters are valid. + // Ref: https://tools.ietf.org/html/rfc6455#section-9.1 + // + if (isEscaping) { + if (tokenChars[code] !== 1) { + throw new Error(`unexpected character at index ${i}`); + } + if (start === -1) start = i; + else if (!mustUnescape) mustUnescape = true; + isEscaping = false; + } else if (inQuotes) { + if (tokenChars[code] === 1) { + if (start === -1) start = i; + } else if (code === 0x22/* '"' */ && start !== -1) { + inQuotes = false; + end = i; + } else if (code === 0x5c/* '\' */) { + isEscaping = true; + } else { + throw new Error(`unexpected character at index ${i}`); + } + } else if (code === 0x22 && header.charCodeAt(i - 1) === 0x3d) { + inQuotes = true; + } else if (end === -1 && tokenChars[code] === 1) { + if (start === -1) start = i; + } else if (start !== -1 && (code === 0x20 || code === 0x09)) { + if (end === -1) end = i; + } else if (code === 0x3b || code === 0x2c) { + if (start === -1) throw new Error(`unexpected character at index ${i}`); + + if (end === -1) end = i; + var value = header.slice(start, end); + if (mustUnescape) { + value = value.replace(/\\/g, ''); + mustUnescape = false; + } + pushParam(params, paramName, value); + if (code === 0x2c) { + pushOffer(offers, extensionName, params); + params = {}; + extensionName = undefined; + } - if (parsedParams[key] === undefined) { - parsedParams[key] = [value]; - } else if (parsedParams.hasOwnProperty(key)) { - parsedParams[key].push(value); + paramName = undefined; + start = end = -1; + } else { + throw new Error(`unexpected character at index ${i}`); } - }); + } + } + + if (start === -1 || inQuotes) throw new Error('unexpected end of input'); - extensions[token].push(parsedParams); - }); + if (end === -1) end = i; + const token = header.slice(start, end); + if (extensionName === undefined) { + pushOffer(offers, token, {}); + } else { + if (paramName === undefined) { + pushParam(params, token, true); + } else if (mustUnescape) { + pushParam(params, paramName, token.replace(/\\/g, '')); + } else { + pushParam(params, paramName, token); + } + pushOffer(offers, extensionName, params); + } - return extensions; -}; + return offers; +} /** - * Serialize a parsed `Sec-WebSocket-Extensions` header to a string. + * Serializes a parsed `Sec-WebSocket-Extensions` header to a string. * * @param {Object} value The object to format * @return {String} A string representing the given value * @public */ -const format = (value) => { +function format (value) { return Object.keys(value).map((token) => { var paramsList = value[token]; if (!Array.isArray(paramsList)) paramsList = [paramsList]; @@ -73,6 +209,6 @@ const format = (value) => { })).join('; '); }).join(', '); }).join(', '); -}; +} module.exports = { format, parse }; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index ce1831287..ccfc169ab 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -692,18 +692,17 @@ function initAsClient (address, protocols, options) { if (serverProt) this.protocol = serverProt; - const serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']); + try { + const serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']); - if (perMessageDeflate && serverExtensions[PerMessageDeflate.extensionName]) { - try { + if (perMessageDeflate && serverExtensions[PerMessageDeflate.extensionName]) { perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]); - } catch (err) { - socket.destroy(); - this.emit('error', new Error('invalid extension parameter')); - return this.finalize(true); + this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } - - this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + } catch (err) { + socket.destroy(); + this.emit('error', new Error('invalid Sec-WebSocket-Extensions header')); + return this.finalize(true); } this.setSocket(socket, head); diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 790add824..fd1fb8347 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -227,11 +227,11 @@ class WebSocketServer extends EventEmitter { if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); - const offer = Extensions.parse(req.headers['sec-websocket-extensions']); var extensions; try { - extensions = acceptExtensions(this.options, offer); + const offers = Extensions.parse(req.headers['sec-websocket-extensions']); + extensions = acceptExtensions(this.options, offers); } catch (err) { return abortConnection(socket, 400); } diff --git a/test/Extensions.test.js b/test/Extensions.test.js index 169f68ec5..f08ab0852 100644 --- a/test/Extensions.test.js +++ b/test/Extensions.test.js @@ -6,6 +6,11 @@ const Extensions = require('../lib/Extensions'); describe('Extensions', function () { describe('parse', function () { + it('returns an empty object if the argument is undefined', function () { + assert.deepStrictEqual(Extensions.parse(), {}); + assert.deepStrictEqual(Extensions.parse(''), {}); + }); + it('parses a single extension', function () { const extensions = Extensions.parse('foo'); @@ -13,15 +18,15 @@ describe('Extensions', function () { }); it('parses params', function () { - const extensions = Extensions.parse('foo; bar; baz=1; bar=2'); + const extensions = Extensions.parse('foo;bar;baz=1;bar=2'); assert.deepStrictEqual(extensions, { foo: [{ bar: [true, '2'], baz: ['1'] }] }); }); - it('parse multiple extensions', function () { - const extensions = Extensions.parse('foo, bar; baz, foo; baz'); + it('parses multiple extensions', function () { + const extensions = Extensions.parse('foo,bar;baz,foo;baz'); assert.deepStrictEqual(extensions, { foo: [{}, { baz: [true] }], @@ -30,18 +35,121 @@ describe('Extensions', function () { }); it('parses quoted params', function () { - const extensions = Extensions.parse('foo; bar="hi"'); - - assert.deepStrictEqual(extensions, { + assert.deepStrictEqual(Extensions.parse('foo;bar="hi"'), { foo: [{ bar: ['hi'] }] }); + assert.deepStrictEqual(Extensions.parse('foo;bar="\\0"'), { + foo: [{ bar: ['0'] }] + }); + assert.deepStrictEqual(Extensions.parse('foo;bar="b\\a\\z"'), { + foo: [{ bar: ['baz'] }] + }); + assert.deepStrictEqual(Extensions.parse('foo;bar="b\\az";bar'), { + foo: [{ bar: ['baz', true] }] + }); + assert.throws( + () => Extensions.parse('foo;bar="baz"qux'), + /^Error: unexpected character at index 13$/ + ); + assert.throws( + () => Extensions.parse('foo;bar="baz" qux'), + /^Error: unexpected character at index 14$/ + ); }); - it('ignores names that match Object.prototype properties', function () { + it('works with names that match Object.prototype property names', function () { const parse = Extensions.parse; - assert.deepStrictEqual(parse('hasOwnProperty, toString'), {}); - assert.deepStrictEqual(parse('foo; constructor'), { foo: [{}] }); + assert.deepStrictEqual(parse('hasOwnProperty, toString'), { + hasOwnProperty: [{}], + toString: [{}] + }); + assert.deepStrictEqual(parse('foo;constructor'), { + foo: [{ constructor: [true] }] + }); + }); + + it('ignores the optional white spaces', function () { + const header = 'foo; bar\t; \tbaz=1\t ; bar="1"\t\t, \tqux\t ;norf '; + + assert.deepStrictEqual(Extensions.parse(header), { + foo: [{ bar: [true, '1'], baz: ['1'] }], + qux: [{ norf: [true] }] + }); + }); + + it('throws an error if a name is empty', function () { + [ + [',', 0], + ['foo,,', 4], + ['foo, ,', 6], + ['foo;=', 4], + ['foo; =', 5], + ['foo;;', 4], + ['foo; ;', 5], + ['foo;bar=,', 8], + ['foo;bar=""', 9] + ].forEach((element) => { + assert.throws( + () => Extensions.parse(element[0]), + new RegExp(`^Error: unexpected character at index ${element[1]}$`) + ); + }); + }); + + it('throws an error if a white space is misplaced', function () { + [ + ['f oo', 2], + ['foo;ba r', 7], + ['foo;bar =', 8], + ['foo;bar= ', 8] + ].forEach((element) => { + assert.throws( + () => Extensions.parse(element[0]), + new RegExp(`^Error: unexpected character at index ${element[1]}$`) + ); + }); + }); + + it('throws an error if a token contains invalid characters', function () { + [ + ['f@o', 1], + ['f\\oo', 1], + ['"foo"', 0], + ['f"oo"', 1], + ['foo;b@r', 5], + ['foo;b\\ar', 5], + ['foo;"bar"', 4], + ['foo;b"ar"', 5], + ['foo;bar=b@z', 9], + ['foo;bar=b\\az ', 9], + ['foo;bar="b@z"', 10], + ['foo;bar="baz;"', 12], + ['foo;bar=b"az"', 9], + ['foo;bar="\\\\"', 10] + ].forEach((element) => { + assert.throws( + () => Extensions.parse(element[0]), + new RegExp(`^Error: unexpected character at index ${element[1]}$`) + ); + }); + }); + + it('throws an error if the header value ends prematurely', function () { + [ + 'foo, ', + 'foo;', + 'foo;bar,', + 'foo;bar; ', + 'foo;bar=', + 'foo;bar="baz', + 'foo;bar="1\\' + ].forEach((header) => { + assert.throws( + () => Extensions.parse(header), + /^Error: unexpected end of input$/ + ); + }); }); }); diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index b66ddd082..bfe25acb7 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -174,13 +174,6 @@ describe('PerMessageDeflate', function () { assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); - it('should throw an error if a parameter is undefined', function () { - const perMessageDeflate = new PerMessageDeflate(); - const extensions = Extensions.parse('permessage-deflate; foo;'); - - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); - }); - it('should throw an error if server_no_context_takeover has a value', function () { const perMessageDeflate = new PerMessageDeflate(); const extensions = Extensions.parse('permessage-deflate; server_no_context_takeover=10'); From 5d973fb42d4cd96ff742946a0224d687f03b0209 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 16 Nov 2017 17:52:36 +0100 Subject: [PATCH 416/669] [minor] Parse the Sec-WebSocket-Extensions header only when necessary Avoid parsing the `Sec-WebSocket-Extensions` header if the only supported extension, `permessage-deflate`, is disabled. --- lib/WebSocket.js | 49 +++++++++++----------- lib/WebSocketServer.js | 95 +++++++++++++++++++----------------------- 2 files changed, 68 insertions(+), 76 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index ccfc169ab..76e42c078 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -527,21 +527,8 @@ function initAsClient (address, protocols, options) { const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:'; const key = crypto.randomBytes(16).toString('base64'); const httpObj = isSecure ? https : http; - - // - // Prepare extensions. - // - const extensionsOffer = {}; var perMessageDeflate; - if (options.perMessageDeflate) { - perMessageDeflate = new PerMessageDeflate( - options.perMessageDeflate !== true ? options.perMessageDeflate : {}, - false - ); - extensionsOffer[PerMessageDeflate.extensionName] = perMessageDeflate.offer(); - } - const requestOptions = { port: serverUrl.port || (isSecure ? 443 : 80), host: serverUrl.hostname, @@ -555,8 +542,14 @@ function initAsClient (address, protocols, options) { }; if (options.headers) Object.assign(requestOptions.headers, options.headers); - if (Object.keys(extensionsOffer).length) { - requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format(extensionsOffer); + if (options.perMessageDeflate) { + perMessageDeflate = new PerMessageDeflate( + options.perMessageDeflate !== true ? options.perMessageDeflate : {}, + false + ); + requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format({ + [PerMessageDeflate.extensionName]: perMessageDeflate.offer() + }); } if (options.protocol) { requestOptions.headers['Sec-WebSocket-Protocol'] = options.protocol; @@ -692,17 +685,23 @@ function initAsClient (address, protocols, options) { if (serverProt) this.protocol = serverProt; - try { - const serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']); - - if (perMessageDeflate && serverExtensions[PerMessageDeflate.extensionName]) { - perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]); - this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + if (perMessageDeflate) { + try { + const serverExtensions = Extensions.parse( + res.headers['sec-websocket-extensions'] + ); + + if (serverExtensions[PerMessageDeflate.extensionName]) { + perMessageDeflate.accept( + serverExtensions[PerMessageDeflate.extensionName] + ); + this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + } + } catch (err) { + socket.destroy(); + this.emit('error', new Error('invalid Sec-WebSocket-Extensions header')); + return this.finalize(true); } - } catch (err) { - socket.destroy(); - this.emit('error', new Error('invalid Sec-WebSocket-Extensions header')); - return this.finalize(true); } this.setSocket(socket, head); diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index fd1fb8347..571078f80 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -90,6 +90,7 @@ class WebSocketServer extends EventEmitter { }); } + if (options.perMessageDeflate === true) options.perMessageDeflate = {}; if (options.clientTracking) this.clients = new Set(); this.options = options; } @@ -151,6 +152,7 @@ class WebSocketServer extends EventEmitter { socket.on('error', socketError); const version = +req.headers['sec-websocket-version']; + const extensions = {}; if ( req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket' || @@ -160,6 +162,27 @@ class WebSocketServer extends EventEmitter { return abortConnection(socket, 400); } + if (this.options.perMessageDeflate) { + const perMessageDeflate = new PerMessageDeflate( + this.options.perMessageDeflate, + true, + this.options.maxPayload + ); + + try { + const offers = Extensions.parse( + req.headers['sec-websocket-extensions'] + ); + + if (offers[PerMessageDeflate.extensionName]) { + perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]); + extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + } + } catch (err) { + return abortConnection(socket, 400); + } + } + var protocol = (req.headers['sec-websocket-protocol'] || '').split(/, */); // @@ -186,21 +209,30 @@ class WebSocketServer extends EventEmitter { this.options.verifyClient(info, (verified, code, message) => { if (!verified) return abortConnection(socket, code || 401, message); - this.completeUpgrade(protocol, version, req, socket, head, cb); + this.completeUpgrade( + protocol, + extensions, + version, + req, + socket, + head, + cb + ); }); return; - } else if (!this.options.verifyClient(info)) { - return abortConnection(socket, 401); } + + if (!this.options.verifyClient(info)) return abortConnection(socket, 401); } - this.completeUpgrade(protocol, version, req, socket, head, cb); + this.completeUpgrade(protocol, extensions, version, req, socket, head, cb); } /** * Upgrade the connection to WebSocket. * * @param {String} protocol The chosen subprotocol + * @param {Object} extensions The accepted extensions * @param {Number} version The WebSocket protocol version * @param {http.IncomingMessage} req The request object * @param {net.Socket} socket The network socket between the server and client @@ -208,7 +240,7 @@ class WebSocketServer extends EventEmitter { * @param {Function} cb Callback * @private */ - completeUpgrade (protocol, version, req, socket, head, cb) { + completeUpgrade (protocol, extensions, version, req, socket, head, cb) { // // Destroy the socket if the client has already sent a FIN packet. // @@ -226,25 +258,12 @@ class WebSocketServer extends EventEmitter { ]; if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); - - var extensions; - - try { - const offers = Extensions.parse(req.headers['sec-websocket-extensions']); - extensions = acceptExtensions(this.options, offers); - } catch (err) { - return abortConnection(socket, 400); - } - - const props = Object.keys(extensions); - - if (props.length) { - const serverExtensions = props.reduce((obj, key) => { - obj[key] = [extensions[key].params]; - return obj; - }, {}); - - headers.push(`Sec-WebSocket-Extensions: ${Extensions.format(serverExtensions)}`); + if (extensions[PerMessageDeflate.extensionName]) { + const params = extensions[PerMessageDeflate.extensionName].params; + const value = Extensions.format({ + [PerMessageDeflate.extensionName]: [params] + }); + headers.push(`Sec-WebSocket-Extensions: ${value}`); } // @@ -252,7 +271,7 @@ class WebSocketServer extends EventEmitter { // this.emit('headers', headers, req); - socket.write(headers.concat('', '').join('\r\n')); + socket.write(headers.concat('\r\n').join('\r\n')); const client = new WebSocket([socket, head], null, { maxPayload: this.options.maxPayload, @@ -282,32 +301,6 @@ function socketError () { this.destroy(); } -/** - * Accept WebSocket extensions. - * - * @param {Object} options The `WebSocketServer` configuration options - * @param {Object} offer The parsed value of the `sec-websocket-extensions` header - * @return {Object} Accepted extensions - * @private - */ -function acceptExtensions (options, offer) { - const pmd = options.perMessageDeflate; - const extensions = {}; - - if (pmd && offer[PerMessageDeflate.extensionName]) { - const perMessageDeflate = new PerMessageDeflate( - pmd !== true ? pmd : {}, - true, - options.maxPayload - ); - - perMessageDeflate.accept(offer[PerMessageDeflate.extensionName]); - extensions[PerMessageDeflate.extensionName] = perMessageDeflate; - } - - return extensions; -} - /** * Close the connection when preconditions are not fulfilled. * From b4465f6c86e0ca7317f97a180b347baa0f109f6e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 17 Nov 2017 10:50:23 +0100 Subject: [PATCH 417/669] [test] Increase code coverage --- test/WebSocket.test.js | 87 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 16 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 99cd7551c..84e20baf7 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -352,7 +352,7 @@ describe('WebSocket', function () { it('invalid server key is denied', function (done) { server.once('upgrade', (req, socket) => { - socket.on('end', () => socket.end()); + socket.on('end', socket.end); socket.write( 'HTTP/1.1 101 Switching Protocols\r\n' + 'Upgrade: websocket\r\n' + @@ -408,7 +408,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'unexpected server response (401)'); @@ -430,8 +430,8 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); - ws.on('error', () => assert.fail(null, null, 'error shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('error', () => done(new Error("unexpected 'error' event"))); ws.on('unexpected-response', (req, res) => { assert.strictEqual(res.statusCode, 401); @@ -462,8 +462,8 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); - ws.on('error', () => assert.fail(null, null, 'error shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('error', () => done(new Error("unexpected 'error' event"))); ws.on('unexpected-response', (req, res) => { assert.strictEqual(res.statusCode, 401); @@ -472,20 +472,75 @@ describe('WebSocket', function () { }); }); - it('emits an error if the opening handshake timeout expires', function (done) { + it('fails if the opening handshake timeout expires', function (done) { server.once('upgrade', (req, socket) => socket.on('end', socket.end)); const ws = new WebSocket(`ws://localhost:${port}`, null, { handshakeTimeout: 100 }); - ws.on('open', () => assert.fail(null, null, 'connect shouldn\'t be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'opening handshake has timed out'); done(); }); }); + + it('fails if the Sec-WebSocket-Extensions response header is invalid', function (done) { + server.once('upgrade', (req, socket) => { + const key = crypto.createHash('sha1') + .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary') + .digest('base64'); + + socket.end( + 'HTTP/1.1 101 Switching Protocols\r\n' + + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + `Sec-WebSocket-Accept: ${key}\r\n` + + 'Sec-WebSocket-Extensions: foo;=\r\n' + + '\r\n' + ); + }); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid Sec-WebSocket-Extensions header'); + ws.on('close', () => done()); + }); + }); + + it('fails if server sends a subprotocol when none was requested', function (done) { + server.once('upgrade', (req, socket) => { + const key = crypto.createHash('sha1') + .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary') + .digest('base64'); + + socket.end( + 'HTTP/1.1 101 Switching Protocols\r\n' + + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + `Sec-WebSocket-Accept: ${key}\r\n` + + 'Sec-WebSocket-Protocol: foo\r\n' + + '\r\n' + ); + }); + + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'server sent a subprotocol even though none requested' + ); + ws.on('close', () => done()); + }); + }); }); describe('connection with query string', function () { @@ -968,7 +1023,7 @@ describe('WebSocket', function () { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'closed before the connection is established'); @@ -985,7 +1040,7 @@ describe('WebSocket', function () { }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'closed before the connection is established'); @@ -998,7 +1053,7 @@ describe('WebSocket', function () { it('can be called from an error listener while connecting', function (done) { const ws = new WebSocket(`ws://localhost:${++port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.code, 'ECONNREFUSED'); @@ -1011,7 +1066,7 @@ describe('WebSocket', function () { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'closed before the connection is established'); @@ -1192,7 +1247,7 @@ describe('WebSocket', function () { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'closed before the connection is established'); @@ -1209,7 +1264,7 @@ describe('WebSocket', function () { }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'closed before the connection is established'); @@ -1222,7 +1277,7 @@ describe('WebSocket', function () { it('can be called from an error listener while connecting', function (done) { const ws = new WebSocket(`ws://localhost:${++port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.code, 'ECONNREFUSED'); @@ -1235,7 +1290,7 @@ describe('WebSocket', function () { const wss = new WebSocketServer({ port: ++port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => assert.fail(null, null, 'connect shouldnt be raised here')); + ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'closed before the connection is established'); From 0c8c0b8785ad0b85ef248cf921e58b1b5026db3c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 18 Nov 2017 15:29:12 +0100 Subject: [PATCH 418/669] [minor] Add JSDoc for `PerMessageDeflate` constructor --- lib/PerMessageDeflate.js | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 35c687d30..7c538892b 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -19,18 +19,41 @@ const kBuffers = Symbol('buffers'); const kError = Symbol('error'); const kOwner = Symbol('owner'); +// // We limit zlib concurrency, which prevents severe memory fragmentation // as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913 // and https://github.com/websockets/ws/issues/1202 // -// Intentionally global; it's the global thread pool that's -// an issue. +// Intentionally global; it's the global thread pool that's an issue. +// let zlibLimiter; /** - * Per-message Deflate implementation. + * permessage-deflate implementation. */ class PerMessageDeflate { + /** + * Creates a PerMessageDeflate instance. + * + * @param {Object} options Configuration options + * @param {Boolean} options.serverNoContextTakeover Request/accept disabling + * of server context takeover + * @param {Boolean} options.clientNoContextTakeover Advertise/acknowledge + * disabling of client context takeover + * @param {(Boolean|Number)} options.serverMaxWindowBits Request/confirm the + * use of a custom server window size + * @param {(Boolean|Number)} options.clientMaxWindowBits Advertise support + * for, or request, a custom client window size + * @param {Number} options.level The value of zlib's `level` param + * @param {Number} options.memLevel The value of zlib's `memLevel` param + * @param {Number} options.threshold Size (in bytes) below which messages + * should not be compressed + * @param {Number} options.concurrencyLimit The number of concurrent calls to + * zlib + * @param {Boolean} isServer Create the instance in either server or client + * mode + * @param {Number} maxPayload The maximum allowed message length + */ constructor (options, isServer, maxPayload) { this._maxPayload = maxPayload | 0; this._options = options || {}; @@ -51,6 +74,9 @@ class PerMessageDeflate { } } + /** + * @type {String} + */ static get extensionName () { return 'permessage-deflate'; } From 16f727d3e061eff7713eabc1ae367f65438d8ddb Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 18 Nov 2017 16:05:12 +0100 Subject: [PATCH 419/669] [doc] Clarify `PerMessageDeflate` options --- doc/ws.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 14f981eec..2975958f1 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -66,12 +66,11 @@ status code, otherwise the returned value sets the value of the The extension is disabled when `false` (default value). If an object is provided then that is extension parameters: -- `serverNoContextTakeover` {Boolean} Whether to use context take over or not. -- `clientNoContextTakeover` {Boolean} The value to be requested to clients - whether to use context take over or not. -- `serverMaxWindowBits` {Number} The value of windowBits. -- `clientMaxWindowBits` {Number} The value of max windowBits to be requested - to clients. +- `serverNoContextTakeover` {Boolean} Whether to use context takeover or not. +- `clientNoContextTakeover` {Boolean} Acknowledge disabling of client context + takeover. +- `serverMaxWindowBits` {Number} The value of `windowBits`. +- `clientMaxWindowBits` {Number} Request a custom client window size. - `level` {Number} The value of zlib's `level` param (0-9, default 8). - `memLevel` {Number} The value of zlib's `memLevel` param (1-9, default 8). - `threshold` {Number} Payloads smaller than this will not be compressed. @@ -198,8 +197,9 @@ This class represents a WebSocket. It extends the `EventEmitter`. - `ca` {Array} Trusted certificates. `perMessageDeflate` default value is `true`. When using an object, parameters -are the same of the server. The only difference is the direction of requests -(e.g. `serverNoContextTakeover` is the value to be requested to the server). +are the same of the server. The only difference is the direction of requests. +For example, `serverNoContextTakeover` can be used to ask the server to +disable context takeover. Create a new WebSocket instance. From 0a9621f9ff35e6f80c9c8471d0b202af4e357705 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 18 Nov 2017 18:41:58 +0100 Subject: [PATCH 420/669] [minor] Merge `pushOffer()` and `pushParam()` into `push()` --- lib/Extensions.js | 49 ++++++++++++++++++----------------------------- 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/lib/Extensions.js b/lib/Extensions.js index 14c38ac2b..15fee1216 100644 --- a/lib/Extensions.js +++ b/lib/Extensions.js @@ -23,29 +23,18 @@ const tokenChars = [ ]; /** - * Adds an offer to the map of extension offers. + * Adds an offer to the map of extension offers or a parameter to the map of + * parameters. * - * @param {Object} offers The map of extension offers - * @param {String} name The extension name - * @param {Object} params The extension parameters + * @param {Object} dest The map of extension offers or parameters + * @param {String} name The extension or parameter name + * @param {(Object|Boolean|String)} elem The extension parameters or the + * parameter value * @private */ -function pushOffer (offers, name, params) { - if (Object.hasOwnProperty.call(offers, name)) offers[name].push(params); - else offers[name] = [params]; -} - -/** - * Adds a parameter to the map of parameters. - * - * @param {Object} params The map of parameters - * @param {String} name The parameter name - * @param {Object} value The parameter value - * @private - */ -function pushParam (params, name, value) { - if (Object.hasOwnProperty.call(params, name)) params[name].push(value); - else params[name] = [value]; +function push (dest, name, elem) { + if (Object.prototype.hasOwnProperty.call(dest, name)) dest[name].push(elem); + else dest[name] = [elem]; } /** @@ -83,7 +72,7 @@ function parse (header) { if (end === -1) end = i; const name = header.slice(start, end); if (code === 0x2c) { - pushOffer(offers, name, params); + push(offers, name, params); params = {}; } else { extensionName = name; @@ -102,9 +91,9 @@ function parse (header) { if (start === -1) throw new Error(`unexpected character at index ${i}`); if (end === -1) end = i; - pushParam(params, header.slice(start, end), true); + push(params, header.slice(start, end), true); if (code === 0x2c) { - pushOffer(offers, extensionName, params); + push(offers, extensionName, params); params = {}; extensionName = undefined; } @@ -155,9 +144,9 @@ function parse (header) { value = value.replace(/\\/g, ''); mustUnescape = false; } - pushParam(params, paramName, value); + push(params, paramName, value); if (code === 0x2c) { - pushOffer(offers, extensionName, params); + push(offers, extensionName, params); params = {}; extensionName = undefined; } @@ -175,16 +164,16 @@ function parse (header) { if (end === -1) end = i; const token = header.slice(start, end); if (extensionName === undefined) { - pushOffer(offers, token, {}); + push(offers, token, {}); } else { if (paramName === undefined) { - pushParam(params, token, true); + push(params, token, true); } else if (mustUnescape) { - pushParam(params, paramName, token.replace(/\\/g, '')); + push(params, paramName, token.replace(/\\/g, '')); } else { - pushParam(params, paramName, token); + push(params, paramName, token); } - pushOffer(offers, extensionName, params); + push(offers, extensionName, params); } return offers; From 9c73abe805db1cf7d8fd511c7e0a46b3a7139ede Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 19 Nov 2017 10:58:06 +0100 Subject: [PATCH 421/669] [minor] Remove some redundant code --- lib/PerMessageDeflate.js | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 7c538892b..6ed12a7b7 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -221,28 +221,21 @@ class PerMessageDeflate { acceptAsClient (paramsList) { const params = paramsList[0]; - if (this._options.clientNoContextTakeover != null) { - if ( - this._options.clientNoContextTakeover === false && - params.client_no_context_takeover - ) { - throw new Error('Invalid value for "client_no_context_takeover"'); - } + if ( + this._options.clientNoContextTakeover === false && + params.client_no_context_takeover + ) { + throw new Error('Invalid value for "client_no_context_takeover"'); } - if (this._options.clientMaxWindowBits != null) { - if ( - this._options.clientMaxWindowBits === false && - params.client_max_window_bits - ) { - throw new Error('Invalid value for "client_max_window_bits"'); - } - if ( - typeof this._options.clientMaxWindowBits === 'number' && + + if ( + (typeof this._options.clientMaxWindowBits === 'number' && (!params.client_max_window_bits || - params.client_max_window_bits > this._options.clientMaxWindowBits) - ) { - throw new Error('Invalid value for "client_max_window_bits"'); - } + params.client_max_window_bits > this._options.clientMaxWindowBits)) || + (this._options.clientMaxWindowBits === false && + params.client_max_window_bits) + ) { + throw new Error('Invalid value for "client_max_window_bits"'); } return params; From 02d0011e73786fd00b09f66a06338ee204535255 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 20 Nov 2017 15:43:20 +0100 Subject: [PATCH 422/669] [test] Use an OS-assigned arbitrary unused port --- test/WebSocket.test.js | 358 ++++++++++++++++++------------ test/WebSocketServer.test.js | 411 ++++++++++++++++------------------- 2 files changed, 406 insertions(+), 363 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 84e20baf7..0cad5ddac 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -13,8 +13,6 @@ const constants = require('../lib/Constants'); const WebSocket = require('..'); const Buffer = safeBuffer.Buffer; -const WebSocketServer = WebSocket.Server; -let port = 20000; class CustomAgent extends http.Agent { addRequest () {} @@ -65,7 +63,8 @@ describe('WebSocket', function () { }); it('accepts the localAddress option', function (done) { - const wss = new WebSocketServer({ host: '127.0.0.1', port: ++port }, () => { + const wss = new WebSocket.Server({ host: '127.0.0.1', port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { localAddress: '127.0.0.2' }); @@ -87,7 +86,7 @@ describe('WebSocket', function () { const localAddress = '123.456.789.428'; assert.throws(() => { - const ws = new WebSocket(`ws://localhost:${port}`, { localAddress }); + const ws = new WebSocket('ws://localhost', { localAddress }); }, (err) => { return err instanceof TypeError && (err.code === 'ERR_INVALID_IP_ADDRESS' || err.message.includes(`must be a valid IP: ${localAddress}`)); @@ -95,7 +94,8 @@ describe('WebSocket', function () { }); it('accepts the family option', function (done) { - const wss = new WebSocketServer({ host: '::1', port: ++port }, () => { + const wss = new WebSocket.Server({ host: '::1', port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 }); }); @@ -114,7 +114,8 @@ describe('WebSocket', function () { describe('properties', function () { it('#bytesReceived exposes number of bytes received', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('message', () => { assert.strictEqual(ws.bytesReceived, 8); @@ -125,14 +126,14 @@ describe('WebSocket', function () { }); it('#url exposes the server url', function () { - const url = `ws://localhost:${port}`; + const url = 'ws://localhost'; const ws = new WebSocket(url, { agent: new CustomAgent() }); assert.strictEqual(ws.url, url); }); it('#protocolVersion exposes the protocol version', function () { - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -141,7 +142,7 @@ describe('WebSocket', function () { describe('#bufferedAmount', function () { it('defaults to zero', function () { - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -149,7 +150,8 @@ describe('WebSocket', function () { }); it('defaults to zero upon "open"', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.onopen = () => { @@ -160,10 +162,11 @@ describe('WebSocket', function () { }); it('takes into account the data in the sender queue', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: true, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { threshold: 0 } }); @@ -182,7 +185,8 @@ describe('WebSocket', function () { }); it('takes into account the data in the socket queue', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); }); @@ -202,11 +206,11 @@ describe('WebSocket', function () { describe('Custom headers', function () { const server = http.createServer(); - beforeEach((done) => server.listen(++port, done)); + beforeEach((done) => server.listen(0, done)); afterEach((done) => server.close(done)); it('request has an authorization header', function (done) { - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); const auth = 'test:testpass'; server.once('upgrade', (req, socket, head) => { @@ -219,11 +223,12 @@ describe('WebSocket', function () { wss.close(done); }); + const port = server.address().port; const ws = new WebSocket(`ws://${auth}@localhost:${port}`); }); it('accepts custom headers', function (done) { - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); server.once('upgrade', (req, socket, head) => { assert.ok(req.headers.cookie); @@ -232,7 +237,7 @@ describe('WebSocket', function () { wss.close(done); }); - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket(`ws://localhost:${server.address().port}`, { headers: { 'Cookie': 'foo=bar' } }); }); @@ -240,7 +245,7 @@ describe('WebSocket', function () { describe('#readyState', function () { it('defaults to connecting', function () { - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -248,7 +253,8 @@ describe('WebSocket', function () { }); it('set to open once connection is established', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { @@ -261,7 +267,8 @@ describe('WebSocket', function () { }); it('set to closed once connection is closed', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', () => { @@ -274,7 +281,8 @@ describe('WebSocket', function () { }); it('set to closed once connection is terminated', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', () => { @@ -316,7 +324,8 @@ describe('WebSocket', function () { describe('events', function () { it('emits a ping event', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('ping', () => wss.close(done)); }); @@ -325,7 +334,8 @@ describe('WebSocket', function () { }); it('emits a pong event', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('pong', () => wss.close(done)); }); @@ -334,7 +344,8 @@ describe('WebSocket', function () { }); it('emits a headers event', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('headers', (headers, res) => { assert.strictEqual(headers, res.headers); @@ -347,7 +358,7 @@ describe('WebSocket', function () { describe('connection establishing', function () { const server = http.createServer(); - beforeEach((done) => server.listen(++port, done)); + beforeEach((done) => server.listen(0, done)); afterEach((done) => server.close(done)); it('invalid server key is denied', function (done) { @@ -362,7 +373,7 @@ describe('WebSocket', function () { ); }); - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('error', (err) => { assert.ok(err instanceof Error); @@ -386,7 +397,7 @@ describe('WebSocket', function () { ); }); - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('close', (code, reason) => { assert.strictEqual(code, 1006); @@ -406,7 +417,7 @@ describe('WebSocket', function () { ); }); - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { @@ -428,7 +439,7 @@ describe('WebSocket', function () { ); }); - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', () => done(new Error("unexpected 'error' event"))); @@ -460,7 +471,7 @@ describe('WebSocket', function () { ); }); - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', () => done(new Error("unexpected 'error' event"))); @@ -475,6 +486,7 @@ describe('WebSocket', function () { it('fails if the opening handshake timeout expires', function (done) { server.once('upgrade', (req, socket) => socket.on('end', socket.end)); + const port = server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, null, { handshakeTimeout: 100 }); @@ -503,7 +515,7 @@ describe('WebSocket', function () { ); }); - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { @@ -529,7 +541,7 @@ describe('WebSocket', function () { ); }); - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { @@ -545,7 +557,8 @@ describe('WebSocket', function () { describe('connection with query string', function () { it('connects when pathname is not null', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}/?token=qwerty`); ws.on('open', () => wss.close(done)); @@ -553,7 +566,8 @@ describe('WebSocket', function () { }); it('connects when pathname is null', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}?token=qwerty`); ws.on('open', () => wss.close(done)); @@ -601,7 +615,8 @@ describe('WebSocket', function () { client.send('foo'); }; - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); serverClient = ws; @@ -617,7 +632,7 @@ describe('WebSocket', function () { describe('#ping', function () { it('before connect should fail', function () { - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -625,7 +640,7 @@ describe('WebSocket', function () { }); it('before connect can silently fail', function () { - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -633,7 +648,8 @@ describe('WebSocket', function () { }); it('without message is successfully transmitted to the server', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.ping()); @@ -645,7 +661,8 @@ describe('WebSocket', function () { }); it('with message is successfully transmitted to the server', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { @@ -664,7 +681,8 @@ describe('WebSocket', function () { }); it('can send numbers as ping payload', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.ping(0)); @@ -681,7 +699,7 @@ describe('WebSocket', function () { describe('#pong', function () { it('before connect should fail', () => { - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -689,7 +707,7 @@ describe('WebSocket', function () { }); it('before connect can silently fail', function () { - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -697,7 +715,8 @@ describe('WebSocket', function () { }); it('without message is successfully transmitted to the server', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.pong()); @@ -709,7 +728,8 @@ describe('WebSocket', function () { }); it('with message is successfully transmitted to the server', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { @@ -728,7 +748,8 @@ describe('WebSocket', function () { }); it('can send numbers as pong payload', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.pong(0)); @@ -745,13 +766,14 @@ describe('WebSocket', function () { describe('#send', function () { it('very long binary data can be sent and received', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { const array = new Float32Array(5 * 1024 * 1024); for (let i = 0; i < array.length; i++) { array[i] = i / 5; } + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(array, { compress: false })); @@ -767,7 +789,8 @@ describe('WebSocket', function () { }); it('can send and receive text data', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send('hi')); @@ -783,7 +806,8 @@ describe('WebSocket', function () { }); it('does not override the `fin` option', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { @@ -801,7 +825,8 @@ describe('WebSocket', function () { }); it('sends numbers as strings', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(0)); @@ -816,7 +841,7 @@ describe('WebSocket', function () { }); it('can send binary data as an array', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { const array = new Float32Array(6); for (let i = 0; i < array.length; ++i) { @@ -827,6 +852,7 @@ describe('WebSocket', function () { const buf = Buffer.from(partial.buffer) .slice(partial.byteOffset, partial.byteOffset + partial.byteLength); + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(partial, { binary: true })); @@ -842,8 +868,9 @@ describe('WebSocket', function () { }); it('can send binary data as a buffer', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { const buf = Buffer.from('foobar'); + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(buf, { binary: true })); @@ -859,13 +886,14 @@ describe('WebSocket', function () { }); it('ArrayBuffer is auto-detected without binary flag', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { const array = new Float32Array(5); for (let i = 0; i < array.length; ++i) { array[i] = i / 2; } + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(array.buffer)); @@ -881,8 +909,9 @@ describe('WebSocket', function () { }); it('Buffer is auto-detected without binary flag', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { const buf = Buffer.from('foobar'); + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(buf)); @@ -899,7 +928,7 @@ describe('WebSocket', function () { }); it('before connect should fail', function () { - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -907,7 +936,7 @@ describe('WebSocket', function () { }); it('before connect should pass error through callback, if present', function () { - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -918,7 +947,8 @@ describe('WebSocket', function () { }); it('without data should be successful', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send()); @@ -933,7 +963,8 @@ describe('WebSocket', function () { }); it('calls optional callback when flushed', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { @@ -946,7 +977,8 @@ describe('WebSocket', function () { }); it('with unmasked message is successfully transmitted to the server', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send('hi', { mask: false })); @@ -961,7 +993,8 @@ describe('WebSocket', function () { }); it('with masked message is successfully transmitted to the server', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send('hi', { mask: true })); @@ -982,7 +1015,8 @@ describe('WebSocket', function () { array[i] = i / 2; } - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(array, { mask: false, binary: true })); @@ -1003,7 +1037,8 @@ describe('WebSocket', function () { array[i] = i / 2; } - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.send(array, { mask: true, binary: true })); @@ -1020,7 +1055,8 @@ describe('WebSocket', function () { describe('#close', function () { it('closes the connection if called while connecting (1/2)', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); @@ -1034,10 +1070,11 @@ describe('WebSocket', function () { }); it('closes the connection if called while connecting (2/2)', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (info, cb) => setTimeout(cb, 300, true), - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); @@ -1051,7 +1088,7 @@ describe('WebSocket', function () { }); it('can be called from an error listener while connecting', function (done) { - const ws = new WebSocket(`ws://localhost:${++port}`); + const ws = new WebSocket('ws://localhost:1337'); ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { @@ -1063,7 +1100,8 @@ describe('WebSocket', function () { }); it('can be called from a listener of the headers event', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); @@ -1077,7 +1115,8 @@ describe('WebSocket', function () { }); it('throws an error if the first argument is invalid (1/2)', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { @@ -1092,7 +1131,8 @@ describe('WebSocket', function () { }); it('throws an error if the first argument is invalid (2/2)', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { @@ -1107,7 +1147,8 @@ describe('WebSocket', function () { }); it('works when close reason is not specified', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.close(1000)); @@ -1123,7 +1164,8 @@ describe('WebSocket', function () { }); it('works when close reason is specified', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.close(1000, 'some reason')); @@ -1139,10 +1181,11 @@ describe('WebSocket', function () { }); it('ends connection to the server', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ clientTracking: false, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { @@ -1157,10 +1200,11 @@ describe('WebSocket', function () { }); it('permits all buffered data to be delivered', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); const messages = []; @@ -1181,7 +1225,8 @@ describe('WebSocket', function () { }); it('allows close code 1013', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', (code) => { @@ -1195,7 +1240,7 @@ describe('WebSocket', function () { it('closes the connection when an error occurs', function (done) { const server = http.createServer(); - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); let closed = false; wss.on('connection', (ws) => { @@ -1212,8 +1257,8 @@ describe('WebSocket', function () { }); }); - server.listen(++port, () => { - const ws = new WebSocket(`ws://localhost:${port}`); + server.listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('open', () => ws._socket.write(Buffer.from([0xa1, 0x00]))); ws.on('close', (code, reason) => { @@ -1227,7 +1272,8 @@ describe('WebSocket', function () { }); it('does nothing if the connection is already CLOSED', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', (code) => { @@ -1244,7 +1290,8 @@ describe('WebSocket', function () { describe('#terminate', function () { it('closes the connection if called while connecting (1/2)', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); @@ -1258,10 +1305,11 @@ describe('WebSocket', function () { }); it('closes the connection if called while connecting (2/2)', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (info, cb) => setTimeout(cb, 300, true), - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); @@ -1275,7 +1323,7 @@ describe('WebSocket', function () { }); it('can be called from an error listener while connecting', function (done) { - const ws = new WebSocket(`ws://localhost:${++port}`); + const ws = new WebSocket('ws://localhost:1337'); ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { @@ -1287,7 +1335,8 @@ describe('WebSocket', function () { }); it('can be called from a listener of the headers event', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => done(new Error("unexpected 'open' event"))); @@ -1301,7 +1350,8 @@ describe('WebSocket', function () { }); it('does nothing if the connection is already CLOSED', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', (code) => { @@ -1363,10 +1413,11 @@ describe('WebSocket', function () { }); it('should work the same as the EventEmitter api', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ clientTracking: false, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); let message = 0; let close = 0; @@ -1468,7 +1519,8 @@ describe('WebSocket', function () { }); it('should receive text data wrapped in a MessageEvent when using addEventListener', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('open', () => ws.send('hi')); @@ -1484,7 +1536,8 @@ describe('WebSocket', function () { }); it('should receive valid CloseEvent when server closes with code 1000', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('close', (closeEvent) => { @@ -1498,7 +1551,8 @@ describe('WebSocket', function () { }); it('should assign "true" to wasClean when server closes with code 3000', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('close', (closeEvent) => { @@ -1511,7 +1565,8 @@ describe('WebSocket', function () { }); it('should assign "true" to wasClean when server closes with code 4999', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('close', (closeEvent) => { @@ -1524,7 +1579,8 @@ describe('WebSocket', function () { }); it('should receive valid CloseEvent when server closes with code 1001', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('close', (closeEvent) => { @@ -1539,7 +1595,8 @@ describe('WebSocket', function () { }); it('should have target set on Events', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('open', (openEvent) => { @@ -1565,7 +1622,8 @@ describe('WebSocket', function () { }); it('should have type set on Events', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('open', (openEvent) => { @@ -1591,7 +1649,8 @@ describe('WebSocket', function () { }); it('should pass binary data as a Node.js Buffer by default', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.onmessage = (evt) => { @@ -1604,7 +1663,8 @@ describe('WebSocket', function () { }); it('should pass an ArrayBuffer for event.data if binaryType = arraybuffer', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.binaryType = 'arraybuffer'; @@ -1619,7 +1679,8 @@ describe('WebSocket', function () { }); it('should ignore binaryType for text messages', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.binaryType = 'arraybuffer'; @@ -1634,7 +1695,8 @@ describe('WebSocket', function () { }); it('should allow to update binaryType on the fly', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); function testType (binaryType, next) { @@ -1678,16 +1740,18 @@ describe('WebSocket', function () { cert: fs.readFileSync('test/fixtures/certificate.pem'), key: fs.readFileSync('test/fixtures/key.pem') }); - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); wss.on('connection', (ws) => { wss.close(); server.close(done); }); - server.listen(++port, () => new WebSocket(`wss://localhost:${port}`, { - rejectUnauthorized: false - })); + server.listen(0, () => { + const ws = new WebSocket(`wss://localhost:${server.address().port}`, { + rejectUnauthorized: false + }); + }); }); it('can connect to secure websocket server with client side certificate', function (done) { @@ -1699,7 +1763,7 @@ describe('WebSocket', function () { }); let success = false; - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (info) => { success = !!info.req.client.authorized; return true; @@ -1713,8 +1777,8 @@ describe('WebSocket', function () { wss.close(); }); - server.listen(++port, () => { - const ws = new WebSocket(`wss://localhost:${port}`, { + server.listen(0, () => { + const ws = new WebSocket(`wss://localhost:${server.address().port}`, { cert: fs.readFileSync('test/fixtures/agent1-cert.pem'), key: fs.readFileSync('test/fixtures/agent1-key.pem'), rejectUnauthorized: false @@ -1727,10 +1791,10 @@ describe('WebSocket', function () { cert: fs.readFileSync('test/fixtures/certificate.pem'), key: fs.readFileSync('test/fixtures/key.pem') }); - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); - server.listen(++port, () => { - const ws = new WebSocket(`ws://localhost:${port}`, { + server.listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`, { rejectUnauthorized: false }); @@ -1746,7 +1810,7 @@ describe('WebSocket', function () { cert: fs.readFileSync('test/fixtures/certificate.pem'), key: fs.readFileSync('test/fixtures/key.pem') }); - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); wss.on('connection', (ws) => { ws.on('message', (message) => { @@ -1756,8 +1820,8 @@ describe('WebSocket', function () { }); }); - server.listen(++port, () => { - const ws = new WebSocket(`wss://localhost:${port}`, { + server.listen(0, () => { + const ws = new WebSocket(`wss://localhost:${server.address().port}`, { rejectUnauthorized: false }); @@ -1773,14 +1837,14 @@ describe('WebSocket', function () { cert: fs.readFileSync('test/fixtures/certificate.pem'), key: fs.readFileSync('test/fixtures/key.pem') }); - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); wss.on('connection', (ws) => { ws.on('message', (message) => ws.send(message)); }); - server.listen(++port, () => { - const ws = new WebSocket(`wss://localhost:${port}`, { + server.listen(0, () => { + const ws = new WebSocket(`wss://localhost:${server.address().port}`, { rejectUnauthorized: false }); @@ -1800,11 +1864,11 @@ describe('WebSocket', function () { const agent = new CustomAgent(); agent.addRequest = (req) => { - assert.strictEqual(req._headers.host, `localhost:${port}`); + assert.strictEqual(req._headers.host, 'localhost:1337'); done(); }; - const ws = new WebSocket(`ws://localhost:${port}`, { agent }); + const ws = new WebSocket('ws://localhost:1337', { agent }); }); it('lacks default origin header', function (done) { @@ -1815,7 +1879,7 @@ describe('WebSocket', function () { done(); }; - const ws = new WebSocket(`ws://localhost:${port}`, { agent }); + const ws = new WebSocket('ws://localhost', { agent }); }); it('honors origin set in options (1/2)', function (done) { @@ -1826,7 +1890,7 @@ describe('WebSocket', function () { done(); }; - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { origin: 'https://example.com:8000', agent }); @@ -1843,7 +1907,7 @@ describe('WebSocket', function () { done(); }; - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket('ws://localhost', { origin: 'https://example.com:8000', protocolVersion: 8, agent @@ -1877,14 +1941,14 @@ describe('WebSocket', function () { describe('permessage-deflate', function () { it('is enabled by default', (done) => { const server = http.createServer(); - const wss = new WebSocketServer({ server, perMessageDeflate: true }); + const wss = new WebSocket.Server({ server, perMessageDeflate: true }); server.on('upgrade', (req, socket, head) => { assert.ok(req.headers['sec-websocket-extensions'].includes('permessage-deflate')); }); - server.listen(++port, () => { - const ws = new WebSocket(`ws://localhost:${port}`); + server.listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('open', () => { assert.ok(ws.extensions['permessage-deflate']); @@ -1896,14 +1960,14 @@ describe('WebSocket', function () { it('can be disabled', function (done) { const server = http.createServer(); - const wss = new WebSocketServer({ server, perMessageDeflate: true }); + const wss = new WebSocket.Server({ server, perMessageDeflate: true }); server.on('upgrade', (req, socket, head) => { assert.strictEqual(req.headers['sec-websocket-extensions'], undefined); }); - server.listen(++port, () => { - const ws = new WebSocket(`ws://localhost:${port}`, { + server.listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`, { perMessageDeflate: false }); @@ -1916,7 +1980,7 @@ describe('WebSocket', function () { it('can send extension parameters', function (done) { const server = http.createServer(); - const wss = new WebSocketServer({ server, perMessageDeflate: true }); + const wss = new WebSocket.Server({ server, perMessageDeflate: true }); server.on('upgrade', (req, socket, head) => { const extensions = req.headers['sec-websocket-extensions']; @@ -1928,8 +1992,8 @@ describe('WebSocket', function () { assert.ok(extensions.includes('client_max_window_bits')); }); - server.listen(++port, () => { - const ws = new WebSocket(`ws://localhost:${port}`, { + server.listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`, { perMessageDeflate: { serverNoContextTakeover: true, clientNoContextTakeover: true, @@ -1946,10 +2010,11 @@ describe('WebSocket', function () { }); it('can send and receive text data', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { threshold: 0 } }); @@ -1973,10 +2038,11 @@ describe('WebSocket', function () { array[i] = i / 2; } - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { threshold: 0 } }); @@ -2000,10 +2066,11 @@ describe('WebSocket', function () { array[i] = i / 2; } - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { threshold: 0 } }); @@ -2021,10 +2088,11 @@ describe('WebSocket', function () { }); it('consumes all received data when connection is closed abnormally', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); const messages = []; @@ -2046,7 +2114,8 @@ describe('WebSocket', function () { describe('#send', function () { it('can set the compress option true when perMessageDeflate is disabled', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: false }); @@ -2066,10 +2135,11 @@ describe('WebSocket', function () { describe('#close', function () { it('should not raise error callback, if any, if called during send data', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { threshold: 0 } }); @@ -2094,10 +2164,11 @@ describe('WebSocket', function () { describe('#terminate', function () { it('will raise error callback, if any, if called during send data', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { threshold: 0 } }); @@ -2113,10 +2184,11 @@ describe('WebSocket', function () { }); it('can call during receiving data', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { perMessageDeflate: { threshold: 0 } }); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 83519527e..5fca819bb 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -13,29 +13,30 @@ const fs = require('fs'); const WebSocket = require('..'); const Buffer = safeBuffer.Buffer; -const WebSocketServer = WebSocket.Server; -let port = 8000; describe('WebSocketServer', function () { describe('#ctor', function () { it('throws an error if no option object is passed', function () { - assert.throws(() => new WebSocketServer()); + assert.throws(() => new WebSocket.Server()); }); it('throws an error if no port or server is specified', function () { - assert.throws(() => new WebSocketServer({})); + assert.throws(() => new WebSocket.Server({})); }); it('emits an error if http server bind fails', function (done) { - const wss1 = new WebSocketServer({ port: 50003 }, () => { - const wss2 = new WebSocketServer({ port: 50003 }); + const wss1 = new WebSocket.Server({ port: 0 }, () => { + const wss2 = new WebSocket.Server({ + port: wss1._server.address().port + }); wss2.on('error', () => wss1.close(done)); }); }); it('starts a server on a given port', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const port = 1337; + const wss = new WebSocket.Server({ port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); }); @@ -43,7 +44,7 @@ describe('WebSocketServer', function () { }); it('binds the server on any IPv6 address when available', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { assert.strictEqual(wss._server.address().address, '::'); wss.close(done); }); @@ -52,9 +53,9 @@ describe('WebSocketServer', function () { it('uses a precreated http server', function (done) { const server = http.createServer(); - server.listen(++port, () => { - const wss = new WebSocketServer({ server }); - const ws = new WebSocket(`ws://localhost:${port}`); + server.listen(0, () => { + const wss = new WebSocket.Server({ server }); + const ws = new WebSocket(`ws://localhost:${server.address().port}`); wss.on('connection', (client) => { wss.close(); @@ -64,8 +65,8 @@ describe('WebSocketServer', function () { }); it('426s for non-Upgrade requests', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - http.get(`http://localhost:${port}`, (res) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + http.get(`http://localhost:${wss._server.address().port}`, (res) => { let body = ''; assert.strictEqual(res.statusCode, 426); @@ -88,7 +89,7 @@ describe('WebSocketServer', function () { const sockPath = `/tmp/ws.${crypto.randomBytes(16).toString('hex')}.socket`; server.listen(sockPath, () => { - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); wss.on('connection', (ws, req) => { if (wss.clients.size === 1) { @@ -106,21 +107,26 @@ describe('WebSocketServer', function () { }); it('will not crash when it receives an unhandled opcode', function (done) { - const wss = new WebSocketServer({ port: ++port }); + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}/`); - wss.on('connection', (ws) => { - ws.onerror = () => wss.close(done); + ws.on('open', () => ws._socket.write(Buffer.from([0x85, 0x00]))); }); - const ws = new WebSocket(`ws://localhost:${port}/`); - - ws.onopen = () => ws._socket.write(Buffer.from([0x85, 0x00])); + wss.on('connection', (ws) => { + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid opcode: 5'); + wss.close(done); + }); + }); }); }); describe('#close', function () { it('does not thrown when called twice', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { wss.close(); wss.close(); wss.close(); @@ -130,15 +136,16 @@ describe('WebSocketServer', function () { }); it('closes all clients', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', () => { if (++closes === 2) done(); }); }); let closes = 0; - wss.on('connection', (client) => { - client.on('close', () => { + wss.on('connection', (ws) => { + ws.on('close', () => { if (++closes === 2) done(); }); wss.close(); @@ -153,7 +160,7 @@ describe('WebSocketServer', function () { throw new Error('must not close pre-created server'); }; - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); wss.on('connection', (ws) => { wss.close(); @@ -161,22 +168,22 @@ describe('WebSocketServer', function () { server.close(done); }); - server.listen(++port, () => { - const ws = new WebSocket(`ws://localhost:${port}`); + server.listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`); }); }); it('invokes the callback in noServer mode', function (done) { - const wss = new WebSocketServer({ noServer: true }); + const wss = new WebSocket.Server({ noServer: true }); wss.close(done); }); it('cleans event handlers on precreated server', function (done) { const server = http.createServer(); - const wss = new WebSocketServer({ server }); + const wss = new WebSocket.Server({ server }); - server.listen(++port, () => { + server.listen(0, () => { wss.close(() => { assert.strictEqual(server.listeners('listening').length, 0); assert.strictEqual(server.listeners('upgrade').length, 0); @@ -190,8 +197,9 @@ describe('WebSocketServer', function () { describe('#clients', function () { it('returns a list of connected clients', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { assert.strictEqual(wss.clients.size, 0); + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); }); @@ -202,8 +210,9 @@ describe('WebSocketServer', function () { }); it('can be disabled', function (done) { - const wss = new WebSocketServer({ port: ++port, clientTracking: false }, () => { + const wss = new WebSocket.Server({ port: 0, clientTracking: false }, () => { assert.strictEqual(wss.clients, undefined); + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.close()); @@ -216,7 +225,8 @@ describe('WebSocketServer', function () { }); it('is updated when client terminates the connection', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.terminate()); @@ -231,7 +241,8 @@ describe('WebSocketServer', function () { }); it('is updated when client closes the connection', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => ws.close()); @@ -248,8 +259,8 @@ describe('WebSocketServer', function () { describe('#options', function () { it('exposes options passed to constructor', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - assert.strictEqual(wss.options.port, port); + const wss = new WebSocket.Server({ port: 0 }, () => { + assert.strictEqual(wss.options.port, 0); wss.close(done); }); }); @@ -258,7 +269,8 @@ describe('WebSocketServer', function () { describe('#maxpayload', function () { it('maxpayload is passed on to clients', function (done) { const maxPayload = 20480; - const wss = new WebSocketServer({ port: ++port, maxPayload }, () => { + const wss = new WebSocket.Server({ port: 0, maxPayload }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); }); @@ -270,7 +282,8 @@ describe('WebSocketServer', function () { it('maxpayload is passed on to hybi receivers', function (done) { const maxPayload = 20480; - const wss = new WebSocketServer({ port: ++port, maxPayload }, () => { + const wss = new WebSocket.Server({ port: 0, maxPayload }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); }); @@ -283,11 +296,14 @@ describe('WebSocketServer', function () { it('maxpayload is passed on to permessage-deflate', function (done) { const PerMessageDeflate = require('../lib/PerMessageDeflate'); const maxPayload = 20480; - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: true, - port: ++port, - maxPayload - }, () => new WebSocket(`ws://localhost:${port}`)); + maxPayload, + port: 0 + }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); + }); wss.on('connection', (client) => { assert.strictEqual( @@ -301,13 +317,13 @@ describe('WebSocketServer', function () { describe('#shouldHandle', function () { it('returns true when the path matches', function () { - const wss = new WebSocketServer({ noServer: true, path: '/foo' }); + const wss = new WebSocket.Server({ noServer: true, path: '/foo' }); assert.strictEqual(wss.shouldHandle({ url: '/foo' }), true); }); it('returns false when the path does not match', function () { - const wss = new WebSocketServer({ noServer: true, path: '/foo' }); + const wss = new WebSocket.Server({ noServer: true, path: '/foo' }); assert.strictEqual(wss.shouldHandle({ url: '/bar' }), false); }); @@ -317,14 +333,14 @@ describe('WebSocketServer', function () { it('can be used for a pre-existing server', function (done) { const server = http.createServer(); - server.listen(++port, () => { - const wss = new WebSocketServer({ noServer: true }); + server.listen(0, () => { + const wss = new WebSocket.Server({ noServer: true }); server.on('upgrade', (req, socket, head) => { wss.handleUpgrade(req, socket, head, (client) => client.send('hello')); }); - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${server.address().port}`); ws.on('message', (message) => { assert.strictEqual(message, 'hello'); @@ -335,66 +351,58 @@ describe('WebSocketServer', function () { }); it('closes the connection when path does not match', function (done) { - const wss = new WebSocketServer({ port: ++port, path: '/ws' }, () => { - const req = http.request({ + const wss = new WebSocket.Server({ port: 0, path: '/ws' }, () => { + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket' - }, - host: '127.0.0.1', - port + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 400); wss.close(done); }); - - req.end(); }); }); it('closes the connection when protocol version is Hixie-76', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const req = http.request({ + const wss = new WebSocket.Server({ port: 0 }, () => { + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'WebSocket', 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', 'Sec-WebSocket-Protocol': 'sample' - }, - port + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 400); wss.close(done); }); - - req.end(); }); }); }); describe('connection establishing', function () { it('does not accept connections with no sec-websocket-key', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const req = http.request({ + const wss = new WebSocket.Server({ port: 0 }, () => { + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket' - }, - host: '127.0.0.1', - port + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 400); wss.close(done); }); - - req.end(); }); wss.on('connection', (ws) => { @@ -403,23 +411,20 @@ describe('WebSocketServer', function () { }); it('does not accept connections with no sec-websocket-version', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const req = http.request({ + const wss = new WebSocket.Server({ port: 0 }, () => { + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==' - }, - host: '127.0.0.1', - port + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 400); wss.close(done); }); - - req.end(); }); wss.on('connection', (ws) => { @@ -428,24 +433,21 @@ describe('WebSocketServer', function () { }); it('does not accept connections with invalid sec-websocket-version', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const req = http.request({ + const wss = new WebSocket.Server({ port: 0 }, () => { + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 12 - }, - host: '127.0.0.1', - port + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 400); wss.close(done); }); - - req.end(); }); wss.on('connection', (ws) => { @@ -454,28 +456,24 @@ describe('WebSocketServer', function () { }); it('client can be denied', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (o) => false, - port: ++port + port: 0 }, () => { - const req = http.request({ + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8, - 'Sec-WebSocket-Origin': 'http://foobar.com' - }, - host: '127.0.0.1', - port + 'Sec-WebSocket-Version': 8 + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 401); wss.close(done); }); - - req.end(); }); wss.on('connection', (ws) => { @@ -484,23 +482,19 @@ describe('WebSocketServer', function () { }); it('client can be accepted', function (done) { - const wss = new WebSocketServer({ - port: ++port, + const wss = new WebSocket.Server({ + port: 0, verifyClient: (o) => true }, () => { - const req = http.request({ + http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobar.com' - }, - host: '127.0.0.1', - port + 'Sec-WebSocket-Version': 13 + } }); - - req.end(); }); wss.on('connection', (ws) => wss.close(done)); @@ -508,38 +502,35 @@ describe('WebSocketServer', function () { it('verifyClient gets client origin', function (done) { let verifyClientCalled = false; - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (info) => { assert.strictEqual(info.origin, 'http://foobarbaz.com'); verifyClientCalled = true; return false; }, - port: ++port + port: 0 }, () => { - const req = http.request({ + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13, 'Origin': 'http://foobarbaz.com' - }, - host: '127.0.0.1', - port + } }); req.on('response', (res) => { assert.ok(verifyClientCalled); wss.close(done); }); - - req.end(); }); }); it('verifyClient gets original request', function (done) { let verifyClientCalled = false; - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (info) => { assert.strictEqual( info.req.headers['sec-websocket-key'], @@ -548,26 +539,22 @@ describe('WebSocketServer', function () { verifyClientCalled = true; return false; }, - port: ++port + port: 0 }, () => { - const req = http.request({ + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobarbaz.com' - }, - host: '127.0.0.1', - port + 'Sec-WebSocket-Version': 13 + } }); req.on('response', (res) => { assert.ok(verifyClientCalled); wss.close(done); }); - - req.end(); }); }); @@ -578,7 +565,7 @@ describe('WebSocketServer', function () { }); let success = false; - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (info) => { success = info.secure === true; return true; @@ -592,16 +579,18 @@ describe('WebSocketServer', function () { server.close(done); }); - server.listen(++port, () => new WebSocket(`wss://localhost:${port}`, { - rejectUnauthorized: false - })); + server.listen(0, () => { + const ws = new WebSocket(`wss://localhost:${server.address().port}`, { + rejectUnauthorized: false + }); + }); }); it('verifyClient has secure:false for non-ssl connections', function (done) { const server = http.createServer(); let success = false; - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ server: server, verifyClient: (info) => { success = info.secure === false; @@ -615,34 +604,30 @@ describe('WebSocketServer', function () { server.close(done); }); - server.listen(++port, () => { - const ws = new WebSocket(`ws://localhost:${port}`); + server.listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`); }); }); it('client can be denied asynchronously', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (o, cb) => process.nextTick(cb, false), - port: ++port + port: 0 }, () => { - const req = http.request({ + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8, - 'Sec-WebSocket-Origin': 'http://foobar.com' - }, - host: '127.0.0.1', - port + 'Sec-WebSocket-Version': 8 + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 401); wss.close(done); }); - - req.end(); }); wss.on('connection', (ws) => { @@ -651,28 +636,24 @@ describe('WebSocketServer', function () { }); it('client can be denied asynchronously with custom response code', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (o, cb) => process.nextTick(cb, false, 404), - port: ++port + port: 0 }, () => { - const req = http.request({ + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8, - 'Sec-WebSocket-Origin': 'http://foobar.com' - }, - host: '127.0.0.1', - port + 'Sec-WebSocket-Version': 8 + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 404); wss.close(done); }); - - req.end(); }); wss.on('connection', (ws) => { @@ -681,23 +662,19 @@ describe('WebSocketServer', function () { }); it('client can be accepted asynchronously', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ verifyClient: (o, cb) => process.nextTick(cb, true), - port: ++port + port: 0 }, () => { - const req = http.request({ + http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobar.com' - }, - host: '127.0.0.1', - port + 'Sec-WebSocket-Version': 13 + } }); - - req.end(); }); wss.on('connection', (ws) => wss.close(done)); @@ -706,8 +683,8 @@ describe('WebSocketServer', function () { it('doesn\'t emit the `connection` event if socket is closed prematurely', function (done) { const server = http.createServer(); - server.listen(++port, () => { - const wss = new WebSocketServer({ + server.listen(0, () => { + const wss = new WebSocket.Server({ verifyClient: (o, cb) => setTimeout(cb, 100, true), server }); @@ -716,7 +693,7 @@ describe('WebSocketServer', function () { throw new Error('connection event emitted'); }); - const socket = net.connect({ host: 'localhost', port }, () => { + const socket = net.connect({ port: server.address().port }, () => { socket.write([ 'GET / HTTP/1.1', 'Host: localhost', @@ -739,17 +716,15 @@ describe('WebSocketServer', function () { }); it('handles messages passed along with the upgrade request (upgrade head)', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.request({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobar.com' - }, - host: '127.0.0.1', - port + 'Sec-WebSocket-Version': 13 + } }); req.write(Buffer.from([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f])); @@ -765,7 +740,8 @@ describe('WebSocketServer', function () { }); it('selects the first protocol by default', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); ws.on('open', () => { @@ -781,7 +757,8 @@ describe('WebSocketServer', function () { assert.strictEqual(request.url, '/'); return protocols.pop(); }; - const wss = new WebSocketServer({ handleProtocols, port: ++port }, () => { + const wss = new WebSocket.Server({ handleProtocols, port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); ws.on('open', () => { @@ -792,10 +769,11 @@ describe('WebSocketServer', function () { }); it('client detects invalid server protocol', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ handleProtocols: (ps) => 'prot3', - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); ws.on('open', () => done(new Error('connection must not be established'))); @@ -804,10 +782,11 @@ describe('WebSocketServer', function () { }); it('client detects no server protocol', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ handleProtocols: (ps) => {}, - port: ++port + port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); ws.on('open', () => done(new Error('connection must not be established'))); @@ -816,53 +795,47 @@ describe('WebSocketServer', function () { }); it('server detects unauthorized protocol handler', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ handleProtocols: (ps) => false, - port: ++port + port: 0 }, () => { - const req = http.request({ + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobar.com' - }, - host: '127.0.0.1', - port + 'Sec-WebSocket-Version': 13 + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 401); wss.close(done); }); - - req.end(); }); }); it('accept connections with sec-websocket-extensions', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const req = http.request({ + const wss = new WebSocket.Server({ port: 0 }, () => { + http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13, 'Sec-WebSocket-Extensions': 'permessage-foo; x=10' - }, - host: '127.0.0.1', - port + } }); - - req.end(); }); wss.on('connection', (ws) => wss.close(done)); }); it('emits the `headers` event', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); wss.on('headers', (headers, request) => { @@ -889,7 +862,8 @@ describe('WebSocketServer', function () { } data = data.join(''); - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('message', (message) => ws.send(message)); @@ -908,7 +882,8 @@ describe('WebSocketServer', function () { describe('client properties', function () { it('protocol is exposed', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, 'hi'); }); @@ -919,8 +894,11 @@ describe('WebSocketServer', function () { }); it('protocolVersion is exposed', function (done) { - const wss = new WebSocketServer({ port: ++port }, () => { - const ws = new WebSocket(`ws://localhost:${port}`, { protocolVersion: 8 }); + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`, { + protocolVersion: 8 + }); }); wss.on('connection', (client) => { @@ -932,51 +910,47 @@ describe('WebSocketServer', function () { describe('permessage-deflate', function () { it('accept connections with permessage-deflate extension', function (done) { - const wss = new WebSocketServer({ + const wss = new WebSocket.Server({ perMessageDeflate: true, - port: ++port + port: 0 }, () => { - const req = http.request({ + http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits=8; server_max_window_bits=8; client_no_context_takeover; server_no_context_takeover' - }, - host: '127.0.0.1', - port + 'Sec-WebSocket-Extensions': 'permessage-deflate; ' + + 'client_max_window_bits=8; server_max_window_bits=8; ' + + 'client_no_context_takeover; server_no_context_takeover' + } }); - - req.end(); }); wss.on('connection', (ws) => wss.close(done)); }); - it('does not accept connections with not defined extension parameter', function (done) { - const wss = new WebSocketServer({ + it('does not accept connections with invalid extension parameters (name)', function (done) { + const wss = new WebSocket.Server({ perMessageDeflate: true, - port: ++port + port: 0 }, () => { - const req = http.request({ + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13, 'Sec-WebSocket-Extensions': 'permessage-deflate; foo=15' - }, - host: '127.0.0.1', - port + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 400); wss.close(done); }); - - req.end(); }); wss.on('connection', (ws) => { @@ -984,29 +958,26 @@ describe('WebSocketServer', function () { }); }); - it('does not accept connections with invalid extension parameter', function (done) { - const wss = new WebSocketServer({ + it('does not accept connections with invalid extension parameters (value)', function (done) { + const wss = new WebSocket.Server({ perMessageDeflate: true, - port: ++port + port: 0 }, () => { - const req = http.request({ + const req = http.get({ + port: wss._server.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13, 'Sec-WebSocket-Extensions': 'permessage-deflate; server_max_window_bits=foo' - }, - host: '127.0.0.1', - port + } }); req.on('response', (res) => { assert.strictEqual(res.statusCode, 400); wss.close(done); }); - - req.end(); }); wss.on('connection', (ws) => { From 46b25476b13fdd24e1ab2c85d304b05bea75287e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 21 Nov 2017 07:42:24 +0100 Subject: [PATCH 423/669] [dist] 3.3.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c20eb812e..2fe112887 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "3.3.1", + "version": "3.3.2", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 355d24a2db2c32a8ed7b2b820ddf60e511f26d7c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 21 Nov 2017 09:29:32 +0100 Subject: [PATCH 424/669] [doc] Add direct links to the Autobahn test suite reports --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 88c480d78..e2cc3bd38 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ ws is a simple to use, blazing fast, and thoroughly tested WebSocket client and server implementation. -Passes the quite extensive Autobahn test suite. See http://websockets.github.io/ws/ -for the full reports. +Passes the quite extensive Autobahn test suite: [server][server-report], +[client][client-report]. **Note**: This module does not work in the browser. The client in the docs is a reference to a back end with the role of a client in the WebSocket @@ -335,5 +335,7 @@ We're using the GitHub [releases][changelog] for changelog entries. [https-proxy-agent]: https://github.com/TooTallNate/node-https-proxy-agent [socks-proxy-agent]: https://github.com/TooTallNate/node-socks-proxy-agent +[client-report]: http://websockets.github.io/ws/autobahn/clients/ +[server-report]: http://websockets.github.io/ws/autobahn/servers/ [permessage-deflate]: https://tools.ietf.org/html/rfc7692 [changelog]: https://github.com/websockets/ws/releases From 5ccb86e4a880ed139bb5ef141753c10451c318b2 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 24 Nov 2017 21:57:30 +0100 Subject: [PATCH 425/669] [test] Simplify a test --- test/WebSocket.test.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 0cad5ddac..fef3b2fb7 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -2183,7 +2183,7 @@ describe('WebSocket', function () { }); }); - it('can call during receiving data', function (done) { + it('can be used while data is being processed', function (done) { const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, port: 0 @@ -2198,9 +2198,8 @@ describe('WebSocket', function () { client.send('hi'); } client.send('hi', () => { - ws.extensions['permessage-deflate']._inflate.on('close', () => { - wss.close(done); - }); + assert.strictEqual(ws._receiver._state, 5); + ws.on('close', () => wss.close(done)); ws.terminate(); }); }); From ac86a04bafc92207d5f57b532a8226fe33b3489a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 25 Nov 2017 16:44:02 +0100 Subject: [PATCH 426/669] [minor] Refactor `WebSocket#finalize()` Clear the close timer only when it could be set. --- lib/WebSocket.js | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 76e42c078..341dcd71b 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -55,9 +55,9 @@ class WebSocket extends EventEmitter { this._binaryType = constants.BINARY_TYPES[0]; this._finalize = this.finalize.bind(this); - this._finalizeCalled = false; this._closeMessage = null; this._closeTimer = null; + this._finalized = false; this._closeCode = null; this._receiver = null; this._sender = null; @@ -164,17 +164,14 @@ class WebSocket extends EventEmitter { /** * Clean up and release internal resources. * - * @param {(Boolean|Error)} Indicates whether or not an error occurred + * @param {(Boolean|Error)} error Indicates whether or not an error occurred * @private */ finalize (error) { - if (this._finalizeCalled) return; + if (this._finalized) return; this.readyState = WebSocket.CLOSING; - this._finalizeCalled = true; - - clearTimeout(this._closeTimer); - this._closeTimer = null; + this._finalized = true; // // If the connection was closed abnormally (with an error), or if the close @@ -183,24 +180,24 @@ class WebSocket extends EventEmitter { // if (error) this._closeCode = 1006; - if (this._socket) { - this._ultron.destroy(); - this._socket.on('error', function onerror () { - this.destroy(); - }); + if (!this._socket) return this.emitClose(); - if (!error) this._socket.end(); - else this._socket.destroy(); + clearTimeout(this._closeTimer); + this._closeTimer = null; - this._receiver.cleanup(() => this.emitClose()); + this._ultron.destroy(); + this._ultron = null; - this._receiver = null; - this._sender = null; - this._socket = null; - this._ultron = null; - } else { - this.emitClose(); - } + this._socket.on('error', constants.NOOP); + + if (!error) this._socket.end(); + else this._socket.destroy(); + + this._socket = null; + this._sender = null; + + this._receiver.cleanup(() => this.emitClose()); + this._receiver = null; } /** From 81cd85b85c1057659e7a15d8c83ab1fbcfbdef78 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 26 Nov 2017 07:47:40 +0100 Subject: [PATCH 427/669] chore(package): update eslint to version 4.12.0 (#1242) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2fe112887..6720fabe8 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.11.0", + "eslint": "~4.12.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~5.2.0", From 4e7d48aafec4ad85958e4c5171db2fd2b0e85ea4 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 28 Nov 2017 12:02:11 +0100 Subject: [PATCH 428/669] [fix] Close cleanly only if a close frame has been sent and received --- lib/WebSocket.js | 69 +++++++++++++++++--------- test/WebSocket.test.js | 110 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 22 deletions(-) diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 341dcd71b..72a62e56f 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -55,6 +55,7 @@ class WebSocket extends EventEmitter { this._binaryType = constants.BINARY_TYPES[0]; this._finalize = this.finalize.bind(this); + this._closeFrameSent = false; this._closeMessage = null; this._closeTimer = null; this._finalized = false; @@ -149,7 +150,7 @@ class WebSocket extends EventEmitter { this._receiver.onclose = (code, reason) => { this._closeMessage = reason; this._closeCode = code; - this.close(code, reason); + if (!this._finalized) this.close(code, reason); }; this._receiver.onerror = (error, code) => { // close the connection when the receiver reports a HyBi error code @@ -173,14 +174,7 @@ class WebSocket extends EventEmitter { this.readyState = WebSocket.CLOSING; this._finalized = true; - // - // If the connection was closed abnormally (with an error), or if the close - // control frame was malformed or not received then the close code must be - // 1006. - // - if (error) this._closeCode = 1006; - - if (!this._socket) return this.emitClose(); + if (!this._socket) return this.emitClose(error); clearTimeout(this._closeTimer); this._closeTimer = null; @@ -196,17 +190,28 @@ class WebSocket extends EventEmitter { this._socket = null; this._sender = null; - this._receiver.cleanup(() => this.emitClose()); + this._receiver.cleanup(() => this.emitClose(error)); this._receiver = null; } /** * Emit the `close` event. * + * @param {(Boolean|Error)} error Indicates whether or not an error occurred * @private */ - emitClose () { + emitClose (error) { this.readyState = WebSocket.CLOSED; + + // + // If the connection was closed abnormally (with an error), or if the close + // control frame was not sent or received then the close code must be 1006. + // + if (error || !this._closeFrameSent) { + this._closeMessage = ''; + this._closeCode = 1006; + } + this.emit('close', this._closeCode || 1006, this._closeMessage || ''); if (this.extensions[PerMessageDeflate.extensionName]) { @@ -216,7 +221,6 @@ class WebSocket extends EventEmitter { this.extensions = null; this.removeAllListeners(); - this.on('error', constants.NOOP); // Catch all errors after this. } /** @@ -244,6 +248,22 @@ class WebSocket extends EventEmitter { /** * Start a closing handshake. * + * +----------+ +-----------+ +----------+ + * + - - -|ws.close()|---->|close frame|-->|ws.close()|- - - - + * +----------+ +-----------+ +----------+ | + * | +----------+ +-----------+ | + * |ws.close()|<----|close frame|<--------+ | + * +----------+ +-----------+ | + * CLOSING | +---+ | CLOSING + * | +---|fin|<------------+ + * | | | +---+ | + * | | +---+ +-------------+ + * | +----------+-->|fin|----->|ws.finalize()| - - + + * | +---+ +-------------+ + * | +-------------+ | + * - - -|ws.finalize()|<--+ + * +-------------+ + * * @param {Number} code Status code explaining why the connection is closing * @param {String} data A string explaining why the connection is closing * @public @@ -260,23 +280,28 @@ class WebSocket extends EventEmitter { } if (this.readyState === WebSocket.CLOSING) { - if (this._closeCode && this._socket) this._socket.end(); + if (this._closeFrameSent && this._closeCode) this._socket.end(); return; } this.readyState = WebSocket.CLOSING; this._sender.close(code, data, !this._isServer, (err) => { - if (err) this.emit('error', err); + if (this._finalized) return; - if (this._socket) { - if (this._closeCode) this._socket.end(); - // - // Ensure that the connection is cleaned up even when the closing - // handshake fails. - // - clearTimeout(this._closeTimer); - this._closeTimer = setTimeout(this._finalize, closeTimeout, true); + if (err) { + this.emit('error', err); + this.finalize(true); + return; } + + if (this._closeCode) this._socket.end(); + this._closeFrameSent = true; + + // + // Ensure that the connection is cleaned up even when the closing + // handshake fails. + // + this._closeTimer = setTimeout(this._finalize, closeTimeout, true); }); } diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index fef3b2fb7..178dbfd15 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -7,6 +7,7 @@ const assert = require('assert'); const crypto = require('crypto'); const https = require('https'); const http = require('http'); +const net = require('net'); const fs = require('fs'); const constants = require('../lib/Constants'); @@ -1146,6 +1147,45 @@ describe('WebSocket', function () { }); }); + it('emits an error if the close frame can not be sent', function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; + const socket = net.createConnection(port, () => { + socket.write( + 'GET / HTTP/1.1\r\n' + + 'Host: localhost\r\n' + + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + 'Sec-WebSocket-Key: qqFVFwaCnSMXiqfezY/AZQ==\r\n' + + 'Sec-WebSocket-Version: 13\r\n' + + '\r\n' + ); + socket.destroy(); + }); + + wss.on('connection', (ws) => { + // + // Remove our `'error'` listener from the socket to ensure that + // `WebSocket#finalize()` is not called before the `Sender#close()` + // callback. + // + const listeners = ws._socket.listeners('error'); + ws._socket.removeListener('error', listeners[listeners.length - 1]); + + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.ok(err.message.startsWith('write E')); + ws.on('close', (code, message) => { + assert.strictEqual(message, ''); + assert.strictEqual(code, 1006); + wss.close(done); + }); + }); + ws.close(); + }); + }); + }); + it('works when close reason is not specified', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; @@ -1221,6 +1261,7 @@ describe('WebSocket', function () { ws.send('bar'); ws.send('baz'); ws.close(); + ws.close(); }); }); @@ -1366,6 +1407,75 @@ describe('WebSocket', function () { }); }); + describe('abnormal closures', function () { + it('ignores close frame data if connection is forcibly closed', function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); + const emitClose = ws.emitClose; + const messages = []; + + ws.on('message', (message) => { + messages.push(message); + if (messages.length > 1) ws.terminate(); + }); + + ws.emitClose = (error) => { + assert.deepStrictEqual(messages, ['', '', '']); + assert.strictEqual(ws._closeMessage, 'foo'); + assert.strictEqual(ws._closeCode, 4000); + assert.strictEqual(error, true); + emitClose.call(ws, error); + }; + + ws.on('close', (code, message) => { + assert.strictEqual(message, ''); + assert.strictEqual(code, 1006); + wss.close(done); + }); + }); + + wss.on('connection', (ws) => { + const buf = Buffer.from('81008100810088050fa0666f6f', 'hex'); + ws._socket.write(buf); + }); + }); + + it('closes with 1006 if close frame is received but not sent', function (done) { + const wss = new WebSocket.Server({ + perMessageDeflate: { threshold: 0 }, + port: 0 + }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); + const emitClose = ws.emitClose; + const messages = []; + + ws.on('message', (message) => messages.push(message)); + + ws.emitClose = (error) => { + assert.deepStrictEqual(messages, ['', '', '']); + assert.strictEqual(ws._closeFrameSent, false); + assert.strictEqual(ws._closeMessage, 'foo'); + assert.strictEqual(ws._closeCode, 4000); + assert.strictEqual(error, undefined); + emitClose.call(ws, error); + }; + + ws.on('close', (code, message) => { + assert.strictEqual(message, ''); + assert.strictEqual(code, 1006); + wss.close(done); + }); + }); + + wss.on('connection', (ws) => { + const buf = Buffer.from('c10100c10100c1010088050fa0666f6f', 'hex'); + ws._socket.end(buf); + }); + }); + }); + describe('WHATWG API emulation', function () { it('should not throw errors when getting and setting', function () { const listener = () => {}; From 009d05c4384b5fe4762decaa6e37592d8885fafe Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 1 Dec 2017 10:12:03 +0100 Subject: [PATCH 429/669] [test] Mark skipped tests as pending --- test/WebSocket.test.js | 22 ++++++++++++++++------ test/WebSocketServer.test.js | 2 +- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 178dbfd15..302ac493c 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -71,9 +71,14 @@ describe('WebSocket', function () { }); ws.on('error', (err) => { - // Skip this test on machines where 127.0.0.2 is disabled. - if (err.code === 'EADDRNOTAVAIL') err = undefined; - wss.close(() => done(err)); + wss.close(() => { + // + // Skip this test on machines where 127.0.0.2 is disabled. + // + if (err.code === 'EADDRNOTAVAIL') return this.skip(); + + done(err); + }); }); }); @@ -101,9 +106,14 @@ describe('WebSocket', function () { }); wss.on('error', (err) => { - // Skip this test on machines where IPv6 is not supported. - if (err.code === 'EADDRNOTAVAIL') err = undefined; - wss.close(() => done(err)); + wss.close(() => { + // + // Skip this test on machines where IPv6 is not supported. + // + if (err.code === 'EADDRNOTAVAIL') return this.skip(); + + done(err); + }); }); wss.on('connection', (ws, req) => { diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 5fca819bb..c367d6117 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -83,7 +83,7 @@ describe('WebSocketServer', function () { // // Skip this test on Windows as it throws errors for obvious reasons. // - if (process.platform === 'win32') return done(); + if (process.platform === 'win32') return this.skip(); const server = http.createServer(); const sockPath = `/tmp/ws.${crypto.randomBytes(16).toString('hex')}.socket`; From a166af4c50ab7092c0bc64c42579ed2c34b49cc5 Mon Sep 17 00:00:00 2001 From: dcharbonnier Date: Fri, 1 Dec 2017 10:53:26 +0100 Subject: [PATCH 430/669] [test] Skip `family` test if localhost doesn't resolve to ::1 (#1246) --- test/WebSocket.test.js | 43 ++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 302ac493c..70df7c101 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -6,9 +6,11 @@ const safeBuffer = require('safe-buffer'); const assert = require('assert'); const crypto = require('crypto'); const https = require('https'); +const dns = require('dns'); const http = require('http'); const net = require('net'); const fs = require('fs'); +const os = require('os'); const constants = require('../lib/Constants'); const WebSocket = require('..'); @@ -100,25 +102,38 @@ describe('WebSocket', function () { }); it('accepts the family option', function (done) { - const wss = new WebSocket.Server({ host: '::1', port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 }); + const re = process.platform === 'win32' ? /Loopback Pseudo-Interface/ : /lo/; + const ifaces = os.networkInterfaces(); + const hasIPv6 = Object.keys(ifaces).some((name) => { + return re.test(name) && ifaces[name].some((info) => info.family === 'IPv6'); }); - wss.on('error', (err) => { - wss.close(() => { - // - // Skip this test on machines where IPv6 is not supported. - // - if (err.code === 'EADDRNOTAVAIL') return this.skip(); + // + // Skip this test on machines where IPv6 is not supported. + // + if (!hasIPv6) return this.skip(); + + dns.lookup('localhost', { family: 6, all: true }, (err, addresses) => { + // + // Skip this test if localhost does not resolve to ::1. + // + if (err) { + return err.code === 'ENOTFOUND' || err.code === 'EAI_AGAIN' + ? this.skip() + : done(err); + } + + if (!addresses.some((val) => val.address === '::1')) return this.skip(); - done(err); + const wss = new WebSocket.Server({ host: '::1', port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 }); }); - }); - wss.on('connection', (ws, req) => { - assert.strictEqual(req.connection.remoteAddress, '::1'); - wss.close(done); + wss.on('connection', (ws, req) => { + assert.strictEqual(req.connection.remoteAddress, '::1'); + wss.close(done); + }); }); }); }); From b3bc7dbb881605e1717baf6d99220c1a6c3bb280 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 1 Dec 2017 12:02:45 +0100 Subject: [PATCH 431/669] [minor] Do not set `allowHalfOpen` to `false` Use the default value when the HTTP server is created internally. --- lib/WebSocketServer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 571078f80..8ebd6bf81 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -73,7 +73,6 @@ class WebSocketServer extends EventEmitter { }); res.end(body); }); - this._server.allowHalfOpen = false; this._server.listen(options.port, options.host, options.backlog, callback); } else if (options.server) { this._server = options.server; From f6e56855ab213510ab058b1171cffba3dce0f701 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 3 Dec 2017 13:40:27 +0100 Subject: [PATCH 432/669] chore(package): update utf-8-validate to version 4.0.0 (#1247) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6720fabe8..b87e5ec2b 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,6 @@ "eslint-plugin-standard": "~3.0.0", "mocha": "~4.0.0", "nyc": "~11.3.0", - "utf-8-validate": "~3.0.0" + "utf-8-validate": "~4.0.0" } } From ca76e58f4a969618319e1993e3072f406679cde9 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 9 Dec 2017 07:06:22 +0100 Subject: [PATCH 433/669] chore(package): update eslint to version 4.13.0 (#1249) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b87e5ec2b..76af690e0 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.12.0", + "eslint": "~4.13.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~5.2.0", From ae903b1ab2d2f0a9e1b41e849317977e49dcf789 Mon Sep 17 00:00:00 2001 From: George Date: Sat, 9 Dec 2017 11:40:32 -0500 Subject: [PATCH 434/669] [doc] Fix rendering of history in SECURITY.md (#1250) --- SECURITY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index e8a5b70fb..8e063cca5 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -30,5 +30,5 @@ all vulnerabilities to the [Node Security Project](https://nodesecurity.io/). ## History -04 Jan 2016: [Buffer vulnerablity](https://github.com/websockets/ws/releases/tag/1.0.1) -08 Nov 2017: [DoS vulnerablity](https://github.com/websockets/ws/releases/tag/3.3.1) +- 04 Jan 2016: [Buffer vulnerability](https://github.com/websockets/ws/releases/tag/1.0.1) +- 08 Nov 2017: [DoS vulnerability](https://github.com/websockets/ws/releases/tag/3.3.1) From beff62081d5591c4f1beb9a3993b24ea7473d792 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 10 Dec 2017 14:50:10 +0100 Subject: [PATCH 435/669] [fix] Use status code from close frame if received --- lib/EventTarget.js | 2 +- lib/WebSocket.js | 102 +++++++++----------- test/WebSocket.test.js | 174 +++++------------------------------ test/WebSocketServer.test.js | 46 +++++---- 4 files changed, 97 insertions(+), 227 deletions(-) diff --git a/lib/EventTarget.js b/lib/EventTarget.js index 9d0461a18..c48137b91 100644 --- a/lib/EventTarget.js +++ b/lib/EventTarget.js @@ -55,7 +55,7 @@ class CloseEvent extends Event { constructor (code, reason, target) { super('close', target); - this.wasClean = code === undefined || code === 1000 || (code >= 3000 && code <= 4999); + this.wasClean = target._closeFrameReceived && target._closeFrameSent; this.reason = reason; this.code = code; } diff --git a/lib/WebSocket.js b/lib/WebSocket.js index 72a62e56f..a8c7b99b1 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -55,11 +55,12 @@ class WebSocket extends EventEmitter { this._binaryType = constants.BINARY_TYPES[0]; this._finalize = this.finalize.bind(this); + this._closeFrameReceived = false; this._closeFrameSent = false; - this._closeMessage = null; + this._closeMessage = ''; this._closeTimer = null; this._finalized = false; - this._closeCode = null; + this._closeCode = 1006; this._receiver = null; this._sender = null; this._socket = null; @@ -126,21 +127,17 @@ class WebSocket extends EventEmitter { this._ultron = new Ultron(socket); this._socket = socket; - // socket cleanup handlers this._ultron.on('close', this._finalize); this._ultron.on('error', this._finalize); this._ultron.on('end', this._finalize); - // ensure that the head is added to the receiver if (head.length > 0) socket.unshift(head); - // subsequent packets are pushed to the receiver this._ultron.on('data', (data) => { this.bytesReceived += data.length; this._receiver.add(data); }); - // receiver event handlers this._receiver.onmessage = (data) => this.emit('message', data); this._receiver.onping = (data) => { this.pong(data, !this._isServer, true); @@ -148,14 +145,22 @@ class WebSocket extends EventEmitter { }; this._receiver.onpong = (data) => this.emit('pong', data); this._receiver.onclose = (code, reason) => { + this._closeFrameReceived = true; this._closeMessage = reason; this._closeCode = code; if (!this._finalized) this.close(code, reason); }; this._receiver.onerror = (error, code) => { - // close the connection when the receiver reports a HyBi error code - this.close(code, ''); + this._closeMessage = ''; + this._closeCode = code; + + // + // Ensure that the error is emitted even if `WebSocket#finalize()` has + // already been called. + // + this.readyState = WebSocket.CLOSING; this.emit('error', error); + this.finalize(true); }; this.readyState = WebSocket.OPEN; @@ -174,7 +179,8 @@ class WebSocket extends EventEmitter { this.readyState = WebSocket.CLOSING; this._finalized = true; - if (!this._socket) return this.emitClose(error); + if (typeof error === 'object') this.emit('error', error); + if (!this._socket) return this.emitClose(); clearTimeout(this._closeTimer); this._closeTimer = null; @@ -190,29 +196,19 @@ class WebSocket extends EventEmitter { this._socket = null; this._sender = null; - this._receiver.cleanup(() => this.emitClose(error)); + this._receiver.cleanup(() => this.emitClose()); this._receiver = null; } /** * Emit the `close` event. * - * @param {(Boolean|Error)} error Indicates whether or not an error occurred * @private */ - emitClose (error) { + emitClose () { this.readyState = WebSocket.CLOSED; - // - // If the connection was closed abnormally (with an error), or if the close - // control frame was not sent or received then the close code must be 1006. - // - if (error || !this._closeFrameSent) { - this._closeMessage = ''; - this._closeCode = 1006; - } - - this.emit('close', this._closeCode || 1006, this._closeMessage || ''); + this.emit('close', this._closeCode, this._closeMessage); if (this.extensions[PerMessageDeflate.extensionName]) { this.extensions[PerMessageDeflate.extensionName].cleanup(); @@ -271,37 +267,35 @@ class WebSocket extends EventEmitter { close (code, data) { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { - if (this._req && !this._req.aborted) { - this._req.abort(); - this.emit('error', new Error('closed before the connection is established')); - this.finalize(true); - } + this._req.abort(); + this.finalize(new Error('closed before the connection is established')); return; } if (this.readyState === WebSocket.CLOSING) { - if (this._closeFrameSent && this._closeCode) this._socket.end(); + if (this._closeFrameSent && this._closeFrameReceived) this._socket.end(); return; } this.readyState = WebSocket.CLOSING; this._sender.close(code, data, !this._isServer, (err) => { - if (this._finalized) return; - - if (err) { - this.emit('error', err); - this.finalize(true); - return; - } + // + // This error is handled by the `'error'` listener on the socket. We only + // want to know if the close frame has been sent here. + // + if (err) return; - if (this._closeCode) this._socket.end(); this._closeFrameSent = true; - // - // Ensure that the connection is cleaned up even when the closing - // handshake fails. - // - this._closeTimer = setTimeout(this._finalize, closeTimeout, true); + if (!this._finalized) { + if (this._closeFrameReceived) this._socket.end(); + + // + // Ensure that the connection is cleaned up even when the closing + // handshake fails. + // + this._closeTimer = setTimeout(this._finalize, closeTimeout, true); + } }); } @@ -391,11 +385,8 @@ class WebSocket extends EventEmitter { terminate () { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { - if (this._req && !this._req.aborted) { - this._req.abort(); - this.emit('error', new Error('closed before the connection is established')); - this.finalize(true); - } + this._req.abort(); + this.finalize(new Error('closed before the connection is established')); return; } @@ -645,8 +636,7 @@ function initAsClient (address, protocols, options) { if (options.handshakeTimeout) { this._req.setTimeout(options.handshakeTimeout, () => { this._req.abort(); - this.emit('error', new Error('opening handshake has timed out')); - this.finalize(true); + this.finalize(new Error('opening handshake has timed out')); }); } @@ -654,15 +644,13 @@ function initAsClient (address, protocols, options) { if (this._req.aborted) return; this._req = null; - this.emit('error', error); - this.finalize(true); + this.finalize(error); }); this._req.on('response', (res) => { if (!this.emit('unexpected-response', this._req, res)) { this._req.abort(); - this.emit('error', new Error(`unexpected server response (${res.statusCode})`)); - this.finalize(true); + this.finalize(new Error(`unexpected server response (${res.statusCode})`)); } }); @@ -683,8 +671,7 @@ function initAsClient (address, protocols, options) { if (res.headers['sec-websocket-accept'] !== digest) { socket.destroy(); - this.emit('error', new Error('invalid server key')); - return this.finalize(true); + return this.finalize(new Error('invalid server key')); } const serverProt = res.headers['sec-websocket-protocol']; @@ -701,8 +688,7 @@ function initAsClient (address, protocols, options) { if (protError) { socket.destroy(); - this.emit('error', new Error(protError)); - return this.finalize(true); + return this.finalize(new Error(protError)); } if (serverProt) this.protocol = serverProt; @@ -721,8 +707,8 @@ function initAsClient (address, protocols, options) { } } catch (err) { socket.destroy(); - this.emit('error', new Error('invalid Sec-WebSocket-Extensions header')); - return this.finalize(true); + this.finalize(new Error('invalid Sec-WebSocket-Extensions header')); + return; } } diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 70df7c101..32a4245b8 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -6,8 +6,8 @@ const safeBuffer = require('safe-buffer'); const assert = require('assert'); const crypto = require('crypto'); const https = require('https'); -const dns = require('dns'); const http = require('http'); +const dns = require('dns'); const net = require('net'); const fs = require('fs'); const os = require('os'); @@ -1189,14 +1189,6 @@ describe('WebSocket', function () { }); wss.on('connection', (ws) => { - // - // Remove our `'error'` listener from the socket to ensure that - // `WebSocket#finalize()` is not called before the `Sender#close()` - // callback. - // - const listeners = ws._socket.listeners('error'); - ws._socket.removeListener('error', listeners[listeners.length - 1]); - ws.on('error', (err) => { assert.ok(err instanceof Error); assert.ok(err.message.startsWith('write E')); @@ -1304,39 +1296,6 @@ describe('WebSocket', function () { wss.on('connection', (ws) => ws.close(1013)); }); - it('closes the connection when an error occurs', function (done) { - const server = http.createServer(); - const wss = new WebSocket.Server({ server }); - let closed = false; - - wss.on('connection', (ws) => { - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'RSV2 and RSV3 must be clear'); - - ws.on('close', (code, reason) => { - assert.strictEqual(code, 1006); - assert.strictEqual(reason, ''); - - closed = true; - }); - }); - }); - - server.listen(0, () => { - const ws = new WebSocket(`ws://localhost:${server.address().port}`); - - ws.on('open', () => ws._socket.write(Buffer.from([0xa1, 0x00]))); - ws.on('close', (code, reason) => { - assert.strictEqual(code, 1002); - assert.strictEqual(reason, ''); - assert.ok(closed); - - server.close(done); - }); - }); - }); - it('does nothing if the connection is already CLOSED', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; @@ -1432,75 +1391,6 @@ describe('WebSocket', function () { }); }); - describe('abnormal closures', function () { - it('ignores close frame data if connection is forcibly closed', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - const emitClose = ws.emitClose; - const messages = []; - - ws.on('message', (message) => { - messages.push(message); - if (messages.length > 1) ws.terminate(); - }); - - ws.emitClose = (error) => { - assert.deepStrictEqual(messages, ['', '', '']); - assert.strictEqual(ws._closeMessage, 'foo'); - assert.strictEqual(ws._closeCode, 4000); - assert.strictEqual(error, true); - emitClose.call(ws, error); - }; - - ws.on('close', (code, message) => { - assert.strictEqual(message, ''); - assert.strictEqual(code, 1006); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => { - const buf = Buffer.from('81008100810088050fa0666f6f', 'hex'); - ws._socket.write(buf); - }); - }); - - it('closes with 1006 if close frame is received but not sent', function (done) { - const wss = new WebSocket.Server({ - perMessageDeflate: { threshold: 0 }, - port: 0 - }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - const emitClose = ws.emitClose; - const messages = []; - - ws.on('message', (message) => messages.push(message)); - - ws.emitClose = (error) => { - assert.deepStrictEqual(messages, ['', '', '']); - assert.strictEqual(ws._closeFrameSent, false); - assert.strictEqual(ws._closeMessage, 'foo'); - assert.strictEqual(ws._closeCode, 4000); - assert.strictEqual(error, undefined); - emitClose.call(ws, error); - }; - - ws.on('close', (code, message) => { - assert.strictEqual(message, ''); - assert.strictEqual(code, 1006); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => { - const buf = Buffer.from('c10100c10100c1010088050fa0666f6f', 'hex'); - ws._socket.end(buf); - }); - }); - }); - describe('WHATWG API emulation', function () { it('should not throw errors when getting and setting', function () { const listener = () => {}; @@ -1677,6 +1567,7 @@ describe('WebSocket', function () { ws.addEventListener('close', (closeEvent) => { assert.ok(closeEvent.wasClean); + assert.strictEqual(closeEvent.reason, ''); assert.strictEqual(closeEvent.code, 1000); wss.close(done); }); @@ -1685,43 +1576,15 @@ describe('WebSocket', function () { wss.on('connection', (client) => client.close(1000)); }); - it('should assign "true" to wasClean when server closes with code 3000', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.addEventListener('close', (closeEvent) => { - assert.ok(closeEvent.wasClean); - wss.close(done); - }); - }); - - wss.on('connection', (client) => client.close(3000)); - }); - - it('should assign "true" to wasClean when server closes with code 4999', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.addEventListener('close', (closeEvent) => { - assert.ok(closeEvent.wasClean); - wss.close(done); - }); - }); - - wss.on('connection', (client) => client.close(4999)); - }); - it('should receive valid CloseEvent when server closes with code 1001', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('close', (closeEvent) => { - assert.ok(!closeEvent.wasClean); - assert.strictEqual(closeEvent.code, 1001); + assert.ok(closeEvent.wasClean); assert.strictEqual(closeEvent.reason, 'some daft reason'); + assert.strictEqual(closeEvent.code, 1001); wss.close(done); }); }); @@ -2320,24 +2183,33 @@ describe('WebSocket', function () { it('can be used while data is being processed', function (done) { const wss = new WebSocket.Server({ - perMessageDeflate: { threshold: 0 }, + perMessageDeflate: true, port: 0 }, () => { const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { - perMessageDeflate: { threshold: 0 } - }); + const ws = new WebSocket(`ws://localhost:${port}`); + const messages = []; - wss.on('connection', (client) => { - for (let i = 0; i < 10; i++) { - client.send('hi'); - } - client.send('hi', () => { + ws.on('message', (message) => { + if (messages.push(message) > 1) return; + + process.nextTick(() => { assert.strictEqual(ws._receiver._state, 5); - ws.on('close', () => wss.close(done)); ws.terminate(); }); }); + + ws.on('close', (code, reason) => { + assert.deepStrictEqual(messages, ['', '', '', '']); + assert.strictEqual(code, 1006); + assert.strictEqual(reason, ''); + wss.close(done); + }); + }); + + wss.on('connection', (ws) => { + const buf = Buffer.from('c10100c10100c10100c10100', 'hex'); + ws._socket.write(buf); }); }); }); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index c367d6117..fc8bbe266 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -105,23 +105,6 @@ describe('WebSocketServer', function () { ws.on('open', () => new WebSocket(`ws+unix://${sockPath}`)); }); }); - - it('will not crash when it receives an unhandled opcode', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}/`); - - ws.on('open', () => ws._socket.write(Buffer.from([0x85, 0x00]))); - }); - - wss.on('connection', (ws) => { - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid opcode: 5'); - wss.close(done); - }); - }); - }); }); describe('#close', function () { @@ -878,6 +861,35 @@ describe('WebSocketServer', function () { client.send(data); }); }); + + it('does not crash when it receives an unhandled opcode', function (done) { + let closed = false; + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}/`); + + ws.on('open', () => ws._socket.write(Buffer.from([0x85, 0x00]))); + ws.on('close', (code, reason) => { + assert.strictEqual(code, 1006); + assert.strictEqual(reason, ''); + assert.ok(closed); + wss.close(done); + }); + }); + + wss.on('connection', (ws) => { + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'invalid opcode: 5'); + + ws.on('close', (code, reason) => { + assert.strictEqual(code, 1002); + assert.strictEqual(reason, ''); + closed = true; + }); + }); + }); + }); }); describe('client properties', function () { From 85919f2d11ad7fc0aa3bc20c9f35b4737fdc37e3 Mon Sep 17 00:00:00 2001 From: H1Gdev Date: Fri, 15 Dec 2017 15:04:57 +0900 Subject: [PATCH 436/669] [doc] Remove duplicate 'is' (#1252) --- doc/ws.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index 2975958f1..e913d3e5c 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -27,7 +27,7 @@ provided or an error is thrown. If `verifyClient` is not set then the handshake is automatically accepted. If -it is is provided with a single argument then that is: +it is provided with a single argument then that is: - `info` {Object} - `origin` {String} The value in the Origin header indicated by the client. From 6a6ae04ef3514e2f8b1b0890649523d59dcf27f4 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 16 Dec 2017 18:54:35 +0100 Subject: [PATCH 437/669] [minor] Send the close status code only when necessary --- lib/Sender.js | 23 ++++++++++++++++++----- test/Sender.test.js | 2 +- test/WebSocket.test.js | 28 ++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/lib/Sender.js b/lib/Sender.js index 53953d8d2..046a0e1d4 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -12,6 +12,7 @@ const crypto = require('crypto'); const PerMessageDeflate = require('./PerMessageDeflate'); const bufferUtil = require('./BufferUtil'); const ErrorCodes = require('./ErrorCodes'); +const constants = require('./Constants'); const Buffer = safeBuffer.Buffer; @@ -112,14 +113,26 @@ class Sender { * @public */ close (code, data, mask, cb) { - if (code !== undefined && (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code))) { + var buf; + + if (code === undefined) { + code = 1000; + } else if (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code)) { throw new Error('first argument must be a valid error code number'); } - const buf = Buffer.allocUnsafe(2 + (data ? Buffer.byteLength(data) : 0)); - - buf.writeUInt16BE(code || 1000, 0, true); - if (buf.length > 2) buf.write(data, 2); + if (data === undefined || data === '') { + if (code === 1000) { + buf = constants.EMPTY_BUFFER; + } else { + buf = Buffer.allocUnsafe(2); + buf.writeUInt16BE(code, 0, true); + } + } else { + buf = Buffer.allocUnsafe(2 + Buffer.byteLength(data)); + buf.writeUInt16BE(code, 0, true); + buf.write(data, 2); + } if (this._deflating) { this.enqueue([this.doClose, buf, mask, cb]); diff --git a/test/Sender.test.js b/test/Sender.test.js index 385011476..ac19d1ecd 100644 --- a/test/Sender.test.js +++ b/test/Sender.test.js @@ -267,7 +267,7 @@ describe('Sender', function () { sender.send('bar', { compress: true, fin: true }); sender.send('baz', { compress: true, fin: true }); - sender.close(1000, null, false, () => { + sender.close(1000, undefined, false, () => { assert.strictEqual(count, 4); done(); }); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index 32a4245b8..f293491b9 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1203,6 +1203,34 @@ describe('WebSocket', function () { }); }); + it('sends the close status code only when necessary', function (done) { + let sent; + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); + + ws.on('open', () => { + ws._socket.once('data', (data) => { + sent = data; + }); + }); + }); + + wss.on('connection', (ws) => { + ws._socket.once('data', (received) => { + assert.ok(received.slice(0, 2).equals(Buffer.from([0x88, 0x80]))); + assert.ok(sent.equals(Buffer.from([0x88, 0x00]))); + + ws.on('close', (code, reason) => { + assert.strictEqual(code, 1000); + assert.strictEqual(reason, ''); + wss.close(done); + }); + }); + ws.close(); + }); + }); + it('works when close reason is not specified', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; From 157f58a73250674eb57c505a522c7e5fe5fb3bee Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 17 Dec 2017 10:23:10 +0100 Subject: [PATCH 438/669] [dist] 3.3.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 76af690e0..96d0cb3b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "3.3.2", + "version": "3.3.3", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 180b388a2ed83fddd667d88970a943d0f47d32ec Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 20 Dec 2017 07:47:27 +0100 Subject: [PATCH 439/669] chore(package): update nyc to version 11.4.1 (#1258) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 96d0cb3b7..7b6ea7849 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "eslint-plugin-promise": "~3.6.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~4.0.0", - "nyc": "~11.3.0", + "nyc": "~11.4.1", "utf-8-validate": "~4.0.0" } } From cc815a76d111816bbc752c6758a4e7150633339b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 22 Dec 2017 17:56:27 +0100 Subject: [PATCH 440/669] [fix] Make permessage-deflate parameters validation stricter --- lib/PerMessageDeflate.js | 151 ++++++++++++++++----------------- test/PerMessageDeflate.test.js | 32 ++++++- test/WebSocketServer.test.js | 29 +------ 3 files changed, 103 insertions(+), 109 deletions(-) diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index 6ed12a7b7..ccdb0f70c 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -118,15 +118,11 @@ class PerMessageDeflate { accept (paramsList) { paramsList = this.normalizeParams(paramsList); - var params; - if (this._isServer) { - params = this.acceptAsServer(paramsList); - } else { - params = this.acceptAsClient(paramsList); - } + this.params = this._isServer + ? this.acceptAsServer(paramsList) + : this.acceptAsClient(paramsList); - this.params = params; - return params; + return this.params; } /** @@ -161,52 +157,43 @@ class PerMessageDeflate { * @private */ acceptAsServer (paramsList) { - const accepted = {}; - const result = paramsList.some((params) => { + const opts = this._options; + const accepted = paramsList.find((params) => { if ( - (this._options.serverNoContextTakeover === false && + (opts.serverNoContextTakeover === false && params.server_no_context_takeover) || - (this._options.serverMaxWindowBits === false && - params.server_max_window_bits) || - (typeof this._options.serverMaxWindowBits === 'number' && - typeof params.server_max_window_bits === 'number' && - this._options.serverMaxWindowBits > params.server_max_window_bits) || - (typeof this._options.clientMaxWindowBits === 'number' && + (params.server_max_window_bits && + (opts.serverMaxWindowBits === false || + (typeof opts.serverMaxWindowBits === 'number' && + opts.serverMaxWindowBits > params.server_max_window_bits))) || + (typeof opts.clientMaxWindowBits === 'number' && !params.client_max_window_bits) ) { - return; + return false; } - if ( - this._options.serverNoContextTakeover || - params.server_no_context_takeover - ) { - accepted.server_no_context_takeover = true; - } - if ( - this._options.clientNoContextTakeover || - (this._options.clientNoContextTakeover !== false && - params.client_no_context_takeover) - ) { - accepted.client_no_context_takeover = true; - } - if (typeof this._options.serverMaxWindowBits === 'number') { - accepted.server_max_window_bits = this._options.serverMaxWindowBits; - } else if (typeof params.server_max_window_bits === 'number') { - accepted.server_max_window_bits = params.server_max_window_bits; - } - if (typeof this._options.clientMaxWindowBits === 'number') { - accepted.client_max_window_bits = this._options.clientMaxWindowBits; - } else if ( - this._options.clientMaxWindowBits !== false && - typeof params.client_max_window_bits === 'number' - ) { - accepted.client_max_window_bits = params.client_max_window_bits; - } return true; }); - if (!result) throw new Error("Doesn't support the offered configuration"); + if (!accepted) throw new Error("Doesn't support the offered configuration"); + + if (opts.serverNoContextTakeover) { + accepted.server_no_context_takeover = true; + } + if (opts.clientNoContextTakeover) { + accepted.client_no_context_takeover = true; + } + if (typeof opts.serverMaxWindowBits === 'number') { + accepted.server_max_window_bits = opts.serverMaxWindowBits; + } + if (typeof opts.clientMaxWindowBits === 'number') { + accepted.client_max_window_bits = opts.clientMaxWindowBits; + } else if ( + accepted.client_max_window_bits === true || + opts.clientMaxWindowBits === false + ) { + delete accepted.client_max_window_bits; + } return accepted; } @@ -228,12 +215,14 @@ class PerMessageDeflate { throw new Error('Invalid value for "client_no_context_takeover"'); } - if ( + if (!params.client_max_window_bits) { + if (typeof this._options.clientMaxWindowBits === 'number') { + params.client_max_window_bits = this._options.clientMaxWindowBits; + } + } else if ( + this._options.clientMaxWindowBits === false || (typeof this._options.clientMaxWindowBits === 'number' && - (!params.client_max_window_bits || - params.client_max_window_bits > this._options.clientMaxWindowBits)) || - (this._options.clientMaxWindowBits === false && - params.client_max_window_bits) + params.client_max_window_bits > this._options.clientMaxWindowBits) ) { throw new Error('Invalid value for "client_max_window_bits"'); } @@ -249,46 +238,52 @@ class PerMessageDeflate { * @private */ normalizeParams (paramsList) { - return paramsList.map((params) => { + paramsList.forEach((params) => { Object.keys(params).forEach((key) => { var value = params[key]; + if (value.length > 1) { throw new Error(`Multiple extension parameters for ${key}`); } value = value[0]; - switch (key) { - case 'server_no_context_takeover': - case 'client_no_context_takeover': - if (value !== true) { - throw new Error(`invalid extension parameter value for ${key} (${value})`); - } - params[key] = true; - break; - case 'server_max_window_bits': - case 'client_max_window_bits': - if (typeof value === 'string') { - value = parseInt(value, 10); - if ( - Number.isNaN(value) || - value < zlib.Z_MIN_WINDOWBITS || - value > zlib.Z_MAX_WINDOWBITS - ) { - throw new Error(`invalid extension parameter value for ${key} (${value})`); - } + if (key === 'client_max_window_bits') { + if (value !== true) { + value = +value; + if (!Number.isInteger(value) || value < 8 || value > 15) { + throw new Error( + `invalid extension parameter value for ${key} (${value})` + ); } - if (!this._isServer && value === true) { - throw new Error(`Missing extension parameter value for ${key}`); - } - params[key] = value; - break; - default: - throw new Error(`Not defined extension parameter (${key})`); + } else if (!this._isServer) { + throw new Error(`Missing extension parameter value for ${key}`); + } + } else if (key === 'server_max_window_bits') { + value = +value; + if (!Number.isInteger(value) || value < 8 || value > 15) { + throw new Error( + `invalid extension parameter value for ${key} (${value})` + ); + } + } else if ( + key === 'client_no_context_takeover' || + key === 'server_no_context_takeover' + ) { + if (value !== true) { + throw new Error( + `invalid extension parameter value for ${key} (${value})` + ); + } + } else { + throw new Error(`Not defined extension parameter (${key})`); } + + params[key] = value; }); - return params; }); + + return paramsList; } /** diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index bfe25acb7..0065c7bee 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -162,6 +162,16 @@ describe('PerMessageDeflate', function () { assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); + + it('uses the config value if client_max_window_bits is not specified', function () { + const perMessageDeflate = new PerMessageDeflate({ + clientMaxWindowBits: 10 + }); + + assert.deepStrictEqual(perMessageDeflate.accept([{}]), { + client_max_window_bits: 10 + }); + }); }); describe('validate parameters', function () { @@ -189,18 +199,34 @@ describe('PerMessageDeflate', function () { }); it('should throw an error if server_max_window_bits has an invalid value', function () { - const perMessageDeflate = new PerMessageDeflate(); - const extensions = Extensions.parse('permessage-deflate; server_max_window_bits=7'); + const perMessageDeflate = new PerMessageDeflate({}, true); + let extensions = Extensions.parse('permessage-deflate; server_max_window_bits=7'); + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); + + extensions = Extensions.parse('permessage-deflate; server_max_window_bits'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_max_window_bits has an invalid value', function () { const perMessageDeflate = new PerMessageDeflate(); - const extensions = Extensions.parse('permessage-deflate; client_max_window_bits=16'); + let extensions = Extensions.parse('permessage-deflate; client_max_window_bits=16'); + assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); + + extensions = Extensions.parse('permessage-deflate; client_max_window_bits'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); + + it('throws an error if a parameter has an invalid name', function () { + const perMessageDeflate = new PerMessageDeflate(); + const extensions = Extensions.parse('permessage-deflate;foo'); + + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: Not defined extension parameter \(foo\)$/ + ); + }); }); }); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index fc8bbe266..2bfa09cb0 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -943,34 +943,7 @@ describe('WebSocketServer', function () { wss.on('connection', (ws) => wss.close(done)); }); - it('does not accept connections with invalid extension parameters (name)', function (done) { - const wss = new WebSocket.Server({ - perMessageDeflate: true, - port: 0 - }, () => { - const req = http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Extensions': 'permessage-deflate; foo=15' - } - }); - - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 400); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => { - done(new Error('connection must not be established')); - }); - }); - - it('does not accept connections with invalid extension parameters (value)', function (done) { + it('does not accept connections with invalid extension parameters', function (done) { const wss = new WebSocket.Server({ perMessageDeflate: true, port: 0 From 5678618f191113c17169d5e0a0e920954f1c88fb Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 24 Dec 2017 07:30:31 +0100 Subject: [PATCH 441/669] chore(package): update eslint to version 4.14.0 (#1262) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7b6ea7849..205d91589 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.13.0", + "eslint": "~4.14.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~5.2.0", From f941a4046481f1aa182aba11bb958e4306fd92fc Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 29 Dec 2017 08:05:32 +0100 Subject: [PATCH 442/669] chore(package): update mocha to version 4.1.0 (#1265) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 205d91589..af23fd9e0 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "eslint-plugin-node": "~5.2.0", "eslint-plugin-promise": "~3.6.0", "eslint-plugin-standard": "~3.0.0", - "mocha": "~4.0.0", + "mocha": "~4.1.0", "nyc": "~11.4.1", "utf-8-validate": "~4.0.0" } From a31b1f6439936e6de8cc1f7775410455551a2792 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 20 Dec 2017 10:19:25 +0100 Subject: [PATCH 443/669] [fix] Use 1005 status code if close frame payload length is 0 Fixes #1257 --- doc/ws.md | 2 +- lib/Receiver.js | 2 +- lib/Sender.js | 14 ++++---------- lib/WebSocket.js | 6 +++++- test/Receiver.test.js | 4 ++-- test/WebSocket.test.js | 8 ++++---- 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index e913d3e5c..b2962b2ff 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -308,7 +308,7 @@ not yet transmitted to the network. Received bytes count. -### websocket.close([code][, reason]) +### websocket.close([code[, reason]]) - `code` {Number} A numeric value indicating the status code explaining why the connection is being closed. diff --git a/lib/Receiver.js b/lib/Receiver.js index 91196706c..9e0d86474 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -402,7 +402,7 @@ class Receiver { controlMessage (data) { if (this._opcode === 0x08) { if (data.length === 0) { - this.onclose(1000, ''); + this.onclose(1005, ''); this._loop = false; this.cleanup(this._cleanupCallback); } else if (data.length === 1) { diff --git a/lib/Sender.js b/lib/Sender.js index 046a0e1d4..39e5a73f5 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -116,18 +116,12 @@ class Sender { var buf; if (code === undefined) { - code = 1000; + buf = constants.EMPTY_BUFFER; } else if (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code)) { throw new Error('first argument must be a valid error code number'); - } - - if (data === undefined || data === '') { - if (code === 1000) { - buf = constants.EMPTY_BUFFER; - } else { - buf = Buffer.allocUnsafe(2); - buf.writeUInt16BE(code, 0, true); - } + } else if (data === undefined || data === '') { + buf = Buffer.allocUnsafe(2); + buf.writeUInt16BE(code, 0, true); } else { buf = Buffer.allocUnsafe(2 + Buffer.byteLength(data)); buf.writeUInt16BE(code, 0, true); diff --git a/lib/WebSocket.js b/lib/WebSocket.js index a8c7b99b1..b115b1477 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -148,7 +148,11 @@ class WebSocket extends EventEmitter { this._closeFrameReceived = true; this._closeMessage = reason; this._closeCode = code; - if (!this._finalized) this.close(code, reason); + + if (this._finalized) return; + + if (code === 1005) this.close(); + else this.close(code, reason); }; this._receiver.onerror = (error, code) => { this._closeMessage = ''; diff --git a/test/Receiver.test.js b/test/Receiver.test.js index bb75a8993..cbb61e6fe 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -26,7 +26,7 @@ describe('Receiver', function () { const p = new Receiver(); p.onclose = function (code, data) { - assert.strictEqual(code, 1000); + assert.strictEqual(code, 1005); assert.strictEqual(data, ''); done(); }; @@ -836,7 +836,7 @@ describe('Receiver', function () { assert.strictEqual(p._bufferedBytes, textFrame.length + closeFrame.length); p.cleanup(() => { - assert.deepStrictEqual(results, ['Hello', 'Hello', 1000, '']); + assert.deepStrictEqual(results, ['Hello', 'Hello', 1005, '']); assert.strictEqual(p.onmessage, null); done(); }); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index f293491b9..ca2d34a36 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1222,7 +1222,7 @@ describe('WebSocket', function () { assert.ok(sent.equals(Buffer.from([0x88, 0x00]))); ws.on('close', (code, reason) => { - assert.strictEqual(code, 1000); + assert.strictEqual(code, 1005); assert.strictEqual(reason, ''); wss.close(done); }); @@ -1295,7 +1295,7 @@ describe('WebSocket', function () { ws.on('message', (message) => messages.push(message)); ws.on('close', (code) => { - assert.strictEqual(code, 1000); + assert.strictEqual(code, 1005); assert.deepStrictEqual(messages, ['foo', 'bar', 'baz']); wss.close(done); }); @@ -1330,7 +1330,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${port}`); ws.on('close', (code) => { - assert.strictEqual(code, 1000); + assert.strictEqual(code, 1005); assert.strictEqual(ws.readyState, WebSocket.CLOSED); ws.close(); wss.close(done); @@ -2180,7 +2180,7 @@ describe('WebSocket', function () { ws.on('message', (message) => { assert.strictEqual(message, 'hi'); ws.on('close', (code) => { - assert.strictEqual(code, 1000); + assert.strictEqual(code, 1005); wss.close(done); }); }); From 695c5ea988801ed75c121ad9bc76ed7abd70ccd1 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 23 Dec 2017 10:47:40 +0100 Subject: [PATCH 444/669] [major] Improve error messages and use specific error types --- lib/Extensions.js | 26 ++++--- lib/PerMessageDeflate.js | 42 ++++++----- lib/Receiver.js | 87 ++++++++++++++++++----- lib/Sender.js | 2 +- lib/WebSocket.js | 71 +++++++++++++------ lib/WebSocketServer.js | 4 +- test/Extensions.test.js | 12 ++-- test/PerMessageDeflate.test.js | 6 +- test/Receiver.test.js | 126 +++++++++++++++++++++++---------- test/WebSocket.test.js | 78 ++++++++++++++------ test/WebSocketServer.test.js | 7 +- 11 files changed, 322 insertions(+), 139 deletions(-) diff --git a/lib/Extensions.js b/lib/Extensions.js index 15fee1216..d59d39202 100644 --- a/lib/Extensions.js +++ b/lib/Extensions.js @@ -67,7 +67,9 @@ function parse (header) { } else if (code === 0x20/* ' ' */|| code === 0x09/* '\t' */) { if (end === -1 && start !== -1) end = i; } else if (code === 0x3b/* ';' */ || code === 0x2c/* ',' */) { - if (start === -1) throw new Error(`unexpected character at index ${i}`); + if (start === -1) { + throw new SyntaxError(`Unexpected character at index ${i}`); + } if (end === -1) end = i; const name = header.slice(start, end); @@ -80,7 +82,7 @@ function parse (header) { start = end = -1; } else { - throw new Error(`unexpected character at index ${i}`); + throw new SyntaxError(`Unexpected character at index ${i}`); } } else if (paramName === undefined) { if (end === -1 && tokenChars[code] === 1) { @@ -88,7 +90,9 @@ function parse (header) { } else if (code === 0x20 || code === 0x09) { if (end === -1 && start !== -1) end = i; } else if (code === 0x3b || code === 0x2c) { - if (start === -1) throw new Error(`unexpected character at index ${i}`); + if (start === -1) { + throw new SyntaxError(`Unexpected character at index ${i}`); + } if (end === -1) end = i; push(params, header.slice(start, end), true); @@ -103,7 +107,7 @@ function parse (header) { paramName = header.slice(start, i); start = end = -1; } else { - throw new Error(`unexpected character at index ${i}`); + throw new SyntaxError(`Unexpected character at index ${i}`); } } else { // @@ -113,7 +117,7 @@ function parse (header) { // if (isEscaping) { if (tokenChars[code] !== 1) { - throw new Error(`unexpected character at index ${i}`); + throw new SyntaxError(`Unexpected character at index ${i}`); } if (start === -1) start = i; else if (!mustUnescape) mustUnescape = true; @@ -127,7 +131,7 @@ function parse (header) { } else if (code === 0x5c/* '\' */) { isEscaping = true; } else { - throw new Error(`unexpected character at index ${i}`); + throw new SyntaxError(`Unexpected character at index ${i}`); } } else if (code === 0x22 && header.charCodeAt(i - 1) === 0x3d) { inQuotes = true; @@ -136,7 +140,9 @@ function parse (header) { } else if (start !== -1 && (code === 0x20 || code === 0x09)) { if (end === -1) end = i; } else if (code === 0x3b || code === 0x2c) { - if (start === -1) throw new Error(`unexpected character at index ${i}`); + if (start === -1) { + throw new SyntaxError(`Unexpected character at index ${i}`); + } if (end === -1) end = i; var value = header.slice(start, end); @@ -154,12 +160,14 @@ function parse (header) { paramName = undefined; start = end = -1; } else { - throw new Error(`unexpected character at index ${i}`); + throw new SyntaxError(`Unexpected character at index ${i}`); } } } - if (start === -1 || inQuotes) throw new Error('unexpected end of input'); + if (start === -1 || inQuotes) { + throw new SyntaxError('Unexpected end of input'); + } if (end === -1) end = i; const token = header.slice(start, end); diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js index ccdb0f70c..b452b574b 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/PerMessageDeflate.js @@ -175,7 +175,9 @@ class PerMessageDeflate { return true; }); - if (!accepted) throw new Error("Doesn't support the offered configuration"); + if (!accepted) { + throw new Error('None of the extension offers can be accepted'); + } if (opts.serverNoContextTakeover) { accepted.server_no_context_takeover = true; @@ -212,7 +214,7 @@ class PerMessageDeflate { this._options.clientNoContextTakeover === false && params.client_no_context_takeover ) { - throw new Error('Invalid value for "client_no_context_takeover"'); + throw new Error('Unexpected parameter "client_no_context_takeover"'); } if (!params.client_max_window_bits) { @@ -224,7 +226,9 @@ class PerMessageDeflate { (typeof this._options.clientMaxWindowBits === 'number' && params.client_max_window_bits > this._options.clientMaxWindowBits) ) { - throw new Error('Invalid value for "client_max_window_bits"'); + throw new Error( + 'Unexpected or invalid parameter "client_max_window_bits"' + ); } return params; @@ -243,40 +247,44 @@ class PerMessageDeflate { var value = params[key]; if (value.length > 1) { - throw new Error(`Multiple extension parameters for ${key}`); + throw new Error(`Parameter "${key}" must have only a single value`); } value = value[0]; if (key === 'client_max_window_bits') { if (value !== true) { - value = +value; - if (!Number.isInteger(value) || value < 8 || value > 15) { - throw new Error( - `invalid extension parameter value for ${key} (${value})` + const num = +value; + if (!Number.isInteger(num) || num < 8 || num > 15) { + throw new TypeError( + `Invalid value for parameter "${key}": ${value}` ); } + value = num; } else if (!this._isServer) { - throw new Error(`Missing extension parameter value for ${key}`); + throw new TypeError( + `Invalid value for parameter "${key}": ${value}` + ); } } else if (key === 'server_max_window_bits') { - value = +value; - if (!Number.isInteger(value) || value < 8 || value > 15) { - throw new Error( - `invalid extension parameter value for ${key} (${value})` + const num = +value; + if (!Number.isInteger(num) || num < 8 || num > 15) { + throw new TypeError( + `Invalid value for parameter "${key}": ${value}` ); } + value = num; } else if ( key === 'client_no_context_takeover' || key === 'server_no_context_takeover' ) { if (value !== true) { - throw new Error( - `invalid extension parameter value for ${key} (${value})` + throw new TypeError( + `Invalid value for parameter "${key}": ${value}` ); } } else { - throw new Error(`Not defined extension parameter (${key})`); + throw new Error(`Unknown parameter "${key}"`); } params[key] = value; @@ -480,7 +488,7 @@ function inflateOnData (chunk) { return; } - this[kError] = new Error('max payload size exceeded'); + this[kError] = new RangeError('Max payload size exceeded'); this[kError].closeCode = 1009; this.removeListener('data', inflateOnData); this.reset(); diff --git a/lib/Receiver.js b/lib/Receiver.js index 9e0d86474..126ff9676 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -181,14 +181,20 @@ class Receiver { const buf = this.readBuffer(2); if ((buf[0] & 0x30) !== 0x00) { - this.error(new Error('RSV2 and RSV3 must be clear'), 1002); + this.error( + new RangeError('Invalid WebSocket frame: RSV2 and RSV3 must be clear'), + 1002 + ); return; } const compressed = (buf[0] & 0x40) === 0x40; if (compressed && !this._extensions[PerMessageDeflate.extensionName]) { - this.error(new Error('RSV1 must be clear'), 1002); + this.error( + new RangeError('Invalid WebSocket frame: RSV1 must be clear'), + 1002 + ); return; } @@ -198,40 +204,68 @@ class Receiver { if (this._opcode === 0x00) { if (compressed) { - this.error(new Error('RSV1 must be clear'), 1002); + this.error( + new RangeError('Invalid WebSocket frame: RSV1 must be clear'), + 1002 + ); return; } if (!this._fragmented) { - this.error(new Error(`invalid opcode: ${this._opcode}`), 1002); + this.error( + new RangeError('Invalid WebSocket frame: invalid opcode 0'), + 1002 + ); return; } else { this._opcode = this._fragmented; } } else if (this._opcode === 0x01 || this._opcode === 0x02) { if (this._fragmented) { - this.error(new Error(`invalid opcode: ${this._opcode}`), 1002); + this.error( + new RangeError( + `Invalid WebSocket frame: invalid opcode ${this._opcode}` + ), + 1002 + ); return; } this._compressed = compressed; } else if (this._opcode > 0x07 && this._opcode < 0x0b) { if (!this._fin) { - this.error(new Error('FIN must be set'), 1002); + this.error( + new RangeError('Invalid WebSocket frame: FIN must be set'), + 1002 + ); return; } if (compressed) { - this.error(new Error('RSV1 must be clear'), 1002); + this.error( + new RangeError('Invalid WebSocket frame: RSV1 must be clear'), + 1002 + ); return; } if (this._payloadLength > 0x7d) { - this.error(new Error('invalid payload length'), 1002); + this.error( + new RangeError( + `Invalid WebSocket frame: invalid payload length ` + + `${this._payloadLength}` + ), + 1002 + ); return; } } else { - this.error(new Error(`invalid opcode: ${this._opcode}`), 1002); + this.error( + new RangeError( + `Invalid WebSocket frame: invalid opcode ${this._opcode}` + ), + 1002 + ); return; } @@ -272,11 +306,16 @@ class Receiver { // if payload length is greater than this number. // if (num > Math.pow(2, 53 - 32) - 1) { - this.error(new Error('max payload size exceeded'), 1009); + this.error( + new RangeError( + 'Unsupported WebSocket frame: payload length > 2^53 - 1' + ), + 1009 + ); return; } - this._payloadLength = (num * Math.pow(2, 32)) + buf.readUInt32BE(4, true); + this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4, true); this.haveLength(); } @@ -382,7 +421,10 @@ class Receiver { const buf = toBuffer(fragments, messageLength); if (!isValidUTF8(buf)) { - this.error(new Error('invalid utf8 sequence'), 1007); + this.error( + new Error('Invalid WebSocket frame: invalid UTF-8 sequence'), + 1007 + ); return; } @@ -406,19 +448,30 @@ class Receiver { this._loop = false; this.cleanup(this._cleanupCallback); } else if (data.length === 1) { - this.error(new Error('invalid payload length'), 1002); + this.error( + new RangeError('Invalid WebSocket frame: invalid payload length 1'), + 1002 + ); } else { const code = data.readUInt16BE(0, true); if (!ErrorCodes.isValidErrorCode(code)) { - this.error(new Error(`invalid status code: ${code}`), 1002); + this.error( + new RangeError( + `Invalid WebSocket frame: invalid status code ${code}` + ), + 1002 + ); return; } const buf = data.slice(2); if (!isValidUTF8(buf)) { - this.error(new Error('invalid utf8 sequence'), 1007); + this.error( + new Error('Invalid WebSocket frame: invalid UTF-8 sequence'), + 1007 + ); return; } @@ -466,7 +519,7 @@ class Receiver { return false; } - this.error(new Error('max payload size exceeded'), 1009); + this.error(new RangeError('Max payload size exceeded'), 1009); return true; } @@ -489,7 +542,7 @@ class Receiver { return true; } - this.error(new Error('max payload size exceeded'), 1009); + this.error(new RangeError('Max payload size exceeded'), 1009); return false; } diff --git a/lib/Sender.js b/lib/Sender.js index 39e5a73f5..b01e4dba5 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -118,7 +118,7 @@ class Sender { if (code === undefined) { buf = constants.EMPTY_BUFFER; } else if (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code)) { - throw new Error('first argument must be a valid error code number'); + throw new TypeError('First argument must be a valid error code number'); } else if (data === undefined || data === '') { buf = Buffer.allocUnsafe(2); buf.writeUInt16BE(code, 0, true); diff --git a/lib/WebSocket.js b/lib/WebSocket.js index b115b1477..4b7f044df 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -20,6 +20,7 @@ const constants = require('./Constants'); const Receiver = require('./Receiver'); const Sender = require('./Sender'); +const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']; const protocolVersions = [8, 13]; const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly. @@ -229,7 +230,12 @@ class WebSocket extends EventEmitter { * @public */ pause () { - if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); + if (this.readyState !== WebSocket.OPEN) { + throw new Error( + `WebSocket is not open: readyState ${this.readyState} ` + + `(${readyStates[this.readyState]})` + ); + } this._socket.pause(); } @@ -240,7 +246,12 @@ class WebSocket extends EventEmitter { * @public */ resume () { - if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); + if (this.readyState !== WebSocket.OPEN) { + throw new Error( + `WebSocket is not open: readyState ${this.readyState} ` + + `(${readyStates[this.readyState]})` + ); + } this._socket.resume(); } @@ -272,7 +283,9 @@ class WebSocket extends EventEmitter { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { this._req.abort(); - this.finalize(new Error('closed before the connection is established')); + this.finalize( + new Error('WebSocket was closed before the connection was established') + ); return; } @@ -314,7 +327,10 @@ class WebSocket extends EventEmitter { ping (data, mask, failSilently) { if (this.readyState !== WebSocket.OPEN) { if (failSilently) return; - throw new Error('not opened'); + throw new Error( + `WebSocket is not open: readyState ${this.readyState} ` + + `(${readyStates[this.readyState]})` + ); } if (typeof data === 'number') data = data.toString(); @@ -333,7 +349,10 @@ class WebSocket extends EventEmitter { pong (data, mask, failSilently) { if (this.readyState !== WebSocket.OPEN) { if (failSilently) return; - throw new Error('not opened'); + throw new Error( + `WebSocket is not open: readyState ${this.readyState} ` + + `(${readyStates[this.readyState]})` + ); } if (typeof data === 'number') data = data.toString(); @@ -360,8 +379,13 @@ class WebSocket extends EventEmitter { } if (this.readyState !== WebSocket.OPEN) { - if (cb) cb(new Error('not opened')); - else throw new Error('not opened'); + const err = new Error( + `WebSocket is not open: readyState ${this.readyState} ` + + `(${readyStates[this.readyState]})` + ); + + if (cb) cb(err); + else throw err; return; } @@ -390,7 +414,9 @@ class WebSocket extends EventEmitter { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { this._req.abort(); - this.finalize(new Error('closed before the connection is established')); + this.finalize( + new Error('WebSocket was closed before the connection was established') + ); return; } @@ -398,10 +424,9 @@ class WebSocket extends EventEmitter { } } -WebSocket.CONNECTING = 0; -WebSocket.OPEN = 1; -WebSocket.CLOSING = 2; -WebSocket.CLOSED = 3; +readyStates.forEach((readyState, i) => { + WebSocket[readyStates[i]] = i; +}); // // Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes. @@ -524,9 +549,9 @@ function initAsClient (address, protocols, options) { }, options); if (protocolVersions.indexOf(options.protocolVersion) === -1) { - throw new Error( - `unsupported protocol version: ${options.protocolVersion} ` + - `(supported versions: ${protocolVersions.join(', ')})` + throw new RangeError( + `Unsupported protocol version: ${options.protocolVersion} ` + + `(supported versions: ${protocolVersions.join(', ')})` ); } @@ -538,7 +563,7 @@ function initAsClient (address, protocols, options) { const isUnixSocket = serverUrl.protocol === 'ws+unix:'; if (!serverUrl.host && (!isUnixSocket || !serverUrl.path)) { - throw new Error('invalid url'); + throw new Error(`Invalid URL: ${address}`); } const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:'; @@ -640,7 +665,7 @@ function initAsClient (address, protocols, options) { if (options.handshakeTimeout) { this._req.setTimeout(options.handshakeTimeout, () => { this._req.abort(); - this.finalize(new Error('opening handshake has timed out')); + this.finalize(new Error('Opening handshake has timed out')); }); } @@ -654,7 +679,7 @@ function initAsClient (address, protocols, options) { this._req.on('response', (res) => { if (!this.emit('unexpected-response', this._req, res)) { this._req.abort(); - this.finalize(new Error(`unexpected server response (${res.statusCode})`)); + this.finalize(new Error(`Unexpected server response: ${res.statusCode}`)); } }); @@ -675,7 +700,7 @@ function initAsClient (address, protocols, options) { if (res.headers['sec-websocket-accept'] !== digest) { socket.destroy(); - return this.finalize(new Error('invalid server key')); + return this.finalize(new Error('Invalid Sec-WebSocket-Accept header')); } const serverProt = res.headers['sec-websocket-protocol']; @@ -683,11 +708,11 @@ function initAsClient (address, protocols, options) { var protError; if (!options.protocol && serverProt) { - protError = 'server sent a subprotocol even though none requested'; + protError = 'Server sent a subprotocol but none was requested'; } else if (options.protocol && !serverProt) { - protError = 'server sent no subprotocol even though requested'; + protError = 'Server sent no subprotocol'; } else if (serverProt && protList.indexOf(serverProt) === -1) { - protError = 'server responded with an invalid protocol'; + protError = 'Server sent an invalid subprotocol'; } if (protError) { @@ -711,7 +736,7 @@ function initAsClient (address, protocols, options) { } } catch (err) { socket.destroy(); - this.finalize(new Error('invalid Sec-WebSocket-Extensions header')); + this.finalize(new Error('Invalid Sec-WebSocket-Extensions header')); return; } } diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 8ebd6bf81..b5efb1df7 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -60,7 +60,9 @@ class WebSocketServer extends EventEmitter { }, options); if (options.port == null && !options.server && !options.noServer) { - throw new TypeError('missing or invalid options'); + throw new TypeError( + 'One of the "port", "server", or "noServer" options must be specified' + ); } if (options.port != null) { diff --git a/test/Extensions.test.js b/test/Extensions.test.js index f08ab0852..8f23d05e2 100644 --- a/test/Extensions.test.js +++ b/test/Extensions.test.js @@ -49,11 +49,11 @@ describe('Extensions', function () { }); assert.throws( () => Extensions.parse('foo;bar="baz"qux'), - /^Error: unexpected character at index 13$/ + /^SyntaxError: Unexpected character at index 13$/ ); assert.throws( () => Extensions.parse('foo;bar="baz" qux'), - /^Error: unexpected character at index 14$/ + /^SyntaxError: Unexpected character at index 14$/ ); }); @@ -92,7 +92,7 @@ describe('Extensions', function () { ].forEach((element) => { assert.throws( () => Extensions.parse(element[0]), - new RegExp(`^Error: unexpected character at index ${element[1]}$`) + new RegExp(`^SyntaxError: Unexpected character at index ${element[1]}$`) ); }); }); @@ -106,7 +106,7 @@ describe('Extensions', function () { ].forEach((element) => { assert.throws( () => Extensions.parse(element[0]), - new RegExp(`^Error: unexpected character at index ${element[1]}$`) + new RegExp(`^SyntaxError: Unexpected character at index ${element[1]}$`) ); }); }); @@ -130,7 +130,7 @@ describe('Extensions', function () { ].forEach((element) => { assert.throws( () => Extensions.parse(element[0]), - new RegExp(`^Error: unexpected character at index ${element[1]}$`) + new RegExp(`^SyntaxError: Unexpected character at index ${element[1]}$`) ); }); }); @@ -147,7 +147,7 @@ describe('Extensions', function () { ].forEach((header) => { assert.throws( () => Extensions.parse(header), - /^Error: unexpected end of input$/ + /^SyntaxError: Unexpected end of input$/ ); }); }); diff --git a/test/PerMessageDeflate.test.js b/test/PerMessageDeflate.test.js index 0065c7bee..5f74b9d3d 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/PerMessageDeflate.test.js @@ -224,7 +224,7 @@ describe('PerMessageDeflate', function () { assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), - /^Error: Not defined extension parameter \(foo\)$/ + /^Error: Unknown parameter "foo"$/ ); }); }); @@ -429,8 +429,8 @@ describe('PerMessageDeflate', function () { perMessageDeflate.decompress(data, true, (err) => errors.push(err)); perMessageDeflate._inflate.flush(() => { assert.strictEqual(errors.length, 1); - assert.ok(errors[0] instanceof Error); - assert.strictEqual(errors[0].message, 'max payload size exceeded'); + assert.ok(errors[0] instanceof RangeError); + assert.strictEqual(errors[0].message, 'Max payload size exceeded'); done(); }); }); diff --git a/test/Receiver.test.js b/test/Receiver.test.js index cbb61e6fe..b6affbd00 100644 --- a/test/Receiver.test.js +++ b/test/Receiver.test.js @@ -447,8 +447,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'RSV1 must be clear'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: RSV1 must be clear' + ); assert.strictEqual(code, 1002); done(); }; @@ -463,8 +466,11 @@ describe('Receiver', function () { const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'RSV1 must be clear'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: RSV1 must be clear' + ); assert.strictEqual(code, 1002); done(); }; @@ -476,8 +482,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'RSV2 and RSV3 must be clear'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: RSV2 and RSV3 must be clear' + ); assert.strictEqual(code, 1002); done(); }; @@ -489,8 +498,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'RSV2 and RSV3 must be clear'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: RSV2 and RSV3 must be clear' + ); assert.strictEqual(code, 1002); done(); }; @@ -502,8 +514,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid opcode: 0'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid opcode 0' + ); assert.strictEqual(code, 1002); done(); }; @@ -515,8 +530,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid opcode: 1'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid opcode 1' + ); assert.strictEqual(code, 1002); done(); }; @@ -529,8 +547,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid opcode: 2'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid opcode 2' + ); assert.strictEqual(code, 1002); done(); }; @@ -543,8 +564,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'FIN must be set'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: FIN must be set' + ); assert.strictEqual(code, 1002); done(); }; @@ -559,8 +583,11 @@ describe('Receiver', function () { const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'RSV1 must be clear'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: RSV1 must be clear' + ); assert.strictEqual(code, 1002); done(); }; @@ -572,8 +599,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'FIN must be set'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: FIN must be set' + ); assert.strictEqual(code, 1002); done(); }; @@ -585,8 +615,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid payload length'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid payload length 126' + ); assert.strictEqual(code, 1002); done(); }; @@ -598,8 +631,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'max payload size exceeded'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Unsupported WebSocket frame: payload length > 2^53 - 1' + ); assert.strictEqual(code, 1009); done(); }; @@ -616,7 +652,10 @@ describe('Receiver', function () { p.onerror = function (err, code) { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid utf8 sequence'); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid UTF-8 sequence' + ); assert.strictEqual(code, 1007); done(); }; @@ -628,8 +667,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid payload length'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid payload length 1' + ); assert.strictEqual(code, 1002); done(); }; @@ -641,8 +683,11 @@ describe('Receiver', function () { const p = new Receiver(); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid status code: 0'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid status code 0' + ); assert.strictEqual(code, 1002); done(); }; @@ -655,7 +700,10 @@ describe('Receiver', function () { p.onerror = function (err, code) { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid utf8 sequence'); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid UTF-8 sequence' + ); assert.strictEqual(code, 1007); done(); }; @@ -678,8 +726,8 @@ describe('Receiver', function () { const frame = Buffer.concat(list); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'max payload size exceeded'); + assert.ok(err instanceof RangeError); + assert.strictEqual(err.message, 'Max payload size exceeded'); assert.strictEqual(code, 1009); done(); }; @@ -702,8 +750,8 @@ describe('Receiver', function () { const frame = Buffer.concat(list); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'max payload size exceeded'); + assert.ok(err instanceof RangeError); + assert.strictEqual(err.message, 'Max payload size exceeded'); assert.strictEqual(code, 1009); done(); }; @@ -719,8 +767,8 @@ describe('Receiver', function () { const buf = Buffer.from('A'.repeat(50)); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'max payload size exceeded'); + assert.ok(err instanceof RangeError); + assert.strictEqual(err.message, 'Max payload size exceeded'); assert.strictEqual(code, 1009); done(); }; @@ -741,8 +789,8 @@ describe('Receiver', function () { const buf = Buffer.from('A'.repeat(15)); p.onerror = function (err, code) { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'max payload size exceeded'); + assert.ok(err instanceof RangeError); + assert.strictEqual(err.message, 'Max payload size exceeded'); assert.strictEqual(code, 1009); done(); }; @@ -871,7 +919,7 @@ describe('Receiver', function () { assert.deepStrictEqual(results, [ 'Hello', 'Hello', - 'RSV2 and RSV3 must be clear', + 'Invalid WebSocket frame: RSV2 and RSV3 must be clear', 1002 ]); assert.strictEqual(p.onmessage, null); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index ca2d34a36..aa4fced44 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -26,7 +26,7 @@ describe('WebSocket', function () { it('throws an error when using an invalid url', function () { assert.throws( () => new WebSocket('echo.websocket.org'), - /^Error: invalid url$/ + /^Error: Invalid URL: echo\.websocket\.org$/ ); }); }); @@ -61,7 +61,7 @@ describe('WebSocket', function () { assert.throws( () => new WebSocket('ws://localhost', options), - /^Error: unsupported protocol version: 1000 \(supported versions: 8, 13\)$/ + /^RangeError: Unsupported protocol version: 1000 \(supported versions: 8, 13\)$/ ); }); @@ -403,7 +403,7 @@ describe('WebSocket', function () { ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid server key'); + assert.strictEqual(err.message, 'Invalid Sec-WebSocket-Accept header'); done(); }); }); @@ -448,7 +448,7 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'unexpected server response (401)'); + assert.strictEqual(err.message, 'Unexpected server response: 401'); done(); }); }); @@ -520,7 +520,7 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'opening handshake has timed out'); + assert.strictEqual(err.message, 'Opening handshake has timed out'); done(); }); }); @@ -546,7 +546,7 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid Sec-WebSocket-Extensions header'); + assert.strictEqual(err.message, 'Invalid Sec-WebSocket-Extensions header'); ws.on('close', () => done()); }); }); @@ -574,7 +574,7 @@ describe('WebSocket', function () { assert.ok(err instanceof Error); assert.strictEqual( err.message, - 'server sent a subprotocol even though none requested' + 'Server sent a subprotocol but none was requested' ); ws.on('close', () => done()); }); @@ -605,13 +605,19 @@ describe('WebSocket', function () { it('throws an error when `readyState` is not `OPEN` (pause)', function () { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - assert.throws(() => ws.pause(), /^Error: not opened$/); + assert.throws( + () => ws.pause(), + /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ + ); }); it('throws an error when `readyState` is not `OPEN` (resume)', function () { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - assert.throws(() => ws.resume(), /^Error: not opened$/); + assert.throws( + () => ws.resume(), + /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ + ); }); it('pauses the underlying stream', function (done) { @@ -662,7 +668,10 @@ describe('WebSocket', function () { agent: new CustomAgent() }); - assert.throws(() => ws.ping(), /^Error: not opened$/); + assert.throws( + () => ws.ping(), + /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ + ); }); it('before connect can silently fail', function () { @@ -729,7 +738,10 @@ describe('WebSocket', function () { agent: new CustomAgent() }); - assert.throws(() => ws.pong(), /^Error: not opened$/); + assert.throws( + () => ws.pong(), + /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ + ); }); it('before connect can silently fail', function () { @@ -958,7 +970,10 @@ describe('WebSocket', function () { agent: new CustomAgent() }); - assert.throws(() => ws.send('hi'), /^Error: not opened$/); + assert.throws( + () => ws.send('hi'), + /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ + ); }); it('before connect should pass error through callback, if present', function () { @@ -968,7 +983,10 @@ describe('WebSocket', function () { ws.send('hi', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'not opened'); + assert.strictEqual( + err.message, + 'WebSocket is not open: readyState 0 (CONNECTING)' + ); }); }); @@ -1088,7 +1106,10 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); + assert.strictEqual( + err.message, + 'WebSocket was closed before the connection was established' + ); ws.on('close', () => wss.close(done)); }); ws.close(1001); @@ -1106,7 +1127,10 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); + assert.strictEqual( + err.message, + 'WebSocket was closed before the connection was established' + ); ws.on('close', () => wss.close(done)); }); setTimeout(() => ws.close(1001), 150); @@ -1133,7 +1157,10 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); + assert.strictEqual( + err.message, + 'WebSocket was closed before the connection was established' + ); ws.on('close', () => wss.close(done)); }); ws.on('headers', () => ws.close()); @@ -1148,7 +1175,7 @@ describe('WebSocket', function () { ws.on('open', () => { assert.throws( () => ws.close('error'), - /^Error: first argument must be a valid error code number$/ + /^TypeError: First argument must be a valid error code number$/ ); wss.close(done); @@ -1164,7 +1191,7 @@ describe('WebSocket', function () { ws.on('open', () => { assert.throws( () => ws.close(1004), - /^Error: first argument must be a valid error code number$/ + /^TypeError: First argument must be a valid error code number$/ ); wss.close(done); @@ -1350,7 +1377,10 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); + assert.strictEqual( + err.message, + 'WebSocket was closed before the connection was established' + ); ws.on('close', () => wss.close(done)); }); ws.terminate(); @@ -1368,7 +1398,10 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); + assert.strictEqual( + err.message, + 'WebSocket was closed before the connection was established' + ); ws.on('close', () => wss.close(done)); }); setTimeout(() => ws.terminate(), 150); @@ -1395,7 +1428,10 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'closed before the connection is established'); + assert.strictEqual( + err.message, + 'WebSocket was closed before the connection was established' + ); ws.on('close', () => wss.close(done)); }); ws.on('headers', () => ws.terminate()); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index 2bfa09cb0..8e97c9bb0 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -879,8 +879,11 @@ describe('WebSocketServer', function () { wss.on('connection', (ws) => { ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'invalid opcode: 5'); + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid opcode 5' + ); ws.on('close', (code, reason) => { assert.strictEqual(code, 1002); From 63ce954d8fa1446cbeea555ce18859fc2c4e4cdc Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 23 Dec 2017 14:48:35 +0100 Subject: [PATCH 445/669] [major] Remove ErrorCodes module Move the `isValidErrorCode` function to the Validation module and rename it to `isValidStatusCode`. --- lib/ErrorCodes.js | 28 ---------------------------- lib/Receiver.js | 9 ++++----- lib/Sender.js | 4 ++-- lib/Validation.js | 22 ++++++++++++++++++++-- test/Validation.test.js | 12 ++++++------ 5 files changed, 32 insertions(+), 43 deletions(-) delete mode 100644 lib/ErrorCodes.js diff --git a/lib/ErrorCodes.js b/lib/ErrorCodes.js deleted file mode 100644 index f51557162..000000000 --- a/lib/ErrorCodes.js +++ /dev/null @@ -1,28 +0,0 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - -'use strict'; - -module.exports = { - isValidErrorCode: function (code) { - return (code >= 1000 && code <= 1013 && code !== 1004 && code !== 1005 && code !== 1006) || - (code >= 3000 && code <= 4999); - }, - 1000: 'normal', - 1001: 'going away', - 1002: 'protocol error', - 1003: 'unsupported data', - 1004: 'reserved', - 1005: 'reserved for extensions', - 1006: 'reserved for extensions', - 1007: 'inconsistent or invalid data', - 1008: 'policy violation', - 1009: 'message too big', - 1010: 'extension handshake missing', - 1011: 'an unexpected condition prevented the request from being fulfilled', - 1012: 'service restart', - 1013: 'try again later' -}; diff --git a/lib/Receiver.js b/lib/Receiver.js index 126ff9676..546d40cf0 100644 --- a/lib/Receiver.js +++ b/lib/Receiver.js @@ -9,9 +9,8 @@ const safeBuffer = require('safe-buffer'); const PerMessageDeflate = require('./PerMessageDeflate'); -const isValidUTF8 = require('./Validation'); +const validation = require('./Validation'); const bufferUtil = require('./BufferUtil'); -const ErrorCodes = require('./ErrorCodes'); const constants = require('./Constants'); const Buffer = safeBuffer.Buffer; @@ -420,7 +419,7 @@ class Receiver { } else { const buf = toBuffer(fragments, messageLength); - if (!isValidUTF8(buf)) { + if (!validation.isValidUTF8(buf)) { this.error( new Error('Invalid WebSocket frame: invalid UTF-8 sequence'), 1007 @@ -455,7 +454,7 @@ class Receiver { } else { const code = data.readUInt16BE(0, true); - if (!ErrorCodes.isValidErrorCode(code)) { + if (!validation.isValidStatusCode(code)) { this.error( new RangeError( `Invalid WebSocket frame: invalid status code ${code}` @@ -467,7 +466,7 @@ class Receiver { const buf = data.slice(2); - if (!isValidUTF8(buf)) { + if (!validation.isValidUTF8(buf)) { this.error( new Error('Invalid WebSocket frame: invalid UTF-8 sequence'), 1007 diff --git a/lib/Sender.js b/lib/Sender.js index b01e4dba5..3c19f046f 100644 --- a/lib/Sender.js +++ b/lib/Sender.js @@ -11,7 +11,7 @@ const crypto = require('crypto'); const PerMessageDeflate = require('./PerMessageDeflate'); const bufferUtil = require('./BufferUtil'); -const ErrorCodes = require('./ErrorCodes'); +const validation = require('./Validation'); const constants = require('./Constants'); const Buffer = safeBuffer.Buffer; @@ -117,7 +117,7 @@ class Sender { if (code === undefined) { buf = constants.EMPTY_BUFFER; - } else if (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code)) { + } else if (typeof code !== 'number' || !validation.isValidStatusCode(code)) { throw new TypeError('First argument must be a valid error code number'); } else if (data === undefined || data === '') { buf = Buffer.allocUnsafe(2); diff --git a/lib/Validation.js b/lib/Validation.js index 35c7e4f2a..a8ac2e634 100644 --- a/lib/Validation.js +++ b/lib/Validation.js @@ -9,9 +9,27 @@ try { const isValidUTF8 = require('utf-8-validate'); - module.exports = typeof isValidUTF8 === 'object' + exports.isValidUTF8 = typeof isValidUTF8 === 'object' ? isValidUTF8.Validation.isValidUTF8 // utf-8-validate@<3.0.0 : isValidUTF8; } catch (e) /* istanbul ignore next */ { - module.exports = () => true; + exports.isValidUTF8 = () => true; } + +/** + * Checks if a status code is allowed in a close frame. + * + * @param {Number} code The status code + * @return {Boolean} `true` if the status code is valid, else `false` + * @public + */ +exports.isValidStatusCode = (code) => { + return ( + (code >= 1000 && + code <= 1013 && + code !== 1004 && + code !== 1005 && + code !== 1006) || + (code >= 3000 && code <= 4999) + ); +}; diff --git a/test/Validation.test.js b/test/Validation.test.js index 88dc37aa5..b2f38d3a1 100644 --- a/test/Validation.test.js +++ b/test/Validation.test.js @@ -3,7 +3,7 @@ const safeBuffer = require('safe-buffer'); const assert = require('assert'); -const isValidUTF8 = require('../lib/Validation'); +const validation = require('../lib/Validation'); const Buffer = safeBuffer.Buffer; @@ -28,7 +28,7 @@ describe('Validation', function () { 'vulputate quis. Morbi ut pulvinar augue.' ); - assert.ok(isValidUTF8(validBuffer)); + assert.ok(validation.isValidUTF8(validBuffer)); }); it('should return false for an erroneous string', function () { @@ -38,16 +38,16 @@ describe('Validation', function () { 0x65, 0x64, 0x69, 0x74, 0x65, 0x64 ]); - assert.ok(!isValidUTF8(invalidBuffer)); + assert.ok(!validation.isValidUTF8(invalidBuffer)); }); it('should return true for valid cases from the autobahn test suite', function () { - assert.ok(isValidUTF8(Buffer.from([0xf0, 0x90, 0x80, 0x80]))); - assert.ok(isValidUTF8(Buffer.from('\xf0\x90\x80\x80'))); + assert.ok(validation.isValidUTF8(Buffer.from([0xf0, 0x90, 0x80, 0x80]))); + assert.ok(validation.isValidUTF8(Buffer.from('\xf0\x90\x80\x80'))); }); it('should return false for erroneous autobahn strings', function () { - assert.ok(!isValidUTF8(Buffer.from([0xce, 0xba, 0xe1, 0xbd]))); + assert.ok(!validation.isValidUTF8(Buffer.from([0xce, 0xba, 0xe1, 0xbd]))); }); }); }); From 3936f3a8a8b3e37e8d43ebcb9558d226d44fb964 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 23 Dec 2017 15:05:13 +0100 Subject: [PATCH 446/669] [major] Rename modules files --- index.js | 8 +-- lib/{BufferUtil.js => buffer-util.js} | 0 lib/{Constants.js => constants.js} | 0 lib/{EventTarget.js => event-target.js} | 0 lib/{Extensions.js => extension.js} | 0 ...essageDeflate.js => permessage-deflate.js} | 2 +- lib/{Receiver.js => receiver.js} | 8 +-- lib/{Sender.js => sender.js} | 8 +-- lib/{Validation.js => validation.js} | 0 ...WebSocketServer.js => websocket-server.js} | 12 ++--- lib/{WebSocket.js => websocket.js} | 20 +++---- .../{Extensions.test.js => extension.test.js} | 44 ++++++++-------- ...ate.test.js => permessage-deflate.test.js} | 52 +++++++++---------- test/{Receiver.test.js => receiver.test.js} | 6 +-- test/{Sender.test.js => sender.test.js} | 4 +- ...{Validation.test.js => validation.test.js} | 4 +- ...erver.test.js => websocket-server.test.js} | 3 +- ...ntegration.js => websocket.integration.js} | 0 test/{WebSocket.test.js => websocket.test.js} | 2 +- 19 files changed, 86 insertions(+), 87 deletions(-) rename lib/{BufferUtil.js => buffer-util.js} (100%) rename lib/{Constants.js => constants.js} (100%) rename lib/{EventTarget.js => event-target.js} (100%) rename lib/{Extensions.js => extension.js} (100%) rename lib/{PerMessageDeflate.js => permessage-deflate.js} (99%) rename lib/{Receiver.js => receiver.js} (98%) rename lib/{Sender.js => sender.js} (98%) rename lib/{Validation.js => validation.js} (100%) rename lib/{WebSocketServer.js => websocket-server.js} (97%) rename lib/{WebSocket.js => websocket.js} (97%) rename test/{Extensions.test.js => extension.test.js} (75%) rename test/{PerMessageDeflate.test.js => permessage-deflate.test.js} (88%) rename test/{Receiver.test.js => receiver.test.js} (99%) rename test/{Sender.test.js => sender.test.js} (98%) rename test/{Validation.test.js => validation.test.js} (96%) rename test/{WebSocketServer.test.js => websocket-server.test.js} (99%) rename test/{WebSocket.integration.js => websocket.integration.js} (100%) rename test/{WebSocket.test.js => websocket.test.js} (99%) diff --git a/index.js b/index.js index 489e16942..4a7715f4a 100644 --- a/index.js +++ b/index.js @@ -6,10 +6,10 @@ 'use strict'; -const WebSocket = require('./lib/WebSocket'); +const WebSocket = require('./lib/websocket'); -WebSocket.Server = require('./lib/WebSocketServer'); -WebSocket.Receiver = require('./lib/Receiver'); -WebSocket.Sender = require('./lib/Sender'); +WebSocket.Server = require('./lib/websocket-server'); +WebSocket.Receiver = require('./lib/receiver'); +WebSocket.Sender = require('./lib/sender'); module.exports = WebSocket; diff --git a/lib/BufferUtil.js b/lib/buffer-util.js similarity index 100% rename from lib/BufferUtil.js rename to lib/buffer-util.js diff --git a/lib/Constants.js b/lib/constants.js similarity index 100% rename from lib/Constants.js rename to lib/constants.js diff --git a/lib/EventTarget.js b/lib/event-target.js similarity index 100% rename from lib/EventTarget.js rename to lib/event-target.js diff --git a/lib/Extensions.js b/lib/extension.js similarity index 100% rename from lib/Extensions.js rename to lib/extension.js diff --git a/lib/PerMessageDeflate.js b/lib/permessage-deflate.js similarity index 99% rename from lib/PerMessageDeflate.js rename to lib/permessage-deflate.js index b452b574b..5db327f69 100644 --- a/lib/PerMessageDeflate.js +++ b/lib/permessage-deflate.js @@ -4,7 +4,7 @@ const safeBuffer = require('safe-buffer'); const Limiter = require('async-limiter'); const zlib = require('zlib'); -const bufferUtil = require('./BufferUtil'); +const bufferUtil = require('./buffer-util'); const Buffer = safeBuffer.Buffer; diff --git a/lib/Receiver.js b/lib/receiver.js similarity index 98% rename from lib/Receiver.js rename to lib/receiver.js index 546d40cf0..cc761f40d 100644 --- a/lib/Receiver.js +++ b/lib/receiver.js @@ -8,10 +8,10 @@ const safeBuffer = require('safe-buffer'); -const PerMessageDeflate = require('./PerMessageDeflate'); -const validation = require('./Validation'); -const bufferUtil = require('./BufferUtil'); -const constants = require('./Constants'); +const PerMessageDeflate = require('./permessage-deflate'); +const bufferUtil = require('./buffer-util'); +const validation = require('./validation'); +const constants = require('./constants'); const Buffer = safeBuffer.Buffer; diff --git a/lib/Sender.js b/lib/sender.js similarity index 98% rename from lib/Sender.js rename to lib/sender.js index 3c19f046f..4526d2204 100644 --- a/lib/Sender.js +++ b/lib/sender.js @@ -9,10 +9,10 @@ const safeBuffer = require('safe-buffer'); const crypto = require('crypto'); -const PerMessageDeflate = require('./PerMessageDeflate'); -const bufferUtil = require('./BufferUtil'); -const validation = require('./Validation'); -const constants = require('./Constants'); +const PerMessageDeflate = require('./permessage-deflate'); +const bufferUtil = require('./buffer-util'); +const validation = require('./validation'); +const constants = require('./constants'); const Buffer = safeBuffer.Buffer; diff --git a/lib/Validation.js b/lib/validation.js similarity index 100% rename from lib/Validation.js rename to lib/validation.js diff --git a/lib/WebSocketServer.js b/lib/websocket-server.js similarity index 97% rename from lib/WebSocketServer.js rename to lib/websocket-server.js index b5efb1df7..3ff6690e6 100644 --- a/lib/WebSocketServer.js +++ b/lib/websocket-server.js @@ -13,10 +13,10 @@ const Ultron = require('ultron'); const http = require('http'); const url = require('url'); -const PerMessageDeflate = require('./PerMessageDeflate'); -const Extensions = require('./Extensions'); -const constants = require('./Constants'); -const WebSocket = require('./WebSocket'); +const PerMessageDeflate = require('./permessage-deflate'); +const extension = require('./extension'); +const constants = require('./constants'); +const WebSocket = require('./websocket'); const Buffer = safeBuffer.Buffer; @@ -171,7 +171,7 @@ class WebSocketServer extends EventEmitter { ); try { - const offers = Extensions.parse( + const offers = extension.parse( req.headers['sec-websocket-extensions'] ); @@ -261,7 +261,7 @@ class WebSocketServer extends EventEmitter { if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); if (extensions[PerMessageDeflate.extensionName]) { const params = extensions[PerMessageDeflate.extensionName].params; - const value = Extensions.format({ + const value = extension.format({ [PerMessageDeflate.extensionName]: [params] }); headers.push(`Sec-WebSocket-Extensions: ${value}`); diff --git a/lib/WebSocket.js b/lib/websocket.js similarity index 97% rename from lib/WebSocket.js rename to lib/websocket.js index 4b7f044df..31f4ba7a6 100644 --- a/lib/WebSocket.js +++ b/lib/websocket.js @@ -13,12 +13,12 @@ const https = require('https'); const http = require('http'); const url = require('url'); -const PerMessageDeflate = require('./PerMessageDeflate'); -const EventTarget = require('./EventTarget'); -const Extensions = require('./Extensions'); -const constants = require('./Constants'); -const Receiver = require('./Receiver'); -const Sender = require('./Sender'); +const PerMessageDeflate = require('./permessage-deflate'); +const EventTarget = require('./event-target'); +const extension = require('./extension'); +const constants = require('./constants'); +const Receiver = require('./receiver'); +const Sender = require('./sender'); const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']; const protocolVersions = [8, 13]; @@ -589,7 +589,7 @@ function initAsClient (address, protocols, options) { options.perMessageDeflate !== true ? options.perMessageDeflate : {}, false ); - requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format({ + requestOptions.headers['Sec-WebSocket-Extensions'] = extension.format({ [PerMessageDeflate.extensionName]: perMessageDeflate.offer() }); } @@ -724,13 +724,13 @@ function initAsClient (address, protocols, options) { if (perMessageDeflate) { try { - const serverExtensions = Extensions.parse( + const extensions = extension.parse( res.headers['sec-websocket-extensions'] ); - if (serverExtensions[PerMessageDeflate.extensionName]) { + if (extensions[PerMessageDeflate.extensionName]) { perMessageDeflate.accept( - serverExtensions[PerMessageDeflate.extensionName] + extensions[PerMessageDeflate.extensionName] ); this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } diff --git a/test/Extensions.test.js b/test/extension.test.js similarity index 75% rename from test/Extensions.test.js rename to test/extension.test.js index 8f23d05e2..15d1787a6 100644 --- a/test/Extensions.test.js +++ b/test/extension.test.js @@ -2,23 +2,23 @@ const assert = require('assert'); -const Extensions = require('../lib/Extensions'); +const extension = require('../lib/extension'); -describe('Extensions', function () { +describe('extension', function () { describe('parse', function () { it('returns an empty object if the argument is undefined', function () { - assert.deepStrictEqual(Extensions.parse(), {}); - assert.deepStrictEqual(Extensions.parse(''), {}); + assert.deepStrictEqual(extension.parse(), {}); + assert.deepStrictEqual(extension.parse(''), {}); }); it('parses a single extension', function () { - const extensions = Extensions.parse('foo'); + const extensions = extension.parse('foo'); assert.deepStrictEqual(extensions, { foo: [{}] }); }); it('parses params', function () { - const extensions = Extensions.parse('foo;bar;baz=1;bar=2'); + const extensions = extension.parse('foo;bar;baz=1;bar=2'); assert.deepStrictEqual(extensions, { foo: [{ bar: [true, '2'], baz: ['1'] }] @@ -26,7 +26,7 @@ describe('Extensions', function () { }); it('parses multiple extensions', function () { - const extensions = Extensions.parse('foo,bar;baz,foo;baz'); + const extensions = extension.parse('foo,bar;baz,foo;baz'); assert.deepStrictEqual(extensions, { foo: [{}, { baz: [true] }], @@ -35,30 +35,30 @@ describe('Extensions', function () { }); it('parses quoted params', function () { - assert.deepStrictEqual(Extensions.parse('foo;bar="hi"'), { + assert.deepStrictEqual(extension.parse('foo;bar="hi"'), { foo: [{ bar: ['hi'] }] }); - assert.deepStrictEqual(Extensions.parse('foo;bar="\\0"'), { + assert.deepStrictEqual(extension.parse('foo;bar="\\0"'), { foo: [{ bar: ['0'] }] }); - assert.deepStrictEqual(Extensions.parse('foo;bar="b\\a\\z"'), { + assert.deepStrictEqual(extension.parse('foo;bar="b\\a\\z"'), { foo: [{ bar: ['baz'] }] }); - assert.deepStrictEqual(Extensions.parse('foo;bar="b\\az";bar'), { + assert.deepStrictEqual(extension.parse('foo;bar="b\\az";bar'), { foo: [{ bar: ['baz', true] }] }); assert.throws( - () => Extensions.parse('foo;bar="baz"qux'), + () => extension.parse('foo;bar="baz"qux'), /^SyntaxError: Unexpected character at index 13$/ ); assert.throws( - () => Extensions.parse('foo;bar="baz" qux'), + () => extension.parse('foo;bar="baz" qux'), /^SyntaxError: Unexpected character at index 14$/ ); }); it('works with names that match Object.prototype property names', function () { - const parse = Extensions.parse; + const parse = extension.parse; assert.deepStrictEqual(parse('hasOwnProperty, toString'), { hasOwnProperty: [{}], @@ -72,7 +72,7 @@ describe('Extensions', function () { it('ignores the optional white spaces', function () { const header = 'foo; bar\t; \tbaz=1\t ; bar="1"\t\t, \tqux\t ;norf '; - assert.deepStrictEqual(Extensions.parse(header), { + assert.deepStrictEqual(extension.parse(header), { foo: [{ bar: [true, '1'], baz: ['1'] }], qux: [{ norf: [true] }] }); @@ -91,7 +91,7 @@ describe('Extensions', function () { ['foo;bar=""', 9] ].forEach((element) => { assert.throws( - () => Extensions.parse(element[0]), + () => extension.parse(element[0]), new RegExp(`^SyntaxError: Unexpected character at index ${element[1]}$`) ); }); @@ -105,7 +105,7 @@ describe('Extensions', function () { ['foo;bar= ', 8] ].forEach((element) => { assert.throws( - () => Extensions.parse(element[0]), + () => extension.parse(element[0]), new RegExp(`^SyntaxError: Unexpected character at index ${element[1]}$`) ); }); @@ -129,7 +129,7 @@ describe('Extensions', function () { ['foo;bar="\\\\"', 10] ].forEach((element) => { assert.throws( - () => Extensions.parse(element[0]), + () => extension.parse(element[0]), new RegExp(`^SyntaxError: Unexpected character at index ${element[1]}$`) ); }); @@ -146,7 +146,7 @@ describe('Extensions', function () { 'foo;bar="1\\' ].forEach((header) => { assert.throws( - () => Extensions.parse(header), + () => extension.parse(header), /^SyntaxError: Unexpected end of input$/ ); }); @@ -155,19 +155,19 @@ describe('Extensions', function () { describe('format', function () { it('formats a single extension', function () { - const extensions = Extensions.format({ foo: {} }); + const extensions = extension.format({ foo: {} }); assert.strictEqual(extensions, 'foo'); }); it('formats params', function () { - const extensions = Extensions.format({ foo: { bar: [true, 2], baz: 1 } }); + const extensions = extension.format({ foo: { bar: [true, 2], baz: 1 } }); assert.strictEqual(extensions, 'foo; bar; bar=2; baz=1'); }); it('formats multiple extensions', function () { - const extensions = Extensions.format({ + const extensions = extension.format({ foo: [{}, { baz: true }], bar: { baz: true } }); diff --git a/test/PerMessageDeflate.test.js b/test/permessage-deflate.test.js similarity index 88% rename from test/PerMessageDeflate.test.js rename to test/permessage-deflate.test.js index 5f74b9d3d..76dc8aee6 100644 --- a/test/PerMessageDeflate.test.js +++ b/test/permessage-deflate.test.js @@ -3,8 +3,8 @@ const safeBuffer = require('safe-buffer'); const assert = require('assert'); -const PerMessageDeflate = require('../lib/PerMessageDeflate'); -const Extensions = require('../lib/Extensions'); +const PerMessageDeflate = require('../lib/permessage-deflate'); +const extension = require('../lib/extension'); const Buffer = safeBuffer.Buffer; @@ -46,7 +46,7 @@ describe('PerMessageDeflate', function () { it('should accept offer', function () { const perMessageDeflate = new PerMessageDeflate({}, true); - const extensions = Extensions.parse( + const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; ' + 'client_no_context_takeover; server_max_window_bits=10; ' + 'client_max_window_bits=11' @@ -67,7 +67,7 @@ describe('PerMessageDeflate', function () { serverMaxWindowBits: 12, clientMaxWindowBits: 11 }, true); - const extensions = Extensions.parse( + const extensions = extension.parse( 'permessage-deflate; server_max_window_bits=14; client_max_window_bits=13' ); @@ -81,7 +81,7 @@ describe('PerMessageDeflate', function () { it('should fallback', function () { const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); - const extensions = Extensions.parse( + const extensions = extension.parse( 'permessage-deflate; server_max_window_bits=10, permessage-deflate' ); @@ -92,28 +92,28 @@ describe('PerMessageDeflate', function () { it('should throw an error if server_no_context_takeover is unsupported', function () { const perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: false }, true); - const extensions = Extensions.parse('permessage-deflate; server_no_context_takeover'); + const extensions = extension.parse('permessage-deflate; server_no_context_takeover'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if server_max_window_bits is unsupported', function () { const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: false }, true); - const extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10'); + const extensions = extension.parse('permessage-deflate; server_max_window_bits=10'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if server_max_window_bits is less than configuration', function () { const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); - const extensions = Extensions.parse('permessage-deflate; server_max_window_bits=10'); + const extensions = extension.parse('permessage-deflate; server_max_window_bits=10'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_max_window_bits is unsupported on client', function () { const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }, true); - const extensions = Extensions.parse('permessage-deflate'); + const extensions = extension.parse('permessage-deflate'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); @@ -128,7 +128,7 @@ describe('PerMessageDeflate', function () { it('should accept response parameter', function () { const perMessageDeflate = new PerMessageDeflate({}); - const extensions = Extensions.parse( + const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; ' + 'client_no_context_takeover; server_max_window_bits=10; ' + 'client_max_window_bits=11' @@ -144,21 +144,21 @@ describe('PerMessageDeflate', function () { it('should throw an error if client_no_context_takeover is unsupported', function () { const perMessageDeflate = new PerMessageDeflate({ clientNoContextTakeover: false }); - const extensions = Extensions.parse('permessage-deflate; client_no_context_takeover'); + const extensions = extension.parse('permessage-deflate; client_no_context_takeover'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_max_window_bits is unsupported', function () { const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: false }); - const extensions = Extensions.parse('permessage-deflate; client_max_window_bits=10'); + const extensions = extension.parse('permessage-deflate; client_max_window_bits=10'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_max_window_bits is greater than configuration', function () { const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }); - const extensions = Extensions.parse('permessage-deflate; client_max_window_bits=11'); + const extensions = extension.parse('permessage-deflate; client_max_window_bits=11'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); @@ -177,7 +177,7 @@ describe('PerMessageDeflate', function () { describe('validate parameters', function () { it('should throw an error if a parameter has multiple values', function () { const perMessageDeflate = new PerMessageDeflate(); - const extensions = Extensions.parse( + const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; server_no_context_takeover' ); @@ -186,14 +186,14 @@ describe('PerMessageDeflate', function () { it('should throw an error if server_no_context_takeover has a value', function () { const perMessageDeflate = new PerMessageDeflate(); - const extensions = Extensions.parse('permessage-deflate; server_no_context_takeover=10'); + const extensions = extension.parse('permessage-deflate; server_no_context_takeover=10'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_no_context_takeover has a value', function () { const perMessageDeflate = new PerMessageDeflate(); - const extensions = Extensions.parse('permessage-deflate; client_no_context_takeover=10'); + const extensions = extension.parse('permessage-deflate; client_no_context_takeover=10'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); @@ -201,26 +201,26 @@ describe('PerMessageDeflate', function () { it('should throw an error if server_max_window_bits has an invalid value', function () { const perMessageDeflate = new PerMessageDeflate({}, true); - let extensions = Extensions.parse('permessage-deflate; server_max_window_bits=7'); + let extensions = extension.parse('permessage-deflate; server_max_window_bits=7'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); - extensions = Extensions.parse('permessage-deflate; server_max_window_bits'); + extensions = extension.parse('permessage-deflate; server_max_window_bits'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('should throw an error if client_max_window_bits has an invalid value', function () { const perMessageDeflate = new PerMessageDeflate(); - let extensions = Extensions.parse('permessage-deflate; client_max_window_bits=16'); + let extensions = extension.parse('permessage-deflate; client_max_window_bits=16'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); - extensions = Extensions.parse('permessage-deflate; client_max_window_bits'); + extensions = extension.parse('permessage-deflate; client_max_window_bits'); assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); it('throws an error if a parameter has an invalid name', function () { const perMessageDeflate = new PerMessageDeflate(); - const extensions = Extensions.parse('permessage-deflate;foo'); + const extensions = extension.parse('permessage-deflate;foo'); assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), @@ -279,7 +279,7 @@ describe('PerMessageDeflate', function () { memLevel: 5, level: 9 }); - const extensions = Extensions.parse( + const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; ' + 'client_no_context_takeover; server_max_window_bits=10; ' + 'client_max_window_bits=11' @@ -317,8 +317,8 @@ describe('PerMessageDeflate', function () { const srcData = 'Some compressible data, it\'s compressible.'; const srcDataBuffer = Buffer.from(srcData, 'utf8'); - perMessageDeflateLev0.accept(Extensions.parse(extensionStr)['permessage-deflate']); - perMessageDeflateLev9.accept(Extensions.parse(extensionStr)['permessage-deflate']); + perMessageDeflateLev0.accept(extension.parse(extensionStr)['permessage-deflate']); + perMessageDeflateLev9.accept(extension.parse(extensionStr)['permessage-deflate']); perMessageDeflateLev0.compress(srcDataBuffer, true, (err, compressed1) => { if (err) return done(err); @@ -349,7 +349,7 @@ describe('PerMessageDeflate', function () { it('should compress/decompress data with no context takeover', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); - const extensions = Extensions.parse( + const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; client_no_context_takeover' ); const buf = Buffer.from('foofoo'); @@ -379,7 +379,7 @@ describe('PerMessageDeflate', function () { it('should compress data between contexts when allowed', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); - const extensions = Extensions.parse('permessage-deflate'); + const extensions = extension.parse('permessage-deflate'); const buf = Buffer.from('foofoo'); perMessageDeflate.accept(extensions['permessage-deflate']); diff --git a/test/Receiver.test.js b/test/receiver.test.js similarity index 99% rename from test/Receiver.test.js rename to test/receiver.test.js index b6affbd00..d96528ee2 100644 --- a/test/Receiver.test.js +++ b/test/receiver.test.js @@ -4,9 +4,9 @@ const safeBuffer = require('safe-buffer'); const assert = require('assert'); const crypto = require('crypto'); -const PerMessageDeflate = require('../lib/PerMessageDeflate'); -const Receiver = require('../lib/Receiver'); -const Sender = require('../lib/Sender'); +const PerMessageDeflate = require('../lib/permessage-deflate'); +const Receiver = require('../lib/receiver'); +const Sender = require('../lib/sender'); const Buffer = safeBuffer.Buffer; diff --git a/test/Sender.test.js b/test/sender.test.js similarity index 98% rename from test/Sender.test.js rename to test/sender.test.js index ac19d1ecd..69d4012e2 100644 --- a/test/Sender.test.js +++ b/test/sender.test.js @@ -3,8 +3,8 @@ const safeBuffer = require('safe-buffer'); const assert = require('assert'); -const PerMessageDeflate = require('../lib/PerMessageDeflate'); -const Sender = require('../lib/Sender'); +const PerMessageDeflate = require('../lib/permessage-deflate'); +const Sender = require('../lib/sender'); const Buffer = safeBuffer.Buffer; diff --git a/test/Validation.test.js b/test/validation.test.js similarity index 96% rename from test/Validation.test.js rename to test/validation.test.js index b2f38d3a1..d7dcad28e 100644 --- a/test/Validation.test.js +++ b/test/validation.test.js @@ -3,11 +3,11 @@ const safeBuffer = require('safe-buffer'); const assert = require('assert'); -const validation = require('../lib/Validation'); +const validation = require('../lib/validation'); const Buffer = safeBuffer.Buffer; -describe('Validation', function () { +describe('validation', function () { describe('isValidUTF8', function () { it('should return true for a valid utf8 string', function () { const validBuffer = Buffer.from( diff --git a/test/WebSocketServer.test.js b/test/websocket-server.test.js similarity index 99% rename from test/WebSocketServer.test.js rename to test/websocket-server.test.js index 8e97c9bb0..0d8c86737 100644 --- a/test/WebSocketServer.test.js +++ b/test/websocket-server.test.js @@ -277,7 +277,6 @@ describe('WebSocketServer', function () { }); it('maxpayload is passed on to permessage-deflate', function (done) { - const PerMessageDeflate = require('../lib/PerMessageDeflate'); const maxPayload = 20480; const wss = new WebSocket.Server({ perMessageDeflate: true, @@ -290,7 +289,7 @@ describe('WebSocketServer', function () { wss.on('connection', (client) => { assert.strictEqual( - client._receiver._extensions[PerMessageDeflate.extensionName]._maxPayload, + client._receiver._extensions['permessage-deflate']._maxPayload, maxPayload ); wss.close(done); diff --git a/test/WebSocket.integration.js b/test/websocket.integration.js similarity index 100% rename from test/WebSocket.integration.js rename to test/websocket.integration.js diff --git a/test/WebSocket.test.js b/test/websocket.test.js similarity index 99% rename from test/WebSocket.test.js rename to test/websocket.test.js index aa4fced44..5356c7ee8 100644 --- a/test/WebSocket.test.js +++ b/test/websocket.test.js @@ -12,7 +12,7 @@ const net = require('net'); const fs = require('fs'); const os = require('os'); -const constants = require('../lib/Constants'); +const constants = require('../lib/constants'); const WebSocket = require('..'); const Buffer = safeBuffer.Buffer; From 63e275e75f8ab8f4385e7ef104a74c582cd0c793 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 23 Dec 2017 17:54:00 +0100 Subject: [PATCH 447/669] [fix] Pass an `ErrorEvent` to the `onerror` event handler Fixes #1173 --- lib/event-target.js | 27 +++++++++++++++++++++++---- test/websocket.test.js | 37 ++++++++----------------------------- 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/lib/event-target.js b/lib/event-target.js index c48137b91..574e9080e 100644 --- a/lib/event-target.js +++ b/lib/event-target.js @@ -78,6 +78,27 @@ class OpenEvent extends Event { } } +/** + * Class representing an error event. + * + * @extends Event + * @private + */ +class ErrorEvent extends Event { + /** + * Create a new `ErrorEvent`. + * + * @param {Object} error The error that generated this event + * @param {WebSocket} target A reference to the target to which the event was dispatched + */ + constructor (error, target) { + super('error', target); + + this.message = error.message; + this.error = error; + } +} + /** * This provides methods for emulating the `EventTarget` interface. It's not * meant to be used directly. @@ -103,10 +124,8 @@ const EventTarget = { listener.call(this, new CloseEvent(code, message, this)); } - function onError (event) { - event.type = 'error'; - event.target = this; - listener.call(this, event); + function onError (error) { + listener.call(this, new ErrorEvent(error, this)); } function onOpen () { diff --git a/test/websocket.test.js b/test/websocket.test.js index 5356c7ee8..634340349 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -1656,52 +1656,31 @@ describe('WebSocket', function () { wss.on('connection', (client) => client.close(1001, 'some daft reason')); }); - it('should have target set on Events', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.addEventListener('open', (openEvent) => { - assert.strictEqual(openEvent.target, ws); - }); - ws.addEventListener('message', (messageEvent) => { - assert.strictEqual(messageEvent.target, ws); - wss.close(); - }); - ws.addEventListener('close', (closeEvent) => { - assert.strictEqual(closeEvent.target, ws); - ws.emit('error', new Error('forced')); - }); - ws.addEventListener('error', (errorEvent) => { - assert.strictEqual(errorEvent.message, 'forced'); - assert.strictEqual(errorEvent.target, ws); - - done(); - }); - }); - - wss.on('connection', (client) => client.send('hi')); - }); - - it('should have type set on Events', function (done) { + it('sets `target` and `type` on events', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { + const err = new Error('forced'); const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.addEventListener('open', (openEvent) => { assert.strictEqual(openEvent.type, 'open'); + assert.strictEqual(openEvent.target, ws); }); ws.addEventListener('message', (messageEvent) => { assert.strictEqual(messageEvent.type, 'message'); + assert.strictEqual(messageEvent.target, ws); wss.close(); }); ws.addEventListener('close', (closeEvent) => { assert.strictEqual(closeEvent.type, 'close'); - ws.emit('error', new Error('forced')); + assert.strictEqual(closeEvent.target, ws); + ws.emit('error', err); }); ws.addEventListener('error', (errorEvent) => { assert.strictEqual(errorEvent.message, 'forced'); assert.strictEqual(errorEvent.type, 'error'); + assert.strictEqual(errorEvent.target, ws); + assert.strictEqual(errorEvent.error, err); done(); }); From 30c9f715247b2f5ad0c691e535708f894d82ed2b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 24 Dec 2017 10:11:49 +0100 Subject: [PATCH 448/669] [major] Make `WebSocket#p{i,o}ng()` accept an optional callback Fixes #599 --- README.md | 4 ++- doc/ws.md | 14 ++++----- lib/sender.js | 24 ++++++++------- lib/websocket.js | 55 +++++++++++++++++++++++------------ test/websocket.test.js | 66 +++++++++++++++++++++++++----------------- 5 files changed, 101 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index e2cc3bd38..12f06c16c 100644 --- a/README.md +++ b/README.md @@ -298,6 +298,8 @@ const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 }); +function noop() {} + function heartbeat() { this.isAlive = true; } @@ -312,7 +314,7 @@ const interval = setInterval(function ping() { if (ws.isAlive === false) return ws.terminate(); ws.isAlive = false; - ws.ping('', false, true); + ws.ping(noop); }); }, 30000); ``` diff --git a/doc/ws.md b/doc/ws.md index b2962b2ff..c9cb973eb 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -355,23 +355,23 @@ receives an `OpenEvent` named "open". Pause the socket. -### websocket.ping([data[, mask[, failSilently]]]) +### websocket.ping([data[, mask]][, callback]) - `data` {Any} The data to send in the ping frame. - `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults to `true` when `websocket` is not a server client. -- `failSilently` {Boolean} Specifies whether or not to throw an error if the - connection is not open. +- `callback` {Function} An optional callback which is invoked when the ping + frame is written out. Send a ping. -### websocket.pong([data[, mask[, failSilently]]]) +### websocket.pong([data[, mask]][, callback]) - `data` {Any} The data to send in the pong frame. - `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults to `true` when `websocket` is not a server client. -- `failSilently` {Boolean} Specifies whether or not to throw an error if the - connection is not open. +- `callback` {Function} An optional callback which is invoked when the pong + frame is written out. Send a pong. @@ -404,7 +404,7 @@ Removes an event listener emulating the `EventTarget` interface. Resume the socket. -### websocket.send(data, [options][, callback]) +### websocket.send(data[, options][, callback]) - `data` {Any} The data to send. - `options` {Object} diff --git a/lib/sender.js b/lib/sender.js index 4526d2204..61d73df30 100644 --- a/lib/sender.js +++ b/lib/sender.js @@ -158,9 +158,10 @@ class Sender { * * @param {*} data The message to send * @param {Boolean} mask Specifies whether or not to mask `data` + * @param {Function} cb Callback * @public */ - ping (data, mask) { + ping (data, mask, cb) { var readOnly = true; if (!Buffer.isBuffer(data)) { @@ -175,9 +176,9 @@ class Sender { } if (this._deflating) { - this.enqueue([this.doPing, data, mask, readOnly]); + this.enqueue([this.doPing, data, mask, readOnly, cb]); } else { - this.doPing(data, mask, readOnly); + this.doPing(data, mask, readOnly, cb); } } @@ -187,16 +188,17 @@ class Sender { * @param {*} data The message to send * @param {Boolean} mask Specifies whether or not to mask `data` * @param {Boolean} readOnly Specifies whether `data` can be modified + * @param {Function} cb Callback * @private */ - doPing (data, mask, readOnly) { + doPing (data, mask, readOnly, cb) { this.sendFrame(Sender.frame(data, { fin: true, rsv1: false, opcode: 0x09, mask, readOnly - })); + }), cb); } /** @@ -204,9 +206,10 @@ class Sender { * * @param {*} data The message to send * @param {Boolean} mask Specifies whether or not to mask `data` + * @param {Function} cb Callback * @public */ - pong (data, mask) { + pong (data, mask, cb) { var readOnly = true; if (!Buffer.isBuffer(data)) { @@ -221,9 +224,9 @@ class Sender { } if (this._deflating) { - this.enqueue([this.doPong, data, mask, readOnly]); + this.enqueue([this.doPong, data, mask, readOnly, cb]); } else { - this.doPong(data, mask, readOnly); + this.doPong(data, mask, readOnly, cb); } } @@ -233,16 +236,17 @@ class Sender { * @param {*} data The message to send * @param {Boolean} mask Specifies whether or not to mask `data` * @param {Boolean} readOnly Specifies whether `data` can be modified + * @param {Function} cb Callback * @private */ - doPong (data, mask, readOnly) { + doPong (data, mask, readOnly, cb) { this.sendFrame(Sender.frame(data, { fin: true, rsv1: false, opcode: 0x0a, mask, readOnly - })); + }), cb); } /** diff --git a/lib/websocket.js b/lib/websocket.js index 31f4ba7a6..1a38497c9 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -141,7 +141,7 @@ class WebSocket extends EventEmitter { this._receiver.onmessage = (data) => this.emit('message', data); this._receiver.onping = (data) => { - this.pong(data, !this._isServer, true); + this.pong(data, !this._isServer, constants.NOOP); this.emit('ping', data); }; this._receiver.onpong = (data) => this.emit('pong', data); @@ -317,47 +317,67 @@ class WebSocket extends EventEmitter { } /** - * Send a ping message. + * Send a ping. * - * @param {*} data The message to send + * @param {*} data The data to send * @param {Boolean} mask Indicates whether or not to mask `data` - * @param {Boolean} failSilently Indicates whether or not to throw if `readyState` isn't `OPEN` + * @param {Function} cb Callback which is executed when the ping is sent * @public */ - ping (data, mask, failSilently) { + ping (data, mask, cb) { + if (typeof data === 'function') { + cb = data; + data = mask = undefined; + } else if (typeof mask === 'function') { + cb = mask; + mask = undefined; + } + if (this.readyState !== WebSocket.OPEN) { - if (failSilently) return; - throw new Error( + const err = new Error( `WebSocket is not open: readyState ${this.readyState} ` + `(${readyStates[this.readyState]})` ); + + if (cb) return cb(err); + throw err; } if (typeof data === 'number') data = data.toString(); if (mask === undefined) mask = !this._isServer; - this._sender.ping(data || constants.EMPTY_BUFFER, mask); + this._sender.ping(data || constants.EMPTY_BUFFER, mask, cb); } /** - * Send a pong message. + * Send a pong. * - * @param {*} data The message to send + * @param {*} data The data to send * @param {Boolean} mask Indicates whether or not to mask `data` - * @param {Boolean} failSilently Indicates whether or not to throw if `readyState` isn't `OPEN` + * @param {Function} cb Callback which is executed when the pong is sent * @public */ - pong (data, mask, failSilently) { + pong (data, mask, cb) { + if (typeof data === 'function') { + cb = data; + data = mask = undefined; + } else if (typeof mask === 'function') { + cb = mask; + mask = undefined; + } + if (this.readyState !== WebSocket.OPEN) { - if (failSilently) return; - throw new Error( + const err = new Error( `WebSocket is not open: readyState ${this.readyState} ` + `(${readyStates[this.readyState]})` ); + + if (cb) return cb(err); + throw err; } if (typeof data === 'number') data = data.toString(); if (mask === undefined) mask = !this._isServer; - this._sender.pong(data || constants.EMPTY_BUFFER, mask); + this._sender.pong(data || constants.EMPTY_BUFFER, mask, cb); } /** @@ -384,9 +404,8 @@ class WebSocket extends EventEmitter { `(${readyStates[this.readyState]})` ); - if (cb) cb(err); - else throw err; - return; + if (cb) return cb(err); + throw err; } if (typeof data === 'number') data = data.toString(); diff --git a/test/websocket.test.js b/test/websocket.test.js index 634340349..30ad6e9e6 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -663,7 +663,7 @@ describe('WebSocket', function () { }); describe('#ping', function () { - it('before connect should fail', function () { + it('throws an error if `readyState` is not `OPEN`', function (done) { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -672,37 +672,44 @@ describe('WebSocket', function () { () => ws.ping(), /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ ); - }); - it('before connect can silently fail', function () { - const ws = new WebSocket('ws://localhost', { - agent: new CustomAgent() + ws.ping((err) => { + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'WebSocket is not open: readyState 0 (CONNECTING)' + ); + done(); }); - - assert.doesNotThrow(() => ws.ping('', true, true)); }); - it('without message is successfully transmitted to the server', function (done) { + it('can send a ping with no data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.ping()); + ws.on('open', () => { + ws.ping(() => ws.ping()); + }); }); wss.on('connection', (ws) => { - ws.on('ping', () => wss.close(done)); + let pings = 0; + ws.on('ping', (data) => { + assert.ok(Buffer.isBuffer(data)); + assert.strictEqual(data.length, 0); + if (++pings === 2) wss.close(done); + }); }); }); - it('with message is successfully transmitted to the server', function (done) { + it('can send a ping with data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { - ws.ping('hi', true); - ws.ping('hi'); + ws.ping('hi', () => ws.ping('hi', true)); }); }); @@ -733,7 +740,7 @@ describe('WebSocket', function () { }); describe('#pong', function () { - it('before connect should fail', () => { + it('throws an error if `readyState` is not `OPEN`', (done) => { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -742,37 +749,44 @@ describe('WebSocket', function () { () => ws.pong(), /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ ); - }); - it('before connect can silently fail', function () { - const ws = new WebSocket('ws://localhost', { - agent: new CustomAgent() + ws.pong((err) => { + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'WebSocket is not open: readyState 0 (CONNECTING)' + ); + done(); }); - - assert.doesNotThrow(() => ws.pong('', true, true)); }); - it('without message is successfully transmitted to the server', function (done) { + it('can send a pong with no data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.pong()); + ws.on('open', () => { + ws.pong(() => ws.pong()); + }); }); wss.on('connection', (ws) => { - ws.on('pong', () => wss.close(done)); + let pongs = 0; + ws.on('pong', (data) => { + assert.ok(Buffer.isBuffer(data)); + assert.strictEqual(data.length, 0); + if (++pongs === 2) wss.close(done); + }); }); }); - it('with message is successfully transmitted to the server', function (done) { + it('can send a pong with data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', () => { - ws.pong('hi', true); - ws.pong('hi'); + ws.pong('hi', () => ws.pong('hi', true)); }); }); From 7f8ebc660896abd586d2abcad63a5533e577b9d6 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 24 Dec 2017 17:40:13 +0100 Subject: [PATCH 449/669] [major] Remove non-standard `protocolVersion` attribute --- doc/ws.md | 6 ------ lib/websocket-server.js | 16 +++------------- lib/websocket.js | 3 --- test/websocket-server.test.js | 14 -------------- test/websocket.test.js | 8 -------- 5 files changed, 3 insertions(+), 44 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index c9cb973eb..b101d1612 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -381,12 +381,6 @@ Send a pong. The subprotocol selected by the server. -### websocket.protocolVersion - -- {Number} - -The WebSocket protocol version used for this connection, 8 or 13. - ### websocket.readyState - {Number} diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 3ff6690e6..0bfc24b1a 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -210,15 +210,7 @@ class WebSocketServer extends EventEmitter { this.options.verifyClient(info, (verified, code, message) => { if (!verified) return abortConnection(socket, code || 401, message); - this.completeUpgrade( - protocol, - extensions, - version, - req, - socket, - head, - cb - ); + this.completeUpgrade(protocol, extensions, req, socket, head, cb); }); return; } @@ -226,7 +218,7 @@ class WebSocketServer extends EventEmitter { if (!this.options.verifyClient(info)) return abortConnection(socket, 401); } - this.completeUpgrade(protocol, extensions, version, req, socket, head, cb); + this.completeUpgrade(protocol, extensions, req, socket, head, cb); } /** @@ -234,14 +226,13 @@ class WebSocketServer extends EventEmitter { * * @param {String} protocol The chosen subprotocol * @param {Object} extensions The accepted extensions - * @param {Number} version The WebSocket protocol version * @param {http.IncomingMessage} req The request object * @param {net.Socket} socket The network socket between the server and client * @param {Buffer} head The first packet of the upgraded stream * @param {Function} cb Callback * @private */ - completeUpgrade (protocol, extensions, version, req, socket, head, cb) { + completeUpgrade (protocol, extensions, req, socket, head, cb) { // // Destroy the socket if the client has already sent a FIN packet. // @@ -276,7 +267,6 @@ class WebSocketServer extends EventEmitter { const client = new WebSocket([socket, head], null, { maxPayload: this.options.maxPayload, - protocolVersion: version, extensions, protocol }); diff --git a/lib/websocket.js b/lib/websocket.js index 1a38497c9..f0953d289 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -496,14 +496,12 @@ module.exports = WebSocket; * @param {net.Socket} socket The network socket between the server and client * @param {Buffer} head The first packet of the upgraded stream * @param {Object} options WebSocket attributes - * @param {Number} options.protocolVersion The WebSocket protocol version * @param {Object} options.extensions The negotiated extensions * @param {Number} options.maxPayload The maximum allowed message size * @param {String} options.protocol The chosen subprotocol * @private */ function initAsServerClient (socket, head, options) { - this.protocolVersion = options.protocolVersion; this._maxPayload = options.maxPayload; this.extensions = options.extensions; this.protocol = options.protocol; @@ -574,7 +572,6 @@ function initAsClient (address, protocols, options) { ); } - this.protocolVersion = options.protocolVersion; this._isServer = false; this.url = address; diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 0d8c86737..64ed60c77 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -906,20 +906,6 @@ describe('WebSocketServer', function () { wss.close(done); }); }); - - it('protocolVersion is exposed', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { - protocolVersion: 8 - }); - }); - - wss.on('connection', (client) => { - assert.strictEqual(client.protocolVersion, 8); - wss.close(done); - }); - }); }); describe('permessage-deflate', function () { diff --git a/test/websocket.test.js b/test/websocket.test.js index 30ad6e9e6..cf69b226c 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -158,14 +158,6 @@ describe('WebSocket', function () { assert.strictEqual(ws.url, url); }); - it('#protocolVersion exposes the protocol version', function () { - const ws = new WebSocket('ws://localhost', { - agent: new CustomAgent() - }); - - assert.strictEqual(ws.protocolVersion, 13); - }); - describe('#bufferedAmount', function () { it('defaults to zero', function () { const ws = new WebSocket('ws://localhost', { From ee9b5f3515f21509242162942d8efb1ec723c836 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 25 Dec 2017 07:43:46 +0100 Subject: [PATCH 450/669] [major] Remove non-standard `bytesReceived` attribute --- doc/ws.md | 6 ------ lib/websocket.js | 6 +----- test/websocket.test.js | 12 ------------ 3 files changed, 1 insertion(+), 23 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index b101d1612..215a3010a 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -302,12 +302,6 @@ of binary protocols transferring large messages with multiple fragments. The number of bytes of data that have been queued using calls to `send()` but not yet transmitted to the network. -### websocket.bytesReceived - -- {Number} - -Received bytes count. - ### websocket.close([code[, reason]]) - `code` {Number} A numeric value indicating the status code explaining why diff --git a/lib/websocket.js b/lib/websocket.js index f0953d289..d0b476084 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -50,7 +50,6 @@ class WebSocket extends EventEmitter { } this.readyState = WebSocket.CONNECTING; - this.bytesReceived = 0; this.extensions = {}; this.protocol = ''; @@ -134,10 +133,7 @@ class WebSocket extends EventEmitter { if (head.length > 0) socket.unshift(head); - this._ultron.on('data', (data) => { - this.bytesReceived += data.length; - this._receiver.add(data); - }); + this._ultron.on('data', (data) => this._receiver.add(data)); this._receiver.onmessage = (data) => this.emit('message', data); this._receiver.onping = (data) => { diff --git a/test/websocket.test.js b/test/websocket.test.js index cf69b226c..2b9836275 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -139,18 +139,6 @@ describe('WebSocket', function () { }); describe('properties', function () { - it('#bytesReceived exposes number of bytes received', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('message', () => { - assert.strictEqual(ws.bytesReceived, 8); - wss.close(done); - }); - }); - wss.on('connection', (ws) => ws.send('foobar')); - }); - it('#url exposes the server url', function () { const url = 'ws://localhost'; const ws = new WebSocket(url, { agent: new CustomAgent() }); From 46461a937f8fe6d25cd83510f7bfda69c23109be Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 25 Dec 2017 11:19:46 +0100 Subject: [PATCH 451/669] [minor] Refactor server client initialization --- lib/websocket-server.js | 22 ++++++++------- lib/websocket.js | 52 ++++++++++------------------------- test/websocket-server.test.js | 13 --------- 3 files changed, 27 insertions(+), 60 deletions(-) diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 0bfc24b1a..0654eb0bf 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -249,13 +249,19 @@ class WebSocketServer extends EventEmitter { `Sec-WebSocket-Accept: ${key}` ]; - if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); + const ws = new WebSocket(null); + + if (protocol) { + headers.push(`Sec-WebSocket-Protocol: ${protocol}`); + ws.protocol = protocol; + } if (extensions[PerMessageDeflate.extensionName]) { const params = extensions[PerMessageDeflate.extensionName].params; const value = extension.format({ [PerMessageDeflate.extensionName]: [params] }); headers.push(`Sec-WebSocket-Extensions: ${value}`); + ws.extensions = extensions; } // @@ -264,20 +270,16 @@ class WebSocketServer extends EventEmitter { this.emit('headers', headers, req); socket.write(headers.concat('\r\n').join('\r\n')); + socket.removeListener('error', socketError); - const client = new WebSocket([socket, head], null, { - maxPayload: this.options.maxPayload, - extensions, - protocol - }); + ws.setSocket(socket, head, this.options.maxPayload); if (this.clients) { - this.clients.add(client); - client.on('close', () => this.clients.delete(client)); + this.clients.add(ws); + ws.on('close', () => this.clients.delete(ws)); } - socket.removeListener('error', socketError); - cb(client); + cb(ws); } } diff --git a/lib/websocket.js b/lib/websocket.js index d0b476084..c1954824b 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -40,15 +40,6 @@ class WebSocket extends EventEmitter { constructor (address, protocols, options) { super(); - if (!protocols) { - protocols = []; - } else if (typeof protocols === 'string') { - protocols = [protocols]; - } else if (!Array.isArray(protocols)) { - options = protocols; - protocols = []; - } - this.readyState = WebSocket.CONNECTING; this.extensions = {}; this.protocol = ''; @@ -61,14 +52,22 @@ class WebSocket extends EventEmitter { this._closeTimer = null; this._finalized = false; this._closeCode = 1006; + this._isServer = true; this._receiver = null; this._sender = null; this._socket = null; this._ultron = null; - if (Array.isArray(address)) { - initAsServerClient.call(this, address[0], address[1], options); - } else { + if (address !== null) { + if (!protocols) { + protocols = []; + } else if (typeof protocols === 'string') { + protocols = [protocols]; + } else if (!Array.isArray(protocols)) { + options = protocols; + protocols = []; + } + initAsClient.call(this, address, protocols, options); } } @@ -116,13 +115,14 @@ class WebSocket extends EventEmitter { * * @param {net.Socket} socket The network socket between the server and client * @param {Buffer} head The first packet of the upgraded stream + * @param {Number} maxPayload The maximum allowed message size * @private */ - setSocket (socket, head) { + setSocket (socket, head, maxPayload) { socket.setTimeout(0); socket.setNoDelay(); - this._receiver = new Receiver(this.extensions, this._maxPayload, this.binaryType); + this._receiver = new Receiver(this.extensions, maxPayload, this.binaryType); this._sender = new Sender(socket, this.extensions); this._ultron = new Ultron(socket); this._socket = socket; @@ -485,28 +485,6 @@ WebSocket.prototype.removeEventListener = EventTarget.removeEventListener; module.exports = WebSocket; -/** - * Initialize a WebSocket server client. - * - * @param {http.IncomingMessage} req The request object - * @param {net.Socket} socket The network socket between the server and client - * @param {Buffer} head The first packet of the upgraded stream - * @param {Object} options WebSocket attributes - * @param {Object} options.extensions The negotiated extensions - * @param {Number} options.maxPayload The maximum allowed message size - * @param {String} options.protocol The chosen subprotocol - * @private - */ -function initAsServerClient (socket, head, options) { - this._maxPayload = options.maxPayload; - this.extensions = options.extensions; - this.protocol = options.protocol; - - this._isServer = true; - - this.setSocket(socket, head); -} - /** * Initialize a WebSocket client. * @@ -753,6 +731,6 @@ function initAsClient (address, protocols, options) { } } - this.setSocket(socket, head); + this.setSocket(socket, head, 0); }); } diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 64ed60c77..ff1b367bc 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -250,19 +250,6 @@ describe('WebSocketServer', function () { }); describe('#maxpayload', function () { - it('maxpayload is passed on to clients', function (done) { - const maxPayload = 20480; - const wss = new WebSocket.Server({ port: 0, maxPayload }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - }); - - wss.on('connection', (client) => { - assert.strictEqual(client._maxPayload, maxPayload); - wss.close(done); - }); - }); - it('maxpayload is passed on to hybi receivers', function (done) { const maxPayload = 20480; const wss = new WebSocket.Server({ port: 0, maxPayload }, () => { From fdec524555a29875b3a449d597a3432e453e87f8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 25 Dec 2017 18:25:47 +0100 Subject: [PATCH 452/669] [fix] Fix `extentions` property type Make `extensions` a getter that returns the negotiated extensions names. Fixes #1244 --- lib/websocket-server.js | 2 +- lib/websocket.js | 47 +++++++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 0654eb0bf..8bc3b2938 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -261,7 +261,7 @@ class WebSocketServer extends EventEmitter { [PerMessageDeflate.extensionName]: [params] }); headers.push(`Sec-WebSocket-Extensions: ${value}`); - ws.extensions = extensions; + ws._extensions = extensions; } // diff --git a/lib/websocket.js b/lib/websocket.js index c1954824b..907de2117 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -41,7 +41,6 @@ class WebSocket extends EventEmitter { super(); this.readyState = WebSocket.CONNECTING; - this.extensions = {}; this.protocol = ''; this._binaryType = constants.BINARY_TYPES[0]; @@ -52,6 +51,7 @@ class WebSocket extends EventEmitter { this._closeTimer = null; this._finalized = false; this._closeCode = 1006; + this._extensions = {}; this._isServer = true; this._receiver = null; this._sender = null; @@ -77,18 +77,6 @@ class WebSocket extends EventEmitter { get CLOSED () { return WebSocket.CLOSED; } get OPEN () { return WebSocket.OPEN; } - /** - * @type {Number} - */ - get bufferedAmount () { - var amount = 0; - - if (this._socket) { - amount = this._socket.bufferSize + this._sender._bufferedBytes; - } - return amount; - } - /** * This deviates from the WHATWG interface since ws doesn't support the required * default "blob" type (instead we define a custom "nodebuffer" type). @@ -110,6 +98,25 @@ class WebSocket extends EventEmitter { if (this._receiver) this._receiver._binaryType = type; } + /** + * @type {Number} + */ + get bufferedAmount () { + var amount = 0; + + if (this._socket) { + amount = this._socket.bufferSize + this._sender._bufferedBytes; + } + return amount; + } + + /** + * @type {String} + */ + get extensions () { + return Object.keys(this._extensions).join(); + } + /** * Set up the socket and the internal resources. * @@ -122,8 +129,8 @@ class WebSocket extends EventEmitter { socket.setTimeout(0); socket.setNoDelay(); - this._receiver = new Receiver(this.extensions, maxPayload, this.binaryType); - this._sender = new Sender(socket, this.extensions); + this._receiver = new Receiver(this._extensions, maxPayload, this.binaryType); + this._sender = new Sender(socket, this._extensions); this._ultron = new Ultron(socket); this._socket = socket; @@ -211,12 +218,10 @@ class WebSocket extends EventEmitter { this.emit('close', this._closeCode, this._closeMessage); - if (this.extensions[PerMessageDeflate.extensionName]) { - this.extensions[PerMessageDeflate.extensionName].cleanup(); + if (this._extensions[PerMessageDeflate.extensionName]) { + this._extensions[PerMessageDeflate.extensionName].cleanup(); } - this.extensions = null; - this.removeAllListeners(); } @@ -413,7 +418,7 @@ class WebSocket extends EventEmitter { fin: true }, options); - if (!this.extensions[PerMessageDeflate.extensionName]) { + if (!this._extensions[PerMessageDeflate.extensionName]) { opts.compress = false; } @@ -722,7 +727,7 @@ function initAsClient (address, protocols, options) { perMessageDeflate.accept( extensions[PerMessageDeflate.extensionName] ); - this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + this._extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } } catch (err) { socket.destroy(); From 9bbc9783681160e3ef98f2be47de1ecef228a1a9 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 26 Dec 2017 10:59:14 +0100 Subject: [PATCH 453/669] [test] Reorganize tests --- test/extension.test.js | 4 +- test/permessage-deflate.test.js | 289 ++++++----- test/validation.test.js | 53 -- test/websocket-server.test.js | 637 ++++++++--------------- test/websocket.test.js | 863 ++++++++++++++++---------------- 5 files changed, 807 insertions(+), 1039 deletions(-) delete mode 100644 test/validation.test.js diff --git a/test/extension.test.js b/test/extension.test.js index 15d1787a6..7e389811b 100644 --- a/test/extension.test.js +++ b/test/extension.test.js @@ -6,7 +6,7 @@ const extension = require('../lib/extension'); describe('extension', function () { describe('parse', function () { - it('returns an empty object if the argument is undefined', function () { + it('returns an empty object if the argument is `undefined`', function () { assert.deepStrictEqual(extension.parse(), {}); assert.deepStrictEqual(extension.parse(''), {}); }); @@ -57,7 +57,7 @@ describe('extension', function () { ); }); - it('works with names that match Object.prototype property names', function () { + it('works with names that match `Object.prototype` property names', function () { const parse = extension.parse; assert.deepStrictEqual(parse('hasOwnProperty, toString'), { diff --git a/test/permessage-deflate.test.js b/test/permessage-deflate.test.js index 76dc8aee6..929315988 100644 --- a/test/permessage-deflate.test.js +++ b/test/permessage-deflate.test.js @@ -10,7 +10,7 @@ const Buffer = safeBuffer.Buffer; describe('PerMessageDeflate', function () { describe('#offer', function () { - it('should create default params', function () { + it('creates an offer', function () { const perMessageDeflate = new PerMessageDeflate(); assert.deepStrictEqual( @@ -19,7 +19,7 @@ describe('PerMessageDeflate', function () { ); }); - it('should create params from options', function () { + it('uses the configuration options', function () { const perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: true, clientNoContextTakeover: true, @@ -37,14 +37,72 @@ describe('PerMessageDeflate', function () { }); describe('#accept', function () { - describe('as server', function () { - it('should accept empty offer', function () { + it('throws an error if a parameter has multiple values', function () { + const perMessageDeflate = new PerMessageDeflate(); + const extensions = extension.parse( + 'permessage-deflate; server_no_context_takeover; server_no_context_takeover' + ); + + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: Parameter "server_no_context_takeover" must have only a single value$/ + ); + }); + + it('throws an error if a parameter has an invalid name', function () { + const perMessageDeflate = new PerMessageDeflate(); + const extensions = extension.parse('permessage-deflate;foo'); + + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: Unknown parameter "foo"$/ + ); + }); + + it('throws an error if client_no_context_takeover has a value', function () { + const perMessageDeflate = new PerMessageDeflate(); + const extensions = extension.parse('permessage-deflate; client_no_context_takeover=10'); + + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^TypeError: Invalid value for parameter "client_no_context_takeover": 10$/ + ); + }); + + it('throws an error if server_no_context_takeover has a value', function () { + const perMessageDeflate = new PerMessageDeflate(); + const extensions = extension.parse('permessage-deflate; server_no_context_takeover=10'); + + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^TypeError: Invalid value for parameter "server_no_context_takeover": 10$/ + ); + }); + + it('throws an error if server_max_window_bits has an invalid value', function () { + const perMessageDeflate = new PerMessageDeflate(); + + let extensions = extension.parse('permessage-deflate; server_max_window_bits=7'); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^TypeError: Invalid value for parameter "server_max_window_bits": 7$/ + ); + + extensions = extension.parse('permessage-deflate; server_max_window_bits'); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^TypeError: Invalid value for parameter "server_max_window_bits": true$/ + ); + }); + + describe('As server', function () { + it('accepts an offer with no parameters', function () { const perMessageDeflate = new PerMessageDeflate({}, true); assert.deepStrictEqual(perMessageDeflate.accept([{}]), {}); }); - it('should accept offer', function () { + it('accepts an offer with parameters', function () { const perMessageDeflate = new PerMessageDeflate({}, true); const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; ' + @@ -60,7 +118,7 @@ describe('PerMessageDeflate', function () { }); }); - it('should prefer configuration than offer', function () { + it('prefers the configuration options', function () { const perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: true, clientNoContextTakeover: true, @@ -79,7 +137,7 @@ describe('PerMessageDeflate', function () { }); }); - it('should fallback', function () { + it('accepts the first supported offer', function () { const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); const extensions = extension.parse( 'permessage-deflate; server_max_window_bits=10, permessage-deflate' @@ -90,43 +148,65 @@ describe('PerMessageDeflate', function () { }); }); - it('should throw an error if server_no_context_takeover is unsupported', function () { + it('throws an error if server_no_context_takeover is unsupported', function () { const perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: false }, true); const extensions = extension.parse('permessage-deflate; server_no_context_takeover'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: None of the extension offers can be accepted$/ + ); }); - it('should throw an error if server_max_window_bits is unsupported', function () { + it('throws an error if server_max_window_bits is unsupported', function () { const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: false }, true); const extensions = extension.parse('permessage-deflate; server_max_window_bits=10'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: None of the extension offers can be accepted$/ + ); }); - it('should throw an error if server_max_window_bits is less than configuration', function () { + it('throws an error if server_max_window_bits is less than configuration', function () { const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); const extensions = extension.parse('permessage-deflate; server_max_window_bits=10'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: None of the extension offers can be accepted$/ + ); }); - it('should throw an error if client_max_window_bits is unsupported on client', function () { + it('throws an error if client_max_window_bits is unsupported on client', function () { const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }, true); const extensions = extension.parse('permessage-deflate'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: None of the extension offers can be accepted$/ + ); + }); + + it('throws an error if client_max_window_bits has an invalid value', function () { + const perMessageDeflate = new PerMessageDeflate({}, true); + + const extensions = extension.parse('permessage-deflate; client_max_window_bits=16'); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^TypeError: Invalid value for parameter "client_max_window_bits": 16$/ + ); }); }); - describe('as client', function () { - it('should accept empty response', function () { + describe('As client', function () { + it('accepts a response with no parameters', function () { const perMessageDeflate = new PerMessageDeflate({}); assert.deepStrictEqual(perMessageDeflate.accept([{}]), {}); }); - it('should accept response parameter', function () { + it('accepts a response with parameters', function () { const perMessageDeflate = new PerMessageDeflate({}); const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; ' + @@ -142,112 +222,83 @@ describe('PerMessageDeflate', function () { }); }); - it('should throw an error if client_no_context_takeover is unsupported', function () { + it('throws an error if client_no_context_takeover is unsupported', function () { const perMessageDeflate = new PerMessageDeflate({ clientNoContextTakeover: false }); const extensions = extension.parse('permessage-deflate; client_no_context_takeover'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: Unexpected parameter "client_no_context_takeover"$/ + ); }); - it('should throw an error if client_max_window_bits is unsupported', function () { + it('throws an error if client_max_window_bits is unsupported', function () { const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: false }); const extensions = extension.parse('permessage-deflate; client_max_window_bits=10'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: Unexpected or invalid parameter "client_max_window_bits"$/ + ); }); - it('should throw an error if client_max_window_bits is greater than configuration', function () { + it('throws an error if client_max_window_bits is greater than configuration', function () { const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }); const extensions = extension.parse('permessage-deflate; client_max_window_bits=11'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); - }); - - it('uses the config value if client_max_window_bits is not specified', function () { - const perMessageDeflate = new PerMessageDeflate({ - clientMaxWindowBits: 10 - }); - - assert.deepStrictEqual(perMessageDeflate.accept([{}]), { - client_max_window_bits: 10 - }); - }); - }); - - describe('validate parameters', function () { - it('should throw an error if a parameter has multiple values', function () { - const perMessageDeflate = new PerMessageDeflate(); - const extensions = extension.parse( - 'permessage-deflate; server_no_context_takeover; server_no_context_takeover' + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^Error: Unexpected or invalid parameter "client_max_window_bits"$/ ); - - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); }); - it('should throw an error if server_no_context_takeover has a value', function () { - const perMessageDeflate = new PerMessageDeflate(); - const extensions = extension.parse('permessage-deflate; server_no_context_takeover=10'); - - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); - }); - - it('should throw an error if client_no_context_takeover has a value', function () { - const perMessageDeflate = new PerMessageDeflate(); - const extensions = extension.parse('permessage-deflate; client_no_context_takeover=10'); - - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); - }); - - it('should throw an error if server_max_window_bits has an invalid value', function () { - const perMessageDeflate = new PerMessageDeflate({}, true); - - let extensions = extension.parse('permessage-deflate; server_max_window_bits=7'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); - - extensions = extension.parse('permessage-deflate; server_max_window_bits'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); - }); - - it('should throw an error if client_max_window_bits has an invalid value', function () { + it('throws an error if client_max_window_bits has an invalid value', function () { const perMessageDeflate = new PerMessageDeflate(); let extensions = extension.parse('permessage-deflate; client_max_window_bits=16'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); + assert.throws( + () => perMessageDeflate.accept(extensions['permessage-deflate']), + /^TypeError: Invalid value for parameter "client_max_window_bits": 16$/ + ); extensions = extension.parse('permessage-deflate; client_max_window_bits'); - assert.throws(() => perMessageDeflate.accept(extensions['permessage-deflate'])); - }); - - it('throws an error if a parameter has an invalid name', function () { - const perMessageDeflate = new PerMessageDeflate(); - const extensions = extension.parse('permessage-deflate;foo'); - assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), - /^Error: Unknown parameter "foo"$/ + /^TypeError: Invalid value for parameter "client_max_window_bits": true$/ ); }); + + it('uses the config value if client_max_window_bits is not specified', function () { + const perMessageDeflate = new PerMessageDeflate({ + clientMaxWindowBits: 10 + }); + + assert.deepStrictEqual(perMessageDeflate.accept([{}]), { + client_max_window_bits: 10 + }); + }); }); }); - describe('#compress/#decompress', function () { - it('should compress/decompress data', function (done) { + describe('#compress and #decompress', function () { + it('works with unfragmented messages', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const buf = Buffer.from([1, 2, 3]); perMessageDeflate.accept([{}]); - perMessageDeflate.compress(Buffer.from([1, 2, 3]), true, (err, compressed) => { + perMessageDeflate.compress(buf, true, (err, data) => { if (err) return done(err); - perMessageDeflate.decompress(compressed, true, (err, data) => { + perMessageDeflate.decompress(data, true, (err, data) => { if (err) return done(err); - assert.ok(data.equals(Buffer.from([1, 2, 3]))); + assert.ok(data.equals(buf)); done(); }); }); }); - it('should compress/decompress fragments', function (done) { + it('works with fragmented messages', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const buf = Buffer.from([1, 2, 3, 4]); @@ -265,7 +316,7 @@ describe('PerMessageDeflate', function () { perMessageDeflate.decompress(compressed2, true, (err, data2) => { if (err) return done(err); - assert.ok(Buffer.concat([data1, data2]).equals(Buffer.from([1, 2, 3, 4]))); + assert.ok(Buffer.concat([data1, data2]).equals(buf)); done(); }); }); @@ -273,7 +324,7 @@ describe('PerMessageDeflate', function () { }); }); - it('should compress/decompress data with parameters', function (done) { + it('works with the negotiated parameters', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0, memLevel: 5, @@ -284,62 +335,54 @@ describe('PerMessageDeflate', function () { 'client_no_context_takeover; server_max_window_bits=10; ' + 'client_max_window_bits=11' ); - const srcData = 'Some compressible data, it\'s compressible.'; + const buf = Buffer.from("Some compressible data, it's compressible."); perMessageDeflate.accept(extensions['permessage-deflate']); - perMessageDeflate.compress(Buffer.from(srcData, 'utf8'), true, (err, compressed) => { + perMessageDeflate.compress(buf, true, (err, data) => { if (err) return done(err); - perMessageDeflate.decompress(compressed, true, (err, data) => { + perMessageDeflate.decompress(data, true, (err, data) => { if (err) return done(err); - assert.ok(data.equals(Buffer.from(srcData, 'utf8'))); + assert.ok(data.equals(buf)); done(); }); }); }); - it('should compress/decompress with level parameter', function (done) { - const perMessageDeflateLev9 = new PerMessageDeflate({ - threshold: 0, - level: 9 - }); - const perMessageDeflateLev0 = new PerMessageDeflate({ - threshold: 0, - level: 0 - }); + it('honors the `level` option', function (done) { + const lev9 = new PerMessageDeflate({ threshold: 0, level: 9 }); + const lev0 = new PerMessageDeflate({ threshold: 0, level: 0 }); const extensionStr = ( 'permessage-deflate; server_no_context_takeover; ' + 'client_no_context_takeover; server_max_window_bits=10; ' + 'client_max_window_bits=11' ); - const srcData = 'Some compressible data, it\'s compressible.'; - const srcDataBuffer = Buffer.from(srcData, 'utf8'); + const buf = Buffer.from("Some compressible data, it's compressible."); - perMessageDeflateLev0.accept(extension.parse(extensionStr)['permessage-deflate']); - perMessageDeflateLev9.accept(extension.parse(extensionStr)['permessage-deflate']); + lev0.accept(extension.parse(extensionStr)['permessage-deflate']); + lev9.accept(extension.parse(extensionStr)['permessage-deflate']); - perMessageDeflateLev0.compress(srcDataBuffer, true, (err, compressed1) => { + lev0.compress(buf, true, (err, compressed1) => { if (err) return done(err); - perMessageDeflateLev0.decompress(compressed1, true, (err, data1) => { + lev0.decompress(compressed1, true, (err, decompressed1) => { if (err) return done(err); - perMessageDeflateLev9.compress(srcDataBuffer, true, (err, compressed2) => { + lev9.compress(buf, true, (err, compressed2) => { if (err) return done(err); - perMessageDeflateLev9.decompress(compressed2, true, (err, data2) => { + lev9.decompress(compressed2, true, (err, decompressed2) => { if (err) return done(err); - // Level 0 compression actually adds a few bytes due to headers - assert.ok(compressed1.length > srcDataBuffer.length); + // Level 0 compression actually adds a few bytes due to headers. + assert.ok(compressed1.length > buf.length); // Level 9 should not, of course. - assert.ok(compressed2.length < compressed1.length); - assert.ok(compressed2.length < srcDataBuffer.length); + assert.ok(compressed2.length < buf.length); // Ensure they both decompress back properly. - assert.ok(data1.equals(srcDataBuffer)); - assert.ok(data2.equals(srcDataBuffer)); + assert.ok(decompressed1.equals(buf)); + assert.ok(decompressed2.equals(buf)); done(); }); }); @@ -347,10 +390,10 @@ describe('PerMessageDeflate', function () { }); }); - it('should compress/decompress data with no context takeover', function (done) { - const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + it("doesn't use contex takeover if not allowed", function (done) { + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }, true); const extensions = extension.parse( - 'permessage-deflate; server_no_context_takeover; client_no_context_takeover' + 'permessage-deflate;server_no_context_takeover' ); const buf = Buffer.from('foofoo'); @@ -362,13 +405,14 @@ describe('PerMessageDeflate', function () { perMessageDeflate.decompress(compressed1, true, (err, data) => { if (err) return done(err); + assert.ok(data.equals(buf)); perMessageDeflate.compress(data, true, (err, compressed2) => { if (err) return done(err); + assert.strictEqual(compressed2.length, compressed1.length); perMessageDeflate.decompress(compressed2, true, (err, data) => { if (err) return done(err); - assert.strictEqual(compressed2.length, compressed1.length); assert.ok(data.equals(buf)); done(); }); @@ -377,8 +421,8 @@ describe('PerMessageDeflate', function () { }); }); - it('should compress data between contexts when allowed', function (done) { - const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + it('uses contex takeover if allowed', function (done) { + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }, true); const extensions = extension.parse('permessage-deflate'); const buf = Buffer.from('foofoo'); @@ -390,13 +434,14 @@ describe('PerMessageDeflate', function () { perMessageDeflate.decompress(compressed1, true, (err, data) => { if (err) return done(err); + assert.ok(data.equals(buf)); perMessageDeflate.compress(data, true, (err, compressed2) => { if (err) return done(err); + assert.ok(compressed2.length < compressed1.length); perMessageDeflate.decompress(compressed2, true, (err, data) => { if (err) return done(err); - assert.ok(compressed2.length < compressed1.length); assert.ok(data.equals(buf)); done(); }); @@ -405,7 +450,7 @@ describe('PerMessageDeflate', function () { }); }); - it('should call the callback when an error occurs (inflate)', function (done) { + it('calls the callback when an error occurs (inflate)', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const data = Buffer.from('something invalid'); @@ -417,7 +462,7 @@ describe('PerMessageDeflate', function () { }); }); - it('should not call the callback twice when `maxPayload` is exceeded', function (done) { + it("doesn't call the callback twice when `maxPayload` is exceeded", function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }, false, 25); const buf = Buffer.from('A'.repeat(50)); const errors = []; diff --git a/test/validation.test.js b/test/validation.test.js deleted file mode 100644 index d7dcad28e..000000000 --- a/test/validation.test.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict'; - -const safeBuffer = require('safe-buffer'); -const assert = require('assert'); - -const validation = require('../lib/validation'); - -const Buffer = safeBuffer.Buffer; - -describe('validation', function () { - describe('isValidUTF8', function () { - it('should return true for a valid utf8 string', function () { - const validBuffer = Buffer.from( - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' + - 'Quisque gravida mattis rhoncus. Donec iaculis, metus ' + - 'quis varius accumsan, erat mauris condimentum diam, et ' + - 'egestas erat enim ut ligula. Praesent sollicitudin tellus ' + - 'eget dolor euismod euismod. Nullam ac augue nec neque ' + - 'varius luctus. Curabitur elit mi, consequat ultricies ' + - 'adipiscing mollis, scelerisque in erat. Phasellus facilisis ' + - ' fermentum ullamcorper. Nulla et sem eu arcu pharetra ' + - 'pellentesque. Praesent consectetur tempor justo, vel ' + - 'iaculis dui ullamcorper sit amet. Integer tristique viverra ' + - 'ullamcorper. Vivamus laoreet, nulla eget suscipit eleifend, ' + - 'lacus lectus feugiat libero, non fermentum erat nisi at ' + - 'risus. Lorem ipsum dolor sit amet, consectetur adipiscing ' + - 'elit. Ut pulvinar dignissim tellus, eu dignissim lorem ' + - 'vulputate quis. Morbi ut pulvinar augue.' - ); - - assert.ok(validation.isValidUTF8(validBuffer)); - }); - - it('should return false for an erroneous string', function () { - const invalidBuffer = Buffer.from([ - 0xce, 0xba, 0xe1, 0xbd, 0xb9, 0xcf, 0x83, - 0xce, 0xbc, 0xce, 0xb5, 0xed, 0xa0, 0x80, - 0x65, 0x64, 0x69, 0x74, 0x65, 0x64 - ]); - - assert.ok(!validation.isValidUTF8(invalidBuffer)); - }); - - it('should return true for valid cases from the autobahn test suite', function () { - assert.ok(validation.isValidUTF8(Buffer.from([0xf0, 0x90, 0x80, 0x80]))); - assert.ok(validation.isValidUTF8(Buffer.from('\xf0\x90\x80\x80'))); - }); - - it('should return false for erroneous autobahn strings', function () { - assert.ok(!validation.isValidUTF8(Buffer.from([0xce, 0xba, 0xe1, 0xbd]))); - }); - }); -}); diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index ff1b367bc..4ba93675f 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -24,6 +24,36 @@ describe('WebSocketServer', function () { assert.throws(() => new WebSocket.Server({})); }); + describe('options', function () { + it('exposes options passed to constructor', function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + assert.strictEqual(wss.options.port, 0); + wss.close(done); + }); + }); + + it('accepts the `maxPayload` option', function (done) { + const maxPayload = 20480; + const wss = new WebSocket.Server({ + perMessageDeflate: true, + maxPayload, + port: 0 + }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); + }); + + wss.on('connection', (ws) => { + assert.strictEqual(ws._receiver._maxPayload, maxPayload); + assert.strictEqual( + ws._receiver._extensions['permessage-deflate']._maxPayload, + maxPayload + ); + wss.close(done); + }); + }); + }); + it('emits an error if http server bind fails', function (done) { const wss1 = new WebSocket.Server({ port: 0 }, () => { const wss2 = new WebSocket.Server({ @@ -119,6 +149,7 @@ describe('WebSocketServer', function () { }); it('closes all clients', function (done) { + let closes = 0; const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -126,7 +157,7 @@ describe('WebSocketServer', function () { if (++closes === 2) done(); }); }); - let closes = 0; + wss.on('connection', (ws) => { ws.on('close', () => { if (++closes === 2) done(); @@ -135,12 +166,12 @@ describe('WebSocketServer', function () { }); }); - it('does not close a precreated server', function (done) { + it("doesn't close a precreated server", function (done) { const server = http.createServer(); const realClose = server.close; server.close = () => { - throw new Error('must not close pre-created server'); + done(new Error('Must not close pre-created server')); }; const wss = new WebSocket.Server({ server }); @@ -168,9 +199,9 @@ describe('WebSocketServer', function () { server.listen(0, () => { wss.close(() => { - assert.strictEqual(server.listeners('listening').length, 0); - assert.strictEqual(server.listeners('upgrade').length, 0); - assert.strictEqual(server.listeners('error').length, 0); + assert.strictEqual(server.listenerCount('listening'), 0); + assert.strictEqual(server.listenerCount('upgrade'), 0); + assert.strictEqual(server.listenerCount('error'), 0); server.close(done); }); @@ -240,50 +271,6 @@ describe('WebSocketServer', function () { }); }); - describe('#options', function () { - it('exposes options passed to constructor', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - assert.strictEqual(wss.options.port, 0); - wss.close(done); - }); - }); - }); - - describe('#maxpayload', function () { - it('maxpayload is passed on to hybi receivers', function (done) { - const maxPayload = 20480; - const wss = new WebSocket.Server({ port: 0, maxPayload }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - }); - - wss.on('connection', (client) => { - assert.strictEqual(client._receiver._maxPayload, maxPayload); - wss.close(done); - }); - }); - - it('maxpayload is passed on to permessage-deflate', function (done) { - const maxPayload = 20480; - const wss = new WebSocket.Server({ - perMessageDeflate: true, - maxPayload, - port: 0 - }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - }); - - wss.on('connection', (client) => { - assert.strictEqual( - client._receiver._extensions['permessage-deflate']._maxPayload, - maxPayload - ); - wss.close(done); - }); - }); - }); - describe('#shouldHandle', function () { it('returns true when the path matches', function () { const wss = new WebSocket.Server({ noServer: true, path: '/foo' }); @@ -291,7 +278,7 @@ describe('WebSocketServer', function () { assert.strictEqual(wss.shouldHandle({ url: '/foo' }), true); }); - it('returns false when the path does not match', function () { + it("returns false when the path doesn't match", function () { const wss = new WebSocket.Server({ noServer: true, path: '/foo' }); assert.strictEqual(wss.shouldHandle({ url: '/bar' }), false); @@ -319,7 +306,7 @@ describe('WebSocketServer', function () { }); }); - it('closes the connection when path does not match', function (done) { + it("closes the connection when path doesn't match", function (done) { const wss = new WebSocket.Server({ port: 0, path: '/ws' }, () => { const req = http.get({ port: wss._server.address().port, @@ -357,8 +344,8 @@ describe('WebSocketServer', function () { }); }); - describe('connection establishing', function () { - it('does not accept connections with no sec-websocket-key', function (done) { + describe('Connection establishing', function () { + it('fails if the Sec-WebSocket-Key header is invalid', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ port: wss._server.address().port, @@ -375,11 +362,11 @@ describe('WebSocketServer', function () { }); wss.on('connection', (ws) => { - done(new Error('connection must not be established')); + done(new Error("Unexpected 'connection' event")); }); }); - it('does not accept connections with no sec-websocket-version', function (done) { + it('fails is the Sec-WebSocket-Version header is invalid (1/2)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ port: wss._server.address().port, @@ -397,11 +384,11 @@ describe('WebSocketServer', function () { }); wss.on('connection', (ws) => { - done(new Error('connection must not be established')); + done(new Error("Unexpected 'connection' event")); }); }); - it('does not accept connections with invalid sec-websocket-version', function (done) { + it('fails is the Sec-WebSocket-Version header is invalid (2/2)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ port: wss._server.address().port, @@ -420,13 +407,13 @@ describe('WebSocketServer', function () { }); wss.on('connection', (ws) => { - done(new Error('connection must not be established')); + done(new Error("Unexpected 'connection' event")); }); }); - it('client can be denied', function (done) { + it('fails is the Sec-WebSocket-Extensions header is invalid', function (done) { const wss = new WebSocket.Server({ - verifyClient: (o) => false, + perMessageDeflate: true, port: 0 }, () => { const req = http.get({ @@ -435,221 +422,151 @@ describe('WebSocketServer', function () { 'Connection': 'Upgrade', 'Upgrade': 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8 + 'Sec-WebSocket-Version': 13, + 'Sec-WebSocket-Extensions': + 'permessage-deflate; server_max_window_bits=foo' } }); req.on('response', (res) => { - assert.strictEqual(res.statusCode, 401); + assert.strictEqual(res.statusCode, 400); wss.close(done); }); }); wss.on('connection', (ws) => { - done(new Error('connection must not be established')); + done(new Error("Unexpected 'connection' event")); }); }); - it('client can be accepted', function (done) { - const wss = new WebSocket.Server({ - port: 0, - verifyClient: (o) => true - }, () => { - http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13 - } - }); - }); - - wss.on('connection', (ws) => wss.close(done)); - }); + describe('`verifyClient`', function () { + it('can reject client synchronously', function (done) { + const wss = new WebSocket.Server({ + verifyClient: (info) => false, + port: 0 + }, () => { + const req = http.get({ + port: wss._server.address().port, + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8 + } + }); - it('verifyClient gets client origin', function (done) { - let verifyClientCalled = false; - const wss = new WebSocket.Server({ - verifyClient: (info) => { - assert.strictEqual(info.origin, 'http://foobarbaz.com'); - verifyClientCalled = true; - return false; - }, - port: 0 - }, () => { - const req = http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Origin': 'http://foobarbaz.com' - } + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 401); + wss.close(done); + }); }); - req.on('response', (res) => { - assert.ok(verifyClientCalled); - wss.close(done); + wss.on('connection', (ws) => { + done(new Error("Unexpected 'connection' event")); }); }); - }); - it('verifyClient gets original request', function (done) { - let verifyClientCalled = false; - const wss = new WebSocket.Server({ - verifyClient: (info) => { - assert.strictEqual( - info.req.headers['sec-websocket-key'], - 'dGhlIHNhbXBsZSBub25jZQ==' - ); - verifyClientCalled = true; - return false; - }, - port: 0 - }, () => { - const req = http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13 - } + it('can accept client synchronously', function (done) { + const server = https.createServer({ + cert: fs.readFileSync('test/fixtures/certificate.pem'), + key: fs.readFileSync('test/fixtures/key.pem') }); - req.on('response', (res) => { - assert.ok(verifyClientCalled); - wss.close(done); + const wss = new WebSocket.Server({ + verifyClient: (info) => { + assert.strictEqual(info.origin, 'https://example.com'); + assert.strictEqual( + info.req.headers['sec-websocket-key'], + 'dGhlIHNhbXBsZSBub25jZQ==' + ); + assert.ok(info.secure, true); + return true; + }, + server }); - }); - }); - it('verifyClient has secure:true for ssl connections', function (done) { - const server = https.createServer({ - cert: fs.readFileSync('test/fixtures/certificate.pem'), - key: fs.readFileSync('test/fixtures/key.pem') - }); - - let success = false; - const wss = new WebSocket.Server({ - verifyClient: (info) => { - success = info.secure === true; - return true; - }, - server - }); - - wss.on('connection', (ws) => { - assert.ok(success); - wss.close(); - server.close(done); - }); + wss.on('connection', (ws) => { + wss.close(); + server.close(done); + }); - server.listen(0, () => { - const ws = new WebSocket(`wss://localhost:${server.address().port}`, { - rejectUnauthorized: false + server.listen(0, () => { + const ws = new WebSocket(`wss://localhost:${server.address().port}`, { + headers: { + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + Origin: 'https://example.com' + }, + rejectUnauthorized: false + }); }); }); - }); - it('verifyClient has secure:false for non-ssl connections', function (done) { - const server = http.createServer(); - - let success = false; - const wss = new WebSocket.Server({ - server: server, - verifyClient: (info) => { - success = info.secure === false; - return true; - } - }); + it('can accept client asynchronously', function (done) { + const wss = new WebSocket.Server({ + verifyClient: (o, cb) => process.nextTick(cb, true), + port: 0 + }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); + }); - wss.on('connection', (ws) => { - assert.ok(success); - wss.close(); - server.close(done); + wss.on('connection', (ws) => wss.close(done)); }); - server.listen(0, () => { - const ws = new WebSocket(`ws://localhost:${server.address().port}`); - }); - }); + it('can reject client asynchronously', function (done) { + const wss = new WebSocket.Server({ + verifyClient: (info, cb) => process.nextTick(cb, false), + port: 0 + }, () => { + const req = http.get({ + port: wss._server.address().port, + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8 + } + }); - it('client can be denied asynchronously', function (done) { - const wss = new WebSocket.Server({ - verifyClient: (o, cb) => process.nextTick(cb, false), - port: 0 - }, () => { - const req = http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8 - } + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 401); + wss.close(done); + }); }); - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 401); - wss.close(done); + wss.on('connection', (ws) => { + done(new Error("Unexpected 'connection' event")); }); }); - wss.on('connection', (ws) => { - done(new Error('connection must not be established')); - }); - }); - - it('client can be denied asynchronously with custom response code', function (done) { - const wss = new WebSocket.Server({ - verifyClient: (o, cb) => process.nextTick(cb, false, 404), - port: 0 - }, () => { - const req = http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8 - } - }); + it('can reject client asynchronously with status code', function (done) { + const wss = new WebSocket.Server({ + verifyClient: (info, cb) => process.nextTick(cb, false, 404), + port: 0 + }, () => { + const req = http.get({ + port: wss._server.address().port, + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8 + } + }); - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 404); - wss.close(done); + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 404); + wss.close(done); + }); }); - }); - - wss.on('connection', (ws) => { - done(new Error('connection must not be established')); - }); - }); - it('client can be accepted asynchronously', function (done) { - const wss = new WebSocket.Server({ - verifyClient: (o, cb) => process.nextTick(cb, true), - port: 0 - }, () => { - http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13 - } + wss.on('connection', (ws) => { + done(new Error("Unexpected 'connection' event")); }); }); - - wss.on('connection', (ws) => wss.close(done)); }); - it('doesn\'t emit the `connection` event if socket is closed prematurely', function (done) { + it("doesn't emit the 'connection' event if socket is closed prematurely", function (done) { const server = http.createServer(); server.listen(0, () => { @@ -659,10 +576,13 @@ describe('WebSocketServer', function () { }); wss.on('connection', () => { - throw new Error('connection event emitted'); + done(new Error("Unexpected 'connection' event")); }); - const socket = net.connect({ port: server.address().port }, () => { + const socket = net.connect({ + port: server.address().port, + allowHalfOpen: true + }, () => { socket.write([ 'GET / HTTP/1.1', 'Host: localhost', @@ -670,8 +590,7 @@ describe('WebSocketServer', function () { 'Connection: Upgrade', 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version: 13', - '', - '' + '\r\n' ].join('\r\n')); }); @@ -684,7 +603,7 @@ describe('WebSocketServer', function () { }); }); - it('handles messages passed along with the upgrade request (upgrade head)', function (done) { + it('handles data passed along with the upgrade request', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.request({ port: wss._server.address().port, @@ -708,98 +627,45 @@ describe('WebSocketServer', function () { }); }); - it('selects the first protocol by default', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); - - ws.on('open', () => { - assert.strictEqual(ws.protocol, 'prot1'); - wss.close(done); - }); - }); - }); - - it('selects the last protocol via protocol handler', function (done) { - const handleProtocols = (protocols, request) => { - assert.ok(request instanceof http.IncomingMessage); - assert.strictEqual(request.url, '/'); - return protocols.pop(); - }; - const wss = new WebSocket.Server({ handleProtocols, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); - - ws.on('open', () => { - assert.strictEqual(ws.protocol, 'prot2'); - wss.close(done); + describe('`handleProtocols`', function () { + it('can select the last protocol', function (done) { + const handleProtocols = (protocols, request) => { + assert.ok(request instanceof http.IncomingMessage); + assert.strictEqual(request.url, '/'); + return protocols.pop(); + }; + const wss = new WebSocket.Server({ handleProtocols, port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`, ['foo', 'bar']); + + ws.on('open', () => { + assert.strictEqual(ws.protocol, 'bar'); + wss.close(done); + }); }); }); - }); - - it('client detects invalid server protocol', function (done) { - const wss = new WebSocket.Server({ - handleProtocols: (ps) => 'prot3', - port: 0 - }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); - - ws.on('open', () => done(new Error('connection must not be established'))); - ws.on('error', () => wss.close(done)); - }); - }); - - it('client detects no server protocol', function (done) { - const wss = new WebSocket.Server({ - handleProtocols: (ps) => {}, - port: 0 - }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, ['prot1', 'prot2']); - - ws.on('open', () => done(new Error('connection must not be established'))); - ws.on('error', () => wss.close(done)); - }); - }); - it('server detects unauthorized protocol handler', function (done) { - const wss = new WebSocket.Server({ - handleProtocols: (ps) => false, - port: 0 - }, () => { - const req = http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13 - } - }); - - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 401); - wss.close(done); - }); - }); - }); + it('closes the connection if return value is `false`', function (done) { + const wss = new WebSocket.Server({ + handleProtocols: (protocols) => false, + port: 0 + }, () => { + const req = http.get({ + port: wss._server.address().port, + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13 + } + }); - it('accept connections with sec-websocket-extensions', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Extensions': 'permessage-foo; x=10' - } + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 401); + wss.close(done); + }); }); }); - - wss.on('connection', (ws) => wss.close(done)); }); it('emits the `headers` event', function (done) { @@ -822,127 +688,40 @@ describe('WebSocketServer', function () { }); }); - describe('messaging', function () { - it('can send and receive data', function (done) { - let data = new Array(65 * 1024); - - for (let i = 0; i < data.length; ++i) { - data[i] = String.fromCharCode(65 + ~~(25 * Math.random())); - } - data = data.join(''); - + describe('permessage-deflate', function () { + it('is disabled by default', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('message', (message) => ws.send(message)); - }); - - wss.on('connection', (client) => { - client.on('message', (message) => { - assert.strictEqual(message, data); - wss.close(done); - }); - - client.send(data); - }); - }); - - it('does not crash when it receives an unhandled opcode', function (done) { - let closed = false; - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}/`); - - ws.on('open', () => ws._socket.write(Buffer.from([0x85, 0x00]))); - ws.on('close', (code, reason) => { - assert.strictEqual(code, 1006); - assert.strictEqual(reason, ''); - assert.ok(closed); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => { - ws.on('error', (err) => { - assert.ok(err instanceof RangeError); - assert.strictEqual( - err.message, - 'Invalid WebSocket frame: invalid opcode 5' - ); - - ws.on('close', (code, reason) => { - assert.strictEqual(code, 1002); - assert.strictEqual(reason, ''); - closed = true; - }); - }); - }); - }); - }); - - describe('client properties', function () { - it('protocol is exposed', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, 'hi'); }); - wss.on('connection', (client) => { - assert.strictEqual(client.protocol, 'hi'); + wss.on('connection', (ws, req) => { + assert.strictEqual( + req.headers['sec-websocket-extensions'], + 'permessage-deflate; client_max_window_bits' + ); + assert.strictEqual(ws.extensions, ''); wss.close(done); }); }); - }); - describe('permessage-deflate', function () { - it('accept connections with permessage-deflate extension', function (done) { + it('uses configuration options', function (done) { const wss = new WebSocket.Server({ - perMessageDeflate: true, + perMessageDeflate: { clientMaxWindowBits: 8 }, port: 0 }, () => { - http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Extensions': 'permessage-deflate; ' + - 'client_max_window_bits=8; server_max_window_bits=8; ' + - 'client_no_context_takeover; server_no_context_takeover' - } - }); - }); - - wss.on('connection', (ws) => wss.close(done)); - }); + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); - it('does not accept connections with invalid extension parameters', function (done) { - const wss = new WebSocket.Server({ - perMessageDeflate: true, - port: 0 - }, () => { - const req = http.get({ - port: wss._server.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Extensions': 'permessage-deflate; server_max_window_bits=foo' - } - }); + ws.on('headers', (headers) => { + assert.strictEqual( + headers['sec-websocket-extensions'], + 'permessage-deflate; client_max_window_bits=8' + ); - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 400); wss.close(done); }); }); - - wss.on('connection', (ws) => { - done(new Error('connection must not be established')); - }); }); }); }); diff --git a/test/websocket.test.js b/test/websocket.test.js index 2b9836275..397cb153b 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -29,124 +29,165 @@ describe('WebSocket', function () { /^Error: Invalid URL: echo\.websocket\.org$/ ); }); - }); - describe('options', function () { - it('accepts an `agent` option', function (done) { - const agent = new CustomAgent(); + describe('options', function () { + it('accepts an `agent` option', function (done) { + const agent = new CustomAgent(); - agent.addRequest = () => { - done(); - }; + agent.addRequest = () => { + done(); + }; - const ws = new WebSocket('ws://localhost', { agent }); - }); + const ws = new WebSocket('ws://localhost', { agent }); + }); - it('accepts the `options` object as the 3rd argument', function () { - const agent = new CustomAgent(); - let count = 0; - let ws; + it('accepts the `options` object as 3rd argument', function () { + const agent = new CustomAgent(); + let count = 0; + let ws; - agent.addRequest = (req) => count++; + agent.addRequest = (req) => count++; - ws = new WebSocket('ws://localhost', undefined, { agent }); - ws = new WebSocket('ws://localhost', null, { agent }); - ws = new WebSocket('ws://localhost', [], { agent }); + ws = new WebSocket('ws://localhost', undefined, { agent }); + ws = new WebSocket('ws://localhost', null, { agent }); + ws = new WebSocket('ws://localhost', [], { agent }); - assert.strictEqual(count, 3); - }); + assert.strictEqual(count, 3); + }); - it('throws an error when using an invalid `protocolVersion`', function () { - const options = { agent: new CustomAgent(), protocolVersion: 1000 }; + it('throws an error when using an invalid `protocolVersion`', function () { + const options = { agent: new CustomAgent(), protocolVersion: 1000 }; - assert.throws( - () => new WebSocket('ws://localhost', options), - /^RangeError: Unsupported protocol version: 1000 \(supported versions: 8, 13\)$/ - ); - }); + assert.throws( + () => new WebSocket('ws://localhost', options), + /^RangeError: Unsupported protocol version: 1000 \(supported versions: 8, 13\)$/ + ); + }); - it('accepts the localAddress option', function (done) { - const wss = new WebSocket.Server({ host: '127.0.0.1', port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { - localAddress: '127.0.0.2' - }); + it('accepts the `localAddress` option', function (done) { + const wss = new WebSocket.Server({ host: '127.0.0.1', port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`, { + localAddress: '127.0.0.2' + }); - ws.on('error', (err) => { - wss.close(() => { - // - // Skip this test on machines where 127.0.0.2 is disabled. - // - if (err.code === 'EADDRNOTAVAIL') return this.skip(); + ws.on('error', (err) => { + wss.close(() => { + // + // Skip this test on machines where 127.0.0.2 is disabled. + // + if (err.code === 'EADDRNOTAVAIL') return this.skip(); - done(err); + done(err); + }); }); }); - }); - wss.on('connection', (ws, req) => { - assert.strictEqual(req.connection.remoteAddress, '127.0.0.2'); - wss.close(done); + wss.on('connection', (ws, req) => { + assert.strictEqual(req.connection.remoteAddress, '127.0.0.2'); + wss.close(done); + }); }); - }); - it('accepts the localAddress option whether it was wrong interface', function () { - const localAddress = '123.456.789.428'; + it('accepts the `family` option', function (done) { + const re = process.platform === 'win32' ? /Loopback Pseudo-Interface/ : /lo/; + const ifaces = os.networkInterfaces(); + const hasIPv6 = Object.keys(ifaces).some((name) => { + return re.test(name) && ifaces[name].some((info) => info.family === 'IPv6'); + }); - assert.throws(() => { - const ws = new WebSocket('ws://localhost', { localAddress }); - }, (err) => { - return err instanceof TypeError && (err.code === 'ERR_INVALID_IP_ADDRESS' || - err.message.includes(`must be a valid IP: ${localAddress}`)); - }); - }); + // + // Skip this test on machines where IPv6 is not supported. + // + if (!hasIPv6) return this.skip(); + + dns.lookup('localhost', { family: 6, all: true }, (err, addresses) => { + // + // Skip this test if localhost does not resolve to ::1. + // + if (err) { + return err.code === 'ENOTFOUND' || err.code === 'EAI_AGAIN' + ? this.skip() + : done(err); + } - it('accepts the family option', function (done) { - const re = process.platform === 'win32' ? /Loopback Pseudo-Interface/ : /lo/; - const ifaces = os.networkInterfaces(); - const hasIPv6 = Object.keys(ifaces).some((name) => { - return re.test(name) && ifaces[name].some((info) => info.family === 'IPv6'); - }); + if (!addresses.some((val) => val.address === '::1')) return this.skip(); - // - // Skip this test on machines where IPv6 is not supported. - // - if (!hasIPv6) return this.skip(); + const wss = new WebSocket.Server({ host: '::1', port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 }); + }); - dns.lookup('localhost', { family: 6, all: true }, (err, addresses) => { - // - // Skip this test if localhost does not resolve to ::1. - // - if (err) { - return err.code === 'ENOTFOUND' || err.code === 'EAI_AGAIN' - ? this.skip() - : done(err); - } + wss.on('connection', (ws, req) => { + assert.strictEqual(req.connection.remoteAddress, '::1'); + wss.close(done); + }); + }); + }); + }); + }); + + describe('Constants', function () { + const readyStates = { + CONNECTING: 0, + OPEN: 1, + CLOSING: 2, + CLOSED: 3 + }; - if (!addresses.some((val) => val.address === '::1')) return this.skip(); + Object.keys(readyStates).forEach((state) => { + describe(`\`${state}\``, function () { + it('is enumerable property of class', function () { + const propertyDescripter = Object.getOwnPropertyDescriptor(WebSocket, state); - const wss = new WebSocket.Server({ host: '::1', port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 }); + assert.strictEqual(propertyDescripter.value, readyStates[state]); + assert.strictEqual(propertyDescripter.enumerable, true); }); - wss.on('connection', (ws, req) => { - assert.strictEqual(req.connection.remoteAddress, '::1'); - wss.close(done); + it('is property of instance', function () { + const ws = new WebSocket('ws://localhost', { + agent: new CustomAgent() + }); + + assert.strictEqual(ws[state], readyStates[state]); }); }); }); }); - describe('properties', function () { - it('#url exposes the server url', function () { - const url = 'ws://localhost'; - const ws = new WebSocket(url, { agent: new CustomAgent() }); + describe('Attributes', function () { + describe('`binaryType`', function () { + it("defaults to 'nodebuffer'", function () { + const ws = new WebSocket('ws://localhost', { + agent: new CustomAgent() + }); + + assert.strictEqual(ws.binaryType, 'nodebuffer'); + }); - assert.strictEqual(ws.url, url); + it("can be changed to 'arraybuffer' or 'fragments'", function () { + const ws = new WebSocket('ws://localhost', { + agent: new CustomAgent() + }); + + ws.binaryType = 'arraybuffer'; + assert.strictEqual(ws.binaryType, 'arraybuffer'); + + ws.binaryType = 'foo'; + assert.strictEqual(ws.binaryType, 'arraybuffer'); + + ws.binaryType = 'fragments'; + assert.strictEqual(ws.binaryType, 'fragments'); + + ws.binaryType = ''; + assert.strictEqual(ws.binaryType, 'fragments'); + + ws.binaryType = 'nodebuffer'; + assert.strictEqual(ws.binaryType, 'nodebuffer'); + }); }); - describe('#bufferedAmount', function () { + describe('`bufferedAmount`', function () { it('defaults to zero', function () { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() @@ -209,48 +250,72 @@ describe('WebSocket', function () { }); }); - describe('Custom headers', function () { - const server = http.createServer(); + describe('`extensions`', function () { + it('exposes the negotiated extensions names (1/2)', function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); - beforeEach((done) => server.listen(0, done)); - afterEach((done) => server.close(done)); + assert.strictEqual(ws.extensions, ''); - it('request has an authorization header', function (done) { - const wss = new WebSocket.Server({ server }); - const auth = 'test:testpass'; + ws.on('open', () => { + assert.strictEqual(ws.extensions, ''); + ws.on('close', () => wss.close(done)); + }); + }); - server.once('upgrade', (req, socket, head) => { - assert.ok(req.headers.authorization); - assert.strictEqual( - req.headers.authorization, - `Basic ${Buffer.from(auth).toString('base64')}` - ); + wss.on('connection', (ws) => { + assert.strictEqual(ws.extensions, ''); + ws.close(); + }); + }); - wss.close(done); + it('exposes the negotiated extensions names (2/2)', function (done) { + const wss = new WebSocket.Server({ + perMessageDeflate: true, + port: 0 + }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); + + assert.strictEqual(ws.extensions, ''); + + ws.on('open', () => { + assert.strictEqual(ws.extensions, 'permessage-deflate'); + ws.on('close', () => wss.close(done)); + }); }); - const port = server.address().port; - const ws = new WebSocket(`ws://${auth}@localhost:${port}`); + wss.on('connection', (ws) => { + assert.strictEqual(ws.extensions, 'permessage-deflate'); + ws.close(); + }); }); + }); - it('accepts custom headers', function (done) { - const wss = new WebSocket.Server({ server }); + describe('`protocol`', function () { + it('exposes the subprotocol selected by the server', function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`, 'foo'); - server.once('upgrade', (req, socket, head) => { - assert.ok(req.headers.cookie); - assert.strictEqual(req.headers.cookie, 'foo=bar'); + assert.strictEqual(ws.extensions, ''); - wss.close(done); + ws.on('open', () => { + assert.strictEqual(ws.protocol, 'foo'); + ws.on('close', () => wss.close(done)); + }); }); - const ws = new WebSocket(`ws://localhost:${server.address().port}`, { - headers: { 'Cookie': 'foo=bar' } + wss.on('connection', (ws) => { + assert.strictEqual(ws.protocol, 'foo'); + ws.close(); }); }); }); - describe('#readyState', function () { - it('defaults to connecting', function () { + describe('`readyState`', function () { + it('defaults to `CONNECTING`', function () { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -258,7 +323,7 @@ describe('WebSocket', function () { assert.strictEqual(ws.readyState, WebSocket.CONNECTING); }); - it('set to open once connection is established', function (done) { + it('is set to `OPEN` once connection is established', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -272,7 +337,7 @@ describe('WebSocket', function () { }); }); - it('set to closed once connection is closed', function (done) { + it('is set to `CLOSED` once connection is closed', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -286,7 +351,7 @@ describe('WebSocket', function () { }); }); - it('set to closed once connection is terminated', function (done) { + it('is set to `CLOSED` once connection is terminated', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -301,73 +366,81 @@ describe('WebSocket', function () { }); }); - const readyStates = { - CONNECTING: 0, - OPEN: 1, - CLOSING: 2, - CLOSED: 3 - }; + describe('`url`', function () { + it('exposes the server url', function () { + const url = 'ws://localhost'; + const ws = new WebSocket(url, { agent: new CustomAgent() }); - Object.keys(readyStates).forEach((state) => { - describe(`.${state}`, function () { - it('is enumerable property of class', function () { - const propertyDescripter = Object.getOwnPropertyDescriptor(WebSocket, state); + assert.strictEqual(ws.url, url); + }); + }); + }); - assert.strictEqual(propertyDescripter.value, readyStates[state]); - assert.strictEqual(propertyDescripter.enumerable, true); - }); + describe('Events', function () { + it("emits an 'error' event if an error occurs", function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + const port = wss._server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`); - it('is property of instance', function () { - const ws = new WebSocket('ws://localhost', { - agent: new CustomAgent() - }); + ws.on('error', (err) => { + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid opcode 5' + ); - assert.strictEqual(ws[state], readyStates[state]); + ws.on('close', (code, reason) => { + assert.strictEqual(code, 1002); + assert.strictEqual(reason, ''); + wss.close(done); + }); }); }); + + wss.on('connection', (ws) => { + ws._socket.write(Buffer.from([0x85, 0x00])); + }); }); - }); - describe('events', function () { - it('emits a ping event', function (done) { + it("emits a 'headers' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('ping', () => wss.close(done)); + ws.on('headers', (headers, res) => { + assert.strictEqual(headers, res.headers); + wss.close(done); + }); }); - - wss.on('connection', (client) => client.ping()); }); - it('emits a pong event', function (done) { + it("emits a 'ping' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('pong', () => wss.close(done)); + ws.on('ping', () => wss.close(done)); }); - wss.on('connection', (client) => client.pong()); + wss.on('connection', (ws) => ws.ping()); }); - it('emits a headers event', function (done) { + it("emits a 'pong' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('headers', (headers, res) => { - assert.strictEqual(headers, res.headers); - wss.close(done); - }); + ws.on('pong', () => wss.close(done)); }); + + wss.on('connection', (ws) => ws.pong()); }); }); - describe('connection establishing', function () { + describe('Connection establishing', function () { const server = http.createServer(); beforeEach((done) => server.listen(0, done)); afterEach((done) => server.close(done)); - it('invalid server key is denied', function (done) { + it('fails if the Sec-WebSocket-Accept header is invalid', function (done) { server.once('upgrade', (req, socket) => { socket.on('end', socket.end); socket.write( @@ -425,7 +498,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${server.address().port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'Unexpected server response: 401'); @@ -447,8 +520,8 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${server.address().port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); - ws.on('error', () => done(new Error("unexpected 'error' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); + ws.on('error', () => done(new Error("Unexpected 'error' event"))); ws.on('unexpected-response', (req, res) => { assert.strictEqual(res.statusCode, 401); @@ -479,8 +552,8 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${server.address().port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); - ws.on('error', () => done(new Error("unexpected 'error' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); + ws.on('error', () => done(new Error("Unexpected 'error' event"))); ws.on('unexpected-response', (req, res) => { assert.strictEqual(res.statusCode, 401); @@ -497,7 +570,7 @@ describe('WebSocket', function () { handshakeTimeout: 100 }); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'Opening handshake has timed out'); @@ -523,7 +596,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://localhost:${server.address().port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.message, 'Invalid Sec-WebSocket-Extensions header'); @@ -532,36 +605,64 @@ describe('WebSocket', function () { }); it('fails if server sends a subprotocol when none was requested', function (done) { - server.once('upgrade', (req, socket) => { - const key = crypto.createHash('sha1') - .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary') - .digest('base64'); - - socket.end( - 'HTTP/1.1 101 Switching Protocols\r\n' + - 'Upgrade: websocket\r\n' + - 'Connection: Upgrade\r\n' + - `Sec-WebSocket-Accept: ${key}\r\n` + - 'Sec-WebSocket-Protocol: foo\r\n' + - '\r\n' - ); + const wss = new WebSocket.Server({ + handleProtocols: () => 'foo', + server }); const ws = new WebSocket(`ws://localhost:${server.address().port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( err.message, 'Server sent a subprotocol but none was requested' ); - ws.on('close', () => done()); + ws.on('close', () => wss.close(done)); + }); + }); + + it('fails if server sends an invalid subprotocol', function (done) { + const wss = new WebSocket.Server({ + handleProtocols: () => 'baz', + server + }); + + const ws = new WebSocket(`ws://localhost:${server.address().port}`, [ + 'foo', + 'bar' + ]); + + ws.on('open', () => done(new Error("Unexpected 'open' event"))); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'Server sent an invalid subprotocol'); + ws.on('close', () => wss.close(done)); + }); + }); + + it('fails if server sends no subprotocol', function (done) { + const wss = new WebSocket.Server({ + handleProtocols: () => {}, + server + }); + + const ws = new WebSocket(`ws://localhost:${server.address().port}`, [ + 'foo', + 'bar' + ]); + + ws.on('open', () => done(new Error("Unexpected 'open' event"))); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'Server sent no subprotocol'); + ws.on('close', () => wss.close(done)); }); }); }); - describe('connection with query string', function () { + describe('Connection with query string', function () { it('connects when pathname is not null', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; @@ -797,7 +898,7 @@ describe('WebSocket', function () { }); describe('#send', function () { - it('very long binary data can be sent and received', function (done) { + it('can send a big binary message', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const array = new Float32Array(5 * 1024 * 1024); @@ -820,7 +921,7 @@ describe('WebSocket', function () { }); }); - it('can send and receive text data', function (done) { + it('can send text data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -917,7 +1018,7 @@ describe('WebSocket', function () { }); }); - it('ArrayBuffer is auto-detected without binary flag', function (done) { + it('can send an `ArrayBuffer`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const array = new Float32Array(5); @@ -940,7 +1041,7 @@ describe('WebSocket', function () { }); }); - it('Buffer is auto-detected without binary flag', function (done) { + it('can send a `Buffer`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const buf = Buffer.from('foobar'); const port = wss._server.address().port; @@ -959,7 +1060,7 @@ describe('WebSocket', function () { }); }); - it('before connect should fail', function () { + it('throws an error if `readyState` is not `OPEN`', function () { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -970,7 +1071,7 @@ describe('WebSocket', function () { ); }); - it('before connect should pass error through callback, if present', function () { + it('passes errors to the callback, if present', function () { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -984,23 +1085,7 @@ describe('WebSocket', function () { }); }); - it('without data should be successful', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => ws.send()); - }); - - wss.on('connection', (ws) => { - ws.on('message', (message) => { - assert.ok(message.equals(Buffer.alloc(0))); - wss.close(done); - }); - }); - }); - - it('calls optional callback when flushed', function (done) { + it('calls the optional callback when data is written out', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1014,28 +1099,28 @@ describe('WebSocket', function () { }); }); - it('with unmasked message is successfully transmitted to the server', function (done) { + it('works when the `data` argument is falsy', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.send('hi', { mask: false })); + ws.on('open', () => ws.send()); }); wss.on('connection', (ws) => { ws.on('message', (message) => { - assert.strictEqual(message, 'hi'); + assert.ok(message.equals(Buffer.alloc(0))); wss.close(done); }); }); }); - it('with masked message is successfully transmitted to the server', function (done) { + it('can send text data with `mask` option set to `false`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.send('hi', { mask: true })); + ws.on('open', () => ws.send('hi', { mask: false })); }); wss.on('connection', (ws) => { @@ -1046,7 +1131,7 @@ describe('WebSocket', function () { }); }); - it('with unmasked binary message is successfully transmitted to the server', function (done) { + it('can send binary data with `mask` option set to `false`', function (done) { const array = new Float32Array(5); for (let i = 0; i < array.length; ++i) { @@ -1057,29 +1142,7 @@ describe('WebSocket', function () { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => ws.send(array, { mask: false, binary: true })); - }); - - wss.on('connection', (ws) => { - ws.on('message', (message) => { - assert.ok(message.equals(Buffer.from(array.buffer))); - wss.close(done); - }); - }); - }); - - it('with masked binary message is successfully transmitted to the server', function (done) { - const array = new Float32Array(5); - - for (let i = 0; i < array.length; ++i) { - array[i] = i / 2; - } - - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.on('open', () => ws.send(array, { mask: true, binary: true })); + ws.on('open', () => ws.send(array, { mask: false })); }); wss.on('connection', (ws) => { @@ -1097,7 +1160,7 @@ describe('WebSocket', function () { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( @@ -1118,7 +1181,7 @@ describe('WebSocket', function () { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( @@ -1134,7 +1197,7 @@ describe('WebSocket', function () { it('can be called from an error listener while connecting', function (done) { const ws = new WebSocket('ws://localhost:1337'); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.code, 'ECONNREFUSED'); @@ -1148,7 +1211,7 @@ describe('WebSocket', function () { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( @@ -1323,9 +1386,11 @@ describe('WebSocket', function () { }); wss.on('connection', (ws) => { - ws.send('foo'); - ws.send('bar'); - ws.send('baz'); + const callback = (err) => assert.ifError(err); + + ws.send('foo', callback); + ws.send('bar', callback); + ws.send('baz', callback); ws.close(); ws.close(); }); @@ -1345,7 +1410,7 @@ describe('WebSocket', function () { wss.on('connection', (ws) => ws.close(1013)); }); - it('does nothing if the connection is already CLOSED', function (done) { + it('does nothing if `readyState` is `CLOSED`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1368,7 +1433,7 @@ describe('WebSocket', function () { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( @@ -1389,7 +1454,7 @@ describe('WebSocket', function () { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( @@ -1405,7 +1470,7 @@ describe('WebSocket', function () { it('can be called from an error listener while connecting', function (done) { const ws = new WebSocket('ws://localhost:1337'); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual(err.code, 'ECONNREFUSED'); @@ -1419,7 +1484,7 @@ describe('WebSocket', function () { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('open', () => done(new Error("unexpected 'open' event"))); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( @@ -1432,7 +1497,7 @@ describe('WebSocket', function () { }); }); - it('does nothing if the connection is already CLOSED', function (done) { + it('does nothing if `readyState` is `CLOSED`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1450,7 +1515,7 @@ describe('WebSocket', function () { }); describe('WHATWG API emulation', function () { - it('should not throw errors when getting and setting', function () { + it('supports the `on{close,error,message,open}` attributes', function () { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1464,65 +1529,29 @@ describe('WebSocket', function () { ws.onclose = listener; ws.onopen = listener; - assert.strictEqual(ws.binaryType, 'nodebuffer'); - ws.binaryType = 'arraybuffer'; - assert.strictEqual(ws.binaryType, 'arraybuffer'); - ws.binaryType = 'nodebuffer'; - assert.strictEqual(ws.binaryType, 'nodebuffer'); - assert.strictEqual(ws.onmessage, listener); assert.strictEqual(ws.onclose, listener); assert.strictEqual(ws.onerror, listener); assert.strictEqual(ws.onopen, listener); }); - it('should ignore when setting an invalid binary type', function () { - const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - - ws.binaryType = 'nodebuffer'; - assert.strictEqual(ws.binaryType, 'nodebuffer'); - ws.binaryType = 'foo'; - assert.strictEqual(ws.binaryType, 'nodebuffer'); - ws.binaryType = 'arraybuffer'; - assert.strictEqual(ws.binaryType, 'arraybuffer'); - ws.binaryType = ''; - assert.strictEqual(ws.binaryType, 'arraybuffer'); - ws.binaryType = 'fragments'; - assert.strictEqual(ws.binaryType, 'fragments'); - ws.binaryType = 'buffer'; - assert.strictEqual(ws.binaryType, 'fragments'); - ws.binaryType = 'nodebuffer'; - assert.strictEqual(ws.binaryType, 'nodebuffer'); - }); - - it('should work the same as the EventEmitter api', function (done) { - const wss = new WebSocket.Server({ - clientTracking: false, - port: 0 - }, () => { + it('works like the `EventEmitter` interface', function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - let message = 0; - let close = 0; - let open = 0; ws.onmessage = (messageEvent) => { assert.strictEqual(messageEvent.data, 'foo'); - ++message; + ws.onclose = (closeEvent) => { + assert.strictEqual(closeEvent.wasClean, true); + assert.strictEqual(closeEvent.code, 1005); + assert.strictEqual(closeEvent.reason, ''); + wss.close(done); + }; ws.close(); }; - ws.onopen = () => ++open; - ws.onclose = () => ++close; - - ws.on('open', () => ws.send('foo')); - - ws.on('close', () => { - assert.strictEqual(message, 1); - assert.strictEqual(open, 1); - assert.strictEqual(close, 1); - wss.close(done); - }); + ws.onopen = () => ws.send('foo'); }); wss.on('connection', (ws) => { @@ -1530,7 +1559,7 @@ describe('WebSocket', function () { }); }); - it('doesn\'t return event listeners added with `on`', function () { + it("doesn't return listeners added with `on`", function () { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1540,7 +1569,7 @@ describe('WebSocket', function () { assert.strictEqual(ws.onopen, undefined); }); - it('doesn\'t remove event listeners added with `on`', function () { + it("doesn't remove listeners added with `on`", function () { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1562,7 +1591,7 @@ describe('WebSocket', function () { assert.strictEqual(listeners[1]._listener, listener); }); - it('registers listeners for custom events with addEventListener', function () { + it('adds listeners for custom events with `addEventListener`', function () { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1576,7 +1605,7 @@ describe('WebSocket', function () { assert.strictEqual(ws.listeners('bar').length, 0); }); - it('removes event listeners added with addEventListener', function () { + it('supports the `removeEventListener` method', function () { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1596,12 +1625,12 @@ describe('WebSocket', function () { ws.removeEventListener('open', listener); ws.removeEventListener('foo', listener); - assert.strictEqual(ws.listeners('message').length, 0); - assert.strictEqual(ws.listeners('open').length, 0); - assert.strictEqual(ws.listeners('foo').length, 0); + assert.strictEqual(ws.listenerCount('message'), 0); + assert.strictEqual(ws.listenerCount('open'), 0); + assert.strictEqual(ws.listenerCount('foo'), 0); }); - it('should receive text data wrapped in a MessageEvent when using addEventListener', function (done) { + it('wraps text data in a `MessageEvent`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1618,7 +1647,7 @@ describe('WebSocket', function () { }); }); - it('should receive valid CloseEvent when server closes with code 1000', function (done) { + it('receives a `CloseEvent` when server closes (1000)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1631,10 +1660,10 @@ describe('WebSocket', function () { }); }); - wss.on('connection', (client) => client.close(1000)); + wss.on('connection', (ws) => ws.close(1000)); }); - it('should receive valid CloseEvent when server closes with code 1001', function (done) { + it('receives a `CloseEvent` when server closes (4000)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1642,12 +1671,12 @@ describe('WebSocket', function () { ws.addEventListener('close', (closeEvent) => { assert.ok(closeEvent.wasClean); assert.strictEqual(closeEvent.reason, 'some daft reason'); - assert.strictEqual(closeEvent.code, 1001); + assert.strictEqual(closeEvent.code, 4000); wss.close(done); }); }); - wss.on('connection', (client) => client.close(1001, 'some daft reason')); + wss.on('connection', (ws) => ws.close(4000, 'some daft reason')); }); it('sets `target` and `type` on events', function (done) { @@ -1683,7 +1712,7 @@ describe('WebSocket', function () { wss.on('connection', (client) => client.send('hi')); }); - it('should pass binary data as a Node.js Buffer by default', function (done) { + it('passes binary data as a Node.js `Buffer` by default', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1697,23 +1726,7 @@ describe('WebSocket', function () { wss.on('connection', (ws) => ws.send(new Uint8Array(4096))); }); - it('should pass an ArrayBuffer for event.data if binaryType = arraybuffer', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - - ws.binaryType = 'arraybuffer'; - - ws.onmessage = (evt) => { - assert.ok(evt.data instanceof ArrayBuffer); - wss.close(done); - }; - }); - - wss.on('connection', (ws) => ws.send(new Uint8Array(4096))); - }); - - it('should ignore binaryType for text messages', function (done) { + it('ignores `binaryType` for text messages', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1729,7 +1742,7 @@ describe('WebSocket', function () { wss.on('connection', (ws) => ws.send('foo')); }); - it('should allow to update binaryType on the fly', function (done) { + it('allows to update `binaryType` on the fly', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1769,8 +1782,8 @@ describe('WebSocket', function () { }); }); - describe('ssl', function () { - it('can connect to secure websocket server', function (done) { + describe('SSL', function () { + it('connects to secure websocket server', function (done) { const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), key: fs.readFileSync('test/fixtures/key.pem') @@ -1789,7 +1802,7 @@ describe('WebSocket', function () { }); }); - it('can connect to secure websocket server with client side certificate', function (done) { + it('connects to secure websocket server with client side certificate', function (done) { const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), ca: [fs.readFileSync('test/fixtures/ca1-cert.pem')], @@ -1864,7 +1877,7 @@ describe('WebSocket', function () { }); }); - it('can send and receive very long binary data', function (done) { + it('can send a big binary message', function (done) { this.timeout(4000); const buf = crypto.randomBytes(5 * 1024 * 1024); @@ -1894,7 +1907,36 @@ describe('WebSocket', function () { }); }); - describe('host and origin headers', function () { + describe('Request headers', function () { + it('adds the authorization header if userinfo is present', function (done) { + const agent = new CustomAgent(); + const auth = 'test:testpass'; + + agent.addRequest = (req) => { + assert.strictEqual( + req._headers.authorization, + `Basic ${Buffer.from(auth).toString('base64')}` + ); + done(); + }; + + const ws = new WebSocket(`ws://${auth}@localhost`, { agent }); + }); + + it('adds custom headers', function (done) { + const agent = new CustomAgent(); + + agent.addRequest = (req) => { + assert.strictEqual(req._headers.cookie, 'foo=bar'); + done(); + }; + + const ws = new WebSocket('ws://localhost', { + headers: { 'Cookie': 'foo=bar' }, + agent + }); + }); + it('includes the host header with port number', function (done) { const agent = new CustomAgent(); @@ -1906,7 +1948,30 @@ describe('WebSocket', function () { const ws = new WebSocket('ws://localhost:1337', { agent }); }); - it('lacks default origin header', function (done) { + it('excludes default ports from host header', function () { + const httpsAgent = new https.Agent(); + const httpAgent = new http.Agent(); + const values = []; + let ws; + + httpsAgent.addRequest = httpAgent.addRequest = (req) => { + values.push(req._headers.host); + }; + + ws = new WebSocket('wss://localhost:8443', { agent: httpsAgent }); + ws = new WebSocket('wss://localhost:443', { agent: httpsAgent }); + ws = new WebSocket('ws://localhost:88', { agent: httpAgent }); + ws = new WebSocket('ws://localhost:80', { agent: httpAgent }); + + assert.deepStrictEqual(values, [ + 'localhost:8443', + 'localhost', + 'localhost:88', + 'localhost' + ]); + }); + + it("doesn't add the origin header by default", function (done) { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -1917,7 +1982,7 @@ describe('WebSocket', function () { const ws = new WebSocket('ws://localhost', { agent }); }); - it('honors origin set in options (1/2)', function (done) { + it('honors the `origin` option (1/2)', function (done) { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -1931,7 +1996,7 @@ describe('WebSocket', function () { }); }); - it('honors origin set in options (2/2)', function (done) { + it('honors the `origin` option (2/2)', function (done) { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -1948,99 +2013,60 @@ describe('WebSocket', function () { agent }); }); - - it('excludes default ports from host header', function () { - const httpsAgent = new https.Agent(); - const httpAgent = new http.Agent(); - const values = []; - let ws; - - httpsAgent.addRequest = httpAgent.addRequest = (req) => { - values.push(req._headers.host); - }; - - ws = new WebSocket('wss://localhost:8443', { agent: httpsAgent }); - ws = new WebSocket('wss://localhost:443', { agent: httpsAgent }); - ws = new WebSocket('ws://localhost:88', { agent: httpAgent }); - ws = new WebSocket('ws://localhost:80', { agent: httpAgent }); - - assert.deepStrictEqual(values, [ - 'localhost:8443', - 'localhost', - 'localhost:88', - 'localhost' - ]); - }); }); describe('permessage-deflate', function () { it('is enabled by default', (done) => { - const server = http.createServer(); - const wss = new WebSocket.Server({ server, perMessageDeflate: true }); - - server.on('upgrade', (req, socket, head) => { - assert.ok(req.headers['sec-websocket-extensions'].includes('permessage-deflate')); - }); + const agent = new CustomAgent(); - server.listen(0, () => { - const ws = new WebSocket(`ws://localhost:${server.address().port}`); + agent.addRequest = (req) => { + assert.strictEqual( + req._headers['sec-websocket-extensions'], + 'permessage-deflate; client_max_window_bits' + ); + done(); + }; - ws.on('open', () => { - assert.ok(ws.extensions['permessage-deflate']); - server.close(done); - wss.close(); - }); - }); + const ws = new WebSocket('ws://localhost', { agent }); }); it('can be disabled', function (done) { - const server = http.createServer(); - const wss = new WebSocket.Server({ server, perMessageDeflate: true }); - - server.on('upgrade', (req, socket, head) => { - assert.strictEqual(req.headers['sec-websocket-extensions'], undefined); - }); + const agent = new CustomAgent(); - server.listen(0, () => { - const ws = new WebSocket(`ws://localhost:${server.address().port}`, { - perMessageDeflate: false - }); + agent.addRequest = (req) => { + assert.strictEqual(req._headers['sec-websocket-extensions'], undefined); + done(); + }; - ws.on('open', () => { - server.close(done); - wss.close(); - }); + const ws = new WebSocket('ws://localhost', { + perMessageDeflate: false, + agent }); }); it('can send extension parameters', function (done) { - const server = http.createServer(); - const wss = new WebSocket.Server({ server, perMessageDeflate: true }); - - server.on('upgrade', (req, socket, head) => { - const extensions = req.headers['sec-websocket-extensions']; + const agent = new CustomAgent(); - assert.ok(extensions.includes('permessage-deflate')); - assert.ok(extensions.includes('server_no_context_takeover')); - assert.ok(extensions.includes('client_no_context_takeover')); - assert.ok(extensions.includes('server_max_window_bits=10')); - assert.ok(extensions.includes('client_max_window_bits')); - }); + const value = 'permessage-deflate; server_no_context_takeover;' + + ' client_no_context_takeover; server_max_window_bits=10;' + + ' client_max_window_bits'; - server.listen(0, () => { - const ws = new WebSocket(`ws://localhost:${server.address().port}`, { - perMessageDeflate: { - serverNoContextTakeover: true, - clientNoContextTakeover: true, - serverMaxWindowBits: 10, - clientMaxWindowBits: true - } - }); + agent.addRequest = (req) => { + assert.strictEqual( + req._headers['sec-websocket-extensions'], + value + ); + done(); + }; - ws.on('open', () => { - server.close(done); - wss.close(); - }); + const ws = new WebSocket('ws://localhost', { + perMessageDeflate: { + clientNoContextTakeover: true, + serverNoContextTakeover: true, + clientMaxWindowBits: true, + serverMaxWindowBits: 10 + }, + agent }); }); @@ -2066,7 +2092,7 @@ describe('WebSocket', function () { }); }); - it('can send and receive a typed array', function (done) { + it('can send and receive a `TypedArray`', function (done) { const array = new Float32Array(5); for (let i = 0; i < array.length; i++) { @@ -2094,7 +2120,7 @@ describe('WebSocket', function () { }); }); - it('can send and receive ArrayBuffer', function (done) { + it('can send and receive an `ArrayBuffer`', function (done) { const array = new Float32Array(5); for (let i = 0; i < array.length; i++) { @@ -2148,7 +2174,7 @@ describe('WebSocket', function () { }); describe('#send', function () { - it('can set the compress option true when perMessageDeflate is disabled', function (done) { + it('ignores the `compress` option if the extension is disabled', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`, { @@ -2168,37 +2194,8 @@ describe('WebSocket', function () { }); }); - describe('#close', function () { - it('should not raise error callback, if any, if called during send data', function (done) { - const wss = new WebSocket.Server({ - perMessageDeflate: { threshold: 0 }, - port: 0 - }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { - perMessageDeflate: { threshold: 0 } - }); - - ws.on('open', () => { - ws.send('hi', (error) => assert.ifError(error)); - ws.close(); - }); - }); - - wss.on('connection', (ws) => { - ws.on('message', (message) => { - assert.strictEqual(message, 'hi'); - ws.on('close', (code) => { - assert.strictEqual(code, 1005); - wss.close(done); - }); - }); - }); - }); - }); - describe('#terminate', function () { - it('will raise error callback, if any, if called during send data', function (done) { + it('can be used while data is being compressed', function (done) { const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 0 }, port: 0 @@ -2209,8 +2206,8 @@ describe('WebSocket', function () { }); ws.on('open', () => { - ws.send('hi', (error) => { - assert.ok(error instanceof Error); + ws.send('hi', (err) => { + assert.ok(err instanceof Error); wss.close(done); }); ws.terminate(); @@ -2218,7 +2215,7 @@ describe('WebSocket', function () { }); }); - it('can be used while data is being processed', function (done) { + it('can be used while data is being decompressed', function (done) { const wss = new WebSocket.Server({ perMessageDeflate: true, port: 0 From 1c783c295c9e6bc3f3149c712434b5dff018f586 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 28 Dec 2017 11:24:14 +0100 Subject: [PATCH 454/669] [major] Rename the 'headers' event to 'upgrade' Refs: https://github.com/websockets/ws/pull/1082#issuecomment-295755780 --- doc/ws.md | 17 ++++++++--------- lib/websocket.js | 4 ++-- test/websocket-server.test.js | 4 ++-- test/websocket.test.js | 14 +++++++------- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 215a3010a..6844cbeb2 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -237,15 +237,6 @@ human-readable string explaining why the connection has been closed. Emitted when an error occurs. Errors from the underlying `net.Socket` are forwarded here. -### Event: 'headers' - -- `headers` {Object} -- `response` {http.IncomingMessage} - -Emitted when response headers are received from the server as part of the -handshake. This allows you to read headers from the server, for example -'set-cookie' headers. - ### Event: 'message' - `data` {String|Buffer|ArrayBuffer|Buffer[]} @@ -278,6 +269,14 @@ response. This event gives the ability to read the response in order to extract useful information. If the server sends an invalid response and there isn't a listener for this event, an error is emitted. +### Event: 'upgrade' + +- `response` {http.IncomingMessage} + +Emitted when response headers are received from the server as part of the +handshake. This allows you to read headers from the server, for example +'set-cookie' headers. + ### websocket.addEventListener(type, listener) - `type` {String} A string representing the event type to listen for. diff --git a/lib/websocket.js b/lib/websocket.js index 907de2117..491a56223 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -679,10 +679,10 @@ function initAsClient (address, protocols, options) { }); this._req.on('upgrade', (res, socket, head) => { - this.emit('headers', res.headers, res); + this.emit('upgrade', res); // - // The user may have closed the connection from a listener of the `headers` + // The user may have closed the connection from a listener of the `upgrade` // event. // if (this.readyState !== WebSocket.CONNECTING) return; diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 4ba93675f..14ea76c49 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -713,9 +713,9 @@ describe('WebSocketServer', function () { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('headers', (headers) => { + ws.on('upgrade', (res) => { assert.strictEqual( - headers['sec-websocket-extensions'], + res.headers['sec-websocket-extensions'], 'permessage-deflate; client_max_window_bits=8' ); diff --git a/test/websocket.test.js b/test/websocket.test.js index 397cb153b..eb4b48fe1 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -402,12 +402,12 @@ describe('WebSocket', function () { }); }); - it("emits a 'headers' event", function (done) { + it("emits an 'upgrade' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); - ws.on('headers', (headers, res) => { - assert.strictEqual(headers, res.headers); + ws.on('upgrade', (res) => { + assert.ok(res instanceof http.IncomingMessage); wss.close(done); }); }); @@ -1206,7 +1206,7 @@ describe('WebSocket', function () { }); }); - it('can be called from a listener of the headers event', function (done) { + it("can be called from a listener of the 'upgrade' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1220,7 +1220,7 @@ describe('WebSocket', function () { ); ws.on('close', () => wss.close(done)); }); - ws.on('headers', () => ws.close()); + ws.on('upgrade', () => ws.close()); }); }); @@ -1479,7 +1479,7 @@ describe('WebSocket', function () { }); }); - it('can be called from a listener of the headers event', function (done) { + it("can be called from a listener of the 'upgrade' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss._server.address().port; const ws = new WebSocket(`ws://localhost:${port}`); @@ -1493,7 +1493,7 @@ describe('WebSocket', function () { ); ws.on('close', () => wss.close(done)); }); - ws.on('headers', () => ws.terminate()); + ws.on('upgrade', () => ws.terminate()); }); }); From a206e986fcdbf6f71d008c910fdd60f5141ba7ac Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 31 Dec 2017 10:03:14 +0100 Subject: [PATCH 455/669] [major] Remove `WebSocket#pause()` and `WebSocket#resume()` This is in preparation for read backpressure handling when permessage-deflate is enabled. The user should not interfere by pausing/resuming the underlying `net.Socket` stream. --- doc/ws.md | 8 ------ lib/websocket.js | 32 ---------------------- test/websocket.test.js | 61 ------------------------------------------ 3 files changed, 101 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 6844cbeb2..44f1bd8e7 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -344,10 +344,6 @@ listener receives a `MessageEvent` named "message". An event listener to be called when the connection is established. The listener receives an `OpenEvent` named "open". -### websocket.pause() - -Pause the socket. - ### websocket.ping([data[, mask]][, callback]) - `data` {Any} The data to send in the ping frame. @@ -387,10 +383,6 @@ The current state of the connection. This is one of the ready state constants. Removes an event listener emulating the `EventTarget` interface. -### websocket.resume() - -Resume the socket. - ### websocket.send(data[, options][, callback]) - `data` {Any} The data to send. diff --git a/lib/websocket.js b/lib/websocket.js index 491a56223..032d2d43c 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -225,38 +225,6 @@ class WebSocket extends EventEmitter { this.removeAllListeners(); } - /** - * Pause the socket stream. - * - * @public - */ - pause () { - if (this.readyState !== WebSocket.OPEN) { - throw new Error( - `WebSocket is not open: readyState ${this.readyState} ` + - `(${readyStates[this.readyState]})` - ); - } - - this._socket.pause(); - } - - /** - * Resume the socket stream - * - * @public - */ - resume () { - if (this.readyState !== WebSocket.OPEN) { - throw new Error( - `WebSocket is not open: readyState ${this.readyState} ` + - `(${readyStates[this.readyState]})` - ); - } - - this._socket.resume(); - } - /** * Start a closing handshake. * diff --git a/test/websocket.test.js b/test/websocket.test.js index eb4b48fe1..cf41e7055 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -682,67 +682,6 @@ describe('WebSocket', function () { }); }); - describe('#pause and #resume', function () { - it('throws an error when `readyState` is not `OPEN` (pause)', function () { - const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - - assert.throws( - () => ws.pause(), - /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ - ); - }); - - it('throws an error when `readyState` is not `OPEN` (resume)', function () { - const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - - assert.throws( - () => ws.resume(), - /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ - ); - }); - - it('pauses the underlying stream', function (done) { - // this test is sort-of racecondition'y, since an unlikely slow connection - // to localhost can cause the test to succeed even when the stream pausing - // isn't working as intended. that is an extremely unlikely scenario, though - // and an acceptable risk for the test. - let openCount = 0; - let serverClient; - let client; - - const onOpen = () => { - if (++openCount !== 2) return; - - let paused = true; - serverClient.on('message', () => { - assert.ok(!paused); - wss.close(done); - }); - serverClient.pause(); - - setTimeout(() => { - paused = false; - serverClient.resume(); - }, 200); - - client.send('foo'); - }; - - const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); - - serverClient = ws; - serverClient.on('open', onOpen); - }); - - wss.on('connection', (ws) => { - client = ws; - onOpen(); - }); - }); - }); - describe('#ping', function () { it('throws an error if `readyState` is not `OPEN`', function (done) { const ws = new WebSocket('ws://localhost', { From d03ada231a7961ebfbd797c1ea54632dc8078707 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 4 Jan 2018 09:55:38 +0100 Subject: [PATCH 456/669] [minor] Rename some variables for clarity --- lib/extension.js | 24 +++++++++++----------- lib/permessage-deflate.js | 42 +++++++++++++++++++-------------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/lib/extension.js b/lib/extension.js index d59d39202..3f48d7517 100644 --- a/lib/extension.js +++ b/lib/extension.js @@ -188,21 +188,21 @@ function parse (header) { } /** - * Serializes a parsed `Sec-WebSocket-Extensions` header to a string. + * Builds the `Sec-WebSocket-Extensions` header field value. * - * @param {Object} value The object to format - * @return {String} A string representing the given value + * @param {Object} extensions The map of extensions and parameters to format + * @return {String} A string representing the given object * @public */ -function format (value) { - return Object.keys(value).map((token) => { - var paramsList = value[token]; - if (!Array.isArray(paramsList)) paramsList = [paramsList]; - return paramsList.map((params) => { - return [token].concat(Object.keys(params).map((k) => { - var p = params[k]; - if (!Array.isArray(p)) p = [p]; - return p.map((v) => v === true ? k : `${k}=${v}`).join('; '); +function format (extensions) { + return Object.keys(extensions).map((extension) => { + var configurations = extensions[extension]; + if (!Array.isArray(configurations)) configurations = [configurations]; + return configurations.map((params) => { + return [extension].concat(Object.keys(params).map((k) => { + var values = params[k]; + if (!Array.isArray(values)) values = [values]; + return values.map((v) => v === true ? k : `${k}=${v}`).join('; '); })).join('; '); }).join(', '); }).join(', '); diff --git a/lib/permessage-deflate.js b/lib/permessage-deflate.js index 5db327f69..63182200e 100644 --- a/lib/permessage-deflate.js +++ b/lib/permessage-deflate.js @@ -82,7 +82,7 @@ class PerMessageDeflate { } /** - * Create extension parameters offer. + * Create an extension negotiation offer. * * @return {Object} Extension parameters * @public @@ -109,18 +109,18 @@ class PerMessageDeflate { } /** - * Accept extension offer. + * Accept an extension negotiation offer/response. * - * @param {Array} paramsList Extension parameters + * @param {Array} configurations The extension negotiation offers/reponse * @return {Object} Accepted configuration * @public */ - accept (paramsList) { - paramsList = this.normalizeParams(paramsList); + accept (configurations) { + configurations = this.normalizeParams(configurations); this.params = this._isServer - ? this.acceptAsServer(paramsList) - : this.acceptAsClient(paramsList); + ? this.acceptAsServer(configurations) + : this.acceptAsClient(configurations); return this.params; } @@ -150,15 +150,15 @@ class PerMessageDeflate { } /** - * Accept extension offer from client. + * Accept an extension negotiation offer. * - * @param {Array} paramsList Extension parameters + * @param {Array} offers The extension negotiation offers * @return {Object} Accepted configuration * @private */ - acceptAsServer (paramsList) { + acceptAsServer (offers) { const opts = this._options; - const accepted = paramsList.find((params) => { + const accepted = offers.find((params) => { if ( (opts.serverNoContextTakeover === false && params.server_no_context_takeover) || @@ -201,14 +201,14 @@ class PerMessageDeflate { } /** - * Accept extension response from server. + * Accept the extension negotiation response. * - * @param {Array} paramsList Extension parameters + * @param {Array} response The extension negotiation response * @return {Object} Accepted configuration * @private */ - acceptAsClient (paramsList) { - const params = paramsList[0]; + acceptAsClient (response) { + const params = response[0]; if ( this._options.clientNoContextTakeover === false && @@ -235,14 +235,14 @@ class PerMessageDeflate { } /** - * Normalize extensions parameters. + * Normalize parameters. * - * @param {Array} paramsList Extension parameters - * @return {Array} Normalized extensions parameters + * @param {Array} configurations The extension negotiation offers/reponse + * @return {Array} The offers/response with normalized parameters * @private */ - normalizeParams (paramsList) { - paramsList.forEach((params) => { + normalizeParams (configurations) { + configurations.forEach((params) => { Object.keys(params).forEach((key) => { var value = params[key]; @@ -291,7 +291,7 @@ class PerMessageDeflate { }); }); - return paramsList; + return configurations; } /** From a04d9855a5a3d9bab62f9d22bcf5d1ee8185726b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 5 Jan 2018 10:39:18 +0100 Subject: [PATCH 457/669] [dist] 4.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af23fd9e0..23f24fca4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "3.3.3", + "version": "4.0.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 02274090a4eeaffc2d3fbc9f927517b8a31b0ee6 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 7 Jan 2018 07:18:36 +0100 Subject: [PATCH 458/669] chore(package): update eslint to version 4.15.0 (#1273) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 23f24fca4..e81558816 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.14.0", + "eslint": "~4.15.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~5.2.0", From 435f92370d9a77b9be11c35c41703229c357625c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 8 Jan 2018 16:25:35 +0100 Subject: [PATCH 459/669] [minor] Fix JSDoc comment --- lib/receiver.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/receiver.js b/lib/receiver.js index cc761f40d..c18f6cc1f 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -128,6 +128,7 @@ class Receiver { /** * Adds new data to the parser. * + * @param {Buffer} data A chunk of data * @public */ add (data) { From be3717e29dd95c121cebe1f41f213d5c427823fb Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 8 Jan 2018 16:09:31 +0100 Subject: [PATCH 460/669] [test] Replace no longer valid test with a new one When an error occurs data can no longer be added to the parser as the socket is destroyed. --- test/receiver.test.js | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/test/receiver.test.js b/test/receiver.test.js index d96528ee2..dcf113163 100644 --- a/test/receiver.test.js +++ b/test/receiver.test.js @@ -443,6 +443,19 @@ describe('Receiver', function () { assert.deepStrictEqual(data, ['', 'Hello']); }); + it('ignores data received after a close frame', function () { + const results = []; + const push = results.push.bind(results); + const p = new Receiver(); + + p.onclose = p.onmessage = push; + + p.add(Buffer.from('8800', 'hex')); + p.add(Buffer.from('8100', 'hex')); + + assert.deepStrictEqual(results, [1005, '']); + }); + it('raises an error when RSV1 is on and permessage-deflate is disabled', function (done) { const p = new Receiver(); @@ -810,26 +823,6 @@ describe('Receiver', function () { }); }); - it('doesn\'t crash if data is received after `maxPayload` is exceeded', function (done) { - const p = new Receiver({}, 5); - const buf = crypto.randomBytes(10); - - let gotError = false; - - p.onerror = function (reason, code) { - gotError = true; - assert.strictEqual(code, 1009); - }; - - p.add(Buffer.from([0x82, buf.length])); - - assert.ok(gotError); - assert.strictEqual(p.onerror, null); - - p.add(buf); - done(); - }); - it('consumes all data before calling `cleanup` callback (1/4)', function (done) { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); From e3660934e8de9891e8b06ce04a6585cc10e1fce4 Mon Sep 17 00:00:00 2001 From: Quentin Barbe Date: Mon, 15 Jan 2018 18:56:25 +0100 Subject: [PATCH 461/669] [minor] Remove license comments (#1278) --- bench/parser.benchmark.js | 6 ------ bench/sender.benchmark.js | 6 ------ index.js | 6 ------ lib/buffer-util.js | 6 ------ lib/receiver.js | 6 ------ lib/sender.js | 6 ------ lib/validation.js | 6 ------ lib/websocket-server.js | 6 ------ lib/websocket.js | 6 ------ 9 files changed, 54 deletions(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index b3484b611..ca4282b4e 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -1,9 +1,3 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - 'use strict'; const safeBuffer = require('safe-buffer'); diff --git a/bench/sender.benchmark.js b/bench/sender.benchmark.js index 46132c190..89d3be24b 100644 --- a/bench/sender.benchmark.js +++ b/bench/sender.benchmark.js @@ -1,9 +1,3 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - 'use strict'; const benchmark = require('benchmark'); diff --git a/index.js b/index.js index 4a7715f4a..b8d6be1c9 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,3 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - 'use strict'; const WebSocket = require('./lib/websocket'); diff --git a/lib/buffer-util.js b/lib/buffer-util.js index 6a35e8f43..5ab9e289f 100644 --- a/lib/buffer-util.js +++ b/lib/buffer-util.js @@ -1,9 +1,3 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - 'use strict'; const safeBuffer = require('safe-buffer'); diff --git a/lib/receiver.js b/lib/receiver.js index c18f6cc1f..396c9020d 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -1,9 +1,3 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - 'use strict'; const safeBuffer = require('safe-buffer'); diff --git a/lib/sender.js b/lib/sender.js index 61d73df30..b3dacbc41 100644 --- a/lib/sender.js +++ b/lib/sender.js @@ -1,9 +1,3 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - 'use strict'; const safeBuffer = require('safe-buffer'); diff --git a/lib/validation.js b/lib/validation.js index a8ac2e634..06269fcf1 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -1,9 +1,3 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - 'use strict'; try { diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 8bc3b2938..7ee643cce 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -1,9 +1,3 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - 'use strict'; const safeBuffer = require('safe-buffer'); diff --git a/lib/websocket.js b/lib/websocket.js index 032d2d43c..394f73c9a 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -1,9 +1,3 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - 'use strict'; const EventEmitter = require('events'); From 8d364067d4fc99df137b331bcb2c1294d3a71ab7 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 17 Jan 2018 18:44:59 +0100 Subject: [PATCH 462/669] [benchmark] Take into account both the incoming and outgoing data --- bench/speed.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bench/speed.js b/bench/speed.js index cae60a2bf..75dfd5a80 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -40,6 +40,7 @@ if (cluster.isMaster) { }; const humanSize = (bytes) => { + if (bytes >= 1073741824) return roundPrec(bytes / 1073741824, 2) + ' GiB'; if (bytes >= 1048576) return roundPrec(bytes / 1048576, 2) + ' MiB'; if (bytes >= 1024) return roundPrec(bytes / 1024, 2) + ' KiB'; return roundPrec(bytes, 2) + ' B'; @@ -61,7 +62,7 @@ if (cluster.isMaster) { ws.on('error', (err) => { console.error(err.stack); - cluster.worker.kill(); + cluster.worker.disconnect(); }); ws.on('open', () => { time = process.hrtime(); @@ -79,7 +80,7 @@ if (cluster.isMaster) { humanSize(size), useBinary ? 'binary' : 'text', roundPrec(elapsed / 1e9, 1), - humanSize(size * roundtrips / elapsed * 1e9) + '/s' + humanSize(size * 2 * roundtrips / elapsed * 1e9) + '/s' ); ws.close(); @@ -88,7 +89,7 @@ if (cluster.isMaster) { }; (function run () { - if (configs.length === 0) return cluster.worker.kill(); + if (configs.length === 0) return cluster.worker.disconnect(); var config = configs.shift(); config.push(run); runConfig.apply(null, config); From 141b696b0481dfdd8f59e5edf55e99fb2c88b1b6 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 18 Jan 2018 07:19:25 +0100 Subject: [PATCH 463/669] chore(package): update mocha to version 5.0.0 (#1283) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e81558816..d01e7da1f 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "eslint-plugin-node": "~5.2.0", "eslint-plugin-promise": "~3.6.0", "eslint-plugin-standard": "~3.0.0", - "mocha": "~4.1.0", + "mocha": "~5.0.0", "nyc": "~11.4.1", "utf-8-validate": "~4.0.0" } From c7b71435460083d1110eba55d6d3567c4ae6d283 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 20 Jan 2018 07:50:23 +0100 Subject: [PATCH 464/669] chore(package): update eslint to version 4.16.0 (#1284) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d01e7da1f..f43108545 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.15.0", + "eslint": "~4.16.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~5.2.0", From b8900786c2925a43543751e6dcf8fec671ebe504 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 25 Jan 2018 10:27:17 +0100 Subject: [PATCH 465/669] [minor] Clean up and rename `Receiver#readBuffer()` Rename `Receiver.prototype.readBuffer()` to `consume` and merge `Receiver.prototype.hasBufferedBytes()` into it. --- lib/receiver.js | 87 ++++++++++++++++++++----------------------------- 1 file changed, 36 insertions(+), 51 deletions(-) diff --git a/lib/receiver.js b/lib/receiver.js index 396c9020d..60b1409f1 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -62,63 +62,48 @@ class Receiver { } /** - * Consumes bytes from the available buffered data. + * Consumes `n` bytes from the buffered data, calls `cleanup` if necessary. * - * @param {Number} bytes The number of bytes to consume - * @return {Buffer} Consumed bytes + * @param {Number} n The number of bytes to consume + * @return {(Buffer|null)} The consumed bytes or `null` if `n` bytes are not + * available * @private */ - readBuffer (bytes) { - var offset = 0; - var dst; - var l; + consume (n) { + if (this._bufferedBytes < n) { + this._loop = false; + if (this._dead) this.cleanup(this._cleanupCallback); + return null; + } - this._bufferedBytes -= bytes; + this._bufferedBytes -= n; - if (bytes === this._buffers[0].length) return this._buffers.shift(); + if (n === this._buffers[0].length) return this._buffers.shift(); - if (bytes < this._buffers[0].length) { - dst = this._buffers[0].slice(0, bytes); - this._buffers[0] = this._buffers[0].slice(bytes); - return dst; + if (n < this._buffers[0].length) { + const buf = this._buffers[0]; + this._buffers[0] = buf.slice(n); + return buf.slice(0, n); } - dst = Buffer.allocUnsafe(bytes); + const dst = Buffer.allocUnsafe(n); - while (bytes > 0) { - l = this._buffers[0].length; + do { + const buf = this._buffers[0]; - if (bytes >= l) { - this._buffers[0].copy(dst, offset); - offset += l; - this._buffers.shift(); + if (n >= buf.length) { + this._buffers.shift().copy(dst, dst.length - n); } else { - this._buffers[0].copy(dst, offset, 0, bytes); - this._buffers[0] = this._buffers[0].slice(bytes); + buf.copy(dst, dst.length - n, 0, n); + this._buffers[0] = buf.slice(n); } - bytes -= l; - } + n -= buf.length; + } while (n > 0); return dst; } - /** - * Checks if the number of buffered bytes is bigger or equal than `n` and - * calls `cleanup` if necessary. - * - * @param {Number} n The number of bytes to check against - * @return {Boolean} `true` if `bufferedBytes >= n`, else `false` - * @private - */ - hasBufferedBytes (n) { - if (this._bufferedBytes >= n) return true; - - this._loop = false; - if (this._dead) this.cleanup(this._cleanupCallback); - return false; - } - /** * Adds new data to the parser. * @@ -170,9 +155,8 @@ class Receiver { * @private */ getInfo () { - if (!this.hasBufferedBytes(2)) return; - - const buf = this.readBuffer(2); + const buf = this.consume(2); + if (buf === null) return; if ((buf[0] & 0x30) !== 0x00) { this.error( @@ -278,9 +262,10 @@ class Receiver { * @private */ getPayloadLength16 () { - if (!this.hasBufferedBytes(2)) return; + const buf = this.consume(2); + if (buf === null) return; - this._payloadLength = this.readBuffer(2).readUInt16BE(0, true); + this._payloadLength = buf.readUInt16BE(0, true); this.haveLength(); } @@ -290,9 +275,9 @@ class Receiver { * @private */ getPayloadLength64 () { - if (!this.hasBufferedBytes(8)) return; + const buf = this.consume(8); + if (buf === null) return; - const buf = this.readBuffer(8); const num = buf.readUInt32BE(0, true); // @@ -333,9 +318,9 @@ class Receiver { * @private */ getMask () { - if (!this.hasBufferedBytes(4)) return; + this._mask = this.consume(4); + if (this._mask === null) return; - this._mask = this.readBuffer(4); this._state = GET_DATA; } @@ -348,9 +333,9 @@ class Receiver { var data = constants.EMPTY_BUFFER; if (this._payloadLength) { - if (!this.hasBufferedBytes(this._payloadLength)) return; + data = this.consume(this._payloadLength); + if (data === null) return; - data = this.readBuffer(this._payloadLength); if (this._masked) bufferUtil.unmask(data, this._mask); } From 5d8ab0eecff0c905150b9fe9a0925bb34d24b5d9 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 26 Jan 2018 13:04:25 +0100 Subject: [PATCH 466/669] [minor] Discard any data received after the close frame Remove the `'data'` listener when the close frame is received. --- lib/receiver.js | 18 ++++++------- lib/websocket.js | 59 +++++++++++++++++++------------------------ test/receiver.test.js | 13 ---------- 3 files changed, 34 insertions(+), 56 deletions(-) diff --git a/lib/receiver.js b/lib/receiver.js index 60b1409f1..c12070851 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -48,10 +48,11 @@ class Receiver { this._fragments = []; this._cleanupCallback = null; + this._isCleaningUp = false; this._hadError = false; - this._dead = false; this._loop = false; + this.add = this.add.bind(this); this.onmessage = null; this.onclose = null; this.onerror = null; @@ -72,7 +73,7 @@ class Receiver { consume (n) { if (this._bufferedBytes < n) { this._loop = false; - if (this._dead) this.cleanup(this._cleanupCallback); + if (this._isCleaningUp) this.cleanup(this._cleanupCallback); return null; } @@ -107,14 +108,12 @@ class Receiver { /** * Adds new data to the parser. * - * @param {Buffer} data A chunk of data + * @param {Buffer} chunk A chunk of data * @public */ - add (data) { - if (this._dead) return; - - this._bufferedBytes += data.length; - this._buffers.push(data); + add (chunk) { + this._bufferedBytes += chunk.length; + this._buffers.push(chunk); this.startLoop(); } @@ -532,10 +531,9 @@ class Receiver { * @public */ cleanup (cb) { - this._dead = true; - if (!this._hadError && (this._loop || this._state === INFLATING)) { this._cleanupCallback = cb; + this._isCleaningUp = true; } else { this._extensions = null; this._fragments = null; diff --git a/lib/websocket.js b/lib/websocket.js index 394f73c9a..1a465fffd 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -2,7 +2,6 @@ const EventEmitter = require('events'); const crypto = require('crypto'); -const Ultron = require('ultron'); const https = require('https'); const http = require('http'); const url = require('url'); @@ -50,7 +49,6 @@ class WebSocket extends EventEmitter { this._receiver = null; this._sender = null; this._socket = null; - this._ultron = null; if (address !== null) { if (!protocols) { @@ -123,18 +121,17 @@ class WebSocket extends EventEmitter { socket.setTimeout(0); socket.setNoDelay(); + socket.on('close', this._finalize); + socket.on('error', this._finalize); + socket.on('end', this._finalize); + this._receiver = new Receiver(this._extensions, maxPayload, this.binaryType); this._sender = new Sender(socket, this._extensions); - this._ultron = new Ultron(socket); this._socket = socket; - this._ultron.on('close', this._finalize); - this._ultron.on('error', this._finalize); - this._ultron.on('end', this._finalize); - if (head.length > 0) socket.unshift(head); - this._ultron.on('data', (data) => this._receiver.add(data)); + socket.on('data', this._receiver.add); this._receiver.onmessage = (data) => this.emit('message', data); this._receiver.onping = (data) => { @@ -143,6 +140,11 @@ class WebSocket extends EventEmitter { }; this._receiver.onpong = (data) => this.emit('pong', data); this._receiver.onclose = (code, reason) => { + // + // Discard any additional data that is received on the socket. + // + this._socket.removeListener('data', this._receiver.add); + this._closeFrameReceived = true; this._closeMessage = reason; this._closeCode = code; @@ -182,41 +184,32 @@ class WebSocket extends EventEmitter { this._finalized = true; if (typeof error === 'object') this.emit('error', error); - if (!this._socket) return this.emitClose(); + if (!this._socket) { + this.readyState = WebSocket.CLOSED; + this.emit('close', this._closeCode, this._closeMessage); + return; + } clearTimeout(this._closeTimer); - this._closeTimer = null; - - this._ultron.destroy(); - this._ultron = null; + this._socket.removeListener('data', this._receiver.add); + this._socket.removeListener('close', this._finalize); + this._socket.removeListener('error', this._finalize); + this._socket.removeListener('end', this._finalize); this._socket.on('error', constants.NOOP); if (!error) this._socket.end(); else this._socket.destroy(); - this._socket = null; - this._sender = null; + this._receiver.cleanup(() => { + this.readyState = WebSocket.CLOSED; - this._receiver.cleanup(() => this.emitClose()); - this._receiver = null; - } - - /** - * Emit the `close` event. - * - * @private - */ - emitClose () { - this.readyState = WebSocket.CLOSED; - - this.emit('close', this._closeCode, this._closeMessage); - - if (this._extensions[PerMessageDeflate.extensionName]) { - this._extensions[PerMessageDeflate.extensionName].cleanup(); - } + if (this._extensions[PerMessageDeflate.extensionName]) { + this._extensions[PerMessageDeflate.extensionName].cleanup(); + } - this.removeAllListeners(); + this.emit('close', this._closeCode, this._closeMessage); + }); } /** diff --git a/test/receiver.test.js b/test/receiver.test.js index dcf113163..4335a12c2 100644 --- a/test/receiver.test.js +++ b/test/receiver.test.js @@ -443,19 +443,6 @@ describe('Receiver', function () { assert.deepStrictEqual(data, ['', 'Hello']); }); - it('ignores data received after a close frame', function () { - const results = []; - const push = results.push.bind(results); - const p = new Receiver(); - - p.onclose = p.onmessage = push; - - p.add(Buffer.from('8800', 'hex')); - p.add(Buffer.from('8100', 'hex')); - - assert.deepStrictEqual(results, [1005, '']); - }); - it('raises an error when RSV1 is on and permessage-deflate is disabled', function (done) { const p = new Receiver(); From be0b5652b26ffd0eb6e28784f452b782d4b5c87a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 26 Jan 2018 15:13:18 +0100 Subject: [PATCH 467/669] [fix] Handle cases where `socket.bufferSize` is `undefined` --- lib/websocket.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 1a465fffd..66c549090 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -94,12 +94,12 @@ class WebSocket extends EventEmitter { * @type {Number} */ get bufferedAmount () { - var amount = 0; + if (!this._socket) return 0; - if (this._socket) { - amount = this._socket.bufferSize + this._sender._bufferedBytes; - } - return amount; + // + // `socket.bufferSize` is `undefined` if the socket is closed. + // + return (this._socket.bufferSize || 0) + this._sender._bufferedBytes; } /** From 9c39adfc9734073c1e949c62d56614f3c315ebee Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 27 Jan 2018 19:52:17 +0100 Subject: [PATCH 468/669] [minor] Remove ultron dependency --- lib/websocket-server.js | 47 +++++++++++++++++++++++++++++------------ package.json | 3 +-- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 7ee643cce..7c1438e76 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -3,7 +3,6 @@ const safeBuffer = require('safe-buffer'); const EventEmitter = require('events'); const crypto = require('crypto'); -const Ultron = require('ultron'); const http = require('http'); const url = require('url'); @@ -75,13 +74,14 @@ class WebSocketServer extends EventEmitter { } if (this._server) { - this._ultron = new Ultron(this._server); - this._ultron.on('listening', () => this.emit('listening')); - this._ultron.on('error', (err) => this.emit('error', err)); - this._ultron.on('upgrade', (req, socket, head) => { - this.handleUpgrade(req, socket, head, (client) => { - this.emit('connection', client, req); - }); + this._removeListeners = addListeners(this._server, { + listening: this.emit.bind(this, 'listening'), + error: this.emit.bind(this, 'error'), + upgrade: (req, socket, head) => { + this.handleUpgrade(req, socket, head, (ws) => { + this.emit('connection', ws, req); + }); + } }); } @@ -107,8 +107,8 @@ class WebSocketServer extends EventEmitter { const server = this._server; if (server) { - this._ultron.destroy(); - this._ultron = this._server = null; + this._removeListeners(); + this._removeListeners = this._server = null; // // Close the http server if it was internally created. @@ -144,7 +144,7 @@ class WebSocketServer extends EventEmitter { * @public */ handleUpgrade (req, socket, head, cb) { - socket.on('error', socketError); + socket.on('error', socketOnError); const version = +req.headers['sec-websocket-version']; const extensions = {}; @@ -264,7 +264,7 @@ class WebSocketServer extends EventEmitter { this.emit('headers', headers, req); socket.write(headers.concat('\r\n').join('\r\n')); - socket.removeListener('error', socketError); + socket.removeListener('error', socketOnError); ws.setSocket(socket, head, this.options.maxPayload); @@ -279,12 +279,31 @@ class WebSocketServer extends EventEmitter { module.exports = WebSocketServer; +/** + * Add event listeners on an `EventEmitter` using a map of + * pairs. + * + * @param {EventEmitter} server The event emitter + * @param {Object.} map The listeners to add + * @return {Function} A function that will remove the added listeners when called + * @private + */ +function addListeners (server, map) { + for (const event of Object.keys(map)) server.on(event, map[event]); + + return function removeListeners () { + for (const event of Object.keys(map)) { + server.removeListener(event, map[event]); + } + }; +} + /** * Handle premature socket errors. * * @private */ -function socketError () { +function socketOnError () { this.destroy(); } @@ -309,6 +328,6 @@ function abortConnection (socket, code, message) { ); } - socket.removeListener('error', socketError); + socket.removeListener('error', socketOnError); socket.destroy(); } diff --git a/package.json b/package.json index f43108545..1b87bc46f 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,7 @@ }, "dependencies": { "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" + "safe-buffer": "~5.1.0" }, "devDependencies": { "benchmark": "~2.1.2", From 7e5ed3d8216d2b5220bdfd84b1823912334fc0d5 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 31 Jan 2018 14:14:24 +0100 Subject: [PATCH 469/669] [minor] Optimize `Receiver#cleanup()` for the common case --- lib/receiver.js | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/lib/receiver.js b/lib/receiver.js index c12070851..ff424f3a7 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -422,8 +422,8 @@ class Receiver { controlMessage (data) { if (this._opcode === 0x08) { if (data.length === 0) { - this.onclose(1005, ''); this._loop = false; + this.onclose(1005, ''); this.cleanup(this._cleanupCallback); } else if (data.length === 1) { this.error( @@ -453,8 +453,8 @@ class Receiver { return; } - this.onclose(code, buf.toString()); this._loop = false; + this.onclose(code, buf.toString()); this.cleanup(this._cleanupCallback); } @@ -475,9 +475,9 @@ class Receiver { * @private */ error (err, code) { - this.onerror(err, code); this._hadError = true; this._loop = false; + this.onerror(err, code); this.cleanup(this._cleanupCallback); } @@ -531,24 +531,30 @@ class Receiver { * @public */ cleanup (cb) { + if (this._extensions === null) { + if (cb) cb(); + return; + } + if (!this._hadError && (this._loop || this._state === INFLATING)) { this._cleanupCallback = cb; this._isCleaningUp = true; - } else { - this._extensions = null; - this._fragments = null; - this._buffers = null; - this._mask = null; - - this._cleanupCallback = null; - this.onmessage = null; - this.onclose = null; - this.onerror = null; - this.onping = null; - this.onpong = null; - - if (cb) cb(); + return; } + + this._extensions = null; + this._fragments = null; + this._buffers = null; + this._mask = null; + + this._cleanupCallback = null; + this.onmessage = null; + this.onclose = null; + this.onerror = null; + this.onping = null; + this.onpong = null; + + if (cb) cb(); } } From 57c8d29d7139d5bb899fc9e08b58d6b5c27d16f8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 31 Jan 2018 17:42:43 +0100 Subject: [PATCH 470/669] [fix] Ensure that the `'error'` event is emitted at most once --- lib/websocket.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 66c549090..20df7a59b 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -172,9 +172,10 @@ class WebSocket extends EventEmitter { } /** - * Clean up and release internal resources. + * Clean up internal resources and emit the `'close'` event. * - * @param {(Boolean|Error)} error Indicates whether or not an error occurred + * @param {(Boolean|Error|undefined)} error Indicates whether or not an error + * occurred * @private */ finalize (error) { @@ -183,8 +184,11 @@ class WebSocket extends EventEmitter { this.readyState = WebSocket.CLOSING; this._finalized = true; - if (typeof error === 'object') this.emit('error', error); if (!this._socket) { + // + // `error` is always an `Error` instance in this case. + // + this.emit('error', error); this.readyState = WebSocket.CLOSED; this.emit('close', this._closeCode, this._closeMessage); return; @@ -202,6 +206,14 @@ class WebSocket extends EventEmitter { else this._socket.destroy(); this._receiver.cleanup(() => { + if (typeof error === 'object' && !this._receiver._hadError) { + // + // Re-emit the `net.Socket` error if an `'error'` event has not already + // been emitted. + // + this.emit('error', error); + } + this.readyState = WebSocket.CLOSED; if (this._extensions[PerMessageDeflate.extensionName]) { From 6f32e1715ccbaba0a14e2ecdc88a86833d2a4fe2 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 3 Feb 2018 07:47:32 +0100 Subject: [PATCH 471/669] chore(package): update eslint to version 4.17.0 (#1291) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1b87bc46f..e2530737f 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.16.0", + "eslint": "~4.17.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~5.2.0", From 8ea9402928f2c2d16ad4e78220984a71e2ec04db Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 3 Feb 2018 11:28:13 +0100 Subject: [PATCH 472/669] [fix] Emit the first error that occurred, not the last --- lib/websocket.js | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 20df7a59b..98051b913 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -49,6 +49,7 @@ class WebSocket extends EventEmitter { this._receiver = null; this._sender = null; this._socket = null; + this._error = null; if (address !== null) { if (!protocols) { @@ -158,13 +159,8 @@ class WebSocket extends EventEmitter { this._closeMessage = ''; this._closeCode = code; - // - // Ensure that the error is emitted even if `WebSocket#finalize()` has - // already been called. - // - this.readyState = WebSocket.CLOSING; - this.emit('error', error); - this.finalize(true); + if (!this._finalized) this.finalize(error); + else if (!this._error) this.emit('error', error); }; this.readyState = WebSocket.OPEN; @@ -174,8 +170,7 @@ class WebSocket extends EventEmitter { /** * Clean up internal resources and emit the `'close'` event. * - * @param {(Boolean|Error|undefined)} error Indicates whether or not an error - * occurred + * @param {(Boolean|Error)} error Indicates whether or not an error occurred * @private */ finalize (error) { @@ -202,16 +197,19 @@ class WebSocket extends EventEmitter { this._socket.removeListener('end', this._finalize); this._socket.on('error', constants.NOOP); - if (!error) this._socket.end(); - else this._socket.destroy(); + if (error) { + if (error !== true) this._error = error; + this._socket.destroy(); + } else { + this._socket.end(); + } this._receiver.cleanup(() => { - if (typeof error === 'object' && !this._receiver._hadError) { - // - // Re-emit the `net.Socket` error if an `'error'` event has not already - // been emitted. - // - this.emit('error', error); + const err = this._error; + + if (err) { + this._error = null; + this.emit('error', err); } this.readyState = WebSocket.CLOSED; From 66b0d553d146cfc9fa17c93f954d2540667b4731 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 3 Feb 2018 12:50:20 +0100 Subject: [PATCH 473/669] [minor] Use `do...while` in `Receiver#startLoop()` --- lib/receiver.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/receiver.js b/lib/receiver.js index ff424f3a7..ad1187b83 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -125,7 +125,7 @@ class Receiver { startLoop () { this._loop = true; - while (this._loop) { + do { switch (this._state) { case GET_INFO: this.getInfo(); @@ -145,7 +145,7 @@ class Receiver { default: // `INFLATING` this._loop = false; } - } + } while (this._loop); } /** From 8d61fa057ecc509376713ba42ee1fdea4340ff90 Mon Sep 17 00:00:00 2001 From: Tossapon Nuanchuay Date: Tue, 6 Feb 2018 02:48:05 +0700 Subject: [PATCH 474/669] [api] Add `WebSocketServer.prototype.address()` (#1294) --- doc/ws.md | 8 ++++++++ lib/websocket-server.js | 18 ++++++++++++++++++ test/websocket-server.test.js | 28 ++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/doc/ws.md b/doc/ws.md index 44f1bd8e7..62de3f844 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -124,6 +124,14 @@ Emitted when the underlying server has been bound. A set that stores all connected clients. Please note that this property is only added when the `clientTracking` is truthy. +### server.address() + +Returns an object with `port`, `family`, and `address` properties specifying +the bound address, the address family name, and port of the server as reported +by the operating system if listening on an IP socket. +If the server is listening on a pipe or UNIX domain socket, the name is +returned as a string. + ### server.close([callback]) Close the server and terminate all clients, calls callback when done. diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 7c1438e76..ee0913fb5 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -90,6 +90,24 @@ class WebSocketServer extends EventEmitter { this.options = options; } + /** + * Returns the bound address, the address family name, and port of the server + * as reported by the operating system if listening on an IP socket. + * If the server is listening on a pipe or UNIX domain socket, the name is + * returned as a string. + * + * @return {(Object|String|null)} The address of the server + * @public + */ + address () { + if (this.options.noServer) { + throw new Error('The server is operating in "noServer" mode'); + } + + if (!this._server) return null; + return this._server.address(); + } + /** * Close the server. * diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 14ea76c49..21f2c3ecc 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -137,6 +137,34 @@ describe('WebSocketServer', function () { }); }); + describe('#address', function () { + it('returns the address of the server', function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + const addr = wss.address(); + + assert.deepStrictEqual(addr, wss._server.address()); + wss.close(done); + }); + }); + + it('throws an error when operating in "noServer" mode', function () { + const wss = new WebSocket.Server({ noServer: true }); + + assert.throws(() => { + wss.address(); + }, /^Error: The server is operating in "noServer" mode$/); + }); + + it('returns `null` if called after close', function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + wss.close(() => { + assert.strictEqual(wss.address(), null); + done(); + }); + }); + }); + }); + describe('#close', function () { it('does not thrown when called twice', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { From 828194044bf247af852b31c49e2800d557fedeff Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 5 Feb 2018 21:01:26 +0100 Subject: [PATCH 475/669] chore(package): update eslint-plugin-node to version 6.0.0 (#1295) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e2530737f..db9da5ced 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "eslint": "~4.17.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", - "eslint-plugin-node": "~5.2.0", + "eslint-plugin-node": "~6.0.0", "eslint-plugin-promise": "~3.6.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~5.0.0", From 98d195578eb36b171ad8ce8c5ee7c9e733db911b Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Sat, 10 Feb 2018 14:18:41 +0100 Subject: [PATCH 476/669] [doc] Add a link to isomorphic-ws (#1298) `isomorphic-ws` is a tiny wrapper around `ws` (peer dependency) which returns `global.WebSocket` on the browser. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 12f06c16c..ccf013b5c 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,9 @@ Passes the quite extensive Autobahn test suite: [server][server-report], reference to a back end with the role of a client in the WebSocket communication. Browser clients must use the native [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) object. +To make the same code work seamlessy on Node.js and the browser, you can use +one of the many wrappers available on npm, like +[isomorphic-ws](https://github.com/heineiuo/isomorphic-ws). ## Table of Contents From 91947706f4a2ce6d73d8b0f59f6e05c014554823 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 10 Feb 2018 21:28:09 +0100 Subject: [PATCH 477/669] [doc] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ccf013b5c..83d21dad3 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Passes the quite extensive Autobahn test suite: [server][server-report], reference to a back end with the role of a client in the WebSocket communication. Browser clients must use the native [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) object. -To make the same code work seamlessy on Node.js and the browser, you can use +To make the same code work seamlessly on Node.js and the browser, you can use one of the many wrappers available on npm, like [isomorphic-ws](https://github.com/heineiuo/isomorphic-ws). From c8408c04c472b49ba63eea4d2c1795bcd74b5fd5 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 10 Feb 2018 21:31:50 +0100 Subject: [PATCH 478/669] [doc] Fix docs for `onerror` listener --- doc/ws.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index 62de3f844..da0f285f4 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -336,7 +336,7 @@ a `CloseEvent` named "close". - {Function} An event listener to be called when an error occurs. The listener receives -an `Error` instance. +an `ErrorEvent` named "error". ### websocket.onmessage From 75b63975d1d90a3e366fb4ca40e230113dc40c27 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 15 Feb 2018 16:00:13 +0100 Subject: [PATCH 479/669] [fix] Ensure that the status code is not incorrectly overwritten --- lib/websocket.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 98051b913..a5f606f9f 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -150,17 +150,16 @@ class WebSocket extends EventEmitter { this._closeMessage = reason; this._closeCode = code; - if (this._finalized) return; - if (code === 1005) this.close(); else this.close(code, reason); }; this._receiver.onerror = (error, code) => { - this._closeMessage = ''; + if (this._error) return; + this._closeCode = code; if (!this._finalized) this.finalize(error); - else if (!this._error) this.emit('error', error); + else this.emit('error', error); }; this.readyState = WebSocket.OPEN; From 563edbd6fa14d70aace66f56b613927be05d5d0c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 15 Feb 2018 16:38:00 +0100 Subject: [PATCH 480/669] [test] Use `WebSocketServer#address()` --- test/websocket-server.test.js | 62 +++++------ test/websocket.test.js | 200 ++++++++++++---------------------- 2 files changed, 96 insertions(+), 166 deletions(-) diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 21f2c3ecc..06c3e8c35 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -39,8 +39,7 @@ describe('WebSocketServer', function () { maxPayload, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); }); wss.on('connection', (ws) => { @@ -57,7 +56,7 @@ describe('WebSocketServer', function () { it('emits an error if http server bind fails', function (done) { const wss1 = new WebSocket.Server({ port: 0 }, () => { const wss2 = new WebSocket.Server({ - port: wss1._server.address().port + port: wss1.address().port }); wss2.on('error', () => wss1.close(done)); @@ -96,7 +95,7 @@ describe('WebSocketServer', function () { it('426s for non-Upgrade requests', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - http.get(`http://localhost:${wss._server.address().port}`, (res) => { + http.get(`http://localhost:${wss.address().port}`, (res) => { let body = ''; assert.strictEqual(res.statusCode, 426); @@ -179,8 +178,7 @@ describe('WebSocketServer', function () { it('closes all clients', function (done) { let closes = 0; const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('close', () => { if (++closes === 2) done(); }); @@ -241,8 +239,7 @@ describe('WebSocketServer', function () { it('returns a list of connected clients', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { assert.strictEqual(wss.clients.size, 0); - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); }); wss.on('connection', (ws) => { @@ -254,8 +251,7 @@ describe('WebSocketServer', function () { it('can be disabled', function (done) { const wss = new WebSocket.Server({ port: 0, clientTracking: false }, () => { assert.strictEqual(wss.clients, undefined); - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.close()); }); @@ -268,8 +264,7 @@ describe('WebSocketServer', function () { it('is updated when client terminates the connection', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.terminate()); }); @@ -284,8 +279,7 @@ describe('WebSocketServer', function () { it('is updated when client closes the connection', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.close()); }); @@ -337,7 +331,7 @@ describe('WebSocketServer', function () { it("closes the connection when path doesn't match", function (done) { const wss = new WebSocket.Server({ port: 0, path: '/ws' }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket' @@ -354,7 +348,7 @@ describe('WebSocketServer', function () { it('closes the connection when protocol version is Hixie-76', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'WebSocket', @@ -376,7 +370,7 @@ describe('WebSocketServer', function () { it('fails if the Sec-WebSocket-Key header is invalid', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket' @@ -397,7 +391,7 @@ describe('WebSocketServer', function () { it('fails is the Sec-WebSocket-Version header is invalid (1/2)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', @@ -419,7 +413,7 @@ describe('WebSocketServer', function () { it('fails is the Sec-WebSocket-Version header is invalid (2/2)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', @@ -445,7 +439,7 @@ describe('WebSocketServer', function () { port: 0 }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', @@ -474,7 +468,7 @@ describe('WebSocketServer', function () { port: 0 }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', @@ -534,8 +528,7 @@ describe('WebSocketServer', function () { verifyClient: (o, cb) => process.nextTick(cb, true), port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); }); wss.on('connection', (ws) => wss.close(done)); @@ -547,7 +540,7 @@ describe('WebSocketServer', function () { port: 0 }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', @@ -573,7 +566,7 @@ describe('WebSocketServer', function () { port: 0 }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', @@ -634,7 +627,7 @@ describe('WebSocketServer', function () { it('handles data passed along with the upgrade request', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.request({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', @@ -663,8 +656,10 @@ describe('WebSocketServer', function () { return protocols.pop(); }; const wss = new WebSocket.Server({ handleProtocols, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, ['foo', 'bar']); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, [ + 'foo', + 'bar' + ]); ws.on('open', () => { assert.strictEqual(ws.protocol, 'bar'); @@ -679,7 +674,7 @@ describe('WebSocketServer', function () { port: 0 }, () => { const req = http.get({ - port: wss._server.address().port, + port: wss.address().port, headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket', @@ -698,8 +693,7 @@ describe('WebSocketServer', function () { it('emits the `headers` event', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); wss.on('headers', (headers, request) => { assert.deepStrictEqual(headers.slice(0, 3), [ @@ -719,8 +713,7 @@ describe('WebSocketServer', function () { describe('permessage-deflate', function () { it('is disabled by default', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); }); wss.on('connection', (ws, req) => { @@ -738,8 +731,7 @@ describe('WebSocketServer', function () { perMessageDeflate: { clientMaxWindowBits: 8 }, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('upgrade', (res) => { assert.strictEqual( diff --git a/test/websocket.test.js b/test/websocket.test.js index cf41e7055..3801fdd9c 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -66,8 +66,7 @@ describe('WebSocket', function () { it('accepts the `localAddress` option', function (done) { const wss = new WebSocket.Server({ host: '127.0.0.1', port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { localAddress: '127.0.0.2' }); @@ -114,8 +113,9 @@ describe('WebSocket', function () { if (!addresses.some((val) => val.address === '::1')) return this.skip(); const wss = new WebSocket.Server({ host: '::1', port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { family: 6 }); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { + family: 6 + }); }); wss.on('connection', (ws, req) => { @@ -198,8 +198,7 @@ describe('WebSocket', function () { it('defaults to zero upon "open"', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.onopen = () => { assert.strictEqual(ws.bufferedAmount, 0); @@ -213,8 +212,7 @@ describe('WebSocket', function () { perMessageDeflate: true, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { perMessageDeflate: { threshold: 0 } }); @@ -233,8 +231,7 @@ describe('WebSocket', function () { it('takes into account the data in the socket queue', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); }); wss.on('connection', (ws) => { @@ -253,8 +250,7 @@ describe('WebSocket', function () { describe('`extensions`', function () { it('exposes the negotiated extensions names (1/2)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); assert.strictEqual(ws.extensions, ''); @@ -275,8 +271,7 @@ describe('WebSocket', function () { perMessageDeflate: true, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); assert.strictEqual(ws.extensions, ''); @@ -296,7 +291,7 @@ describe('WebSocket', function () { describe('`protocol`', function () { it('exposes the subprotocol selected by the server', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; + const port = wss.address().port; const ws = new WebSocket(`ws://localhost:${port}`, 'foo'); assert.strictEqual(ws.extensions, ''); @@ -325,8 +320,7 @@ describe('WebSocket', function () { it('is set to `OPEN` once connection is established', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { assert.strictEqual(ws.readyState, WebSocket.OPEN); @@ -339,8 +333,7 @@ describe('WebSocket', function () { it('is set to `CLOSED` once connection is closed', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('close', () => { assert.strictEqual(ws.readyState, WebSocket.CLOSED); @@ -353,8 +346,7 @@ describe('WebSocket', function () { it('is set to `CLOSED` once connection is terminated', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('close', () => { assert.strictEqual(ws.readyState, WebSocket.CLOSED); @@ -379,8 +371,7 @@ describe('WebSocket', function () { describe('Events', function () { it("emits an 'error' event if an error occurs", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('error', (err) => { assert.ok(err instanceof RangeError); @@ -404,8 +395,7 @@ describe('WebSocket', function () { it("emits an 'upgrade' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('upgrade', (res) => { assert.ok(res instanceof http.IncomingMessage); wss.close(done); @@ -415,8 +405,7 @@ describe('WebSocket', function () { it("emits a 'ping' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('ping', () => wss.close(done)); }); @@ -425,8 +414,7 @@ describe('WebSocket', function () { it("emits a 'pong' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('pong', () => wss.close(done)); }); @@ -665,7 +653,7 @@ describe('WebSocket', function () { describe('Connection with query string', function () { it('connects when pathname is not null', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; + const port = wss.address().port; const ws = new WebSocket(`ws://localhost:${port}/?token=qwerty`); ws.on('open', () => wss.close(done)); @@ -674,7 +662,7 @@ describe('WebSocket', function () { it('connects when pathname is null', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; + const port = wss.address().port; const ws = new WebSocket(`ws://localhost:${port}?token=qwerty`); ws.on('open', () => wss.close(done)); @@ -705,8 +693,7 @@ describe('WebSocket', function () { it('can send a ping with no data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { ws.ping(() => ws.ping()); @@ -725,8 +712,7 @@ describe('WebSocket', function () { it('can send a ping with data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { ws.ping('hi', () => ws.ping('hi', true)); @@ -744,8 +730,7 @@ describe('WebSocket', function () { it('can send numbers as ping payload', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.ping(0)); }); @@ -782,8 +767,7 @@ describe('WebSocket', function () { it('can send a pong with no data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { ws.pong(() => ws.pong()); @@ -802,8 +786,7 @@ describe('WebSocket', function () { it('can send a pong with data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { ws.pong('hi', () => ws.pong('hi', true)); @@ -821,8 +804,7 @@ describe('WebSocket', function () { it('can send numbers as pong payload', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.pong(0)); }); @@ -845,8 +827,7 @@ describe('WebSocket', function () { array[i] = i / 5; } - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send(array, { compress: false })); ws.on('message', (msg) => { @@ -862,8 +843,7 @@ describe('WebSocket', function () { it('can send text data', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send('hi')); ws.on('message', (message) => { @@ -879,8 +859,7 @@ describe('WebSocket', function () { it('does not override the `fin` option', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { ws.send('fragment', { fin: false }); @@ -898,8 +877,7 @@ describe('WebSocket', function () { it('sends numbers as strings', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send(0)); }); @@ -924,8 +902,7 @@ describe('WebSocket', function () { const buf = Buffer.from(partial.buffer) .slice(partial.byteOffset, partial.byteOffset + partial.byteLength); - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send(partial, { binary: true })); ws.on('message', (message) => { @@ -942,8 +919,7 @@ describe('WebSocket', function () { it('can send binary data as a buffer', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const buf = Buffer.from('foobar'); - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send(buf, { binary: true })); ws.on('message', (message) => { @@ -965,8 +941,7 @@ describe('WebSocket', function () { array[i] = i / 2; } - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send(array.buffer)); ws.onmessage = (event) => { @@ -983,8 +958,7 @@ describe('WebSocket', function () { it('can send a `Buffer`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const buf = Buffer.from('foobar'); - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send(buf)); @@ -1026,8 +1000,7 @@ describe('WebSocket', function () { it('calls the optional callback when data is written out', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { ws.send('hi', (err) => { @@ -1040,8 +1013,7 @@ describe('WebSocket', function () { it('works when the `data` argument is falsy', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send()); }); @@ -1056,8 +1028,7 @@ describe('WebSocket', function () { it('can send text data with `mask` option set to `false`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send('hi', { mask: false })); }); @@ -1078,8 +1049,7 @@ describe('WebSocket', function () { } const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.send(array, { mask: false })); }); @@ -1096,8 +1066,7 @@ describe('WebSocket', function () { describe('#close', function () { it('closes the connection if called while connecting (1/2)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { @@ -1117,8 +1086,7 @@ describe('WebSocket', function () { verifyClient: (info, cb) => setTimeout(cb, 300, true), port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { @@ -1147,8 +1115,7 @@ describe('WebSocket', function () { it("can be called from a listener of the 'upgrade' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { @@ -1165,8 +1132,7 @@ describe('WebSocket', function () { it('throws an error if the first argument is invalid (1/2)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { assert.throws( @@ -1181,8 +1147,7 @@ describe('WebSocket', function () { it('throws an error if the first argument is invalid (2/2)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { assert.throws( @@ -1197,8 +1162,7 @@ describe('WebSocket', function () { it('emits an error if the close frame can not be sent', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const socket = net.createConnection(port, () => { + const socket = net.createConnection(wss.address().port, () => { socket.write( 'GET / HTTP/1.1\r\n' + 'Host: localhost\r\n' + @@ -1229,8 +1193,7 @@ describe('WebSocket', function () { it('sends the close status code only when necessary', function (done) { let sent; const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { ws._socket.once('data', (data) => { @@ -1256,8 +1219,7 @@ describe('WebSocket', function () { it('works when close reason is not specified', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.close(1000)); }); @@ -1273,8 +1235,7 @@ describe('WebSocket', function () { it('works when close reason is specified', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => ws.close(1000, 'some reason')); }); @@ -1293,8 +1254,7 @@ describe('WebSocket', function () { clientTracking: false, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { ws.on('close', (code, reason) => { @@ -1312,8 +1272,7 @@ describe('WebSocket', function () { perMessageDeflate: { threshold: 0 }, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); const messages = []; ws.on('message', (message) => messages.push(message)); @@ -1337,8 +1296,7 @@ describe('WebSocket', function () { it('allows close code 1013', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('close', (code) => { assert.strictEqual(code, 1013); @@ -1351,8 +1309,7 @@ describe('WebSocket', function () { it('does nothing if `readyState` is `CLOSED`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('close', (code) => { assert.strictEqual(code, 1005); @@ -1369,8 +1326,7 @@ describe('WebSocket', function () { describe('#terminate', function () { it('closes the connection if called while connecting (1/2)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { @@ -1390,8 +1346,7 @@ describe('WebSocket', function () { verifyClient: (info, cb) => setTimeout(cb, 300, true), port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { @@ -1420,8 +1375,7 @@ describe('WebSocket', function () { it("can be called from a listener of the 'upgrade' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { @@ -1438,8 +1392,7 @@ describe('WebSocket', function () { it('does nothing if `readyState` is `CLOSED`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('close', (code) => { assert.strictEqual(code, 1006); @@ -1476,8 +1429,7 @@ describe('WebSocket', function () { it('works like the `EventEmitter` interface', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.onmessage = (messageEvent) => { assert.strictEqual(messageEvent.data, 'foo'); @@ -1571,8 +1523,7 @@ describe('WebSocket', function () { it('wraps text data in a `MessageEvent`', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.addEventListener('open', () => ws.send('hi')); ws.addEventListener('message', (messageEvent) => { @@ -1588,8 +1539,7 @@ describe('WebSocket', function () { it('receives a `CloseEvent` when server closes (1000)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.addEventListener('close', (closeEvent) => { assert.ok(closeEvent.wasClean); @@ -1604,8 +1554,7 @@ describe('WebSocket', function () { it('receives a `CloseEvent` when server closes (4000)', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.addEventListener('close', (closeEvent) => { assert.ok(closeEvent.wasClean); @@ -1621,8 +1570,7 @@ describe('WebSocket', function () { it('sets `target` and `type` on events', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const err = new Error('forced'); - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.addEventListener('open', (openEvent) => { assert.strictEqual(openEvent.type, 'open'); @@ -1653,8 +1601,7 @@ describe('WebSocket', function () { it('passes binary data as a Node.js `Buffer` by default', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.onmessage = (evt) => { assert.ok(Buffer.isBuffer(evt.data)); @@ -1667,8 +1614,7 @@ describe('WebSocket', function () { it('ignores `binaryType` for text messages', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.binaryType = 'arraybuffer'; @@ -1683,8 +1629,7 @@ describe('WebSocket', function () { it('allows to update `binaryType` on the fly', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); function testType (binaryType, next) { const buf = Buffer.from(binaryType); @@ -2014,8 +1959,7 @@ describe('WebSocket', function () { perMessageDeflate: { threshold: 0 }, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { perMessageDeflate: { threshold: 0 } }); @@ -2042,8 +1986,7 @@ describe('WebSocket', function () { perMessageDeflate: { threshold: 0 }, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { perMessageDeflate: { threshold: 0 } }); @@ -2070,8 +2013,7 @@ describe('WebSocket', function () { perMessageDeflate: { threshold: 0 }, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { perMessageDeflate: { threshold: 0 } }); @@ -2092,8 +2034,7 @@ describe('WebSocket', function () { perMessageDeflate: { threshold: 0 }, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); const messages = []; ws.on('message', (message) => messages.push(message)); @@ -2115,8 +2056,7 @@ describe('WebSocket', function () { describe('#send', function () { it('ignores the `compress` option if the extension is disabled', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { perMessageDeflate: false }); @@ -2139,8 +2079,7 @@ describe('WebSocket', function () { perMessageDeflate: { threshold: 0 }, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { perMessageDeflate: { threshold: 0 } }); @@ -2159,8 +2098,7 @@ describe('WebSocket', function () { perMessageDeflate: true, port: 0 }, () => { - const port = wss._server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); const messages = []; ws.on('message', (message) => { From 36f249df9ca23720f4cd4bbe57ac600b5cf1b0b4 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 17 Feb 2018 07:33:44 +0100 Subject: [PATCH 481/669] chore(package): update eslint to version 4.18.0 (#1304) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index db9da5ced..bd27bdbdc 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.17.0", + "eslint": "~4.18.0", "eslint-config-standard": "~10.2.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~6.0.0", From 84fa837dc074baef3d4f81d33120602b58a6c133 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 19 Feb 2018 07:19:29 +0100 Subject: [PATCH 482/669] chore(package): update eslint-config-standard to version 11.0.0 (#1307) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bd27bdbdc..64ff4aedd 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "benchmark": "~2.1.2", "bufferutil": "~3.0.0", "eslint": "~4.18.0", - "eslint-config-standard": "~10.2.0", + "eslint-config-standard": "~11.0.0", "eslint-plugin-import": "~2.8.0", "eslint-plugin-node": "~6.0.0", "eslint-plugin-promise": "~3.6.0", From f2b5105b0389be854c2f09ed1b0ff481eae73f46 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 22 Feb 2018 07:42:00 +0100 Subject: [PATCH 483/669] chore(package): update eslint-plugin-import to version 2.9.0 (#1309) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 64ff4aedd..6adac8b8b 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "bufferutil": "~3.0.0", "eslint": "~4.18.0", "eslint-config-standard": "~11.0.0", - "eslint-plugin-import": "~2.8.0", + "eslint-plugin-import": "~2.9.0", "eslint-plugin-node": "~6.0.0", "eslint-plugin-promise": "~3.6.0", "eslint-plugin-standard": "~3.0.0", From 053a3220fa80e800097c2c2e24b6ac1269fa045e Mon Sep 17 00:00:00 2001 From: Samuel Reed Date: Wed, 21 Feb 2018 23:38:51 -0800 Subject: [PATCH 484/669] [minor] Add `zlib{Deflate,Inflate}Options` options (#1306) Useful to adjust other options, and rather than support and document every single one, it is simpler to allow passing on an object wholesale. The `level` and `memLevel` options are still supported but should be removed in 5.x. --- README.md | 44 ++++++++++++++++++++++ doc/ws.md | 5 ++- lib/permessage-deflate.js | 29 +++++++++----- test/permessage-deflate.test.js | 67 ++++++++++++++++++++++++++++++++- 4 files changed, 133 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 83d21dad3..423ed6a33 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,47 @@ The extension is disabled by default on the server and enabled by default on the client. It adds a significant overhead in terms of performance and memory consumption so we suggest to enable it only if it is really needed. +Note that Node.js has a variety of issues with high-performance compression, +where increased concurrency, especially on Linux, can lead to +[catastrophic memory fragmentation][node-zlib-bug] and slow performance. +If you intend to use `permessage-deflate` in production, it is worthwhile +to set up a test representative of your workload and ensure Node.js/zlib will +handle it with acceptable performance and memory usage. + +Tuning of `permessage-deflate` can be done via the options defined below. +You can also use `zlibDeflateOptions` and `zlibInflateOptions`, which is passed +directly into the creation of +[raw deflate/inflate streams][node-zlib-deflaterawdocs]. + +See [the docs][ws-server-options] for more options. + +```js +const WebSocket = require('ws'); + +const wss = new WebSocket.Server({ + port: 8080, + perMessageDeflate: { + zlibDeflateOptions: { // See zlib defaults + chunkSize: 1024, + memLevel: 7, + level: 3, + }, + zlibInflateOptions: { + chunkSize: 10 * 1024 + }, + // Other options settable: + clientNoContextTakeover: true, // defaults to negotiated value + serverNoContextTakeover: true, // defaults to negotiated value + clientMaxWindowBits: 10, // defaults to negotiated value + serverMaxWindowBits: 10, // defaults to negotiated value + // Below options specified as default values + concurrencyLimit: 10, // limits zlib concurrency for perf + threshold: 1024, // Size (in bytes) below which messages + // should not be compressed + } +}); +``` + The client will only use the extension if it is supported and enabled on the server. To always disable the extension on the client set the `perMessageDeflate` option to `false`. @@ -344,3 +385,6 @@ We're using the GitHub [releases][changelog] for changelog entries. [server-report]: http://websockets.github.io/ws/autobahn/servers/ [permessage-deflate]: https://tools.ietf.org/html/rfc7692 [changelog]: https://github.com/websockets/ws/releases +[node-zlib-bug]: https://github.com/nodejs/node/issues/8871 +[node-zlib-deflaterawdocs]: https://nodejs.org/api/zlib.html#zlib_zlib_createdeflateraw_options +[ws-server-options]: https://github.com/websockets/ws/blob/master/doc/ws.md#new-websocketserveroptions-callback diff --git a/doc/ws.md b/doc/ws.md index da0f285f4..213af0064 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -71,8 +71,8 @@ provided then that is extension parameters: takeover. - `serverMaxWindowBits` {Number} The value of `windowBits`. - `clientMaxWindowBits` {Number} Request a custom client window size. -- `level` {Number} The value of zlib's `level` param (0-9, default 8). -- `memLevel` {Number} The value of zlib's `memLevel` param (1-9, default 8). +- `zlibOptions` {Object} [Additional options][zlib-options] to pass to zlib + on deflate. - `threshold` {Number} Payloads smaller than this will not be compressed. Defaults to 1024 bytes. - `concurrencyLimit` {Number} The number of concurrent calls to zlib. @@ -420,3 +420,4 @@ The URL of the WebSocket server. Server clients don't have this attribute. [concurrency-limit]: https://github.com/websockets/ws/issues/1202 [permessage-deflate]: https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19 +[zlib-options]: https://nodejs.org/api/zlib.html#zlib_class_options diff --git a/lib/permessage-deflate.js b/lib/permessage-deflate.js index 63182200e..cabab9710 100644 --- a/lib/permessage-deflate.js +++ b/lib/permessage-deflate.js @@ -44,8 +44,8 @@ class PerMessageDeflate { * use of a custom server window size * @param {(Boolean|Number)} options.clientMaxWindowBits Advertise support * for, or request, a custom client window size - * @param {Number} options.level The value of zlib's `level` param - * @param {Number} options.memLevel The value of zlib's `memLevel` param + * @param {Object} options.zlibDeflateOptions Options to pass to zlib on deflate + * @param {Object} options.zlibInflateOptions Options to pass to zlib on inflate * @param {Number} options.threshold Size (in bytes) below which messages * should not be compressed * @param {Number} options.concurrencyLimit The number of concurrent calls to @@ -345,7 +345,13 @@ class PerMessageDeflate { ? zlib.Z_DEFAULT_WINDOWBITS : this.params[key]; - this._inflate = zlib.createInflateRaw({ windowBits }); + this._inflate = zlib.createInflateRaw( + Object.assign( + {}, + this._options.zlibInflateOptions, + { windowBits } + ) + ); this._inflate[kTotalLength] = 0; this._inflate[kBuffers] = []; this._inflate[kOwner] = this; @@ -412,12 +418,17 @@ class PerMessageDeflate { ? zlib.Z_DEFAULT_WINDOWBITS : this.params[key]; - this._deflate = zlib.createDeflateRaw({ - memLevel: this._options.memLevel, - level: this._options.level, - flush: zlib.Z_SYNC_FLUSH, - windowBits - }); + this._deflate = zlib.createDeflateRaw( + Object.assign( + // TODO deprecate memLevel/level and recommend zlibDeflateOptions instead + { + memLevel: this._options.memLevel, + level: this._options.level + }, + this._options.zlibDeflateOptions, + { windowBits } + ) + ); this._deflate[kTotalLength] = 0; this._deflate[kBuffers] = []; diff --git a/test/permessage-deflate.test.js b/test/permessage-deflate.test.js index 929315988..038af2df7 100644 --- a/test/permessage-deflate.test.js +++ b/test/permessage-deflate.test.js @@ -352,8 +352,8 @@ describe('PerMessageDeflate', function () { }); it('honors the `level` option', function (done) { - const lev9 = new PerMessageDeflate({ threshold: 0, level: 9 }); const lev0 = new PerMessageDeflate({ threshold: 0, level: 0 }); + const lev9 = new PerMessageDeflate({ threshold: 0, level: 9 }); const extensionStr = ( 'permessage-deflate; server_no_context_takeover; ' + 'client_no_context_takeover; server_max_window_bits=10; ' + @@ -390,6 +390,71 @@ describe('PerMessageDeflate', function () { }); }); + it('honors the `zlib{Deflate,Inflate}Options` option', function (done) { + const lev0 = new PerMessageDeflate({ + threshold: 0, + zlibDeflateOptions: { + level: 0, + chunkSize: 256 + }, + zlibInflateOptions: { + chunkSize: 2048 + } + }); + const lev9 = new PerMessageDeflate({ + threshold: 0, + zlibDeflateOptions: { + level: 9, + chunkSize: 128 + }, + zlibInflateOptions: { + chunkSize: 1024 + } + }); + + // Note no context takeover so we can get a hold of the raw streams after we do the dance + const extensionStr = ( + 'permessage-deflate; server_max_window_bits=10; ' + + 'client_max_window_bits=11' + ); + const buf = Buffer.from("Some compressible data, it's compressible."); + + lev0.accept(extension.parse(extensionStr)['permessage-deflate']); + lev9.accept(extension.parse(extensionStr)['permessage-deflate']); + + lev0.compress(buf, true, (err, compressed1) => { + if (err) return done(err); + + lev0.decompress(compressed1, true, (err, decompressed1) => { + if (err) return done(err); + + lev9.compress(buf, true, (err, compressed2) => { + if (err) return done(err); + + lev9.decompress(compressed2, true, (err, decompressed2) => { + if (err) return done(err); + // Level 0 compression actually adds a few bytes due to headers. + assert.ok(compressed1.length > buf.length); + // Level 9 should not, of course. + assert.ok(compressed2.length < buf.length); + // Ensure they both decompress back properly. + assert.ok(decompressed1.equals(buf)); + assert.ok(decompressed2.equals(buf)); + + // Assert options were set. + assert.ok(lev0._deflate._level === 0); + assert.ok(lev9._deflate._level === 9); + assert.ok(lev0._deflate._chunkSize === 256); + assert.ok(lev9._deflate._chunkSize === 128); + assert.ok(lev0._inflate._chunkSize === 2048); + assert.ok(lev9._inflate._chunkSize === 1024); + done(); + }); + }); + }); + }); + }); + it("doesn't use contex takeover if not allowed", function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }, true); const extensions = extension.parse( From d8da1b2a183381e180982548f5e6ecbc0da2d1bb Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 22 Feb 2018 08:53:17 +0100 Subject: [PATCH 485/669] [doc] Add missing `zlibInflateOptions` option --- README.md | 29 ++++++++++++++--------------- doc/ws.md | 6 ++++-- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 423ed6a33..8f94ca303 100644 --- a/README.md +++ b/README.md @@ -84,14 +84,13 @@ consumption so we suggest to enable it only if it is really needed. Note that Node.js has a variety of issues with high-performance compression, where increased concurrency, especially on Linux, can lead to [catastrophic memory fragmentation][node-zlib-bug] and slow performance. -If you intend to use `permessage-deflate` in production, it is worthwhile -to set up a test representative of your workload and ensure Node.js/zlib will -handle it with acceptable performance and memory usage. +If you intend to use permessage-deflate in production, it is worthwhile to set +up a test representative of your workload and ensure Node.js/zlib will handle +it with acceptable performance and memory usage. -Tuning of `permessage-deflate` can be done via the options defined below. -You can also use `zlibDeflateOptions` and `zlibInflateOptions`, which is passed -directly into the creation of -[raw deflate/inflate streams][node-zlib-deflaterawdocs]. +Tuning of permessage-deflate can be done via the options defined below. You can +also use `zlibDeflateOptions` and `zlibInflateOptions`, which is passed directly +into the creation of [raw deflate/inflate streams][node-zlib-deflaterawdocs]. See [the docs][ws-server-options] for more options. @@ -101,7 +100,7 @@ const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080, perMessageDeflate: { - zlibDeflateOptions: { // See zlib defaults + zlibDeflateOptions: { // See zlib defaults. chunkSize: 1024, memLevel: 7, level: 3, @@ -110,14 +109,14 @@ const wss = new WebSocket.Server({ chunkSize: 10 * 1024 }, // Other options settable: - clientNoContextTakeover: true, // defaults to negotiated value - serverNoContextTakeover: true, // defaults to negotiated value - clientMaxWindowBits: 10, // defaults to negotiated value - serverMaxWindowBits: 10, // defaults to negotiated value - // Below options specified as default values - concurrencyLimit: 10, // limits zlib concurrency for perf + clientNoContextTakeover: true, // Defaults to negotiated value. + serverNoContextTakeover: true, // Defaults to negotiated value. + clientMaxWindowBits: 10, // Defaults to negotiated value. + serverMaxWindowBits: 10, // Defaults to negotiated value. + // Below options specified as default values. + concurrencyLimit: 10, // Limits zlib concurrency for perf. threshold: 1024, // Size (in bytes) below which messages - // should not be compressed + // should not be compressed. } }); ``` diff --git a/doc/ws.md b/doc/ws.md index 213af0064..464df38b0 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -71,8 +71,10 @@ provided then that is extension parameters: takeover. - `serverMaxWindowBits` {Number} The value of `windowBits`. - `clientMaxWindowBits` {Number} Request a custom client window size. -- `zlibOptions` {Object} [Additional options][zlib-options] to pass to zlib - on deflate. +- `zlibDeflateOptions` {Object} [Additional options][zlib-options] to pass to + zlib on deflate. +- `zlibInflateOptions` {Object} [Additional options][zlib-options] to pass to + zlib on inflate. - `threshold` {Number} Payloads smaller than this will not be compressed. Defaults to 1024 bytes. - `concurrencyLimit` {Number} The number of concurrent calls to zlib. From a70bd65485fe78f7b4fe56d48c3715f90c372367 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 22 Feb 2018 08:54:42 +0100 Subject: [PATCH 486/669] [test] Fix typo --- test/websocket-server.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 06c3e8c35..1e9438803 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -165,7 +165,7 @@ describe('WebSocketServer', function () { }); describe('#close', function () { - it('does not thrown when called twice', function (done) { + it('does not throw when called twice', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { wss.close(); wss.close(); From d390dc5b34c269669d5fa4450cd394d957055296 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 22 Feb 2018 09:00:56 +0100 Subject: [PATCH 487/669] [dist] 4.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6adac8b8b..0623d2437 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "4.0.0", + "version": "4.1.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 70fff53e6e3701f3a07fe166331500dad18e8121 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 15 Feb 2018 07:58:29 +0100 Subject: [PATCH 488/669] [minor] Make `Receiver` inherit from `stream.Writable` --- .travis.yml | 2 +- appveyor.yml | 2 +- lib/constants.js | 12 +- lib/permessage-deflate.js | 20 +- lib/receiver.js | 417 +++++++++------------- lib/websocket.js | 378 +++++++++++++------- test/receiver.test.js | 729 ++++++++++++++++---------------------- 7 files changed, 746 insertions(+), 814 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1d63d7d33..e21953a5a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,6 @@ node_js: - "8" - "6" - "4" - - "4.2.0" + - "4.2.2" after_success: - "npm install coveralls@3 && nyc report --reporter=text-lcov | coveralls" diff --git a/appveyor.yml b/appveyor.yml index 2502b8c8f..394bc1e89 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ environment: - nodejs_version: "8" - nodejs_version: "6" - nodejs_version: "4" - - nodejs_version: "4.2.0" + - nodejs_version: "4.2.2" platform: - x86 - x64 diff --git a/lib/constants.js b/lib/constants.js index 390441462..613a6766c 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -4,7 +4,11 @@ const safeBuffer = require('safe-buffer'); const Buffer = safeBuffer.Buffer; -exports.BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments']; -exports.GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; -exports.EMPTY_BUFFER = Buffer.alloc(0); -exports.NOOP = () => {}; +module.exports = { + BINARY_TYPES: ['nodebuffer', 'arraybuffer', 'fragments'], + GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', + kStatusCode: Symbol('status-code'), + kWebSocket: Symbol('websocket'), + EMPTY_BUFFER: Buffer.alloc(0), + NOOP: () => {} +}; diff --git a/lib/permessage-deflate.js b/lib/permessage-deflate.js index cabab9710..05832f28f 100644 --- a/lib/permessage-deflate.js +++ b/lib/permessage-deflate.js @@ -5,19 +5,20 @@ const Limiter = require('async-limiter'); const zlib = require('zlib'); const bufferUtil = require('./buffer-util'); +const constants = require('./constants'); const Buffer = safeBuffer.Buffer; const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); const EMPTY_BLOCK = Buffer.from([0x00]); +const kPerMessageDeflate = Symbol('permessage-deflate'); const kWriteInProgress = Symbol('write-in-progress'); const kPendingClose = Symbol('pending-close'); const kTotalLength = Symbol('total-length'); const kCallback = Symbol('callback'); const kBuffers = Symbol('buffers'); const kError = Symbol('error'); -const kOwner = Symbol('owner'); // // We limit zlib concurrency, which prevents severe memory fragmentation @@ -346,15 +347,11 @@ class PerMessageDeflate { : this.params[key]; this._inflate = zlib.createInflateRaw( - Object.assign( - {}, - this._options.zlibInflateOptions, - { windowBits } - ) + Object.assign({}, this._options.zlibInflateOptions, { windowBits }) ); + this._inflate[kPerMessageDeflate] = this; this._inflate[kTotalLength] = 0; this._inflate[kBuffers] = []; - this._inflate[kOwner] = this; this._inflate.on('error', inflateOnError); this._inflate.on('data', inflateOnData); } @@ -492,15 +489,15 @@ function inflateOnData (chunk) { this[kTotalLength] += chunk.length; if ( - this[kOwner]._maxPayload < 1 || - this[kTotalLength] <= this[kOwner]._maxPayload + this[kPerMessageDeflate]._maxPayload < 1 || + this[kTotalLength] <= this[kPerMessageDeflate]._maxPayload ) { this[kBuffers].push(chunk); return; } this[kError] = new RangeError('Max payload size exceeded'); - this[kError].closeCode = 1009; + this[kError][constants.kStatusCode] = 1009; this.removeListener('data', inflateOnData); this.reset(); } @@ -516,6 +513,7 @@ function inflateOnError (err) { // There is no need to call `Zlib#close()` as the handle is automatically // closed when an error is emitted. // - this[kOwner]._inflate = null; + this[kPerMessageDeflate]._inflate = null; + err[constants.kStatusCode] = 1007; this[kCallback](err); } diff --git a/lib/receiver.js b/lib/receiver.js index ad1187b83..a66cc10ee 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -1,6 +1,7 @@ 'use strict'; const safeBuffer = require('safe-buffer'); +const stream = require('stream'); const PerMessageDeflate = require('./permessage-deflate'); const bufferUtil = require('./buffer-util'); @@ -18,8 +19,10 @@ const INFLATING = 5; /** * HyBi Receiver implementation. + * + * @extends stream.Writable */ -class Receiver { +class Receiver extends stream.Writable { /** * Creates a Receiver instance. * @@ -28,7 +31,10 @@ class Receiver { * @param {String} binaryType The type for binary data */ constructor (extensions, maxPayload, binaryType) { + super(); + this._binaryType = binaryType || constants.BINARY_TYPES[0]; + this[constants.kWebSocket] = undefined; this._extensions = extensions || {}; this._maxPayload = maxPayload | 0; @@ -37,46 +43,43 @@ class Receiver { this._compressed = false; this._payloadLength = 0; + this._mask = undefined; this._fragmented = 0; this._masked = false; this._fin = false; - this._mask = null; this._opcode = 0; this._totalPayloadLength = 0; this._messageLength = 0; this._fragments = []; - this._cleanupCallback = null; - this._isCleaningUp = false; - this._hadError = false; + this._state = GET_INFO; this._loop = false; + } - this.add = this.add.bind(this); - this.onmessage = null; - this.onclose = null; - this.onerror = null; - this.onping = null; - this.onpong = null; + /** + * Implements `Writable.prototype._write()`. + * + * @param {Buffer} chunk The chunk of data to write + * @param {String} encoding The character encoding of `chunk` + * @param {Function} cb Callback + */ + _write (chunk, encoding, cb) { + if (this._opcode === 0x08) return cb(); - this._state = GET_INFO; + this._bufferedBytes += chunk.length; + this._buffers.push(chunk); + this.startLoop(cb); } /** - * Consumes `n` bytes from the buffered data, calls `cleanup` if necessary. + * Consumes `n` bytes from the buffered data. * * @param {Number} n The number of bytes to consume - * @return {(Buffer|null)} The consumed bytes or `null` if `n` bytes are not - * available + * @return {Buffer} The consumed bytes * @private */ consume (n) { - if (this._bufferedBytes < n) { - this._loop = false; - if (this._isCleaningUp) this.cleanup(this._cleanupCallback); - return null; - } - this._bufferedBytes -= n; if (n === this._buffers[0].length) return this._buffers.shift(); @@ -105,74 +108,66 @@ class Receiver { return dst; } - /** - * Adds new data to the parser. - * - * @param {Buffer} chunk A chunk of data - * @public - */ - add (chunk) { - this._bufferedBytes += chunk.length; - this._buffers.push(chunk); - this.startLoop(); - } - /** * Starts the parsing loop. * + * @param {Function} cb Callback * @private */ - startLoop () { + startLoop (cb) { + var err; this._loop = true; do { switch (this._state) { case GET_INFO: - this.getInfo(); + err = this.getInfo(); break; case GET_PAYLOAD_LENGTH_16: - this.getPayloadLength16(); + err = this.getPayloadLength16(); break; case GET_PAYLOAD_LENGTH_64: - this.getPayloadLength64(); + err = this.getPayloadLength64(); break; case GET_MASK: this.getMask(); break; case GET_DATA: - this.getData(); + err = this.getData(cb); break; default: // `INFLATING` this._loop = false; + return; } } while (this._loop); + + cb(err); } /** * Reads the first two bytes of a frame. * + * @return {(RangeError|undefined)} A possible error * @private */ getInfo () { + if (this._bufferedBytes < 2) { + this._loop = false; + return; + } + const buf = this.consume(2); - if (buf === null) return; if ((buf[0] & 0x30) !== 0x00) { - this.error( - new RangeError('Invalid WebSocket frame: RSV2 and RSV3 must be clear'), - 1002 - ); - return; + this._loop = false; + return error(RangeError, 'RSV2 and RSV3 must be clear', true, 1002); } const compressed = (buf[0] & 0x40) === 0x40; if (compressed && !this._extensions[PerMessageDeflate.extensionName]) { - this.error( - new RangeError('Invalid WebSocket frame: RSV1 must be clear'), - 1002 - ); - return; + this._loop = false; + return error(RangeError, 'RSV1 must be clear', true, 1002); } this._fin = (buf[0] & 0x80) === 0x80; @@ -181,102 +176,85 @@ class Receiver { if (this._opcode === 0x00) { if (compressed) { - this.error( - new RangeError('Invalid WebSocket frame: RSV1 must be clear'), - 1002 - ); - return; + this._loop = false; + return error(RangeError, 'RSV1 must be clear', true, 1002); } if (!this._fragmented) { - this.error( - new RangeError('Invalid WebSocket frame: invalid opcode 0'), - 1002 - ); - return; - } else { - this._opcode = this._fragmented; + this._loop = false; + return error(RangeError, 'invalid opcode 0', true, 1002); } + + this._opcode = this._fragmented; } else if (this._opcode === 0x01 || this._opcode === 0x02) { if (this._fragmented) { - this.error( - new RangeError( - `Invalid WebSocket frame: invalid opcode ${this._opcode}` - ), - 1002 - ); - return; + this._loop = false; + return error(RangeError, `invalid opcode ${this._opcode}`, true, 1002); } this._compressed = compressed; } else if (this._opcode > 0x07 && this._opcode < 0x0b) { if (!this._fin) { - this.error( - new RangeError('Invalid WebSocket frame: FIN must be set'), - 1002 - ); - return; + this._loop = false; + return error(RangeError, 'FIN must be set', true, 1002); } if (compressed) { - this.error( - new RangeError('Invalid WebSocket frame: RSV1 must be clear'), - 1002 - ); - return; + this._loop = false; + return error(RangeError, 'RSV1 must be clear', true, 1002); } if (this._payloadLength > 0x7d) { - this.error( - new RangeError( - `Invalid WebSocket frame: invalid payload length ` + - `${this._payloadLength}` - ), + this._loop = false; + return error( + RangeError, + `invalid payload length ${this._payloadLength}`, + true, 1002 ); - return; } } else { - this.error( - new RangeError( - `Invalid WebSocket frame: invalid opcode ${this._opcode}` - ), - 1002 - ); - return; + this._loop = false; + return error(RangeError, `invalid opcode ${this._opcode}`, true, 1002); } if (!this._fin && !this._fragmented) this._fragmented = this._opcode; - this._masked = (buf[1] & 0x80) === 0x80; if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16; else if (this._payloadLength === 127) this._state = GET_PAYLOAD_LENGTH_64; - else this.haveLength(); + else return this.haveLength(); } /** * Gets extended payload length (7+16). * + * @return {(RangeError|undefined)} A possible error * @private */ getPayloadLength16 () { - const buf = this.consume(2); - if (buf === null) return; + if (this._bufferedBytes < 2) { + this._loop = false; + return; + } - this._payloadLength = buf.readUInt16BE(0, true); - this.haveLength(); + this._payloadLength = this.consume(2).readUInt16BE(0, true); + return this.haveLength(); } /** * Gets extended payload length (7+64). * + * @return {(RangeError|undefined)} A possible error * @private */ getPayloadLength64 () { - const buf = this.consume(8); - if (buf === null) return; + if (this._bufferedBytes < 8) { + this._loop = false; + return; + } + const buf = this.consume(8); const num = buf.readUInt32BE(0, true); // @@ -284,27 +262,32 @@ class Receiver { // if payload length is greater than this number. // if (num > Math.pow(2, 53 - 32) - 1) { - this.error( - new RangeError( - 'Unsupported WebSocket frame: payload length > 2^53 - 1' - ), + this._loop = false; + return error( + RangeError, + 'Unsupported WebSocket frame: payload length > 2^53 - 1', + false, 1009 ); - return; } this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4, true); - this.haveLength(); + return this.haveLength(); } /** * Payload length has been read. * + * @return {(RangeError|undefined)} A possible error * @private */ haveLength () { - if (this._opcode < 0x08 && this.maxPayloadExceeded(this._payloadLength)) { - return; + if (this._payloadLength && this._opcode < 0x08) { + this._totalPayloadLength += this._payloadLength; + if (this._totalPayloadLength > this._maxPayload && this._maxPayload > 0) { + this._loop = false; + return error(RangeError, 'Max payload size exceeded', false, 1009); + } } if (this._masked) this._state = GET_MASK; @@ -317,60 +300,88 @@ class Receiver { * @private */ getMask () { - this._mask = this.consume(4); - if (this._mask === null) return; + if (this._bufferedBytes < 4) { + this._loop = false; + return; + } + this._mask = this.consume(4); this._state = GET_DATA; } /** * Reads data bytes. * + * @param {Function} cb Callback + * @return {(Error|RangeError|undefined)} A possible error * @private */ - getData () { + getData (cb) { var data = constants.EMPTY_BUFFER; if (this._payloadLength) { - data = this.consume(this._payloadLength); - if (data === null) return; + if (this._bufferedBytes < this._payloadLength) { + this._loop = false; + return; + } + data = this.consume(this._payloadLength); if (this._masked) bufferUtil.unmask(data, this._mask); } - if (this._opcode > 0x07) { - this.controlMessage(data); - } else if (this._compressed) { + if (this._opcode > 0x07) return this.controlMessage(data); + + if (this._compressed) { this._state = INFLATING; - this.decompress(data); - } else if (this.pushFragment(data)) { - this.dataMessage(); + this.decompress(data, cb); + return; + } + + if (data.length) { + // + // This message is not compressed so its lenght is the sum of the payload + // length of all fragments. + // + this._messageLength = this._totalPayloadLength; + this._fragments.push(data); } + + return this.dataMessage(); } /** * Decompresses data. * * @param {Buffer} data Compressed data + * @param {Function} cb Callback * @private */ - decompress (data) { + decompress (data, cb) { const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; perMessageDeflate.decompress(data, this._fin, (err, buf) => { - if (err) { - this.error(err, err.closeCode === 1009 ? 1009 : 1007); - return; + if (err) return cb(err); + + if (buf.length) { + this._messageLength += buf.length; + if (this._messageLength > this._maxPayload && this._maxPayload > 0) { + return cb(error(RangeError, 'Max payload size exceeded', false, 1009)); + } + + this._fragments.push(buf); } - if (this.pushFragment(buf)) this.dataMessage(); - this.startLoop(); + const er = this.dataMessage(); + if (er) return cb(er); + + this.startLoop(cb); }); } /** * Handles a data message. * + * @return {(Error|undefined)} A possible error * @private */ dataMessage () { @@ -394,19 +405,16 @@ class Receiver { data = fragments; } - this.onmessage(data); + this.emit('message', data); } else { const buf = toBuffer(fragments, messageLength); if (!validation.isValidUTF8(buf)) { - this.error( - new Error('Invalid WebSocket frame: invalid UTF-8 sequence'), - 1007 - ); - return; + this._loop = false; + return error(Error, 'invalid UTF-8 sequence', true, 1007); } - this.onmessage(buf.toString()); + this.emit('message', buf.toString()); } } @@ -417,149 +425,68 @@ class Receiver { * Handles a control message. * * @param {Buffer} data Data to handle + * @return {(Error|RangeError|undefined)} A possible error * @private */ controlMessage (data) { if (this._opcode === 0x08) { + this._loop = false; + if (data.length === 0) { - this._loop = false; - this.onclose(1005, ''); - this.cleanup(this._cleanupCallback); + this.emit('close', 1005, ''); + this.end(); } else if (data.length === 1) { - this.error( - new RangeError('Invalid WebSocket frame: invalid payload length 1'), - 1002 - ); + return error(RangeError, 'invalid payload length 1', true, 1002); } else { const code = data.readUInt16BE(0, true); if (!validation.isValidStatusCode(code)) { - this.error( - new RangeError( - `Invalid WebSocket frame: invalid status code ${code}` - ), - 1002 - ); - return; + return error(RangeError, `invalid status code ${code}`, true, 1002); } const buf = data.slice(2); if (!validation.isValidUTF8(buf)) { - this.error( - new Error('Invalid WebSocket frame: invalid UTF-8 sequence'), - 1007 - ); - return; + return error(Error, 'invalid UTF-8 sequence', true, 1007); } - this._loop = false; - this.onclose(code, buf.toString()); - this.cleanup(this._cleanupCallback); + this.emit('close', code, buf.toString()); + this.end(); } return; } - if (this._opcode === 0x09) this.onping(data); - else this.onpong(data); + if (this._opcode === 0x09) this.emit('ping', data); + else this.emit('pong', data); this._state = GET_INFO; } - - /** - * Handles an error. - * - * @param {Error} err The error - * @param {Number} code Close code - * @private - */ - error (err, code) { - this._hadError = true; - this._loop = false; - this.onerror(err, code); - this.cleanup(this._cleanupCallback); - } - - /** - * Checks payload size, disconnects socket when it exceeds `maxPayload`. - * - * @param {Number} length Payload length - * @private - */ - maxPayloadExceeded (length) { - if (length === 0 || this._maxPayload < 1) return false; - - const fullLength = this._totalPayloadLength + length; - - if (fullLength <= this._maxPayload) { - this._totalPayloadLength = fullLength; - return false; - } - - this.error(new RangeError('Max payload size exceeded'), 1009); - return true; - } - - /** - * Appends a fragment in the fragments array after checking that the sum of - * fragment lengths does not exceed `maxPayload`. - * - * @param {Buffer} fragment The fragment to add - * @return {Boolean} `true` if `maxPayload` is not exceeded, else `false` - * @private - */ - pushFragment (fragment) { - if (fragment.length === 0) return true; - - const totalLength = this._messageLength + fragment.length; - - if (this._maxPayload < 1 || totalLength <= this._maxPayload) { - this._messageLength = totalLength; - this._fragments.push(fragment); - return true; - } - - this.error(new RangeError('Max payload size exceeded'), 1009); - return false; - } - - /** - * Releases resources used by the receiver. - * - * @param {Function} cb Callback - * @public - */ - cleanup (cb) { - if (this._extensions === null) { - if (cb) cb(); - return; - } - - if (!this._hadError && (this._loop || this._state === INFLATING)) { - this._cleanupCallback = cb; - this._isCleaningUp = true; - return; - } - - this._extensions = null; - this._fragments = null; - this._buffers = null; - this._mask = null; - - this._cleanupCallback = null; - this.onmessage = null; - this.onclose = null; - this.onerror = null; - this.onping = null; - this.onpong = null; - - if (cb) cb(); - } } module.exports = Receiver; +/** + * Builds an error object. + * + * @param {(Error|RangeError)} ErrorCtor The error constructor + * @param {String} message The error message + * @param {Boolean} prefix Specifies whether or not to add a default prefix to + * `message` + * @param {Number} statusCode The status code + * @return {(Error|RangeError)} The error + * @private + */ +function error (ErrorCtor, message, prefix, statusCode) { + const err = new ErrorCtor( + prefix ? `Invalid WebSocket frame: ${message}` : message + ); + + Error.captureStackTrace(err, error); + err[constants.kStatusCode] = statusCode; + return err; +} + /** * Makes a buffer from a list of fragments. * diff --git a/lib/websocket.js b/lib/websocket.js index a5f606f9f..dee00c3cc 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -14,8 +14,10 @@ const Receiver = require('./receiver'); const Sender = require('./sender'); const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']; +const kWebSocket = constants.kWebSocket; const protocolVersions = [8, 13]; const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly. +const NOOP = constants.NOOP; /** * Class representing a WebSocket. @@ -37,12 +39,10 @@ class WebSocket extends EventEmitter { this.protocol = ''; this._binaryType = constants.BINARY_TYPES[0]; - this._finalize = this.finalize.bind(this); this._closeFrameReceived = false; this._closeFrameSent = false; this._closeMessage = ''; this._closeTimer = null; - this._finalized = false; this._closeCode = 1006; this._extensions = {}; this._isServer = true; @@ -119,126 +119,81 @@ class WebSocket extends EventEmitter { * @private */ setSocket (socket, head, maxPayload) { - socket.setTimeout(0); - socket.setNoDelay(); - - socket.on('close', this._finalize); - socket.on('error', this._finalize); - socket.on('end', this._finalize); + const receiver = new Receiver( + this._extensions, + maxPayload, + this._binaryType + ); - this._receiver = new Receiver(this._extensions, maxPayload, this.binaryType); this._sender = new Sender(socket, this._extensions); + this._receiver = receiver; this._socket = socket; - if (head.length > 0) socket.unshift(head); - - socket.on('data', this._receiver.add); - - this._receiver.onmessage = (data) => this.emit('message', data); - this._receiver.onping = (data) => { - this.pong(data, !this._isServer, constants.NOOP); - this.emit('ping', data); - }; - this._receiver.onpong = (data) => this.emit('pong', data); - this._receiver.onclose = (code, reason) => { - // - // Discard any additional data that is received on the socket. - // - this._socket.removeListener('data', this._receiver.add); + receiver[kWebSocket] = this; + socket[kWebSocket] = this; - this._closeFrameReceived = true; - this._closeMessage = reason; - this._closeCode = code; + socket.on('close', socketOnClose); + socket.on('end', socketOnEnd); + socket.on('error', socketOnError); + socket.on('error', NOOP); - if (code === 1005) this.close(); - else this.close(code, reason); - }; - this._receiver.onerror = (error, code) => { - if (this._error) return; + receiver.on('message', receiverOnMessage); + receiver.on('close', receiverOnClose); + receiver.on('error', receiverOnError); + receiver.on('ping', receiverOnPing); + receiver.on('pong', receiverOnPong); - this._closeCode = code; + socket.setTimeout(0); + socket.setNoDelay(); - if (!this._finalized) this.finalize(error); - else this.emit('error', error); - }; + if (head.length > 0) socket.unshift(head); + socket.pipe(receiver); this.readyState = WebSocket.OPEN; this.emit('open'); } /** - * Clean up internal resources and emit the `'close'` event. + * Emit the `'close'` event. * - * @param {(Boolean|Error)} error Indicates whether or not an error occurred * @private */ - finalize (error) { - if (this._finalized) return; - - this.readyState = WebSocket.CLOSING; - this._finalized = true; + emitClose () { + this.readyState = WebSocket.CLOSED; if (!this._socket) { - // - // `error` is always an `Error` instance in this case. - // - this.emit('error', error); - this.readyState = WebSocket.CLOSED; this.emit('close', this._closeCode, this._closeMessage); return; } - clearTimeout(this._closeTimer); - - this._socket.removeListener('data', this._receiver.add); - this._socket.removeListener('close', this._finalize); - this._socket.removeListener('error', this._finalize); - this._socket.removeListener('end', this._finalize); - this._socket.on('error', constants.NOOP); + const err = this._error; - if (error) { - if (error !== true) this._error = error; - this._socket.destroy(); - } else { - this._socket.end(); + if (err) { + this._error = null; + this.emit('error', err); } - this._receiver.cleanup(() => { - const err = this._error; - - if (err) { - this._error = null; - this.emit('error', err); - } - - this.readyState = WebSocket.CLOSED; - - if (this._extensions[PerMessageDeflate.extensionName]) { - this._extensions[PerMessageDeflate.extensionName].cleanup(); - } + if (this._extensions[PerMessageDeflate.extensionName]) { + this._extensions[PerMessageDeflate.extensionName].cleanup(); + } - this.emit('close', this._closeCode, this._closeMessage); - }); + this.emit('close', this._closeCode, this._closeMessage); } /** * Start a closing handshake. * - * +----------+ +-----------+ +----------+ - * + - - -|ws.close()|---->|close frame|-->|ws.close()|- - - - - * +----------+ +-----------+ +----------+ | - * | +----------+ +-----------+ | - * |ws.close()|<----|close frame|<--------+ | - * +----------+ +-----------+ | - * CLOSING | +---+ | CLOSING - * | +---|fin|<------------+ - * | | | +---+ | - * | | +---+ +-------------+ - * | +----------+-->|fin|----->|ws.finalize()| - - + - * | +---+ +-------------+ - * | +-------------+ | - * - - -|ws.finalize()|<--+ - * +-------------+ + * +----------+ +-----------+ +----------+ + * - - -|ws.close()|-->|close frame|-->|ws.close()|- - - + * | +----------+ +-----------+ +----------+ | + * +----------+ +-----------+ | + * CLOSING |ws.close()|<--|close frame|<--+-----+ CLOSING + * +----------+ +-----------+ | + * | | | +---+ | + * +------------------------+-->|fin| - - - - + * | +---+ | +---+ + * - - - - -|fin|<---------------------+ + * +---+ * * @param {Number} code Status code explaining why the connection is closing * @param {String} data A string explaining why the connection is closing @@ -247,11 +202,8 @@ class WebSocket extends EventEmitter { close (code, data) { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { - this._req.abort(); - this.finalize( - new Error('WebSocket was closed before the connection was established') - ); - return; + const msg = 'WebSocket was closed before the connection was established'; + return abortHandshake(this, this._req, msg); } if (this.readyState === WebSocket.CLOSING) { @@ -269,14 +221,17 @@ class WebSocket extends EventEmitter { this._closeFrameSent = true; - if (!this._finalized) { + if (this._socket.writable) { if (this._closeFrameReceived) this._socket.end(); // - // Ensure that the connection is cleaned up even when the closing - // handshake fails. + // Ensure that the connection is closed even if the closing handshake + // fails. // - this._closeTimer = setTimeout(this._finalize, closeTimeout, true); + this._closeTimer = setTimeout( + this._socket.destroy.bind(this._socket), + closeTimeout + ); } }); } @@ -397,14 +352,15 @@ class WebSocket extends EventEmitter { terminate () { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { - this._req.abort(); - this.finalize( - new Error('WebSocket was closed before the connection was established') - ); - return; + const msg = 'WebSocket was closed before the connection was established'; + return abortHandshake(this, this._req, msg); } - this.finalize(true); + if (this._socket) { + this.readyState = WebSocket.CLOSING; + this._socket.removeListener('error', socketOnError); + this._socket.destroy(); + } } } @@ -619,30 +575,31 @@ function initAsClient (address, protocols, options) { if (agent) requestOptions.agent = agent; - this._req = httpObj.get(requestOptions); + var req = this._req = httpObj.get(requestOptions); if (options.handshakeTimeout) { - this._req.setTimeout(options.handshakeTimeout, () => { - this._req.abort(); - this.finalize(new Error('Opening handshake has timed out')); - }); + req.setTimeout( + options.handshakeTimeout, + abortHandshake.bind(null, this, req, 'Opening handshake has timed out') + ); } - this._req.on('error', (error) => { + req.on('error', (err) => { if (this._req.aborted) return; - this._req = null; - this.finalize(error); + req = this._req = null; + this.readyState = WebSocket.CLOSING; + this.emit('error', err); + this.emitClose(); }); - this._req.on('response', (res) => { - if (!this.emit('unexpected-response', this._req, res)) { - this._req.abort(); - this.finalize(new Error(`Unexpected server response: ${res.statusCode}`)); - } + req.on('response', (res) => { + if (this.emit('unexpected-response', req, res)) return; + + abortHandshake(this, req, `Unexpected server response: ${res.statusCode}`); }); - this._req.on('upgrade', (res, socket, head) => { + req.on('upgrade', (res, socket, head) => { this.emit('upgrade', res); // @@ -651,15 +608,15 @@ function initAsClient (address, protocols, options) { // if (this.readyState !== WebSocket.CONNECTING) return; - this._req = null; + req = this._req = null; const digest = crypto.createHash('sha1') .update(key + constants.GUID, 'binary') .digest('base64'); if (res.headers['sec-websocket-accept'] !== digest) { - socket.destroy(); - return this.finalize(new Error('Invalid Sec-WebSocket-Accept header')); + abortHandshake(this, socket, 'Invalid Sec-WebSocket-Accept header'); + return; } const serverProt = res.headers['sec-websocket-protocol']; @@ -675,8 +632,8 @@ function initAsClient (address, protocols, options) { } if (protError) { - socket.destroy(); - return this.finalize(new Error(protError)); + abortHandshake(this, socket, protError); + return; } if (serverProt) this.protocol = serverProt; @@ -694,8 +651,7 @@ function initAsClient (address, protocols, options) { this._extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } } catch (err) { - socket.destroy(); - this.finalize(new Error('Invalid Sec-WebSocket-Extensions header')); + abortHandshake(this, socket, 'Invalid Sec-WebSocket-Extensions header'); return; } } @@ -703,3 +659,173 @@ function initAsClient (address, protocols, options) { this.setSocket(socket, head, 0); }); } + +/** + * Abort the handshake and emit an error. + * + * @param {WebSocket} websocket The WebSocket instance + * @param {(http.ClientRequest|net.Socket)} stream The request to abort or the + * socket to destroy + * @param {String} message The error message + * @private + */ +function abortHandshake (websocket, stream, message) { + websocket.readyState = WebSocket.CLOSING; + + const err = new Error(message); + Error.captureStackTrace(err, abortHandshake); + + if (stream.setHeader) { + stream.abort(); + stream.once('abort', websocket.emitClose.bind(websocket)); + websocket.emit('error', err); + } else { + stream.destroy(err); + stream.once('error', websocket.emit.bind(websocket, 'error')); + stream.once('close', websocket.emitClose.bind(websocket)); + } +} + +/** + * The listener of the `Receiver` `'close'` event. + * + * @param {Number} code The status code + * @param {String} reason The reason for closing + * @private + */ +function receiverOnClose (code, reason) { + const websocket = this[kWebSocket]; + + websocket._socket.unpipe(websocket._receiver); + websocket._socket.resume(); + + websocket._closeFrameReceived = true; + websocket._closeMessage = reason; + websocket._closeCode = code; + + if (code === 1005) websocket.close(); + else websocket.close(code, reason); +} + +/** + * The listener of the `Receiver` `'error'` event. + * + * @param {(RangeError|Error)} err The emitted error + * @private + */ +function receiverOnError (err) { + const websocket = this[kWebSocket]; + + if (websocket._error) return; + + websocket.readyState = WebSocket.CLOSING; + websocket._socket.removeListener('error', socketOnError); + websocket._closeCode = err[constants.kStatusCode]; + websocket._closeMessage = ''; + websocket.emit('error', err); + websocket._socket.destroy(); +} + +/** + * The listener of the `Receiver` `'message'` event. + * + * @param {(String|Buffer|ArrayBuffer|Buffer[])} data The message + * @private + */ +function receiverOnMessage (data) { + this[kWebSocket].emit('message', data); +} + +/** + * The listener of the `Receiver` `'ping'` event. + * + * @param {Buffer} data The data included in the ping frame + * @private + */ +function receiverOnPing (data) { + const websocket = this[kWebSocket]; + + websocket.pong(data, !websocket._isServer, NOOP); + websocket.emit('ping', data); +} + +/** + * The listener of the `Receiver` `'pong'` event. + * + * @param {Buffer} data The data included in the pong frame + * @private + */ +function receiverOnPong (data) { + this[kWebSocket].emit('pong', data); +} + +/** + * The listener of the `net.Socket` `'error'` event. + * + * @param {Error} err The emitted error + * @private + */ +function socketOnError (err) { + const websocket = this[kWebSocket]; + + websocket.readyState = WebSocket.CLOSING; + this.removeListener('error', socketOnError); + + // + // There might be valid buffered data in the socket waiting to be read so we + // can't re-emit this error immediately. + // + websocket._error = err; +} + +/** + * The listener of the `net.Socket` `'end'` event. + * + * @private + */ +function socketOnEnd () { + this[kWebSocket].readyState = WebSocket.CLOSING; + this.removeListener('error', socketOnError); + this.end(); +} + +/** + * The listener of the `net.Socket` `'close'` event. + * + * @private + */ +function socketOnClose () { + const websocket = this[kWebSocket]; + + this.removeListener('error', socketOnError); + this.removeListener('end', socketOnEnd); + this[kWebSocket] = undefined; + + websocket.readyState = WebSocket.CLOSING; + + // + // The close frame might not have been received or the `'end'` event emitted, + // for example, if the socket was destroyed due to an error. Ensure that the + // `receiver` stream is closed after writing any remaining buffered data to + // it. + // + websocket._socket.read(); + websocket._receiver.end(); + + clearTimeout(websocket._closeTimer); + + if ( + websocket._receiver._writableState.finished || + websocket._receiver._writableState.errorEmitted + ) { + websocket.emitClose(); + } else { + const emitClose = () => { + websocket._receiver.removeAllListeners(); + websocket.emitClose(); + }; + + websocket._receiver.on('error', emitClose); + websocket._receiver.on('finish', emitClose); + } +} diff --git a/test/receiver.test.js b/test/receiver.test.js index 4335a12c2..f235719ea 100644 --- a/test/receiver.test.js +++ b/test/receiver.test.js @@ -5,48 +5,52 @@ const assert = require('assert'); const crypto = require('crypto'); const PerMessageDeflate = require('../lib/permessage-deflate'); +const constants = require('../lib/constants'); const Receiver = require('../lib/receiver'); const Sender = require('../lib/sender'); +const kStatusCode = constants.kStatusCode; const Buffer = safeBuffer.Buffer; describe('Receiver', function () { - it('can parse unmasked text message', function (done) { - const p = new Receiver(); + it('parses an unmasked text message', function (done) { + const receiver = new Receiver(); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, 'Hello'); done(); - }; + }); - p.add(Buffer.from('810548656c6c6f', 'hex')); + receiver.write(Buffer.from('810548656c6c6f', 'hex')); }); - it('can parse close message', function (done) { - const p = new Receiver(); + it('parses a close message', function (done) { + const receiver = new Receiver(); - p.onclose = function (code, data) { + receiver.on('close', (code, data) => { assert.strictEqual(code, 1005); assert.strictEqual(data, ''); done(); - }; + }); - p.add(Buffer.from('8800', 'hex')); + receiver.write(Buffer.from('8800', 'hex')); }); - it('can parse masked text message', function (done) { - const p = new Receiver(); + it('parses a masked text message', function (done) { + const receiver = new Receiver(); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, '5:::{"name":"echo"}'); done(); - }; + }); - p.add(Buffer.from('81933483a86801b992524fa1c60959e68a5216e6cb005ba1d5', 'hex')); + receiver.write( + Buffer.from('81933483a86801b992524fa1c60959e68a5216e6cb005ba1d5', 'hex') + ); }); - it('can parse a masked text message longer than 125 B', function (done) { - const p = new Receiver(); + it('parses a masked text message longer than 125 B', function (done) { + const receiver = new Receiver(); const msg = 'A'.repeat(200); const list = Sender.frame(Buffer.from(msg), { @@ -59,17 +63,17 @@ describe('Receiver', function () { const frame = Buffer.concat(list); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, msg); done(); - }; + }); - p.add(frame.slice(0, 2)); - setImmediate(() => p.add(frame.slice(2))); + receiver.write(frame.slice(0, 2)); + setImmediate(() => receiver.write(frame.slice(2))); }); - it('can parse a really long masked text message', function (done) { - const p = new Receiver(); + it('parses a really long masked text message', function (done) { + const receiver = new Receiver(); const msg = 'A'.repeat(64 * 1024); const list = Sender.frame(Buffer.from(msg), { @@ -82,16 +86,16 @@ describe('Receiver', function () { const frame = Buffer.concat(list); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, msg); done(); - }; + }); - p.add(frame); + receiver.write(frame); }); - it('can parse a fragmented masked text message of 300 B', function (done) { - const p = new Receiver(); + it('parses a 300 B fragmented masked text message', function (done) { + const receiver = new Receiver(); const msg = 'A'.repeat(300); const fragment1 = msg.substr(0, 150); @@ -108,17 +112,17 @@ describe('Receiver', function () { Object.assign({ fin: true, opcode: 0x00 }, options) )); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, msg); done(); - }; + }); - p.add(frame1); - p.add(frame2); + receiver.write(frame1); + receiver.write(frame2); }); - it('can parse a ping message', function (done) { - const p = new Receiver(); + it('parses a ping message', function (done) { + const receiver = new Receiver(); const msg = 'Hello'; const list = Sender.frame(Buffer.from(msg), { @@ -131,27 +135,27 @@ describe('Receiver', function () { const frame = Buffer.concat(list); - p.onping = function (data) { + receiver.on('ping', (data) => { assert.strictEqual(data.toString(), msg); done(); - }; + }); - p.add(frame); + receiver.write(frame); }); - it('can parse a ping with no data', function (done) { - const p = new Receiver(); + it('parses a ping message with no data', function (done) { + const receiver = new Receiver(); - p.onping = function (data) { + receiver.on('ping', (data) => { assert.ok(data.equals(Buffer.alloc(0))); done(); - }; + }); - p.add(Buffer.from('8900', 'hex')); + receiver.write(Buffer.from('8900', 'hex')); }); - it('can parse a fragmented masked text message of 300 B with a ping in the middle (1/2)', function (done) { - const p = new Receiver(); + it('parses a 300 B fragmented masked text message with a ping in the middle (1/2)', function (done) { + const receiver = new Receiver(); const msg = 'A'.repeat(300); const pingMessage = 'Hello'; @@ -175,23 +179,23 @@ describe('Receiver', function () { let gotPing = false; - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, msg); assert.ok(gotPing); done(); - }; - p.onping = function (data) { + }); + receiver.on('ping', (data) => { gotPing = true; assert.strictEqual(data.toString(), pingMessage); - }; + }); - p.add(frame1); - p.add(frame2); - p.add(frame3); + receiver.write(frame1); + receiver.write(frame2); + receiver.write(frame3); }); - it('can parse a fragmented masked text message of 300 B with a ping in the middle (2/2)', function (done) { - const p = new Receiver(); + it('parses a 300 B fragmented masked text message with a ping in the middle (2/2)', function (done) { + const receiver = new Receiver(); const msg = 'A'.repeat(300); const pingMessage = 'Hello'; @@ -225,23 +229,23 @@ describe('Receiver', function () { let gotPing = false; - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, msg); assert.ok(gotPing); done(); - }; - p.onping = function (data) { + }); + receiver.on('ping', (data) => { gotPing = true; assert.strictEqual(data.toString(), pingMessage); - }; + }); for (let i = 0; i < chunks.length; ++i) { - p.add(chunks[i]); + receiver.write(chunks[i]); } }); - it('can parse a 100 B long masked binary message', function (done) { - const p = new Receiver(); + it('parses a 100 B masked binary message', function (done) { + const receiver = new Receiver(); const msg = crypto.randomBytes(100); const list = Sender.frame(msg, { @@ -254,16 +258,16 @@ describe('Receiver', function () { const frame = Buffer.concat(list); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.ok(data.equals(msg)); done(); - }; + }); - p.add(frame); + receiver.write(frame); }); - it('can parse a 256 B long masked binary message', function (done) { - const p = new Receiver(); + it('parses a 256 B masked binary message', function (done) { + const receiver = new Receiver(); const msg = crypto.randomBytes(256); const list = Sender.frame(msg, { @@ -276,16 +280,16 @@ describe('Receiver', function () { const frame = Buffer.concat(list); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.ok(data.equals(msg)); done(); - }; + }); - p.add(frame); + receiver.write(frame); }); - it('can parse a 200 KiB long masked binary message', function (done) { - const p = new Receiver(); + it('parses a 200 KiB masked binary message', function (done) { + const receiver = new Receiver(); const msg = crypto.randomBytes(200 * 1024); const list = Sender.frame(msg, { @@ -298,16 +302,16 @@ describe('Receiver', function () { const frame = Buffer.concat(list); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.ok(data.equals(msg)); done(); - }; + }); - p.add(frame); + receiver.write(frame); }); - it('can parse a 200 KiB long unmasked binary message', function (done) { - const p = new Receiver(); + it('parses a 200 KiB unmasked binary message', function (done) { + const receiver = new Receiver(); const msg = crypto.randomBytes(200 * 1024); const list = Sender.frame(msg, { @@ -320,63 +324,63 @@ describe('Receiver', function () { const frame = Buffer.concat(list); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.ok(data.equals(msg)); done(); - }; + }); - p.add(frame); + receiver.write(frame); }); - it('can parse compressed message', function (done) { + it('parses a compressed message', function (done) { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); const buf = Buffer.from('Hello'); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, 'Hello'); done(); - }; + }); - perMessageDeflate.compress(buf, true, function (err, compressed) { + perMessageDeflate.compress(buf, true, (err, data) => { if (err) return done(err); - p.add(Buffer.from([0xc1, compressed.length])); - p.add(compressed); + receiver.write(Buffer.from([0xc1, data.length])); + receiver.write(data); }); }); - it('can parse compressed fragments', function (done) { + it('parses a compressed and fragmented message', function (done) { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); const buf1 = Buffer.from('foo'); const buf2 = Buffer.from('bar'); - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, 'foobar'); done(); - }; + }); - perMessageDeflate.compress(buf1, false, function (err, compressed1) { + perMessageDeflate.compress(buf1, false, function (err, fragment1) { if (err) return done(err); - p.add(Buffer.from([0x41, compressed1.length])); - p.add(compressed1); + receiver.write(Buffer.from([0x41, fragment1.length])); + receiver.write(fragment1); - perMessageDeflate.compress(buf2, true, function (err, compressed2) { + perMessageDeflate.compress(buf2, true, function (err, fragment2) { if (err) return done(err); - p.add(Buffer.from([0x80, compressed2.length])); - p.add(compressed2); + receiver.write(Buffer.from([0x80, fragment2.length])); + receiver.write(fragment2); }); }); }); - it('can parse a buffer with thousands of frames', function (done) { + it('parses a buffer with thousands of frames', function (done) { const buf = Buffer.allocUnsafe(40000); for (let i = 0; i < buf.length; i += 2) { @@ -384,335 +388,357 @@ describe('Receiver', function () { buf[i + 1] = 0x00; } - const p = new Receiver(); + const receiver = new Receiver(); let counter = 0; - p.onmessage = function (data) { + receiver.on('message', (data) => { assert.strictEqual(data, ''); if (++counter === 20000) done(); - }; + }); - p.add(buf); + receiver.write(buf); }); - it('resets `totalPayloadLength` only on final frame (unfragmented)', function () { - const p = new Receiver({}, 10); - let message; + it('resets `totalPayloadLength` only on final frame (unfragmented)', function (done) { + const receiver = new Receiver({}, 10); - p.onmessage = function (msg) { - message = msg; - }; + receiver.on('message', (data) => { + assert.strictEqual(receiver._totalPayloadLength, 0); + assert.strictEqual(data, 'Hello'); + done(); + }); - assert.strictEqual(p._totalPayloadLength, 0); - p.add(Buffer.from('810548656c6c6f', 'hex')); - assert.strictEqual(p._totalPayloadLength, 0); - assert.strictEqual(message, 'Hello'); + assert.strictEqual(receiver._totalPayloadLength, 0); + receiver.write(Buffer.from('810548656c6c6f', 'hex')); }); - it('resets `totalPayloadLength` only on final frame (fragmented)', function () { - const p = new Receiver({}, 10); - let message; + it('resets `totalPayloadLength` only on final frame (fragmented)', function (done) { + const receiver = new Receiver({}, 10); - p.onmessage = function (msg) { - message = msg; - }; + receiver.on('message', (data) => { + assert.strictEqual(receiver._totalPayloadLength, 0); + assert.strictEqual(data, 'Hello'); + done(); + }); - assert.strictEqual(p._totalPayloadLength, 0); - p.add(Buffer.from('01024865', 'hex')); - assert.strictEqual(p._totalPayloadLength, 2); - p.add(Buffer.from('80036c6c6f', 'hex')); - assert.strictEqual(p._totalPayloadLength, 0); - assert.strictEqual(message, 'Hello'); + assert.strictEqual(receiver._totalPayloadLength, 0); + receiver.write(Buffer.from('01024865', 'hex')); + assert.strictEqual(receiver._totalPayloadLength, 2); + receiver.write(Buffer.from('80036c6c6f', 'hex')); }); - it('resets `totalPayloadLength` only on final frame (fragmented + ping)', function () { - const p = new Receiver({}, 10); - const data = []; + it('resets `totalPayloadLength` only on final frame (fragmented + ping)', function (done) { + const receiver = new Receiver({}, 10); + let data; - p.onmessage = p.onping = function (buf) { - data.push(buf.toString()); - }; + receiver.on('ping', (buf) => { + assert.strictEqual(receiver._totalPayloadLength, 2); + data = buf.toString(); + }); + receiver.on('message', (buf) => { + assert.strictEqual(receiver._totalPayloadLength, 0); + assert.strictEqual(data, ''); + assert.strictEqual(buf.toString(), 'Hello'); + done(); + }); + + assert.strictEqual(receiver._totalPayloadLength, 0); + receiver.write(Buffer.from('02024865', 'hex')); + receiver.write(Buffer.from('8900', 'hex')); + receiver.write(Buffer.from('80036c6c6f', 'hex')); + }); + + it('ignores any data after a close frame', function (done) { + const perMessageDeflate = new PerMessageDeflate(); + perMessageDeflate.accept([{}]); + + const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const results = []; + const push = results.push.bind(results); - assert.strictEqual(p._totalPayloadLength, 0); - p.add(Buffer.from('02024865', 'hex')); - assert.strictEqual(p._totalPayloadLength, 2); - p.add(Buffer.from('8900', 'hex')); - assert.strictEqual(p._totalPayloadLength, 2); - p.add(Buffer.from('80036c6c6f', 'hex')); - assert.strictEqual(p._totalPayloadLength, 0); - assert.deepStrictEqual(data, ['', 'Hello']); + receiver.on('close', push).on('message', push); + receiver.on('finish', () => { + assert.deepStrictEqual(results, ['', 1005, '']); + done(); + }); + + receiver.write(Buffer.from([0xc1, 0x01, 0x00])); + receiver.write(Buffer.from([0x88, 0x00])); + receiver.write(Buffer.from([0x81, 0x00])); }); - it('raises an error when RSV1 is on and permessage-deflate is disabled', function (done) { - const p = new Receiver(); + it('emits an error if RSV1 is on and permessage-deflate is disabled', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: RSV1 must be clear' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0xc2, 0x80, 0x00, 0x00, 0x00, 0x00])); + receiver.write(Buffer.from([0xc2, 0x80, 0x00, 0x00, 0x00, 0x00])); }); - it('raises an error when RSV1 is on and opcode is 0', function (done) { + it('emits an error if RSV1 is on and opcode is 0', function (done) { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: RSV1 must be clear' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x40, 0x00])); + receiver.write(Buffer.from([0x40, 0x00])); }); - it('raises an error when RSV2 is on', function (done) { - const p = new Receiver(); + it('emits an error if RSV2 is on', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: RSV2 and RSV3 must be clear' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0xa2, 0x00])); + receiver.write(Buffer.from([0xa2, 0x00])); }); - it('raises an error when RSV3 is on', function (done) { - const p = new Receiver(); + it('emits an error if RSV3 is on', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: RSV2 and RSV3 must be clear' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x92, 0x00])); + receiver.write(Buffer.from([0x92, 0x00])); }); - it('raises an error if the first frame in a fragmented message has opcode 0', function (done) { - const p = new Receiver(); + it('emits an error if the first frame in a fragmented message has opcode 0', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid opcode 0' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x00, 0x00])); + receiver.write(Buffer.from([0x00, 0x00])); }); - it('raises an error if a frame has opcode 1 in the middle of a fragmented message', function (done) { - const p = new Receiver(); + it('emits an error if a frame has opcode 1 in the middle of a fragmented message', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid opcode 1' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x01, 0x00])); - p.add(Buffer.from([0x01, 0x00])); + receiver.write(Buffer.from([0x01, 0x00])); + receiver.write(Buffer.from([0x01, 0x00])); }); - it('raises an error if a frame has opcode 2 in the middle of a fragmented message', function (done) { - const p = new Receiver(); + it('emits an error if a frame has opcode 2 in the middle of a fragmented message', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid opcode 2' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x01, 0x00])); - p.add(Buffer.from([0x02, 0x00])); + receiver.write(Buffer.from([0x01, 0x00])); + receiver.write(Buffer.from([0x02, 0x00])); }); - it('raises an error when a control frame has the FIN bit off', function (done) { - const p = new Receiver(); + it('emits an error if a control frame has the FIN bit off', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: FIN must be set' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x09, 0x00])); + receiver.write(Buffer.from([0x09, 0x00])); }); - it('raises an error when a control frame has the RSV1 bit on', function (done) { + it('emits an error if a control frame has the RSV1 bit on', function (done) { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: RSV1 must be clear' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0xc9, 0x00])); + receiver.write(Buffer.from([0xc9, 0x00])); }); - it('raises an error when a control frame has the FIN bit off', function (done) { - const p = new Receiver(); + it('emits an error if a control frame has the FIN bit off', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: FIN must be set' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x09, 0x00])); + receiver.write(Buffer.from([0x09, 0x00])); }); - it('raises an error when a control frame has a payload bigger than 125 B', function (done) { - const p = new Receiver(); + it('emits an error if a control frame has a payload bigger than 125 B', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid payload length 126' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x89, 0x7e])); + receiver.write(Buffer.from([0x89, 0x7e])); }); - it('raises an error when a data frame has a payload bigger than 2^53 - 1 B', function (done) { - const p = new Receiver(); + it('emits an error if a data frame has a payload bigger than 2^53 - 1 B', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Unsupported WebSocket frame: payload length > 2^53 - 1' ); - assert.strictEqual(code, 1009); + assert.strictEqual(err[kStatusCode], 1009); done(); - }; + }); - p.add(Buffer.from([0x82, 0x7f])); - setImmediate(() => p.add(Buffer.from([ + receiver.write(Buffer.from([0x82, 0x7f])); + setImmediate(() => receiver.write(Buffer.from([ 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]))); }); - it('raises an error if a text frame contains invalid UTF-8 data', function (done) { - const p = new Receiver(); + it('emits an error if a text frame contains invalid UTF-8 data', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid UTF-8 sequence' ); - assert.strictEqual(code, 1007); + assert.strictEqual(err[kStatusCode], 1007); done(); - }; + }); - p.add(Buffer.from([0x81, 0x04, 0xce, 0xba, 0xe1, 0xbd])); + receiver.write(Buffer.from([0x81, 0x04, 0xce, 0xba, 0xe1, 0xbd])); }); - it('raises an error if a close frame has a payload of 1 B', function (done) { - const p = new Receiver(); + it('emits an error if a close frame has a payload of 1 B', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid payload length 1' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x88, 0x01, 0x00])); + receiver.write(Buffer.from([0x88, 0x01, 0x00])); }); - it('raises an error if a close frame contains an invalid close code', function (done) { - const p = new Receiver(); + it('emits an error if a close frame contains an invalid close code', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid status code 0' ); - assert.strictEqual(code, 1002); + assert.strictEqual(err[kStatusCode], 1002); done(); - }; + }); - p.add(Buffer.from([0x88, 0x02, 0x00, 0x00])); + receiver.write(Buffer.from([0x88, 0x02, 0x00, 0x00])); }); - it('raises an error if a close frame contains invalid UTF-8 data', function (done) { - const p = new Receiver(); + it('emits an error if a close frame contains invalid UTF-8 data', function (done) { + const receiver = new Receiver(); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid UTF-8 sequence' ); - assert.strictEqual(code, 1007); + assert.strictEqual(err[kStatusCode], 1007); done(); - }; + }); - p.add(Buffer.from([0x88, 0x06, 0x03, 0xef, 0xce, 0xba, 0xe1, 0xbd])); + receiver.write( + Buffer.from([0x88, 0x06, 0x03, 0xef, 0xce, 0xba, 0xe1, 0xbd]) + ); }); - it('raises an error on a 200 KiB long masked binary message when `maxPayload` is 20 KiB', function (done) { - const p = new Receiver({}, 20 * 1024); + it('emits an error if a frame payload length is bigger than `maxPayload`', function (done) { + const receiver = new Receiver({}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); const list = Sender.frame(msg, { @@ -725,221 +751,73 @@ describe('Receiver', function () { const frame = Buffer.concat(list); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual(err.message, 'Max payload size exceeded'); - assert.strictEqual(code, 1009); + assert.strictEqual(err[kStatusCode], 1009); done(); - }; - - p.add(frame); - }); - - it('raises an error on a 200 KiB long unmasked binary message when `maxPayload` is 20 KiB', function (done) { - const p = new Receiver({}, 20 * 1024); - const msg = crypto.randomBytes(200 * 1024); - - const list = Sender.frame(msg, { - fin: true, - rsv1: false, - opcode: 0x02, - mask: false, - readOnly: true }); - const frame = Buffer.concat(list); - - p.onerror = function (err, code) { - assert.ok(err instanceof RangeError); - assert.strictEqual(err.message, 'Max payload size exceeded'); - assert.strictEqual(code, 1009); - done(); - }; - - p.add(frame); + receiver.write(frame); }); - it('raises an error on a compressed message that exceeds `maxPayload`', function (done) { + it('emits an error if the message length exceeds `maxPayload`', function (done) { const perMessageDeflate = new PerMessageDeflate({}, false, 25); perMessageDeflate.accept([{}]); - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 25); + const receiver = new Receiver({ + 'permessage-deflate': perMessageDeflate + }, 25); const buf = Buffer.from('A'.repeat(50)); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual(err.message, 'Max payload size exceeded'); - assert.strictEqual(code, 1009); + assert.strictEqual(err[kStatusCode], 1009); done(); - }; + }); perMessageDeflate.compress(buf, true, function (err, data) { if (err) return done(err); - p.add(Buffer.from([0xc1, data.length])); - p.add(data); + receiver.write(Buffer.from([0xc1, data.length])); + receiver.write(data); }); }); - it('raises an error if the sum of fragment lengths exceeds `maxPayload`', function (done) { + it('emits an error if the sum of fragment lengths exceeds `maxPayload`', function (done) { const perMessageDeflate = new PerMessageDeflate({}, false, 25); perMessageDeflate.accept([{}]); - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }, 25); + const receiver = new Receiver({ + 'permessage-deflate': perMessageDeflate + }, 25); const buf = Buffer.from('A'.repeat(15)); - p.onerror = function (err, code) { + receiver.on('error', (err) => { assert.ok(err instanceof RangeError); assert.strictEqual(err.message, 'Max payload size exceeded'); - assert.strictEqual(code, 1009); + assert.strictEqual(err[kStatusCode], 1009); done(); - }; + }); perMessageDeflate.compress(buf, false, function (err, fragment1) { if (err) return done(err); - p.add(Buffer.from([0x41, fragment1.length])); - p.add(fragment1); + receiver.write(Buffer.from([0x41, fragment1.length])); + receiver.write(fragment1); perMessageDeflate.compress(buf, true, function (err, fragment2) { if (err) return done(err); - p.add(Buffer.from([0x80, fragment2.length])); - p.add(fragment2); - }); - }); - }); - - it('consumes all data before calling `cleanup` callback (1/4)', function (done) { - const perMessageDeflate = new PerMessageDeflate(); - perMessageDeflate.accept([{}]); - - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); - const buf = Buffer.from('Hello'); - const results = []; - - p.onmessage = (message) => results.push(message); - - perMessageDeflate.compress(buf, true, (err, data) => { - if (err) return done(err); - - const frame = Buffer.concat([Buffer.from([0xc1, data.length]), data]); - - p.add(frame); - p.add(frame); - - assert.strictEqual(p._state, 5); - assert.strictEqual(p._bufferedBytes, frame.length); - - p.cleanup(() => { - assert.deepStrictEqual(results, ['Hello', 'Hello']); - assert.strictEqual(p.onmessage, null); - done(); - }); - }); - }); - - it('consumes all data before calling `cleanup` callback (2/4)', function (done) { - const perMessageDeflate = new PerMessageDeflate(); - perMessageDeflate.accept([{}]); - - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); - const buf = Buffer.from('Hello'); - const results = []; - - p.onclose = (code, reason) => results.push(code, reason); - p.onmessage = (message) => results.push(message); - - perMessageDeflate.compress(buf, true, (err, data) => { - if (err) return done(err); - - const textFrame = Buffer.concat([Buffer.from([0xc1, data.length]), data]); - const closeFrame = Buffer.from([0x88, 0x00]); - - p.add(textFrame); - p.add(textFrame); - p.add(closeFrame); - - assert.strictEqual(p._state, 5); - assert.strictEqual(p._bufferedBytes, textFrame.length + closeFrame.length); - - p.cleanup(() => { - assert.deepStrictEqual(results, ['Hello', 'Hello', 1005, '']); - assert.strictEqual(p.onmessage, null); - done(); - }); - }); - }); - - it('consumes all data before calling `cleanup` callback (3/4)', function (done) { - const perMessageDeflate = new PerMessageDeflate(); - perMessageDeflate.accept([{}]); - - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); - const buf = Buffer.from('Hello'); - const results = []; - - p.onerror = (err, code) => results.push(err.message, code); - p.onmessage = (message) => results.push(message); - - perMessageDeflate.compress(buf, true, (err, data) => { - if (err) return done(err); - - const textFrame = Buffer.concat([Buffer.from([0xc1, data.length]), data]); - const invalidFrame = Buffer.from([0xa0, 0x00]); - - p.add(textFrame); - p.add(textFrame); - p.add(invalidFrame); - - assert.strictEqual(p._state, 5); - assert.strictEqual(p._bufferedBytes, textFrame.length + invalidFrame.length); - - p.cleanup(() => { - assert.deepStrictEqual(results, [ - 'Hello', - 'Hello', - 'Invalid WebSocket frame: RSV2 and RSV3 must be clear', - 1002 - ]); - assert.strictEqual(p.onmessage, null); - done(); + receiver.write(Buffer.from([0x80, fragment2.length])); + receiver.write(fragment2); }); }); }); - it('consumes all data before calling `cleanup` callback (4/4)', function (done) { - const perMessageDeflate = new PerMessageDeflate(); - perMessageDeflate.accept([{}]); - - const p = new Receiver({ 'permessage-deflate': perMessageDeflate }); - const buf = Buffer.from('Hello'); - const results = []; - - p.onmessage = (message) => results.push(message); - - perMessageDeflate.compress(buf, true, (err, data) => { - if (err) return done(err); - - const textFrame = Buffer.concat([Buffer.from([0xc1, data.length]), data]); - const incompleteFrame = Buffer.from([0x82, 0x0a, 0x00, 0x00]); - - p.add(textFrame); - p.add(incompleteFrame); - - assert.strictEqual(p._state, 5); - assert.strictEqual(p._bufferedBytes, incompleteFrame.length); - - p.cleanup(() => { - assert.deepStrictEqual(results, ['Hello']); - assert.strictEqual(p.onmessage, null); - done(); - }); - }); - }); - - it('can emit nodebuffer of fragmented binary message', function (done) { - const p = new Receiver(); + it("honors the 'nodebuffer' binary type", function (done) { + const receiver = new Receiver(); const frags = [ crypto.randomBytes(7321), crypto.randomBytes(137), @@ -947,12 +825,11 @@ describe('Receiver', function () { crypto.randomBytes(3) ]; - p.binaryType = 'nodebuffer'; - p.onmessage = (data) => { + receiver.on('message', (data) => { assert.ok(Buffer.isBuffer(data)); assert.ok(data.equals(Buffer.concat(frags))); done(); - }; + }); frags.forEach((frag, i) => { Sender.frame(frag, { @@ -961,24 +838,24 @@ describe('Receiver', function () { readOnly: true, mask: false, rsv1: false - }).forEach((buf) => p.add(buf)); + }).forEach((buf) => receiver.write(buf)); }); }); - it('can emit arraybuffer of fragmented binary message', function (done) { - const p = new Receiver(); + it("honors the 'arraybuffer' binary type", function (done) { + const receiver = new Receiver(); const frags = [ crypto.randomBytes(19221), crypto.randomBytes(954), crypto.randomBytes(623987) ]; - p._binaryType = 'arraybuffer'; - p.onmessage = (data) => { + receiver._binaryType = 'arraybuffer'; + receiver.on('message', (data) => { assert.ok(data instanceof ArrayBuffer); assert.ok(Buffer.from(data).equals(Buffer.concat(frags))); done(); - }; + }); frags.forEach((frag, i) => { Sender.frame(frag, { @@ -987,12 +864,12 @@ describe('Receiver', function () { readOnly: true, mask: false, rsv1: false - }).forEach((buf) => p.add(buf)); + }).forEach((buf) => receiver.write(buf)); }); }); - it('can emit fragments of fragmented binary message', function (done) { - const p = new Receiver(); + it("honors the 'fragments' binary type", function (done) { + const receiver = new Receiver(); const frags = [ crypto.randomBytes(17), crypto.randomBytes(419872), @@ -1001,11 +878,11 @@ describe('Receiver', function () { crypto.randomBytes(1) ]; - p._binaryType = 'fragments'; - p.onmessage = (data) => { + receiver._binaryType = 'fragments'; + receiver.on('message', (data) => { assert.deepStrictEqual(data, frags); done(); - }; + }); frags.forEach((frag, i) => { Sender.frame(frag, { @@ -1014,7 +891,7 @@ describe('Receiver', function () { readOnly: true, mask: false, rsv1: false - }).forEach((buf) => p.add(buf)); + }).forEach((buf) => receiver.write(buf)); }); }); }); From aa2c423e3ea232afd5b3d184a806fdcd878efb3e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 19 Feb 2018 18:15:37 +0100 Subject: [PATCH 489/669] [minor] Do not use `Readable.prototype.pipe()` --- lib/websocket.js | 100 +++++++++++++++++++++++++++++------------------ 1 file changed, 62 insertions(+), 38 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index dee00c3cc..672268868 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -132,13 +132,9 @@ class WebSocket extends EventEmitter { receiver[kWebSocket] = this; socket[kWebSocket] = this; - socket.on('close', socketOnClose); - socket.on('end', socketOnEnd); - socket.on('error', socketOnError); - socket.on('error', NOOP); - receiver.on('message', receiverOnMessage); receiver.on('close', receiverOnClose); + receiver.on('drain', receiverOnDrain); receiver.on('error', receiverOnError); receiver.on('ping', receiverOnPing); receiver.on('pong', receiverOnPong); @@ -147,7 +143,12 @@ class WebSocket extends EventEmitter { socket.setNoDelay(); if (head.length > 0) socket.unshift(head); - socket.pipe(receiver); + + socket.on('close', socketOnClose); + socket.on('data', socketOnData); + socket.on('end', socketOnEnd); + socket.on('error', socketOnError); + socket.on('error', NOOP); this.readyState = WebSocket.OPEN; this.emit('open'); @@ -696,7 +697,7 @@ function abortHandshake (websocket, stream, message) { function receiverOnClose (code, reason) { const websocket = this[kWebSocket]; - websocket._socket.unpipe(websocket._receiver); + websocket._socket.removeListener('data', socketOnData); websocket._socket.resume(); websocket._closeFrameReceived = true; @@ -707,6 +708,15 @@ function receiverOnClose (code, reason) { else websocket.close(code, reason); } +/** + * The listener of the `Receiver` `'drain'` event. + * + * @private + */ +function receiverOnDrain () { + this[kWebSocket]._socket.resume(); +} + /** * The listener of the `Receiver` `'error'` event. * @@ -721,7 +731,6 @@ function receiverOnError (err) { websocket.readyState = WebSocket.CLOSING; websocket._socket.removeListener('error', socketOnError); websocket._closeCode = err[constants.kStatusCode]; - websocket._closeMessage = ''; websocket.emit('error', err); websocket._socket.destroy(); } @@ -759,36 +768,6 @@ function receiverOnPong (data) { this[kWebSocket].emit('pong', data); } -/** - * The listener of the `net.Socket` `'error'` event. - * - * @param {Error} err The emitted error - * @private - */ -function socketOnError (err) { - const websocket = this[kWebSocket]; - - websocket.readyState = WebSocket.CLOSING; - this.removeListener('error', socketOnError); - - // - // There might be valid buffered data in the socket waiting to be read so we - // can't re-emit this error immediately. - // - websocket._error = err; -} - -/** - * The listener of the `net.Socket` `'end'` event. - * - * @private - */ -function socketOnEnd () { - this[kWebSocket].readyState = WebSocket.CLOSING; - this.removeListener('error', socketOnError); - this.end(); -} - /** * The listener of the `net.Socket` `'close'` event. * @@ -829,3 +808,48 @@ function socketOnClose () { websocket._receiver.on('finish', emitClose); } } + +/** + * The listener of the `net.Socket` `'data'` event. + * + * @param {Buffer} chunk A chunk of data + * @private + */ +function socketOnData (chunk) { + if (!this[kWebSocket]._receiver.write(chunk)) { + this.pause(); + } +} + +/** + * The listener of the `net.Socket` `'end'` event. + * + * @private + */ +function socketOnEnd () { + const websocket = this[kWebSocket]; + + websocket.readyState = WebSocket.CLOSING; + this.removeListener('error', socketOnError); + websocket._receiver.end(); + this.end(); +} + +/** + * The listener of the `net.Socket` `'error'` event. + * + * @param {Error} err The emitted error + * @private + */ +function socketOnError (err) { + const websocket = this[kWebSocket]; + + websocket.readyState = WebSocket.CLOSING; + this.removeListener('error', socketOnError); + + // + // There might be valid buffered data in the socket waiting to be read so we + // can't re-emit this error immediately. + // + websocket._error = err; +} From a4050db3ad981db1c49daf5ba756ec4f4f71bc44 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 20 Feb 2018 13:54:29 +0100 Subject: [PATCH 490/669] [major] Do not re-emit `net.Socket` errors --- doc/ws.md | 3 +-- lib/websocket.js | 55 +++++++++++++++++------------------------- test/websocket.test.js | 53 +++++++++++++++++----------------------- 3 files changed, 45 insertions(+), 66 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 464df38b0..f505ed5e2 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -244,8 +244,7 @@ human-readable string explaining why the connection has been closed. - `error` {Error} -Emitted when an error occurs. Errors from the underlying `net.Socket` are -forwarded here. +Emitted when an error occurs. ### Event: 'message' diff --git a/lib/websocket.js b/lib/websocket.js index 672268868..f3b460e30 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -17,7 +17,6 @@ const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']; const kWebSocket = constants.kWebSocket; const protocolVersions = [8, 13]; const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly. -const NOOP = constants.NOOP; /** * Class representing a WebSocket. @@ -49,7 +48,6 @@ class WebSocket extends EventEmitter { this._receiver = null; this._sender = null; this._socket = null; - this._error = null; if (address !== null) { if (!protocols) { @@ -132,10 +130,10 @@ class WebSocket extends EventEmitter { receiver[kWebSocket] = this; socket[kWebSocket] = this; - receiver.on('message', receiverOnMessage); receiver.on('close', receiverOnClose); receiver.on('drain', receiverOnDrain); receiver.on('error', receiverOnError); + receiver.on('message', receiverOnMessage); receiver.on('ping', receiverOnPing); receiver.on('pong', receiverOnPong); @@ -148,7 +146,6 @@ class WebSocket extends EventEmitter { socket.on('data', socketOnData); socket.on('end', socketOnEnd); socket.on('error', socketOnError); - socket.on('error', NOOP); this.readyState = WebSocket.OPEN; this.emit('open'); @@ -167,17 +164,11 @@ class WebSocket extends EventEmitter { return; } - const err = this._error; - - if (err) { - this._error = null; - this.emit('error', err); - } - if (this._extensions[PerMessageDeflate.extensionName]) { this._extensions[PerMessageDeflate.extensionName].cleanup(); } + this._receiver.removeAllListeners(); this.emit('close', this._closeCode, this._closeMessage); } @@ -359,7 +350,6 @@ class WebSocket extends EventEmitter { if (this._socket) { this.readyState = WebSocket.CLOSING; - this._socket.removeListener('error', socketOnError); this._socket.destroy(); } } @@ -726,15 +716,21 @@ function receiverOnDrain () { function receiverOnError (err) { const websocket = this[kWebSocket]; - if (websocket._error) return; - websocket.readyState = WebSocket.CLOSING; - websocket._socket.removeListener('error', socketOnError); websocket._closeCode = err[constants.kStatusCode]; websocket.emit('error', err); websocket._socket.destroy(); } +/** + * The listener of the `Receiver` `'finish'` event. + * + * @private + */ +function receiverOnFinish () { + this[kWebSocket].emitClose(); +} + /** * The listener of the `Receiver` `'message'` event. * @@ -754,7 +750,7 @@ function receiverOnMessage (data) { function receiverOnPing (data) { const websocket = this[kWebSocket]; - websocket.pong(data, !websocket._isServer, NOOP); + websocket.pong(data, !websocket._isServer, constants.NOOP); websocket.emit('ping', data); } @@ -776,7 +772,8 @@ function receiverOnPong (data) { function socketOnClose () { const websocket = this[kWebSocket]; - this.removeListener('error', socketOnError); + this.removeListener('close', socketOnClose); + this.removeListener('data', socketOnData); this.removeListener('end', socketOnEnd); this[kWebSocket] = undefined; @@ -799,13 +796,8 @@ function socketOnClose () { ) { websocket.emitClose(); } else { - const emitClose = () => { - websocket._receiver.removeAllListeners(); - websocket.emitClose(); - }; - - websocket._receiver.on('error', emitClose); - websocket._receiver.on('finish', emitClose); + websocket._receiver.on('error', receiverOnFinish); + websocket._receiver.on('finish', receiverOnFinish); } } @@ -830,7 +822,6 @@ function socketOnEnd () { const websocket = this[kWebSocket]; websocket.readyState = WebSocket.CLOSING; - this.removeListener('error', socketOnError); websocket._receiver.end(); this.end(); } @@ -838,18 +829,16 @@ function socketOnEnd () { /** * The listener of the `net.Socket` `'error'` event. * - * @param {Error} err The emitted error * @private */ -function socketOnError (err) { +function socketOnError () { const websocket = this[kWebSocket]; - websocket.readyState = WebSocket.CLOSING; this.removeListener('error', socketOnError); + this.on('error', constants.NOOP); - // - // There might be valid buffered data in the socket waiting to be read so we - // can't re-emit this error immediately. - // - websocket._error = err; + if (websocket) { + websocket.readyState = WebSocket.CLOSING; + this.destroy(); + } } diff --git a/test/websocket.test.js b/test/websocket.test.js index 3801fdd9c..66e59acd5 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -8,7 +8,6 @@ const crypto = require('crypto'); const https = require('https'); const http = require('http'); const dns = require('dns'); -const net = require('net'); const fs = require('fs'); const os = require('os'); @@ -393,6 +392,28 @@ describe('WebSocket', function () { }); }); + it('does not re-emit `net.Socket` errors', function (done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + + ws.on('open', () => { + ws._socket.on('error', (err) => { + assert.ok(err instanceof Error); + assert.ok(err.message.startsWith('write E')); + ws.on('close', (code, message) => { + assert.strictEqual(message, ''); + assert.strictEqual(code, 1006); + wss.close(done); + }); + }); + + for (const client of wss.clients) client.terminate(); + ws.send('foo'); + ws.send('bar'); + }); + }); + }); + it("emits an 'upgrade' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1160,36 +1181,6 @@ describe('WebSocket', function () { }); }); - it('emits an error if the close frame can not be sent', function (done) { - const wss = new WebSocket.Server({ port: 0 }, () => { - const socket = net.createConnection(wss.address().port, () => { - socket.write( - 'GET / HTTP/1.1\r\n' + - 'Host: localhost\r\n' + - 'Upgrade: websocket\r\n' + - 'Connection: Upgrade\r\n' + - 'Sec-WebSocket-Key: qqFVFwaCnSMXiqfezY/AZQ==\r\n' + - 'Sec-WebSocket-Version: 13\r\n' + - '\r\n' - ); - socket.destroy(); - }); - - wss.on('connection', (ws) => { - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.ok(err.message.startsWith('write E')); - ws.on('close', (code, message) => { - assert.strictEqual(message, ''); - assert.strictEqual(code, 1006); - wss.close(done); - }); - }); - ws.close(); - }); - }); - }); - it('sends the close status code only when necessary', function (done) { let sent; const wss = new WebSocket.Server({ port: 0 }, () => { From 3913c594b403513feb670ba9834f7e1e9636c2c6 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 2 Mar 2018 11:57:20 +0100 Subject: [PATCH 491/669] [benchmark] Fix parser benchmark --- bench/parser.benchmark.js | 61 +++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index ca4282b4e..78a2888fa 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -10,14 +10,6 @@ const Receiver = WebSocket.Receiver; const Sender = WebSocket.Sender; const Buffer = safeBuffer.Buffer; -// -// Override the `cleanup` method to make the "close message" test work as -// expected. -// -Receiver.prototype.cleanup = function () { - this._state = 0; -}; - const options = { fin: true, rsv1: false, @@ -41,7 +33,6 @@ const pingFrame1 = Buffer.concat(Sender.frame( const textFrame = Buffer.from('819461616161' + '61'.repeat(20), 'hex'); const pingFrame2 = Buffer.from('8900', 'hex'); -const closeFrame = Buffer.from('8800', 'hex'); const binaryFrame1 = createBinaryFrame(125); const binaryFrame2 = createBinaryFrame(65535); const binaryFrame3 = createBinaryFrame(200 * 1024); @@ -50,16 +41,48 @@ const binaryFrame4 = createBinaryFrame(1024 * 1024); const suite = new benchmark.Suite(); const receiver = new Receiver(); -receiver.onmessage = receiver.onclose = receiver.onping = () => {}; - -suite.add('ping frame (5 bytes payload)', () => receiver.add(pingFrame1)); -suite.add('ping frame (no payload)', () => receiver.add(pingFrame2)); -suite.add('close frame (no payload)', () => receiver.add(closeFrame)); -suite.add('text frame (20 bytes payload)', () => receiver.add(textFrame)); -suite.add('binary frame (125 bytes payload)', () => receiver.add(binaryFrame1)); -suite.add('binary frame (65535 bytes payload)', () => receiver.add(binaryFrame2)); -suite.add('binary frame (200 KiB payload)', () => receiver.add(binaryFrame3)); -suite.add('binary frame (1 MiB payload)', () => receiver.add(binaryFrame4)); +suite.add('ping frame (5 bytes payload)', { + defer: true, + fn: (deferred) => { + receiver.write(pingFrame1, deferred.resolve.bind(deferred)); + } +}); +suite.add('ping frame (no payload)', { + defer: true, + fn: (deferred) => { + receiver.write(pingFrame2, deferred.resolve.bind(deferred)); + } +}); +suite.add('text frame (20 bytes payload)', { + defer: true, + fn: (deferred) => { + receiver.write(textFrame, deferred.resolve.bind(deferred)); + } +}); +suite.add('binary frame (125 bytes payload)', { + defer: true, + fn: (deferred) => { + receiver.write(binaryFrame1, deferred.resolve.bind(deferred)); + } +}); +suite.add('binary frame (65535 bytes payload)', { + defer: true, + fn: (deferred) => { + receiver.write(binaryFrame2, deferred.resolve.bind(deferred)); + } +}); +suite.add('binary frame (200 KiB payload)', { + defer: true, + fn: (deferred) => { + receiver.write(binaryFrame3, deferred.resolve.bind(deferred)); + } +}); +suite.add('binary frame (1 MiB payload)', { + defer: true, + fn: (deferred) => { + receiver.write(binaryFrame4, deferred.resolve.bind(deferred)); + } +}); suite.on('cycle', (e) => console.log(e.target.toString())); From 14538dbf965e73e040d71069d39fdefb9a19fcba Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 3 Mar 2018 07:27:20 +0100 Subject: [PATCH 492/669] [major] Fix subprotocol handling (#1312) Do not close the connection if the server does not agree to any of the client's requested subprotocols. Fixes #1296 --- doc/ws.md | 13 +++++++----- lib/websocket-server.js | 38 ++++++++++++++++++----------------- test/websocket-server.test.js | 24 +--------------------- test/websocket.test.js | 7 ++++--- 4 files changed, 33 insertions(+), 49 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index f505ed5e2..18e707ecf 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -50,16 +50,19 @@ if `verifyClient` is provided with two arguments then those are: reason phrase. -If `handleProtocols` is not set then the handshake is automatically accepted, -otherwise the function takes two arguments: +`handleProtocols` takes two arguments: - `protocols` {Array} The list of WebSocket subprotocols indicated by the client in the `Sec-WebSocket-Protocol` header. - `request` {http.IncomingMessage} The client HTTP GET request. -If returned value is `false` then the handshake is rejected with the HTTP 401 -status code, otherwise the returned value sets the value of the -`Sec-WebSocket-Protocol` header in the HTTP 101 response. +The returned value sets the value of the `Sec-WebSocket-Protocol` header in the +HTTP 101 response. If returned value is `false` the header is not added in the +response. + +If `handleProtocols` is not set then the first of the client's requested +subprotocols is used. + `perMessageDeflate` can be used to control the behavior of [permessage-deflate extension][permessage-deflate]. diff --git a/lib/websocket-server.js b/lib/websocket-server.js index ee0913fb5..e1da56e6d 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -196,18 +196,6 @@ class WebSocketServer extends EventEmitter { } } - var protocol = (req.headers['sec-websocket-protocol'] || '').split(/, */); - - // - // Optionally call external protocol selection handler. - // - if (this.options.handleProtocols) { - protocol = this.options.handleProtocols(protocol, req); - if (protocol === false) return abortConnection(socket, 401); - } else { - protocol = protocol[0]; - } - // // Optionally call external client verification handler. // @@ -222,7 +210,7 @@ class WebSocketServer extends EventEmitter { this.options.verifyClient(info, (verified, code, message) => { if (!verified) return abortConnection(socket, code || 401, message); - this.completeUpgrade(protocol, extensions, req, socket, head, cb); + this.completeUpgrade(extensions, req, socket, head, cb); }); return; } @@ -230,13 +218,12 @@ class WebSocketServer extends EventEmitter { if (!this.options.verifyClient(info)) return abortConnection(socket, 401); } - this.completeUpgrade(protocol, extensions, req, socket, head, cb); + this.completeUpgrade(extensions, req, socket, head, cb); } /** * Upgrade the connection to WebSocket. * - * @param {String} protocol The chosen subprotocol * @param {Object} extensions The accepted extensions * @param {http.IncomingMessage} req The request object * @param {net.Socket} socket The network socket between the server and client @@ -244,7 +231,7 @@ class WebSocketServer extends EventEmitter { * @param {Function} cb Callback * @private */ - completeUpgrade (protocol, extensions, req, socket, head, cb) { + completeUpgrade (extensions, req, socket, head, cb) { // // Destroy the socket if the client has already sent a FIN packet. // @@ -262,11 +249,26 @@ class WebSocketServer extends EventEmitter { ]; const ws = new WebSocket(null); + var protocol = req.headers['sec-websocket-protocol']; if (protocol) { - headers.push(`Sec-WebSocket-Protocol: ${protocol}`); - ws.protocol = protocol; + protocol = protocol.trim().split(/ *, */); + + // + // Optionally call external protocol selection handler. + // + if (this.options.handleProtocols) { + protocol = this.options.handleProtocols(protocol, req); + } else { + protocol = protocol[0]; + } + + if (protocol) { + headers.push(`Sec-WebSocket-Protocol: ${protocol}`); + ws.protocol = protocol; + } } + if (extensions[PerMessageDeflate.extensionName]) { const params = extensions[PerMessageDeflate.extensionName].params; const value = extension.format({ diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 1e9438803..9ef7f2ec3 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -649,7 +649,7 @@ describe('WebSocketServer', function () { }); describe('`handleProtocols`', function () { - it('can select the last protocol', function (done) { + it('allows to select a subprotocol', function (done) { const handleProtocols = (protocols, request) => { assert.ok(request instanceof http.IncomingMessage); assert.strictEqual(request.url, '/'); @@ -667,28 +667,6 @@ describe('WebSocketServer', function () { }); }); }); - - it('closes the connection if return value is `false`', function (done) { - const wss = new WebSocket.Server({ - handleProtocols: (protocols) => false, - port: 0 - }, () => { - const req = http.get({ - port: wss.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13 - } - }); - - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 401); - wss.close(done); - }); - }); - }); }); it('emits the `headers` event', function (done) { diff --git a/test/websocket.test.js b/test/websocket.test.js index 66e59acd5..6c91bfa19 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -614,9 +614,10 @@ describe('WebSocket', function () { }); it('fails if server sends a subprotocol when none was requested', function (done) { - const wss = new WebSocket.Server({ - handleProtocols: () => 'foo', - server + const wss = new WebSocket.Server({ server }); + + wss.on('headers', (headers) => { + headers.push('Sec-WebSocket-Protocol: foo'); }); const ws = new WebSocket(`ws://localhost:${server.address().port}`); From 3f80ab717c3bf1a7a6fd175a1c3bfb8e76b22976 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 4 Mar 2018 13:36:53 +0100 Subject: [PATCH 493/669] [major] Drop support for Node.js < 4.5.0 (#1313) --- .travis.yml | 1 - appveyor.yml | 1 - bench/parser.benchmark.js | 2 -- bench/speed.js | 2 -- lib/buffer-util.js | 4 ---- lib/constants.js | 4 ---- lib/permessage-deflate.js | 3 --- lib/receiver.js | 3 --- lib/sender.js | 3 --- lib/websocket-server.js | 3 --- package.json | 3 +-- test/permessage-deflate.test.js | 3 --- test/receiver.test.js | 2 -- test/sender.test.js | 3 --- test/websocket-server.test.js | 3 --- test/websocket.test.js | 3 --- 16 files changed, 1 insertion(+), 42 deletions(-) diff --git a/.travis.yml b/.travis.yml index e21953a5a..668010445 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,5 @@ node_js: - "8" - "6" - "4" - - "4.2.2" after_success: - "npm install coveralls@3 && nyc report --reporter=text-lcov | coveralls" diff --git a/appveyor.yml b/appveyor.yml index 394bc1e89..ce931fe9f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,6 @@ environment: - nodejs_version: "8" - nodejs_version: "6" - nodejs_version: "4" - - nodejs_version: "4.2.2" platform: - x86 - x64 diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index 78a2888fa..43cc1ea4e 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -1,6 +1,5 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const benchmark = require('benchmark'); const crypto = require('crypto'); @@ -8,7 +7,6 @@ const WebSocket = require('..'); const Receiver = WebSocket.Receiver; const Sender = WebSocket.Sender; -const Buffer = safeBuffer.Buffer; const options = { fin: true, diff --git a/bench/speed.js b/bench/speed.js index 75dfd5a80..e5d1591dd 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -1,11 +1,9 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const cluster = require('cluster'); const WebSocket = require('..'); -const Buffer = safeBuffer.Buffer; const port = 8181; if (cluster.isMaster) { diff --git a/lib/buffer-util.js b/lib/buffer-util.js index 5ab9e289f..38e16df3f 100644 --- a/lib/buffer-util.js +++ b/lib/buffer-util.js @@ -1,9 +1,5 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); - -const Buffer = safeBuffer.Buffer; - /** * Merges an array of buffers into a new buffer. * diff --git a/lib/constants.js b/lib/constants.js index 613a6766c..4082981f8 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -1,9 +1,5 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); - -const Buffer = safeBuffer.Buffer; - module.exports = { BINARY_TYPES: ['nodebuffer', 'arraybuffer', 'fragments'], GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', diff --git a/lib/permessage-deflate.js b/lib/permessage-deflate.js index 05832f28f..0d54f035b 100644 --- a/lib/permessage-deflate.js +++ b/lib/permessage-deflate.js @@ -1,14 +1,11 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const Limiter = require('async-limiter'); const zlib = require('zlib'); const bufferUtil = require('./buffer-util'); const constants = require('./constants'); -const Buffer = safeBuffer.Buffer; - const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); const EMPTY_BLOCK = Buffer.from([0x00]); diff --git a/lib/receiver.js b/lib/receiver.js index a66cc10ee..da9b1a1a5 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -1,6 +1,5 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const stream = require('stream'); const PerMessageDeflate = require('./permessage-deflate'); @@ -8,8 +7,6 @@ const bufferUtil = require('./buffer-util'); const validation = require('./validation'); const constants = require('./constants'); -const Buffer = safeBuffer.Buffer; - const GET_INFO = 0; const GET_PAYLOAD_LENGTH_16 = 1; const GET_PAYLOAD_LENGTH_64 = 2; diff --git a/lib/sender.js b/lib/sender.js index b3dacbc41..42cbb890f 100644 --- a/lib/sender.js +++ b/lib/sender.js @@ -1,6 +1,5 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const crypto = require('crypto'); const PerMessageDeflate = require('./permessage-deflate'); @@ -8,8 +7,6 @@ const bufferUtil = require('./buffer-util'); const validation = require('./validation'); const constants = require('./constants'); -const Buffer = safeBuffer.Buffer; - /** * HyBi Sender implementation. */ diff --git a/lib/websocket-server.js b/lib/websocket-server.js index e1da56e6d..ce1d6c242 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -1,6 +1,5 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const EventEmitter = require('events'); const crypto = require('crypto'); const http = require('http'); @@ -11,8 +10,6 @@ const extension = require('./extension'); const constants = require('./constants'); const WebSocket = require('./websocket'); -const Buffer = safeBuffer.Buffer; - /** * Class representing a WebSocket server. * diff --git a/package.json b/package.json index 0623d2437..e82afba69 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,7 @@ "lint": "eslint ." }, "dependencies": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0" + "async-limiter": "~1.0.0" }, "devDependencies": { "benchmark": "~2.1.2", diff --git a/test/permessage-deflate.test.js b/test/permessage-deflate.test.js index 038af2df7..ab2a9ab11 100644 --- a/test/permessage-deflate.test.js +++ b/test/permessage-deflate.test.js @@ -1,13 +1,10 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const assert = require('assert'); const PerMessageDeflate = require('../lib/permessage-deflate'); const extension = require('../lib/extension'); -const Buffer = safeBuffer.Buffer; - describe('PerMessageDeflate', function () { describe('#offer', function () { it('creates an offer', function () { diff --git a/test/receiver.test.js b/test/receiver.test.js index f235719ea..0b7c87a57 100644 --- a/test/receiver.test.js +++ b/test/receiver.test.js @@ -1,6 +1,5 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const assert = require('assert'); const crypto = require('crypto'); @@ -10,7 +9,6 @@ const Receiver = require('../lib/receiver'); const Sender = require('../lib/sender'); const kStatusCode = constants.kStatusCode; -const Buffer = safeBuffer.Buffer; describe('Receiver', function () { it('parses an unmasked text message', function (done) { diff --git a/test/sender.test.js b/test/sender.test.js index 69d4012e2..623ce5954 100644 --- a/test/sender.test.js +++ b/test/sender.test.js @@ -1,13 +1,10 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const assert = require('assert'); const PerMessageDeflate = require('../lib/permessage-deflate'); const Sender = require('../lib/sender'); -const Buffer = safeBuffer.Buffer; - describe('Sender', function () { describe('.frame', function () { it('does not mutate the input buffer if data is `readOnly`', function () { diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 9ef7f2ec3..1e949c29f 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -2,7 +2,6 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const assert = require('assert'); const crypto = require('crypto'); const https = require('https'); @@ -12,8 +11,6 @@ const fs = require('fs'); const WebSocket = require('..'); -const Buffer = safeBuffer.Buffer; - describe('WebSocketServer', function () { describe('#ctor', function () { it('throws an error if no option object is passed', function () { diff --git a/test/websocket.test.js b/test/websocket.test.js index 6c91bfa19..b5edea86e 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -2,7 +2,6 @@ 'use strict'; -const safeBuffer = require('safe-buffer'); const assert = require('assert'); const crypto = require('crypto'); const https = require('https'); @@ -14,8 +13,6 @@ const os = require('os'); const constants = require('../lib/constants'); const WebSocket = require('..'); -const Buffer = safeBuffer.Buffer; - class CustomAgent extends http.Agent { addRequest () {} } From fb05059620609d5ff71cee87c5d85e5ed10534ea Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 6 Mar 2018 07:58:05 +0100 Subject: [PATCH 494/669] [test] Fix faulty test --- test/websocket-server.test.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 1e949c29f..402cd827b 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -494,10 +494,7 @@ describe('WebSocketServer', function () { const wss = new WebSocket.Server({ verifyClient: (info) => { assert.strictEqual(info.origin, 'https://example.com'); - assert.strictEqual( - info.req.headers['sec-websocket-key'], - 'dGhlIHNhbXBsZSBub25jZQ==' - ); + assert.strictEqual(info.req.headers.foo, 'bar'); assert.ok(info.secure, true); return true; }, @@ -511,10 +508,7 @@ describe('WebSocketServer', function () { server.listen(0, () => { const ws = new WebSocket(`wss://localhost:${server.address().port}`, { - headers: { - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - Origin: 'https://example.com' - }, + headers: { Origin: 'https://example.com', foo: 'bar' }, rejectUnauthorized: false }); }); From 7fb82a3adaa5dcaf5bf4e244ef406f55c7eac4d2 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 6 Mar 2018 10:22:06 +0100 Subject: [PATCH 495/669] [fix] Rename the `'close'` event to `'conclude'` Avoid collisions with the `stream.Writable` `'close'` event. --- lib/receiver.js | 8 ++++---- lib/websocket.js | 10 +++++----- test/receiver.test.js | 36 +++++++++++++++++++++++------------- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/lib/receiver.js b/lib/receiver.js index da9b1a1a5..ab2d68c8d 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -23,11 +23,11 @@ class Receiver extends stream.Writable { /** * Creates a Receiver instance. * + * @param {String} binaryType The type for binary data * @param {Object} extensions An object containing the negotiated extensions * @param {Number} maxPayload The maximum allowed message length - * @param {String} binaryType The type for binary data */ - constructor (extensions, maxPayload, binaryType) { + constructor (binaryType, extensions, maxPayload) { super(); this._binaryType = binaryType || constants.BINARY_TYPES[0]; @@ -430,7 +430,7 @@ class Receiver extends stream.Writable { this._loop = false; if (data.length === 0) { - this.emit('close', 1005, ''); + this.emit('conclude', 1005, ''); this.end(); } else if (data.length === 1) { return error(RangeError, 'invalid payload length 1', true, 1002); @@ -447,7 +447,7 @@ class Receiver extends stream.Writable { return error(Error, 'invalid UTF-8 sequence', true, 1007); } - this.emit('close', code, buf.toString()); + this.emit('conclude', code, buf.toString()); this.end(); } diff --git a/lib/websocket.js b/lib/websocket.js index f3b460e30..87f3606fb 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -118,9 +118,9 @@ class WebSocket extends EventEmitter { */ setSocket (socket, head, maxPayload) { const receiver = new Receiver( + this._binaryType, this._extensions, - maxPayload, - this._binaryType + maxPayload ); this._sender = new Sender(socket, this._extensions); @@ -130,7 +130,7 @@ class WebSocket extends EventEmitter { receiver[kWebSocket] = this; socket[kWebSocket] = this; - receiver.on('close', receiverOnClose); + receiver.on('conclude', receiverOnConclude); receiver.on('drain', receiverOnDrain); receiver.on('error', receiverOnError); receiver.on('message', receiverOnMessage); @@ -678,13 +678,13 @@ function abortHandshake (websocket, stream, message) { } /** - * The listener of the `Receiver` `'close'` event. + * The listener of the `Receiver` `'conclude'` event. * * @param {Number} code The status code * @param {String} reason The reason for closing * @private */ -function receiverOnClose (code, reason) { +function receiverOnConclude (code, reason) { const websocket = this[kWebSocket]; websocket._socket.removeListener('data', socketOnData); diff --git a/test/receiver.test.js b/test/receiver.test.js index 0b7c87a57..21497e1ca 100644 --- a/test/receiver.test.js +++ b/test/receiver.test.js @@ -25,7 +25,7 @@ describe('Receiver', function () { it('parses a close message', function (done) { const receiver = new Receiver(); - receiver.on('close', (code, data) => { + receiver.on('conclude', (code, data) => { assert.strictEqual(code, 1005); assert.strictEqual(data, ''); done(); @@ -334,7 +334,9 @@ describe('Receiver', function () { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const receiver = new Receiver(undefined, { + 'permessage-deflate': perMessageDeflate + }); const buf = Buffer.from('Hello'); receiver.on('message', (data) => { @@ -354,7 +356,9 @@ describe('Receiver', function () { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const receiver = new Receiver(undefined, { + 'permessage-deflate': perMessageDeflate + }); const buf1 = Buffer.from('foo'); const buf2 = Buffer.from('bar'); @@ -398,7 +402,7 @@ describe('Receiver', function () { }); it('resets `totalPayloadLength` only on final frame (unfragmented)', function (done) { - const receiver = new Receiver({}, 10); + const receiver = new Receiver(undefined, {}, 10); receiver.on('message', (data) => { assert.strictEqual(receiver._totalPayloadLength, 0); @@ -411,7 +415,7 @@ describe('Receiver', function () { }); it('resets `totalPayloadLength` only on final frame (fragmented)', function (done) { - const receiver = new Receiver({}, 10); + const receiver = new Receiver(undefined, {}, 10); receiver.on('message', (data) => { assert.strictEqual(receiver._totalPayloadLength, 0); @@ -426,7 +430,7 @@ describe('Receiver', function () { }); it('resets `totalPayloadLength` only on final frame (fragmented + ping)', function (done) { - const receiver = new Receiver({}, 10); + const receiver = new Receiver(undefined, {}, 10); let data; receiver.on('ping', (buf) => { @@ -450,11 +454,13 @@ describe('Receiver', function () { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const receiver = new Receiver(undefined, { + 'permessage-deflate': perMessageDeflate + }); const results = []; const push = results.push.bind(results); - receiver.on('close', push).on('message', push); + receiver.on('conclude', push).on('message', push); receiver.on('finish', () => { assert.deepStrictEqual(results, ['', 1005, '']); done(); @@ -485,7 +491,9 @@ describe('Receiver', function () { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const receiver = new Receiver(undefined, { + 'permessage-deflate': perMessageDeflate + }); receiver.on('error', (err) => { assert.ok(err instanceof RangeError); @@ -602,7 +610,9 @@ describe('Receiver', function () { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); - const receiver = new Receiver({ 'permessage-deflate': perMessageDeflate }); + const receiver = new Receiver(undefined, { + 'permessage-deflate': perMessageDeflate + }); receiver.on('error', (err) => { assert.ok(err instanceof RangeError); @@ -736,7 +746,7 @@ describe('Receiver', function () { }); it('emits an error if a frame payload length is bigger than `maxPayload`', function (done) { - const receiver = new Receiver({}, 20 * 1024); + const receiver = new Receiver(undefined, {}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); const list = Sender.frame(msg, { @@ -763,7 +773,7 @@ describe('Receiver', function () { const perMessageDeflate = new PerMessageDeflate({}, false, 25); perMessageDeflate.accept([{}]); - const receiver = new Receiver({ + const receiver = new Receiver(undefined, { 'permessage-deflate': perMessageDeflate }, 25); const buf = Buffer.from('A'.repeat(50)); @@ -787,7 +797,7 @@ describe('Receiver', function () { const perMessageDeflate = new PerMessageDeflate({}, false, 25); perMessageDeflate.accept([{}]); - const receiver = new Receiver({ + const receiver = new Receiver(undefined, { 'permessage-deflate': perMessageDeflate }, 25); const buf = Buffer.from('A'.repeat(15)); From 7abe823874f713d8d60a315a7c0ea877b6f544a8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 6 Mar 2018 11:41:51 +0100 Subject: [PATCH 496/669] [minor] Rename `abortConnection` to `abortHandshake` --- lib/websocket-server.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/websocket-server.js b/lib/websocket-server.js index ce1d6c242..72618d028 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -169,7 +169,7 @@ class WebSocketServer extends EventEmitter { !req.headers['sec-websocket-key'] || (version !== 8 && version !== 13) || !this.shouldHandle(req) ) { - return abortConnection(socket, 400); + return abortHandshake(socket, 400); } if (this.options.perMessageDeflate) { @@ -189,7 +189,7 @@ class WebSocketServer extends EventEmitter { extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } } catch (err) { - return abortConnection(socket, 400); + return abortHandshake(socket, 400); } } @@ -205,14 +205,14 @@ class WebSocketServer extends EventEmitter { if (this.options.verifyClient.length === 2) { this.options.verifyClient(info, (verified, code, message) => { - if (!verified) return abortConnection(socket, code || 401, message); + if (!verified) return abortHandshake(socket, code || 401, message); this.completeUpgrade(extensions, req, socket, head, cb); }); return; } - if (!this.options.verifyClient(info)) return abortConnection(socket, 401); + if (!this.options.verifyClient(info)) return abortHandshake(socket, 401); } this.completeUpgrade(extensions, req, socket, head, cb); @@ -332,7 +332,7 @@ function socketOnError () { * @param {String} [message] The HTTP response body * @private */ -function abortConnection (socket, code, message) { +function abortHandshake (socket, code, message) { if (socket.writable) { message = message || http.STATUS_CODES[code]; socket.write( From d3af50627de62b0d8b9c42d915e8c6a426238363 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 6 Mar 2018 15:11:56 +0100 Subject: [PATCH 497/669] [dist] 5.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e82afba69..36a267567 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "4.1.0", + "version": "5.0.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 2a55ddae7c6274d87d4bd7e26a9d0289778ef45c Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 8 Mar 2018 21:09:04 +0100 Subject: [PATCH 498/669] chore(package): update eslint-plugin-promise to version 3.7.0 (#1321) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 36a267567..8093d140e 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "eslint-config-standard": "~11.0.0", "eslint-plugin-import": "~2.9.0", "eslint-plugin-node": "~6.0.0", - "eslint-plugin-promise": "~3.6.0", + "eslint-plugin-promise": "~3.7.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~5.0.0", "nyc": "~11.4.1", From 23f5957d908d08569fb5eee688bc34935556f392 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Sun, 11 Mar 2018 07:54:23 +0100 Subject: [PATCH 499/669] [fix] Remove buffer `noAssert` argument (#1324) The `noAssert` argument is not supported in Node.js 10.x. Refs: https://github.com/nodejs/node/pull/18395 --- lib/receiver.js | 8 ++++---- lib/sender.js | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/receiver.js b/lib/receiver.js index ab2d68c8d..81dc0bf8a 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -235,7 +235,7 @@ class Receiver extends stream.Writable { return; } - this._payloadLength = this.consume(2).readUInt16BE(0, true); + this._payloadLength = this.consume(2).readUInt16BE(0); return this.haveLength(); } @@ -252,7 +252,7 @@ class Receiver extends stream.Writable { } const buf = this.consume(8); - const num = buf.readUInt32BE(0, true); + const num = buf.readUInt32BE(0); // // The maximum safe integer in JavaScript is 2^53 - 1. An error is returned @@ -268,7 +268,7 @@ class Receiver extends stream.Writable { ); } - this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4, true); + this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4); return this.haveLength(); } @@ -435,7 +435,7 @@ class Receiver extends stream.Writable { } else if (data.length === 1) { return error(RangeError, 'invalid payload length 1', true, 1002); } else { - const code = data.readUInt16BE(0, true); + const code = data.readUInt16BE(0); if (!validation.isValidStatusCode(code)) { return error(RangeError, `invalid status code ${code}`, true, 1002); diff --git a/lib/sender.js b/lib/sender.js index 42cbb890f..060e55392 100644 --- a/lib/sender.js +++ b/lib/sender.js @@ -61,10 +61,10 @@ class Sender { if (options.rsv1) target[0] |= 0x40; if (payloadLength === 126) { - target.writeUInt16BE(data.length, 2, true); + target.writeUInt16BE(data.length, 2); } else if (payloadLength === 127) { - target.writeUInt32BE(0, 2, true); - target.writeUInt32BE(data.length, 6, true); + target.writeUInt32BE(0, 2); + target.writeUInt32BE(data.length, 6); } if (!options.mask) { @@ -112,10 +112,10 @@ class Sender { throw new TypeError('First argument must be a valid error code number'); } else if (data === undefined || data === '') { buf = Buffer.allocUnsafe(2); - buf.writeUInt16BE(code, 0, true); + buf.writeUInt16BE(code, 0); } else { buf = Buffer.allocUnsafe(2 + Buffer.byteLength(data)); - buf.writeUInt16BE(code, 0, true); + buf.writeUInt16BE(code, 0); buf.write(data, 2); } From 57d3dbb9e1b6d81132bbf2c18a35f09eab783a87 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 13 Mar 2018 08:13:49 +0100 Subject: [PATCH 500/669] chore(package): update nyc to version 11.6.0 (#1326) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8093d140e..3bdb75b39 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "eslint-plugin-promise": "~3.7.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~5.0.0", - "nyc": "~11.4.1", + "nyc": "~11.6.0", "utf-8-validate": "~4.0.0" } } From 1504d6713138431dacd67407c115290c0985899c Mon Sep 17 00:00:00 2001 From: Arnout Kazemier <3rd-Eden@users.noreply.github.com> Date: Fri, 16 Mar 2018 15:02:25 +0100 Subject: [PATCH 501/669] [fix] Allow URL instances as URL in WebSocket constructor (#1329) Fixes #1143 --- doc/ws.md | 2 +- lib/websocket.js | 39 +++++++++++++++++++---------------- test/websocket.test.js | 46 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 19 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 18e707ecf..cc634ad98 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -183,7 +183,7 @@ This class represents a WebSocket. It extends the `EventEmitter`. ### new WebSocket(address[, protocols][, options]) -- `address` {String} The URL to which to connect. +- `address` {String|url.Url|url.URL} The URL to which to connect. - `protocols` {String|Array} The list of subprotocols. - `options` {Object} - `protocol` {String} Value of the `Sec-WebSocket-Protocol` header. diff --git a/lib/websocket.js b/lib/websocket.js index 87f3606fb..53aa1ebc9 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -27,7 +27,7 @@ class WebSocket extends EventEmitter { /** * Create a new `WebSocket`. * - * @param {String} address The URL to which to connect + * @param {(String|url.Url|url.URL)} address The URL to which to connect * @param {(String|String[])} protocols The subprotocols * @param {Object} options Connection options */ @@ -404,7 +404,7 @@ module.exports = WebSocket; /** * Initialize a WebSocket client. * - * @param {String} address The URL to which to connect + * @param {(String|url.Url|url.URL)} address The URL to which to connect * @param {String[]} protocols The list of subprotocols * @param {Object} options Connection options * @param {String} options.protocol Value of the `Sec-WebSocket-Protocol` header @@ -463,24 +463,35 @@ function initAsClient (address, protocols, options) { } this._isServer = false; - this.url = address; - const serverUrl = url.parse(address); + var serverUrl; + + if (typeof address === 'object' && address.href !== undefined) { + serverUrl = address; + this.url = address.href; + } else { + serverUrl = url.parse(address); + this.url = address; + } + const isUnixSocket = serverUrl.protocol === 'ws+unix:'; - if (!serverUrl.host && (!isUnixSocket || !serverUrl.path)) { - throw new Error(`Invalid URL: ${address}`); + if (!serverUrl.host && (!isUnixSocket || !serverUrl.pathname)) { + throw new Error(`Invalid URL: ${this.url}`); } const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:'; const key = crypto.randomBytes(16).toString('base64'); const httpObj = isSecure ? https : http; + const path = serverUrl.search + ? `${serverUrl.pathname || '/'}${serverUrl.search}` + : serverUrl.pathname || '/'; var perMessageDeflate; const requestOptions = { port: serverUrl.port || (isSecure ? 443 : 80), host: serverUrl.hostname, - path: '/', + path: path, headers: { 'Sec-WebSocket-Version': options.protocolVersion, 'Sec-WebSocket-Key': key, @@ -511,24 +522,18 @@ function initAsClient (address, protocols, options) { } if (options.host) requestOptions.headers.Host = options.host; if (serverUrl.auth) requestOptions.auth = serverUrl.auth; + else if (serverUrl.username || serverUrl.password) { + requestOptions.auth = `${serverUrl.username}:${serverUrl.password}`; + } if (options.localAddress) requestOptions.localAddress = options.localAddress; if (options.family) requestOptions.family = options.family; if (isUnixSocket) { - const parts = serverUrl.path.split(':'); + const parts = path.split(':'); requestOptions.socketPath = parts[0]; requestOptions.path = parts[1]; - } else if (serverUrl.path) { - // - // Make sure that path starts with `/`. - // - if (serverUrl.path.charAt(0) !== '/') { - requestOptions.path = `/${serverUrl.path}`; - } else { - requestOptions.path = serverUrl.path; - } } var agent = options.agent; diff --git a/test/websocket.test.js b/test/websocket.test.js index b5edea86e..79d0addec 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -7,6 +7,7 @@ const crypto = require('crypto'); const https = require('https'); const http = require('http'); const dns = require('dns'); +const url = require('url'); const fs = require('fs'); const os = require('os'); @@ -26,6 +27,30 @@ describe('WebSocket', function () { ); }); + it('accepts `url.Url` objects as url', function (done) { + const agent = new CustomAgent(); + + agent.addRequest = (req) => { + assert.strictEqual(req.path, '/'); + done(); + }; + + const ws = new WebSocket(url.parse('ws://localhost'), { agent }); + }); + + it('accepts `url.URL` objects as url', function (done) { + if (!url.URL) return this.skip(); + + const agent = new CustomAgent(); + + agent.addRequest = (req) => { + assert.strictEqual(req.path, '/'); + done(); + }; + + const ws = new WebSocket(new url.URL('ws://localhost'), { agent }); + }); + describe('options', function () { it('accepts an `agent` option', function (done) { const agent = new CustomAgent(); @@ -1781,7 +1806,7 @@ describe('WebSocket', function () { }); describe('Request headers', function () { - it('adds the authorization header if userinfo is present', function (done) { + it('adds the authorization header if the url has userinfo (1/2)', function (done) { const agent = new CustomAgent(); const auth = 'test:testpass'; @@ -1796,6 +1821,25 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://${auth}@localhost`, { agent }); }); + it('adds the authorization header if the url has userinfo (2/2)', function (done) { + if (!url.URL) return this.skip(); + + const agent = new CustomAgent(); + const auth = 'test:testpass'; + + agent.addRequest = (req) => { + assert.strictEqual( + req._headers.authorization, + `Basic ${Buffer.from(auth).toString('base64')}` + ); + done(); + }; + + const ws = new WebSocket(new url.URL(`ws://${auth}@localhost`), { + agent + }); + }); + it('adds custom headers', function (done) { const agent = new CustomAgent(); From 938cddedc09d01e58a2ba7353666e577f47275ba Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 17 Mar 2018 07:27:46 +0100 Subject: [PATCH 502/669] chore(package): update eslint to version 4.19.0 (#1330) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3bdb75b39..f0bb08c11 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.18.0", + "eslint": "~4.19.0", "eslint-config-standard": "~11.0.0", "eslint-plugin-import": "~2.9.0", "eslint-plugin-node": "~6.0.0", From 9e152f920a1818d8a23e0a113b4625c83f90d30d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 19 Mar 2018 14:04:19 +0100 Subject: [PATCH 503/669] [feature] Allow all options accepted by `http{,s}.request()` (#1332) Do not use an agent by default and add ability to use all options allowed in `http.request()` and `https.request()`. --- doc/ws.md | 20 +--- lib/websocket.js | 203 +++++++++++++++++------------------------ test/websocket.test.js | 73 --------------- 3 files changed, 89 insertions(+), 207 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index cc634ad98..6f1546bb2 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -186,28 +186,12 @@ This class represents a WebSocket. It extends the `EventEmitter`. - `address` {String|url.Url|url.URL} The URL to which to connect. - `protocols` {String|Array} The list of subprotocols. - `options` {Object} - - `protocol` {String} Value of the `Sec-WebSocket-Protocol` header. - `handshakeTimeout` {Number} Timeout in milliseconds for the handshake request. - `perMessageDeflate` {Boolean|Object} Enable/disable permessage-deflate. - - `localAddress` {String} Local interface to bind for network connections. - `protocolVersion` {Number} Value of the `Sec-WebSocket-Version` header. - - `headers` {Object} An object with custom headers to send along with the - request. - `origin` {String} Value of the `Origin` or `Sec-WebSocket-Origin` header depending on the `protocolVersion`. - - `agent` {http.Agent|https.Agent} Use the specified Agent. - - `host` {String} Value of the `Host` header. - - `family` {Number} IP address family to use during hostname lookup (4 or 6). - - `checkServerIdentity` {Function} A function to validate the server hostname. - - `rejectUnauthorized` {Boolean} Verify or not the server certificate. - - `passphrase` {String} The passphrase for the private key or pfx. - - `ecdhCurve` {String} A named curve or a colon separated list of curve NIDs - or names to use for ECDH key agreement. - - `ciphers` {String} The ciphers to use or exclude - - `cert` {String|Array|Buffer} The certificate key. - - `key` {String|Array|Buffer} The private key. - - `pfx` {String|Buffer} The private key, certificate, and CA certs. - - `ca` {Array} Trusted certificates. + - Any other option allowed in [http.request()][] or [https.request()][]. `perMessageDeflate` default value is `true`. When using an object, parameters are the same of the server. The only difference is the direction of requests. @@ -425,3 +409,5 @@ The URL of the WebSocket server. Server clients don't have this attribute. [concurrency-limit]: https://github.com/websockets/ws/issues/1202 [permessage-deflate]: https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19 [zlib-options]: https://nodejs.org/api/zlib.html#zlib_class_options +[http.request()]: https://nodejs.org/api/http.html#http_http_request_options_callback +[https.request()]: https://nodejs.org/api/https.html#https_https_request_options_callback diff --git a/lib/websocket.js b/lib/websocket.js index 53aa1ebc9..c603675a9 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -4,6 +4,8 @@ const EventEmitter = require('events'); const crypto = require('crypto'); const https = require('https'); const http = require('http'); +const net = require('net'); +const tls = require('tls'); const url = require('url'); const PerMessageDeflate = require('./permessage-deflate'); @@ -50,13 +52,11 @@ class WebSocket extends EventEmitter { this._socket = null; if (address !== null) { - if (!protocols) { - protocols = []; - } else if (typeof protocols === 'string') { - protocols = [protocols]; - } else if (!Array.isArray(protocols)) { + if (Array.isArray(protocols)) { + protocols = protocols.join(', '); + } else if (typeof protocols === 'object' && protocols !== null) { options = protocols; - protocols = []; + protocols = undefined; } initAsClient.call(this, address, protocols, options); @@ -405,55 +405,30 @@ module.exports = WebSocket; * Initialize a WebSocket client. * * @param {(String|url.Url|url.URL)} address The URL to which to connect - * @param {String[]} protocols The list of subprotocols + * @param {String} protocols The subprotocols * @param {Object} options Connection options - * @param {String} options.protocol Value of the `Sec-WebSocket-Protocol` header * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate * @param {Number} options.handshakeTimeout Timeout in milliseconds for the handshake request - * @param {String} options.localAddress Local interface to bind for network connections * @param {Number} options.protocolVersion Value of the `Sec-WebSocket-Version` header - * @param {Object} options.headers An object containing request headers * @param {String} options.origin Value of the `Origin` or `Sec-WebSocket-Origin` header - * @param {http.Agent} options.agent Use the specified Agent - * @param {String} options.host Value of the `Host` header - * @param {Number} options.family IP address family to use during hostname lookup (4 or 6). - * @param {Function} options.checkServerIdentity A function to validate the server hostname - * @param {Boolean} options.rejectUnauthorized Verify or not the server certificate - * @param {String} options.passphrase The passphrase for the private key or pfx - * @param {String} options.ciphers The ciphers to use or exclude - * @param {String} options.ecdhCurve The curves for ECDH key agreement to use or exclude - * @param {(String|String[]|Buffer|Buffer[])} options.cert The certificate key - * @param {(String|String[]|Buffer|Buffer[])} options.key The private key - * @param {(String|Buffer)} options.pfx The private key, certificate, and CA certs - * @param {(String|String[]|Buffer|Buffer[])} options.ca Trusted certificates * @private */ function initAsClient (address, protocols, options) { options = Object.assign({ protocolVersion: protocolVersions[1], - protocol: protocols.join(','), - perMessageDeflate: true, - handshakeTimeout: null, - localAddress: null, - headers: null, - family: null, - origin: null, - agent: null, - host: null, - - // - // SSL options. - // - checkServerIdentity: null, - rejectUnauthorized: null, - passphrase: null, - ciphers: null, - ecdhCurve: null, - cert: null, - key: null, - pfx: null, - ca: null - }, options); + perMessageDeflate: true + }, options, { + createConnection: undefined, + socketPath: undefined, + hostname: undefined, + protocol: undefined, + timeout: undefined, + method: undefined, + auth: undefined, + host: undefined, + path: undefined, + port: undefined + }); if (protocolVersions.indexOf(options.protocolVersion) === -1) { throw new RangeError( @@ -464,114 +439,84 @@ function initAsClient (address, protocols, options) { this._isServer = false; - var serverUrl; + var parsedUrl; if (typeof address === 'object' && address.href !== undefined) { - serverUrl = address; + parsedUrl = address; this.url = address.href; } else { - serverUrl = url.parse(address); + parsedUrl = url.parse(address); this.url = address; } - const isUnixSocket = serverUrl.protocol === 'ws+unix:'; + const isUnixSocket = parsedUrl.protocol === 'ws+unix:'; - if (!serverUrl.host && (!isUnixSocket || !serverUrl.pathname)) { + if (!parsedUrl.host && (!isUnixSocket || !parsedUrl.pathname)) { throw new Error(`Invalid URL: ${this.url}`); } - const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:'; + const isSecure = parsedUrl.protocol === 'wss:' || parsedUrl.protocol === 'https:'; const key = crypto.randomBytes(16).toString('base64'); const httpObj = isSecure ? https : http; - const path = serverUrl.search - ? `${serverUrl.pathname || '/'}${serverUrl.search}` - : serverUrl.pathname || '/'; + const path = parsedUrl.search + ? `${parsedUrl.pathname || '/'}${parsedUrl.search}` + : parsedUrl.pathname || '/'; var perMessageDeflate; - const requestOptions = { - port: serverUrl.port || (isSecure ? 443 : 80), - host: serverUrl.hostname, - path: path, - headers: { - 'Sec-WebSocket-Version': options.protocolVersion, - 'Sec-WebSocket-Key': key, - 'Connection': 'Upgrade', - 'Upgrade': 'websocket' - } - }; + options.createConnection = isSecure ? tlsConnect : netConnect; + options.port = parsedUrl.port || (isSecure ? 443 : 80); + options.host = parsedUrl.hostname; + options.headers = Object.assign({ + 'Sec-WebSocket-Version': options.protocolVersion, + 'Sec-WebSocket-Key': key, + 'Connection': 'Upgrade', + 'Upgrade': 'websocket' + }, options.headers); + options.path = path; - if (options.headers) Object.assign(requestOptions.headers, options.headers); if (options.perMessageDeflate) { perMessageDeflate = new PerMessageDeflate( options.perMessageDeflate !== true ? options.perMessageDeflate : {}, false ); - requestOptions.headers['Sec-WebSocket-Extensions'] = extension.format({ + options.headers['Sec-WebSocket-Extensions'] = extension.format({ [PerMessageDeflate.extensionName]: perMessageDeflate.offer() }); } - if (options.protocol) { - requestOptions.headers['Sec-WebSocket-Protocol'] = options.protocol; + if (protocols) { + options.headers['Sec-WebSocket-Protocol'] = protocols; } if (options.origin) { if (options.protocolVersion < 13) { - requestOptions.headers['Sec-WebSocket-Origin'] = options.origin; + options.headers['Sec-WebSocket-Origin'] = options.origin; } else { - requestOptions.headers.Origin = options.origin; + options.headers.Origin = options.origin; } } - if (options.host) requestOptions.headers.Host = options.host; - if (serverUrl.auth) requestOptions.auth = serverUrl.auth; - else if (serverUrl.username || serverUrl.password) { - requestOptions.auth = `${serverUrl.username}:${serverUrl.password}`; + if (parsedUrl.auth) { + options.auth = parsedUrl.auth; + } else if (parsedUrl.username || parsedUrl.password) { + options.auth = `${parsedUrl.username}:${parsedUrl.password}`; } - if (options.localAddress) requestOptions.localAddress = options.localAddress; - if (options.family) requestOptions.family = options.family; - if (isUnixSocket) { const parts = path.split(':'); - requestOptions.socketPath = parts[0]; - requestOptions.path = parts[1]; - } - - var agent = options.agent; - - // - // A custom agent is required for these options. - // - if ( - options.rejectUnauthorized != null || - options.checkServerIdentity || - options.passphrase || - options.ciphers || - options.ecdhCurve || - options.cert || - options.key || - options.pfx || - options.ca - ) { - if (options.passphrase) requestOptions.passphrase = options.passphrase; - if (options.ciphers) requestOptions.ciphers = options.ciphers; - if (options.ecdhCurve) requestOptions.ecdhCurve = options.ecdhCurve; - if (options.cert) requestOptions.cert = options.cert; - if (options.key) requestOptions.key = options.key; - if (options.pfx) requestOptions.pfx = options.pfx; - if (options.ca) requestOptions.ca = options.ca; - if (options.checkServerIdentity) { - requestOptions.checkServerIdentity = options.checkServerIdentity; - } - if (options.rejectUnauthorized != null) { - requestOptions.rejectUnauthorized = options.rejectUnauthorized; + if (options.agent == null && process.versions.modules < 57) { + // + // Setting `socketPath` in conjunction with `createConnection` without an + // agent throws an error on Node.js < 8. Work around the issue by using a + // different property. + // + options._socketPath = parts[0]; + } else { + options.socketPath = parts[0]; } - if (!agent) agent = new httpObj.Agent(requestOptions); + options.path = parts[1]; } - if (agent) requestOptions.agent = agent; - - var req = this._req = httpObj.get(requestOptions); + var req = this._req = httpObj.get(options); if (options.handshakeTimeout) { req.setTimeout( @@ -616,12 +561,12 @@ function initAsClient (address, protocols, options) { } const serverProt = res.headers['sec-websocket-protocol']; - const protList = (options.protocol || '').split(/, */); + const protList = (protocols || '').split(/, */); var protError; - if (!options.protocol && serverProt) { + if (!protocols && serverProt) { protError = 'Server sent a subprotocol but none was requested'; - } else if (options.protocol && !serverProt) { + } else if (protocols && !serverProt) { protError = 'Server sent no subprotocol'; } else if (serverProt && protList.indexOf(serverProt) === -1) { protError = 'Server sent an invalid subprotocol'; @@ -656,6 +601,30 @@ function initAsClient (address, protocols, options) { }); } +/** + * Create a `net.Socket` and initiate a connection. + * + * @param {Object} options Connection options + * @return {net.Socket} The newly created socket used to start the connection + * @private + */ +function netConnect (options) { + options.path = options.socketPath || options._socketPath || undefined; + return net.connect(options); +} + +/** + * Create a `tls.TLSSocket` and initiate a connection. + * + * @param {Object} options Connection options + * @return {tls.TLSSocket} The newly created socket used to start the connection + * @private + */ +function tlsConnect (options) { + options.path = options.socketPath || options._socketPath || undefined; + return tls.connect(options); +} + /** * Abort the handshake and emit an error. * diff --git a/test/websocket.test.js b/test/websocket.test.js index 79d0addec..a33187480 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -6,10 +6,8 @@ const assert = require('assert'); const crypto = require('crypto'); const https = require('https'); const http = require('http'); -const dns = require('dns'); const url = require('url'); const fs = require('fs'); -const os = require('os'); const constants = require('../lib/constants'); const WebSocket = require('..'); @@ -52,16 +50,6 @@ describe('WebSocket', function () { }); describe('options', function () { - it('accepts an `agent` option', function (done) { - const agent = new CustomAgent(); - - agent.addRequest = () => { - done(); - }; - - const ws = new WebSocket('ws://localhost', { agent }); - }); - it('accepts the `options` object as 3rd argument', function () { const agent = new CustomAgent(); let count = 0; @@ -84,67 +72,6 @@ describe('WebSocket', function () { /^RangeError: Unsupported protocol version: 1000 \(supported versions: 8, 13\)$/ ); }); - - it('accepts the `localAddress` option', function (done) { - const wss = new WebSocket.Server({ host: '127.0.0.1', port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { - localAddress: '127.0.0.2' - }); - - ws.on('error', (err) => { - wss.close(() => { - // - // Skip this test on machines where 127.0.0.2 is disabled. - // - if (err.code === 'EADDRNOTAVAIL') return this.skip(); - - done(err); - }); - }); - }); - - wss.on('connection', (ws, req) => { - assert.strictEqual(req.connection.remoteAddress, '127.0.0.2'); - wss.close(done); - }); - }); - - it('accepts the `family` option', function (done) { - const re = process.platform === 'win32' ? /Loopback Pseudo-Interface/ : /lo/; - const ifaces = os.networkInterfaces(); - const hasIPv6 = Object.keys(ifaces).some((name) => { - return re.test(name) && ifaces[name].some((info) => info.family === 'IPv6'); - }); - - // - // Skip this test on machines where IPv6 is not supported. - // - if (!hasIPv6) return this.skip(); - - dns.lookup('localhost', { family: 6, all: true }, (err, addresses) => { - // - // Skip this test if localhost does not resolve to ::1. - // - if (err) { - return err.code === 'ENOTFOUND' || err.code === 'EAI_AGAIN' - ? this.skip() - : done(err); - } - - if (!addresses.some((val) => val.address === '::1')) return this.skip(); - - const wss = new WebSocket.Server({ host: '::1', port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { - family: 6 - }); - }); - - wss.on('connection', (ws, req) => { - assert.strictEqual(req.connection.remoteAddress, '::1'); - wss.close(done); - }); - }); - }); }); }); From 7c74567b974b79253ac32aac8f7d3dcb3e6d17e8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 19 Mar 2018 18:23:31 +0100 Subject: [PATCH 504/669] [dist] 5.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f0bb08c11..e4cbc3a39 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "5.0.0", + "version": "5.1.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 83ce53489617d6ae68cf0f4ec314103be0160a5f Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 30 Mar 2018 14:59:08 +0200 Subject: [PATCH 505/669] chore(package): update eslint-plugin-import to version 2.10.0 (#1340) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e4cbc3a39..f23ca7fdb 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "bufferutil": "~3.0.0", "eslint": "~4.19.0", "eslint-config-standard": "~11.0.0", - "eslint-plugin-import": "~2.9.0", + "eslint-plugin-import": "~2.10.0", "eslint-plugin-node": "~6.0.0", "eslint-plugin-promise": "~3.7.0", "eslint-plugin-standard": "~3.0.0", From f52debe945f412f22f4fe27b9c1c9a9bfb82accd Mon Sep 17 00:00:00 2001 From: An-Li Ting Date: Fri, 30 Mar 2018 22:18:42 +0800 Subject: [PATCH 506/669] [doc] Explain the behavior of `server.close()` more precisely (#1342) --- doc/ws.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index 6f1546bb2..f8643122f 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -139,7 +139,9 @@ returned as a string. ### server.close([callback]) -Close the server and terminate all clients, calls callback when done. +Close the HTTP server if created internally, terminate all clients and call +callback when done. If an external HTTP server is used via the `server` or +`noServer` constructor options, it must be closed manually. ### server.handleUpgrade(request, socket, head, callback) From 2d7bf88eafdf4bce81c16edd35c811d6a2a87743 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 2 Apr 2018 14:50:34 +0200 Subject: [PATCH 507/669] [perf] Use js version of `{un,}mask()` for very small frames (#1348) --- lib/buffer-util.js | 81 ++++++++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/lib/buffer-util.js b/lib/buffer-util.js index 38e16df3f..6974dd6a8 100644 --- a/lib/buffer-util.js +++ b/lib/buffer-util.js @@ -8,7 +8,7 @@ * @return {Buffer} The resulting buffer * @public */ -const concat = (list, totalLength) => { +function concat (list, totalLength) { const target = Buffer.allocUnsafe(totalLength); var offset = 0; @@ -19,43 +19,54 @@ const concat = (list, totalLength) => { } return target; -}; +} + +/** + * Masks a buffer using the given mask. + * + * @param {Buffer} source The buffer to mask + * @param {Buffer} mask The mask to use + * @param {Buffer} output The buffer where to store the result + * @param {Number} offset The offset at which to start writing + * @param {Number} length The number of bytes to mask. + * @public + */ +function _mask (source, mask, output, offset, length) { + for (var i = 0; i < length; i++) { + output[offset + i] = source[i] ^ mask[i & 3]; + } +} + +/** + * Unmasks a buffer using the given mask. + * + * @param {Buffer} buffer The buffer to unmask + * @param {Buffer} mask The mask to use + * @public + */ +function _unmask (buffer, mask) { + // Required until https://github.com/nodejs/node/issues/9006 is resolved. + const length = buffer.length; + for (var i = 0; i < length; i++) { + buffer[i] ^= mask[i & 3]; + } +} try { const bufferUtil = require('bufferutil'); + const bu = bufferUtil.BufferUtil || bufferUtil; - module.exports = Object.assign({ concat }, bufferUtil.BufferUtil || bufferUtil); -} catch (e) /* istanbul ignore next */ { - /** - * Masks a buffer using the given mask. - * - * @param {Buffer} source The buffer to mask - * @param {Buffer} mask The mask to use - * @param {Buffer} output The buffer where to store the result - * @param {Number} offset The offset at which to start writing - * @param {Number} length The number of bytes to mask. - * @public - */ - const mask = (source, mask, output, offset, length) => { - for (var i = 0; i < length; i++) { - output[offset + i] = source[i] ^ mask[i & 3]; - } - }; - - /** - * Unmasks a buffer using the given mask. - * - * @param {Buffer} buffer The buffer to unmask - * @param {Buffer} mask The mask to use - * @public - */ - const unmask = (buffer, mask) => { - // Required until https://github.com/nodejs/node/issues/9006 is resolved. - const length = buffer.length; - for (var i = 0; i < length; i++) { - buffer[i] ^= mask[i & 3]; - } + module.exports = { + mask (source, mask, output, offset, length) { + if (length < 48) _mask(source, mask, output, offset, length); + else bu.mask(source, mask, output, offset, length); + }, + unmask (buffer, mask) { + if (buffer.length < 32) _unmask(buffer, mask); + else bu.unmask(buffer, mask); + }, + concat }; - - module.exports = { concat, mask, unmask }; +} catch (e) /* istanbul ignore next */ { + module.exports = { concat, mask: _mask, unmask: _unmask }; } From f335d7994142ecf61b3ad325752c82a8e26fa763 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 2 Apr 2018 15:07:10 +0200 Subject: [PATCH 508/669] [fix] Add default value for `servername` option (#1347) Fixes #1346 --- lib/websocket.js | 5 ++++- test/websocket.test.js | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index c603675a9..67cd8a49f 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -465,7 +465,9 @@ function initAsClient (address, protocols, options) { options.createConnection = isSecure ? tlsConnect : netConnect; options.port = parsedUrl.port || (isSecure ? 443 : 80); - options.host = parsedUrl.hostname; + options.host = parsedUrl.hostname.startsWith('[') + ? parsedUrl.hostname.slice(1, -1) + : parsedUrl.hostname; options.headers = Object.assign({ 'Sec-WebSocket-Version': options.protocolVersion, 'Sec-WebSocket-Key': key, @@ -622,6 +624,7 @@ function netConnect (options) { */ function tlsConnect (options) { options.path = options.socketPath || options._socketPath || undefined; + options.servername = options.servername || options.host; return tls.connect(options); } diff --git a/test/websocket.test.js b/test/websocket.test.js index a33187480..2807e59fb 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -41,12 +41,13 @@ describe('WebSocket', function () { const agent = new CustomAgent(); - agent.addRequest = (req) => { + agent.addRequest = (req, opts) => { + assert.strictEqual(opts.host, '::1'); assert.strictEqual(req.path, '/'); done(); }; - const ws = new WebSocket(new url.URL('ws://localhost'), { agent }); + const ws = new WebSocket(new url.URL('ws://[::1]'), { agent }); }); describe('options', function () { From 10c92fff16c53be18c7be05c8a4c65d25cae8088 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 2 Apr 2018 15:17:31 +0200 Subject: [PATCH 509/669] [dist] 5.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f23ca7fdb..ba787ba90 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "5.1.0", + "version": "5.1.1", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From c801e991ae52fdaaeabe0dd647db41f0f831e6d1 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 8 Apr 2018 21:43:54 +0200 Subject: [PATCH 510/669] [doc] Improve docs and examples (#1355) Fixes #1334 Fixes #1338 --- README.md | 106 ++++++++++++++++++++++++++++++++++-------------------- doc/ws.md | 14 +++++--- 2 files changed, 76 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 8f94ca303..e35732673 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,10 @@ one of the many wrappers available on npm, like * [Usage examples](#usage-examples) + [Sending and receiving text data](#sending-and-receiving-text-data) + [Sending binary data](#sending-binary-data) - + [Server example](#server-example) - + [Broadcast example](#broadcast-example) - + [ExpressJS example](#expressjs-example) + + [Simple server](#simple-server) + + [External HTTP/S server](#external-https-server) + + [Multiple servers sharing a single HTTP/S server](#multiple-servers-sharing-a-single-https-server) + + [Server broadcast](#server-broadcast) + [echo.websocket.org demo](#echowebsocketorg-demo) + [Other examples](#other-examples) * [Error handling best practices](#error-handling-best-practices) @@ -169,7 +170,7 @@ ws.on('open', function open() { }); ``` -### Server example +### Simple server ```js const WebSocket = require('ws'); @@ -185,7 +186,68 @@ wss.on('connection', function connection(ws) { }); ``` -### Broadcast example +### External HTTP/S server + +```js +const fs = require('fs'); +const https = require('https'); +const WebSocket = require('ws'); + +const server = new https.createServer({ + cert: fs.readFileSync('/path/to/cert.pem'), + key: fs.readFileSync('/path/to/key.pem') +}); +const wss = new WebSocket.Server({ server }); + +wss.on('connection', function connection(ws) { + ws.on('message', function incoming(message) { + console.log('received: %s', message); + }); + + ws.send('something'); +}); + +server.listen(8080); +``` + +### Multiple servers sharing a single HTTP/S server + +```js +const http = require('http'); +const WebSocket = require('ws'); + +const server = http.createServer(); +const wss1 = new WebSocket.Server({ noServer: true }); +const wss2 = new WebSocket.Server({ noServer: true }); + +wss1.on('connection', function connection(ws) { + // ... +}); + +wss2.on('connection', function connection(ws) { + // ... +}); + +server.on('upgrade', function upgrade(request, socket, head) { + const pathname = url.parse(request.url).pathname; + + if (pathname === '/foo') { + wss1.handleUpgrade(request, socket, head, function done(ws) { + wss1.emit('connection', ws); + }); + } else if (pathname === '/bar') { + wss2.handleUpgrade(request, socket, head, function done(ws) { + wss2.emit('connection', ws); + }); + } else { + socket.destroy(); + } +}); + +server.listen(8080); +``` + +### Server broadcast ```js const WebSocket = require('ws'); @@ -213,40 +275,6 @@ wss.on('connection', function connection(ws) { }); ``` -### ExpressJS example - -```js -const express = require('express'); -const http = require('http'); -const url = require('url'); -const WebSocket = require('ws'); - -const app = express(); - -app.use(function (req, res) { - res.send({ msg: "hello" }); -}); - -const server = http.createServer(app); -const wss = new WebSocket.Server({ server }); - -wss.on('connection', function connection(ws, req) { - const location = url.parse(req.url, true); - // You might use location.query.access_token to authenticate or share sessions - // or req.headers.cookie (see http://stackoverflow.com/a/16395220/151312) - - ws.on('message', function incoming(message) { - console.log('received: %s', message); - }); - - ws.send('something'); -}); - -server.listen(8080, function listening() { - console.log('Listening on %d', server.address().port); -}); -``` - ### echo.websocket.org demo ```js diff --git a/doc/ws.md b/doc/ws.md index f8643122f..89c377da9 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -10,7 +10,7 @@ This class represents a WebSocket server. It extends the `EventEmitter`. - `host` {String} The hostname where to bind the server. - `port` {Number} The port where to bind the server. - `backlog` {Number} The maximum length of the queue of pending connections. - - `server` {http.Server|https.Server} A pre-created Node.js HTTP server. + - `server` {http.Server|https.Server} A pre-created Node.js HTTP/S server. - `verifyClient` {Function} A function which can be used to validate incoming connections. See description below. - `handleProtocols` {Function} A function which can be used to handle the @@ -23,7 +23,12 @@ This class represents a WebSocket server. It extends the `EventEmitter`. - `callback` {Function} Create a new server instance. One of `port`, `server` or `noServer` must be -provided or an error is thrown. +provided or an error is thrown. An HTTP server is automatically created, +started, and used if `port` is set. To use an external HTTP/S server instead, +specify only `server` or `noServer`. In this case the HTTP/S server must be +started manually. The "noServer" mode allows the WebSocket server to be +completly detached from the HTTP/S server. This makes it possible, for example, +to share a single HTTP/S server between multiple WebSocket servers. If `verifyClient` is not set then the handshake is automatically accepted. If @@ -91,9 +96,8 @@ When sending a fragmented message the length of the first fragment is compared to the threshold. This determines if compression is used for the entire message. -`callback` will be added as a listener for the `listening` event when the -HTTP server is created internally and that is when the `port` option is -provided. +`callback` will be added as a listener for the `listening` event on the HTTP +server when not operating in "noServer" mode. ### Event: 'connection' From 0100d82045125ef08b703ad0722822a3969cda37 Mon Sep 17 00:00:00 2001 From: Zoli Kahan Date: Wed, 11 Apr 2018 15:29:58 -0500 Subject: [PATCH 511/669] [doc] Improve FAQ example for X-Forwarded-For header (#1360) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e35732673..aea7ed721 100644 --- a/README.md +++ b/README.md @@ -351,7 +351,7 @@ the `X-Forwarded-For` header. ```js wss.on('connection', function connection(ws, req) { - const ip = req.headers['x-forwarded-for']; + const ip = req.headers['x-forwarded-for'].split(/\s*,\s*/)[0]; }); ``` From 3215cf3bf8bd754728b4a0bff070fe41d5788520 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 12 Apr 2018 17:03:30 +0200 Subject: [PATCH 512/669] chore(package): update eslint-plugin-import to version 2.11.0 (#1361) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ba787ba90..23f20eac9 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "bufferutil": "~3.0.0", "eslint": "~4.19.0", "eslint-config-standard": "~11.0.0", - "eslint-plugin-import": "~2.10.0", + "eslint-plugin-import": "~2.11.0", "eslint-plugin-node": "~6.0.0", "eslint-plugin-promise": "~3.7.0", "eslint-plugin-standard": "~3.0.0", From a81e580badd6141f23532c9a5f453a8ade8ba8fc Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 13 Apr 2018 07:26:52 +0200 Subject: [PATCH 513/669] chore(package): update mocha to version 5.1.0 (#1362) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 23f20eac9..d40eb2120 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "eslint-plugin-node": "~6.0.0", "eslint-plugin-promise": "~3.7.0", "eslint-plugin-standard": "~3.0.0", - "mocha": "~5.0.0", + "mocha": "~5.1.0", "nyc": "~11.6.0", "utf-8-validate": "~4.0.0" } From 9dc25a380d003142aad9bf1bbfce6fa5d1b5e81c Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 19 Apr 2018 11:00:24 +0200 Subject: [PATCH 514/669] chore(package): update nyc to version 11.7.1 (#1364) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d40eb2120..2c4890b9a 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "eslint-plugin-promise": "~3.7.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~5.1.0", - "nyc": "~11.6.0", + "nyc": "~11.7.1", "utf-8-validate": "~4.0.0" } } From 690b3f277c6f5c3aef8cd84792929450f516b3ae Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 23 Apr 2018 10:07:20 +0200 Subject: [PATCH 515/669] [minor] Replace bound function with arrow function Do not mask potential issues, like #1280, with bound arguments. --- lib/websocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/websocket.js b/lib/websocket.js index 67cd8a49f..2a2a33b30 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -523,7 +523,7 @@ function initAsClient (address, protocols, options) { if (options.handshakeTimeout) { req.setTimeout( options.handshakeTimeout, - abortHandshake.bind(null, this, req, 'Opening handshake has timed out') + () => abortHandshake(this, req, 'Opening handshake has timed out') ); } From 4385c7890a45ebc38df2404def4f648aa8ed228d Mon Sep 17 00:00:00 2001 From: Luke Avsec Date: Mon, 30 Apr 2018 01:34:55 -0400 Subject: [PATCH 516/669] [doc] Add `request` to emit arguments in shared server example (#1372) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aea7ed721..3fd9a8cc0 100644 --- a/README.md +++ b/README.md @@ -233,11 +233,11 @@ server.on('upgrade', function upgrade(request, socket, head) { if (pathname === '/foo') { wss1.handleUpgrade(request, socket, head, function done(ws) { - wss1.emit('connection', ws); + wss1.emit('connection', ws, request); }); } else if (pathname === '/bar') { wss2.handleUpgrade(request, socket, head, function done(ws) { - wss2.emit('connection', ws); + wss2.emit('connection', ws, request); }); } else { socket.destroy(); From 6d8f1f4d494c0470629680ffd77b18390c641668 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 5 May 2018 15:56:36 +0200 Subject: [PATCH 517/669] [ci] Test on node 10 --- .travis.yml | 1 + appveyor.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 668010445..0ec5464e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: node_js sudo: false node_js: + - "10" - "9" - "8" - "6" diff --git a/appveyor.yml b/appveyor.yml index ce931fe9f..f797a9607 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,6 @@ environment: matrix: + - nodejs_version: "10" - nodejs_version: "9" - nodejs_version: "8" - nodejs_version: "6" From bb9c21c1aff2949b648747825f1b482d389fd2ae Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 5 May 2018 17:09:59 +0200 Subject: [PATCH 518/669] [test] Fix failing test on node 10 --- test/permessage-deflate.test.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/permessage-deflate.test.js b/test/permessage-deflate.test.js index ab2a9ab11..3d51bd57e 100644 --- a/test/permessage-deflate.test.js +++ b/test/permessage-deflate.test.js @@ -527,17 +527,14 @@ describe('PerMessageDeflate', function () { it("doesn't call the callback twice when `maxPayload` is exceeded", function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }, false, 25); const buf = Buffer.from('A'.repeat(50)); - const errors = []; perMessageDeflate.accept([{}]); perMessageDeflate.compress(buf, true, (err, data) => { if (err) return done(err); - perMessageDeflate.decompress(data, true, (err) => errors.push(err)); - perMessageDeflate._inflate.flush(() => { - assert.strictEqual(errors.length, 1); - assert.ok(errors[0] instanceof RangeError); - assert.strictEqual(errors[0].message, 'Max payload size exceeded'); + perMessageDeflate.decompress(data, true, (err) => { + assert.ok(err instanceof RangeError); + assert.strictEqual(err.message, 'Max payload size exceeded'); done(); }); }); From d871bdfdc806122862ee5e2b781989b576771caf Mon Sep 17 00:00:00 2001 From: Dmitry Kirilyuk Date: Sat, 12 May 2018 22:42:31 +0300 Subject: [PATCH 519/669] [feature] Add `headers` argument to `verifyClient()` callback (#1379) Add ability to specify custom headers when rejecting the handshake. --- doc/ws.md | 2 ++ lib/websocket-server.js | 21 ++++++++++++++------- test/websocket-server.test.js | 31 ++++++++++++++++++++++++++++++- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 89c377da9..47d8abeda 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -53,6 +53,8 @@ if `verifyClient` is provided with two arguments then those are: error status code to be sent to the client. - `name` {String} When `result` is `false` this field determines the HTTP reason phrase. + - `headers` {Object} When `result` is `false` this field determines additional + HTTP headers to be sent to the client. For example, `{ 'Retry-After': 120 }`. `handleProtocols` takes two arguments: diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 72618d028..70513edf2 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -204,8 +204,10 @@ class WebSocketServer extends EventEmitter { }; if (this.options.verifyClient.length === 2) { - this.options.verifyClient(info, (verified, code, message) => { - if (!verified) return abortHandshake(socket, code || 401, message); + this.options.verifyClient(info, (verified, code, message, headers) => { + if (!verified) { + return abortHandshake(socket, code || 401, message, headers); + } this.completeUpgrade(extensions, req, socket, head, cb); }); @@ -330,17 +332,22 @@ function socketOnError () { * @param {net.Socket} socket The socket of the upgrade request * @param {Number} code The HTTP response status code * @param {String} [message] The HTTP response body + * @param {Object} [headers] Additional HTTP response headers * @private */ -function abortHandshake (socket, code, message) { +function abortHandshake (socket, code, message, headers) { if (socket.writable) { message = message || http.STATUS_CODES[code]; + headers = Object.assign({ + 'Connection': 'close', + 'Content-type': 'text/html', + 'Content-Length': Buffer.byteLength(message) + }, headers); + socket.write( `HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` + - 'Connection: close\r\n' + - 'Content-type: text/html\r\n' + - `Content-Length: ${Buffer.byteLength(message)}\r\n` + - '\r\n' + + Object.keys(headers).map(h => `${h}: ${headers[h]}`).join('\r\n') + + '\r\n\r\n' + message ); } diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 402cd827b..45d287978 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -551,7 +551,7 @@ describe('WebSocketServer', function () { }); }); - it('can reject client asynchronously with status code', function (done) { + it('can reject client asynchronously w/ status code', function (done) { const wss = new WebSocket.Server({ verifyClient: (info, cb) => process.nextTick(cb, false, 404), port: 0 @@ -576,6 +576,35 @@ describe('WebSocketServer', function () { done(new Error("Unexpected 'connection' event")); }); }); + + it('can reject client asynchronously w/ custom headers', function (done) { + const wss = new WebSocket.Server({ + verifyClient: (info, cb) => { + process.nextTick(cb, false, 503, '', { 'Retry-After': 120 }); + }, + port: 0 + }, () => { + const req = http.get({ + port: wss.address().port, + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8 + } + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 503); + assert.strictEqual(res.headers['retry-after'], '120'); + wss.close(done); + }); + }); + + wss.on('connection', (ws) => { + done(new Error("Unexpected 'connection' event")); + }); + }); }); it("doesn't emit the 'connection' event if socket is closed prematurely", function (done) { From aebda2bce3c0e0216f2ef79d192d2e79ffaee29b Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 16 May 2018 07:21:49 +0200 Subject: [PATCH 520/669] chore(package): update nyc to version 11.8.0 (#1382) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2c4890b9a..3bb326e57 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "eslint-plugin-promise": "~3.7.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~5.1.0", - "nyc": "~11.7.1", + "nyc": "~11.8.0", "utf-8-validate": "~4.0.0" } } From 6dae94bc65f9aa6acf21891ba0d46457d5d095a7 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 17 May 2018 14:39:30 +0200 Subject: [PATCH 521/669] chore(package): update eslint-plugin-import to version 2.12.0 (#1384) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3bb326e57..12d7b9a26 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "bufferutil": "~3.0.0", "eslint": "~4.19.0", "eslint-config-standard": "~11.0.0", - "eslint-plugin-import": "~2.11.0", + "eslint-plugin-import": "~2.12.0", "eslint-plugin-node": "~6.0.0", "eslint-plugin-promise": "~3.7.0", "eslint-plugin-standard": "~3.0.0", From e7bfe5f13895701cab8492a6ed7872ec6b724da2 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 19 May 2018 07:29:18 +0200 Subject: [PATCH 522/669] chore(package): update mocha to version 5.2.0 (#1385) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 12d7b9a26..bfaafd80e 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "eslint-plugin-node": "~6.0.0", "eslint-plugin-promise": "~3.7.0", "eslint-plugin-standard": "~3.0.0", - "mocha": "~5.1.0", + "mocha": "~5.2.0", "nyc": "~11.8.0", "utf-8-validate": "~4.0.0" } From e4d032c383dd2931de7dfbe81a0a8185cdffbf52 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 21 May 2018 21:11:33 +0200 Subject: [PATCH 523/669] [dist] 5.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bfaafd80e..af5a01146 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "5.1.1", + "version": "5.2.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From bcab5310beb5a07f62fff92105d4183b0e011252 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 25 May 2018 07:23:05 +0200 Subject: [PATCH 524/669] chore(package): update eslint-plugin-promise to version 3.8.0 (#1389) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af5a01146..f799b2d57 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "eslint-config-standard": "~11.0.0", "eslint-plugin-import": "~2.12.0", "eslint-plugin-node": "~6.0.0", - "eslint-plugin-promise": "~3.7.0", + "eslint-plugin-promise": "~3.8.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~5.2.0", "nyc": "~11.8.0", From bf9b2ececbe42dd07ef9619d2b4953f57243c843 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 4 Jun 2018 07:49:18 +0200 Subject: [PATCH 525/669] chore(package): update nyc to version 12.0.2 (#1395) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f799b2d57..8f1171860 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "eslint-plugin-promise": "~3.8.0", "eslint-plugin-standard": "~3.0.0", "mocha": "~5.2.0", - "nyc": "~11.8.0", + "nyc": "~12.0.2", "utf-8-validate": "~4.0.0" } } From 6046a2873944793b01457488aed8062ccaa53743 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 19 Jun 2018 22:38:36 +0200 Subject: [PATCH 526/669] [fix] Do not prematurely remove the listener of the `'data'` event --- lib/websocket.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 2a2a33b30..4d7c4f15e 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -750,7 +750,6 @@ function socketOnClose () { const websocket = this[kWebSocket]; this.removeListener('close', socketOnClose); - this.removeListener('data', socketOnData); this.removeListener('end', socketOnEnd); this[kWebSocket] = undefined; @@ -760,11 +759,16 @@ function socketOnClose () { // The close frame might not have been received or the `'end'` event emitted, // for example, if the socket was destroyed due to an error. Ensure that the // `receiver` stream is closed after writing any remaining buffered data to - // it. + // it. If the readable side of the socket is in flowing mode then there is no + // buffered data as everything has been already written and `readable.read()` + // will return `null`. If instead, the socket is paused, any possible buffered + // data will be read as a single chunk and emitted synchronously in a single + // `'data'` event. // websocket._socket.read(); websocket._receiver.end(); + this.removeListener('data', socketOnData); clearTimeout(websocket._closeTimer); if ( From 307be7a81ee1c08cbd1e2acf2ea98fbf26ae390d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 23 Jun 2018 17:35:16 +0200 Subject: [PATCH 527/669] [fix] Remove the `'data'` listener when the receiver emits an error --- lib/websocket.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/websocket.js b/lib/websocket.js index 4d7c4f15e..643124f20 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -693,6 +693,8 @@ function receiverOnDrain () { function receiverOnError (err) { const websocket = this[kWebSocket]; + websocket._socket.removeListener('data', socketOnData); + websocket.readyState = WebSocket.CLOSING; websocket._closeCode = err[constants.kStatusCode]; websocket.emit('error', err); From 175ce4605b80d610e558c858b0f8d74599a16db1 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 23 Jun 2018 18:01:12 +0200 Subject: [PATCH 528/669] [dist] 5.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8f1171860..3a35a499d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "5.2.0", + "version": "5.2.1", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 5d90141505dc41c129ab5d5228e37d49979d7541 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 24 Jun 2018 21:17:21 +0200 Subject: [PATCH 529/669] chore(package): update eslint-plugin-import to version 2.13.0 (#1405) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3a35a499d..889f1efaf 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "bufferutil": "~3.0.0", "eslint": "~4.19.0", "eslint-config-standard": "~11.0.0", - "eslint-plugin-import": "~2.12.0", + "eslint-plugin-import": "~2.13.0", "eslint-plugin-node": "~6.0.0", "eslint-plugin-promise": "~3.8.0", "eslint-plugin-standard": "~3.0.0", From d73885c3f7c70b583030d683d9a0a025c98fbe00 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 7 Jul 2018 18:24:12 +0200 Subject: [PATCH 530/669] [major] Drop support for Node.js 4 --- .travis.yml | 2 -- appveyor.yml | 2 -- 2 files changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0ec5464e1..4b1d501b4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,7 @@ language: node_js sudo: false node_js: - "10" - - "9" - "8" - "6" - - "4" after_success: - "npm install coveralls@3 && nyc report --reporter=text-lcov | coveralls" diff --git a/appveyor.yml b/appveyor.yml index f797a9607..eba878327 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,10 +1,8 @@ environment: matrix: - nodejs_version: "10" - - nodejs_version: "9" - nodejs_version: "8" - nodejs_version: "6" - - nodejs_version: "4" platform: - x86 - x64 From 3bc6b9672f60b7178f115e51beaf4f664f91484c Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 7 Jul 2018 18:28:38 +0200 Subject: [PATCH 531/669] chore(package): update eslint to version 5.0.0 (#1403) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 889f1efaf..4ce71fffb 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~3.0.0", - "eslint": "~4.19.0", + "eslint": "~5.0.0", "eslint-config-standard": "~11.0.0", "eslint-plugin-import": "~2.13.0", "eslint-plugin-node": "~6.0.0", From 5bb29ed019529f17a557d59b3eb5d9a14f9e5643 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 7 Jul 2018 18:31:48 +0200 Subject: [PATCH 532/669] chore(package): update utf-8-validate to version 5.0.0 (#1415) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4ce71fffb..87eeb145e 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,6 @@ "eslint-plugin-standard": "~3.0.0", "mocha": "~5.2.0", "nyc": "~12.0.2", - "utf-8-validate": "~4.0.0" + "utf-8-validate": "~5.0.0" } } From 72bfbe84f3d747b96416a646728932c9181ceffd Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 7 Jul 2018 18:32:47 +0200 Subject: [PATCH 533/669] chore(package): update bufferutil to version 4.0.0 (#1413) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 87eeb145e..0f27f3123 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ }, "devDependencies": { "benchmark": "~2.1.2", - "bufferutil": "~3.0.0", + "bufferutil": "~4.0.0", "eslint": "~5.0.0", "eslint-config-standard": "~11.0.0", "eslint-plugin-import": "~2.13.0", From 9f87842888688318464af498300395b197b29712 Mon Sep 17 00:00:00 2001 From: Huan LI Date: Sun, 8 Jul 2018 04:30:01 +0800 Subject: [PATCH 534/669] [major] Make bundlers use a browser shim that throws an error (#1345) --- browser.js | 8 ++++++++ package.json | 2 ++ 2 files changed, 10 insertions(+) create mode 100644 browser.js diff --git a/browser.js b/browser.js new file mode 100644 index 000000000..ca4f628ac --- /dev/null +++ b/browser.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = function () { + throw new Error( + 'ws does not work in the browser. Browser clients must use the native ' + + 'WebSocket object' + ); +}; diff --git a/package.json b/package.json index 0f27f3123..314e8b11c 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,9 @@ "author": "Einar Otto Stangvik (http://2x.io)", "license": "MIT", "main": "index.js", + "browser": "browser.js", "files": [ + "browser.js", "index.js", "lib" ], From 92d0a2e9fc2b1cca6eb1ddf88ec4347986164cb1 Mon Sep 17 00:00:00 2001 From: Ban44n Date: Sat, 7 Jul 2018 22:44:11 +0200 Subject: [PATCH 535/669] [major] Add `maxPayload` option for the client (#1402) --- bench/speed.js | 4 +++- doc/ws.md | 1 + lib/websocket.js | 9 ++++++--- test/websocket.test.js | 22 ++++++++++++++++++++++ 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/bench/speed.js b/bench/speed.js index e5d1591dd..608dc2782 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -54,7 +54,9 @@ if (cluster.isMaster) { const runConfig = (useBinary, roundtrips, size, cb) => { const data = randomBytes.slice(0, size); - const ws = new WebSocket(`ws://localhost:${port}`); + const ws = new WebSocket(`ws://localhost:${port}`, { + maxPayload: 600 * 1024 * 1024 + }); var roundtrip = 0; var time; diff --git a/doc/ws.md b/doc/ws.md index 47d8abeda..1a19229ff 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -199,6 +199,7 @@ This class represents a WebSocket. It extends the `EventEmitter`. - `protocolVersion` {Number} Value of the `Sec-WebSocket-Version` header. - `origin` {String} Value of the `Origin` or `Sec-WebSocket-Origin` header depending on the `protocolVersion`. + - `maxPayload` {Number} The maximum allowed message size in bytes. - Any other option allowed in [http.request()][] or [https.request()][]. `perMessageDeflate` default value is `true`. When using an object, parameters diff --git a/lib/websocket.js b/lib/websocket.js index 643124f20..6be8e0656 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -411,12 +411,14 @@ module.exports = WebSocket; * @param {Number} options.handshakeTimeout Timeout in milliseconds for the handshake request * @param {Number} options.protocolVersion Value of the `Sec-WebSocket-Version` header * @param {String} options.origin Value of the `Origin` or `Sec-WebSocket-Origin` header + * @param {Number} options.maxPayload The maximum allowed message size * @private */ function initAsClient (address, protocols, options) { options = Object.assign({ protocolVersion: protocolVersions[1], - perMessageDeflate: true + perMessageDeflate: true, + maxPayload: 100 * 1024 * 1024 }, options, { createConnection: undefined, socketPath: undefined, @@ -479,7 +481,8 @@ function initAsClient (address, protocols, options) { if (options.perMessageDeflate) { perMessageDeflate = new PerMessageDeflate( options.perMessageDeflate !== true ? options.perMessageDeflate : {}, - false + false, + options.maxPayload ); options.headers['Sec-WebSocket-Extensions'] = extension.format({ [PerMessageDeflate.extensionName]: perMessageDeflate.offer() @@ -599,7 +602,7 @@ function initAsClient (address, protocols, options) { } } - this.setSocket(socket, head, 0); + this.setSocket(socket, head, options.maxPayload); }); } diff --git a/test/websocket.test.js b/test/websocket.test.js index 2807e59fb..23aa79ebf 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -65,6 +65,28 @@ describe('WebSocket', function () { assert.strictEqual(count, 3); }); + it('accepts the `maxPayload` option', function (done) { + const maxPayload = 20480; + const wss = new WebSocket.Server({ + perMessageDeflate: true, + port: 0 + }, () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { + perMessageDeflate: true, + maxPayload + }); + + ws.on('open', () => { + assert.strictEqual(ws._receiver._maxPayload, maxPayload); + assert.strictEqual( + ws._receiver._extensions['permessage-deflate']._maxPayload, + maxPayload + ); + wss.close(done); + }); + }); + }); + it('throws an error when using an invalid `protocolVersion`', function () { const options = { agent: new CustomAgent(), protocolVersion: 1000 }; From 80e20021f314d66e80032ecb8c2854ac61c6073c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 7 Jul 2018 22:53:40 +0200 Subject: [PATCH 536/669] [major] Drop support for the `memLevel` and `level` options --- lib/permessage-deflate.js | 10 +--------- test/permessage-deflate.test.js | 13 ++++++++++--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/permessage-deflate.js b/lib/permessage-deflate.js index 0d54f035b..d4ec149c6 100644 --- a/lib/permessage-deflate.js +++ b/lib/permessage-deflate.js @@ -413,15 +413,7 @@ class PerMessageDeflate { : this.params[key]; this._deflate = zlib.createDeflateRaw( - Object.assign( - // TODO deprecate memLevel/level and recommend zlibDeflateOptions instead - { - memLevel: this._options.memLevel, - level: this._options.level - }, - this._options.zlibDeflateOptions, - { windowBits } - ) + Object.assign({}, this._options.zlibDeflateOptions, { windowBits }) ); this._deflate[kTotalLength] = 0; diff --git a/test/permessage-deflate.test.js b/test/permessage-deflate.test.js index 3d51bd57e..15562431f 100644 --- a/test/permessage-deflate.test.js +++ b/test/permessage-deflate.test.js @@ -349,8 +349,14 @@ describe('PerMessageDeflate', function () { }); it('honors the `level` option', function (done) { - const lev0 = new PerMessageDeflate({ threshold: 0, level: 0 }); - const lev9 = new PerMessageDeflate({ threshold: 0, level: 9 }); + const lev0 = new PerMessageDeflate({ + threshold: 0, + zlibDeflateOptions: { level: 0 } + }); + const lev9 = new PerMessageDeflate({ + threshold: 0, + zlibDeflateOptions: { level: 9 } + }); const extensionStr = ( 'permessage-deflate; server_no_context_takeover; ' + 'client_no_context_takeover; server_max_window_bits=10; ' + @@ -409,7 +415,8 @@ describe('PerMessageDeflate', function () { } }); - // Note no context takeover so we can get a hold of the raw streams after we do the dance + // Note no context takeover so we can get a hold of the raw streams after + // we do the dance. const extensionStr = ( 'permessage-deflate; server_max_window_bits=10; ' + 'client_max_window_bits=11' From ef5a8f5f5e4d3843d318d2b8a463b49d8105bd2e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 8 Jul 2018 07:32:02 +0200 Subject: [PATCH 537/669] [pkg] Update eslint-plugin-standard to version 3.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 314e8b11c..8bf7ba81a 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "eslint-plugin-import": "~2.13.0", "eslint-plugin-node": "~6.0.0", "eslint-plugin-promise": "~3.8.0", - "eslint-plugin-standard": "~3.0.0", + "eslint-plugin-standard": "~3.1.0", "mocha": "~5.2.0", "nyc": "~12.0.2", "utf-8-validate": "~5.0.0" From b354cd138e62dcb1e4e05ce8b0bc097534f23d76 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 8 Jul 2018 07:47:36 +0200 Subject: [PATCH 538/669] [minor] Remove no longer needed workaround for `socketPath` option --- lib/websocket.js | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 6be8e0656..02c4d1a68 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -507,17 +507,7 @@ function initAsClient (address, protocols, options) { if (isUnixSocket) { const parts = path.split(':'); - if (options.agent == null && process.versions.modules < 57) { - // - // Setting `socketPath` in conjunction with `createConnection` without an - // agent throws an error on Node.js < 8. Work around the issue by using a - // different property. - // - options._socketPath = parts[0]; - } else { - options.socketPath = parts[0]; - } - + options.socketPath = parts[0]; options.path = parts[1]; } @@ -614,7 +604,7 @@ function initAsClient (address, protocols, options) { * @private */ function netConnect (options) { - options.path = options.socketPath || options._socketPath || undefined; + options.path = options.socketPath || undefined; return net.connect(options); } @@ -626,7 +616,7 @@ function netConnect (options) { * @private */ function tlsConnect (options) { - options.path = options.socketPath || options._socketPath || undefined; + options.path = options.socketPath || undefined; options.servername = options.servername || options.host; return tls.connect(options); } From fbd43914ad557ed915c41108b2f98b508a720216 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 8 Jul 2018 11:45:48 +0200 Subject: [PATCH 539/669] [fix] Fix compatibility with Node.js 6 --- lib/websocket.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 02c4d1a68..b3df34788 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -604,7 +604,13 @@ function initAsClient (address, protocols, options) { * @private */ function netConnect (options) { - options.path = options.socketPath || undefined; + // + // Override `options.path` only if `options` is a copy of the original options + // object. This is always true on Node.js >= 8 but not on Node.js 6 where + // `options.socketPath` might be `undefined` even if the `socketPath` option + // was originally set. + // + if (options.protocolVersion) options.path = options.socketPath; return net.connect(options); } @@ -616,7 +622,7 @@ function netConnect (options) { * @private */ function tlsConnect (options) { - options.path = options.socketPath || undefined; + options.path = undefined; options.servername = options.servername || options.host; return tls.connect(options); } From fc957939460cd0c461618bef4c6d59a9b5a4b90e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 11 Jul 2018 21:19:59 +0200 Subject: [PATCH 540/669] [fix] Fix use after invalidation bug Fixes #1418 --- lib/websocket.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/websocket.js b/lib/websocket.js index b3df34788..92cb0f891 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -752,7 +752,6 @@ function socketOnClose () { this.removeListener('close', socketOnClose); this.removeListener('end', socketOnEnd); - this[kWebSocket] = undefined; websocket.readyState = WebSocket.CLOSING; @@ -770,6 +769,8 @@ function socketOnClose () { websocket._receiver.end(); this.removeListener('data', socketOnData); + this[kWebSocket] = undefined; + clearTimeout(websocket._closeTimer); if ( From 38d2e8b3f75ebccf8b0b205fafac959c1702ddfb Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 17 Jul 2018 14:16:27 +0200 Subject: [PATCH 541/669] chore(package): update eslint-plugin-node to version 7.0.0 (#1420) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8bf7ba81a..25963bbae 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "eslint": "~5.0.0", "eslint-config-standard": "~11.0.0", "eslint-plugin-import": "~2.13.0", - "eslint-plugin-node": "~6.0.0", + "eslint-plugin-node": "~7.0.0", "eslint-plugin-promise": "~3.8.0", "eslint-plugin-standard": "~3.1.0", "mocha": "~5.2.0", From d963003a40bfe6cdd58eb3d3e4458eb2b2090a2c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 20 Jul 2018 07:43:50 +0200 Subject: [PATCH 542/669] [example] Update dependencies --- examples/express-session-parse/package.json | 6 +++--- examples/fileapi/package.json | 2 +- examples/serverstats/package.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/express-session-parse/package.json b/examples/express-session-parse/package.json index cf96cbc49..5390e7e0a 100644 --- a/examples/express-session-parse/package.json +++ b/examples/express-session-parse/package.json @@ -4,8 +4,8 @@ "version": "0.0.0", "repository": "websockets/ws", "dependencies": { - "express": "~4.14.1", - "express-session": "~1.15.1", - "uuid": "~3.0.1" + "express": "~4.16.3", + "express-session": "~1.15.6", + "uuid": "~3.3.2" } } diff --git a/examples/fileapi/package.json b/examples/fileapi/package.json index 1341f59e9..770b5ebc1 100644 --- a/examples/fileapi/package.json +++ b/examples/fileapi/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "repository": "websockets/ws", "dependencies": { - "express": "~4.14.0", + "express": "~4.16.3", "ansi": "https://github.com/einaros/ansi.js/tarball/master" } } diff --git a/examples/serverstats/package.json b/examples/serverstats/package.json index 321049ab5..dbc86aff0 100644 --- a/examples/serverstats/package.json +++ b/examples/serverstats/package.json @@ -4,6 +4,6 @@ "version": "0.0.0", "repository": "websockets/ws", "dependencies": { - "express": "~4.14.0" + "express": "~4.16.3" } } From 1ee42fd67d365409096c11af0d6bc70fbe292c60 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 21 Jul 2018 15:32:50 +0200 Subject: [PATCH 543/669] [dist] 6.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 25963bbae..4d84269f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "5.2.1", + "version": "6.0.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 07d337a0fb06cd05fb139954d64f23ff2a60e50f Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 18 Aug 2018 07:32:35 +0200 Subject: [PATCH 544/669] chore(package): update eslint-plugin-promise to version 4.0.0 (#1432) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4d84269f2..b09fb0c70 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "eslint-config-standard": "~11.0.0", "eslint-plugin-import": "~2.13.0", "eslint-plugin-node": "~7.0.0", - "eslint-plugin-promise": "~3.8.0", + "eslint-plugin-promise": "~4.0.0", "eslint-plugin-standard": "~3.1.0", "mocha": "~5.2.0", "nyc": "~12.0.2", From 2bb93fbc6ee536fe65a053746149a736d60af2c5 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 18 Aug 2018 10:13:57 +0200 Subject: [PATCH 545/669] [pkg] Update eslint to version 5.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b09fb0c70..168ae140a 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~4.0.0", - "eslint": "~5.0.0", + "eslint": "~5.4.0", "eslint-config-standard": "~11.0.0", "eslint-plugin-import": "~2.13.0", "eslint-plugin-node": "~7.0.0", From 1e78aba9b9b29407517ec7ba1da97427c1678e5c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 18 Aug 2018 10:16:34 +0200 Subject: [PATCH 546/669] [pkg] Update eslint-plugin-import to version 2.14.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 168ae140a..231d9881d 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "bufferutil": "~4.0.0", "eslint": "~5.4.0", "eslint-config-standard": "~11.0.0", - "eslint-plugin-import": "~2.13.0", + "eslint-plugin-import": "~2.14.0", "eslint-plugin-node": "~7.0.0", "eslint-plugin-promise": "~4.0.0", "eslint-plugin-standard": "~3.1.0", From 30335cbece34f9c9e230269209ef3d427f2454f5 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 29 Aug 2018 07:39:26 +0200 Subject: [PATCH 547/669] chore(package): update eslint-plugin-standard to version 4.0.0 (#1439) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 231d9881d..5dfafac07 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "eslint-plugin-import": "~2.14.0", "eslint-plugin-node": "~7.0.0", "eslint-plugin-promise": "~4.0.0", - "eslint-plugin-standard": "~3.1.0", + "eslint-plugin-standard": "~4.0.0", "mocha": "~5.2.0", "nyc": "~12.0.2", "utf-8-validate": "~5.0.0" From 19a0225062a7f60a0e0775cd31d09707c33d9d84 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 29 Aug 2018 10:01:06 +0200 Subject: [PATCH 548/669] [lint] Add missing spaces --- examples/fileapi/server.js | 8 ++++---- examples/serverstats/server.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/fileapi/server.js b/examples/fileapi/server.js index 54e6128f8..80fc1d5ae 100644 --- a/examples/fileapi/server.js +++ b/examples/fileapi/server.js @@ -46,7 +46,7 @@ cursor.eraseData(2).goto(1, 1); app.use(express.static(path.join(__dirname, '/public'))); var clientId = 0; -var wss = new WebSocketServer({server: server}); +var wss = new WebSocketServer({ server: server }); wss.on('connection', function (ws) { var thisId = ++clientId; cursor.goto(1, 4 + thisId).eraseLine(); @@ -69,18 +69,18 @@ wss.on('connection', function (ws) { makePathForFile(currentFile.path, path.join(__dirname, '/uploaded'), function (error, path) { if (error) { console.log(error); - ws.send(JSON.stringify({event: 'error', path: currentFile.path, message: error.message})); + ws.send(JSON.stringify({ event: 'error', path: currentFile.path, message: error.message })); return; } fs.writeFile(path + '/' + currentFile.name, data, function (error) { if (error) { console.log(error); - ws.send(JSON.stringify({event: 'error', path: currentFile.path, message: error.message})); + ws.send(JSON.stringify({ event: 'error', path: currentFile.path, message: error.message })); return; } ++filesReceived; // console.log('received %d bytes long file, %s', data.length, currentFile.path); - ws.send(JSON.stringify({event: 'complete', path: currentFile.path})); + ws.send(JSON.stringify({ event: 'complete', path: currentFile.path })); currentFile = null; }); }); diff --git a/examples/serverstats/server.js b/examples/serverstats/server.js index b1e167806..e0d108cbd 100644 --- a/examples/serverstats/server.js +++ b/examples/serverstats/server.js @@ -6,7 +6,7 @@ var server = require('http').createServer(); app.use(express.static(path.join(__dirname, '/public'))); -var wss = new WebSocketServer({server: server}); +var wss = new WebSocketServer({ server: server }); wss.on('connection', function (ws) { var id = setInterval(function () { ws.send(JSON.stringify(process.memoryUsage()), function () { /* ignore errors */ }); From 7d7ddfd2e2e010bbdba6acfd9b8fb0f0ab79c951 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 29 Aug 2018 10:04:23 +0200 Subject: [PATCH 549/669] chore(package): update eslint-config-standard to version 12.0.0 (#1440) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5dfafac07..1b9146e1b 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "benchmark": "~2.1.2", "bufferutil": "~4.0.0", "eslint": "~5.4.0", - "eslint-config-standard": "~11.0.0", + "eslint-config-standard": "~12.0.0", "eslint-plugin-import": "~2.14.0", "eslint-plugin-node": "~7.0.0", "eslint-plugin-promise": "~4.0.0", From 26b65daebb54796e3a2ca81baf53605b244f7405 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 1 Oct 2018 10:18:40 +0200 Subject: [PATCH 550/669] [pkg] Update eslint to version 5.6.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1b9146e1b..963b51e35 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~4.0.0", - "eslint": "~5.4.0", + "eslint": "~5.6.1", "eslint-config-standard": "~12.0.0", "eslint-plugin-import": "~2.14.0", "eslint-plugin-node": "~7.0.0", From 6de05e07df9ca1d5ab5023b1fe82836896517778 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 1 Oct 2018 10:19:29 +0200 Subject: [PATCH 551/669] [pkg] Update nyc to version 13.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 963b51e35..825fd45d0 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "eslint-plugin-promise": "~4.0.0", "eslint-plugin-standard": "~4.0.0", "mocha": "~5.2.0", - "nyc": "~12.0.2", + "nyc": "~13.0.1", "utf-8-validate": "~5.0.0" } } From 90407bbf3476b379673b69ae440db44c906d60d8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 4 Oct 2018 07:49:18 +0200 Subject: [PATCH 552/669] [feature] Make `WebSocketServer#close()` emit `'close'` (#1453) Fixes #1375 --- doc/ws.md | 6 ++++++ lib/websocket-server.js | 19 +++++++++++++++++-- test/websocket-server.test.js | 9 ++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 1a19229ff..5c36068b3 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -101,6 +101,12 @@ to the threshold. This determines if compression is used for the entire message. `callback` will be added as a listener for the `listening` event on the HTTP server when not operating in "noServer" mode. +### Event: 'close' + +Emitted when the server closes. This event depends on the `'close'` event of +HTTP server only when it is created internally. In all other cases, the event +is emitted independently. + ### Event: 'connection' - `socket` {WebSocket} diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 70513edf2..709f38852 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -112,6 +112,8 @@ class WebSocketServer extends EventEmitter { * @public */ close (cb) { + if (cb) this.once('close', cb); + // // Terminate all associated clients. // @@ -128,10 +130,13 @@ class WebSocketServer extends EventEmitter { // // Close the http server if it was internally created. // - if (this.options.port != null) return server.close(cb); + if (this.options.port != null) { + server.close(() => this.emit('close')); + return; + } } - if (cb) cb(); + process.nextTick(emitClose, this); } /** @@ -317,6 +322,16 @@ function addListeners (server, map) { }; } +/** + * Emit a `'close'` event on an `EventEmitter`. + * + * @param {EventEmitter} server The event emitter + * @private + */ +function emitClose (server) { + server.emit('close'); +} + /** * Handle premature socket errors. * diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 45d287978..46af9f10b 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -230,6 +230,13 @@ describe('WebSocketServer', function () { }); }); }); + + it("emits the 'close' event", function (done) { + const wss = new WebSocket.Server({ noServer: true }); + + wss.on('close', done); + wss.close(); + }); }); describe('#clients', function () { @@ -689,7 +696,7 @@ describe('WebSocketServer', function () { }); }); - it('emits the `headers` event', function (done) { + it("emits the 'headers' event", function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); From 9fe2f3393d5efa02ba198ad7fffeca7dfb232512 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 4 Oct 2018 21:08:14 +0200 Subject: [PATCH 553/669] [test] Fix failing test on Windows --- test/websocket.test.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/websocket.test.js b/test/websocket.test.js index 23aa79ebf..25bdc8f8e 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -206,14 +206,18 @@ describe('WebSocket', function () { }); wss.on('connection', (ws) => { + const data = Buffer.alloc(1024, 61); + while (true) { if (ws._socket.bufferSize > 0) { assert.strictEqual(ws.bufferedAmount, ws._socket.bufferSize); break; } - ws.send('hello'.repeat(1e4)); + ws.send(data); } - wss.close(done); + + ws.on('close', () => wss.close(done)); + ws.close(); }); }); }); From b9ce38d80f847a843c05edfe1a907278bcffde0c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 5 Oct 2018 08:41:08 +0200 Subject: [PATCH 554/669] [dist] 6.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 825fd45d0..5a4bc74a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "6.0.0", + "version": "6.1.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 9022a0d2fa2b2fe7a8d0f6ad49dceade03a0ccb9 Mon Sep 17 00:00:00 2001 From: Muhammad Umer Date: Sun, 7 Oct 2018 12:48:39 -0400 Subject: [PATCH 555/669] [doc] Remove `clientMaxWindowBits` option from README example (#1454) --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 3fd9a8cc0..7de49c27d 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,6 @@ const wss = new WebSocket.Server({ // Other options settable: clientNoContextTakeover: true, // Defaults to negotiated value. serverNoContextTakeover: true, // Defaults to negotiated value. - clientMaxWindowBits: 10, // Defaults to negotiated value. serverMaxWindowBits: 10, // Defaults to negotiated value. // Below options specified as default values. concurrencyLimit: 10, // Limits zlib concurrency for perf. From d2317b1d8dda7d36f1ba952f533ff45e2464ae52 Mon Sep 17 00:00:00 2001 From: Adrian Hope-Bailie Date: Thu, 11 Oct 2018 09:33:58 +0200 Subject: [PATCH 556/669] [benchmark] Add Unix domain sockets to bench (#1456) --- bench/speed.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/bench/speed.js b/bench/speed.js index 608dc2782..34d455935 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -1,24 +1,33 @@ 'use strict'; const cluster = require('cluster'); +const http = require('http'); const WebSocket = require('..'); const port = 8181; +const path = ''; +// const path = '/tmp/wss.sock'; if (cluster.isMaster) { + const server = http.createServer(); const wss = new WebSocket.Server({ maxPayload: 600 * 1024 * 1024, perMessageDeflate: false, clientTracking: false, - port - }, () => cluster.fork()); + server + }); wss.on('connection', (ws) => { ws.on('message', (data) => ws.send(data)); }); - cluster.on('exit', () => wss.close()); + server.listen(path ? { path } : { port }, () => cluster.fork()); + + cluster.on('exit', () => { + wss.close(); + server.close(); + }); } else { const configs = [ [true, 10000, 64], @@ -52,9 +61,12 @@ if (cluster.isMaster) { randomBytes[i] = ~~(Math.random() * 127); } + console.log(`Testing ws on ${path || '[::]:' + port}`); + const runConfig = (useBinary, roundtrips, size, cb) => { const data = randomBytes.slice(0, size); - const ws = new WebSocket(`ws://localhost:${port}`, { + const url = path ? `ws+unix://${path}` : `ws://localhost:${port}`; + const ws = new WebSocket(url, { maxPayload: 600 * 1024 * 1024 }); var roundtrip = 0; From 0da3fdb506dd8a8a3ae2837873cd9878fe1f6965 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 29 Oct 2018 21:48:16 +0100 Subject: [PATCH 557/669] [minor] Do not use the legacy URL API --- lib/websocket-server.js | 8 +++++--- lib/websocket.js | 4 ++-- test/websocket.test.js | 5 +++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 709f38852..f24cb484c 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -3,7 +3,6 @@ const EventEmitter = require('events'); const crypto = require('crypto'); const http = require('http'); -const url = require('url'); const PerMessageDeflate = require('./permessage-deflate'); const extension = require('./extension'); @@ -147,8 +146,11 @@ class WebSocketServer extends EventEmitter { * @public */ shouldHandle (req) { - if (this.options.path && url.parse(req.url).pathname !== this.options.path) { - return false; + if (this.options.path) { + const index = req.url.indexOf('?'); + const pathname = index !== -1 ? req.url.slice(0, index) : req.url; + + if (pathname !== this.options.path) return false; } return true; diff --git a/lib/websocket.js b/lib/websocket.js index 92cb0f891..5b8497d68 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -6,7 +6,7 @@ const https = require('https'); const http = require('http'); const net = require('net'); const tls = require('tls'); -const url = require('url'); +const { URL } = require('url'); const PerMessageDeflate = require('./permessage-deflate'); const EventTarget = require('./event-target'); @@ -447,7 +447,7 @@ function initAsClient (address, protocols, options) { parsedUrl = address; this.url = address.href; } else { - parsedUrl = url.parse(address); + parsedUrl = new URL(address); this.url = address; } diff --git a/test/websocket.test.js b/test/websocket.test.js index 25bdc8f8e..f33feb206 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -20,8 +20,8 @@ describe('WebSocket', function () { describe('#ctor', function () { it('throws an error when using an invalid url', function () { assert.throws( - () => new WebSocket('echo.websocket.org'), - /^Error: Invalid URL: echo\.websocket\.org$/ + () => new WebSocket('ws+unix:'), + /^Error: Invalid URL: ws\+unix:$/ ); }); @@ -33,6 +33,7 @@ describe('WebSocket', function () { done(); }; + // eslint-disable-next-line node/no-deprecated-api const ws = new WebSocket(url.parse('ws://localhost'), { agent }); }); From cddbcf6667e3ead4636b30120477742a430b6454 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 29 Oct 2018 21:56:17 +0100 Subject: [PATCH 558/669] [test] Remove unused variables --- test/websocket-server.test.js | 32 ++++++++++++++++---------------- test/websocket.test.js | 8 ++++---- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 46af9f10b..3b1805f1c 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -1,4 +1,4 @@ -/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^ws$", "args": "none" }] */ +/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^ws$" }] */ 'use strict'; @@ -66,7 +66,7 @@ describe('WebSocketServer', function () { const ws = new WebSocket(`ws://localhost:${port}`); }); - wss.on('connection', (client) => wss.close(done)); + wss.on('connection', () => wss.close(done)); }); it('binds the server on any IPv6 address when available', function (done) { @@ -83,7 +83,7 @@ describe('WebSocketServer', function () { const wss = new WebSocket.Server({ server }); const ws = new WebSocket(`ws://localhost:${server.address().port}`); - wss.on('connection', (client) => { + wss.on('connection', () => { wss.close(); server.close(done); }); @@ -199,7 +199,7 @@ describe('WebSocketServer', function () { const wss = new WebSocket.Server({ server }); - wss.on('connection', (ws) => { + wss.on('connection', () => { wss.close(); server.close = realClose; server.close(done); @@ -246,7 +246,7 @@ describe('WebSocketServer', function () { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); }); - wss.on('connection', (ws) => { + wss.on('connection', () => { assert.strictEqual(wss.clients.size, 1); wss.close(done); }); @@ -387,7 +387,7 @@ describe('WebSocketServer', function () { }); }); - wss.on('connection', (ws) => { + wss.on('connection', () => { done(new Error("Unexpected 'connection' event")); }); }); @@ -409,7 +409,7 @@ describe('WebSocketServer', function () { }); }); - wss.on('connection', (ws) => { + wss.on('connection', () => { done(new Error("Unexpected 'connection' event")); }); }); @@ -432,7 +432,7 @@ describe('WebSocketServer', function () { }); }); - wss.on('connection', (ws) => { + wss.on('connection', () => { done(new Error("Unexpected 'connection' event")); }); }); @@ -460,7 +460,7 @@ describe('WebSocketServer', function () { }); }); - wss.on('connection', (ws) => { + wss.on('connection', () => { done(new Error("Unexpected 'connection' event")); }); }); @@ -468,7 +468,7 @@ describe('WebSocketServer', function () { describe('`verifyClient`', function () { it('can reject client synchronously', function (done) { const wss = new WebSocket.Server({ - verifyClient: (info) => false, + verifyClient: () => false, port: 0 }, () => { const req = http.get({ @@ -487,7 +487,7 @@ describe('WebSocketServer', function () { }); }); - wss.on('connection', (ws) => { + wss.on('connection', () => { done(new Error("Unexpected 'connection' event")); }); }); @@ -508,7 +508,7 @@ describe('WebSocketServer', function () { server }); - wss.on('connection', (ws) => { + wss.on('connection', () => { wss.close(); server.close(done); }); @@ -529,7 +529,7 @@ describe('WebSocketServer', function () { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); }); - wss.on('connection', (ws) => wss.close(done)); + wss.on('connection', () => wss.close(done)); }); it('can reject client asynchronously', function (done) { @@ -553,7 +553,7 @@ describe('WebSocketServer', function () { }); }); - wss.on('connection', (ws) => { + wss.on('connection', () => { done(new Error("Unexpected 'connection' event")); }); }); @@ -579,7 +579,7 @@ describe('WebSocketServer', function () { }); }); - wss.on('connection', (ws) => { + wss.on('connection', () => { done(new Error("Unexpected 'connection' event")); }); }); @@ -608,7 +608,7 @@ describe('WebSocketServer', function () { }); }); - wss.on('connection', (ws) => { + wss.on('connection', () => { done(new Error("Unexpected 'connection' event")); }); }); diff --git a/test/websocket.test.js b/test/websocket.test.js index f33feb206..7383e4109 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -1,4 +1,4 @@ -/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^ws$", "args": "none" }] */ +/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^ws$" }] */ 'use strict'; @@ -57,7 +57,7 @@ describe('WebSocket', function () { let count = 0; let ws; - agent.addRequest = (req) => count++; + agent.addRequest = () => count++; ws = new WebSocket('ws://localhost', undefined, { agent }); ws = new WebSocket('ws://localhost', null, { agent }); @@ -1643,7 +1643,7 @@ describe('WebSocket', function () { }); const wss = new WebSocket.Server({ server }); - wss.on('connection', (ws) => { + wss.on('connection', () => { wss.close(); server.close(done); }); @@ -1672,7 +1672,7 @@ describe('WebSocket', function () { server }); - wss.on('connection', (ws) => { + wss.on('connection', () => { assert.ok(success); server.close(done); wss.close(); From 45f817bdb1edaeb673ae5b0aeda69fc9621b27fc Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 29 Oct 2018 22:01:32 +0100 Subject: [PATCH 559/669] chore(package): update eslint-plugin-node to version 8.0.0 (#1466) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5a4bc74a8..1b9d4b6ab 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "eslint": "~5.6.1", "eslint-config-standard": "~12.0.0", "eslint-plugin-import": "~2.14.0", - "eslint-plugin-node": "~7.0.0", + "eslint-plugin-node": "~8.0.0", "eslint-plugin-promise": "~4.0.0", "eslint-plugin-standard": "~4.0.0", "mocha": "~5.2.0", From bc0f8ab4efd0afe2d7dbedc0a41e9bd0581a1e41 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 29 Oct 2018 22:03:17 +0100 Subject: [PATCH 560/669] [pkg] Update eslint to version 5.8.0 --- lib/extension.js | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/extension.js b/lib/extension.js index 3f48d7517..a9b847282 100644 --- a/lib/extension.js +++ b/lib/extension.js @@ -64,7 +64,7 @@ function parse (header) { if (extensionName === undefined) { if (end === -1 && tokenChars[code] === 1) { if (start === -1) start = i; - } else if (code === 0x20/* ' ' */|| code === 0x09/* '\t' */) { + } else if (code === 0x20/* ' ' */ || code === 0x09/* '\t' */) { if (end === -1 && start !== -1) end = i; } else if (code === 0x3b/* ';' */ || code === 0x2c/* ',' */) { if (start === -1) { @@ -103,7 +103,7 @@ function parse (header) { } start = end = -1; - } else if (code === 0x3d/* '=' */&& start !== -1 && end === -1) { + } else if (code === 0x3d/* '=' */ && start !== -1 && end === -1) { paramName = header.slice(start, i); start = end = -1; } else { diff --git a/package.json b/package.json index 1b9d4b6ab..7cf1061f9 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.2", "bufferutil": "~4.0.0", - "eslint": "~5.6.1", + "eslint": "~5.8.0", "eslint-config-standard": "~12.0.0", "eslint-plugin-import": "~2.14.0", "eslint-plugin-node": "~8.0.0", From 7e061bce4c78affbd9f30a4ffeadb8fb669d9b01 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 29 Oct 2018 22:03:58 +0100 Subject: [PATCH 561/669] [pkg] Update nyc to version 13.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7cf1061f9..884b74191 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "eslint-plugin-promise": "~4.0.0", "eslint-plugin-standard": "~4.0.0", "mocha": "~5.2.0", - "nyc": "~13.0.1", + "nyc": "~13.1.0", "utf-8-validate": "~5.0.0" } } From 1ebff19864acd1232536c2389e2507421b7d4c16 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 4 Nov 2018 09:26:41 +0100 Subject: [PATCH 562/669] [ci] Test on node 11 --- .travis.yml | 1 + appveyor.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4b1d501b4..ebb063d65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: node_js sudo: false node_js: + - "11" - "10" - "8" - "6" diff --git a/appveyor.yml b/appveyor.yml index eba878327..917169ad0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,6 @@ environment: matrix: + - nodejs_version: "11" - nodejs_version: "10" - nodejs_version: "8" - nodejs_version: "6" From 7d51fb9686733d7bfcc77a820d9e378c197c96ae Mon Sep 17 00:00:00 2001 From: Brandon Fulljames Date: Mon, 5 Nov 2018 22:48:37 -0800 Subject: [PATCH 563/669] [fix] Do not waste time compressing when socket is closed (#1464) --- lib/sender.js | 14 ++++++ lib/websocket.js | 5 +++ test/sender.test.js | 98 +++++++++++++++++++++++++++++++++--------- test/websocket.test.js | 34 +++++++++++++++ 4 files changed, 131 insertions(+), 20 deletions(-) diff --git a/lib/sender.js b/lib/sender.js index 060e55392..500bd98cd 100644 --- a/lib/sender.js +++ b/lib/sender.js @@ -27,6 +27,20 @@ class Sender { this._bufferedBytes = 0; this._deflating = false; this._queue = []; + + if (this._extensions[PerMessageDeflate.extensionName]) { + this._socket.once('close', () => { + const err = new Error('WebSocket is not open: readyState 2 (CLOSING)'); + + while (this._queue.length) { + const params = this._queue.shift(); + const cb = params[params.length - 1]; + + this._bufferedBytes -= params[1].length; + if (typeof cb === 'function') cb(err); + } + }); + } } /** diff --git a/lib/websocket.js b/lib/websocket.js index 5b8497d68..79cb219f3 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -123,6 +123,11 @@ class WebSocket extends EventEmitter { maxPayload ); + // + // `Sender` must be instantiated before adding the `socketOnClose` listener. + // This allows the sender queue, when used, to be emptied before + // `socketOnClose` is called. + // this._sender = new Sender(socket, this._extensions); this._receiver = receiver; this._socket = socket; diff --git a/test/sender.test.js b/test/sender.test.js index 623ce5954..e7c7dd104 100644 --- a/test/sender.test.js +++ b/test/sender.test.js @@ -5,6 +5,16 @@ const assert = require('assert'); const PerMessageDeflate = require('../lib/permessage-deflate'); const Sender = require('../lib/sender'); +class MockSocket { + constructor ({ write, once } = {}) { + if (write) this.write = write; + if (once) this.once = once; + } + + write () {} + once () {} +} + describe('Sender', function () { describe('.frame', function () { it('does not mutate the input buffer if data is `readOnly`', function () { @@ -38,12 +48,13 @@ describe('Sender', function () { it('compresses data if compress option is enabled', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); let count = 0; - const sender = new Sender({ + const mockSocket = new MockSocket({ write: (data) => { assert.strictEqual(data[0] & 0x40, 0x40); if (++count === 3) done(); } - }, { + }); + const sender = new Sender(mockSocket, { 'permessage-deflate': perMessageDeflate }); @@ -57,14 +68,53 @@ describe('Sender', function () { sender.send('hi', options); }); + it('does not compress enqueued messages after socket closes', function (done) { + const numMessages = 100; + let numErrors = 0; + + const mockSocket = new MockSocket({ + write: (data) => { + // Test that `PerMessageDeflate#compress()` and `Socket#write()` is + // called only once (for the first message that is not queued). + assert.strictEqual(numErrors, numMessages); + done(); + }, + once: (ev, cb) => { + if (ev === 'close') process.nextTick(cb); + } + }); + + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + perMessageDeflate.accept([{}]); + + const sender = new Sender(mockSocket, { + 'permessage-deflate': perMessageDeflate + }); + + const options = { compress: true, fin: true }; + sender.send('hi', options); + + for (let i = 0; i < numMessages; i++) { + sender.send('hi', options, (err) => { + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'WebSocket is not open: readyState 2 (CLOSING)' + ); + numErrors++; + }); + } + }); + it('does not compress data for small payloads', function (done) { const perMessageDeflate = new PerMessageDeflate(); - const sender = new Sender({ + const mockSocket = new MockSocket({ write: (data) => { assert.notStrictEqual(data[0] & 0x40, 0x40); done(); } - }, { + }); + const sender = new Sender(mockSocket, { 'permessage-deflate': perMessageDeflate }); @@ -76,7 +126,7 @@ describe('Sender', function () { it('compresses all frames in a fragmented message', function (done) { const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); - const sender = new Sender({ + const mockSocket = new MockSocket({ write: (data) => { fragments.push(data); if (fragments.length !== 2) return; @@ -87,7 +137,8 @@ describe('Sender', function () { assert.strictEqual(fragments[1].length, 6); done(); } - }, { + }); + const sender = new Sender(mockSocket, { 'permessage-deflate': perMessageDeflate }); @@ -100,7 +151,7 @@ describe('Sender', function () { it('compresses no frames in a fragmented message', function (done) { const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); - const sender = new Sender({ + const mockSocket = new MockSocket({ write: (data) => { fragments.push(data); if (fragments.length !== 2) return; @@ -111,7 +162,8 @@ describe('Sender', function () { assert.strictEqual(fragments[1].length, 5); done(); } - }, { + }); + const sender = new Sender(mockSocket, { 'permessage-deflate': perMessageDeflate }); @@ -124,7 +176,7 @@ describe('Sender', function () { it('compresses empty buffer as first fragment', function (done) { const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); - const sender = new Sender({ + const mockSocket = new MockSocket({ write: (data) => { fragments.push(data); if (fragments.length !== 2) return; @@ -135,7 +187,8 @@ describe('Sender', function () { assert.strictEqual(fragments[1].length, 8); done(); } - }, { + }); + const sender = new Sender(mockSocket, { 'permessage-deflate': perMessageDeflate }); @@ -148,7 +201,7 @@ describe('Sender', function () { it('compresses empty buffer as last fragment', function (done) { const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); - const sender = new Sender({ + const mockSocket = new MockSocket({ write: (data) => { fragments.push(data); if (fragments.length !== 2) return; @@ -159,7 +212,8 @@ describe('Sender', function () { assert.strictEqual(fragments[1].length, 3); done(); } - }, { + }); + const sender = new Sender(mockSocket, { 'permessage-deflate': perMessageDeflate }); @@ -172,11 +226,12 @@ describe('Sender', function () { it('handles many send calls while processing without crashing on flush', function (done) { let count = 0; const perMessageDeflate = new PerMessageDeflate(); - const sender = new Sender({ + const mockSocket = new MockSocket({ write: () => { if (++count > 1e4) done(); } - }, { + }); + const sender = new Sender(mockSocket, { 'permessage-deflate': perMessageDeflate }); @@ -196,14 +251,15 @@ describe('Sender', function () { it('works with multiple types of data', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); let count = 0; - const sender = new Sender({ + const mockSocket = new MockSocket({ write: (data) => { if (++count === 1) return; assert.ok(data.equals(Buffer.from([0x89, 0x02, 0x68, 0x69]))); if (count === 4) done(); } - }, { + }); + const sender = new Sender(mockSocket, { 'permessage-deflate': perMessageDeflate }); @@ -222,14 +278,15 @@ describe('Sender', function () { it('works with multiple types of data', function (done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); let count = 0; - const sender = new Sender({ + const mockSocket = new MockSocket({ write: (data) => { if (++count === 1) return; assert.ok(data.equals(Buffer.from([0x8a, 0x02, 0x68, 0x69]))); if (count === 4) done(); } - }, { + }); + const sender = new Sender(mockSocket, { 'permessage-deflate': perMessageDeflate }); @@ -249,12 +306,13 @@ describe('Sender', function () { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); let count = 0; - const sender = new Sender({ + const mockSocket = new MockSocket({ write: (data, cb) => { count++; if (cb) cb(); } - }, { + }); + const sender = new Sender(mockSocket, { 'permessage-deflate': perMessageDeflate }); diff --git a/test/websocket.test.js b/test/websocket.test.js index 7383e4109..dc9458657 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -2041,6 +2041,40 @@ describe('WebSocket', function () { }); }); + it('reports the state as `CLOSING` in callbacks of discarded queued messages', function (done) { + const wss = new WebSocket.Server({ + perMessageDeflate: { threshold: 0 }, + port: 0 + }, () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + }); + + wss.on('connection', (ws) => { + const map = () => crypto.randomBytes(16); + let count = 100; + + Array.from({ length: count }).map(map).forEach((data, i) => { + ws.send(data, (err) => { + assert.ok(err instanceof Error); + + if (i > 0) { + // The first message is not queued and compression completes after + // the `'close'` event is emitted on the `net.Socket`. + assert.strictEqual(ws.readyState, WebSocket.CLOSING); + assert.strictEqual( + err.message, + 'WebSocket is not open: readyState 2 (CLOSING)' + ); + } + + if (--count === 0) done(); + }); + }); + + wss.close(); + }); + }); + describe('#send', function () { it('ignores the `compress` option if the extension is disabled', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { From 3fa0e03cc6a134d0132733a5a738c88deaa9b974 Mon Sep 17 00:00:00 2001 From: Manix Date: Thu, 8 Nov 2018 12:08:38 +0200 Subject: [PATCH 564/669] [doc] Suggest implementation of heartbeat on the client (#1469) --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 7de49c27d..1825676d4 100644 --- a/README.md +++ b/README.md @@ -392,6 +392,31 @@ const interval = setInterval(function ping() { Pong messages are automatically sent in response to ping messages as required by the spec. +Just like the server example above your clients might as well lose connection +without knowing it. You might want to add a ping listener on your clients to +prevent that. A simple implementation would be: + +```js +function heartbeat() { + clearTimeout(this.pingTimeout); + + // Use `WebSocket#terminate()` and not `WebSocket#close()`. Delay should be + // equal to the interval at which your server sends out pings plus a + // conservative assumption of the latency. + this.pingTimeout = setTimeout(() => { + this.terminate(); + }, 30000 + 100); +} + +const client = new WebSocket(url); + +client.on('open', heartbeat); +client.on('ping', heartbeat); +client.on('close', function clear() { + clearTimeout(this.pingTimeout); +}); +``` + ### How to connect via a proxy? Use a custom `http.Agent` implementation like [https-proxy-agent][] or From 591420614766c747f06b39e5c5600a1d99f27bce Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 9 Nov 2018 07:36:00 +0100 Subject: [PATCH 565/669] [doc] Fix nits --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1825676d4..cae947caa 100644 --- a/README.md +++ b/README.md @@ -366,14 +366,14 @@ endpoint is still responsive. ```js const WebSocket = require('ws'); -const wss = new WebSocket.Server({ port: 8080 }); - function noop() {} function heartbeat() { this.isAlive = true; } +const wss = new WebSocket.Server({ port: 8080 }); + wss.on('connection', function connection(ws) { ws.isAlive = true; ws.on('pong', heartbeat); @@ -397,6 +397,8 @@ without knowing it. You might want to add a ping listener on your clients to prevent that. A simple implementation would be: ```js +const WebSocket = require('ws'); + function heartbeat() { clearTimeout(this.pingTimeout); @@ -405,10 +407,10 @@ function heartbeat() { // conservative assumption of the latency. this.pingTimeout = setTimeout(() => { this.terminate(); - }, 30000 + 100); + }, 30000 + 1000); } -const client = new WebSocket(url); +const client = new WebSocket('wss://echo.websocket.org/'); client.on('open', heartbeat); client.on('ping', heartbeat); From f26fac817e670a59138f4dd9c1f45c823246b528 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 14 Nov 2018 08:01:18 +0100 Subject: [PATCH 566/669] [minor] Ignore callbacks when clearing the send queue (#1471) Do not invoke callbacks when clearing the send queue due to premature socket closure. Refs: https://github.com/websockets/ws/pull/1464#issuecomment-435578571 Fixes #1226 --- lib/sender.js | 26 +++++++++----------- lib/websocket.js | 5 ---- test/sender.test.js | 55 ++++++++++++++++++++++-------------------- test/websocket.test.js | 45 ++++++---------------------------- 4 files changed, 47 insertions(+), 84 deletions(-) diff --git a/lib/sender.js b/lib/sender.js index 500bd98cd..12ee25a36 100644 --- a/lib/sender.js +++ b/lib/sender.js @@ -27,20 +27,6 @@ class Sender { this._bufferedBytes = 0; this._deflating = false; this._queue = []; - - if (this._extensions[PerMessageDeflate.extensionName]) { - this._socket.once('close', () => { - const err = new Error('WebSocket is not open: readyState 2 (CLOSING)'); - - while (this._queue.length) { - const params = this._queue.shift(); - const cb = params[params.length - 1]; - - this._bufferedBytes -= params[1].length; - if (typeof cb === 'function') cb(err); - } - }); - } } /** @@ -346,9 +332,19 @@ class Sender { this._deflating = true; perMessageDeflate.compress(data, options.fin, (_, buf) => { + this._deflating = false; + + if (!this._socket.readable && !this._socket.writable) { + // + // The socket is closed. Clear the queue and bail out. + // + this._bufferedBytes = 0; + this._queue.length = 0; + return; + } + options.readOnly = false; this.sendFrame(Sender.frame(buf, options), cb); - this._deflating = false; this.dequeue(); }); } diff --git a/lib/websocket.js b/lib/websocket.js index 79cb219f3..5b8497d68 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -123,11 +123,6 @@ class WebSocket extends EventEmitter { maxPayload ); - // - // `Sender` must be instantiated before adding the `socketOnClose` listener. - // This allows the sender queue, when used, to be emptied before - // `socketOnClose` is called. - // this._sender = new Sender(socket, this._extensions); this._receiver = receiver; this._socket = socket; diff --git a/test/sender.test.js b/test/sender.test.js index e7c7dd104..044075275 100644 --- a/test/sender.test.js +++ b/test/sender.test.js @@ -6,13 +6,14 @@ const PerMessageDeflate = require('../lib/permessage-deflate'); const Sender = require('../lib/sender'); class MockSocket { - constructor ({ write, once } = {}) { + constructor ({ write } = {}) { + this.readable = true; + this.writable = true; + if (write) this.write = write; - if (once) this.once = once; } write () {} - once () {} } describe('Sender', function () { @@ -69,41 +70,43 @@ describe('Sender', function () { }); it('does not compress enqueued messages after socket closes', function (done) { - const numMessages = 100; - let numErrors = 0; - const mockSocket = new MockSocket({ - write: (data) => { - // Test that `PerMessageDeflate#compress()` and `Socket#write()` is - // called only once (for the first message that is not queued). - assert.strictEqual(numErrors, numMessages); - done(); - }, - once: (ev, cb) => { - if (ev === 'close') process.nextTick(cb); - } + write: () => done(new Error('Unexpected call to socket.write()')) }); const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); perMessageDeflate.accept([{}]); + const compress = perMessageDeflate.compress; const sender = new Sender(mockSocket, { 'permessage-deflate': perMessageDeflate }); - const options = { compress: true, fin: true }; - sender.send('hi', options); + perMessageDeflate.compress = (data, fin, callback) => { + compress.call(perMessageDeflate, data, fin, (_, buf) => { + assert.strictEqual(sender._bufferedBytes, 198); + assert.strictEqual(sender._queue.length, 99); + assert.strictEqual(mockSocket.readable, false); + assert.strictEqual(mockSocket.writable, false); - for (let i = 0; i < numMessages; i++) { - sender.send('hi', options, (err) => { - assert.ok(err instanceof Error); - assert.strictEqual( - err.message, - 'WebSocket is not open: readyState 2 (CLOSING)' - ); - numErrors++; + process.nextTick(() => { + assert.strictEqual(sender._bufferedBytes, 0); + assert.strictEqual(sender._queue.length, 0); + done(); + }); + + callback(_, buf); }); - } + }; + + const options = { compress: true, fin: true }; + + for (let i = 0; i < 100; i++) sender.send('hi', options); + + process.nextTick(() => { + mockSocket.readable = false; + mockSocket.writable = false; + }); }); it('does not compress data for small payloads', function (done) { diff --git a/test/websocket.test.js b/test/websocket.test.js index dc9458657..c849ade6c 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -2041,40 +2041,6 @@ describe('WebSocket', function () { }); }); - it('reports the state as `CLOSING` in callbacks of discarded queued messages', function (done) { - const wss = new WebSocket.Server({ - perMessageDeflate: { threshold: 0 }, - port: 0 - }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - }); - - wss.on('connection', (ws) => { - const map = () => crypto.randomBytes(16); - let count = 100; - - Array.from({ length: count }).map(map).forEach((data, i) => { - ws.send(data, (err) => { - assert.ok(err instanceof Error); - - if (i > 0) { - // The first message is not queued and compression completes after - // the `'close'` event is emitted on the `net.Socket`. - assert.strictEqual(ws.readyState, WebSocket.CLOSING); - assert.strictEqual( - err.message, - 'WebSocket is not open: readyState 2 (CLOSING)' - ); - } - - if (--count === 0) done(); - }); - }); - - wss.close(); - }); - }); - describe('#send', function () { it('ignores the `compress` option if the extension is disabled', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { @@ -2106,13 +2072,16 @@ describe('WebSocket', function () { }); ws.on('open', () => { - ws.send('hi', (err) => { - assert.ok(err instanceof Error); - wss.close(done); - }); + ws.send('hi', () => done(new Error('Unexpected callback invocation'))); ws.terminate(); }); }); + + wss.on('connection', (ws) => { + ws.on('close', () => { + wss.close(done); + }); + }); }); it('can be used while data is being decompressed', function (done) { From 95bf991ca5cec7964d80387edb67db3cb132c2ee Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 16 Nov 2018 09:06:20 +0100 Subject: [PATCH 567/669] [pkg] Update dev dependencies --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 884b74191..580714a98 100644 --- a/package.json +++ b/package.json @@ -31,13 +31,13 @@ "async-limiter": "~1.0.0" }, "devDependencies": { - "benchmark": "~2.1.2", + "benchmark": "~2.1.4", "bufferutil": "~4.0.0", - "eslint": "~5.8.0", + "eslint": "~5.9.0", "eslint-config-standard": "~12.0.0", "eslint-plugin-import": "~2.14.0", "eslint-plugin-node": "~8.0.0", - "eslint-plugin-promise": "~4.0.0", + "eslint-plugin-promise": "~4.0.1", "eslint-plugin-standard": "~4.0.0", "mocha": "~5.2.0", "nyc": "~13.1.0", From b213bee0651d0ca519f12c19d384a7adc187aa95 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 16 Nov 2018 09:25:25 +0100 Subject: [PATCH 568/669] [pkg] Update list of published files Refs: https://github.com/npm/npm-packlist/pull/14 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 580714a98..20822e5fc 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "files": [ "browser.js", "index.js", - "lib" + "lib/*.js" ], "scripts": { "test": "eslint . && nyc --reporter=html --reporter=text mocha test/*.test.js", From 029de0cc87bc33da460b81321713f04af2d98c56 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 17 Nov 2018 07:59:37 +0100 Subject: [PATCH 569/669] [dist] 6.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 20822e5fc..b3a7af865 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "6.1.0", + "version": "6.1.1", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From b9fad73f53c786bffc831e4cc7740da83b82f23b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 17 Nov 2018 09:39:58 +0100 Subject: [PATCH 570/669] [lint] Use prettier --- .eslintignore | 2 - .eslintrc.yaml | 18 +- .gitignore | 1 - .prettierrc.yaml | 4 + ISSUE_TEMPLATE.md | 11 +- README.md | 116 ++-- SECURITY.md | 18 +- bench/parser.benchmark.js | 9 +- bench/speed.js | 14 +- browser.js | 2 +- doc/ws.md | 123 +++-- examples/fileapi/public/app.js | 16 +- examples/fileapi/public/uploader.js | 12 +- examples/fileapi/server.js | 84 ++- examples/serverstats/server.js | 12 +- examples/ssl.js | 8 +- lib/buffer-util.js | 10 +- lib/event-target.js | 22 +- lib/extension.js | 49 +- lib/permessage-deflate.js | 56 +- lib/receiver.js | 39 +- lib/sender.js | 101 ++-- lib/validation.js | 7 +- lib/websocket-server.js | 95 ++-- lib/websocket.js | 159 +++--- package.json | 15 +- test/autobahn-server.js | 4 +- test/autobahn.js | 6 +- test/extension.test.js | 46 +- test/permessage-deflate.test.js | 269 ++++++---- test/receiver.test.js | 207 ++++---- test/sender.test.js | 42 +- test/websocket-server.test.js | 458 ++++++++-------- test/websocket.integration.js | 6 +- test/websocket.test.js | 784 +++++++++++++++------------- 35 files changed, 1560 insertions(+), 1265 deletions(-) delete mode 100644 .eslintignore create mode 100644 .prettierrc.yaml diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 25fbf5a1c..000000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -coverage/ diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 2524511f9..d370e3d2e 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -1,8 +1,16 @@ -extends: standard env: + browser: true + es6: true mocha: true + node: true +extends: + - eslint:recommended + - prettier +parserOptions: + ecmaVersion: 9 +plugins: + - prettier rules: - no-extra-semi: error - semi: - - error - - always + no-console: off + prefer-const: error + prettier/prettier: error diff --git a/.gitignore b/.gitignore index 1f0951bf2..e37ab1e94 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,3 @@ node_modules/ .nyc_output/ coverage/ .vscode/ -npm-debug.log diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 000000000..754ed23e2 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,4 @@ +arrowParens: always +endOfLine: lf +proseWrap: always +singleQuote: true diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index f75647683..177ab8d71 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -7,7 +7,8 @@ General support questions should be raised on a channel like Stack Overflow. Please fill in as much of the template below as you're able. --> -- [ ] I've searched for any related issues and avoided creating a duplicate issue. +- [ ] I've searched for any related issues and avoided creating a duplicate + issue. #### Description @@ -15,15 +16,11 @@ Please fill in as much of the template below as you're able. #### Reproducible in: -version: -Node.js version(s): -OS version(s): +version: Node.js version(s): OS version(s): #### Steps to reproduce: -1. -2. -3. +1. 2. 3. ### Expected result: diff --git a/README.md b/README.md index cae947caa..f5b8e4ec7 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ [![Windows Build](https://ci.appveyor.com/api/projects/status/github/websockets/ws?branch=master&svg=true)](https://ci.appveyor.com/project/lpinca/ws) [![Coverage Status](https://img.shields.io/coveralls/websockets/ws/master.svg)](https://coveralls.io/r/websockets/ws?branch=master) -ws is a simple to use, blazing fast, and thoroughly tested WebSocket client -and server implementation. +ws is a simple to use, blazing fast, and thoroughly tested WebSocket client and +server implementation. Passes the quite extensive Autobahn test suite: [server][server-report], [client][client-report]. @@ -14,39 +14,40 @@ Passes the quite extensive Autobahn test suite: [server][server-report], **Note**: This module does not work in the browser. The client in the docs is a reference to a back end with the role of a client in the WebSocket communication. Browser clients must use the native -[`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) object. -To make the same code work seamlessly on Node.js and the browser, you can use -one of the many wrappers available on npm, like +[`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) +object. To make the same code work seamlessly on Node.js and the browser, you +can use one of the many wrappers available on npm, like [isomorphic-ws](https://github.com/heineiuo/isomorphic-ws). ## Table of Contents -* [Protocol support](#protocol-support) -* [Installing](#installing) - + [Opt-in for performance and spec compliance](#opt-in-for-performance-and-spec-compliance) -* [API docs](#api-docs) -* [WebSocket compression](#websocket-compression) -* [Usage examples](#usage-examples) - + [Sending and receiving text data](#sending-and-receiving-text-data) - + [Sending binary data](#sending-binary-data) - + [Simple server](#simple-server) - + [External HTTP/S server](#external-https-server) - + [Multiple servers sharing a single HTTP/S server](#multiple-servers-sharing-a-single-https-server) - + [Server broadcast](#server-broadcast) - + [echo.websocket.org demo](#echowebsocketorg-demo) - + [Other examples](#other-examples) -* [Error handling best practices](#error-handling-best-practices) -* [FAQ](#faq) - + [How to get the IP address of the client?](#how-to-get-the-ip-address-of-the-client) - + [How to detect and close broken connections?](#how-to-detect-and-close-broken-connections) - + [How to connect via a proxy?](#how-to-connect-via-a-proxy) -* [Changelog](#changelog) -* [License](#license) +- [Protocol support](#protocol-support) +- [Installing](#installing) + - [Opt-in for performance and spec compliance](#opt-in-for-performance-and-spec-compliance) +- [API docs](#api-docs) +- [WebSocket compression](#websocket-compression) +- [Usage examples](#usage-examples) + - [Sending and receiving text data](#sending-and-receiving-text-data) + - [Sending binary data](#sending-binary-data) + - [Simple server](#simple-server) + - [External HTTP/S server](#external-https-server) + - [Multiple servers sharing a single HTTP/S server](#multiple-servers-sharing-a-single-https-server) + - [Server broadcast](#server-broadcast) + - [echo.websocket.org demo](#echowebsocketorg-demo) + - [Other examples](#other-examples) +- [Error handling best practices](#error-handling-best-practices) +- [FAQ](#faq) + - [How to get the IP address of the client?](#how-to-get-the-ip-address-of-the-client) + - [How to detect and close broken connections?](#how-to-detect-and-close-broken-connections) + - [How to connect via a proxy?](#how-to-connect-via-a-proxy) +- [Changelog](#changelog) +- [License](#license) ## Protocol support -* **HyBi drafts 07-12** (Use the option `protocolVersion: 8`) -* **HyBi drafts 13-17** (Current default, alternatively option `protocolVersion: 13`) +- **HyBi drafts 07-12** (Use the option `protocolVersion: 8`) +- **HyBi drafts 13-17** (Current default, alternatively option + `protocolVersion: 13`) ## Installing @@ -64,8 +65,8 @@ necessarily need to have a C++ compiler installed on your machine. - `npm install --save-optional bufferutil`: Allows to efficiently perform operations such as masking and unmasking the data payload of the WebSocket frames. -- `npm install --save-optional utf-8-validate`: Allows to efficiently check - if a message contains valid UTF-8 as required by the spec. +- `npm install --save-optional utf-8-validate`: Allows to efficiently check if a + message contains valid UTF-8 as required by the spec. ## API docs @@ -73,21 +74,20 @@ See [`/doc/ws.md`](./doc/ws.md) for Node.js-like docs for the ws classes. ## WebSocket compression -ws supports the [permessage-deflate extension][permessage-deflate] which -enables the client and server to negotiate a compression algorithm and its -parameters, and then selectively apply it to the data payloads of each -WebSocket message. +ws supports the [permessage-deflate extension][permessage-deflate] which enables +the client and server to negotiate a compression algorithm and its parameters, +and then selectively apply it to the data payloads of each WebSocket message. -The extension is disabled by default on the server and enabled by default on -the client. It adds a significant overhead in terms of performance and memory +The extension is disabled by default on the server and enabled by default on the +client. It adds a significant overhead in terms of performance and memory consumption so we suggest to enable it only if it is really needed. Note that Node.js has a variety of issues with high-performance compression, -where increased concurrency, especially on Linux, can lead to -[catastrophic memory fragmentation][node-zlib-bug] and slow performance. -If you intend to use permessage-deflate in production, it is worthwhile to set -up a test representative of your workload and ensure Node.js/zlib will handle -it with acceptable performance and memory usage. +where increased concurrency, especially on Linux, can lead to [catastrophic +memory fragmentation][node-zlib-bug] and slow performance. If you intend to use +permessage-deflate in production, it is worthwhile to set up a test +representative of your workload and ensure Node.js/zlib will handle it with +acceptable performance and memory usage. Tuning of permessage-deflate can be done via the options defined below. You can also use `zlibDeflateOptions` and `zlibInflateOptions`, which is passed directly @@ -101,10 +101,11 @@ const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080, perMessageDeflate: { - zlibDeflateOptions: { // See zlib defaults. + zlibDeflateOptions: { + // See zlib defaults. chunkSize: 1024, memLevel: 7, - level: 3, + level: 3 }, zlibInflateOptions: { chunkSize: 10 * 1024 @@ -112,11 +113,11 @@ const wss = new WebSocket.Server({ // Other options settable: clientNoContextTakeover: true, // Defaults to negotiated value. serverNoContextTakeover: true, // Defaults to negotiated value. - serverMaxWindowBits: 10, // Defaults to negotiated value. + serverMaxWindowBits: 10, // Defaults to negotiated value. // Below options specified as default values. - concurrencyLimit: 10, // Limits zlib concurrency for perf. - threshold: 1024, // Size (in bytes) below which messages - // should not be compressed. + concurrencyLimit: 10, // Limits zlib concurrency for perf. + threshold: 1024 // Size (in bytes) below which messages + // should not be compressed. } }); ``` @@ -325,8 +326,11 @@ ws.send('something', function ack(error) { // Immediate errors can also be handled with `try...catch`, but **note** that // since sends are inherently asynchronous, socket write failures will *not* be // captured when this technique is used. -try { ws.send('something'); } -catch (e) { /* handle error */ } +try { + ws.send('something'); +} catch (e) { + /* handle error */ +} ``` ## FAQ @@ -356,8 +360,8 @@ wss.on('connection', function connection(ws, req) { ### How to detect and close broken connections? -Sometimes the link between the server and the client can be interrupted in a -way that keeps both the server and the client unaware of the broken state of the +Sometimes the link between the server and the client can be interrupted in a way +that keeps both the server and the client unaware of the broken state of the connection (e.g. when pulling the cord). In these cases ping messages can be used as a means to verify that the remote @@ -389,8 +393,8 @@ const interval = setInterval(function ping() { }, 30000); ``` -Pong messages are automatically sent in response to ping messages as required -by the spec. +Pong messages are automatically sent in response to ping messages as required by +the spec. Just like the server example above your clients might as well lose connection without knowing it. You might want to add a ping listener on your clients to @@ -439,5 +443,7 @@ We're using the GitHub [releases][changelog] for changelog entries. [permessage-deflate]: https://tools.ietf.org/html/rfc7692 [changelog]: https://github.com/websockets/ws/releases [node-zlib-bug]: https://github.com/nodejs/node/issues/8871 -[node-zlib-deflaterawdocs]: https://nodejs.org/api/zlib.html#zlib_zlib_createdeflateraw_options -[ws-server-options]: https://github.com/websockets/ws/blob/master/doc/ws.md#new-websocketserveroptions-callback +[node-zlib-deflaterawdocs]: + https://nodejs.org/api/zlib.html#zlib_zlib_createdeflateraw_options +[ws-server-options]: + https://github.com/websockets/ws/blob/master/doc/ws.md#new-websocketserveroptions-callback diff --git a/SECURITY.md b/SECURITY.md index 8e063cca5..258ff59fd 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -17,18 +17,20 @@ us the benefit of the doubt as it's possible that we haven't seen it yet. In this case please send us a message **without details** using one of the following methods: -- Contact the lead developers of this project on their personal e-mails. You - can find the e-mails in the git logs, for example using the following command: +- Contact the lead developers of this project on their personal e-mails. You can + find the e-mails in the git logs, for example using the following command: `git --no-pager show -s --format='%an <%ae>' ` where `` is the SHA1 of their latest commit in the project. - Create a GitHub issue stating contact details and the severity of the issue. -Once we have acknowledged receipt of your report and confirmed the bug -ourselves we will work with you to fix the vulnerability and publicly acknowledge -your responsible disclosure, if you wish. In addition to that we will report -all vulnerabilities to the [Node Security Project](https://nodesecurity.io/). +Once we have acknowledged receipt of your report and confirmed the bug ourselves +we will work with you to fix the vulnerability and publicly acknowledge your +responsible disclosure, if you wish. In addition to that we will report all +vulnerabilities to the [Node Security Project](https://nodesecurity.io/). ## History -- 04 Jan 2016: [Buffer vulnerability](https://github.com/websockets/ws/releases/tag/1.0.1) -- 08 Nov 2017: [DoS vulnerability](https://github.com/websockets/ws/releases/tag/3.3.1) +- 04 Jan 2016: + [Buffer vulnerability](https://github.com/websockets/ws/releases/tag/1.0.1) +- 08 Nov 2017: + [DoS vulnerability](https://github.com/websockets/ws/releases/tag/3.3.1) diff --git a/bench/parser.benchmark.js b/bench/parser.benchmark.js index 43cc1ea4e..c0a61675a 100644 --- a/bench/parser.benchmark.js +++ b/bench/parser.benchmark.js @@ -15,7 +15,7 @@ const options = { readOnly: false }; -function createBinaryFrame (length) { +function createBinaryFrame(length) { const list = Sender.frame( crypto.randomBytes(length), Object.assign({ opcode: 0x02 }, options) @@ -24,10 +24,9 @@ function createBinaryFrame (length) { return Buffer.concat(list); } -const pingFrame1 = Buffer.concat(Sender.frame( - crypto.randomBytes(5), - Object.assign({ opcode: 0x09 }, options) -)); +const pingFrame1 = Buffer.concat( + Sender.frame(crypto.randomBytes(5), Object.assign({ opcode: 0x09 }, options)) +); const textFrame = Buffer.from('819461616161' + '61'.repeat(20), 'hex'); const pingFrame2 = Buffer.from('8900', 'hex'); diff --git a/bench/speed.js b/bench/speed.js index 34d455935..c87dc0b78 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -53,7 +53,10 @@ if (cluster.isMaster) { return roundPrec(bytes, 2) + ' B'; }; - const largest = configs.reduce((prev, curr) => curr[2] > prev ? curr[2] : prev, 0); + const largest = configs.reduce( + (prev, curr) => (curr[2] > prev ? curr[2] : prev), + 0 + ); console.log('Generating %s of test data...', humanSize(largest)); const randomBytes = Buffer.allocUnsafe(largest); @@ -81,10 +84,11 @@ if (cluster.isMaster) { ws.send(data, { binary: useBinary }); }); ws.on('message', () => { - if (++roundtrip !== roundtrips) return ws.send(data, { binary: useBinary }); + if (++roundtrip !== roundtrips) + return ws.send(data, { binary: useBinary }); var elapsed = process.hrtime(time); - elapsed = (elapsed[0] * 1e9) + elapsed[1]; + elapsed = elapsed[0] * 1e9 + elapsed[1]; console.log( '%d roundtrips of %s %s data:\t%ss\t%s', @@ -92,7 +96,7 @@ if (cluster.isMaster) { humanSize(size), useBinary ? 'binary' : 'text', roundPrec(elapsed / 1e9, 1), - humanSize(size * 2 * roundtrips / elapsed * 1e9) + '/s' + humanSize(((size * 2 * roundtrips) / elapsed) * 1e9) + '/s' ); ws.close(); @@ -100,7 +104,7 @@ if (cluster.isMaster) { }); }; - (function run () { + (function run() { if (configs.length === 0) return cluster.worker.disconnect(); var config = configs.shift(); config.push(run); diff --git a/browser.js b/browser.js index ca4f628ac..782077969 100644 --- a/browser.js +++ b/browser.js @@ -1,6 +1,6 @@ 'use strict'; -module.exports = function () { +module.exports = function() { throw new Error( 'ws does not work in the browser. Browser clients must use the native ' + 'WebSocket object' diff --git a/doc/ws.md b/doc/ws.md index 5c36068b3..0b21101eb 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -30,9 +30,8 @@ started manually. The "noServer" mode allows the WebSocket server to be completly detached from the HTTP/S server. This makes it possible, for example, to share a single HTTP/S server between multiple WebSocket servers. - -If `verifyClient` is not set then the handshake is automatically accepted. If -it is provided with a single argument then that is: +If `verifyClient` is not set then the handshake is automatically accepted. If it +is provided with a single argument then that is: - `info` {Object} - `origin` {String} The value in the Origin header indicated by the client. @@ -46,21 +45,21 @@ the handshake. if `verifyClient` is provided with two arguments then those are: - `info` {Object} Same as above. -- `cb` {Function} A callback that must be called by the user upon inspection - of the `info` fields. Arguments in this callback are: +- `cb` {Function} A callback that must be called by the user upon inspection of + the `info` fields. Arguments in this callback are: - `result` {Boolean} Whether or not to accept the handshake. - `code` {Number} When `result` is `false` this field determines the HTTP error status code to be sent to the client. - `name` {String} When `result` is `false` this field determines the HTTP reason phrase. - `headers` {Object} When `result` is `false` this field determines additional - HTTP headers to be sent to the client. For example, `{ 'Retry-After': 120 }`. - + HTTP headers to be sent to the client. For example, + `{ 'Retry-After': 120 }`. `handleProtocols` takes two arguments: -- `protocols` {Array} The list of WebSocket subprotocols indicated by the - client in the `Sec-WebSocket-Protocol` header. +- `protocols` {Array} The list of WebSocket subprotocols indicated by the client + in the `Sec-WebSocket-Protocol` header. - `request` {http.IncomingMessage} The client HTTP GET request. The returned value sets the value of the `Sec-WebSocket-Protocol` header in the @@ -70,11 +69,9 @@ response. If `handleProtocols` is not set then the first of the client's requested subprotocols is used. - -`perMessageDeflate` can be used to control the behavior of -[permessage-deflate extension][permessage-deflate]. -The extension is disabled when `false` (default value). If an object is -provided then that is extension parameters: +`perMessageDeflate` can be used to control the behavior of [permessage-deflate +extension][permessage-deflate]. The extension is disabled when `false` (default +value). If an object is provided then that is extension parameters: - `serverNoContextTakeover` {Boolean} Whether to use context takeover or not. - `clientNoContextTakeover` {Boolean} Acknowledge disabling of client context @@ -87,16 +84,14 @@ provided then that is extension parameters: zlib on inflate. - `threshold` {Number} Payloads smaller than this will not be compressed. Defaults to 1024 bytes. -- `concurrencyLimit` {Number} The number of concurrent calls to zlib. - Calls above this limit will be queued. Default 10. You usually won't - need to touch this option. See [this issue][concurrency-limit] for more - details. +- `concurrencyLimit` {Number} The number of concurrent calls to zlib. Calls + above this limit will be queued. Default 10. You usually won't need to touch + this option. See [this issue][concurrency-limit] for more details. If a property is empty then either an offered configuration or a default value -is used. -When sending a fragmented message the length of the first fragment is compared -to the threshold. This determines if compression is used for the entire message. - +is used. When sending a fragmented message the length of the first fragment is +compared to the threshold. This determines if compression is used for the entire +message. `callback` will be added as a listener for the `listening` event on the HTTP server when not operating in "noServer" mode. @@ -104,8 +99,8 @@ server when not operating in "noServer" mode. ### Event: 'close' Emitted when the server closes. This event depends on the `'close'` event of -HTTP server only when it is created internally. In all other cases, the event -is emitted independently. +HTTP server only when it is created internally. In all other cases, the event is +emitted independently. ### Event: 'connection' @@ -143,11 +138,10 @@ added when the `clientTracking` is truthy. ### server.address() -Returns an object with `port`, `family`, and `address` properties specifying -the bound address, the address family name, and port of the server as reported -by the operating system if listening on an IP socket. -If the server is listening on a pipe or UNIX domain socket, the name is -returned as a string. +Returns an object with `port`, `family`, and `address` properties specifying the +bound address, the address family name, and port of the server as reported by +the operating system if listening on an IP socket. If the server is listening on +a pipe or UNIX domain socket, the name is returned as a string. ### server.close([callback]) @@ -167,18 +161,17 @@ when the HTTP server is passed via the `server` option, this method is called automatically. When operating in "noServer" mode, this method must be called manually. -If the upgrade is successful, the `callback` is called with a `WebSocket` -object as parameter. +If the upgrade is successful, the `callback` is called with a `WebSocket` object +as parameter. ### server.shouldHandle(request) - `request` {http.IncomingMessage} The client HTTP GET request. -See if a given request should be handled by this server. -By default this method validates the pathname of the request, matching it -against the `path` option if provided. -The return value, `true` or `false`, determines whether or not to accept the -handshake. +See if a given request should be handled by this server. By default this method +validates the pathname of the request, matching it against the `path` option if +provided. The return value, `true` or `false`, determines whether or not to +accept the handshake. This method can be overridden when a custom handling logic is required. @@ -188,19 +181,20 @@ This class represents a WebSocket. It extends the `EventEmitter`. ### Ready state constants -|Constant | Value | Description | -|-----------|-------|--------------------------------------------------| -|CONNECTING | 0 | The connection is not yet open. | -|OPEN | 1 | The connection is open and ready to communicate. | -|CLOSING | 2 | The connection is in the process of closing. | -|CLOSED | 3 | The connection is closed. | +| Constant | Value | Description | +| ---------- | ----- | ------------------------------------------------ | +| CONNECTING | 0 | The connection is not yet open. | +| OPEN | 1 | The connection is open and ready to communicate. | +| CLOSING | 2 | The connection is in the process of closing. | +| CLOSED | 3 | The connection is closed. | ### new WebSocket(address[, protocols][, options]) - `address` {String|url.Url|url.URL} The URL to which to connect. - `protocols` {String|Array} The list of subprotocols. - `options` {Object} - - `handshakeTimeout` {Number} Timeout in milliseconds for the handshake request. + - `handshakeTimeout` {Number} Timeout in milliseconds for the handshake + request. - `perMessageDeflate` {Boolean|Object} Enable/disable permessage-deflate. - `protocolVersion` {Number} Value of the `Sec-WebSocket-Version` header. - `origin` {String} Value of the `Origin` or `Sec-WebSocket-Origin` header @@ -210,8 +204,8 @@ This class represents a WebSocket. It extends the `EventEmitter`. `perMessageDeflate` default value is `true`. When using an object, parameters are the same of the server. The only difference is the direction of requests. -For example, `serverNoContextTakeover` can be used to ask the server to -disable context takeover. +For example, `serverNoContextTakeover` can be used to ask the server to disable +context takeover. Create a new WebSocket instance. @@ -224,8 +218,8 @@ following URL scheme: ws+unix:///absolute/path/to/uds_socket:/pathname?search_params ``` -Note that `:` is the separator between the socket path and the URL path. If -the URL path is omitted +Note that `:` is the separator between the socket path and the URL path. If the +URL path is omitted ``` ws+unix:///absolute/path/to/uds_socket @@ -285,7 +279,7 @@ listener for this event, an error is emitted. - `response` {http.IncomingMessage} Emitted when response headers are received from the server as part of the -handshake. This allows you to read headers from the server, for example +handshake. This allows you to read headers from the server, for example 'set-cookie' headers. ### websocket.addEventListener(type, listener) @@ -314,8 +308,8 @@ not yet transmitted to the network. ### websocket.close([code[, reason]]) -- `code` {Number} A numeric value indicating the status code explaining why - the connection is being closed. +- `code` {Number} A numeric value indicating the status code explaining why the + connection is being closed. - `reason` {String} A human-readable string explaining why the connection is closing. @@ -338,8 +332,8 @@ a `CloseEvent` named "close". - {Function} -An event listener to be called when an error occurs. The listener receives -an `ErrorEvent` named "error". +An event listener to be called when an error occurs. The listener receives an +`ErrorEvent` named "error". ### websocket.onmessage @@ -358,8 +352,8 @@ receives an `OpenEvent` named "open". ### websocket.ping([data[, mask]][, callback]) - `data` {Any} The data to send in the ping frame. -- `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults - to `true` when `websocket` is not a server client. +- `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults to + `true` when `websocket` is not a server client. - `callback` {Function} An optional callback which is invoked when the ping frame is written out. @@ -368,8 +362,8 @@ Send a ping. ### websocket.pong([data[, mask]][, callback]) - `data` {Any} The data to send in the pong frame. -- `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults - to `true` when `websocket` is not a server client. +- `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults to + `true` when `websocket` is not a server client. - `callback` {Function} An optional callback which is invoked when the pong frame is written out. @@ -400,12 +394,12 @@ Removes an event listener emulating the `EventTarget` interface. - `options` {Object} - `compress` {Boolean} Specifies whether `data` should be compressed or not. Defaults to `true` when permessage-deflate is enabled. - - `binary` {Boolean} Specifies whether `data` should be sent as a binary or not. - Default is autodetected. + - `binary` {Boolean} Specifies whether `data` should be sent as a binary or + not. Default is autodetected. - `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults to `true` when `websocket` is not a server client. - - `fin` {Boolean} Specifies whether `data` is the last fragment of a message or - not. Defaults to `true`. + - `fin` {Boolean} Specifies whether `data` is the last fragment of a message + or not. Defaults to `true`. - `callback` {Function} An optional callback which is invoked when `data` is written out. @@ -422,7 +416,10 @@ Forcibly close the connection. The URL of the WebSocket server. Server clients don't have this attribute. [concurrency-limit]: https://github.com/websockets/ws/issues/1202 -[permessage-deflate]: https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19 +[permessage-deflate]: + https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19 [zlib-options]: https://nodejs.org/api/zlib.html#zlib_class_options -[http.request()]: https://nodejs.org/api/http.html#http_http_request_options_callback -[https.request()]: https://nodejs.org/api/https.html#https_https_request_options_callback +[http.request()]: + https://nodejs.org/api/http.html#http_http_request_options_callback +[https.request()]: + https://nodejs.org/api/https.html#https_https_request_options_callback diff --git a/examples/fileapi/public/app.js b/examples/fileapi/public/app.js index f1a9ee32c..80300db52 100644 --- a/examples/fileapi/public/app.js +++ b/examples/fileapi/public/app.js @@ -1,5 +1,5 @@ /* global Uploader */ -function onFilesSelected (e) { +function onFilesSelected(e) { var button = e.srcElement; button.disabled = true; var progress = document.querySelector('div#progress'); @@ -8,33 +8,33 @@ function onFilesSelected (e) { var totalFiles = files.length; var filesSent = 0; if (totalFiles) { - var uploader = new Uploader('ws://localhost:8080', function () { - Array.prototype.slice.call(files, 0).forEach(function (file) { + var uploader = new Uploader('ws://localhost:8080', function() { + Array.prototype.slice.call(files, 0).forEach(function(file) { if (file.name === '.') { --totalFiles; return; } - uploader.sendFile(file, function (error) { + uploader.sendFile(file, function(error) { if (error) { console.log(error); return; } ++filesSent; - progress.innerHTML = ~~(filesSent / totalFiles * 100) + '%'; + progress.innerHTML = ~~((filesSent / totalFiles) * 100) + '%'; console.log('Sent: ' + file.name); }); }); }); } - uploader.ondone = function () { + uploader.ondone = function() { uploader.close(); progress.innerHTML = '100% done, ' + totalFiles + ' files sent.'; }; } -window.onload = function () { +window.onload = function() { var importButtons = document.querySelectorAll('[type="file"]'); - Array.prototype.slice.call(importButtons, 0).forEach(function (importButton) { + Array.prototype.slice.call(importButtons, 0).forEach(function(importButton) { importButton.addEventListener('change', onFilesSelected, false); }); }; diff --git a/examples/fileapi/public/uploader.js b/examples/fileapi/public/uploader.js index 9b4f98e2f..11332d43c 100644 --- a/examples/fileapi/public/uploader.js +++ b/examples/fileapi/public/uploader.js @@ -1,5 +1,5 @@ /* global WebSocket */ -function Uploader (url, cb) { +function Uploader(url, cb) { this.ws = new WebSocket(url); if (cb) this.ws.onopen = cb; this.sendQueue = []; @@ -7,7 +7,7 @@ function Uploader (url, cb) { this.sendCallback = null; this.ondone = null; var self = this; - this.ws.onmessage = function (event) { + this.ws.onmessage = function(event) { var data = JSON.parse(event.data); var callback; if (data.event === 'complete') { @@ -24,7 +24,9 @@ function Uploader (url, cb) { if (self.sendQueue.length === 0 && self.ondone) self.ondone(null); if (self.sendQueue.length > 0) { var args = self.sendQueue.pop(); - setTimeout(function () { self.sendFile.apply(self, args); }, 0); + setTimeout(function() { + self.sendFile.apply(self, args); + }, 0); } } else if (data.event === 'error') { self.sendQueue = []; @@ -38,7 +40,7 @@ function Uploader (url, cb) { }; } -Uploader.prototype.sendFile = function (file, cb) { +Uploader.prototype.sendFile = function(file, cb) { if (this.ws.readyState !== WebSocket.OPEN) throw new Error('Not connected'); if (this.sending) { this.sendQueue.push(arguments); @@ -51,6 +53,6 @@ Uploader.prototype.sendFile = function (file, cb) { this.ws.send(file); }; -Uploader.prototype.close = function () { +Uploader.prototype.close = function() { this.ws.close(); }; diff --git a/examples/fileapi/server.js b/examples/fileapi/server.js index 80fc1d5ae..4bb1b61e6 100644 --- a/examples/fileapi/server.js +++ b/examples/fileapi/server.js @@ -9,32 +9,32 @@ var events = require('events'); var ansi = require('ansi'); var cursor = ansi(process.stdout); -function BandwidthSampler (ws, interval) { +function BandwidthSampler(ws, interval) { interval = interval || 2000; var previousByteCount = 0; var self = this; - var intervalId = setInterval(function () { + var intervalId = setInterval(function() { var byteCount = ws.bytesReceived; var bytesPerSec = (byteCount - previousByteCount) / (interval / 1000); previousByteCount = byteCount; self.emit('sample', bytesPerSec); }, interval); - ws.on('close', function () { + ws.on('close', function() { clearInterval(intervalId); }); } util.inherits(BandwidthSampler, events.EventEmitter); -function makePathForFile (filePath, prefix, cb) { +function makePathForFile(filePath, prefix, cb) { if (typeof cb !== 'function') throw new Error('callback is required'); filePath = path.dirname(path.normalize(filePath)).replace(/^(\/|\\)+/, ''); var pieces = filePath.split(/(\\|\/)/); var incrementalPath = prefix; - function step (error) { + function step(error) { if (error) return cb(error); if (pieces.length === 0) return cb(null, incrementalPath); incrementalPath += '/' + pieces.shift(); - fs.access(incrementalPath, function (err) { + fs.access(incrementalPath, function(err) { if (err) fs.mkdir(incrementalPath, step); else process.nextTick(step); }); @@ -47,62 +47,88 @@ app.use(express.static(path.join(__dirname, '/public'))); var clientId = 0; var wss = new WebSocketServer({ server: server }); -wss.on('connection', function (ws) { +wss.on('connection', function(ws) { var thisId = ++clientId; cursor.goto(1, 4 + thisId).eraseLine(); console.log('Client #%d connected', thisId); var sampler = new BandwidthSampler(ws); - sampler.on('sample', function (bps) { + sampler.on('sample', function(bps) { cursor.goto(1, 4 + thisId).eraseLine(); - console.log('WebSocket #%d incoming bandwidth: %d MB/s', thisId, Math.round(bps / (1024 * 1024))); + console.log( + 'WebSocket #%d incoming bandwidth: %d MB/s', + thisId, + Math.round(bps / (1024 * 1024)) + ); }); var filesReceived = 0; var currentFile = null; - ws.on('message', function (data) { + ws.on('message', function(data) { if (typeof data === 'string') { currentFile = JSON.parse(data); // note: a real-world app would want to sanity check the data } else { if (currentFile == null) return; - makePathForFile(currentFile.path, path.join(__dirname, '/uploaded'), function (error, path) { - if (error) { - console.log(error); - ws.send(JSON.stringify({ event: 'error', path: currentFile.path, message: error.message })); - return; - } - fs.writeFile(path + '/' + currentFile.name, data, function (error) { + makePathForFile( + currentFile.path, + path.join(__dirname, '/uploaded'), + function(error, path) { if (error) { console.log(error); - ws.send(JSON.stringify({ event: 'error', path: currentFile.path, message: error.message })); + ws.send( + JSON.stringify({ + event: 'error', + path: currentFile.path, + message: error.message + }) + ); return; } - ++filesReceived; - // console.log('received %d bytes long file, %s', data.length, currentFile.path); - ws.send(JSON.stringify({ event: 'complete', path: currentFile.path })); - currentFile = null; - }); - }); + fs.writeFile(path + '/' + currentFile.name, data, function(error) { + if (error) { + console.log(error); + ws.send( + JSON.stringify({ + event: 'error', + path: currentFile.path, + message: error.message + }) + ); + return; + } + ++filesReceived; + // console.log('received %d bytes long file, %s', data.length, currentFile.path); + ws.send( + JSON.stringify({ event: 'complete', path: currentFile.path }) + ); + currentFile = null; + }); + } + ); } }); - ws.on('close', function () { + ws.on('close', function() { cursor.goto(1, 4 + thisId).eraseLine(); - console.log('Client #%d disconnected. %d files received.', thisId, filesReceived); + console.log( + 'Client #%d disconnected. %d files received.', + thisId, + filesReceived + ); }); - ws.on('error', function (e) { + ws.on('error', function(e) { cursor.goto(1, 4 + thisId).eraseLine(); console.log('Client #%d error: %s', thisId, e.message); }); }); -fs.mkdir(path.join(__dirname, '/uploaded'), function () { +fs.mkdir(path.join(__dirname, '/uploaded'), function() { // ignore errors, most likely means directory exists console.log('Uploaded files will be saved to %s/uploaded.', __dirname); console.log('Remember to wipe this directory if you upload lots and lots.'); - server.listen(8080, function () { + server.listen(8080, function() { console.log('Listening on http://localhost:8080'); }); }); diff --git a/examples/serverstats/server.js b/examples/serverstats/server.js index e0d108cbd..7f7f23c0f 100644 --- a/examples/serverstats/server.js +++ b/examples/serverstats/server.js @@ -7,18 +7,20 @@ var server = require('http').createServer(); app.use(express.static(path.join(__dirname, '/public'))); var wss = new WebSocketServer({ server: server }); -wss.on('connection', function (ws) { - var id = setInterval(function () { - ws.send(JSON.stringify(process.memoryUsage()), function () { /* ignore errors */ }); +wss.on('connection', function(ws) { + var id = setInterval(function() { + ws.send(JSON.stringify(process.memoryUsage()), function() { + /* ignore errors */ + }); }, 100); console.log('started client interval'); - ws.on('close', function () { + ws.on('close', function() { console.log('stopping client interval'); clearInterval(id); }); }); server.on('request', app); -server.listen(8080, function () { +server.listen(8080, function() { console.log('Listening on http://localhost:8080'); }); diff --git a/examples/ssl.js b/examples/ssl.js index 496daea80..c4d5b0758 100644 --- a/examples/ssl.js +++ b/examples/ssl.js @@ -12,13 +12,13 @@ const server = https.createServer({ const wss = new WebSocket.Server({ server }); -wss.on('connection', function connection (ws) { - ws.on('message', function message (msg) { +wss.on('connection', function connection(ws) { + ws.on('message', function message(msg) { console.log(msg); }); }); -server.listen(function listening () { +server.listen(function listening() { // // If the `rejectUnauthorized` option is not `false`, the server certificate // is verified against a list of well-known CAs. An 'error' event is emitted @@ -31,7 +31,7 @@ server.listen(function listening () { rejectUnauthorized: false }); - ws.on('open', function open () { + ws.on('open', function open() { ws.send('All glory to WebSockets!'); }); }); diff --git a/lib/buffer-util.js b/lib/buffer-util.js index 6974dd6a8..54867ac4f 100644 --- a/lib/buffer-util.js +++ b/lib/buffer-util.js @@ -8,7 +8,7 @@ * @return {Buffer} The resulting buffer * @public */ -function concat (list, totalLength) { +function concat(list, totalLength) { const target = Buffer.allocUnsafe(totalLength); var offset = 0; @@ -31,7 +31,7 @@ function concat (list, totalLength) { * @param {Number} length The number of bytes to mask. * @public */ -function _mask (source, mask, output, offset, length) { +function _mask(source, mask, output, offset, length) { for (var i = 0; i < length; i++) { output[offset + i] = source[i] ^ mask[i & 3]; } @@ -44,7 +44,7 @@ function _mask (source, mask, output, offset, length) { * @param {Buffer} mask The mask to use * @public */ -function _unmask (buffer, mask) { +function _unmask(buffer, mask) { // Required until https://github.com/nodejs/node/issues/9006 is resolved. const length = buffer.length; for (var i = 0; i < length; i++) { @@ -57,11 +57,11 @@ try { const bu = bufferUtil.BufferUtil || bufferUtil; module.exports = { - mask (source, mask, output, offset, length) { + mask(source, mask, output, offset, length) { if (length < 48) _mask(source, mask, output, offset, length); else bu.mask(source, mask, output, offset, length); }, - unmask (buffer, mask) { + unmask(buffer, mask) { if (buffer.length < 32) _unmask(buffer, mask); else bu.unmask(buffer, mask); }, diff --git a/lib/event-target.js b/lib/event-target.js index 574e9080e..44c81d991 100644 --- a/lib/event-target.js +++ b/lib/event-target.js @@ -12,7 +12,7 @@ class Event { * @param {String} type The name of the event * @param {Object} target A reference to the target to which the event was dispatched */ - constructor (type, target) { + constructor(type, target) { this.target = target; this.type = type; } @@ -31,7 +31,7 @@ class MessageEvent extends Event { * @param {(String|Buffer|ArrayBuffer|Buffer[])} data The received data * @param {WebSocket} target A reference to the target to which the event was dispatched */ - constructor (data, target) { + constructor(data, target) { super('message', target); this.data = data; @@ -52,7 +52,7 @@ class CloseEvent extends Event { * @param {String} reason A human-readable string explaining why the connection is closing * @param {WebSocket} target A reference to the target to which the event was dispatched */ - constructor (code, reason, target) { + constructor(code, reason, target) { super('close', target); this.wasClean = target._closeFrameReceived && target._closeFrameSent; @@ -73,7 +73,7 @@ class OpenEvent extends Event { * * @param {WebSocket} target A reference to the target to which the event was dispatched */ - constructor (target) { + constructor(target) { super('open', target); } } @@ -91,7 +91,7 @@ class ErrorEvent extends Event { * @param {Object} error The error that generated this event * @param {WebSocket} target A reference to the target to which the event was dispatched */ - constructor (error, target) { + constructor(error, target) { super('error', target); this.message = error.message; @@ -113,22 +113,22 @@ const EventTarget = { * @param {Function} listener The listener to add * @public */ - addEventListener (method, listener) { + addEventListener(method, listener) { if (typeof listener !== 'function') return; - function onMessage (data) { + function onMessage(data) { listener.call(this, new MessageEvent(data, this)); } - function onClose (code, message) { + function onClose(code, message) { listener.call(this, new CloseEvent(code, message, this)); } - function onError (error) { + function onError(error) { listener.call(this, new ErrorEvent(error, this)); } - function onOpen () { + function onOpen() { listener.call(this, new OpenEvent(this)); } @@ -156,7 +156,7 @@ const EventTarget = { * @param {Function} listener The listener to remove * @public */ - removeEventListener (method, listener) { + removeEventListener(method, listener) { const listeners = this.listeners(method); for (var i = 0; i < listeners.length; i++) { diff --git a/lib/extension.js b/lib/extension.js index a9b847282..47096b973 100644 --- a/lib/extension.js +++ b/lib/extension.js @@ -11,6 +11,7 @@ // tokenChars[34] === 0 // '"' // ... // +// prettier-ignore const tokenChars = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31 @@ -32,7 +33,7 @@ const tokenChars = [ * parameter value * @private */ -function push (dest, name, elem) { +function push(dest, name, elem) { if (Object.prototype.hasOwnProperty.call(dest, name)) dest[name].push(elem); else dest[name] = [elem]; } @@ -44,7 +45,7 @@ function push (dest, name, elem) { * @return {Object} The parsed object * @public */ -function parse (header) { +function parse(header) { const offers = {}; if (header === undefined || header === '') return offers; @@ -64,9 +65,9 @@ function parse (header) { if (extensionName === undefined) { if (end === -1 && tokenChars[code] === 1) { if (start === -1) start = i; - } else if (code === 0x20/* ' ' */ || code === 0x09/* '\t' */) { + } else if (code === 0x20 /* ' ' */ || code === 0x09 /* '\t' */) { if (end === -1 && start !== -1) end = i; - } else if (code === 0x3b/* ';' */ || code === 0x2c/* ',' */) { + } else if (code === 0x3b /* ';' */ || code === 0x2c /* ',' */) { if (start === -1) { throw new SyntaxError(`Unexpected character at index ${i}`); } @@ -103,7 +104,7 @@ function parse (header) { } start = end = -1; - } else if (code === 0x3d/* '=' */ && start !== -1 && end === -1) { + } else if (code === 0x3d /* '=' */ && start !== -1 && end === -1) { paramName = header.slice(start, i); start = end = -1; } else { @@ -125,10 +126,10 @@ function parse (header) { } else if (inQuotes) { if (tokenChars[code] === 1) { if (start === -1) start = i; - } else if (code === 0x22/* '"' */ && start !== -1) { + } else if (code === 0x22 /* '"' */ && start !== -1) { inQuotes = false; end = i; - } else if (code === 0x5c/* '\' */) { + } else if (code === 0x5c /* '\' */) { isEscaping = true; } else { throw new SyntaxError(`Unexpected character at index ${i}`); @@ -194,18 +195,28 @@ function parse (header) { * @return {String} A string representing the given object * @public */ -function format (extensions) { - return Object.keys(extensions).map((extension) => { - var configurations = extensions[extension]; - if (!Array.isArray(configurations)) configurations = [configurations]; - return configurations.map((params) => { - return [extension].concat(Object.keys(params).map((k) => { - var values = params[k]; - if (!Array.isArray(values)) values = [values]; - return values.map((v) => v === true ? k : `${k}=${v}`).join('; '); - })).join('; '); - }).join(', '); - }).join(', '); +function format(extensions) { + return Object.keys(extensions) + .map((extension) => { + var configurations = extensions[extension]; + if (!Array.isArray(configurations)) configurations = [configurations]; + return configurations + .map((params) => { + return [extension] + .concat( + Object.keys(params).map((k) => { + var values = params[k]; + if (!Array.isArray(values)) values = [values]; + return values + .map((v) => (v === true ? k : `${k}=${v}`)) + .join('; '); + }) + ) + .join('; '); + }) + .join(', '); + }) + .join(', '); } module.exports = { format, parse }; diff --git a/lib/permessage-deflate.js b/lib/permessage-deflate.js index d4ec149c6..ffc8e6c40 100644 --- a/lib/permessage-deflate.js +++ b/lib/permessage-deflate.js @@ -52,12 +52,11 @@ class PerMessageDeflate { * mode * @param {Number} maxPayload The maximum allowed message length */ - constructor (options, isServer, maxPayload) { + constructor(options, isServer, maxPayload) { this._maxPayload = maxPayload | 0; this._options = options || {}; - this._threshold = this._options.threshold !== undefined - ? this._options.threshold - : 1024; + this._threshold = + this._options.threshold !== undefined ? this._options.threshold : 1024; this._isServer = !!isServer; this._deflate = null; this._inflate = null; @@ -65,9 +64,10 @@ class PerMessageDeflate { this.params = null; if (!zlibLimiter) { - const concurrency = this._options.concurrencyLimit !== undefined - ? this._options.concurrencyLimit - : 10; + const concurrency = + this._options.concurrencyLimit !== undefined + ? this._options.concurrencyLimit + : 10; zlibLimiter = new Limiter({ concurrency }); } } @@ -75,7 +75,7 @@ class PerMessageDeflate { /** * @type {String} */ - static get extensionName () { + static get extensionName() { return 'permessage-deflate'; } @@ -85,7 +85,7 @@ class PerMessageDeflate { * @return {Object} Extension parameters * @public */ - offer () { + offer() { const params = {}; if (this._options.serverNoContextTakeover) { @@ -113,7 +113,7 @@ class PerMessageDeflate { * @return {Object} Accepted configuration * @public */ - accept (configurations) { + accept(configurations) { configurations = this.normalizeParams(configurations); this.params = this._isServer @@ -128,7 +128,7 @@ class PerMessageDeflate { * * @public */ - cleanup () { + cleanup() { if (this._inflate) { if (this._inflate[kWriteInProgress]) { this._inflate[kPendingClose] = true; @@ -154,7 +154,7 @@ class PerMessageDeflate { * @return {Object} Accepted configuration * @private */ - acceptAsServer (offers) { + acceptAsServer(offers) { const opts = this._options; const accepted = offers.find((params) => { if ( @@ -205,7 +205,7 @@ class PerMessageDeflate { * @return {Object} Accepted configuration * @private */ - acceptAsClient (response) { + acceptAsClient(response) { const params = response[0]; if ( @@ -239,7 +239,7 @@ class PerMessageDeflate { * @return {Array} The offers/response with normalized parameters * @private */ - normalizeParams (configurations) { + normalizeParams(configurations) { configurations.forEach((params) => { Object.keys(params).forEach((key) => { var value = params[key]; @@ -300,7 +300,7 @@ class PerMessageDeflate { * @param {Function} callback Callback * @public */ - decompress (data, fin, callback) { + decompress(data, fin, callback) { zlibLimiter.push((done) => { this._decompress(data, fin, (err, result) => { done(); @@ -317,7 +317,7 @@ class PerMessageDeflate { * @param {Function} callback Callback * @public */ - compress (data, fin, callback) { + compress(data, fin, callback) { zlibLimiter.push((done) => { this._compress(data, fin, (err, result) => { done(); @@ -334,14 +334,15 @@ class PerMessageDeflate { * @param {Function} callback Callback * @private */ - _decompress (data, fin, callback) { + _decompress(data, fin, callback) { const endpoint = this._isServer ? 'client' : 'server'; if (!this._inflate) { const key = `${endpoint}_max_window_bits`; - const windowBits = typeof this.params[key] !== 'number' - ? zlib.Z_DEFAULT_WINDOWBITS - : this.params[key]; + const windowBits = + typeof this.params[key] !== 'number' + ? zlib.Z_DEFAULT_WINDOWBITS + : this.params[key]; this._inflate = zlib.createInflateRaw( Object.assign({}, this._options.zlibInflateOptions, { windowBits }) @@ -398,7 +399,7 @@ class PerMessageDeflate { * @param {Function} callback Callback * @private */ - _compress (data, fin, callback) { + _compress(data, fin, callback) { if (!data || data.length === 0) { process.nextTick(callback, null, EMPTY_BLOCK); return; @@ -408,9 +409,10 @@ class PerMessageDeflate { if (!this._deflate) { const key = `${endpoint}_max_window_bits`; - const windowBits = typeof this.params[key] !== 'number' - ? zlib.Z_DEFAULT_WINDOWBITS - : this.params[key]; + const windowBits = + typeof this.params[key] !== 'number' + ? zlib.Z_DEFAULT_WINDOWBITS + : this.params[key]; this._deflate = zlib.createDeflateRaw( Object.assign({}, this._options.zlibDeflateOptions, { windowBits }) @@ -463,7 +465,7 @@ module.exports = PerMessageDeflate; * @param {Buffer} chunk A chunk of data * @private */ -function deflateOnData (chunk) { +function deflateOnData(chunk) { this[kBuffers].push(chunk); this[kTotalLength] += chunk.length; } @@ -474,7 +476,7 @@ function deflateOnData (chunk) { * @param {Buffer} chunk A chunk of data * @private */ -function inflateOnData (chunk) { +function inflateOnData(chunk) { this[kTotalLength] += chunk.length; if ( @@ -497,7 +499,7 @@ function inflateOnData (chunk) { * @param {Error} err The emitted error * @private */ -function inflateOnError (err) { +function inflateOnError(err) { // // There is no need to call `Zlib#close()` as the handle is automatically // closed when an error is emitted. diff --git a/lib/receiver.js b/lib/receiver.js index 81dc0bf8a..420ccf957 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -27,7 +27,7 @@ class Receiver extends stream.Writable { * @param {Object} extensions An object containing the negotiated extensions * @param {Number} maxPayload The maximum allowed message length */ - constructor (binaryType, extensions, maxPayload) { + constructor(binaryType, extensions, maxPayload) { super(); this._binaryType = binaryType || constants.BINARY_TYPES[0]; @@ -61,7 +61,7 @@ class Receiver extends stream.Writable { * @param {String} encoding The character encoding of `chunk` * @param {Function} cb Callback */ - _write (chunk, encoding, cb) { + _write(chunk, encoding, cb) { if (this._opcode === 0x08) return cb(); this._bufferedBytes += chunk.length; @@ -76,7 +76,7 @@ class Receiver extends stream.Writable { * @return {Buffer} The consumed bytes * @private */ - consume (n) { + consume(n) { this._bufferedBytes -= n; if (n === this._buffers[0].length) return this._buffers.shift(); @@ -111,7 +111,7 @@ class Receiver extends stream.Writable { * @param {Function} cb Callback * @private */ - startLoop (cb) { + startLoop(cb) { var err; this._loop = true; @@ -132,7 +132,8 @@ class Receiver extends stream.Writable { case GET_DATA: err = this.getData(cb); break; - default: // `INFLATING` + default: + // `INFLATING` this._loop = false; return; } @@ -147,7 +148,7 @@ class Receiver extends stream.Writable { * @return {(RangeError|undefined)} A possible error * @private */ - getInfo () { + getInfo() { if (this._bufferedBytes < 2) { this._loop = false; return; @@ -229,7 +230,7 @@ class Receiver extends stream.Writable { * @return {(RangeError|undefined)} A possible error * @private */ - getPayloadLength16 () { + getPayloadLength16() { if (this._bufferedBytes < 2) { this._loop = false; return; @@ -245,7 +246,7 @@ class Receiver extends stream.Writable { * @return {(RangeError|undefined)} A possible error * @private */ - getPayloadLength64 () { + getPayloadLength64() { if (this._bufferedBytes < 8) { this._loop = false; return; @@ -278,7 +279,7 @@ class Receiver extends stream.Writable { * @return {(RangeError|undefined)} A possible error * @private */ - haveLength () { + haveLength() { if (this._payloadLength && this._opcode < 0x08) { this._totalPayloadLength += this._payloadLength; if (this._totalPayloadLength > this._maxPayload && this._maxPayload > 0) { @@ -296,7 +297,7 @@ class Receiver extends stream.Writable { * * @private */ - getMask () { + getMask() { if (this._bufferedBytes < 4) { this._loop = false; return; @@ -313,7 +314,7 @@ class Receiver extends stream.Writable { * @return {(Error|RangeError|undefined)} A possible error * @private */ - getData (cb) { + getData(cb) { var data = constants.EMPTY_BUFFER; if (this._payloadLength) { @@ -353,7 +354,7 @@ class Receiver extends stream.Writable { * @param {Function} cb Callback * @private */ - decompress (data, cb) { + decompress(data, cb) { const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; perMessageDeflate.decompress(data, this._fin, (err, buf) => { @@ -362,7 +363,9 @@ class Receiver extends stream.Writable { if (buf.length) { this._messageLength += buf.length; if (this._messageLength > this._maxPayload && this._maxPayload > 0) { - return cb(error(RangeError, 'Max payload size exceeded', false, 1009)); + return cb( + error(RangeError, 'Max payload size exceeded', false, 1009) + ); } this._fragments.push(buf); @@ -381,7 +384,7 @@ class Receiver extends stream.Writable { * @return {(Error|undefined)} A possible error * @private */ - dataMessage () { + dataMessage() { if (this._fin) { const messageLength = this._messageLength; const fragments = this._fragments; @@ -425,7 +428,7 @@ class Receiver extends stream.Writable { * @return {(Error|RangeError|undefined)} A possible error * @private */ - controlMessage (data) { + controlMessage(data) { if (this._opcode === 0x08) { this._loop = false; @@ -474,7 +477,7 @@ module.exports = Receiver; * @return {(Error|RangeError)} The error * @private */ -function error (ErrorCtor, message, prefix, statusCode) { +function error(ErrorCtor, message, prefix, statusCode) { const err = new ErrorCtor( prefix ? `Invalid WebSocket frame: ${message}` : message ); @@ -492,7 +495,7 @@ function error (ErrorCtor, message, prefix, statusCode) { * @return {Buffer} * @private */ -function toBuffer (fragments, messageLength) { +function toBuffer(fragments, messageLength) { if (fragments.length === 1) return fragments[0]; if (fragments.length > 1) return bufferUtil.concat(fragments, messageLength); return constants.EMPTY_BUFFER; @@ -504,7 +507,7 @@ function toBuffer (fragments, messageLength) { * @param {Buffer} The buffer to convert * @return {ArrayBuffer} Converted buffer */ -function toArrayBuffer (buf) { +function toArrayBuffer(buf) { if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) { return buf.buffer; } diff --git a/lib/sender.js b/lib/sender.js index 12ee25a36..d0f30f5c5 100644 --- a/lib/sender.js +++ b/lib/sender.js @@ -17,7 +17,7 @@ class Sender { * @param {net.Socket} socket The connection socket * @param {Object} extensions An object containing the negotiated extensions */ - constructor (socket, extensions) { + constructor(socket, extensions) { this._extensions = extensions || {}; this._socket = socket; @@ -42,7 +42,7 @@ class Sender { * @return {Buffer[]} The framed data as a list of `Buffer` instances * @public */ - static frame (data, options) { + static frame(data, options) { const merge = data.length < 1024 || (options.mask && options.readOnly); var offset = options.mask ? 6 : 2; var payloadLength = data.length; @@ -103,12 +103,15 @@ class Sender { * @param {Function} cb Callback * @public */ - close (code, data, mask, cb) { + close(code, data, mask, cb) { var buf; if (code === undefined) { buf = constants.EMPTY_BUFFER; - } else if (typeof code !== 'number' || !validation.isValidStatusCode(code)) { + } else if ( + typeof code !== 'number' || + !validation.isValidStatusCode(code) + ) { throw new TypeError('First argument must be a valid error code number'); } else if (data === undefined || data === '') { buf = Buffer.allocUnsafe(2); @@ -134,14 +137,17 @@ class Sender { * @param {Function} cb Callback * @private */ - doClose (data, mask, cb) { - this.sendFrame(Sender.frame(data, { - fin: true, - rsv1: false, - opcode: 0x08, - mask, - readOnly: false - }), cb); + doClose(data, mask, cb) { + this.sendFrame( + Sender.frame(data, { + fin: true, + rsv1: false, + opcode: 0x08, + mask, + readOnly: false + }), + cb + ); } /** @@ -152,7 +158,7 @@ class Sender { * @param {Function} cb Callback * @public */ - ping (data, mask, cb) { + ping(data, mask, cb) { var readOnly = true; if (!Buffer.isBuffer(data)) { @@ -182,14 +188,17 @@ class Sender { * @param {Function} cb Callback * @private */ - doPing (data, mask, readOnly, cb) { - this.sendFrame(Sender.frame(data, { - fin: true, - rsv1: false, - opcode: 0x09, - mask, - readOnly - }), cb); + doPing(data, mask, readOnly, cb) { + this.sendFrame( + Sender.frame(data, { + fin: true, + rsv1: false, + opcode: 0x09, + mask, + readOnly + }), + cb + ); } /** @@ -200,7 +209,7 @@ class Sender { * @param {Function} cb Callback * @public */ - pong (data, mask, cb) { + pong(data, mask, cb) { var readOnly = true; if (!Buffer.isBuffer(data)) { @@ -230,14 +239,17 @@ class Sender { * @param {Function} cb Callback * @private */ - doPong (data, mask, readOnly, cb) { - this.sendFrame(Sender.frame(data, { - fin: true, - rsv1: false, - opcode: 0x0a, - mask, - readOnly - }), cb); + doPong(data, mask, readOnly, cb) { + this.sendFrame( + Sender.frame(data, { + fin: true, + rsv1: false, + opcode: 0x0a, + mask, + readOnly + }), + cb + ); } /** @@ -252,7 +264,7 @@ class Sender { * @param {Function} cb Callback * @public */ - send (data, options, cb) { + send(data, options, cb) { var opcode = options.binary ? 2 : 1; var rsv1 = options.compress; var readOnly = true; @@ -298,13 +310,16 @@ class Sender { this.dispatch(data, this._compress, opts, cb); } } else { - this.sendFrame(Sender.frame(data, { - fin: options.fin, - rsv1: false, - opcode, - mask: options.mask, - readOnly - }), cb); + this.sendFrame( + Sender.frame(data, { + fin: options.fin, + rsv1: false, + opcode, + mask: options.mask, + readOnly + }), + cb + ); } } @@ -322,7 +337,7 @@ class Sender { * @param {Function} cb Callback * @private */ - dispatch (data, compress, options, cb) { + dispatch(data, compress, options, cb) { if (!compress) { this.sendFrame(Sender.frame(data, options), cb); return; @@ -354,7 +369,7 @@ class Sender { * * @private */ - dequeue () { + dequeue() { while (!this._deflating && this._queue.length) { const params = this._queue.shift(); @@ -369,7 +384,7 @@ class Sender { * @param {Array} params Send operation parameters. * @private */ - enqueue (params) { + enqueue(params) { this._bufferedBytes += params[1].length; this._queue.push(params); } @@ -381,7 +396,7 @@ class Sender { * @param {Function} cb Callback * @private */ - sendFrame (list, cb) { + sendFrame(list, cb) { if (list.length === 2) { this._socket.write(list[0]); this._socket.write(list[1], cb); @@ -400,7 +415,7 @@ module.exports = Sender; * @return {Buffer} Converted view * @private */ -function viewToBuffer (view) { +function viewToBuffer(view) { const buf = Buffer.from(view.buffer); if (view.byteLength !== view.buffer.byteLength) { diff --git a/lib/validation.js b/lib/validation.js index 06269fcf1..479a7db08 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -3,9 +3,10 @@ try { const isValidUTF8 = require('utf-8-validate'); - exports.isValidUTF8 = typeof isValidUTF8 === 'object' - ? isValidUTF8.Validation.isValidUTF8 // utf-8-validate@<3.0.0 - : isValidUTF8; + exports.isValidUTF8 = + typeof isValidUTF8 === 'object' + ? isValidUTF8.Validation.isValidUTF8 // utf-8-validate@<3.0.0 + : isValidUTF8; } catch (e) /* istanbul ignore next */ { exports.isValidUTF8 = () => true; } diff --git a/lib/websocket-server.js b/lib/websocket-server.js index f24cb484c..deca40838 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -31,22 +31,25 @@ class WebSocketServer extends EventEmitter { * @param {Number} options.maxPayload The maximum allowed message size * @param {Function} callback A listener for the `listening` event */ - constructor (options, callback) { + constructor(options, callback) { super(); - options = Object.assign({ - maxPayload: 100 * 1024 * 1024, - perMessageDeflate: false, - handleProtocols: null, - clientTracking: true, - verifyClient: null, - noServer: false, - backlog: null, // use default (511 as implemented in net.js) - server: null, - host: null, - path: null, - port: null - }, options); + options = Object.assign( + { + maxPayload: 100 * 1024 * 1024, + perMessageDeflate: false, + handleProtocols: null, + clientTracking: true, + verifyClient: null, + noServer: false, + backlog: null, // use default (511 as implemented in net.js) + server: null, + host: null, + path: null, + port: null + }, + options + ); if (options.port == null && !options.server && !options.noServer) { throw new TypeError( @@ -64,7 +67,12 @@ class WebSocketServer extends EventEmitter { }); res.end(body); }); - this._server.listen(options.port, options.host, options.backlog, callback); + this._server.listen( + options.port, + options.host, + options.backlog, + callback + ); } else if (options.server) { this._server = options.server; } @@ -95,7 +103,7 @@ class WebSocketServer extends EventEmitter { * @return {(Object|String|null)} The address of the server * @public */ - address () { + address() { if (this.options.noServer) { throw new Error('The server is operating in "noServer" mode'); } @@ -110,7 +118,7 @@ class WebSocketServer extends EventEmitter { * @param {Function} cb Callback * @public */ - close (cb) { + close(cb) { if (cb) this.once('close', cb); // @@ -145,7 +153,7 @@ class WebSocketServer extends EventEmitter { * @return {Boolean} `true` if the request is valid, else `false` * @public */ - shouldHandle (req) { + shouldHandle(req) { if (this.options.path) { const index = req.url.indexOf('?'); const pathname = index !== -1 ? req.url.slice(0, index) : req.url; @@ -165,15 +173,17 @@ class WebSocketServer extends EventEmitter { * @param {Function} cb Callback * @public */ - handleUpgrade (req, socket, head, cb) { + handleUpgrade(req, socket, head, cb) { socket.on('error', socketOnError); const version = +req.headers['sec-websocket-version']; const extensions = {}; if ( - req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket' || - !req.headers['sec-websocket-key'] || (version !== 8 && version !== 13) || + req.method !== 'GET' || + req.headers.upgrade.toLowerCase() !== 'websocket' || + !req.headers['sec-websocket-key'] || + (version !== 8 && version !== 13) || !this.shouldHandle(req) ) { return abortHandshake(socket, 400); @@ -187,9 +197,7 @@ class WebSocketServer extends EventEmitter { ); try { - const offers = extension.parse( - req.headers['sec-websocket-extensions'] - ); + const offers = extension.parse(req.headers['sec-websocket-extensions']); if (offers[PerMessageDeflate.extensionName]) { perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]); @@ -205,7 +213,8 @@ class WebSocketServer extends EventEmitter { // if (this.options.verifyClient) { const info = { - origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`], + origin: + req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`], secure: !!(req.connection.authorized || req.connection.encrypted), req }; @@ -237,13 +246,14 @@ class WebSocketServer extends EventEmitter { * @param {Function} cb Callback * @private */ - completeUpgrade (extensions, req, socket, head, cb) { + completeUpgrade(extensions, req, socket, head, cb) { // // Destroy the socket if the client has already sent a FIN packet. // if (!socket.readable || !socket.writable) return socket.destroy(); - const key = crypto.createHash('sha1') + const key = crypto + .createHash('sha1') .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary') .digest('base64'); @@ -314,10 +324,10 @@ module.exports = WebSocketServer; * @return {Function} A function that will remove the added listeners when called * @private */ -function addListeners (server, map) { +function addListeners(server, map) { for (const event of Object.keys(map)) server.on(event, map[event]); - return function removeListeners () { + return function removeListeners() { for (const event of Object.keys(map)) { server.removeListener(event, map[event]); } @@ -330,7 +340,7 @@ function addListeners (server, map) { * @param {EventEmitter} server The event emitter * @private */ -function emitClose (server) { +function emitClose(server) { server.emit('close'); } @@ -339,7 +349,7 @@ function emitClose (server) { * * @private */ -function socketOnError () { +function socketOnError() { this.destroy(); } @@ -352,20 +362,25 @@ function socketOnError () { * @param {Object} [headers] Additional HTTP response headers * @private */ -function abortHandshake (socket, code, message, headers) { +function abortHandshake(socket, code, message, headers) { if (socket.writable) { message = message || http.STATUS_CODES[code]; - headers = Object.assign({ - 'Connection': 'close', - 'Content-type': 'text/html', - 'Content-Length': Buffer.byteLength(message) - }, headers); + headers = Object.assign( + { + Connection: 'close', + 'Content-type': 'text/html', + 'Content-Length': Buffer.byteLength(message) + }, + headers + ); socket.write( `HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` + - Object.keys(headers).map(h => `${h}: ${headers[h]}`).join('\r\n') + - '\r\n\r\n' + - message + Object.keys(headers) + .map((h) => `${h}: ${headers[h]}`) + .join('\r\n') + + '\r\n\r\n' + + message ); } diff --git a/lib/websocket.js b/lib/websocket.js index 5b8497d68..4e4540c2f 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -33,7 +33,7 @@ class WebSocket extends EventEmitter { * @param {(String|String[])} protocols The subprotocols * @param {Object} options Connection options */ - constructor (address, protocols, options) { + constructor(address, protocols, options) { super(); this.readyState = WebSocket.CONNECTING; @@ -63,10 +63,18 @@ class WebSocket extends EventEmitter { } } - get CONNECTING () { return WebSocket.CONNECTING; } - get CLOSING () { return WebSocket.CLOSING; } - get CLOSED () { return WebSocket.CLOSED; } - get OPEN () { return WebSocket.OPEN; } + get CONNECTING() { + return WebSocket.CONNECTING; + } + get CLOSING() { + return WebSocket.CLOSING; + } + get CLOSED() { + return WebSocket.CLOSED; + } + get OPEN() { + return WebSocket.OPEN; + } /** * This deviates from the WHATWG interface since ws doesn't support the required @@ -74,11 +82,11 @@ class WebSocket extends EventEmitter { * * @type {String} */ - get binaryType () { + get binaryType() { return this._binaryType; } - set binaryType (type) { + set binaryType(type) { if (constants.BINARY_TYPES.indexOf(type) < 0) return; this._binaryType = type; @@ -92,7 +100,7 @@ class WebSocket extends EventEmitter { /** * @type {Number} */ - get bufferedAmount () { + get bufferedAmount() { if (!this._socket) return 0; // @@ -104,7 +112,7 @@ class WebSocket extends EventEmitter { /** * @type {String} */ - get extensions () { + get extensions() { return Object.keys(this._extensions).join(); } @@ -116,7 +124,7 @@ class WebSocket extends EventEmitter { * @param {Number} maxPayload The maximum allowed message size * @private */ - setSocket (socket, head, maxPayload) { + setSocket(socket, head, maxPayload) { const receiver = new Receiver( this._binaryType, this._extensions, @@ -156,7 +164,7 @@ class WebSocket extends EventEmitter { * * @private */ - emitClose () { + emitClose() { this.readyState = WebSocket.CLOSED; if (!this._socket) { @@ -191,7 +199,7 @@ class WebSocket extends EventEmitter { * @param {String} data A string explaining why the connection is closing * @public */ - close (code, data) { + close(code, data) { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { const msg = 'WebSocket was closed before the connection was established'; @@ -236,7 +244,7 @@ class WebSocket extends EventEmitter { * @param {Function} cb Callback which is executed when the ping is sent * @public */ - ping (data, mask, cb) { + ping(data, mask, cb) { if (typeof data === 'function') { cb = data; data = mask = undefined; @@ -268,7 +276,7 @@ class WebSocket extends EventEmitter { * @param {Function} cb Callback which is executed when the pong is sent * @public */ - pong (data, mask, cb) { + pong(data, mask, cb) { if (typeof data === 'function') { cb = data; data = mask = undefined; @@ -304,7 +312,7 @@ class WebSocket extends EventEmitter { * @param {Function} cb Callback which is executed when data is written out * @public */ - send (data, options, cb) { + send(data, options, cb) { if (typeof options === 'function') { cb = options; options = {}; @@ -322,12 +330,15 @@ class WebSocket extends EventEmitter { if (typeof data === 'number') data = data.toString(); - const opts = Object.assign({ - binary: typeof data !== 'string', - mask: !this._isServer, - compress: true, - fin: true - }, options); + const opts = Object.assign( + { + binary: typeof data !== 'string', + mask: !this._isServer, + compress: true, + fin: true + }, + options + ); if (!this._extensions[PerMessageDeflate.extensionName]) { opts.compress = false; @@ -341,7 +352,7 @@ class WebSocket extends EventEmitter { * * @public */ - terminate () { + terminate() { if (this.readyState === WebSocket.CLOSED) return; if (this.readyState === WebSocket.CONNECTING) { const msg = 'WebSocket was closed before the connection was established'; @@ -371,11 +382,13 @@ readyStates.forEach((readyState, i) => { * @return {(Function|undefined)} The event listener or `undefined` * @public */ - get () { + get() { const listeners = this.listeners(method); for (var i = 0; i < listeners.length; i++) { if (listeners[i]._listener) return listeners[i]._listener; } + + return undefined; }, /** * Add a listener for the event. @@ -383,7 +396,7 @@ readyStates.forEach((readyState, i) => { * @param {Function} listener The listener to add * @public */ - set (listener) { + set(listener) { const listeners = this.listeners(method); for (var i = 0; i < listeners.length; i++) { // @@ -414,23 +427,27 @@ module.exports = WebSocket; * @param {Number} options.maxPayload The maximum allowed message size * @private */ -function initAsClient (address, protocols, options) { - options = Object.assign({ - protocolVersion: protocolVersions[1], - perMessageDeflate: true, - maxPayload: 100 * 1024 * 1024 - }, options, { - createConnection: undefined, - socketPath: undefined, - hostname: undefined, - protocol: undefined, - timeout: undefined, - method: undefined, - auth: undefined, - host: undefined, - path: undefined, - port: undefined - }); +function initAsClient(address, protocols, options) { + options = Object.assign( + { + protocolVersion: protocolVersions[1], + perMessageDeflate: true, + maxPayload: 100 * 1024 * 1024 + }, + options, + { + createConnection: undefined, + socketPath: undefined, + hostname: undefined, + protocol: undefined, + timeout: undefined, + method: undefined, + auth: undefined, + host: undefined, + path: undefined, + port: undefined + } + ); if (protocolVersions.indexOf(options.protocolVersion) === -1) { throw new RangeError( @@ -457,7 +474,8 @@ function initAsClient (address, protocols, options) { throw new Error(`Invalid URL: ${this.url}`); } - const isSecure = parsedUrl.protocol === 'wss:' || parsedUrl.protocol === 'https:'; + const isSecure = + parsedUrl.protocol === 'wss:' || parsedUrl.protocol === 'https:'; const key = crypto.randomBytes(16).toString('base64'); const httpObj = isSecure ? https : http; const path = parsedUrl.search @@ -470,12 +488,15 @@ function initAsClient (address, protocols, options) { options.host = parsedUrl.hostname.startsWith('[') ? parsedUrl.hostname.slice(1, -1) : parsedUrl.hostname; - options.headers = Object.assign({ - 'Sec-WebSocket-Version': options.protocolVersion, - 'Sec-WebSocket-Key': key, - 'Connection': 'Upgrade', - 'Upgrade': 'websocket' - }, options.headers); + options.headers = Object.assign( + { + 'Sec-WebSocket-Version': options.protocolVersion, + 'Sec-WebSocket-Key': key, + Connection: 'Upgrade', + Upgrade: 'websocket' + }, + options.headers + ); options.path = path; if (options.perMessageDeflate) { @@ -511,12 +532,11 @@ function initAsClient (address, protocols, options) { options.path = parts[1]; } - var req = this._req = httpObj.get(options); + var req = (this._req = httpObj.get(options)); if (options.handshakeTimeout) { - req.setTimeout( - options.handshakeTimeout, - () => abortHandshake(this, req, 'Opening handshake has timed out') + req.setTimeout(options.handshakeTimeout, () => + abortHandshake(this, req, 'Opening handshake has timed out') ); } @@ -546,7 +566,8 @@ function initAsClient (address, protocols, options) { req = this._req = null; - const digest = crypto.createHash('sha1') + const digest = crypto + .createHash('sha1') .update(key + constants.GUID, 'binary') .digest('base64'); @@ -581,9 +602,7 @@ function initAsClient (address, protocols, options) { ); if (extensions[PerMessageDeflate.extensionName]) { - perMessageDeflate.accept( - extensions[PerMessageDeflate.extensionName] - ); + perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]); this._extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } } catch (err) { @@ -603,7 +622,7 @@ function initAsClient (address, protocols, options) { * @return {net.Socket} The newly created socket used to start the connection * @private */ -function netConnect (options) { +function netConnect(options) { // // Override `options.path` only if `options` is a copy of the original options // object. This is always true on Node.js >= 8 but not on Node.js 6 where @@ -621,7 +640,7 @@ function netConnect (options) { * @return {tls.TLSSocket} The newly created socket used to start the connection * @private */ -function tlsConnect (options) { +function tlsConnect(options) { options.path = undefined; options.servername = options.servername || options.host; return tls.connect(options); @@ -636,7 +655,7 @@ function tlsConnect (options) { * @param {String} message The error message * @private */ -function abortHandshake (websocket, stream, message) { +function abortHandshake(websocket, stream, message) { websocket.readyState = WebSocket.CLOSING; const err = new Error(message); @@ -660,7 +679,7 @@ function abortHandshake (websocket, stream, message) { * @param {String} reason The reason for closing * @private */ -function receiverOnConclude (code, reason) { +function receiverOnConclude(code, reason) { const websocket = this[kWebSocket]; websocket._socket.removeListener('data', socketOnData); @@ -679,7 +698,7 @@ function receiverOnConclude (code, reason) { * * @private */ -function receiverOnDrain () { +function receiverOnDrain() { this[kWebSocket]._socket.resume(); } @@ -689,7 +708,7 @@ function receiverOnDrain () { * @param {(RangeError|Error)} err The emitted error * @private */ -function receiverOnError (err) { +function receiverOnError(err) { const websocket = this[kWebSocket]; websocket._socket.removeListener('data', socketOnData); @@ -705,7 +724,7 @@ function receiverOnError (err) { * * @private */ -function receiverOnFinish () { +function receiverOnFinish() { this[kWebSocket].emitClose(); } @@ -715,7 +734,7 @@ function receiverOnFinish () { * @param {(String|Buffer|ArrayBuffer|Buffer[])} data The message * @private */ -function receiverOnMessage (data) { +function receiverOnMessage(data) { this[kWebSocket].emit('message', data); } @@ -725,7 +744,7 @@ function receiverOnMessage (data) { * @param {Buffer} data The data included in the ping frame * @private */ -function receiverOnPing (data) { +function receiverOnPing(data) { const websocket = this[kWebSocket]; websocket.pong(data, !websocket._isServer, constants.NOOP); @@ -738,7 +757,7 @@ function receiverOnPing (data) { * @param {Buffer} data The data included in the pong frame * @private */ -function receiverOnPong (data) { +function receiverOnPong(data) { this[kWebSocket].emit('pong', data); } @@ -747,7 +766,7 @@ function receiverOnPong (data) { * * @private */ -function socketOnClose () { +function socketOnClose() { const websocket = this[kWebSocket]; this.removeListener('close', socketOnClose); @@ -790,7 +809,7 @@ function socketOnClose () { * @param {Buffer} chunk A chunk of data * @private */ -function socketOnData (chunk) { +function socketOnData(chunk) { if (!this[kWebSocket]._receiver.write(chunk)) { this.pause(); } @@ -801,7 +820,7 @@ function socketOnData (chunk) { * * @private */ -function socketOnEnd () { +function socketOnEnd() { const websocket = this[kWebSocket]; websocket.readyState = WebSocket.CLOSING; @@ -814,7 +833,7 @@ function socketOnEnd () { * * @private */ -function socketOnError () { +function socketOnError() { const websocket = this[kWebSocket]; this.removeListener('error', socketOnError); diff --git a/package.json b/package.json index b3a7af865..473b5441d 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,9 @@ "lib/*.js" ], "scripts": { - "test": "eslint . && nyc --reporter=html --reporter=text mocha test/*.test.js", - "integration": "eslint . && mocha test/*.integration.js", - "lint": "eslint ." + "test": "npm run lint && nyc --reporter=html --reporter=text mocha test/*.test.js", + "integration": "npm run lint && mocha test/*.integration.js", + "lint": "eslint . --ignore-path .gitignore && prettylint **/*.{json,md} --ignore-path .gitignore" }, "dependencies": { "async-limiter": "~1.0.0" @@ -34,13 +34,12 @@ "benchmark": "~2.1.4", "bufferutil": "~4.0.0", "eslint": "~5.9.0", - "eslint-config-standard": "~12.0.0", - "eslint-plugin-import": "~2.14.0", - "eslint-plugin-node": "~8.0.0", - "eslint-plugin-promise": "~4.0.1", - "eslint-plugin-standard": "~4.0.0", + "eslint-config-prettier": "~3.3.0", + "eslint-plugin-prettier": "~3.0.0", "mocha": "~5.2.0", "nyc": "~13.1.0", + "prettier": "~1.15.2", + "prettylint": "~1.0.0", "utf-8-validate": "~5.0.0" } } diff --git a/test/autobahn-server.js b/test/autobahn-server.js index 7d3e92915..6e0be43ff 100644 --- a/test/autobahn-server.js +++ b/test/autobahn-server.js @@ -4,7 +4,9 @@ const WebSocket = require('../'); const port = process.argv.length > 2 ? parseInt(process.argv[2]) : 9001; const wss = new WebSocket.Server({ port }, () => { - console.log(`Listening to port ${port}. Use extra argument to define the port`); + console.log( + `Listening to port ${port}. Use extra argument to define the port` + ); }); wss.on('connection', (ws) => { diff --git a/test/autobahn.js b/test/autobahn.js index cf2492494..cdda513a5 100644 --- a/test/autobahn.js +++ b/test/autobahn.js @@ -5,7 +5,7 @@ const WebSocket = require('../'); let currentTest = 1; let testCount; -function nextTest () { +function nextTest() { let ws; if (currentTest > testCount) { @@ -15,7 +15,9 @@ function nextTest () { console.log(`Running test case ${currentTest}/${testCount}`); - ws = new WebSocket(`ws://localhost:9001/runCase?case=${currentTest}&agent=ws`); + ws = new WebSocket( + `ws://localhost:9001/runCase?case=${currentTest}&agent=ws` + ); ws.on('message', (data) => ws.send(data)); ws.on('close', () => { currentTest++; diff --git a/test/extension.test.js b/test/extension.test.js index 7e389811b..a051ab269 100644 --- a/test/extension.test.js +++ b/test/extension.test.js @@ -4,20 +4,20 @@ const assert = require('assert'); const extension = require('../lib/extension'); -describe('extension', function () { - describe('parse', function () { - it('returns an empty object if the argument is `undefined`', function () { +describe('extension', function() { + describe('parse', function() { + it('returns an empty object if the argument is `undefined`', function() { assert.deepStrictEqual(extension.parse(), {}); assert.deepStrictEqual(extension.parse(''), {}); }); - it('parses a single extension', function () { + it('parses a single extension', function() { const extensions = extension.parse('foo'); assert.deepStrictEqual(extensions, { foo: [{}] }); }); - it('parses params', function () { + it('parses params', function() { const extensions = extension.parse('foo;bar;baz=1;bar=2'); assert.deepStrictEqual(extensions, { @@ -25,7 +25,7 @@ describe('extension', function () { }); }); - it('parses multiple extensions', function () { + it('parses multiple extensions', function() { const extensions = extension.parse('foo,bar;baz,foo;baz'); assert.deepStrictEqual(extensions, { @@ -34,7 +34,7 @@ describe('extension', function () { }); }); - it('parses quoted params', function () { + it('parses quoted params', function() { assert.deepStrictEqual(extension.parse('foo;bar="hi"'), { foo: [{ bar: ['hi'] }] }); @@ -57,7 +57,7 @@ describe('extension', function () { ); }); - it('works with names that match `Object.prototype` property names', function () { + it('works with names that match `Object.prototype` property names', function() { const parse = extension.parse; assert.deepStrictEqual(parse('hasOwnProperty, toString'), { @@ -69,7 +69,7 @@ describe('extension', function () { }); }); - it('ignores the optional white spaces', function () { + it('ignores the optional white spaces', function() { const header = 'foo; bar\t; \tbaz=1\t ; bar="1"\t\t, \tqux\t ;norf '; assert.deepStrictEqual(extension.parse(header), { @@ -78,7 +78,7 @@ describe('extension', function () { }); }); - it('throws an error if a name is empty', function () { + it('throws an error if a name is empty', function() { [ [',', 0], ['foo,,', 4], @@ -92,12 +92,14 @@ describe('extension', function () { ].forEach((element) => { assert.throws( () => extension.parse(element[0]), - new RegExp(`^SyntaxError: Unexpected character at index ${element[1]}$`) + new RegExp( + `^SyntaxError: Unexpected character at index ${element[1]}$` + ) ); }); }); - it('throws an error if a white space is misplaced', function () { + it('throws an error if a white space is misplaced', function() { [ ['f oo', 2], ['foo;ba r', 7], @@ -106,12 +108,14 @@ describe('extension', function () { ].forEach((element) => { assert.throws( () => extension.parse(element[0]), - new RegExp(`^SyntaxError: Unexpected character at index ${element[1]}$`) + new RegExp( + `^SyntaxError: Unexpected character at index ${element[1]}$` + ) ); }); }); - it('throws an error if a token contains invalid characters', function () { + it('throws an error if a token contains invalid characters', function() { [ ['f@o', 1], ['f\\oo', 1], @@ -130,12 +134,14 @@ describe('extension', function () { ].forEach((element) => { assert.throws( () => extension.parse(element[0]), - new RegExp(`^SyntaxError: Unexpected character at index ${element[1]}$`) + new RegExp( + `^SyntaxError: Unexpected character at index ${element[1]}$` + ) ); }); }); - it('throws an error if the header value ends prematurely', function () { + it('throws an error if the header value ends prematurely', function() { [ 'foo, ', 'foo;', @@ -153,20 +159,20 @@ describe('extension', function () { }); }); - describe('format', function () { - it('formats a single extension', function () { + describe('format', function() { + it('formats a single extension', function() { const extensions = extension.format({ foo: {} }); assert.strictEqual(extensions, 'foo'); }); - it('formats params', function () { + it('formats params', function() { const extensions = extension.format({ foo: { bar: [true, 2], baz: 1 } }); assert.strictEqual(extensions, 'foo; bar; bar=2; baz=1'); }); - it('formats multiple extensions', function () { + it('formats multiple extensions', function() { const extensions = extension.format({ foo: [{}, { baz: true }], bar: { baz: true } diff --git a/test/permessage-deflate.test.js b/test/permessage-deflate.test.js index 15562431f..71c652760 100644 --- a/test/permessage-deflate.test.js +++ b/test/permessage-deflate.test.js @@ -5,18 +5,17 @@ const assert = require('assert'); const PerMessageDeflate = require('../lib/permessage-deflate'); const extension = require('../lib/extension'); -describe('PerMessageDeflate', function () { - describe('#offer', function () { - it('creates an offer', function () { +describe('PerMessageDeflate', function() { + describe('#offer', function() { + it('creates an offer', function() { const perMessageDeflate = new PerMessageDeflate(); - assert.deepStrictEqual( - perMessageDeflate.offer(), - { client_max_window_bits: true } - ); + assert.deepStrictEqual(perMessageDeflate.offer(), { + client_max_window_bits: true + }); }); - it('uses the configuration options', function () { + it('uses the configuration options', function() { const perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: true, clientNoContextTakeover: true, @@ -33,8 +32,8 @@ describe('PerMessageDeflate', function () { }); }); - describe('#accept', function () { - it('throws an error if a parameter has multiple values', function () { + describe('#accept', function() { + it('throws an error if a parameter has multiple values', function() { const perMessageDeflate = new PerMessageDeflate(); const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; server_no_context_takeover' @@ -46,7 +45,7 @@ describe('PerMessageDeflate', function () { ); }); - it('throws an error if a parameter has an invalid name', function () { + it('throws an error if a parameter has an invalid name', function() { const perMessageDeflate = new PerMessageDeflate(); const extensions = extension.parse('permessage-deflate;foo'); @@ -56,9 +55,11 @@ describe('PerMessageDeflate', function () { ); }); - it('throws an error if client_no_context_takeover has a value', function () { + it('throws an error if client_no_context_takeover has a value', function() { const perMessageDeflate = new PerMessageDeflate(); - const extensions = extension.parse('permessage-deflate; client_no_context_takeover=10'); + const extensions = extension.parse( + 'permessage-deflate; client_no_context_takeover=10' + ); assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), @@ -66,9 +67,11 @@ describe('PerMessageDeflate', function () { ); }); - it('throws an error if server_no_context_takeover has a value', function () { + it('throws an error if server_no_context_takeover has a value', function() { const perMessageDeflate = new PerMessageDeflate(); - const extensions = extension.parse('permessage-deflate; server_no_context_takeover=10'); + const extensions = extension.parse( + 'permessage-deflate; server_no_context_takeover=10' + ); assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), @@ -76,78 +79,102 @@ describe('PerMessageDeflate', function () { ); }); - it('throws an error if server_max_window_bits has an invalid value', function () { + it('throws an error if server_max_window_bits has an invalid value', function() { const perMessageDeflate = new PerMessageDeflate(); - let extensions = extension.parse('permessage-deflate; server_max_window_bits=7'); + let extensions = extension.parse( + 'permessage-deflate; server_max_window_bits=7' + ); assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), /^TypeError: Invalid value for parameter "server_max_window_bits": 7$/ ); - extensions = extension.parse('permessage-deflate; server_max_window_bits'); + extensions = extension.parse( + 'permessage-deflate; server_max_window_bits' + ); assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), /^TypeError: Invalid value for parameter "server_max_window_bits": true$/ ); }); - describe('As server', function () { - it('accepts an offer with no parameters', function () { + describe('As server', function() { + it('accepts an offer with no parameters', function() { const perMessageDeflate = new PerMessageDeflate({}, true); assert.deepStrictEqual(perMessageDeflate.accept([{}]), {}); }); - it('accepts an offer with parameters', function () { + it('accepts an offer with parameters', function() { const perMessageDeflate = new PerMessageDeflate({}, true); const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; ' + - 'client_no_context_takeover; server_max_window_bits=10; ' + - 'client_max_window_bits=11' + 'client_no_context_takeover; server_max_window_bits=10; ' + + 'client_max_window_bits=11' ); - assert.deepStrictEqual(perMessageDeflate.accept(extensions['permessage-deflate']), { - server_no_context_takeover: true, - client_no_context_takeover: true, - server_max_window_bits: 10, - client_max_window_bits: 11 - }); + assert.deepStrictEqual( + perMessageDeflate.accept(extensions['permessage-deflate']), + { + server_no_context_takeover: true, + client_no_context_takeover: true, + server_max_window_bits: 10, + client_max_window_bits: 11 + } + ); }); - it('prefers the configuration options', function () { - const perMessageDeflate = new PerMessageDeflate({ - serverNoContextTakeover: true, - clientNoContextTakeover: true, - serverMaxWindowBits: 12, - clientMaxWindowBits: 11 - }, true); + it('prefers the configuration options', function() { + const perMessageDeflate = new PerMessageDeflate( + { + serverNoContextTakeover: true, + clientNoContextTakeover: true, + serverMaxWindowBits: 12, + clientMaxWindowBits: 11 + }, + true + ); const extensions = extension.parse( 'permessage-deflate; server_max_window_bits=14; client_max_window_bits=13' ); - assert.deepStrictEqual(perMessageDeflate.accept(extensions['permessage-deflate']), { - server_no_context_takeover: true, - client_no_context_takeover: true, - server_max_window_bits: 12, - client_max_window_bits: 11 - }); + assert.deepStrictEqual( + perMessageDeflate.accept(extensions['permessage-deflate']), + { + server_no_context_takeover: true, + client_no_context_takeover: true, + server_max_window_bits: 12, + client_max_window_bits: 11 + } + ); }); - it('accepts the first supported offer', function () { - const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); + it('accepts the first supported offer', function() { + const perMessageDeflate = new PerMessageDeflate( + { serverMaxWindowBits: 11 }, + true + ); const extensions = extension.parse( 'permessage-deflate; server_max_window_bits=10, permessage-deflate' ); - assert.deepStrictEqual(perMessageDeflate.accept(extensions['permessage-deflate']), { - server_max_window_bits: 11 - }); + assert.deepStrictEqual( + perMessageDeflate.accept(extensions['permessage-deflate']), + { + server_max_window_bits: 11 + } + ); }); - it('throws an error if server_no_context_takeover is unsupported', function () { - const perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: false }, true); - const extensions = extension.parse('permessage-deflate; server_no_context_takeover'); + it('throws an error if server_no_context_takeover is unsupported', function() { + const perMessageDeflate = new PerMessageDeflate( + { serverNoContextTakeover: false }, + true + ); + const extensions = extension.parse( + 'permessage-deflate; server_no_context_takeover' + ); assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), @@ -155,9 +182,14 @@ describe('PerMessageDeflate', function () { ); }); - it('throws an error if server_max_window_bits is unsupported', function () { - const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: false }, true); - const extensions = extension.parse('permessage-deflate; server_max_window_bits=10'); + it('throws an error if server_max_window_bits is unsupported', function() { + const perMessageDeflate = new PerMessageDeflate( + { serverMaxWindowBits: false }, + true + ); + const extensions = extension.parse( + 'permessage-deflate; server_max_window_bits=10' + ); assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), @@ -165,9 +197,14 @@ describe('PerMessageDeflate', function () { ); }); - it('throws an error if server_max_window_bits is less than configuration', function () { - const perMessageDeflate = new PerMessageDeflate({ serverMaxWindowBits: 11 }, true); - const extensions = extension.parse('permessage-deflate; server_max_window_bits=10'); + it('throws an error if server_max_window_bits is less than configuration', function() { + const perMessageDeflate = new PerMessageDeflate( + { serverMaxWindowBits: 11 }, + true + ); + const extensions = extension.parse( + 'permessage-deflate; server_max_window_bits=10' + ); assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), @@ -175,8 +212,11 @@ describe('PerMessageDeflate', function () { ); }); - it('throws an error if client_max_window_bits is unsupported on client', function () { - const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }, true); + it('throws an error if client_max_window_bits is unsupported on client', function() { + const perMessageDeflate = new PerMessageDeflate( + { clientMaxWindowBits: 10 }, + true + ); const extensions = extension.parse('permessage-deflate'); assert.throws( @@ -185,10 +225,12 @@ describe('PerMessageDeflate', function () { ); }); - it('throws an error if client_max_window_bits has an invalid value', function () { + it('throws an error if client_max_window_bits has an invalid value', function() { const perMessageDeflate = new PerMessageDeflate({}, true); - const extensions = extension.parse('permessage-deflate; client_max_window_bits=16'); + const extensions = extension.parse( + 'permessage-deflate; client_max_window_bits=16' + ); assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), /^TypeError: Invalid value for parameter "client_max_window_bits": 16$/ @@ -196,32 +238,39 @@ describe('PerMessageDeflate', function () { }); }); - describe('As client', function () { - it('accepts a response with no parameters', function () { + describe('As client', function() { + it('accepts a response with no parameters', function() { const perMessageDeflate = new PerMessageDeflate({}); assert.deepStrictEqual(perMessageDeflate.accept([{}]), {}); }); - it('accepts a response with parameters', function () { + it('accepts a response with parameters', function() { const perMessageDeflate = new PerMessageDeflate({}); const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; ' + - 'client_no_context_takeover; server_max_window_bits=10; ' + - 'client_max_window_bits=11' + 'client_no_context_takeover; server_max_window_bits=10; ' + + 'client_max_window_bits=11' ); - assert.deepStrictEqual(perMessageDeflate.accept(extensions['permessage-deflate']), { - server_no_context_takeover: true, - client_no_context_takeover: true, - server_max_window_bits: 10, - client_max_window_bits: 11 - }); + assert.deepStrictEqual( + perMessageDeflate.accept(extensions['permessage-deflate']), + { + server_no_context_takeover: true, + client_no_context_takeover: true, + server_max_window_bits: 10, + client_max_window_bits: 11 + } + ); }); - it('throws an error if client_no_context_takeover is unsupported', function () { - const perMessageDeflate = new PerMessageDeflate({ clientNoContextTakeover: false }); - const extensions = extension.parse('permessage-deflate; client_no_context_takeover'); + it('throws an error if client_no_context_takeover is unsupported', function() { + const perMessageDeflate = new PerMessageDeflate({ + clientNoContextTakeover: false + }); + const extensions = extension.parse( + 'permessage-deflate; client_no_context_takeover' + ); assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), @@ -229,9 +278,13 @@ describe('PerMessageDeflate', function () { ); }); - it('throws an error if client_max_window_bits is unsupported', function () { - const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: false }); - const extensions = extension.parse('permessage-deflate; client_max_window_bits=10'); + it('throws an error if client_max_window_bits is unsupported', function() { + const perMessageDeflate = new PerMessageDeflate({ + clientMaxWindowBits: false + }); + const extensions = extension.parse( + 'permessage-deflate; client_max_window_bits=10' + ); assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), @@ -239,9 +292,13 @@ describe('PerMessageDeflate', function () { ); }); - it('throws an error if client_max_window_bits is greater than configuration', function () { - const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }); - const extensions = extension.parse('permessage-deflate; client_max_window_bits=11'); + it('throws an error if client_max_window_bits is greater than configuration', function() { + const perMessageDeflate = new PerMessageDeflate({ + clientMaxWindowBits: 10 + }); + const extensions = extension.parse( + 'permessage-deflate; client_max_window_bits=11' + ); assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), @@ -249,23 +306,27 @@ describe('PerMessageDeflate', function () { ); }); - it('throws an error if client_max_window_bits has an invalid value', function () { + it('throws an error if client_max_window_bits has an invalid value', function() { const perMessageDeflate = new PerMessageDeflate(); - let extensions = extension.parse('permessage-deflate; client_max_window_bits=16'); + let extensions = extension.parse( + 'permessage-deflate; client_max_window_bits=16' + ); assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), /^TypeError: Invalid value for parameter "client_max_window_bits": 16$/ ); - extensions = extension.parse('permessage-deflate; client_max_window_bits'); + extensions = extension.parse( + 'permessage-deflate; client_max_window_bits' + ); assert.throws( () => perMessageDeflate.accept(extensions['permessage-deflate']), /^TypeError: Invalid value for parameter "client_max_window_bits": true$/ ); }); - it('uses the config value if client_max_window_bits is not specified', function () { + it('uses the config value if client_max_window_bits is not specified', function() { const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }); @@ -277,8 +338,8 @@ describe('PerMessageDeflate', function () { }); }); - describe('#compress and #decompress', function () { - it('works with unfragmented messages', function (done) { + describe('#compress and #decompress', function() { + it('works with unfragmented messages', function(done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const buf = Buffer.from([1, 2, 3]); @@ -295,7 +356,7 @@ describe('PerMessageDeflate', function () { }); }); - it('works with fragmented messages', function (done) { + it('works with fragmented messages', function(done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const buf = Buffer.from([1, 2, 3, 4]); @@ -321,7 +382,7 @@ describe('PerMessageDeflate', function () { }); }); - it('works with the negotiated parameters', function (done) { + it('works with the negotiated parameters', function(done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0, memLevel: 5, @@ -329,8 +390,8 @@ describe('PerMessageDeflate', function () { }); const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; ' + - 'client_no_context_takeover; server_max_window_bits=10; ' + - 'client_max_window_bits=11' + 'client_no_context_takeover; server_max_window_bits=10; ' + + 'client_max_window_bits=11' ); const buf = Buffer.from("Some compressible data, it's compressible."); @@ -348,7 +409,7 @@ describe('PerMessageDeflate', function () { }); }); - it('honors the `level` option', function (done) { + it('honors the `level` option', function(done) { const lev0 = new PerMessageDeflate({ threshold: 0, zlibDeflateOptions: { level: 0 } @@ -357,11 +418,10 @@ describe('PerMessageDeflate', function () { threshold: 0, zlibDeflateOptions: { level: 9 } }); - const extensionStr = ( + const extensionStr = 'permessage-deflate; server_no_context_takeover; ' + 'client_no_context_takeover; server_max_window_bits=10; ' + - 'client_max_window_bits=11' - ); + 'client_max_window_bits=11'; const buf = Buffer.from("Some compressible data, it's compressible."); lev0.accept(extension.parse(extensionStr)['permessage-deflate']); @@ -393,7 +453,7 @@ describe('PerMessageDeflate', function () { }); }); - it('honors the `zlib{Deflate,Inflate}Options` option', function (done) { + it('honors the `zlib{Deflate,Inflate}Options` option', function(done) { const lev0 = new PerMessageDeflate({ threshold: 0, zlibDeflateOptions: { @@ -417,10 +477,9 @@ describe('PerMessageDeflate', function () { // Note no context takeover so we can get a hold of the raw streams after // we do the dance. - const extensionStr = ( + const extensionStr = 'permessage-deflate; server_max_window_bits=10; ' + - 'client_max_window_bits=11' - ); + 'client_max_window_bits=11'; const buf = Buffer.from("Some compressible data, it's compressible."); lev0.accept(extension.parse(extensionStr)['permessage-deflate']); @@ -459,7 +518,7 @@ describe('PerMessageDeflate', function () { }); }); - it("doesn't use contex takeover if not allowed", function (done) { + it("doesn't use contex takeover if not allowed", function(done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }, true); const extensions = extension.parse( 'permessage-deflate;server_no_context_takeover' @@ -490,7 +549,7 @@ describe('PerMessageDeflate', function () { }); }); - it('uses contex takeover if allowed', function (done) { + it('uses contex takeover if allowed', function(done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }, true); const extensions = extension.parse('permessage-deflate'); const buf = Buffer.from('foofoo'); @@ -519,7 +578,7 @@ describe('PerMessageDeflate', function () { }); }); - it('calls the callback when an error occurs (inflate)', function (done) { + it('calls the callback when an error occurs (inflate)', function(done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const data = Buffer.from('something invalid'); @@ -531,8 +590,12 @@ describe('PerMessageDeflate', function () { }); }); - it("doesn't call the callback twice when `maxPayload` is exceeded", function (done) { - const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }, false, 25); + it("doesn't call the callback twice when `maxPayload` is exceeded", function(done) { + const perMessageDeflate = new PerMessageDeflate( + { threshold: 0 }, + false, + 25 + ); const buf = Buffer.from('A'.repeat(50)); perMessageDeflate.accept([{}]); diff --git a/test/receiver.test.js b/test/receiver.test.js index 21497e1ca..6211b31a2 100644 --- a/test/receiver.test.js +++ b/test/receiver.test.js @@ -10,8 +10,8 @@ const Sender = require('../lib/sender'); const kStatusCode = constants.kStatusCode; -describe('Receiver', function () { - it('parses an unmasked text message', function (done) { +describe('Receiver', function() { + it('parses an unmasked text message', function(done) { const receiver = new Receiver(); receiver.on('message', (data) => { @@ -22,7 +22,7 @@ describe('Receiver', function () { receiver.write(Buffer.from('810548656c6c6f', 'hex')); }); - it('parses a close message', function (done) { + it('parses a close message', function(done) { const receiver = new Receiver(); receiver.on('conclude', (code, data) => { @@ -34,7 +34,7 @@ describe('Receiver', function () { receiver.write(Buffer.from('8800', 'hex')); }); - it('parses a masked text message', function (done) { + it('parses a masked text message', function(done) { const receiver = new Receiver(); receiver.on('message', (data) => { @@ -47,7 +47,7 @@ describe('Receiver', function () { ); }); - it('parses a masked text message longer than 125 B', function (done) { + it('parses a masked text message longer than 125 B', function(done) { const receiver = new Receiver(); const msg = 'A'.repeat(200); @@ -70,7 +70,7 @@ describe('Receiver', function () { setImmediate(() => receiver.write(frame.slice(2))); }); - it('parses a really long masked text message', function (done) { + it('parses a really long masked text message', function(done) { const receiver = new Receiver(); const msg = 'A'.repeat(64 * 1024); @@ -92,7 +92,7 @@ describe('Receiver', function () { receiver.write(frame); }); - it('parses a 300 B fragmented masked text message', function (done) { + it('parses a 300 B fragmented masked text message', function(done) { const receiver = new Receiver(); const msg = 'A'.repeat(300); @@ -101,14 +101,18 @@ describe('Receiver', function () { const options = { rsv1: false, mask: true, readOnly: false }; - const frame1 = Buffer.concat(Sender.frame( - Buffer.from(fragment1), - Object.assign({ fin: false, opcode: 0x01 }, options) - )); - const frame2 = Buffer.concat(Sender.frame( - Buffer.from(fragment2), - Object.assign({ fin: true, opcode: 0x00 }, options) - )); + const frame1 = Buffer.concat( + Sender.frame( + Buffer.from(fragment1), + Object.assign({ fin: false, opcode: 0x01 }, options) + ) + ); + const frame2 = Buffer.concat( + Sender.frame( + Buffer.from(fragment2), + Object.assign({ fin: true, opcode: 0x00 }, options) + ) + ); receiver.on('message', (data) => { assert.strictEqual(data, msg); @@ -119,7 +123,7 @@ describe('Receiver', function () { receiver.write(frame2); }); - it('parses a ping message', function (done) { + it('parses a ping message', function(done) { const receiver = new Receiver(); const msg = 'Hello'; @@ -141,7 +145,7 @@ describe('Receiver', function () { receiver.write(frame); }); - it('parses a ping message with no data', function (done) { + it('parses a ping message with no data', function(done) { const receiver = new Receiver(); receiver.on('ping', (data) => { @@ -152,7 +156,7 @@ describe('Receiver', function () { receiver.write(Buffer.from('8900', 'hex')); }); - it('parses a 300 B fragmented masked text message with a ping in the middle (1/2)', function (done) { + it('parses a 300 B fragmented masked text message with a ping in the middle (1/2)', function(done) { const receiver = new Receiver(); const msg = 'A'.repeat(300); const pingMessage = 'Hello'; @@ -162,18 +166,24 @@ describe('Receiver', function () { const options = { rsv1: false, mask: true, readOnly: false }; - const frame1 = Buffer.concat(Sender.frame( - Buffer.from(fragment1), - Object.assign({ fin: false, opcode: 0x01 }, options) - )); - const frame2 = Buffer.concat(Sender.frame( - Buffer.from(pingMessage), - Object.assign({ fin: true, opcode: 0x09 }, options) - )); - const frame3 = Buffer.concat(Sender.frame( - Buffer.from(fragment2), - Object.assign({ fin: true, opcode: 0x00 }, options) - )); + const frame1 = Buffer.concat( + Sender.frame( + Buffer.from(fragment1), + Object.assign({ fin: false, opcode: 0x01 }, options) + ) + ); + const frame2 = Buffer.concat( + Sender.frame( + Buffer.from(pingMessage), + Object.assign({ fin: true, opcode: 0x09 }, options) + ) + ); + const frame3 = Buffer.concat( + Sender.frame( + Buffer.from(fragment2), + Object.assign({ fin: true, opcode: 0x00 }, options) + ) + ); let gotPing = false; @@ -192,7 +202,7 @@ describe('Receiver', function () { receiver.write(frame3); }); - it('parses a 300 B fragmented masked text message with a ping in the middle (2/2)', function (done) { + it('parses a 300 B fragmented masked text message with a ping in the middle (2/2)', function(done) { const receiver = new Receiver(); const msg = 'A'.repeat(300); const pingMessage = 'Hello'; @@ -202,18 +212,24 @@ describe('Receiver', function () { const options = { rsv1: false, mask: true, readOnly: false }; - const frame1 = Buffer.concat(Sender.frame( - Buffer.from(fragment1), - Object.assign({ fin: false, opcode: 0x01 }, options) - )); - const frame2 = Buffer.concat(Sender.frame( - Buffer.from(pingMessage), - Object.assign({ fin: true, opcode: 0x09 }, options) - )); - const frame3 = Buffer.concat(Sender.frame( - Buffer.from(fragment2), - Object.assign({ fin: true, opcode: 0x00 }, options) - )); + const frame1 = Buffer.concat( + Sender.frame( + Buffer.from(fragment1), + Object.assign({ fin: false, opcode: 0x01 }, options) + ) + ); + const frame2 = Buffer.concat( + Sender.frame( + Buffer.from(pingMessage), + Object.assign({ fin: true, opcode: 0x09 }, options) + ) + ); + const frame3 = Buffer.concat( + Sender.frame( + Buffer.from(fragment2), + Object.assign({ fin: true, opcode: 0x00 }, options) + ) + ); let chunks = []; const splitBuffer = (buf) => { @@ -242,7 +258,7 @@ describe('Receiver', function () { } }); - it('parses a 100 B masked binary message', function (done) { + it('parses a 100 B masked binary message', function(done) { const receiver = new Receiver(); const msg = crypto.randomBytes(100); @@ -264,7 +280,7 @@ describe('Receiver', function () { receiver.write(frame); }); - it('parses a 256 B masked binary message', function (done) { + it('parses a 256 B masked binary message', function(done) { const receiver = new Receiver(); const msg = crypto.randomBytes(256); @@ -286,7 +302,7 @@ describe('Receiver', function () { receiver.write(frame); }); - it('parses a 200 KiB masked binary message', function (done) { + it('parses a 200 KiB masked binary message', function(done) { const receiver = new Receiver(); const msg = crypto.randomBytes(200 * 1024); @@ -308,7 +324,7 @@ describe('Receiver', function () { receiver.write(frame); }); - it('parses a 200 KiB unmasked binary message', function (done) { + it('parses a 200 KiB unmasked binary message', function(done) { const receiver = new Receiver(); const msg = crypto.randomBytes(200 * 1024); @@ -330,7 +346,7 @@ describe('Receiver', function () { receiver.write(frame); }); - it('parses a compressed message', function (done) { + it('parses a compressed message', function(done) { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); @@ -352,7 +368,7 @@ describe('Receiver', function () { }); }); - it('parses a compressed and fragmented message', function (done) { + it('parses a compressed and fragmented message', function(done) { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); @@ -367,13 +383,13 @@ describe('Receiver', function () { done(); }); - perMessageDeflate.compress(buf1, false, function (err, fragment1) { + perMessageDeflate.compress(buf1, false, function(err, fragment1) { if (err) return done(err); receiver.write(Buffer.from([0x41, fragment1.length])); receiver.write(fragment1); - perMessageDeflate.compress(buf2, true, function (err, fragment2) { + perMessageDeflate.compress(buf2, true, function(err, fragment2) { if (err) return done(err); receiver.write(Buffer.from([0x80, fragment2.length])); @@ -382,7 +398,7 @@ describe('Receiver', function () { }); }); - it('parses a buffer with thousands of frames', function (done) { + it('parses a buffer with thousands of frames', function(done) { const buf = Buffer.allocUnsafe(40000); for (let i = 0; i < buf.length; i += 2) { @@ -401,7 +417,7 @@ describe('Receiver', function () { receiver.write(buf); }); - it('resets `totalPayloadLength` only on final frame (unfragmented)', function (done) { + it('resets `totalPayloadLength` only on final frame (unfragmented)', function(done) { const receiver = new Receiver(undefined, {}, 10); receiver.on('message', (data) => { @@ -414,7 +430,7 @@ describe('Receiver', function () { receiver.write(Buffer.from('810548656c6c6f', 'hex')); }); - it('resets `totalPayloadLength` only on final frame (fragmented)', function (done) { + it('resets `totalPayloadLength` only on final frame (fragmented)', function(done) { const receiver = new Receiver(undefined, {}, 10); receiver.on('message', (data) => { @@ -429,7 +445,7 @@ describe('Receiver', function () { receiver.write(Buffer.from('80036c6c6f', 'hex')); }); - it('resets `totalPayloadLength` only on final frame (fragmented + ping)', function (done) { + it('resets `totalPayloadLength` only on final frame (fragmented + ping)', function(done) { const receiver = new Receiver(undefined, {}, 10); let data; @@ -450,7 +466,7 @@ describe('Receiver', function () { receiver.write(Buffer.from('80036c6c6f', 'hex')); }); - it('ignores any data after a close frame', function (done) { + it('ignores any data after a close frame', function(done) { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); @@ -471,7 +487,7 @@ describe('Receiver', function () { receiver.write(Buffer.from([0x81, 0x00])); }); - it('emits an error if RSV1 is on and permessage-deflate is disabled', function (done) { + it('emits an error if RSV1 is on and permessage-deflate is disabled', function(done) { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -487,7 +503,7 @@ describe('Receiver', function () { receiver.write(Buffer.from([0xc2, 0x80, 0x00, 0x00, 0x00, 0x00])); }); - it('emits an error if RSV1 is on and opcode is 0', function (done) { + it('emits an error if RSV1 is on and opcode is 0', function(done) { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); @@ -508,7 +524,7 @@ describe('Receiver', function () { receiver.write(Buffer.from([0x40, 0x00])); }); - it('emits an error if RSV2 is on', function (done) { + it('emits an error if RSV2 is on', function(done) { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -524,7 +540,7 @@ describe('Receiver', function () { receiver.write(Buffer.from([0xa2, 0x00])); }); - it('emits an error if RSV3 is on', function (done) { + it('emits an error if RSV3 is on', function(done) { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -540,7 +556,7 @@ describe('Receiver', function () { receiver.write(Buffer.from([0x92, 0x00])); }); - it('emits an error if the first frame in a fragmented message has opcode 0', function (done) { + it('emits an error if the first frame in a fragmented message has opcode 0', function(done) { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -556,7 +572,7 @@ describe('Receiver', function () { receiver.write(Buffer.from([0x00, 0x00])); }); - it('emits an error if a frame has opcode 1 in the middle of a fragmented message', function (done) { + it('emits an error if a frame has opcode 1 in the middle of a fragmented message', function(done) { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -573,7 +589,7 @@ describe('Receiver', function () { receiver.write(Buffer.from([0x01, 0x00])); }); - it('emits an error if a frame has opcode 2 in the middle of a fragmented message', function (done) { + it('emits an error if a frame has opcode 2 in the middle of a fragmented message', function(done) { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -590,7 +606,7 @@ describe('Receiver', function () { receiver.write(Buffer.from([0x02, 0x00])); }); - it('emits an error if a control frame has the FIN bit off', function (done) { + it('emits an error if a control frame has the FIN bit off', function(done) { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -606,7 +622,7 @@ describe('Receiver', function () { receiver.write(Buffer.from([0x09, 0x00])); }); - it('emits an error if a control frame has the RSV1 bit on', function (done) { + it('emits an error if a control frame has the RSV1 bit on', function(done) { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); @@ -627,7 +643,7 @@ describe('Receiver', function () { receiver.write(Buffer.from([0xc9, 0x00])); }); - it('emits an error if a control frame has the FIN bit off', function (done) { + it('emits an error if a control frame has the FIN bit off', function(done) { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -643,7 +659,7 @@ describe('Receiver', function () { receiver.write(Buffer.from([0x09, 0x00])); }); - it('emits an error if a control frame has a payload bigger than 125 B', function (done) { + it('emits an error if a control frame has a payload bigger than 125 B', function(done) { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -659,7 +675,7 @@ describe('Receiver', function () { receiver.write(Buffer.from([0x89, 0x7e])); }); - it('emits an error if a data frame has a payload bigger than 2^53 - 1 B', function (done) { + it('emits an error if a data frame has a payload bigger than 2^53 - 1 B', function(done) { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -673,13 +689,14 @@ describe('Receiver', function () { }); receiver.write(Buffer.from([0x82, 0x7f])); - setImmediate(() => receiver.write(Buffer.from([ - 0x00, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - ]))); + setImmediate(() => + receiver.write( + Buffer.from([0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + ) + ); }); - it('emits an error if a text frame contains invalid UTF-8 data', function (done) { + it('emits an error if a text frame contains invalid UTF-8 data', function(done) { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -695,7 +712,7 @@ describe('Receiver', function () { receiver.write(Buffer.from([0x81, 0x04, 0xce, 0xba, 0xe1, 0xbd])); }); - it('emits an error if a close frame has a payload of 1 B', function (done) { + it('emits an error if a close frame has a payload of 1 B', function(done) { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -711,7 +728,7 @@ describe('Receiver', function () { receiver.write(Buffer.from([0x88, 0x01, 0x00])); }); - it('emits an error if a close frame contains an invalid close code', function (done) { + it('emits an error if a close frame contains an invalid close code', function(done) { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -727,7 +744,7 @@ describe('Receiver', function () { receiver.write(Buffer.from([0x88, 0x02, 0x00, 0x00])); }); - it('emits an error if a close frame contains invalid UTF-8 data', function (done) { + it('emits an error if a close frame contains invalid UTF-8 data', function(done) { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -745,7 +762,7 @@ describe('Receiver', function () { ); }); - it('emits an error if a frame payload length is bigger than `maxPayload`', function (done) { + it('emits an error if a frame payload length is bigger than `maxPayload`', function(done) { const receiver = new Receiver(undefined, {}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); @@ -769,13 +786,17 @@ describe('Receiver', function () { receiver.write(frame); }); - it('emits an error if the message length exceeds `maxPayload`', function (done) { + it('emits an error if the message length exceeds `maxPayload`', function(done) { const perMessageDeflate = new PerMessageDeflate({}, false, 25); perMessageDeflate.accept([{}]); - const receiver = new Receiver(undefined, { - 'permessage-deflate': perMessageDeflate - }, 25); + const receiver = new Receiver( + undefined, + { + 'permessage-deflate': perMessageDeflate + }, + 25 + ); const buf = Buffer.from('A'.repeat(50)); receiver.on('error', (err) => { @@ -785,7 +806,7 @@ describe('Receiver', function () { done(); }); - perMessageDeflate.compress(buf, true, function (err, data) { + perMessageDeflate.compress(buf, true, function(err, data) { if (err) return done(err); receiver.write(Buffer.from([0xc1, data.length])); @@ -793,13 +814,17 @@ describe('Receiver', function () { }); }); - it('emits an error if the sum of fragment lengths exceeds `maxPayload`', function (done) { + it('emits an error if the sum of fragment lengths exceeds `maxPayload`', function(done) { const perMessageDeflate = new PerMessageDeflate({}, false, 25); perMessageDeflate.accept([{}]); - const receiver = new Receiver(undefined, { - 'permessage-deflate': perMessageDeflate - }, 25); + const receiver = new Receiver( + undefined, + { + 'permessage-deflate': perMessageDeflate + }, + 25 + ); const buf = Buffer.from('A'.repeat(15)); receiver.on('error', (err) => { @@ -809,13 +834,13 @@ describe('Receiver', function () { done(); }); - perMessageDeflate.compress(buf, false, function (err, fragment1) { + perMessageDeflate.compress(buf, false, function(err, fragment1) { if (err) return done(err); receiver.write(Buffer.from([0x41, fragment1.length])); receiver.write(fragment1); - perMessageDeflate.compress(buf, true, function (err, fragment2) { + perMessageDeflate.compress(buf, true, function(err, fragment2) { if (err) return done(err); receiver.write(Buffer.from([0x80, fragment2.length])); @@ -824,7 +849,7 @@ describe('Receiver', function () { }); }); - it("honors the 'nodebuffer' binary type", function (done) { + it("honors the 'nodebuffer' binary type", function(done) { const receiver = new Receiver(); const frags = [ crypto.randomBytes(7321), @@ -850,7 +875,7 @@ describe('Receiver', function () { }); }); - it("honors the 'arraybuffer' binary type", function (done) { + it("honors the 'arraybuffer' binary type", function(done) { const receiver = new Receiver(); const frags = [ crypto.randomBytes(19221), @@ -876,7 +901,7 @@ describe('Receiver', function () { }); }); - it("honors the 'fragments' binary type", function (done) { + it("honors the 'fragments' binary type", function(done) { const receiver = new Receiver(); const frags = [ crypto.randomBytes(17), diff --git a/test/sender.test.js b/test/sender.test.js index 044075275..7382dfe22 100644 --- a/test/sender.test.js +++ b/test/sender.test.js @@ -6,19 +6,19 @@ const PerMessageDeflate = require('../lib/permessage-deflate'); const Sender = require('../lib/sender'); class MockSocket { - constructor ({ write } = {}) { + constructor({ write } = {}) { this.readable = true; this.writable = true; if (write) this.write = write; } - write () {} + write() {} } -describe('Sender', function () { - describe('.frame', function () { - it('does not mutate the input buffer if data is `readOnly`', function () { +describe('Sender', function() { + describe('.frame', function() { + it('does not mutate the input buffer if data is `readOnly`', function() { const buf = Buffer.from([1, 2, 3, 4, 5]); Sender.frame(buf, { @@ -32,7 +32,7 @@ describe('Sender', function () { assert.ok(buf.equals(Buffer.from([1, 2, 3, 4, 5]))); }); - it('sets RSV1 bit if compressed', function () { + it('sets RSV1 bit if compressed', function() { const list = Sender.frame(Buffer.from('hi'), { readOnly: false, mask: false, @@ -45,8 +45,8 @@ describe('Sender', function () { }); }); - describe('#send', function () { - it('compresses data if compress option is enabled', function (done) { + describe('#send', function() { + it('compresses data if compress option is enabled', function(done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); let count = 0; const mockSocket = new MockSocket({ @@ -69,7 +69,7 @@ describe('Sender', function () { sender.send('hi', options); }); - it('does not compress enqueued messages after socket closes', function (done) { + it('does not compress enqueued messages after socket closes', function(done) { const mockSocket = new MockSocket({ write: () => done(new Error('Unexpected call to socket.write()')) }); @@ -109,7 +109,7 @@ describe('Sender', function () { }); }); - it('does not compress data for small payloads', function (done) { + it('does not compress data for small payloads', function(done) { const perMessageDeflate = new PerMessageDeflate(); const mockSocket = new MockSocket({ write: (data) => { @@ -126,7 +126,7 @@ describe('Sender', function () { sender.send('hi', { compress: true, fin: true }); }); - it('compresses all frames in a fragmented message', function (done) { + it('compresses all frames in a fragmented message', function(done) { const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); const mockSocket = new MockSocket({ @@ -151,7 +151,7 @@ describe('Sender', function () { sender.send('12', { compress: true, fin: true }); }); - it('compresses no frames in a fragmented message', function (done) { + it('compresses no frames in a fragmented message', function(done) { const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); const mockSocket = new MockSocket({ @@ -176,7 +176,7 @@ describe('Sender', function () { sender.send('123', { compress: true, fin: true }); }); - it('compresses empty buffer as first fragment', function (done) { + it('compresses empty buffer as first fragment', function(done) { const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const mockSocket = new MockSocket({ @@ -201,7 +201,7 @@ describe('Sender', function () { sender.send('data', { compress: true, fin: true }); }); - it('compresses empty buffer as last fragment', function (done) { + it('compresses empty buffer as last fragment', function(done) { const fragments = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const mockSocket = new MockSocket({ @@ -226,7 +226,7 @@ describe('Sender', function () { sender.send(Buffer.alloc(0), { compress: true, fin: true }); }); - it('handles many send calls while processing without crashing on flush', function (done) { + it('handles many send calls while processing without crashing on flush', function(done) { let count = 0; const perMessageDeflate = new PerMessageDeflate(); const mockSocket = new MockSocket({ @@ -250,8 +250,8 @@ describe('Sender', function () { }); }); - describe('#ping', function () { - it('works with multiple types of data', function (done) { + describe('#ping', function() { + it('works with multiple types of data', function(done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); let count = 0; const mockSocket = new MockSocket({ @@ -277,8 +277,8 @@ describe('Sender', function () { }); }); - describe('#pong', function () { - it('works with multiple types of data', function (done) { + describe('#pong', function() { + it('works with multiple types of data', function(done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); let count = 0; const mockSocket = new MockSocket({ @@ -304,8 +304,8 @@ describe('Sender', function () { }); }); - describe('#close', function () { - it('should consume all data before closing', function (done) { + describe('#close', function() { + it('should consume all data before closing', function(done) { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); let count = 0; diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 3b1805f1c..f6a394f72 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -11,33 +11,36 @@ const fs = require('fs'); const WebSocket = require('..'); -describe('WebSocketServer', function () { - describe('#ctor', function () { - it('throws an error if no option object is passed', function () { +describe('WebSocketServer', function() { + describe('#ctor', function() { + it('throws an error if no option object is passed', function() { assert.throws(() => new WebSocket.Server()); }); - it('throws an error if no port or server is specified', function () { + it('throws an error if no port or server is specified', function() { assert.throws(() => new WebSocket.Server({})); }); - describe('options', function () { - it('exposes options passed to constructor', function (done) { + describe('options', function() { + it('exposes options passed to constructor', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { assert.strictEqual(wss.options.port, 0); wss.close(done); }); }); - it('accepts the `maxPayload` option', function (done) { + it('accepts the `maxPayload` option', function(done) { const maxPayload = 20480; - const wss = new WebSocket.Server({ - perMessageDeflate: true, - maxPayload, - port: 0 - }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - }); + const wss = new WebSocket.Server( + { + perMessageDeflate: true, + maxPayload, + port: 0 + }, + () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + } + ); wss.on('connection', (ws) => { assert.strictEqual(ws._receiver._maxPayload, maxPayload); @@ -50,7 +53,7 @@ describe('WebSocketServer', function () { }); }); - it('emits an error if http server bind fails', function (done) { + it('emits an error if http server bind fails', function(done) { const wss1 = new WebSocket.Server({ port: 0 }, () => { const wss2 = new WebSocket.Server({ port: wss1.address().port @@ -60,7 +63,7 @@ describe('WebSocketServer', function () { }); }); - it('starts a server on a given port', function (done) { + it('starts a server on a given port', function(done) { const port = 1337; const wss = new WebSocket.Server({ port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -69,14 +72,14 @@ describe('WebSocketServer', function () { wss.on('connection', () => wss.close(done)); }); - it('binds the server on any IPv6 address when available', function (done) { + it('binds the server on any IPv6 address when available', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { assert.strictEqual(wss._server.address().address, '::'); wss.close(done); }); }); - it('uses a precreated http server', function (done) { + it('uses a precreated http server', function(done) { const server = http.createServer(); server.listen(0, () => { @@ -90,13 +93,15 @@ describe('WebSocketServer', function () { }); }); - it('426s for non-Upgrade requests', function (done) { + it('426s for non-Upgrade requests', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { http.get(`http://localhost:${wss.address().port}`, (res) => { let body = ''; assert.strictEqual(res.statusCode, 426); - res.on('data', (chunk) => { body += chunk; }); + res.on('data', (chunk) => { + body += chunk; + }); res.on('end', () => { assert.strictEqual(body, http.STATUS_CODES[426]); wss.close(done); @@ -105,14 +110,16 @@ describe('WebSocketServer', function () { }); }); - it('uses a precreated http server listening on unix socket', function (done) { + it('uses a precreated http server listening on unix socket', function(done) { // // Skip this test on Windows as it throws errors for obvious reasons. // if (process.platform === 'win32') return this.skip(); const server = http.createServer(); - const sockPath = `/tmp/ws.${crypto.randomBytes(16).toString('hex')}.socket`; + const sockPath = `/tmp/ws.${crypto + .randomBytes(16) + .toString('hex')}.socket`; server.listen(sockPath, () => { const wss = new WebSocket.Server({ server }); @@ -133,8 +140,8 @@ describe('WebSocketServer', function () { }); }); - describe('#address', function () { - it('returns the address of the server', function (done) { + describe('#address', function() { + it('returns the address of the server', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const addr = wss.address(); @@ -143,7 +150,7 @@ describe('WebSocketServer', function () { }); }); - it('throws an error when operating in "noServer" mode', function () { + it('throws an error when operating in "noServer" mode', function() { const wss = new WebSocket.Server({ noServer: true }); assert.throws(() => { @@ -151,7 +158,7 @@ describe('WebSocketServer', function () { }, /^Error: The server is operating in "noServer" mode$/); }); - it('returns `null` if called after close', function (done) { + it('returns `null` if called after close', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { wss.close(() => { assert.strictEqual(wss.address(), null); @@ -161,8 +168,8 @@ describe('WebSocketServer', function () { }); }); - describe('#close', function () { - it('does not throw when called twice', function (done) { + describe('#close', function() { + it('does not throw when called twice', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { wss.close(); wss.close(); @@ -172,7 +179,7 @@ describe('WebSocketServer', function () { }); }); - it('closes all clients', function (done) { + it('closes all clients', function(done) { let closes = 0; const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -189,7 +196,7 @@ describe('WebSocketServer', function () { }); }); - it("doesn't close a precreated server", function (done) { + it("doesn't close a precreated server", function(done) { const server = http.createServer(); const realClose = server.close; @@ -210,13 +217,13 @@ describe('WebSocketServer', function () { }); }); - it('invokes the callback in noServer mode', function (done) { + it('invokes the callback in noServer mode', function(done) { const wss = new WebSocket.Server({ noServer: true }); wss.close(done); }); - it('cleans event handlers on precreated server', function (done) { + it('cleans event handlers on precreated server', function(done) { const server = http.createServer(); const wss = new WebSocket.Server({ server }); @@ -231,7 +238,7 @@ describe('WebSocketServer', function () { }); }); - it("emits the 'close' event", function (done) { + it("emits the 'close' event", function(done) { const wss = new WebSocket.Server({ noServer: true }); wss.on('close', done); @@ -239,8 +246,8 @@ describe('WebSocketServer', function () { }); }); - describe('#clients', function () { - it('returns a list of connected clients', function (done) { + describe('#clients', function() { + it('returns a list of connected clients', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { assert.strictEqual(wss.clients.size, 0); const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -252,13 +259,16 @@ describe('WebSocketServer', function () { }); }); - it('can be disabled', function (done) { - const wss = new WebSocket.Server({ port: 0, clientTracking: false }, () => { - assert.strictEqual(wss.clients, undefined); - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + it('can be disabled', function(done) { + const wss = new WebSocket.Server( + { port: 0, clientTracking: false }, + () => { + assert.strictEqual(wss.clients, undefined); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - ws.on('open', () => ws.close()); - }); + ws.on('open', () => ws.close()); + } + ); wss.on('connection', (ws) => { assert.strictEqual(wss.clients, undefined); @@ -266,7 +276,7 @@ describe('WebSocketServer', function () { }); }); - it('is updated when client terminates the connection', function (done) { + it('is updated when client terminates the connection', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -281,7 +291,7 @@ describe('WebSocketServer', function () { }); }); - it('is updated when client closes the connection', function (done) { + it('is updated when client closes the connection', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -297,29 +307,31 @@ describe('WebSocketServer', function () { }); }); - describe('#shouldHandle', function () { - it('returns true when the path matches', function () { + describe('#shouldHandle', function() { + it('returns true when the path matches', function() { const wss = new WebSocket.Server({ noServer: true, path: '/foo' }); assert.strictEqual(wss.shouldHandle({ url: '/foo' }), true); }); - it("returns false when the path doesn't match", function () { + it("returns false when the path doesn't match", function() { const wss = new WebSocket.Server({ noServer: true, path: '/foo' }); assert.strictEqual(wss.shouldHandle({ url: '/bar' }), false); }); }); - describe('#handleUpgrade', function () { - it('can be used for a pre-existing server', function (done) { + describe('#handleUpgrade', function() { + it('can be used for a pre-existing server', function(done) { const server = http.createServer(); server.listen(0, () => { const wss = new WebSocket.Server({ noServer: true }); server.on('upgrade', (req, socket, head) => { - wss.handleUpgrade(req, socket, head, (client) => client.send('hello')); + wss.handleUpgrade(req, socket, head, (client) => + client.send('hello') + ); }); const ws = new WebSocket(`ws://localhost:${server.address().port}`); @@ -332,13 +344,13 @@ describe('WebSocketServer', function () { }); }); - it("closes the connection when path doesn't match", function (done) { + it("closes the connection when path doesn't match", function(done) { const wss = new WebSocket.Server({ port: 0, path: '/ws' }, () => { const req = http.get({ port: wss.address().port, headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket' + Connection: 'Upgrade', + Upgrade: 'websocket' } }); @@ -349,13 +361,13 @@ describe('WebSocketServer', function () { }); }); - it('closes the connection when protocol version is Hixie-76', function (done) { + it('closes the connection when protocol version is Hixie-76', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ port: wss.address().port, headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', + Connection: 'Upgrade', + Upgrade: 'WebSocket', 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', 'Sec-WebSocket-Protocol': 'sample' @@ -370,14 +382,14 @@ describe('WebSocketServer', function () { }); }); - describe('Connection establishing', function () { - it('fails if the Sec-WebSocket-Key header is invalid', function (done) { + describe('Connection establishing', function() { + it('fails if the Sec-WebSocket-Key header is invalid', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ port: wss.address().port, headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket' + Connection: 'Upgrade', + Upgrade: 'websocket' } }); @@ -392,13 +404,13 @@ describe('WebSocketServer', function () { }); }); - it('fails is the Sec-WebSocket-Version header is invalid (1/2)', function (done) { + it('fails is the Sec-WebSocket-Version header is invalid (1/2)', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ port: wss.address().port, headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', + Connection: 'Upgrade', + Upgrade: 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==' } }); @@ -414,13 +426,13 @@ describe('WebSocketServer', function () { }); }); - it('fails is the Sec-WebSocket-Version header is invalid (2/2)', function (done) { + it('fails is the Sec-WebSocket-Version header is invalid (2/2)', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ port: wss.address().port, headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', + Connection: 'Upgrade', + Upgrade: 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 12 } @@ -437,62 +449,68 @@ describe('WebSocketServer', function () { }); }); - it('fails is the Sec-WebSocket-Extensions header is invalid', function (done) { - const wss = new WebSocket.Server({ - perMessageDeflate: true, - port: 0 - }, () => { - const req = http.get({ - port: wss.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 13, - 'Sec-WebSocket-Extensions': - 'permessage-deflate; server_max_window_bits=foo' - } - }); - - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 400); - wss.close(done); - }); - }); - - wss.on('connection', () => { - done(new Error("Unexpected 'connection' event")); - }); - }); - - describe('`verifyClient`', function () { - it('can reject client synchronously', function (done) { - const wss = new WebSocket.Server({ - verifyClient: () => false, + it('fails is the Sec-WebSocket-Extensions header is invalid', function(done) { + const wss = new WebSocket.Server( + { + perMessageDeflate: true, port: 0 - }, () => { + }, + () => { const req = http.get({ port: wss.address().port, headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', + Connection: 'Upgrade', + Upgrade: 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8 + 'Sec-WebSocket-Version': 13, + 'Sec-WebSocket-Extensions': + 'permessage-deflate; server_max_window_bits=foo' } }); req.on('response', (res) => { - assert.strictEqual(res.statusCode, 401); + assert.strictEqual(res.statusCode, 400); wss.close(done); }); - }); + } + ); + + wss.on('connection', () => { + done(new Error("Unexpected 'connection' event")); + }); + }); + + describe('`verifyClient`', function() { + it('can reject client synchronously', function(done) { + const wss = new WebSocket.Server( + { + verifyClient: () => false, + port: 0 + }, + () => { + const req = http.get({ + port: wss.address().port, + headers: { + Connection: 'Upgrade', + Upgrade: 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8 + } + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 401); + wss.close(done); + }); + } + ); wss.on('connection', () => { done(new Error("Unexpected 'connection' event")); }); }); - it('can accept client synchronously', function (done) { + it('can accept client synchronously', function(done) { const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), key: fs.readFileSync('test/fixtures/key.pem') @@ -521,92 +539,104 @@ describe('WebSocketServer', function () { }); }); - it('can accept client asynchronously', function (done) { - const wss = new WebSocket.Server({ - verifyClient: (o, cb) => process.nextTick(cb, true), - port: 0 - }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - }); + it('can accept client asynchronously', function(done) { + const wss = new WebSocket.Server( + { + verifyClient: (o, cb) => process.nextTick(cb, true), + port: 0 + }, + () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + } + ); wss.on('connection', () => wss.close(done)); }); - it('can reject client asynchronously', function (done) { - const wss = new WebSocket.Server({ - verifyClient: (info, cb) => process.nextTick(cb, false), - port: 0 - }, () => { - const req = http.get({ - port: wss.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8 - } - }); - - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 401); - wss.close(done); - }); - }); + it('can reject client asynchronously', function(done) { + const wss = new WebSocket.Server( + { + verifyClient: (info, cb) => process.nextTick(cb, false), + port: 0 + }, + () => { + const req = http.get({ + port: wss.address().port, + headers: { + Connection: 'Upgrade', + Upgrade: 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8 + } + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 401); + wss.close(done); + }); + } + ); wss.on('connection', () => { done(new Error("Unexpected 'connection' event")); }); }); - it('can reject client asynchronously w/ status code', function (done) { - const wss = new WebSocket.Server({ - verifyClient: (info, cb) => process.nextTick(cb, false, 404), - port: 0 - }, () => { - const req = http.get({ - port: wss.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8 - } - }); - - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 404); - wss.close(done); - }); - }); + it('can reject client asynchronously w/ status code', function(done) { + const wss = new WebSocket.Server( + { + verifyClient: (info, cb) => process.nextTick(cb, false, 404), + port: 0 + }, + () => { + const req = http.get({ + port: wss.address().port, + headers: { + Connection: 'Upgrade', + Upgrade: 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8 + } + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 404); + wss.close(done); + }); + } + ); wss.on('connection', () => { done(new Error("Unexpected 'connection' event")); }); }); - it('can reject client asynchronously w/ custom headers', function (done) { - const wss = new WebSocket.Server({ - verifyClient: (info, cb) => { - process.nextTick(cb, false, 503, '', { 'Retry-After': 120 }); + it('can reject client asynchronously w/ custom headers', function(done) { + const wss = new WebSocket.Server( + { + verifyClient: (info, cb) => { + process.nextTick(cb, false, 503, '', { 'Retry-After': 120 }); + }, + port: 0 }, - port: 0 - }, () => { - const req = http.get({ - port: wss.address().port, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': 8 - } - }); - - req.on('response', (res) => { - assert.strictEqual(res.statusCode, 503); - assert.strictEqual(res.headers['retry-after'], '120'); - wss.close(done); - }); - }); + () => { + const req = http.get({ + port: wss.address().port, + headers: { + Connection: 'Upgrade', + Upgrade: 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8 + } + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 503); + assert.strictEqual(res.headers['retry-after'], '120'); + wss.close(done); + }); + } + ); wss.on('connection', () => { done(new Error("Unexpected 'connection' event")); @@ -614,7 +644,7 @@ describe('WebSocketServer', function () { }); }); - it("doesn't emit the 'connection' event if socket is closed prematurely", function (done) { + it("doesn't emit the 'connection' event if socket is closed prematurely", function(done) { const server = http.createServer(); server.listen(0, () => { @@ -627,20 +657,25 @@ describe('WebSocketServer', function () { done(new Error("Unexpected 'connection' event")); }); - const socket = net.connect({ - port: server.address().port, - allowHalfOpen: true - }, () => { - socket.write([ - 'GET / HTTP/1.1', - 'Host: localhost', - 'Upgrade: websocket', - 'Connection: Upgrade', - 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version: 13', - '\r\n' - ].join('\r\n')); - }); + const socket = net.connect( + { + port: server.address().port, + allowHalfOpen: true + }, + () => { + socket.write( + [ + 'GET / HTTP/1.1', + 'Host: localhost', + 'Upgrade: websocket', + 'Connection: Upgrade', + 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version: 13', + '\r\n' + ].join('\r\n') + ); + } + ); socket.on('end', () => { wss.close(); @@ -651,13 +686,13 @@ describe('WebSocketServer', function () { }); }); - it('handles data passed along with the upgrade request', function (done) { + it('handles data passed along with the upgrade request', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.request({ port: wss.address().port, headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket', + Connection: 'Upgrade', + Upgrade: 'websocket', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': 13 } @@ -675,8 +710,8 @@ describe('WebSocketServer', function () { }); }); - describe('`handleProtocols`', function () { - it('allows to select a subprotocol', function (done) { + describe('`handleProtocols`', function() { + it('allows to select a subprotocol', function(done) { const handleProtocols = (protocols, request) => { assert.ok(request instanceof http.IncomingMessage); assert.strictEqual(request.url, '/'); @@ -696,7 +731,7 @@ describe('WebSocketServer', function () { }); }); - it("emits the 'headers' event", function (done) { + it("emits the 'headers' event", function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -715,8 +750,8 @@ describe('WebSocketServer', function () { }); }); - describe('permessage-deflate', function () { - it('is disabled by default', function (done) { + describe('permessage-deflate', function() { + it('is disabled by default', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); }); @@ -731,22 +766,25 @@ describe('WebSocketServer', function () { }); }); - it('uses configuration options', function (done) { - const wss = new WebSocket.Server({ - perMessageDeflate: { clientMaxWindowBits: 8 }, - port: 0 - }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + it('uses configuration options', function(done) { + const wss = new WebSocket.Server( + { + perMessageDeflate: { clientMaxWindowBits: 8 }, + port: 0 + }, + () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - ws.on('upgrade', (res) => { - assert.strictEqual( - res.headers['sec-websocket-extensions'], - 'permessage-deflate; client_max_window_bits=8' - ); + ws.on('upgrade', (res) => { + assert.strictEqual( + res.headers['sec-websocket-extensions'], + 'permessage-deflate; client_max_window_bits=8' + ); - wss.close(done); - }); - }); + wss.close(done); + }); + } + ); }); }); }); diff --git a/test/websocket.integration.js b/test/websocket.integration.js index 08810cfc9..cc78af220 100644 --- a/test/websocket.integration.js +++ b/test/websocket.integration.js @@ -4,8 +4,8 @@ const assert = require('assert'); const WebSocket = require('..'); -describe('WebSocket', function () { - it('communicates successfully with echo service (ws)', function (done) { +describe('WebSocket', function() { + it('communicates successfully with echo service (ws)', function(done) { const ws = new WebSocket('ws://echo.websocket.org/', { origin: 'http://www.websocket.org', protocolVersion: 13 @@ -26,7 +26,7 @@ describe('WebSocket', function () { }); }); - it('communicates successfully with echo service (wss)', function (done) { + it('communicates successfully with echo service (wss)', function(done) { const ws = new WebSocket('wss://echo.websocket.org/', { origin: 'https://www.websocket.org', protocolVersion: 13 diff --git a/test/websocket.test.js b/test/websocket.test.js index c849ade6c..e537c773a 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -13,19 +13,19 @@ const constants = require('../lib/constants'); const WebSocket = require('..'); class CustomAgent extends http.Agent { - addRequest () {} + addRequest() {} } -describe('WebSocket', function () { - describe('#ctor', function () { - it('throws an error when using an invalid url', function () { +describe('WebSocket', function() { + describe('#ctor', function() { + it('throws an error when using an invalid url', function() { assert.throws( () => new WebSocket('ws+unix:'), /^Error: Invalid URL: ws\+unix:$/ ); }); - it('accepts `url.Url` objects as url', function (done) { + it('accepts `url.Url` objects as url', function(done) { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -37,7 +37,7 @@ describe('WebSocket', function () { const ws = new WebSocket(url.parse('ws://localhost'), { agent }); }); - it('accepts `url.URL` objects as url', function (done) { + it('accepts `url.URL` objects as url', function(done) { if (!url.URL) return this.skip(); const agent = new CustomAgent(); @@ -51,8 +51,8 @@ describe('WebSocket', function () { const ws = new WebSocket(new url.URL('ws://[::1]'), { agent }); }); - describe('options', function () { - it('accepts the `options` object as 3rd argument', function () { + describe('options', function() { + it('accepts the `options` object as 3rd argument', function() { const agent = new CustomAgent(); let count = 0; let ws; @@ -66,29 +66,32 @@ describe('WebSocket', function () { assert.strictEqual(count, 3); }); - it('accepts the `maxPayload` option', function (done) { + it('accepts the `maxPayload` option', function(done) { const maxPayload = 20480; - const wss = new WebSocket.Server({ - perMessageDeflate: true, - port: 0 - }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { + const wss = new WebSocket.Server( + { perMessageDeflate: true, - maxPayload - }); - - ws.on('open', () => { - assert.strictEqual(ws._receiver._maxPayload, maxPayload); - assert.strictEqual( - ws._receiver._extensions['permessage-deflate']._maxPayload, + port: 0 + }, + () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { + perMessageDeflate: true, maxPayload - ); - wss.close(done); - }); - }); + }); + + ws.on('open', () => { + assert.strictEqual(ws._receiver._maxPayload, maxPayload); + assert.strictEqual( + ws._receiver._extensions['permessage-deflate']._maxPayload, + maxPayload + ); + wss.close(done); + }); + } + ); }); - it('throws an error when using an invalid `protocolVersion`', function () { + it('throws an error when using an invalid `protocolVersion`', function() { const options = { agent: new CustomAgent(), protocolVersion: 1000 }; assert.throws( @@ -99,7 +102,7 @@ describe('WebSocket', function () { }); }); - describe('Constants', function () { + describe('Constants', function() { const readyStates = { CONNECTING: 0, OPEN: 1, @@ -108,15 +111,18 @@ describe('WebSocket', function () { }; Object.keys(readyStates).forEach((state) => { - describe(`\`${state}\``, function () { - it('is enumerable property of class', function () { - const propertyDescripter = Object.getOwnPropertyDescriptor(WebSocket, state); + describe(`\`${state}\``, function() { + it('is enumerable property of class', function() { + const propertyDescripter = Object.getOwnPropertyDescriptor( + WebSocket, + state + ); assert.strictEqual(propertyDescripter.value, readyStates[state]); assert.strictEqual(propertyDescripter.enumerable, true); }); - it('is property of instance', function () { + it('is property of instance', function() { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -127,9 +133,9 @@ describe('WebSocket', function () { }); }); - describe('Attributes', function () { - describe('`binaryType`', function () { - it("defaults to 'nodebuffer'", function () { + describe('Attributes', function() { + describe('`binaryType`', function() { + it("defaults to 'nodebuffer'", function() { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -137,7 +143,7 @@ describe('WebSocket', function () { assert.strictEqual(ws.binaryType, 'nodebuffer'); }); - it("can be changed to 'arraybuffer' or 'fragments'", function () { + it("can be changed to 'arraybuffer' or 'fragments'", function() { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -159,8 +165,8 @@ describe('WebSocket', function () { }); }); - describe('`bufferedAmount`', function () { - it('defaults to zero', function () { + describe('`bufferedAmount`', function() { + it('defaults to zero', function() { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -168,7 +174,7 @@ describe('WebSocket', function () { assert.strictEqual(ws.bufferedAmount, 0); }); - it('defaults to zero upon "open"', function (done) { + it('defaults to zero upon "open"', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -179,29 +185,32 @@ describe('WebSocket', function () { }); }); - it('takes into account the data in the sender queue', function (done) { - const wss = new WebSocket.Server({ - perMessageDeflate: true, - port: 0 - }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { - perMessageDeflate: { threshold: 0 } - }); - - ws.on('open', () => { - ws.send('foo'); - ws.send('bar', (err) => { - assert.ifError(err); - assert.strictEqual(ws.bufferedAmount, 0); - wss.close(done); + it('takes into account the data in the sender queue', function(done) { + const wss = new WebSocket.Server( + { + perMessageDeflate: true, + port: 0 + }, + () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { + perMessageDeflate: { threshold: 0 } }); - assert.strictEqual(ws.bufferedAmount, 3); - }); - }); + ws.on('open', () => { + ws.send('foo'); + ws.send('bar', (err) => { + assert.ifError(err); + assert.strictEqual(ws.bufferedAmount, 0); + wss.close(done); + }); + + assert.strictEqual(ws.bufferedAmount, 3); + }); + } + ); }); - it('takes into account the data in the socket queue', function (done) { + it('takes into account the data in the socket queue', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); }); @@ -209,6 +218,7 @@ describe('WebSocket', function () { wss.on('connection', (ws) => { const data = Buffer.alloc(1024, 61); + // eslint-disable-next-line no-constant-condition while (true) { if (ws._socket.bufferSize > 0) { assert.strictEqual(ws.bufferedAmount, ws._socket.bufferSize); @@ -223,8 +233,8 @@ describe('WebSocket', function () { }); }); - describe('`extensions`', function () { - it('exposes the negotiated extensions names (1/2)', function (done) { + describe('`extensions`', function() { + it('exposes the negotiated extensions names (1/2)', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -242,20 +252,23 @@ describe('WebSocket', function () { }); }); - it('exposes the negotiated extensions names (2/2)', function (done) { - const wss = new WebSocket.Server({ - perMessageDeflate: true, - port: 0 - }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + it('exposes the negotiated extensions names (2/2)', function(done) { + const wss = new WebSocket.Server( + { + perMessageDeflate: true, + port: 0 + }, + () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - assert.strictEqual(ws.extensions, ''); + assert.strictEqual(ws.extensions, ''); - ws.on('open', () => { - assert.strictEqual(ws.extensions, 'permessage-deflate'); - ws.on('close', () => wss.close(done)); - }); - }); + ws.on('open', () => { + assert.strictEqual(ws.extensions, 'permessage-deflate'); + ws.on('close', () => wss.close(done)); + }); + } + ); wss.on('connection', (ws) => { assert.strictEqual(ws.extensions, 'permessage-deflate'); @@ -264,8 +277,8 @@ describe('WebSocket', function () { }); }); - describe('`protocol`', function () { - it('exposes the subprotocol selected by the server', function (done) { + describe('`protocol`', function() { + it('exposes the subprotocol selected by the server', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss.address().port; const ws = new WebSocket(`ws://localhost:${port}`, 'foo'); @@ -285,8 +298,8 @@ describe('WebSocket', function () { }); }); - describe('`readyState`', function () { - it('defaults to `CONNECTING`', function () { + describe('`readyState`', function() { + it('defaults to `CONNECTING`', function() { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -294,7 +307,7 @@ describe('WebSocket', function () { assert.strictEqual(ws.readyState, WebSocket.CONNECTING); }); - it('is set to `OPEN` once connection is established', function (done) { + it('is set to `OPEN` once connection is established', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -307,7 +320,7 @@ describe('WebSocket', function () { }); }); - it('is set to `CLOSED` once connection is closed', function (done) { + it('is set to `CLOSED` once connection is closed', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -320,7 +333,7 @@ describe('WebSocket', function () { }); }); - it('is set to `CLOSED` once connection is terminated', function (done) { + it('is set to `CLOSED` once connection is terminated', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -334,8 +347,8 @@ describe('WebSocket', function () { }); }); - describe('`url`', function () { - it('exposes the server url', function () { + describe('`url`', function() { + it('exposes the server url', function() { const url = 'ws://localhost'; const ws = new WebSocket(url, { agent: new CustomAgent() }); @@ -344,8 +357,8 @@ describe('WebSocket', function () { }); }); - describe('Events', function () { - it("emits an 'error' event if an error occurs", function (done) { + describe('Events', function() { + it("emits an 'error' event if an error occurs", function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -369,7 +382,7 @@ describe('WebSocket', function () { }); }); - it('does not re-emit `net.Socket` errors', function (done) { + it('does not re-emit `net.Socket` errors', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -391,7 +404,7 @@ describe('WebSocket', function () { }); }); - it("emits an 'upgrade' event", function (done) { + it("emits an 'upgrade' event", function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('upgrade', (res) => { @@ -401,7 +414,7 @@ describe('WebSocket', function () { }); }); - it("emits a 'ping' event", function (done) { + it("emits a 'ping' event", function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('ping', () => wss.close(done)); @@ -410,7 +423,7 @@ describe('WebSocket', function () { wss.on('connection', (ws) => ws.ping()); }); - it("emits a 'pong' event", function (done) { + it("emits a 'pong' event", function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('pong', () => wss.close(done)); @@ -420,21 +433,21 @@ describe('WebSocket', function () { }); }); - describe('Connection establishing', function () { + describe('Connection establishing', function() { const server = http.createServer(); beforeEach((done) => server.listen(0, done)); afterEach((done) => server.close(done)); - it('fails if the Sec-WebSocket-Accept header is invalid', function (done) { + it('fails if the Sec-WebSocket-Accept header is invalid', function(done) { server.once('upgrade', (req, socket) => { socket.on('end', socket.end); socket.write( 'HTTP/1.1 101 Switching Protocols\r\n' + - 'Upgrade: websocket\r\n' + - 'Connection: Upgrade\r\n' + - 'Sec-WebSocket-Accept: CxYS6+NgJSBG74mdgLvGscRvpns=\r\n' + - '\r\n' + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + 'Sec-WebSocket-Accept: CxYS6+NgJSBG74mdgLvGscRvpns=\r\n' + + '\r\n' ); }); @@ -447,18 +460,19 @@ describe('WebSocket', function () { }); }); - it('close event is raised when server closes connection', function (done) { + it('close event is raised when server closes connection', function(done) { server.once('upgrade', (req, socket) => { - const key = crypto.createHash('sha1') + const key = crypto + .createHash('sha1') .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary') .digest('base64'); socket.end( 'HTTP/1.1 101 Switching Protocols\r\n' + - 'Upgrade: websocket\r\n' + - 'Connection: Upgrade\r\n' + - `Sec-WebSocket-Accept: ${key}\r\n` + - '\r\n' + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + `Sec-WebSocket-Accept: ${key}\r\n` + + '\r\n' ); }); @@ -471,14 +485,14 @@ describe('WebSocket', function () { }); }); - it('error is emitted if server aborts connection', function (done) { + it('error is emitted if server aborts connection', function(done) { server.once('upgrade', (req, socket) => { socket.end( `HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` + - 'Connection: close\r\n' + - 'Content-type: text/html\r\n' + - `Content-Length: ${http.STATUS_CODES[401].length}\r\n` + - '\r\n' + 'Connection: close\r\n' + + 'Content-type: text/html\r\n' + + `Content-Length: ${http.STATUS_CODES[401].length}\r\n` + + '\r\n' ); }); @@ -492,15 +506,15 @@ describe('WebSocket', function () { }); }); - it('unexpected response can be read when sent by server', function (done) { + it('unexpected response can be read when sent by server', function(done) { server.once('upgrade', (req, socket) => { socket.end( `HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` + - 'Connection: close\r\n' + - 'Content-type: text/html\r\n' + - `Content-Length: ${http.STATUS_CODES[401].length}\r\n` + - '\r\n' + - 'foo' + 'Connection: close\r\n' + + 'Content-type: text/html\r\n' + + `Content-Length: ${http.STATUS_CODES[401].length}\r\n` + + '\r\n' + + 'foo' ); }); @@ -524,15 +538,15 @@ describe('WebSocket', function () { }); }); - it('request can be aborted when unexpected response is sent by server', function (done) { + it('request can be aborted when unexpected response is sent by server', function(done) { server.once('upgrade', (req, socket) => { socket.end( `HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` + - 'Connection: close\r\n' + - 'Content-type: text/html\r\n' + - `Content-Length: ${http.STATUS_CODES[401].length}\r\n` + - '\r\n' + - 'foo' + 'Connection: close\r\n' + + 'Content-type: text/html\r\n' + + `Content-Length: ${http.STATUS_CODES[401].length}\r\n` + + '\r\n' + + 'foo' ); }); @@ -548,7 +562,7 @@ describe('WebSocket', function () { }); }); - it('fails if the opening handshake timeout expires', function (done) { + it('fails if the opening handshake timeout expires', function(done) { server.once('upgrade', (req, socket) => socket.on('end', socket.end)); const port = server.address().port; @@ -564,19 +578,20 @@ describe('WebSocket', function () { }); }); - it('fails if the Sec-WebSocket-Extensions response header is invalid', function (done) { + it('fails if the Sec-WebSocket-Extensions response header is invalid', function(done) { server.once('upgrade', (req, socket) => { - const key = crypto.createHash('sha1') + const key = crypto + .createHash('sha1') .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary') .digest('base64'); socket.end( 'HTTP/1.1 101 Switching Protocols\r\n' + - 'Upgrade: websocket\r\n' + - 'Connection: Upgrade\r\n' + - `Sec-WebSocket-Accept: ${key}\r\n` + - 'Sec-WebSocket-Extensions: foo;=\r\n' + - '\r\n' + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + `Sec-WebSocket-Accept: ${key}\r\n` + + 'Sec-WebSocket-Extensions: foo;=\r\n' + + '\r\n' ); }); @@ -585,12 +600,15 @@ describe('WebSocket', function () { ws.on('open', () => done(new Error("Unexpected 'open' event"))); ws.on('error', (err) => { assert.ok(err instanceof Error); - assert.strictEqual(err.message, 'Invalid Sec-WebSocket-Extensions header'); + assert.strictEqual( + err.message, + 'Invalid Sec-WebSocket-Extensions header' + ); ws.on('close', () => done()); }); }); - it('fails if server sends a subprotocol when none was requested', function (done) { + it('fails if server sends a subprotocol when none was requested', function(done) { const wss = new WebSocket.Server({ server }); wss.on('headers', (headers) => { @@ -610,7 +628,7 @@ describe('WebSocket', function () { }); }); - it('fails if server sends an invalid subprotocol', function (done) { + it('fails if server sends an invalid subprotocol', function(done) { const wss = new WebSocket.Server({ handleProtocols: () => 'baz', server @@ -629,7 +647,7 @@ describe('WebSocket', function () { }); }); - it('fails if server sends no subprotocol', function (done) { + it('fails if server sends no subprotocol', function(done) { const wss = new WebSocket.Server({ handleProtocols: () => {}, server @@ -649,8 +667,8 @@ describe('WebSocket', function () { }); }); - describe('Connection with query string', function () { - it('connects when pathname is not null', function (done) { + describe('Connection with query string', function() { + it('connects when pathname is not null', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss.address().port; const ws = new WebSocket(`ws://localhost:${port}/?token=qwerty`); @@ -659,7 +677,7 @@ describe('WebSocket', function () { }); }); - it('connects when pathname is null', function (done) { + it('connects when pathname is null', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss.address().port; const ws = new WebSocket(`ws://localhost:${port}?token=qwerty`); @@ -669,8 +687,8 @@ describe('WebSocket', function () { }); }); - describe('#ping', function () { - it('throws an error if `readyState` is not `OPEN`', function (done) { + describe('#ping', function() { + it('throws an error if `readyState` is not `OPEN`', function(done) { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -690,7 +708,7 @@ describe('WebSocket', function () { }); }); - it('can send a ping with no data', function (done) { + it('can send a ping with no data', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -709,7 +727,7 @@ describe('WebSocket', function () { }); }); - it('can send a ping with data', function (done) { + it('can send a ping with data', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -727,7 +745,7 @@ describe('WebSocket', function () { }); }); - it('can send numbers as ping payload', function (done) { + it('can send numbers as ping payload', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -743,7 +761,7 @@ describe('WebSocket', function () { }); }); - describe('#pong', function () { + describe('#pong', function() { it('throws an error if `readyState` is not `OPEN`', (done) => { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() @@ -764,7 +782,7 @@ describe('WebSocket', function () { }); }); - it('can send a pong with no data', function (done) { + it('can send a pong with no data', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -783,7 +801,7 @@ describe('WebSocket', function () { }); }); - it('can send a pong with data', function (done) { + it('can send a pong with data', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -801,7 +819,7 @@ describe('WebSocket', function () { }); }); - it('can send numbers as pong payload', function (done) { + it('can send numbers as pong payload', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -817,8 +835,8 @@ describe('WebSocket', function () { }); }); - describe('#send', function () { - it('can send a big binary message', function (done) { + describe('#send', function() { + it('can send a big binary message', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const array = new Float32Array(5 * 1024 * 1024); @@ -840,7 +858,7 @@ describe('WebSocket', function () { }); }); - it('can send text data', function (done) { + it('can send text data', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -856,7 +874,7 @@ describe('WebSocket', function () { }); }); - it('does not override the `fin` option', function (done) { + it('does not override the `fin` option', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -874,7 +892,7 @@ describe('WebSocket', function () { }); }); - it('sends numbers as strings', function (done) { + it('sends numbers as strings', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -889,7 +907,7 @@ describe('WebSocket', function () { }); }); - it('can send binary data as an array', function (done) { + it('can send binary data as an array', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const array = new Float32Array(6); @@ -898,8 +916,10 @@ describe('WebSocket', function () { } const partial = array.subarray(2, 5); - const buf = Buffer.from(partial.buffer) - .slice(partial.byteOffset, partial.byteOffset + partial.byteLength); + const buf = Buffer.from(partial.buffer).slice( + partial.byteOffset, + partial.byteOffset + partial.byteLength + ); const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -915,7 +935,7 @@ describe('WebSocket', function () { }); }); - it('can send binary data as a buffer', function (done) { + it('can send binary data as a buffer', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const buf = Buffer.from('foobar'); const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -932,7 +952,7 @@ describe('WebSocket', function () { }); }); - it('can send an `ArrayBuffer`', function (done) { + it('can send an `ArrayBuffer`', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const array = new Float32Array(5); @@ -954,7 +974,7 @@ describe('WebSocket', function () { }); }); - it('can send a `Buffer`', function (done) { + it('can send a `Buffer`', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const buf = Buffer.from('foobar'); const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -972,7 +992,7 @@ describe('WebSocket', function () { }); }); - it('throws an error if `readyState` is not `OPEN`', function () { + it('throws an error if `readyState` is not `OPEN`', function() { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -983,7 +1003,7 @@ describe('WebSocket', function () { ); }); - it('passes errors to the callback, if present', function () { + it('passes errors to the callback, if present', function() { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -997,7 +1017,7 @@ describe('WebSocket', function () { }); }); - it('calls the optional callback when data is written out', function (done) { + it('calls the optional callback when data is written out', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1010,7 +1030,7 @@ describe('WebSocket', function () { }); }); - it('works when the `data` argument is falsy', function (done) { + it('works when the `data` argument is falsy', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1025,7 +1045,7 @@ describe('WebSocket', function () { }); }); - it('can send text data with `mask` option set to `false`', function (done) { + it('can send text data with `mask` option set to `false`', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1040,7 +1060,7 @@ describe('WebSocket', function () { }); }); - it('can send binary data with `mask` option set to `false`', function (done) { + it('can send binary data with `mask` option set to `false`', function(done) { const array = new Float32Array(5); for (let i = 0; i < array.length; ++i) { @@ -1062,8 +1082,8 @@ describe('WebSocket', function () { }); }); - describe('#close', function () { - it('closes the connection if called while connecting (1/2)', function (done) { + describe('#close', function() { + it('closes the connection if called while connecting (1/2)', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1080,27 +1100,30 @@ describe('WebSocket', function () { }); }); - it('closes the connection if called while connecting (2/2)', function (done) { - const wss = new WebSocket.Server({ - verifyClient: (info, cb) => setTimeout(cb, 300, true), - port: 0 - }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + it('closes the connection if called while connecting (2/2)', function(done) { + const wss = new WebSocket.Server( + { + verifyClient: (info, cb) => setTimeout(cb, 300, true), + port: 0 + }, + () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - ws.on('open', () => done(new Error("Unexpected 'open' event"))); - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual( - err.message, - 'WebSocket was closed before the connection was established' - ); - ws.on('close', () => wss.close(done)); - }); - setTimeout(() => ws.close(1001), 150); - }); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'WebSocket was closed before the connection was established' + ); + ws.on('close', () => wss.close(done)); + }); + setTimeout(() => ws.close(1001), 150); + } + ); }); - it('can be called from an error listener while connecting', function (done) { + it('can be called from an error listener while connecting', function(done) { const ws = new WebSocket('ws://localhost:1337'); ws.on('open', () => done(new Error("Unexpected 'open' event"))); @@ -1112,7 +1135,7 @@ describe('WebSocket', function () { }); }); - it("can be called from a listener of the 'upgrade' event", function (done) { + it("can be called from a listener of the 'upgrade' event", function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1129,7 +1152,7 @@ describe('WebSocket', function () { }); }); - it('throws an error if the first argument is invalid (1/2)', function (done) { + it('throws an error if the first argument is invalid (1/2)', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1144,7 +1167,7 @@ describe('WebSocket', function () { }); }); - it('throws an error if the first argument is invalid (2/2)', function (done) { + it('throws an error if the first argument is invalid (2/2)', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1159,7 +1182,7 @@ describe('WebSocket', function () { }); }); - it('sends the close status code only when necessary', function (done) { + it('sends the close status code only when necessary', function(done) { let sent; const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1186,7 +1209,7 @@ describe('WebSocket', function () { }); }); - it('works when close reason is not specified', function (done) { + it('works when close reason is not specified', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1202,7 +1225,7 @@ describe('WebSocket', function () { }); }); - it('works when close reason is specified', function (done) { + it('works when close reason is specified', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1218,39 +1241,45 @@ describe('WebSocket', function () { }); }); - it('ends connection to the server', function (done) { - const wss = new WebSocket.Server({ - clientTracking: false, - port: 0 - }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + it('ends connection to the server', function(done) { + const wss = new WebSocket.Server( + { + clientTracking: false, + port: 0 + }, + () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - ws.on('open', () => { - ws.on('close', (code, reason) => { - assert.strictEqual(reason, 'some reason'); - assert.strictEqual(code, 1000); - wss.close(done); + ws.on('open', () => { + ws.on('close', (code, reason) => { + assert.strictEqual(reason, 'some reason'); + assert.strictEqual(code, 1000); + wss.close(done); + }); + ws.close(1000, 'some reason'); }); - ws.close(1000, 'some reason'); - }); - }); + } + ); }); - it('permits all buffered data to be delivered', function (done) { - const wss = new WebSocket.Server({ - perMessageDeflate: { threshold: 0 }, - port: 0 - }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - const messages = []; + it('permits all buffered data to be delivered', function(done) { + const wss = new WebSocket.Server( + { + perMessageDeflate: { threshold: 0 }, + port: 0 + }, + () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + const messages = []; - ws.on('message', (message) => messages.push(message)); - ws.on('close', (code) => { - assert.strictEqual(code, 1005); - assert.deepStrictEqual(messages, ['foo', 'bar', 'baz']); - wss.close(done); - }); - }); + ws.on('message', (message) => messages.push(message)); + ws.on('close', (code) => { + assert.strictEqual(code, 1005); + assert.deepStrictEqual(messages, ['foo', 'bar', 'baz']); + wss.close(done); + }); + } + ); wss.on('connection', (ws) => { const callback = (err) => assert.ifError(err); @@ -1263,7 +1292,7 @@ describe('WebSocket', function () { }); }); - it('allows close code 1013', function (done) { + it('allows close code 1013', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1276,7 +1305,7 @@ describe('WebSocket', function () { wss.on('connection', (ws) => ws.close(1013)); }); - it('does nothing if `readyState` is `CLOSED`', function (done) { + it('does nothing if `readyState` is `CLOSED`', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1292,8 +1321,8 @@ describe('WebSocket', function () { }); }); - describe('#terminate', function () { - it('closes the connection if called while connecting (1/2)', function (done) { + describe('#terminate', function() { + it('closes the connection if called while connecting (1/2)', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1310,27 +1339,30 @@ describe('WebSocket', function () { }); }); - it('closes the connection if called while connecting (2/2)', function (done) { - const wss = new WebSocket.Server({ - verifyClient: (info, cb) => setTimeout(cb, 300, true), - port: 0 - }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + it('closes the connection if called while connecting (2/2)', function(done) { + const wss = new WebSocket.Server( + { + verifyClient: (info, cb) => setTimeout(cb, 300, true), + port: 0 + }, + () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - ws.on('open', () => done(new Error("Unexpected 'open' event"))); - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual( - err.message, - 'WebSocket was closed before the connection was established' - ); - ws.on('close', () => wss.close(done)); - }); - setTimeout(() => ws.terminate(), 150); - }); + ws.on('open', () => done(new Error("Unexpected 'open' event"))); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'WebSocket was closed before the connection was established' + ); + ws.on('close', () => wss.close(done)); + }); + setTimeout(() => ws.terminate(), 150); + } + ); }); - it('can be called from an error listener while connecting', function (done) { + it('can be called from an error listener while connecting', function(done) { const ws = new WebSocket('ws://localhost:1337'); ws.on('open', () => done(new Error("Unexpected 'open' event"))); @@ -1342,7 +1374,7 @@ describe('WebSocket', function () { }); }); - it("can be called from a listener of the 'upgrade' event", function (done) { + it("can be called from a listener of the 'upgrade' event", function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1359,7 +1391,7 @@ describe('WebSocket', function () { }); }); - it('does nothing if `readyState` is `CLOSED`', function (done) { + it('does nothing if `readyState` is `CLOSED`', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1375,8 +1407,8 @@ describe('WebSocket', function () { }); }); - describe('WHATWG API emulation', function () { - it('supports the `on{close,error,message,open}` attributes', function () { + describe('WHATWG API emulation', function() { + it('supports the `on{close,error,message,open}` attributes', function() { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1396,7 +1428,7 @@ describe('WebSocket', function () { assert.strictEqual(ws.onopen, listener); }); - it('works like the `EventEmitter` interface', function (done) { + it('works like the `EventEmitter` interface', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1419,7 +1451,7 @@ describe('WebSocket', function () { }); }); - it("doesn't return listeners added with `on`", function () { + it("doesn't return listeners added with `on`", function() { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1429,7 +1461,7 @@ describe('WebSocket', function () { assert.strictEqual(ws.onopen, undefined); }); - it("doesn't remove listeners added with `on`", function () { + it("doesn't remove listeners added with `on`", function() { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1451,7 +1483,7 @@ describe('WebSocket', function () { assert.strictEqual(listeners[1]._listener, listener); }); - it('adds listeners for custom events with `addEventListener`', function () { + it('adds listeners for custom events with `addEventListener`', function() { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1465,7 +1497,7 @@ describe('WebSocket', function () { assert.strictEqual(ws.listeners('bar').length, 0); }); - it('supports the `removeEventListener` method', function () { + it('supports the `removeEventListener` method', function() { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1490,7 +1522,7 @@ describe('WebSocket', function () { assert.strictEqual(ws.listenerCount('foo'), 0); }); - it('wraps text data in a `MessageEvent`', function (done) { + it('wraps text data in a `MessageEvent`', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1506,7 +1538,7 @@ describe('WebSocket', function () { }); }); - it('receives a `CloseEvent` when server closes (1000)', function (done) { + it('receives a `CloseEvent` when server closes (1000)', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1521,7 +1553,7 @@ describe('WebSocket', function () { wss.on('connection', (ws) => ws.close(1000)); }); - it('receives a `CloseEvent` when server closes (4000)', function (done) { + it('receives a `CloseEvent` when server closes (4000)', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1536,7 +1568,7 @@ describe('WebSocket', function () { wss.on('connection', (ws) => ws.close(4000, 'some daft reason')); }); - it('sets `target` and `type` on events', function (done) { + it('sets `target` and `type` on events', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const err = new Error('forced'); const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1568,7 +1600,7 @@ describe('WebSocket', function () { wss.on('connection', (client) => client.send('hi')); }); - it('passes binary data as a Node.js `Buffer` by default', function (done) { + it('passes binary data as a Node.js `Buffer` by default', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1581,7 +1613,7 @@ describe('WebSocket', function () { wss.on('connection', (ws) => ws.send(new Uint8Array(4096))); }); - it('ignores `binaryType` for text messages', function (done) { + it('ignores `binaryType` for text messages', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1596,11 +1628,11 @@ describe('WebSocket', function () { wss.on('connection', (ws) => ws.send('foo')); }); - it('allows to update `binaryType` on the fly', function (done) { + it('allows to update `binaryType` on the fly', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - function testType (binaryType, next) { + function testType(binaryType, next) { const buf = Buffer.from(binaryType); ws.binaryType = binaryType; @@ -1635,8 +1667,8 @@ describe('WebSocket', function () { }); }); - describe('SSL', function () { - it('connects to secure websocket server', function (done) { + describe('SSL', function() { + it('connects to secure websocket server', function(done) { const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), key: fs.readFileSync('test/fixtures/key.pem') @@ -1655,7 +1687,7 @@ describe('WebSocket', function () { }); }); - it('connects to secure websocket server with client side certificate', function (done) { + it('connects to secure websocket server with client side certificate', function(done) { const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), ca: [fs.readFileSync('test/fixtures/ca1-cert.pem')], @@ -1687,7 +1719,7 @@ describe('WebSocket', function () { }); }); - it('cannot connect to secure websocket server via ws://', function (done) { + it('cannot connect to secure websocket server via ws://', function(done) { const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), key: fs.readFileSync('test/fixtures/key.pem') @@ -1706,7 +1738,7 @@ describe('WebSocket', function () { }); }); - it('can send and receive text data', function (done) { + it('can send and receive text data', function(done) { const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), key: fs.readFileSync('test/fixtures/key.pem') @@ -1730,7 +1762,7 @@ describe('WebSocket', function () { }); }); - it('can send a big binary message', function (done) { + it('can send a big binary message', function(done) { this.timeout(4000); const buf = crypto.randomBytes(5 * 1024 * 1024); @@ -1760,8 +1792,8 @@ describe('WebSocket', function () { }); }); - describe('Request headers', function () { - it('adds the authorization header if the url has userinfo (1/2)', function (done) { + describe('Request headers', function() { + it('adds the authorization header if the url has userinfo (1/2)', function(done) { const agent = new CustomAgent(); const auth = 'test:testpass'; @@ -1776,7 +1808,7 @@ describe('WebSocket', function () { const ws = new WebSocket(`ws://${auth}@localhost`, { agent }); }); - it('adds the authorization header if the url has userinfo (2/2)', function (done) { + it('adds the authorization header if the url has userinfo (2/2)', function(done) { if (!url.URL) return this.skip(); const agent = new CustomAgent(); @@ -1795,7 +1827,7 @@ describe('WebSocket', function () { }); }); - it('adds custom headers', function (done) { + it('adds custom headers', function(done) { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -1804,12 +1836,12 @@ describe('WebSocket', function () { }; const ws = new WebSocket('ws://localhost', { - headers: { 'Cookie': 'foo=bar' }, + headers: { Cookie: 'foo=bar' }, agent }); }); - it('includes the host header with port number', function (done) { + it('includes the host header with port number', function(done) { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -1820,7 +1852,7 @@ describe('WebSocket', function () { const ws = new WebSocket('ws://localhost:1337', { agent }); }); - it('excludes default ports from host header', function () { + it('excludes default ports from host header', function() { const httpsAgent = new https.Agent(); const httpAgent = new http.Agent(); const values = []; @@ -1843,7 +1875,7 @@ describe('WebSocket', function () { ]); }); - it("doesn't add the origin header by default", function (done) { + it("doesn't add the origin header by default", function(done) { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -1854,7 +1886,7 @@ describe('WebSocket', function () { const ws = new WebSocket('ws://localhost', { agent }); }); - it('honors the `origin` option (1/2)', function (done) { + it('honors the `origin` option (1/2)', function(done) { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -1868,7 +1900,7 @@ describe('WebSocket', function () { }); }); - it('honors the `origin` option (2/2)', function (done) { + it('honors the `origin` option (2/2)', function(done) { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -1887,7 +1919,7 @@ describe('WebSocket', function () { }); }); - describe('permessage-deflate', function () { + describe('permessage-deflate', function() { it('is enabled by default', (done) => { const agent = new CustomAgent(); @@ -1902,7 +1934,7 @@ describe('WebSocket', function () { const ws = new WebSocket('ws://localhost', { agent }); }); - it('can be disabled', function (done) { + it('can be disabled', function(done) { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -1916,18 +1948,16 @@ describe('WebSocket', function () { }); }); - it('can send extension parameters', function (done) { + it('can send extension parameters', function(done) { const agent = new CustomAgent(); - const value = 'permessage-deflate; server_no_context_takeover;' + + const value = + 'permessage-deflate; server_no_context_takeover;' + ' client_no_context_takeover; server_max_window_bits=10;' + ' client_max_window_bits'; agent.addRequest = (req) => { - assert.strictEqual( - req._headers['sec-websocket-extensions'], - value - ); + assert.strictEqual(req._headers['sec-websocket-extensions'], value); done(); }; @@ -1942,96 +1972,108 @@ describe('WebSocket', function () { }); }); - it('can send and receive text data', function (done) { - const wss = new WebSocket.Server({ - perMessageDeflate: { threshold: 0 }, - port: 0 - }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { - perMessageDeflate: { threshold: 0 } - }); + it('can send and receive text data', function(done) { + const wss = new WebSocket.Server( + { + perMessageDeflate: { threshold: 0 }, + port: 0 + }, + () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { + perMessageDeflate: { threshold: 0 } + }); - ws.on('open', () => ws.send('hi', { compress: true })); - ws.on('message', (message) => { - assert.strictEqual(message, 'hi'); - wss.close(done); - }); - }); + ws.on('open', () => ws.send('hi', { compress: true })); + ws.on('message', (message) => { + assert.strictEqual(message, 'hi'); + wss.close(done); + }); + } + ); wss.on('connection', (ws) => { ws.on('message', (message) => ws.send(message, { compress: true })); }); }); - it('can send and receive a `TypedArray`', function (done) { + it('can send and receive a `TypedArray`', function(done) { const array = new Float32Array(5); for (let i = 0; i < array.length; i++) { array[i] = i / 2; } - const wss = new WebSocket.Server({ - perMessageDeflate: { threshold: 0 }, - port: 0 - }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { - perMessageDeflate: { threshold: 0 } - }); + const wss = new WebSocket.Server( + { + perMessageDeflate: { threshold: 0 }, + port: 0 + }, + () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { + perMessageDeflate: { threshold: 0 } + }); - ws.on('open', () => ws.send(array, { compress: true })); - ws.on('message', (message) => { - assert.ok(message.equals(Buffer.from(array.buffer))); - wss.close(done); - }); - }); + ws.on('open', () => ws.send(array, { compress: true })); + ws.on('message', (message) => { + assert.ok(message.equals(Buffer.from(array.buffer))); + wss.close(done); + }); + } + ); wss.on('connection', (ws) => { ws.on('message', (message) => ws.send(message, { compress: true })); }); }); - it('can send and receive an `ArrayBuffer`', function (done) { + it('can send and receive an `ArrayBuffer`', function(done) { const array = new Float32Array(5); for (let i = 0; i < array.length; i++) { array[i] = i / 2; } - const wss = new WebSocket.Server({ - perMessageDeflate: { threshold: 0 }, - port: 0 - }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { - perMessageDeflate: { threshold: 0 } - }); + const wss = new WebSocket.Server( + { + perMessageDeflate: { threshold: 0 }, + port: 0 + }, + () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { + perMessageDeflate: { threshold: 0 } + }); - ws.on('open', () => ws.send(array.buffer, { compress: true })); - ws.on('message', (message) => { - assert.ok(message.equals(Buffer.from(array.buffer))); - wss.close(done); - }); - }); + ws.on('open', () => ws.send(array.buffer, { compress: true })); + ws.on('message', (message) => { + assert.ok(message.equals(Buffer.from(array.buffer))); + wss.close(done); + }); + } + ); wss.on('connection', (ws) => { ws.on('message', (message) => ws.send(message, { compress: true })); }); }); - it('consumes all received data when connection is closed abnormally', function (done) { - const wss = new WebSocket.Server({ - perMessageDeflate: { threshold: 0 }, - port: 0 - }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - const messages = []; + it('consumes all received data when connection is closed abnormally', function(done) { + const wss = new WebSocket.Server( + { + perMessageDeflate: { threshold: 0 }, + port: 0 + }, + () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + const messages = []; - ws.on('message', (message) => messages.push(message)); - ws.on('close', (code) => { - assert.strictEqual(code, 1006); - assert.deepStrictEqual(messages, ['foo', 'bar', 'baz', 'qux']); - wss.close(done); - }); - }); + ws.on('message', (message) => messages.push(message)); + ws.on('close', (code) => { + assert.strictEqual(code, 1006); + assert.deepStrictEqual(messages, ['foo', 'bar', 'baz', 'qux']); + wss.close(done); + }); + } + ); wss.on('connection', (ws) => { ws.send('foo'); @@ -2041,8 +2083,8 @@ describe('WebSocket', function () { }); }); - describe('#send', function () { - it('ignores the `compress` option if the extension is disabled', function (done) { + describe('#send', function() { + it('ignores the `compress` option if the extension is disabled', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { perMessageDeflate: false @@ -2061,21 +2103,26 @@ describe('WebSocket', function () { }); }); - describe('#terminate', function () { - it('can be used while data is being compressed', function (done) { - const wss = new WebSocket.Server({ - perMessageDeflate: { threshold: 0 }, - port: 0 - }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { - perMessageDeflate: { threshold: 0 } - }); + describe('#terminate', function() { + it('can be used while data is being compressed', function(done) { + const wss = new WebSocket.Server( + { + perMessageDeflate: { threshold: 0 }, + port: 0 + }, + () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { + perMessageDeflate: { threshold: 0 } + }); - ws.on('open', () => { - ws.send('hi', () => done(new Error('Unexpected callback invocation'))); - ws.terminate(); - }); - }); + ws.on('open', () => { + ws.send('hi', () => + done(new Error('Unexpected callback invocation')) + ); + ws.terminate(); + }); + } + ); wss.on('connection', (ws) => { ws.on('close', () => { @@ -2084,30 +2131,33 @@ describe('WebSocket', function () { }); }); - it('can be used while data is being decompressed', function (done) { - const wss = new WebSocket.Server({ - perMessageDeflate: true, - port: 0 - }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - const messages = []; - - ws.on('message', (message) => { - if (messages.push(message) > 1) return; - - process.nextTick(() => { - assert.strictEqual(ws._receiver._state, 5); - ws.terminate(); + it('can be used while data is being decompressed', function(done) { + const wss = new WebSocket.Server( + { + perMessageDeflate: true, + port: 0 + }, + () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + const messages = []; + + ws.on('message', (message) => { + if (messages.push(message) > 1) return; + + process.nextTick(() => { + assert.strictEqual(ws._receiver._state, 5); + ws.terminate(); + }); }); - }); - ws.on('close', (code, reason) => { - assert.deepStrictEqual(messages, ['', '', '', '']); - assert.strictEqual(code, 1006); - assert.strictEqual(reason, ''); - wss.close(done); - }); - }); + ws.on('close', (code, reason) => { + assert.deepStrictEqual(messages, ['', '', '', '']); + assert.strictEqual(code, 1006); + assert.strictEqual(reason, ''); + wss.close(done); + }); + } + ); wss.on('connection', (ws) => { const buf = Buffer.from('c10100c10100c10100c10100', 'hex'); From 761b1a4d204452d96bbc0f21b64c5e7dcc96d8d3 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 17 Nov 2018 09:58:04 +0100 Subject: [PATCH 571/669] [doc] Fix formatting in ISSUE_TEMPLATE.md --- ISSUE_TEMPLATE.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 177ab8d71..5c3ca6a01 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -16,20 +16,26 @@ Please fill in as much of the template below as you're able. #### Reproducible in: -version: Node.js version(s): OS version(s): +- version: +- Node.js version(s): +- OS version(s): #### Steps to reproduce: -1. 2. 3. +1. -### Expected result: +2. + +3. + +#### Expected result: -### Actual result: +#### Actual result: -### Attachments: +#### Attachments: From 96b638c6f42d770d74142de4e9dd9df467722744 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 17 Nov 2018 11:43:58 +0100 Subject: [PATCH 572/669] [pkg] Fix lint script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 473b5441d..49f7dba50 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "scripts": { "test": "npm run lint && nyc --reporter=html --reporter=text mocha test/*.test.js", "integration": "npm run lint && mocha test/*.integration.js", - "lint": "eslint . --ignore-path .gitignore && prettylint **/*.{json,md} --ignore-path .gitignore" + "lint": "eslint . --ignore-path .gitignore && prettylint '**/*.{json,md}' --ignore-path .gitignore" }, "dependencies": { "async-limiter": "~1.0.0" From 26436e05d0e8452b0709ca08a92092de3696cef8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 17 Nov 2018 20:18:03 +0100 Subject: [PATCH 573/669] [fix] Restore compatibility with Node.js < 6.13.0 Fixes #1472 --- lib/websocket.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 4e4540c2f..986105ff9 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -6,7 +6,7 @@ const https = require('https'); const http = require('http'); const net = require('net'); const tls = require('tls'); -const { URL } = require('url'); +const url = require('url'); const PerMessageDeflate = require('./permessage-deflate'); const EventTarget = require('./event-target'); @@ -464,7 +464,10 @@ function initAsClient(address, protocols, options) { parsedUrl = address; this.url = address.href; } else { - parsedUrl = new URL(address); + // + // The WHATWG URL constructor is not available on Node.js < 6.13.0 + // + parsedUrl = url.URL ? new url.URL(address) : url.parse(address); this.url = address; } From cc86d22e4fcc7f0e4f294aafd4375ec493058b89 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 17 Nov 2018 20:46:34 +0100 Subject: [PATCH 574/669] [test] Remove no longer needed comment --- test/websocket.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/websocket.test.js b/test/websocket.test.js index e537c773a..0f4b518ac 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -33,7 +33,6 @@ describe('WebSocket', function() { done(); }; - // eslint-disable-next-line node/no-deprecated-api const ws = new WebSocket(url.parse('ws://localhost'), { agent }); }); From 14d9088391ac4495d04e64d76c3b83d4e75f80e2 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 17 Nov 2018 20:48:38 +0100 Subject: [PATCH 575/669] [dist] 6.1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 49f7dba50..4a13f157a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "6.1.1", + "version": "6.1.2", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 5a45029687189a316465f71f73b14a5fab67b322 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 14 Dec 2018 17:30:41 +0100 Subject: [PATCH 576/669] [minor] Simplify `if` condition in `toArrayBuffer()` --- lib/receiver.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/receiver.js b/lib/receiver.js index 420ccf957..b9102011a 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -504,11 +504,11 @@ function toBuffer(fragments, messageLength) { /** * Converts a buffer to an `ArrayBuffer`. * - * @param {Buffer} The buffer to convert + * @param {Buffer} buf The buffer to convert * @return {ArrayBuffer} Converted buffer */ function toArrayBuffer(buf) { - if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) { + if (buf.byteLength === buf.buffer.byteLength) { return buf.buffer; } From e8ada8affcdd2cee01e193f4f79b0bf2598a682b Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 23 Dec 2018 12:29:08 +0100 Subject: [PATCH 577/669] [pkg] Update eslint to version 5.11.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4a13f157a..936a4fb71 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.4", "bufferutil": "~4.0.0", - "eslint": "~5.9.0", + "eslint": "~5.11.0", "eslint-config-prettier": "~3.3.0", "eslint-plugin-prettier": "~3.0.0", "mocha": "~5.2.0", From 3d6692ac1097d5f85ccf36001434b005f6d0552d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 24 Dec 2018 15:16:54 +0100 Subject: [PATCH 578/669] [minor] Refactor `PerMessageDeflate.prototype.cleanup()` --- lib/permessage-deflate.js | 58 ++++++++++++++------------------- lib/sender.js | 10 ------ test/permessage-deflate.test.js | 13 ++++++++ test/sender.test.js | 40 ----------------------- 4 files changed, 38 insertions(+), 83 deletions(-) diff --git a/lib/permessage-deflate.js b/lib/permessage-deflate.js index ffc8e6c40..9c887647f 100644 --- a/lib/permessage-deflate.js +++ b/lib/permessage-deflate.js @@ -4,14 +4,12 @@ const Limiter = require('async-limiter'); const zlib = require('zlib'); const bufferUtil = require('./buffer-util'); -const constants = require('./constants'); +const { kStatusCode, NOOP } = require('./constants'); const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); const EMPTY_BLOCK = Buffer.from([0x00]); const kPerMessageDeflate = Symbol('permessage-deflate'); -const kWriteInProgress = Symbol('write-in-progress'); -const kPendingClose = Symbol('pending-close'); const kTotalLength = Symbol('total-length'); const kCallback = Symbol('callback'); const kBuffers = Symbol('buffers'); @@ -130,20 +128,13 @@ class PerMessageDeflate { */ cleanup() { if (this._inflate) { - if (this._inflate[kWriteInProgress]) { - this._inflate[kPendingClose] = true; - } else { - this._inflate.close(); - this._inflate = null; - } + this._inflate.close(); + this._inflate = null; } + if (this._deflate) { - if (this._deflate[kWriteInProgress]) { - this._deflate[kPendingClose] = true; - } else { - this._deflate.close(); - this._deflate = null; - } + this._deflate.close(); + this._deflate = null; } } @@ -355,7 +346,6 @@ class PerMessageDeflate { } this._inflate[kCallback] = callback; - this._inflate[kWriteInProgress] = true; this._inflate.write(data); if (fin) this._inflate.write(TRAILER); @@ -375,14 +365,10 @@ class PerMessageDeflate { this._inflate[kTotalLength] ); - if ( - (fin && this.params[`${endpoint}_no_context_takeover`]) || - this._inflate[kPendingClose] - ) { + if (fin && this.params[`${endpoint}_no_context_takeover`]) { this._inflate.close(); this._inflate = null; } else { - this._inflate[kWriteInProgress] = false; this._inflate[kTotalLength] = 0; this._inflate[kBuffers] = []; } @@ -422,17 +408,27 @@ class PerMessageDeflate { this._deflate[kBuffers] = []; // - // `zlib.DeflateRaw` emits an `'error'` event only when an attempt to use - // it is made after it has already been closed. This cannot happen here, - // so we only add a listener for the `'data'` event. + // An `'error'` event is emitted, only on Node.js < 10.0.0, if the + // `zlib.DeflateRaw` instance is closed while data is being processed. + // This can happen if `PerMessageDeflate#cleanup()` is called at the wrong + // time due to an abnormal WebSocket closure. // + this._deflate.on('error', NOOP); this._deflate.on('data', deflateOnData); } - this._deflate[kWriteInProgress] = true; - this._deflate.write(data); this._deflate.flush(zlib.Z_SYNC_FLUSH, () => { + if (!this._deflate) { + // + // This `if` statement is only needed for Node.js < 10.0.0 because as of + // commit https://github.com/nodejs/node/commit/5e3f5164, the flush + // callback is no longer called if the deflate stream is closed while + // data is being processed. + // + return; + } + var data = bufferUtil.concat( this._deflate[kBuffers], this._deflate[kTotalLength] @@ -440,14 +436,10 @@ class PerMessageDeflate { if (fin) data = data.slice(0, data.length - 4); - if ( - (fin && this.params[`${endpoint}_no_context_takeover`]) || - this._deflate[kPendingClose] - ) { + if (fin && this.params[`${endpoint}_no_context_takeover`]) { this._deflate.close(); this._deflate = null; } else { - this._deflate[kWriteInProgress] = false; this._deflate[kTotalLength] = 0; this._deflate[kBuffers] = []; } @@ -488,7 +480,7 @@ function inflateOnData(chunk) { } this[kError] = new RangeError('Max payload size exceeded'); - this[kError][constants.kStatusCode] = 1009; + this[kError][kStatusCode] = 1009; this.removeListener('data', inflateOnData); this.reset(); } @@ -505,6 +497,6 @@ function inflateOnError(err) { // closed when an error is emitted. // this[kPerMessageDeflate]._inflate = null; - err[constants.kStatusCode] = 1007; + err[kStatusCode] = 1007; this[kCallback](err); } diff --git a/lib/sender.js b/lib/sender.js index d0f30f5c5..3ac8bf8c6 100644 --- a/lib/sender.js +++ b/lib/sender.js @@ -348,16 +348,6 @@ class Sender { this._deflating = true; perMessageDeflate.compress(data, options.fin, (_, buf) => { this._deflating = false; - - if (!this._socket.readable && !this._socket.writable) { - // - // The socket is closed. Clear the queue and bail out. - // - this._bufferedBytes = 0; - this._queue.length = 0; - return; - } - options.readOnly = false; this.sendFrame(Sender.frame(buf, options), cb); this.dequeue(); diff --git a/test/permessage-deflate.test.js b/test/permessage-deflate.test.js index 71c652760..c9722bc5e 100644 --- a/test/permessage-deflate.test.js +++ b/test/permessage-deflate.test.js @@ -609,5 +609,18 @@ describe('PerMessageDeflate', function() { }); }); }); + + it("doesn't call the callback if the deflate stream is closed prematurely", function(done) { + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const buf = Buffer.from('A'.repeat(50)); + + perMessageDeflate.accept([{}]); + perMessageDeflate.compress(buf, true, () => { + done(new Error('Unexpected callback invocation')); + }); + perMessageDeflate._deflate.on('close', done); + + process.nextTick(() => perMessageDeflate.cleanup()); + }); }); }); diff --git a/test/sender.test.js b/test/sender.test.js index 7382dfe22..c6be11a83 100644 --- a/test/sender.test.js +++ b/test/sender.test.js @@ -69,46 +69,6 @@ describe('Sender', function() { sender.send('hi', options); }); - it('does not compress enqueued messages after socket closes', function(done) { - const mockSocket = new MockSocket({ - write: () => done(new Error('Unexpected call to socket.write()')) - }); - - const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); - perMessageDeflate.accept([{}]); - - const compress = perMessageDeflate.compress; - const sender = new Sender(mockSocket, { - 'permessage-deflate': perMessageDeflate - }); - - perMessageDeflate.compress = (data, fin, callback) => { - compress.call(perMessageDeflate, data, fin, (_, buf) => { - assert.strictEqual(sender._bufferedBytes, 198); - assert.strictEqual(sender._queue.length, 99); - assert.strictEqual(mockSocket.readable, false); - assert.strictEqual(mockSocket.writable, false); - - process.nextTick(() => { - assert.strictEqual(sender._bufferedBytes, 0); - assert.strictEqual(sender._queue.length, 0); - done(); - }); - - callback(_, buf); - }); - }; - - const options = { compress: true, fin: true }; - - for (let i = 0; i < 100; i++) sender.send('hi', options); - - process.nextTick(() => { - mockSocket.readable = false; - mockSocket.writable = false; - }); - }); - it('does not compress data for small payloads', function(done) { const perMessageDeflate = new PerMessageDeflate(); const mockSocket = new MockSocket({ From dc92e75f45c76230d9ecfbf209c32300b1748017 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 10 Jan 2019 21:24:06 +0100 Subject: [PATCH 579/669] [pkg] Update eslint to version 5.12.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 936a4fb71..902084cb5 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.4", "bufferutil": "~4.0.0", - "eslint": "~5.11.0", + "eslint": "~5.12.0", "eslint-config-prettier": "~3.3.0", "eslint-plugin-prettier": "~3.0.0", "mocha": "~5.2.0", From f502916509fe0892a0b8e8eb7f09c92dd8ba2448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Maia?= Date: Fri, 11 Jan 2019 20:38:20 +0100 Subject: [PATCH 580/669] [doc] Remove no longer needed --save flag (#1484) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f5b8e4ec7..7c87b11e1 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ can use one of the many wrappers available on npm, like ## Installing ``` -npm install --save ws +npm install ws ``` ### Opt-in for performance and spec compliance From 0236e999117d7c5233721efdfc3fbf3d9e007e7e Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 13 Jan 2019 21:19:04 +0100 Subject: [PATCH 581/669] chore(package): update eslint-config-prettier to version 3.4.0 (#1486) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 902084cb5..5ae2fc2f2 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "benchmark": "~2.1.4", "bufferutil": "~4.0.0", "eslint": "~5.12.0", - "eslint-config-prettier": "~3.3.0", + "eslint-config-prettier": "~3.4.0", "eslint-plugin-prettier": "~3.0.0", "mocha": "~5.2.0", "nyc": "~13.1.0", From a515600f415a5581d0728484e81e74475af02dde Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 15 Jan 2019 07:59:19 +0100 Subject: [PATCH 582/669] [minor] Fix nit --- lib/websocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/websocket.js b/lib/websocket.js index 986105ff9..f2dbb39be 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -367,7 +367,7 @@ class WebSocket extends EventEmitter { } readyStates.forEach((readyState, i) => { - WebSocket[readyStates[i]] = i; + WebSocket[readyState] = i; }); // From f7b947030657f1735afefadeab787f28f2777290 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 17 Jan 2019 07:43:07 +0100 Subject: [PATCH 583/669] chore(package): update eslint-config-prettier to version 3.5.0 (#1488) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ae2fc2f2..065f0f2c2 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "benchmark": "~2.1.4", "bufferutil": "~4.0.0", "eslint": "~5.12.0", - "eslint-config-prettier": "~3.4.0", + "eslint-config-prettier": "~3.5.0", "eslint-plugin-prettier": "~3.0.0", "mocha": "~5.2.0", "nyc": "~13.1.0", From 63adb732a5722f7c44376f79ff11068945acf7e1 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 19 Jan 2019 18:27:49 +0100 Subject: [PATCH 584/669] [minor] Set the handshake timeout via the `timeout` option Set the handshake timeout when a socket is assigned to the request. --- lib/websocket.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index f2dbb39be..436e91898 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -501,6 +501,7 @@ function initAsClient(address, protocols, options) { options.headers ); options.path = path; + options.timeout = options.handshakeTimeout; if (options.perMessageDeflate) { perMessageDeflate = new PerMessageDeflate( @@ -538,9 +539,9 @@ function initAsClient(address, protocols, options) { var req = (this._req = httpObj.get(options)); if (options.handshakeTimeout) { - req.setTimeout(options.handshakeTimeout, () => - abortHandshake(this, req, 'Opening handshake has timed out') - ); + req.on('timeout', () => { + abortHandshake(this, req, 'Opening handshake has timed out'); + }); } req.on('error', (err) => { From f9d4b09ddb983c431d3be51d402cccf370b97696 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 19 Jan 2019 18:48:34 +0100 Subject: [PATCH 585/669] [minor] Use `Array.prototype.includes()` Use `Array.prototype.includes()` instead of `Array.prototype.indexOf()`. --- lib/websocket.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 436e91898..6a04f566c 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -87,7 +87,7 @@ class WebSocket extends EventEmitter { } set binaryType(type) { - if (constants.BINARY_TYPES.indexOf(type) < 0) return; + if (!constants.BINARY_TYPES.includes(type)) return; this._binaryType = type; @@ -449,7 +449,7 @@ function initAsClient(address, protocols, options) { } ); - if (protocolVersions.indexOf(options.protocolVersion) === -1) { + if (!protocolVersions.includes(options.protocolVersion)) { throw new RangeError( `Unsupported protocol version: ${options.protocolVersion} ` + `(supported versions: ${protocolVersions.join(', ')})` @@ -588,7 +588,7 @@ function initAsClient(address, protocols, options) { protError = 'Server sent a subprotocol but none was requested'; } else if (protocols && !serverProt) { protError = 'Server sent no subprotocol'; - } else if (serverProt && protList.indexOf(serverProt) === -1) { + } else if (serverProt && !protList.includes(serverProt)) { protError = 'Server sent an invalid subprotocol'; } From 4f889592d16a0347f9b1e4e022168a61121dde4d Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 19 Jan 2019 21:29:01 +0100 Subject: [PATCH 586/669] chore(package): update eslint-config-prettier to version 3.6.0 (#1491) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 065f0f2c2..eb0603810 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "benchmark": "~2.1.4", "bufferutil": "~4.0.0", "eslint": "~5.12.0", - "eslint-config-prettier": "~3.5.0", + "eslint-config-prettier": "~3.6.0", "eslint-plugin-prettier": "~3.0.0", "mocha": "~5.2.0", "nyc": "~13.1.0", From c61218fa77e86b43712f97f1e11219668e59a7c4 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 23 Jan 2019 07:36:45 +0100 Subject: [PATCH 587/669] chore(package): update prettier to version 1.16.1 (#1492) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eb0603810..dd821ae86 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "eslint-plugin-prettier": "~3.0.0", "mocha": "~5.2.0", "nyc": "~13.1.0", - "prettier": "~1.15.2", + "prettier": "~1.16.1", "prettylint": "~1.0.0", "utf-8-validate": "~5.0.0" } From 3df4809523bafcedf6e20908c26e33b2899b8011 Mon Sep 17 00:00:00 2001 From: mwm-twx Date: Wed, 23 Jan 2019 14:03:06 -0500 Subject: [PATCH 588/669] [fix] Handle close frame which spans multiple packets (#1494) --- lib/receiver.js | 3 ++- test/receiver.test.js | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/receiver.js b/lib/receiver.js index b9102011a..9d301235f 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -62,7 +62,7 @@ class Receiver extends stream.Writable { * @param {Function} cb Callback */ _write(chunk, encoding, cb) { - if (this._opcode === 0x08) return cb(); + if (this._opcode === 0x08 && this._state == GET_INFO) return cb(); this._bufferedBytes += chunk.length; this._buffers.push(chunk); @@ -454,6 +454,7 @@ class Receiver extends stream.Writable { this.end(); } + this._state = GET_INFO; return; } diff --git a/test/receiver.test.js b/test/receiver.test.js index 6211b31a2..3bc680719 100644 --- a/test/receiver.test.js +++ b/test/receiver.test.js @@ -34,6 +34,19 @@ describe('Receiver', function() { receiver.write(Buffer.from('8800', 'hex')); }); + it('parses a close message spanning multiple writes', function(done) { + const receiver = new Receiver(); + + receiver.on('conclude', (code, data) => { + assert.strictEqual(code, 1000); + assert.strictEqual(data, 'DONE'); + done(); + }); + + receiver.write(Buffer.from('8806', 'hex')); + receiver.write(Buffer.from('03e8444F4E45', 'hex')); + }); + it('parses a masked text message', function(done) { const receiver = new Receiver(); From 6d2930a65e541842053b2f8124865e9027cbab59 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 24 Jan 2019 07:51:22 +0100 Subject: [PATCH 589/669] [minor] Fix style nit --- lib/receiver.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/receiver.js b/lib/receiver.js index 9d301235f..a2cef8c44 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -453,14 +453,12 @@ class Receiver extends stream.Writable { this.emit('conclude', code, buf.toString()); this.end(); } - - this._state = GET_INFO; - return; + } else if (this._opcode === 0x09) { + this.emit('ping', data); + } else { + this.emit('pong', data); } - if (this._opcode === 0x09) this.emit('ping', data); - else this.emit('pong', data); - this._state = GET_INFO; } } From 6fa6b8b388ad002a7d754d9ccdfac73d2bf73bc2 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 24 Jan 2019 07:54:10 +0100 Subject: [PATCH 590/669] [dist] 6.1.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dd821ae86..e4718e73a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "6.1.2", + "version": "6.1.3", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 460ae81a88f0b6a34bcc33889e090fd7ffb07ef3 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 26 Jan 2019 21:37:46 +0100 Subject: [PATCH 591/669] chore(package): update eslint-config-prettier to version 4.0.0 (#1496) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e4718e73a..08b1a5a34 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "benchmark": "~2.1.4", "bufferutil": "~4.0.0", "eslint": "~5.12.0", - "eslint-config-prettier": "~3.6.0", + "eslint-config-prettier": "~4.0.0", "eslint-plugin-prettier": "~3.0.0", "mocha": "~5.2.0", "nyc": "~13.1.0", From 75cd966f86b125eb06854a22590919c4293dd5ea Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 1 Feb 2019 21:34:53 +0100 Subject: [PATCH 592/669] chore(package): update eslint to version 5.13.0 (#1497) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 08b1a5a34..2d8f42384 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.4", "bufferutil": "~4.0.0", - "eslint": "~5.12.0", + "eslint": "~5.13.0", "eslint-config-prettier": "~4.0.0", "eslint-plugin-prettier": "~3.0.0", "mocha": "~5.2.0", From b45e9121f4a1f067a142c0fb4f2fae65658e90fc Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 5 Feb 2019 07:40:24 +0100 Subject: [PATCH 593/669] chore(package): update nyc to version 13.2.0 (#1500) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2d8f42384..06afd0916 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "eslint-config-prettier": "~4.0.0", "eslint-plugin-prettier": "~3.0.0", "mocha": "~5.2.0", - "nyc": "~13.1.0", + "nyc": "~13.2.0", "prettier": "~1.16.1", "prettylint": "~1.0.0", "utf-8-validate": "~5.0.0" From 5c82aacc1fea8c6030b30f1bff811e5b3ffe7b72 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 15 Feb 2019 07:19:17 +0100 Subject: [PATCH 594/669] chore(package): update nyc to version 13.3.0 (#1506) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 06afd0916..18ee7dd99 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "eslint-config-prettier": "~4.0.0", "eslint-plugin-prettier": "~3.0.0", "mocha": "~5.2.0", - "nyc": "~13.2.0", + "nyc": "~13.3.0", "prettier": "~1.16.1", "prettylint": "~1.0.0", "utf-8-validate": "~5.0.0" From 8b5422eb96a96d1e66f5af03bfc453f57985bcd0 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 16 Feb 2019 07:16:49 +0100 Subject: [PATCH 595/669] chore(package): update eslint to version 5.14.0 (#1509) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 18ee7dd99..9a34c6369 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "devDependencies": { "benchmark": "~2.1.4", "bufferutil": "~4.0.0", - "eslint": "~5.13.0", + "eslint": "~5.14.0", "eslint-config-prettier": "~4.0.0", "eslint-plugin-prettier": "~3.0.0", "mocha": "~5.2.0", From 92b0a650d01727a84cb3ab8f6a5bed2c33127aa7 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 16 Feb 2019 17:50:21 +0100 Subject: [PATCH 596/669] [fix] Use the `defaultPort` option (#1510) Prevent the default port from being added to the `Host` header when no `Agent` is used. Fixes #1505 --- lib/websocket.js | 4 +++- test/websocket.test.js | 44 ++++++++++++++---------------------------- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 6a04f566c..1d7c4468b 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -479,6 +479,7 @@ function initAsClient(address, protocols, options) { const isSecure = parsedUrl.protocol === 'wss:' || parsedUrl.protocol === 'https:'; + const defaultPort = isSecure ? 443 : 80; const key = crypto.randomBytes(16).toString('base64'); const httpObj = isSecure ? https : http; const path = parsedUrl.search @@ -487,7 +488,8 @@ function initAsClient(address, protocols, options) { var perMessageDeflate; options.createConnection = isSecure ? tlsConnect : netConnect; - options.port = parsedUrl.port || (isSecure ? 443 : 80); + options.defaultPort = options.defaultPort || defaultPort; + options.port = parsedUrl.port || defaultPort; options.host = parsedUrl.hostname.startsWith('[') ? parsedUrl.hostname.slice(1, -1) : parsedUrl.hostname; diff --git a/test/websocket.test.js b/test/websocket.test.js index 0f4b518ac..31cf71106 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -1840,38 +1840,24 @@ describe('WebSocket', function() { }); }); - it('includes the host header with port number', function(done) { - const agent = new CustomAgent(); - - agent.addRequest = (req) => { - assert.strictEqual(req._headers.host, 'localhost:1337'); - done(); - }; - - const ws = new WebSocket('ws://localhost:1337', { agent }); + it('includes the host header with port number', function() { + const ws = new WebSocket('ws://localhost:1337', { lookup() {} }); + assert.strictEqual(ws._req._headers.host, 'localhost:1337'); }); it('excludes default ports from host header', function() { - const httpsAgent = new https.Agent(); - const httpAgent = new http.Agent(); - const values = []; - let ws; - - httpsAgent.addRequest = httpAgent.addRequest = (req) => { - values.push(req._headers.host); - }; - - ws = new WebSocket('wss://localhost:8443', { agent: httpsAgent }); - ws = new WebSocket('wss://localhost:443', { agent: httpsAgent }); - ws = new WebSocket('ws://localhost:88', { agent: httpAgent }); - ws = new WebSocket('ws://localhost:80', { agent: httpAgent }); - - assert.deepStrictEqual(values, [ - 'localhost:8443', - 'localhost', - 'localhost:88', - 'localhost' - ]); + const options = { lookup() {} }; + const variants = [ + ['wss://localhost:8443', 'localhost:8443'], + ['wss://localhost:443', 'localhost'], + ['ws://localhost:88', 'localhost:88'], + ['ws://localhost:80', 'localhost'] + ]; + + for (const [url, host] of variants) { + const ws = new WebSocket(url, options); + assert.strictEqual(ws._req._headers.host, host); + } }); it("doesn't add the origin header by default", function(done) { From 15b0bb35900c1069f45b6cda38c891b7b4aa9bb7 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 16 Feb 2019 17:52:48 +0100 Subject: [PATCH 597/669] [test] Remove redundant test --- test/websocket.test.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/websocket.test.js b/test/websocket.test.js index 31cf71106..71f66044f 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -1840,11 +1840,6 @@ describe('WebSocket', function() { }); }); - it('includes the host header with port number', function() { - const ws = new WebSocket('ws://localhost:1337', { lookup() {} }); - assert.strictEqual(ws._req._headers.host, 'localhost:1337'); - }); - it('excludes default ports from host header', function() { const options = { lookup() {} }; const variants = [ From dc745cc10918eec23bd0680bcc7bce26101b8e2d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 16 Feb 2019 17:54:41 +0100 Subject: [PATCH 598/669] [dist] 6.1.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9a34c6369..af096ea35 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "6.1.3", + "version": "6.1.4", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From dd5833a8f576f8dda86c693ff82f0013fcffcc6b Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 19 Feb 2019 08:17:37 +0100 Subject: [PATCH 599/669] chore(package): update mocha to version 6.0.0 (#1511) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af096ea35..23fa6d782 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "eslint": "~5.14.0", "eslint-config-prettier": "~4.0.0", "eslint-plugin-prettier": "~3.0.0", - "mocha": "~5.2.0", + "mocha": "~6.0.0", "nyc": "~13.3.0", "prettier": "~1.16.1", "prettylint": "~1.0.0", From 6ad4d868c0244e93dada6c267a8838f7055829ac Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 21 Feb 2019 17:57:00 +0100 Subject: [PATCH 600/669] [pkg] Remove prettylint --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 23fa6d782..c45076409 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "scripts": { "test": "npm run lint && nyc --reporter=html --reporter=text mocha test/*.test.js", "integration": "npm run lint && mocha test/*.integration.js", - "lint": "eslint . --ignore-path .gitignore && prettylint '**/*.{json,md}' --ignore-path .gitignore" + "lint": "eslint --ignore-path .gitignore . && prettier --check --ignore-path .gitignore \"**/*.{json,md}\"" }, "dependencies": { "async-limiter": "~1.0.0" @@ -39,7 +39,6 @@ "mocha": "~6.0.0", "nyc": "~13.3.0", "prettier": "~1.16.1", - "prettylint": "~1.0.0", "utf-8-validate": "~5.0.0" } } From 1d93fb2739ffff9bd96cafecf7f2153c8c8d5b69 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 21 Feb 2019 18:23:44 +0100 Subject: [PATCH 601/669] [codestyle] Use single quotes in YAML files --- .travis.yml | 10 +++++----- appveyor.yml | 8 ++++---- package.json | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index ebb063d65..7930a167e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: node_js sudo: false node_js: - - "11" - - "10" - - "8" - - "6" + - '11' + - '10' + - '8' + - '6' after_success: - - "npm install coveralls@3 && nyc report --reporter=text-lcov | coveralls" + - 'npm install coveralls@3 && nyc report --reporter=text-lcov | coveralls' diff --git a/appveyor.yml b/appveyor.yml index 917169ad0..2d4b559b6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,9 +1,9 @@ environment: matrix: - - nodejs_version: "11" - - nodejs_version: "10" - - nodejs_version: "8" - - nodejs_version: "6" + - nodejs_version: '11' + - nodejs_version: '10' + - nodejs_version: '8' + - nodejs_version: '6' platform: - x86 - x64 diff --git a/package.json b/package.json index c45076409..3b6614bd9 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "scripts": { "test": "npm run lint && nyc --reporter=html --reporter=text mocha test/*.test.js", "integration": "npm run lint && mocha test/*.integration.js", - "lint": "eslint --ignore-path .gitignore . && prettier --check --ignore-path .gitignore \"**/*.{json,md}\"" + "lint": "eslint --ignore-path .gitignore . && prettier --check --ignore-path .gitignore \"**/*.{json,md,yml}\"" }, "dependencies": { "async-limiter": "~1.0.0" From 160af45bf3bed83237a0552bf8df3daff762b5fc Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 22 Feb 2019 10:46:38 +0100 Subject: [PATCH 602/669] [fix] Abort the handshake if the Sec-WebSocket-Key header is invalid --- lib/websocket-server.js | 24 ++++++++++++++++-------- lib/websocket.js | 28 +++++++++++++++++----------- test/websocket-server.test.js | 24 +++++++++++++++++++++++- test/websocket.test.js | 6 +++--- 4 files changed, 59 insertions(+), 23 deletions(-) diff --git a/lib/websocket-server.js b/lib/websocket-server.js index deca40838..991b50992 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -6,8 +6,10 @@ const http = require('http'); const PerMessageDeflate = require('./permessage-deflate'); const extension = require('./extension'); -const constants = require('./constants'); const WebSocket = require('./websocket'); +const { GUID } = require('./constants'); + +const keyRegex = /^[+/0-9A-Za-z]{22}==$/; /** * Class representing a WebSocket server. @@ -176,13 +178,18 @@ class WebSocketServer extends EventEmitter { handleUpgrade(req, socket, head, cb) { socket.on('error', socketOnError); + const key = + req.headers['sec-websocket-key'] !== undefined + ? req.headers['sec-websocket-key'].trim() + : false; const version = +req.headers['sec-websocket-version']; const extensions = {}; if ( req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket' || - !req.headers['sec-websocket-key'] || + !key || + !keyRegex.test(key) || (version !== 8 && version !== 13) || !this.shouldHandle(req) ) { @@ -225,7 +232,7 @@ class WebSocketServer extends EventEmitter { return abortHandshake(socket, code || 401, message, headers); } - this.completeUpgrade(extensions, req, socket, head, cb); + this.completeUpgrade(key, extensions, req, socket, head, cb); }); return; } @@ -233,12 +240,13 @@ class WebSocketServer extends EventEmitter { if (!this.options.verifyClient(info)) return abortHandshake(socket, 401); } - this.completeUpgrade(extensions, req, socket, head, cb); + this.completeUpgrade(key, extensions, req, socket, head, cb); } /** * Upgrade the connection to WebSocket. * + * @param {String} key The value of the `Sec-WebSocket-Key` header * @param {Object} extensions The accepted extensions * @param {http.IncomingMessage} req The request object * @param {net.Socket} socket The network socket between the server and client @@ -246,22 +254,22 @@ class WebSocketServer extends EventEmitter { * @param {Function} cb Callback * @private */ - completeUpgrade(extensions, req, socket, head, cb) { + completeUpgrade(key, extensions, req, socket, head, cb) { // // Destroy the socket if the client has already sent a FIN packet. // if (!socket.readable || !socket.writable) return socket.destroy(); - const key = crypto + const digest = crypto .createHash('sha1') - .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary') + .update(key + GUID) .digest('base64'); const headers = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', - `Sec-WebSocket-Accept: ${key}` + `Sec-WebSocket-Accept: ${digest}` ]; const ws = new WebSocket(null); diff --git a/lib/websocket.js b/lib/websocket.js index 1d7c4468b..e90665c13 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -11,12 +11,18 @@ const url = require('url'); const PerMessageDeflate = require('./permessage-deflate'); const EventTarget = require('./event-target'); const extension = require('./extension'); -const constants = require('./constants'); const Receiver = require('./receiver'); const Sender = require('./sender'); +const { + BINARY_TYPES, + EMPTY_BUFFER, + GUID, + kStatusCode, + kWebSocket, + NOOP +} = require('./constants'); const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']; -const kWebSocket = constants.kWebSocket; const protocolVersions = [8, 13]; const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly. @@ -39,7 +45,7 @@ class WebSocket extends EventEmitter { this.readyState = WebSocket.CONNECTING; this.protocol = ''; - this._binaryType = constants.BINARY_TYPES[0]; + this._binaryType = BINARY_TYPES[0]; this._closeFrameReceived = false; this._closeFrameSent = false; this._closeMessage = ''; @@ -87,7 +93,7 @@ class WebSocket extends EventEmitter { } set binaryType(type) { - if (!constants.BINARY_TYPES.includes(type)) return; + if (!BINARY_TYPES.includes(type)) return; this._binaryType = type; @@ -265,7 +271,7 @@ class WebSocket extends EventEmitter { if (typeof data === 'number') data = data.toString(); if (mask === undefined) mask = !this._isServer; - this._sender.ping(data || constants.EMPTY_BUFFER, mask, cb); + this._sender.ping(data || EMPTY_BUFFER, mask, cb); } /** @@ -297,7 +303,7 @@ class WebSocket extends EventEmitter { if (typeof data === 'number') data = data.toString(); if (mask === undefined) mask = !this._isServer; - this._sender.pong(data || constants.EMPTY_BUFFER, mask, cb); + this._sender.pong(data || EMPTY_BUFFER, mask, cb); } /** @@ -344,7 +350,7 @@ class WebSocket extends EventEmitter { opts.compress = false; } - this._sender.send(data || constants.EMPTY_BUFFER, opts, cb); + this._sender.send(data || EMPTY_BUFFER, opts, cb); } /** @@ -574,7 +580,7 @@ function initAsClient(address, protocols, options) { const digest = crypto .createHash('sha1') - .update(key + constants.GUID, 'binary') + .update(key + GUID) .digest('base64'); if (res.headers['sec-websocket-accept'] !== digest) { @@ -720,7 +726,7 @@ function receiverOnError(err) { websocket._socket.removeListener('data', socketOnData); websocket.readyState = WebSocket.CLOSING; - websocket._closeCode = err[constants.kStatusCode]; + websocket._closeCode = err[kStatusCode]; websocket.emit('error', err); websocket._socket.destroy(); } @@ -753,7 +759,7 @@ function receiverOnMessage(data) { function receiverOnPing(data) { const websocket = this[kWebSocket]; - websocket.pong(data, !websocket._isServer, constants.NOOP); + websocket.pong(data, !websocket._isServer, NOOP); websocket.emit('ping', data); } @@ -843,7 +849,7 @@ function socketOnError() { const websocket = this[kWebSocket]; this.removeListener('error', socketOnError); - this.on('error', constants.NOOP); + this.on('error', NOOP); if (websocket) { websocket.readyState = WebSocket.CLOSING; diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index f6a394f72..d78ed9fdf 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -383,7 +383,7 @@ describe('WebSocketServer', function() { }); describe('Connection establishing', function() { - it('fails if the Sec-WebSocket-Key header is invalid', function(done) { + it('fails if the Sec-WebSocket-Key header is invalid (1/2)', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ port: wss.address().port, @@ -404,6 +404,28 @@ describe('WebSocketServer', function() { }); }); + it('fails if the Sec-WebSocket-Key header is invalid (2/2)', function(done) { + const wss = new WebSocket.Server({ port: 0 }, () => { + const req = http.get({ + port: wss.address().port, + headers: { + Connection: 'Upgrade', + Upgrade: 'websocket', + 'Sec-WebSocket-Key': 'P5l8BJcZwRc=' + } + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); + wss.close(done); + }); + }); + + wss.on('connection', () => { + done(new Error("Unexpected 'connection' event")); + }); + }); + it('fails is the Sec-WebSocket-Version header is invalid (1/2)', function(done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ diff --git a/test/websocket.test.js b/test/websocket.test.js index 71f66044f..a9af17cf7 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -9,8 +9,8 @@ const http = require('http'); const url = require('url'); const fs = require('fs'); -const constants = require('../lib/constants'); const WebSocket = require('..'); +const { GUID } = require('../lib/constants'); class CustomAgent extends http.Agent { addRequest() {} @@ -463,7 +463,7 @@ describe('WebSocket', function() { server.once('upgrade', (req, socket) => { const key = crypto .createHash('sha1') - .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary') + .update(req.headers['sec-websocket-key'] + GUID) .digest('base64'); socket.end( @@ -581,7 +581,7 @@ describe('WebSocket', function() { server.once('upgrade', (req, socket) => { const key = crypto .createHash('sha1') - .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary') + .update(req.headers['sec-websocket-key'] + GUID) .digest('base64'); socket.end( From 295786bcb1d9e758c6442f6e612d5f50c8054ae9 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 26 Feb 2019 21:07:07 +0100 Subject: [PATCH 603/669] chore(package): update eslint-config-prettier to version 4.1.0 (#1518) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3b6614bd9..7dd258bc4 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "benchmark": "~2.1.4", "bufferutil": "~4.0.0", "eslint": "~5.14.0", - "eslint-config-prettier": "~4.0.0", + "eslint-config-prettier": "~4.1.0", "eslint-plugin-prettier": "~3.0.0", "mocha": "~6.0.0", "nyc": "~13.3.0", From 5e238fb8b7ea029788a11ef5665defd6c4a7cd3c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 28 Feb 2019 09:31:10 +0100 Subject: [PATCH 604/669] [doc] Add logos to badges --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7c87b11e1..2b5c6115a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # ws: a Node.js WebSocket library -[![Version npm](https://img.shields.io/npm/v/ws.svg)](https://www.npmjs.com/package/ws) -[![Linux Build](https://img.shields.io/travis/websockets/ws/master.svg)](https://travis-ci.org/websockets/ws) -[![Windows Build](https://ci.appveyor.com/api/projects/status/github/websockets/ws?branch=master&svg=true)](https://ci.appveyor.com/project/lpinca/ws) -[![Coverage Status](https://img.shields.io/coveralls/websockets/ws/master.svg)](https://coveralls.io/r/websockets/ws?branch=master) +[![Version npm](https://img.shields.io/npm/v/ws.svg?logo=npm)](https://www.npmjs.com/package/ws) +[![Linux Build](https://img.shields.io/travis/websockets/ws/master.svg?logo=travis)](https://travis-ci.org/websockets/ws) +[![Windows Build](https://img.shields.io/appveyor/ci/lpinca/ws/master.svg?logo=appveyor)](https://ci.appveyor.com/project/lpinca/ws) +[![Coverage Status](https://img.shields.io/coveralls/websockets/ws/master.svg)](https://coveralls.io/github/websockets/ws) ws is a simple to use, blazing fast, and thoroughly tested WebSocket client and server implementation. From bcb8a9891d6f6a0881c7628225f46f438d5ed62a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 28 Feb 2019 09:41:07 +0100 Subject: [PATCH 605/669] [ci] Test on macOS --- .travis.yml | 6 ++++-- package.json | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7930a167e..0229655d9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,11 @@ language: node_js -sudo: false node_js: - '11' - '10' - '8' - '6' +os: + - linux + - osx after_success: - - 'npm install coveralls@3 && nyc report --reporter=text-lcov | coveralls' + - nyc report --reporter=text-lcov | coveralls diff --git a/package.json b/package.json index 7dd258bc4..956135c83 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "devDependencies": { "benchmark": "~2.1.4", "bufferutil": "~4.0.0", + "coveralls": "~3.0.3", "eslint": "~5.14.0", "eslint-config-prettier": "~4.1.0", "eslint-plugin-prettier": "~3.0.0", From faf9b36b7f31c56aae2b8c01fd7b9fc2e547e5b2 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 28 Feb 2019 10:02:04 +0100 Subject: [PATCH 606/669] [ci] Let Travis CI handle x64 Windows tests --- .travis.yml | 1 + appveyor.yml | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0229655d9..1cf7887f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,5 +7,6 @@ node_js: os: - linux - osx + - windows after_success: - nyc report --reporter=text-lcov | coveralls diff --git a/appveyor.yml b/appveyor.yml index 2d4b559b6..a316bb770 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,7 +6,6 @@ environment: - nodejs_version: '6' platform: - x86 - - x64 matrix: fast_finish: true install: From 8c21adde2fc0ebc6e2f36928cc415b45d994bf69 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 28 Feb 2019 10:25:12 +0100 Subject: [PATCH 607/669] [codestyle] Add .gitattributes --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..6313b56c5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf From 855494da8d8f86821c8be7621a9b83f6ec32af9c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 2 Mar 2019 15:21:50 +0100 Subject: [PATCH 608/669] [test] Fix flaky test --- test/websocket-server.test.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index d78ed9fdf..06d44533b 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -671,7 +671,16 @@ describe('WebSocketServer', function() { server.listen(0, () => { const wss = new WebSocket.Server({ - verifyClient: (o, cb) => setTimeout(cb, 100, true), + verifyClient: ({ req: { socket } }, cb) => { + assert.strictEqual(socket.readable, true); + assert.strictEqual(socket.writable, true); + + socket.on('end', () => { + assert.strictEqual(socket.readable, false); + assert.strictEqual(socket.writable, true); + cb(true); + }); + }, server }); @@ -685,7 +694,7 @@ describe('WebSocketServer', function() { allowHalfOpen: true }, () => { - socket.write( + socket.end( [ 'GET / HTTP/1.1', 'Host: localhost', @@ -703,8 +712,6 @@ describe('WebSocketServer', function() { wss.close(); server.close(done); }); - - socket.setTimeout(50, () => socket.end()); }); }); From 71ee9ed0cc3b57bf1e5b9f0b46f4f340f22eb0c9 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 2 Mar 2019 15:23:04 +0100 Subject: [PATCH 609/669] chore(package): update eslint to version 5.15.0 (#1522) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 956135c83..d84cc76c5 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "benchmark": "~2.1.4", "bufferutil": "~4.0.0", "coveralls": "~3.0.3", - "eslint": "~5.14.0", + "eslint": "~5.15.0", "eslint-config-prettier": "~4.1.0", "eslint-plugin-prettier": "~3.0.0", "mocha": "~6.0.0", From a6e94f4954ca6fe2ac40b0ddf6d56125b4eca8e1 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 2 Mar 2019 18:55:16 +0100 Subject: [PATCH 610/669] [minor] Move all buffer conversion functions to the buffer-util module --- lib/buffer-util.js | 78 +++++++++++++++++++++++++++++++++-- lib/receiver.js | 65 ++++++++++------------------- lib/sender.js | 100 +++++++++++---------------------------------- 3 files changed, 119 insertions(+), 124 deletions(-) diff --git a/lib/buffer-util.js b/lib/buffer-util.js index 54867ac4f..8fcb88f4a 100644 --- a/lib/buffer-util.js +++ b/lib/buffer-util.js @@ -1,5 +1,7 @@ 'use strict'; +const { EMPTY_BUFFER } = require('./constants'); + /** * Merges an array of buffers into a new buffer. * @@ -9,6 +11,9 @@ * @public */ function concat(list, totalLength) { + if (list.length === 0) return EMPTY_BUFFER; + if (list.length === 1) return list[0]; + const target = Buffer.allocUnsafe(totalLength); var offset = 0; @@ -52,21 +57,88 @@ function _unmask(buffer, mask) { } } +/** + * Converts a buffer to an `ArrayBuffer`. + * + * @param {Buffer} buf The buffer to convert + * @return {ArrayBuffer} Converted buffer + * @public + */ +function toArrayBuffer(buf) { + if (buf.byteLength === buf.buffer.byteLength) { + return buf.buffer; + } + + return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); +} + +/** + * Converts `data` to a `Buffer`. + * + * @param {*} data The data to convert + * @return {Buffer} The buffer + * @throws {TypeError} + * @public + */ +function toBuffer(data) { + toBuffer.readOnly = true; + + if (Buffer.isBuffer(data)) return data; + + var buf; + + if (data instanceof ArrayBuffer) { + buf = Buffer.from(data); + } else if (ArrayBuffer.isView(data)) { + buf = viewToBuffer(data); + } else { + buf = Buffer.from(data); + toBuffer.readOnly = false; + } + + return buf; +} + +/** + * Converts an `ArrayBuffer` view into a buffer. + * + * @param {(DataView|TypedArray)} view The view to convert + * @return {Buffer} Converted view + * @private + */ +function viewToBuffer(view) { + const buf = Buffer.from(view.buffer); + + if (view.byteLength !== view.buffer.byteLength) { + return buf.slice(view.byteOffset, view.byteOffset + view.byteLength); + } + + return buf; +} + try { const bufferUtil = require('bufferutil'); const bu = bufferUtil.BufferUtil || bufferUtil; module.exports = { + concat, mask(source, mask, output, offset, length) { if (length < 48) _mask(source, mask, output, offset, length); else bu.mask(source, mask, output, offset, length); }, + toArrayBuffer, + toBuffer, unmask(buffer, mask) { if (buffer.length < 32) _unmask(buffer, mask); else bu.unmask(buffer, mask); - }, - concat + } }; } catch (e) /* istanbul ignore next */ { - module.exports = { concat, mask: _mask, unmask: _unmask }; + module.exports = { + concat, + mask: _mask, + toArrayBuffer, + toBuffer, + unmask: _unmask + }; } diff --git a/lib/receiver.js b/lib/receiver.js index a2cef8c44..0a8d76df7 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -1,11 +1,16 @@ 'use strict'; -const stream = require('stream'); +const { Writable } = require('stream'); const PerMessageDeflate = require('./permessage-deflate'); -const bufferUtil = require('./buffer-util'); -const validation = require('./validation'); -const constants = require('./constants'); +const { + BINARY_TYPES, + EMPTY_BUFFER, + kStatusCode, + kWebSocket +} = require('./constants'); +const { concat, toArrayBuffer, unmask } = require('./buffer-util'); +const { isValidStatusCode, isValidUTF8 } = require('./validation'); const GET_INFO = 0; const GET_PAYLOAD_LENGTH_16 = 1; @@ -19,7 +24,7 @@ const INFLATING = 5; * * @extends stream.Writable */ -class Receiver extends stream.Writable { +class Receiver extends Writable { /** * Creates a Receiver instance. * @@ -30,8 +35,8 @@ class Receiver extends stream.Writable { constructor(binaryType, extensions, maxPayload) { super(); - this._binaryType = binaryType || constants.BINARY_TYPES[0]; - this[constants.kWebSocket] = undefined; + this._binaryType = binaryType || BINARY_TYPES[0]; + this[kWebSocket] = undefined; this._extensions = extensions || {}; this._maxPayload = maxPayload | 0; @@ -315,7 +320,7 @@ class Receiver extends stream.Writable { * @private */ getData(cb) { - var data = constants.EMPTY_BUFFER; + var data = EMPTY_BUFFER; if (this._payloadLength) { if (this._bufferedBytes < this._payloadLength) { @@ -324,7 +329,7 @@ class Receiver extends stream.Writable { } data = this.consume(this._payloadLength); - if (this._masked) bufferUtil.unmask(data, this._mask); + if (this._masked) unmask(data, this._mask); } if (this._opcode > 0x07) return this.controlMessage(data); @@ -398,18 +403,18 @@ class Receiver extends stream.Writable { var data; if (this._binaryType === 'nodebuffer') { - data = toBuffer(fragments, messageLength); + data = concat(fragments, messageLength); } else if (this._binaryType === 'arraybuffer') { - data = toArrayBuffer(toBuffer(fragments, messageLength)); + data = toArrayBuffer(concat(fragments, messageLength)); } else { data = fragments; } this.emit('message', data); } else { - const buf = toBuffer(fragments, messageLength); + const buf = concat(fragments, messageLength); - if (!validation.isValidUTF8(buf)) { + if (!isValidUTF8(buf)) { this._loop = false; return error(Error, 'invalid UTF-8 sequence', true, 1007); } @@ -440,13 +445,13 @@ class Receiver extends stream.Writable { } else { const code = data.readUInt16BE(0); - if (!validation.isValidStatusCode(code)) { + if (!isValidStatusCode(code)) { return error(RangeError, `invalid status code ${code}`, true, 1002); } const buf = data.slice(2); - if (!validation.isValidUTF8(buf)) { + if (!isValidUTF8(buf)) { return error(Error, 'invalid UTF-8 sequence', true, 1007); } @@ -482,34 +487,6 @@ function error(ErrorCtor, message, prefix, statusCode) { ); Error.captureStackTrace(err, error); - err[constants.kStatusCode] = statusCode; + err[kStatusCode] = statusCode; return err; } - -/** - * Makes a buffer from a list of fragments. - * - * @param {Buffer[]} fragments The list of fragments composing the message - * @param {Number} messageLength The length of the message - * @return {Buffer} - * @private - */ -function toBuffer(fragments, messageLength) { - if (fragments.length === 1) return fragments[0]; - if (fragments.length > 1) return bufferUtil.concat(fragments, messageLength); - return constants.EMPTY_BUFFER; -} - -/** - * Converts a buffer to an `ArrayBuffer`. - * - * @param {Buffer} buf The buffer to convert - * @return {ArrayBuffer} Converted buffer - */ -function toArrayBuffer(buf) { - if (buf.byteLength === buf.buffer.byteLength) { - return buf.buffer; - } - - return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); -} diff --git a/lib/sender.js b/lib/sender.js index 3ac8bf8c6..8e9e5dbe5 100644 --- a/lib/sender.js +++ b/lib/sender.js @@ -1,11 +1,11 @@ 'use strict'; -const crypto = require('crypto'); +const { randomBytes } = require('crypto'); const PerMessageDeflate = require('./permessage-deflate'); -const bufferUtil = require('./buffer-util'); -const validation = require('./validation'); -const constants = require('./constants'); +const { EMPTY_BUFFER } = require('./constants'); +const { isValidStatusCode } = require('./validation'); +const { mask: applyMask, toBuffer } = require('./buffer-util'); /** * HyBi Sender implementation. @@ -77,7 +77,7 @@ class Sender { return [target, data]; } - const mask = crypto.randomBytes(4); + const mask = randomBytes(4); target[1] = payloadLength | 0x80; target[offset - 4] = mask[0]; @@ -86,11 +86,11 @@ class Sender { target[offset - 1] = mask[3]; if (merge) { - bufferUtil.mask(data, mask, target, offset, data.length); + applyMask(data, mask, target, offset, data.length); return [target]; } - bufferUtil.mask(data, mask, data, 0, data.length); + applyMask(data, mask, data, 0, data.length); return [target, data]; } @@ -107,11 +107,8 @@ class Sender { var buf; if (code === undefined) { - buf = constants.EMPTY_BUFFER; - } else if ( - typeof code !== 'number' || - !validation.isValidStatusCode(code) - ) { + buf = EMPTY_BUFFER; + } else if (typeof code !== 'number' || !isValidStatusCode(code)) { throw new TypeError('First argument must be a valid error code number'); } else if (data === undefined || data === '') { buf = Buffer.allocUnsafe(2); @@ -159,23 +156,12 @@ class Sender { * @public */ ping(data, mask, cb) { - var readOnly = true; - - if (!Buffer.isBuffer(data)) { - if (data instanceof ArrayBuffer) { - data = Buffer.from(data); - } else if (ArrayBuffer.isView(data)) { - data = viewToBuffer(data); - } else { - data = Buffer.from(data); - readOnly = false; - } - } + const buf = toBuffer(data); if (this._deflating) { - this.enqueue([this.doPing, data, mask, readOnly, cb]); + this.enqueue([this.doPing, buf, mask, toBuffer.readOnly, cb]); } else { - this.doPing(data, mask, readOnly, cb); + this.doPing(buf, mask, toBuffer.readOnly, cb); } } @@ -210,23 +196,12 @@ class Sender { * @public */ pong(data, mask, cb) { - var readOnly = true; - - if (!Buffer.isBuffer(data)) { - if (data instanceof ArrayBuffer) { - data = Buffer.from(data); - } else if (ArrayBuffer.isView(data)) { - data = viewToBuffer(data); - } else { - data = Buffer.from(data); - readOnly = false; - } - } + const buf = toBuffer(data); if (this._deflating) { - this.enqueue([this.doPong, data, mask, readOnly, cb]); + this.enqueue([this.doPong, buf, mask, toBuffer.readOnly, cb]); } else { - this.doPong(data, mask, readOnly, cb); + this.doPong(buf, mask, toBuffer.readOnly, cb); } } @@ -265,27 +240,15 @@ class Sender { * @public */ send(data, options, cb) { + const buf = toBuffer(data); + const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; var opcode = options.binary ? 2 : 1; var rsv1 = options.compress; - var readOnly = true; - - if (!Buffer.isBuffer(data)) { - if (data instanceof ArrayBuffer) { - data = Buffer.from(data); - } else if (ArrayBuffer.isView(data)) { - data = viewToBuffer(data); - } else { - data = Buffer.from(data); - readOnly = false; - } - } - - const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; if (this._firstFragment) { this._firstFragment = false; if (rsv1 && perMessageDeflate) { - rsv1 = data.length >= perMessageDeflate._threshold; + rsv1 = buf.length >= perMessageDeflate._threshold; } this._compress = rsv1; } else { @@ -301,22 +264,22 @@ class Sender { rsv1, opcode, mask: options.mask, - readOnly + readOnly: toBuffer.readOnly }; if (this._deflating) { - this.enqueue([this.dispatch, data, this._compress, opts, cb]); + this.enqueue([this.dispatch, buf, this._compress, opts, cb]); } else { - this.dispatch(data, this._compress, opts, cb); + this.dispatch(buf, this._compress, opts, cb); } } else { this.sendFrame( - Sender.frame(data, { + Sender.frame(buf, { fin: options.fin, rsv1: false, opcode, mask: options.mask, - readOnly + readOnly: toBuffer.readOnly }), cb ); @@ -397,20 +360,3 @@ class Sender { } module.exports = Sender; - -/** - * Converts an `ArrayBuffer` view into a buffer. - * - * @param {(DataView|TypedArray)} view The view to convert - * @return {Buffer} Converted view - * @private - */ -function viewToBuffer(view) { - const buf = Buffer.from(view.buffer); - - if (view.byteLength !== view.buffer.byteLength) { - return buf.slice(view.byteOffset, view.byteOffset + view.byteLength); - } - - return buf; -} From 161f303b606401bd11d829045793a1ab74503c0e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 6 Mar 2019 07:46:59 +0100 Subject: [PATCH 611/669] [feature] Add ability to follow redirects (#1490) Fixes #812 --- doc/ws.md | 6 +- lib/websocket.js | 158 ++++++++++++++++++++++++++--------------- test/websocket.test.js | 66 +++++++++++++++++ 3 files changed, 171 insertions(+), 59 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index 0b21101eb..c23f1d6c8 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -193,8 +193,12 @@ This class represents a WebSocket. It extends the `EventEmitter`. - `address` {String|url.Url|url.URL} The URL to which to connect. - `protocols` {String|Array} The list of subprotocols. - `options` {Object} + - `followRedirects` {Boolean} Whether or not to follow redirects. Defaults to + `false`. - `handshakeTimeout` {Number} Timeout in milliseconds for the handshake - request. + request. This is reset after every redirection. + - `maxRedirects` {Number} The maximum number of redirects allowed. Defaults + to 10. - `perMessageDeflate` {Boolean|Object} Enable/disable permessage-deflate. - `protocolVersion` {Number} Value of the `Sec-WebSocket-Version` header. - `origin` {String} Value of the `Origin` or `Sec-WebSocket-Origin` header diff --git a/lib/websocket.js b/lib/websocket.js index e90665c13..0dd5fb27b 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -52,12 +52,14 @@ class WebSocket extends EventEmitter { this._closeTimer = null; this._closeCode = 1006; this._extensions = {}; - this._isServer = true; this._receiver = null; this._sender = null; this._socket = null; if (address !== null) { + this._isServer = false; + this._redirects = 0; + if (Array.isArray(protocols)) { protocols = protocols.join(', '); } else if (typeof protocols === 'object' && protocols !== null) { @@ -65,7 +67,9 @@ class WebSocket extends EventEmitter { protocols = undefined; } - initAsClient.call(this, address, protocols, options); + initAsClient(this, address, protocols, options); + } else { + this._isServer = true; } } @@ -423,22 +427,31 @@ module.exports = WebSocket; /** * Initialize a WebSocket client. * + * @param {WebSocket} websocket The client to initialize * @param {(String|url.Url|url.URL)} address The URL to which to connect * @param {String} protocols The subprotocols * @param {Object} options Connection options - * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate - * @param {Number} options.handshakeTimeout Timeout in milliseconds for the handshake request - * @param {Number} options.protocolVersion Value of the `Sec-WebSocket-Version` header - * @param {String} options.origin Value of the `Origin` or `Sec-WebSocket-Origin` header + * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable + * permessage-deflate + * @param {Number} options.handshakeTimeout Timeout in milliseconds for the + * handshake request + * @param {Number} options.protocolVersion Value of the `Sec-WebSocket-Version` + * header + * @param {String} options.origin Value of the `Origin` or + * `Sec-WebSocket-Origin` header * @param {Number} options.maxPayload The maximum allowed message size + * @param {Boolean} options.followRedirects Whether or not to follow redirects + * @param {Number} options.maxRedirects The maximum number of redirects allowed * @private */ -function initAsClient(address, protocols, options) { - options = Object.assign( +function initAsClient(websocket, address, protocols, options) { + const opts = Object.assign( { protocolVersion: protocolVersions[1], + maxPayload: 100 * 1024 * 1024, perMessageDeflate: true, - maxPayload: 100 * 1024 * 1024 + followRedirects: false, + maxRedirects: 10 }, options, { @@ -455,128 +468,151 @@ function initAsClient(address, protocols, options) { } ); - if (!protocolVersions.includes(options.protocolVersion)) { + if (!protocolVersions.includes(opts.protocolVersion)) { throw new RangeError( - `Unsupported protocol version: ${options.protocolVersion} ` + + `Unsupported protocol version: ${opts.protocolVersion} ` + `(supported versions: ${protocolVersions.join(', ')})` ); } - this._isServer = false; - var parsedUrl; if (typeof address === 'object' && address.href !== undefined) { parsedUrl = address; - this.url = address.href; + websocket.url = address.href; } else { // // The WHATWG URL constructor is not available on Node.js < 6.13.0 // parsedUrl = url.URL ? new url.URL(address) : url.parse(address); - this.url = address; + websocket.url = address; } const isUnixSocket = parsedUrl.protocol === 'ws+unix:'; if (!parsedUrl.host && (!isUnixSocket || !parsedUrl.pathname)) { - throw new Error(`Invalid URL: ${this.url}`); + throw new Error(`Invalid URL: ${websocket.url}`); } const isSecure = parsedUrl.protocol === 'wss:' || parsedUrl.protocol === 'https:'; const defaultPort = isSecure ? 443 : 80; const key = crypto.randomBytes(16).toString('base64'); - const httpObj = isSecure ? https : http; + const get = isSecure ? https.get : http.get; const path = parsedUrl.search ? `${parsedUrl.pathname || '/'}${parsedUrl.search}` : parsedUrl.pathname || '/'; var perMessageDeflate; - options.createConnection = isSecure ? tlsConnect : netConnect; - options.defaultPort = options.defaultPort || defaultPort; - options.port = parsedUrl.port || defaultPort; - options.host = parsedUrl.hostname.startsWith('[') + opts.createConnection = isSecure ? tlsConnect : netConnect; + opts.defaultPort = opts.defaultPort || defaultPort; + opts.port = parsedUrl.port || defaultPort; + opts.host = parsedUrl.hostname.startsWith('[') ? parsedUrl.hostname.slice(1, -1) : parsedUrl.hostname; - options.headers = Object.assign( + opts.headers = Object.assign( { - 'Sec-WebSocket-Version': options.protocolVersion, + 'Sec-WebSocket-Version': opts.protocolVersion, 'Sec-WebSocket-Key': key, Connection: 'Upgrade', Upgrade: 'websocket' }, - options.headers + opts.headers ); - options.path = path; - options.timeout = options.handshakeTimeout; + opts.path = path; + opts.timeout = opts.handshakeTimeout; - if (options.perMessageDeflate) { + if (opts.perMessageDeflate) { perMessageDeflate = new PerMessageDeflate( - options.perMessageDeflate !== true ? options.perMessageDeflate : {}, + opts.perMessageDeflate !== true ? opts.perMessageDeflate : {}, false, - options.maxPayload + opts.maxPayload ); - options.headers['Sec-WebSocket-Extensions'] = extension.format({ + opts.headers['Sec-WebSocket-Extensions'] = extension.format({ [PerMessageDeflate.extensionName]: perMessageDeflate.offer() }); } if (protocols) { - options.headers['Sec-WebSocket-Protocol'] = protocols; + opts.headers['Sec-WebSocket-Protocol'] = protocols; } - if (options.origin) { - if (options.protocolVersion < 13) { - options.headers['Sec-WebSocket-Origin'] = options.origin; + if (opts.origin) { + if (opts.protocolVersion < 13) { + opts.headers['Sec-WebSocket-Origin'] = opts.origin; } else { - options.headers.Origin = options.origin; + opts.headers.Origin = opts.origin; } } if (parsedUrl.auth) { - options.auth = parsedUrl.auth; + opts.auth = parsedUrl.auth; } else if (parsedUrl.username || parsedUrl.password) { - options.auth = `${parsedUrl.username}:${parsedUrl.password}`; + opts.auth = `${parsedUrl.username}:${parsedUrl.password}`; } if (isUnixSocket) { const parts = path.split(':'); - options.socketPath = parts[0]; - options.path = parts[1]; + opts.socketPath = parts[0]; + opts.path = parts[1]; } - var req = (this._req = httpObj.get(options)); + var req = (websocket._req = get(opts)); - if (options.handshakeTimeout) { + if (opts.timeout) { req.on('timeout', () => { - abortHandshake(this, req, 'Opening handshake has timed out'); + abortHandshake(websocket, req, 'Opening handshake has timed out'); }); } req.on('error', (err) => { - if (this._req.aborted) return; + if (websocket._req.aborted) return; - req = this._req = null; - this.readyState = WebSocket.CLOSING; - this.emit('error', err); - this.emitClose(); + req = websocket._req = null; + websocket.readyState = WebSocket.CLOSING; + websocket.emit('error', err); + websocket.emitClose(); }); req.on('response', (res) => { - if (this.emit('unexpected-response', req, res)) return; + const location = res.headers.location; + const statusCode = res.statusCode; + + if ( + location && + opts.followRedirects && + statusCode >= 300 && + statusCode < 400 + ) { + if (++websocket._redirects > opts.maxRedirects) { + abortHandshake(websocket, req, 'Maximum redirects exceeded'); + return; + } + + req.abort(); + + const addr = url.URL + ? new url.URL(location, address) + : url.resolve(address, location); - abortHandshake(this, req, `Unexpected server response: ${res.statusCode}`); + initAsClient(websocket, addr, protocols, options); + } else if (!websocket.emit('unexpected-response', req, res)) { + abortHandshake( + websocket, + req, + `Unexpected server response: ${res.statusCode}` + ); + } }); req.on('upgrade', (res, socket, head) => { - this.emit('upgrade', res); + websocket.emit('upgrade', res); // // The user may have closed the connection from a listener of the `upgrade` // event. // - if (this.readyState !== WebSocket.CONNECTING) return; + if (websocket.readyState !== WebSocket.CONNECTING) return; - req = this._req = null; + req = websocket._req = null; const digest = crypto .createHash('sha1') @@ -584,7 +620,7 @@ function initAsClient(address, protocols, options) { .digest('base64'); if (res.headers['sec-websocket-accept'] !== digest) { - abortHandshake(this, socket, 'Invalid Sec-WebSocket-Accept header'); + abortHandshake(websocket, socket, 'Invalid Sec-WebSocket-Accept header'); return; } @@ -601,11 +637,11 @@ function initAsClient(address, protocols, options) { } if (protError) { - abortHandshake(this, socket, protError); + abortHandshake(websocket, socket, protError); return; } - if (serverProt) this.protocol = serverProt; + if (serverProt) websocket.protocol = serverProt; if (perMessageDeflate) { try { @@ -615,15 +651,21 @@ function initAsClient(address, protocols, options) { if (extensions[PerMessageDeflate.extensionName]) { perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]); - this._extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + websocket._extensions[ + PerMessageDeflate.extensionName + ] = perMessageDeflate; } } catch (err) { - abortHandshake(this, socket, 'Invalid Sec-WebSocket-Extensions header'); + abortHandshake( + websocket, + socket, + 'Invalid Sec-WebSocket-Extensions header' + ); return; } } - this.setSocket(socket, head, options.maxPayload); + websocket.setSocket(socket, head, opts.maxPayload); }); } diff --git a/test/websocket.test.js b/test/websocket.test.js index a9af17cf7..c86cd5304 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -664,6 +664,72 @@ describe('WebSocket', function() { ws.on('close', () => wss.close(done)); }); }); + + it('does not follow redirects by default', function(done) { + server.once('upgrade', (req, socket) => { + socket.end( + 'HTTP/1.1 301 Moved Permanently\r\n' + + 'Location: ws://localhost:8080\r\n' + + '\r\n' + ); + }); + + const ws = new WebSocket(`ws://localhost:${server.address().port}`); + + ws.on('open', () => done(new Error("Unexpected 'open' event"))); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'Unexpected server response: 301'); + assert.strictEqual(ws._redirects, 0); + ws.on('close', () => done()); + }); + }); + + it('honors the `followRedirects` option', function(done) { + const wss = new WebSocket.Server({ noServer: true, path: '/foo' }); + + server.once('upgrade', (req, socket) => { + socket.end('HTTP/1.1 302 Found\r\nLocation: /foo\r\n\r\n'); + server.once('upgrade', (req, socket, head) => { + wss.handleUpgrade(req, socket, head, () => {}); + }); + }); + + const port = server.address().port; + const ws = new WebSocket(`ws://localhost:${port}`, { + followRedirects: true + }); + + ws.on('open', () => { + assert.strictEqual(ws.url, `ws://localhost:${port}/foo`); + assert.strictEqual(ws._redirects, 1); + ws.on('close', () => done()); + ws.close(); + }); + }); + + it('honors the `maxRedirects` option', function(done) { + const onUpgrade = (req, socket) => { + socket.end('HTTP/1.1 302 Found\r\nLocation: /\r\n\r\n'); + }; + + server.on('upgrade', onUpgrade); + + const ws = new WebSocket(`ws://localhost:${server.address().port}`, { + followRedirects: true, + maxRedirects: 1 + }); + + ws.on('open', () => done(new Error("Unexpected 'open' event"))); + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'Maximum redirects exceeded'); + assert.strictEqual(ws._redirects, 2); + + server.removeListener('upgrade', onUpgrade); + ws.on('close', () => done()); + }); + }); }); describe('Connection with query string', function() { From eb6f8b05a5862b4e422c98395afff4584c9f1839 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 6 Mar 2019 08:07:29 +0100 Subject: [PATCH 612/669] [dist] 6.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d84cc76c5..2815effc8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "6.1.4", + "version": "6.2.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 1842197de24d644b50cc325b83cb3099a19939f8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 15 Mar 2019 16:22:44 +0100 Subject: [PATCH 613/669] [test] Remove useless test It should have been removed in 8ead18d0. --- test/sender.test.js | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/test/sender.test.js b/test/sender.test.js index c6be11a83..0e2dd16a2 100644 --- a/test/sender.test.js +++ b/test/sender.test.js @@ -185,29 +185,6 @@ describe('Sender', function() { sender.send('data', { compress: true, fin: false }); sender.send(Buffer.alloc(0), { compress: true, fin: true }); }); - - it('handles many send calls while processing without crashing on flush', function(done) { - let count = 0; - const perMessageDeflate = new PerMessageDeflate(); - const mockSocket = new MockSocket({ - write: () => { - if (++count > 1e4) done(); - } - }); - const sender = new Sender(mockSocket, { - 'permessage-deflate': perMessageDeflate - }); - - perMessageDeflate.accept([{}]); - - for (let i = 0; i < 1e4; i++) { - sender.processing = true; - sender.send('hi', { compress: false, fin: true }); - } - - sender.processing = false; - sender.send('hi', { compress: false, fin: true }); - }); }); describe('#ping', function() { From 6c225842ae494457eb3e338a269ada477b5ebeb2 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 15 Mar 2019 16:29:01 +0100 Subject: [PATCH 614/669] [minor] Buffer writes if the frame to send is made of multiple chunks This allows to write all chunks at the same time via `Socket.prototype._writev()`. --- lib/sender.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/sender.js b/lib/sender.js index 8e9e5dbe5..093eef8a9 100644 --- a/lib/sender.js +++ b/lib/sender.js @@ -351,8 +351,10 @@ class Sender { */ sendFrame(list, cb) { if (list.length === 2) { + this._socket.cork(); this._socket.write(list[0]); this._socket.write(list[1], cb); + this._socket.uncork(); } else { this._socket.write(list[0], cb); } From a40e29fd32753c154352ac416fac47de63d7b9cd Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 15 Mar 2019 17:47:51 +0100 Subject: [PATCH 615/669] [minor] Remove length threshold Do not use a length threshold to determine whether or not to copy data. --- lib/sender.js | 16 +++---- test/sender.test.js | 102 +++++++++++++++++++++++++++----------------- 2 files changed, 68 insertions(+), 50 deletions(-) diff --git a/lib/sender.js b/lib/sender.js index 093eef8a9..51158b152 100644 --- a/lib/sender.js +++ b/lib/sender.js @@ -43,7 +43,7 @@ class Sender { * @public */ static frame(data, options) { - const merge = data.length < 1024 || (options.mask && options.readOnly); + const merge = options.mask && options.readOnly; var offset = options.mask ? 6 : 2; var payloadLength = data.length; @@ -60,6 +60,8 @@ class Sender { target[0] = options.fin ? options.opcode | 0x80 : options.opcode; if (options.rsv1) target[0] |= 0x40; + target[1] = payloadLength; + if (payloadLength === 126) { target.writeUInt16BE(data.length, 2); } else if (payloadLength === 127) { @@ -67,19 +69,11 @@ class Sender { target.writeUInt32BE(data.length, 6); } - if (!options.mask) { - target[1] = payloadLength; - if (merge) { - data.copy(target, offset); - return [target]; - } - - return [target, data]; - } + if (!options.mask) return [target, data]; const mask = randomBytes(4); - target[1] = payloadLength | 0x80; + target[1] |= 0x80; target[offset - 4] = mask[0]; target[offset - 3] = mask[1]; target[offset - 2] = mask[2]; diff --git a/test/sender.test.js b/test/sender.test.js index 0e2dd16a2..f20294291 100644 --- a/test/sender.test.js +++ b/test/sender.test.js @@ -13,7 +13,9 @@ class MockSocket { if (write) this.write = write; } + cork() {} write() {} + uncork() {} } describe('Sender', function() { @@ -87,17 +89,20 @@ describe('Sender', function() { }); it('compresses all frames in a fragmented message', function(done) { - const fragments = []; + const chunks = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); const mockSocket = new MockSocket({ - write: (data) => { - fragments.push(data); - if (fragments.length !== 2) return; + write: (chunk) => { + chunks.push(chunk); + if (chunks.length !== 4) return; + + assert.strictEqual(chunks[0].length, 2); + assert.strictEqual(chunks[0][0] & 0x40, 0x40); + assert.strictEqual(chunks[1].length, 9); - assert.strictEqual(fragments[0][0] & 0x40, 0x40); - assert.strictEqual(fragments[0].length, 11); - assert.strictEqual(fragments[1][0] & 0x40, 0x00); - assert.strictEqual(fragments[1].length, 6); + assert.strictEqual(chunks[2].length, 2); + assert.strictEqual(chunks[2][0] & 0x40, 0x00); + assert.strictEqual(chunks[3].length, 4); done(); } }); @@ -112,17 +117,20 @@ describe('Sender', function() { }); it('compresses no frames in a fragmented message', function(done) { - const fragments = []; + const chunks = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); const mockSocket = new MockSocket({ - write: (data) => { - fragments.push(data); - if (fragments.length !== 2) return; + write: (chunk) => { + chunks.push(chunk); + if (chunks.length !== 4) return; - assert.strictEqual(fragments[0][0] & 0x40, 0x00); - assert.strictEqual(fragments[0].length, 4); - assert.strictEqual(fragments[1][0] & 0x40, 0x00); - assert.strictEqual(fragments[1].length, 5); + assert.strictEqual(chunks[0].length, 2); + assert.strictEqual(chunks[0][0] & 0x40, 0x00); + assert.strictEqual(chunks[1].length, 2); + + assert.strictEqual(chunks[2].length, 2); + assert.strictEqual(chunks[2][0] & 0x40, 0x00); + assert.strictEqual(chunks[3].length, 3); done(); } }); @@ -137,17 +145,20 @@ describe('Sender', function() { }); it('compresses empty buffer as first fragment', function(done) { - const fragments = []; + const chunks = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const mockSocket = new MockSocket({ - write: (data) => { - fragments.push(data); - if (fragments.length !== 2) return; + write: (chunk) => { + chunks.push(chunk); + if (chunks.length !== 4) return; - assert.strictEqual(fragments[0][0] & 0x40, 0x40); - assert.strictEqual(fragments[0].length, 3); - assert.strictEqual(fragments[1][0] & 0x40, 0x00); - assert.strictEqual(fragments[1].length, 8); + assert.strictEqual(chunks[0].length, 2); + assert.strictEqual(chunks[0][0] & 0x40, 0x40); + assert.strictEqual(chunks[1].length, 1); + + assert.strictEqual(chunks[2].length, 2); + assert.strictEqual(chunks[2][0] & 0x40, 0x00); + assert.strictEqual(chunks[3].length, 6); done(); } }); @@ -162,17 +173,20 @@ describe('Sender', function() { }); it('compresses empty buffer as last fragment', function(done) { - const fragments = []; + const chunks = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const mockSocket = new MockSocket({ - write: (data) => { - fragments.push(data); - if (fragments.length !== 2) return; + write: (chunk) => { + chunks.push(chunk); + if (chunks.length !== 4) return; + + assert.strictEqual(chunks[0].length, 2); + assert.strictEqual(chunks[0][0] & 0x40, 0x40); + assert.strictEqual(chunks[1].length, 10); - assert.strictEqual(fragments[0][0] & 0x40, 0x40); - assert.strictEqual(fragments[0].length, 12); - assert.strictEqual(fragments[1][0] & 0x40, 0x00); - assert.strictEqual(fragments[1].length, 3); + assert.strictEqual(chunks[2].length, 2); + assert.strictEqual(chunks[2][0] & 0x40, 0x00); + assert.strictEqual(chunks[3].length, 1); done(); } }); @@ -193,10 +207,15 @@ describe('Sender', function() { let count = 0; const mockSocket = new MockSocket({ write: (data) => { - if (++count === 1) return; + if (++count < 3) return; - assert.ok(data.equals(Buffer.from([0x89, 0x02, 0x68, 0x69]))); - if (count === 4) done(); + if (count % 2) { + assert.ok(data.equals(Buffer.from([0x89, 0x02]))); + } else { + assert.ok(data.equals(Buffer.from([0x68, 0x69]))); + } + + if (count === 8) done(); } }); const sender = new Sender(mockSocket, { @@ -220,10 +239,15 @@ describe('Sender', function() { let count = 0; const mockSocket = new MockSocket({ write: (data) => { - if (++count === 1) return; + if (++count < 3) return; + + if (count % 2) { + assert.ok(data.equals(Buffer.from([0x8a, 0x02]))); + } else { + assert.ok(data.equals(Buffer.from([0x68, 0x69]))); + } - assert.ok(data.equals(Buffer.from([0x8a, 0x02, 0x68, 0x69]))); - if (count === 4) done(); + if (count === 8) done(); } }); const sender = new Sender(mockSocket, { @@ -263,7 +287,7 @@ describe('Sender', function() { sender.send('baz', { compress: true, fin: true }); sender.close(1000, undefined, false, () => { - assert.strictEqual(count, 4); + assert.strictEqual(count, 8); done(); }); }); From 3df82423e6e77d046fdc0e5e045401dda1ae4857 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 16 Mar 2019 20:04:19 +0100 Subject: [PATCH 616/669] [test] Remove comment to disable eslint rule --- test/websocket.test.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/websocket.test.js b/test/websocket.test.js index c86cd5304..91aa9ebbf 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -217,15 +217,13 @@ describe('WebSocket', function() { wss.on('connection', (ws) => { const data = Buffer.alloc(1024, 61); - // eslint-disable-next-line no-constant-condition - while (true) { - if (ws._socket.bufferSize > 0) { - assert.strictEqual(ws.bufferedAmount, ws._socket.bufferSize); - break; - } + while (ws._socket.bufferSize === 0) { ws.send(data); } + assert.ok(ws._socket.bufferSize > 0); + assert.strictEqual(ws.bufferedAmount, ws._socket.bufferSize); + ws.on('close', () => wss.close(done)); ws.close(); }); From 148c37363a2d4267135c970cfe18eb7b1be405fd Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 17 Mar 2019 07:49:47 +0100 Subject: [PATCH 617/669] [test] Prefer arrow functions --- test/extension.test.js | 34 ++-- test/permessage-deflate.test.js | 78 ++++----- test/receiver.test.js | 100 +++++------ test/sender.test.js | 34 ++-- test/websocket-server.test.js | 112 ++++++------- test/websocket.integration.js | 6 +- test/websocket.test.js | 282 ++++++++++++++++---------------- 7 files changed, 322 insertions(+), 324 deletions(-) diff --git a/test/extension.test.js b/test/extension.test.js index a051ab269..1179f05c3 100644 --- a/test/extension.test.js +++ b/test/extension.test.js @@ -4,20 +4,20 @@ const assert = require('assert'); const extension = require('../lib/extension'); -describe('extension', function() { - describe('parse', function() { - it('returns an empty object if the argument is `undefined`', function() { +describe('extension', () => { + describe('parse', () => { + it('returns an empty object if the argument is `undefined`', () => { assert.deepStrictEqual(extension.parse(), {}); assert.deepStrictEqual(extension.parse(''), {}); }); - it('parses a single extension', function() { + it('parses a single extension', () => { const extensions = extension.parse('foo'); assert.deepStrictEqual(extensions, { foo: [{}] }); }); - it('parses params', function() { + it('parses params', () => { const extensions = extension.parse('foo;bar;baz=1;bar=2'); assert.deepStrictEqual(extensions, { @@ -25,7 +25,7 @@ describe('extension', function() { }); }); - it('parses multiple extensions', function() { + it('parses multiple extensions', () => { const extensions = extension.parse('foo,bar;baz,foo;baz'); assert.deepStrictEqual(extensions, { @@ -34,7 +34,7 @@ describe('extension', function() { }); }); - it('parses quoted params', function() { + it('parses quoted params', () => { assert.deepStrictEqual(extension.parse('foo;bar="hi"'), { foo: [{ bar: ['hi'] }] }); @@ -57,7 +57,7 @@ describe('extension', function() { ); }); - it('works with names that match `Object.prototype` property names', function() { + it('works with names that match `Object.prototype` property names', () => { const parse = extension.parse; assert.deepStrictEqual(parse('hasOwnProperty, toString'), { @@ -69,7 +69,7 @@ describe('extension', function() { }); }); - it('ignores the optional white spaces', function() { + it('ignores the optional white spaces', () => { const header = 'foo; bar\t; \tbaz=1\t ; bar="1"\t\t, \tqux\t ;norf '; assert.deepStrictEqual(extension.parse(header), { @@ -78,7 +78,7 @@ describe('extension', function() { }); }); - it('throws an error if a name is empty', function() { + it('throws an error if a name is empty', () => { [ [',', 0], ['foo,,', 4], @@ -99,7 +99,7 @@ describe('extension', function() { }); }); - it('throws an error if a white space is misplaced', function() { + it('throws an error if a white space is misplaced', () => { [ ['f oo', 2], ['foo;ba r', 7], @@ -115,7 +115,7 @@ describe('extension', function() { }); }); - it('throws an error if a token contains invalid characters', function() { + it('throws an error if a token contains invalid characters', () => { [ ['f@o', 1], ['f\\oo', 1], @@ -141,7 +141,7 @@ describe('extension', function() { }); }); - it('throws an error if the header value ends prematurely', function() { + it('throws an error if the header value ends prematurely', () => { [ 'foo, ', 'foo;', @@ -159,20 +159,20 @@ describe('extension', function() { }); }); - describe('format', function() { - it('formats a single extension', function() { + describe('format', () => { + it('formats a single extension', () => { const extensions = extension.format({ foo: {} }); assert.strictEqual(extensions, 'foo'); }); - it('formats params', function() { + it('formats params', () => { const extensions = extension.format({ foo: { bar: [true, 2], baz: 1 } }); assert.strictEqual(extensions, 'foo; bar; bar=2; baz=1'); }); - it('formats multiple extensions', function() { + it('formats multiple extensions', () => { const extensions = extension.format({ foo: [{}, { baz: true }], bar: { baz: true } diff --git a/test/permessage-deflate.test.js b/test/permessage-deflate.test.js index c9722bc5e..6af5bf56f 100644 --- a/test/permessage-deflate.test.js +++ b/test/permessage-deflate.test.js @@ -5,9 +5,9 @@ const assert = require('assert'); const PerMessageDeflate = require('../lib/permessage-deflate'); const extension = require('../lib/extension'); -describe('PerMessageDeflate', function() { - describe('#offer', function() { - it('creates an offer', function() { +describe('PerMessageDeflate', () => { + describe('#offer', () => { + it('creates an offer', () => { const perMessageDeflate = new PerMessageDeflate(); assert.deepStrictEqual(perMessageDeflate.offer(), { @@ -15,7 +15,7 @@ describe('PerMessageDeflate', function() { }); }); - it('uses the configuration options', function() { + it('uses the configuration options', () => { const perMessageDeflate = new PerMessageDeflate({ serverNoContextTakeover: true, clientNoContextTakeover: true, @@ -32,8 +32,8 @@ describe('PerMessageDeflate', function() { }); }); - describe('#accept', function() { - it('throws an error if a parameter has multiple values', function() { + describe('#accept', () => { + it('throws an error if a parameter has multiple values', () => { const perMessageDeflate = new PerMessageDeflate(); const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; server_no_context_takeover' @@ -45,7 +45,7 @@ describe('PerMessageDeflate', function() { ); }); - it('throws an error if a parameter has an invalid name', function() { + it('throws an error if a parameter has an invalid name', () => { const perMessageDeflate = new PerMessageDeflate(); const extensions = extension.parse('permessage-deflate;foo'); @@ -55,7 +55,7 @@ describe('PerMessageDeflate', function() { ); }); - it('throws an error if client_no_context_takeover has a value', function() { + it('throws an error if client_no_context_takeover has a value', () => { const perMessageDeflate = new PerMessageDeflate(); const extensions = extension.parse( 'permessage-deflate; client_no_context_takeover=10' @@ -67,7 +67,7 @@ describe('PerMessageDeflate', function() { ); }); - it('throws an error if server_no_context_takeover has a value', function() { + it('throws an error if server_no_context_takeover has a value', () => { const perMessageDeflate = new PerMessageDeflate(); const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover=10' @@ -79,7 +79,7 @@ describe('PerMessageDeflate', function() { ); }); - it('throws an error if server_max_window_bits has an invalid value', function() { + it('throws an error if server_max_window_bits has an invalid value', () => { const perMessageDeflate = new PerMessageDeflate(); let extensions = extension.parse( @@ -99,14 +99,14 @@ describe('PerMessageDeflate', function() { ); }); - describe('As server', function() { - it('accepts an offer with no parameters', function() { + describe('As server', () => { + it('accepts an offer with no parameters', () => { const perMessageDeflate = new PerMessageDeflate({}, true); assert.deepStrictEqual(perMessageDeflate.accept([{}]), {}); }); - it('accepts an offer with parameters', function() { + it('accepts an offer with parameters', () => { const perMessageDeflate = new PerMessageDeflate({}, true); const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; ' + @@ -125,7 +125,7 @@ describe('PerMessageDeflate', function() { ); }); - it('prefers the configuration options', function() { + it('prefers the configuration options', () => { const perMessageDeflate = new PerMessageDeflate( { serverNoContextTakeover: true, @@ -150,7 +150,7 @@ describe('PerMessageDeflate', function() { ); }); - it('accepts the first supported offer', function() { + it('accepts the first supported offer', () => { const perMessageDeflate = new PerMessageDeflate( { serverMaxWindowBits: 11 }, true @@ -167,7 +167,7 @@ describe('PerMessageDeflate', function() { ); }); - it('throws an error if server_no_context_takeover is unsupported', function() { + it('throws an error if server_no_context_takeover is unsupported', () => { const perMessageDeflate = new PerMessageDeflate( { serverNoContextTakeover: false }, true @@ -182,7 +182,7 @@ describe('PerMessageDeflate', function() { ); }); - it('throws an error if server_max_window_bits is unsupported', function() { + it('throws an error if server_max_window_bits is unsupported', () => { const perMessageDeflate = new PerMessageDeflate( { serverMaxWindowBits: false }, true @@ -197,7 +197,7 @@ describe('PerMessageDeflate', function() { ); }); - it('throws an error if server_max_window_bits is less than configuration', function() { + it('throws an error if server_max_window_bits is less than configuration', () => { const perMessageDeflate = new PerMessageDeflate( { serverMaxWindowBits: 11 }, true @@ -212,7 +212,7 @@ describe('PerMessageDeflate', function() { ); }); - it('throws an error if client_max_window_bits is unsupported on client', function() { + it('throws an error if client_max_window_bits is unsupported on client', () => { const perMessageDeflate = new PerMessageDeflate( { clientMaxWindowBits: 10 }, true @@ -225,7 +225,7 @@ describe('PerMessageDeflate', function() { ); }); - it('throws an error if client_max_window_bits has an invalid value', function() { + it('throws an error if client_max_window_bits has an invalid value', () => { const perMessageDeflate = new PerMessageDeflate({}, true); const extensions = extension.parse( @@ -238,14 +238,14 @@ describe('PerMessageDeflate', function() { }); }); - describe('As client', function() { - it('accepts a response with no parameters', function() { + describe('As client', () => { + it('accepts a response with no parameters', () => { const perMessageDeflate = new PerMessageDeflate({}); assert.deepStrictEqual(perMessageDeflate.accept([{}]), {}); }); - it('accepts a response with parameters', function() { + it('accepts a response with parameters', () => { const perMessageDeflate = new PerMessageDeflate({}); const extensions = extension.parse( 'permessage-deflate; server_no_context_takeover; ' + @@ -264,7 +264,7 @@ describe('PerMessageDeflate', function() { ); }); - it('throws an error if client_no_context_takeover is unsupported', function() { + it('throws an error if client_no_context_takeover is unsupported', () => { const perMessageDeflate = new PerMessageDeflate({ clientNoContextTakeover: false }); @@ -278,7 +278,7 @@ describe('PerMessageDeflate', function() { ); }); - it('throws an error if client_max_window_bits is unsupported', function() { + it('throws an error if client_max_window_bits is unsupported', () => { const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: false }); @@ -292,7 +292,7 @@ describe('PerMessageDeflate', function() { ); }); - it('throws an error if client_max_window_bits is greater than configuration', function() { + it('throws an error if client_max_window_bits is greater than configuration', () => { const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }); @@ -306,7 +306,7 @@ describe('PerMessageDeflate', function() { ); }); - it('throws an error if client_max_window_bits has an invalid value', function() { + it('throws an error if client_max_window_bits has an invalid value', () => { const perMessageDeflate = new PerMessageDeflate(); let extensions = extension.parse( @@ -326,7 +326,7 @@ describe('PerMessageDeflate', function() { ); }); - it('uses the config value if client_max_window_bits is not specified', function() { + it('uses the config value if client_max_window_bits is not specified', () => { const perMessageDeflate = new PerMessageDeflate({ clientMaxWindowBits: 10 }); @@ -338,8 +338,8 @@ describe('PerMessageDeflate', function() { }); }); - describe('#compress and #decompress', function() { - it('works with unfragmented messages', function(done) { + describe('#compress and #decompress', () => { + it('works with unfragmented messages', (done) => { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const buf = Buffer.from([1, 2, 3]); @@ -356,7 +356,7 @@ describe('PerMessageDeflate', function() { }); }); - it('works with fragmented messages', function(done) { + it('works with fragmented messages', (done) => { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const buf = Buffer.from([1, 2, 3, 4]); @@ -382,7 +382,7 @@ describe('PerMessageDeflate', function() { }); }); - it('works with the negotiated parameters', function(done) { + it('works with the negotiated parameters', (done) => { const perMessageDeflate = new PerMessageDeflate({ threshold: 0, memLevel: 5, @@ -409,7 +409,7 @@ describe('PerMessageDeflate', function() { }); }); - it('honors the `level` option', function(done) { + it('honors the `level` option', (done) => { const lev0 = new PerMessageDeflate({ threshold: 0, zlibDeflateOptions: { level: 0 } @@ -453,7 +453,7 @@ describe('PerMessageDeflate', function() { }); }); - it('honors the `zlib{Deflate,Inflate}Options` option', function(done) { + it('honors the `zlib{Deflate,Inflate}Options` option', (done) => { const lev0 = new PerMessageDeflate({ threshold: 0, zlibDeflateOptions: { @@ -518,7 +518,7 @@ describe('PerMessageDeflate', function() { }); }); - it("doesn't use contex takeover if not allowed", function(done) { + it("doesn't use contex takeover if not allowed", (done) => { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }, true); const extensions = extension.parse( 'permessage-deflate;server_no_context_takeover' @@ -549,7 +549,7 @@ describe('PerMessageDeflate', function() { }); }); - it('uses contex takeover if allowed', function(done) { + it('uses contex takeover if allowed', (done) => { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }, true); const extensions = extension.parse('permessage-deflate'); const buf = Buffer.from('foofoo'); @@ -578,7 +578,7 @@ describe('PerMessageDeflate', function() { }); }); - it('calls the callback when an error occurs (inflate)', function(done) { + it('calls the callback when an error occurs (inflate)', (done) => { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const data = Buffer.from('something invalid'); @@ -590,7 +590,7 @@ describe('PerMessageDeflate', function() { }); }); - it("doesn't call the callback twice when `maxPayload` is exceeded", function(done) { + it("doesn't call the callback twice when `maxPayload` is exceeded", (done) => { const perMessageDeflate = new PerMessageDeflate( { threshold: 0 }, false, @@ -610,7 +610,7 @@ describe('PerMessageDeflate', function() { }); }); - it("doesn't call the callback if the deflate stream is closed prematurely", function(done) { + it("doesn't call the callback if the deflate stream is closed prematurely", (done) => { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const buf = Buffer.from('A'.repeat(50)); diff --git a/test/receiver.test.js b/test/receiver.test.js index 3bc680719..76ad6a927 100644 --- a/test/receiver.test.js +++ b/test/receiver.test.js @@ -10,8 +10,8 @@ const Sender = require('../lib/sender'); const kStatusCode = constants.kStatusCode; -describe('Receiver', function() { - it('parses an unmasked text message', function(done) { +describe('Receiver', () => { + it('parses an unmasked text message', (done) => { const receiver = new Receiver(); receiver.on('message', (data) => { @@ -22,7 +22,7 @@ describe('Receiver', function() { receiver.write(Buffer.from('810548656c6c6f', 'hex')); }); - it('parses a close message', function(done) { + it('parses a close message', (done) => { const receiver = new Receiver(); receiver.on('conclude', (code, data) => { @@ -34,7 +34,7 @@ describe('Receiver', function() { receiver.write(Buffer.from('8800', 'hex')); }); - it('parses a close message spanning multiple writes', function(done) { + it('parses a close message spanning multiple writes', (done) => { const receiver = new Receiver(); receiver.on('conclude', (code, data) => { @@ -47,7 +47,7 @@ describe('Receiver', function() { receiver.write(Buffer.from('03e8444F4E45', 'hex')); }); - it('parses a masked text message', function(done) { + it('parses a masked text message', (done) => { const receiver = new Receiver(); receiver.on('message', (data) => { @@ -60,7 +60,7 @@ describe('Receiver', function() { ); }); - it('parses a masked text message longer than 125 B', function(done) { + it('parses a masked text message longer than 125 B', (done) => { const receiver = new Receiver(); const msg = 'A'.repeat(200); @@ -83,7 +83,7 @@ describe('Receiver', function() { setImmediate(() => receiver.write(frame.slice(2))); }); - it('parses a really long masked text message', function(done) { + it('parses a really long masked text message', (done) => { const receiver = new Receiver(); const msg = 'A'.repeat(64 * 1024); @@ -105,7 +105,7 @@ describe('Receiver', function() { receiver.write(frame); }); - it('parses a 300 B fragmented masked text message', function(done) { + it('parses a 300 B fragmented masked text message', (done) => { const receiver = new Receiver(); const msg = 'A'.repeat(300); @@ -136,7 +136,7 @@ describe('Receiver', function() { receiver.write(frame2); }); - it('parses a ping message', function(done) { + it('parses a ping message', (done) => { const receiver = new Receiver(); const msg = 'Hello'; @@ -158,7 +158,7 @@ describe('Receiver', function() { receiver.write(frame); }); - it('parses a ping message with no data', function(done) { + it('parses a ping message with no data', (done) => { const receiver = new Receiver(); receiver.on('ping', (data) => { @@ -169,7 +169,7 @@ describe('Receiver', function() { receiver.write(Buffer.from('8900', 'hex')); }); - it('parses a 300 B fragmented masked text message with a ping in the middle (1/2)', function(done) { + it('parses a 300 B fragmented masked text message with a ping in the middle (1/2)', (done) => { const receiver = new Receiver(); const msg = 'A'.repeat(300); const pingMessage = 'Hello'; @@ -215,7 +215,7 @@ describe('Receiver', function() { receiver.write(frame3); }); - it('parses a 300 B fragmented masked text message with a ping in the middle (2/2)', function(done) { + it('parses a 300 B fragmented masked text message with a ping in the middle (2/2)', (done) => { const receiver = new Receiver(); const msg = 'A'.repeat(300); const pingMessage = 'Hello'; @@ -271,7 +271,7 @@ describe('Receiver', function() { } }); - it('parses a 100 B masked binary message', function(done) { + it('parses a 100 B masked binary message', (done) => { const receiver = new Receiver(); const msg = crypto.randomBytes(100); @@ -293,7 +293,7 @@ describe('Receiver', function() { receiver.write(frame); }); - it('parses a 256 B masked binary message', function(done) { + it('parses a 256 B masked binary message', (done) => { const receiver = new Receiver(); const msg = crypto.randomBytes(256); @@ -315,7 +315,7 @@ describe('Receiver', function() { receiver.write(frame); }); - it('parses a 200 KiB masked binary message', function(done) { + it('parses a 200 KiB masked binary message', (done) => { const receiver = new Receiver(); const msg = crypto.randomBytes(200 * 1024); @@ -337,7 +337,7 @@ describe('Receiver', function() { receiver.write(frame); }); - it('parses a 200 KiB unmasked binary message', function(done) { + it('parses a 200 KiB unmasked binary message', (done) => { const receiver = new Receiver(); const msg = crypto.randomBytes(200 * 1024); @@ -359,7 +359,7 @@ describe('Receiver', function() { receiver.write(frame); }); - it('parses a compressed message', function(done) { + it('parses a compressed message', (done) => { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); @@ -381,7 +381,7 @@ describe('Receiver', function() { }); }); - it('parses a compressed and fragmented message', function(done) { + it('parses a compressed and fragmented message', (done) => { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); @@ -396,13 +396,13 @@ describe('Receiver', function() { done(); }); - perMessageDeflate.compress(buf1, false, function(err, fragment1) { + perMessageDeflate.compress(buf1, false, (err, fragment1) => { if (err) return done(err); receiver.write(Buffer.from([0x41, fragment1.length])); receiver.write(fragment1); - perMessageDeflate.compress(buf2, true, function(err, fragment2) { + perMessageDeflate.compress(buf2, true, (err, fragment2) => { if (err) return done(err); receiver.write(Buffer.from([0x80, fragment2.length])); @@ -411,7 +411,7 @@ describe('Receiver', function() { }); }); - it('parses a buffer with thousands of frames', function(done) { + it('parses a buffer with thousands of frames', (done) => { const buf = Buffer.allocUnsafe(40000); for (let i = 0; i < buf.length; i += 2) { @@ -430,7 +430,7 @@ describe('Receiver', function() { receiver.write(buf); }); - it('resets `totalPayloadLength` only on final frame (unfragmented)', function(done) { + it('resets `totalPayloadLength` only on final frame (unfragmented)', (done) => { const receiver = new Receiver(undefined, {}, 10); receiver.on('message', (data) => { @@ -443,7 +443,7 @@ describe('Receiver', function() { receiver.write(Buffer.from('810548656c6c6f', 'hex')); }); - it('resets `totalPayloadLength` only on final frame (fragmented)', function(done) { + it('resets `totalPayloadLength` only on final frame (fragmented)', (done) => { const receiver = new Receiver(undefined, {}, 10); receiver.on('message', (data) => { @@ -458,7 +458,7 @@ describe('Receiver', function() { receiver.write(Buffer.from('80036c6c6f', 'hex')); }); - it('resets `totalPayloadLength` only on final frame (fragmented + ping)', function(done) { + it('resets `totalPayloadLength` only on final frame (fragmented + ping)', (done) => { const receiver = new Receiver(undefined, {}, 10); let data; @@ -479,7 +479,7 @@ describe('Receiver', function() { receiver.write(Buffer.from('80036c6c6f', 'hex')); }); - it('ignores any data after a close frame', function(done) { + it('ignores any data after a close frame', (done) => { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); @@ -500,7 +500,7 @@ describe('Receiver', function() { receiver.write(Buffer.from([0x81, 0x00])); }); - it('emits an error if RSV1 is on and permessage-deflate is disabled', function(done) { + it('emits an error if RSV1 is on and permessage-deflate is disabled', (done) => { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -516,7 +516,7 @@ describe('Receiver', function() { receiver.write(Buffer.from([0xc2, 0x80, 0x00, 0x00, 0x00, 0x00])); }); - it('emits an error if RSV1 is on and opcode is 0', function(done) { + it('emits an error if RSV1 is on and opcode is 0', (done) => { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); @@ -537,7 +537,7 @@ describe('Receiver', function() { receiver.write(Buffer.from([0x40, 0x00])); }); - it('emits an error if RSV2 is on', function(done) { + it('emits an error if RSV2 is on', (done) => { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -553,7 +553,7 @@ describe('Receiver', function() { receiver.write(Buffer.from([0xa2, 0x00])); }); - it('emits an error if RSV3 is on', function(done) { + it('emits an error if RSV3 is on', (done) => { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -569,7 +569,7 @@ describe('Receiver', function() { receiver.write(Buffer.from([0x92, 0x00])); }); - it('emits an error if the first frame in a fragmented message has opcode 0', function(done) { + it('emits an error if the first frame in a fragmented message has opcode 0', (done) => { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -585,7 +585,7 @@ describe('Receiver', function() { receiver.write(Buffer.from([0x00, 0x00])); }); - it('emits an error if a frame has opcode 1 in the middle of a fragmented message', function(done) { + it('emits an error if a frame has opcode 1 in the middle of a fragmented message', (done) => { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -602,7 +602,7 @@ describe('Receiver', function() { receiver.write(Buffer.from([0x01, 0x00])); }); - it('emits an error if a frame has opcode 2 in the middle of a fragmented message', function(done) { + it('emits an error if a frame has opcode 2 in the middle of a fragmented message', (done) => { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -619,7 +619,7 @@ describe('Receiver', function() { receiver.write(Buffer.from([0x02, 0x00])); }); - it('emits an error if a control frame has the FIN bit off', function(done) { + it('emits an error if a control frame has the FIN bit off', (done) => { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -635,7 +635,7 @@ describe('Receiver', function() { receiver.write(Buffer.from([0x09, 0x00])); }); - it('emits an error if a control frame has the RSV1 bit on', function(done) { + it('emits an error if a control frame has the RSV1 bit on', (done) => { const perMessageDeflate = new PerMessageDeflate(); perMessageDeflate.accept([{}]); @@ -656,7 +656,7 @@ describe('Receiver', function() { receiver.write(Buffer.from([0xc9, 0x00])); }); - it('emits an error if a control frame has the FIN bit off', function(done) { + it('emits an error if a control frame has the FIN bit off', (done) => { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -672,7 +672,7 @@ describe('Receiver', function() { receiver.write(Buffer.from([0x09, 0x00])); }); - it('emits an error if a control frame has a payload bigger than 125 B', function(done) { + it('emits an error if a control frame has a payload bigger than 125 B', (done) => { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -688,7 +688,7 @@ describe('Receiver', function() { receiver.write(Buffer.from([0x89, 0x7e])); }); - it('emits an error if a data frame has a payload bigger than 2^53 - 1 B', function(done) { + it('emits an error if a data frame has a payload bigger than 2^53 - 1 B', (done) => { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -709,7 +709,7 @@ describe('Receiver', function() { ); }); - it('emits an error if a text frame contains invalid UTF-8 data', function(done) { + it('emits an error if a text frame contains invalid UTF-8 data', (done) => { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -725,7 +725,7 @@ describe('Receiver', function() { receiver.write(Buffer.from([0x81, 0x04, 0xce, 0xba, 0xe1, 0xbd])); }); - it('emits an error if a close frame has a payload of 1 B', function(done) { + it('emits an error if a close frame has a payload of 1 B', (done) => { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -741,7 +741,7 @@ describe('Receiver', function() { receiver.write(Buffer.from([0x88, 0x01, 0x00])); }); - it('emits an error if a close frame contains an invalid close code', function(done) { + it('emits an error if a close frame contains an invalid close code', (done) => { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -757,7 +757,7 @@ describe('Receiver', function() { receiver.write(Buffer.from([0x88, 0x02, 0x00, 0x00])); }); - it('emits an error if a close frame contains invalid UTF-8 data', function(done) { + it('emits an error if a close frame contains invalid UTF-8 data', (done) => { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -775,7 +775,7 @@ describe('Receiver', function() { ); }); - it('emits an error if a frame payload length is bigger than `maxPayload`', function(done) { + it('emits an error if a frame payload length is bigger than `maxPayload`', (done) => { const receiver = new Receiver(undefined, {}, 20 * 1024); const msg = crypto.randomBytes(200 * 1024); @@ -799,7 +799,7 @@ describe('Receiver', function() { receiver.write(frame); }); - it('emits an error if the message length exceeds `maxPayload`', function(done) { + it('emits an error if the message length exceeds `maxPayload`', (done) => { const perMessageDeflate = new PerMessageDeflate({}, false, 25); perMessageDeflate.accept([{}]); @@ -819,7 +819,7 @@ describe('Receiver', function() { done(); }); - perMessageDeflate.compress(buf, true, function(err, data) { + perMessageDeflate.compress(buf, true, (err, data) => { if (err) return done(err); receiver.write(Buffer.from([0xc1, data.length])); @@ -827,7 +827,7 @@ describe('Receiver', function() { }); }); - it('emits an error if the sum of fragment lengths exceeds `maxPayload`', function(done) { + it('emits an error if the sum of fragment lengths exceeds `maxPayload`', (done) => { const perMessageDeflate = new PerMessageDeflate({}, false, 25); perMessageDeflate.accept([{}]); @@ -847,13 +847,13 @@ describe('Receiver', function() { done(); }); - perMessageDeflate.compress(buf, false, function(err, fragment1) { + perMessageDeflate.compress(buf, false, (err, fragment1) => { if (err) return done(err); receiver.write(Buffer.from([0x41, fragment1.length])); receiver.write(fragment1); - perMessageDeflate.compress(buf, true, function(err, fragment2) { + perMessageDeflate.compress(buf, true, (err, fragment2) => { if (err) return done(err); receiver.write(Buffer.from([0x80, fragment2.length])); @@ -862,7 +862,7 @@ describe('Receiver', function() { }); }); - it("honors the 'nodebuffer' binary type", function(done) { + it("honors the 'nodebuffer' binary type", (done) => { const receiver = new Receiver(); const frags = [ crypto.randomBytes(7321), @@ -888,7 +888,7 @@ describe('Receiver', function() { }); }); - it("honors the 'arraybuffer' binary type", function(done) { + it("honors the 'arraybuffer' binary type", (done) => { const receiver = new Receiver(); const frags = [ crypto.randomBytes(19221), @@ -914,7 +914,7 @@ describe('Receiver', function() { }); }); - it("honors the 'fragments' binary type", function(done) { + it("honors the 'fragments' binary type", (done) => { const receiver = new Receiver(); const frags = [ crypto.randomBytes(17), diff --git a/test/sender.test.js b/test/sender.test.js index f20294291..18a857a54 100644 --- a/test/sender.test.js +++ b/test/sender.test.js @@ -18,9 +18,9 @@ class MockSocket { uncork() {} } -describe('Sender', function() { - describe('.frame', function() { - it('does not mutate the input buffer if data is `readOnly`', function() { +describe('Sender', () => { + describe('.frame', () => { + it('does not mutate the input buffer if data is `readOnly`', () => { const buf = Buffer.from([1, 2, 3, 4, 5]); Sender.frame(buf, { @@ -34,7 +34,7 @@ describe('Sender', function() { assert.ok(buf.equals(Buffer.from([1, 2, 3, 4, 5]))); }); - it('sets RSV1 bit if compressed', function() { + it('sets RSV1 bit if compressed', () => { const list = Sender.frame(Buffer.from('hi'), { readOnly: false, mask: false, @@ -47,8 +47,8 @@ describe('Sender', function() { }); }); - describe('#send', function() { - it('compresses data if compress option is enabled', function(done) { + describe('#send', () => { + it('compresses data if compress option is enabled', (done) => { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); let count = 0; const mockSocket = new MockSocket({ @@ -71,7 +71,7 @@ describe('Sender', function() { sender.send('hi', options); }); - it('does not compress data for small payloads', function(done) { + it('does not compress data for small payloads', (done) => { const perMessageDeflate = new PerMessageDeflate(); const mockSocket = new MockSocket({ write: (data) => { @@ -88,7 +88,7 @@ describe('Sender', function() { sender.send('hi', { compress: true, fin: true }); }); - it('compresses all frames in a fragmented message', function(done) { + it('compresses all frames in a fragmented message', (done) => { const chunks = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); const mockSocket = new MockSocket({ @@ -116,7 +116,7 @@ describe('Sender', function() { sender.send('12', { compress: true, fin: true }); }); - it('compresses no frames in a fragmented message', function(done) { + it('compresses no frames in a fragmented message', (done) => { const chunks = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); const mockSocket = new MockSocket({ @@ -144,7 +144,7 @@ describe('Sender', function() { sender.send('123', { compress: true, fin: true }); }); - it('compresses empty buffer as first fragment', function(done) { + it('compresses empty buffer as first fragment', (done) => { const chunks = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const mockSocket = new MockSocket({ @@ -172,7 +172,7 @@ describe('Sender', function() { sender.send('data', { compress: true, fin: true }); }); - it('compresses empty buffer as last fragment', function(done) { + it('compresses empty buffer as last fragment', (done) => { const chunks = []; const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); const mockSocket = new MockSocket({ @@ -201,8 +201,8 @@ describe('Sender', function() { }); }); - describe('#ping', function() { - it('works with multiple types of data', function(done) { + describe('#ping', () => { + it('works with multiple types of data', (done) => { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); let count = 0; const mockSocket = new MockSocket({ @@ -233,8 +233,8 @@ describe('Sender', function() { }); }); - describe('#pong', function() { - it('works with multiple types of data', function(done) { + describe('#pong', () => { + it('works with multiple types of data', (done) => { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); let count = 0; const mockSocket = new MockSocket({ @@ -265,8 +265,8 @@ describe('Sender', function() { }); }); - describe('#close', function() { - it('should consume all data before closing', function(done) { + describe('#close', () => { + it('should consume all data before closing', (done) => { const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); let count = 0; diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 06d44533b..5d152ab73 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -11,25 +11,25 @@ const fs = require('fs'); const WebSocket = require('..'); -describe('WebSocketServer', function() { - describe('#ctor', function() { - it('throws an error if no option object is passed', function() { +describe('WebSocketServer', () => { + describe('#ctor', () => { + it('throws an error if no option object is passed', () => { assert.throws(() => new WebSocket.Server()); }); - it('throws an error if no port or server is specified', function() { + it('throws an error if no port or server is specified', () => { assert.throws(() => new WebSocket.Server({})); }); - describe('options', function() { - it('exposes options passed to constructor', function(done) { + describe('options', () => { + it('exposes options passed to constructor', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { assert.strictEqual(wss.options.port, 0); wss.close(done); }); }); - it('accepts the `maxPayload` option', function(done) { + it('accepts the `maxPayload` option', (done) => { const maxPayload = 20480; const wss = new WebSocket.Server( { @@ -53,7 +53,7 @@ describe('WebSocketServer', function() { }); }); - it('emits an error if http server bind fails', function(done) { + it('emits an error if http server bind fails', (done) => { const wss1 = new WebSocket.Server({ port: 0 }, () => { const wss2 = new WebSocket.Server({ port: wss1.address().port @@ -63,7 +63,7 @@ describe('WebSocketServer', function() { }); }); - it('starts a server on a given port', function(done) { + it('starts a server on a given port', (done) => { const port = 1337; const wss = new WebSocket.Server({ port }, () => { const ws = new WebSocket(`ws://localhost:${port}`); @@ -72,14 +72,14 @@ describe('WebSocketServer', function() { wss.on('connection', () => wss.close(done)); }); - it('binds the server on any IPv6 address when available', function(done) { + it('binds the server on any IPv6 address when available', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { assert.strictEqual(wss._server.address().address, '::'); wss.close(done); }); }); - it('uses a precreated http server', function(done) { + it('uses a precreated http server', (done) => { const server = http.createServer(); server.listen(0, () => { @@ -93,7 +93,7 @@ describe('WebSocketServer', function() { }); }); - it('426s for non-Upgrade requests', function(done) { + it('426s for non-Upgrade requests', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { http.get(`http://localhost:${wss.address().port}`, (res) => { let body = ''; @@ -140,8 +140,8 @@ describe('WebSocketServer', function() { }); }); - describe('#address', function() { - it('returns the address of the server', function(done) { + describe('#address', () => { + it('returns the address of the server', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const addr = wss.address(); @@ -150,7 +150,7 @@ describe('WebSocketServer', function() { }); }); - it('throws an error when operating in "noServer" mode', function() { + it('throws an error when operating in "noServer" mode', () => { const wss = new WebSocket.Server({ noServer: true }); assert.throws(() => { @@ -158,7 +158,7 @@ describe('WebSocketServer', function() { }, /^Error: The server is operating in "noServer" mode$/); }); - it('returns `null` if called after close', function(done) { + it('returns `null` if called after close', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { wss.close(() => { assert.strictEqual(wss.address(), null); @@ -168,8 +168,8 @@ describe('WebSocketServer', function() { }); }); - describe('#close', function() { - it('does not throw when called twice', function(done) { + describe('#close', () => { + it('does not throw when called twice', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { wss.close(); wss.close(); @@ -179,7 +179,7 @@ describe('WebSocketServer', function() { }); }); - it('closes all clients', function(done) { + it('closes all clients', (done) => { let closes = 0; const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -196,7 +196,7 @@ describe('WebSocketServer', function() { }); }); - it("doesn't close a precreated server", function(done) { + it("doesn't close a precreated server", (done) => { const server = http.createServer(); const realClose = server.close; @@ -217,13 +217,13 @@ describe('WebSocketServer', function() { }); }); - it('invokes the callback in noServer mode', function(done) { + it('invokes the callback in noServer mode', (done) => { const wss = new WebSocket.Server({ noServer: true }); wss.close(done); }); - it('cleans event handlers on precreated server', function(done) { + it('cleans event handlers on precreated server', (done) => { const server = http.createServer(); const wss = new WebSocket.Server({ server }); @@ -238,7 +238,7 @@ describe('WebSocketServer', function() { }); }); - it("emits the 'close' event", function(done) { + it("emits the 'close' event", (done) => { const wss = new WebSocket.Server({ noServer: true }); wss.on('close', done); @@ -246,8 +246,8 @@ describe('WebSocketServer', function() { }); }); - describe('#clients', function() { - it('returns a list of connected clients', function(done) { + describe('#clients', () => { + it('returns a list of connected clients', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { assert.strictEqual(wss.clients.size, 0); const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -259,7 +259,7 @@ describe('WebSocketServer', function() { }); }); - it('can be disabled', function(done) { + it('can be disabled', (done) => { const wss = new WebSocket.Server( { port: 0, clientTracking: false }, () => { @@ -276,7 +276,7 @@ describe('WebSocketServer', function() { }); }); - it('is updated when client terminates the connection', function(done) { + it('is updated when client terminates the connection', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -291,7 +291,7 @@ describe('WebSocketServer', function() { }); }); - it('is updated when client closes the connection', function(done) { + it('is updated when client closes the connection', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -307,22 +307,22 @@ describe('WebSocketServer', function() { }); }); - describe('#shouldHandle', function() { - it('returns true when the path matches', function() { + describe('#shouldHandle', () => { + it('returns true when the path matches', () => { const wss = new WebSocket.Server({ noServer: true, path: '/foo' }); assert.strictEqual(wss.shouldHandle({ url: '/foo' }), true); }); - it("returns false when the path doesn't match", function() { + it("returns false when the path doesn't match", () => { const wss = new WebSocket.Server({ noServer: true, path: '/foo' }); assert.strictEqual(wss.shouldHandle({ url: '/bar' }), false); }); }); - describe('#handleUpgrade', function() { - it('can be used for a pre-existing server', function(done) { + describe('#handleUpgrade', () => { + it('can be used for a pre-existing server', (done) => { const server = http.createServer(); server.listen(0, () => { @@ -344,7 +344,7 @@ describe('WebSocketServer', function() { }); }); - it("closes the connection when path doesn't match", function(done) { + it("closes the connection when path doesn't match", (done) => { const wss = new WebSocket.Server({ port: 0, path: '/ws' }, () => { const req = http.get({ port: wss.address().port, @@ -361,7 +361,7 @@ describe('WebSocketServer', function() { }); }); - it('closes the connection when protocol version is Hixie-76', function(done) { + it('closes the connection when protocol version is Hixie-76', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ port: wss.address().port, @@ -382,8 +382,8 @@ describe('WebSocketServer', function() { }); }); - describe('Connection establishing', function() { - it('fails if the Sec-WebSocket-Key header is invalid (1/2)', function(done) { + describe('Connection establishing', () => { + it('fails if the Sec-WebSocket-Key header is invalid (1/2)', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ port: wss.address().port, @@ -404,7 +404,7 @@ describe('WebSocketServer', function() { }); }); - it('fails if the Sec-WebSocket-Key header is invalid (2/2)', function(done) { + it('fails if the Sec-WebSocket-Key header is invalid (2/2)', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ port: wss.address().port, @@ -426,7 +426,7 @@ describe('WebSocketServer', function() { }); }); - it('fails is the Sec-WebSocket-Version header is invalid (1/2)', function(done) { + it('fails is the Sec-WebSocket-Version header is invalid (1/2)', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ port: wss.address().port, @@ -448,7 +448,7 @@ describe('WebSocketServer', function() { }); }); - it('fails is the Sec-WebSocket-Version header is invalid (2/2)', function(done) { + it('fails is the Sec-WebSocket-Version header is invalid (2/2)', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({ port: wss.address().port, @@ -471,7 +471,7 @@ describe('WebSocketServer', function() { }); }); - it('fails is the Sec-WebSocket-Extensions header is invalid', function(done) { + it('fails is the Sec-WebSocket-Extensions header is invalid', (done) => { const wss = new WebSocket.Server( { perMessageDeflate: true, @@ -502,8 +502,8 @@ describe('WebSocketServer', function() { }); }); - describe('`verifyClient`', function() { - it('can reject client synchronously', function(done) { + describe('`verifyClient`', () => { + it('can reject client synchronously', (done) => { const wss = new WebSocket.Server( { verifyClient: () => false, @@ -532,7 +532,7 @@ describe('WebSocketServer', function() { }); }); - it('can accept client synchronously', function(done) { + it('can accept client synchronously', (done) => { const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), key: fs.readFileSync('test/fixtures/key.pem') @@ -561,7 +561,7 @@ describe('WebSocketServer', function() { }); }); - it('can accept client asynchronously', function(done) { + it('can accept client asynchronously', (done) => { const wss = new WebSocket.Server( { verifyClient: (o, cb) => process.nextTick(cb, true), @@ -575,7 +575,7 @@ describe('WebSocketServer', function() { wss.on('connection', () => wss.close(done)); }); - it('can reject client asynchronously', function(done) { + it('can reject client asynchronously', (done) => { const wss = new WebSocket.Server( { verifyClient: (info, cb) => process.nextTick(cb, false), @@ -604,7 +604,7 @@ describe('WebSocketServer', function() { }); }); - it('can reject client asynchronously w/ status code', function(done) { + it('can reject client asynchronously w/ status code', (done) => { const wss = new WebSocket.Server( { verifyClient: (info, cb) => process.nextTick(cb, false, 404), @@ -633,7 +633,7 @@ describe('WebSocketServer', function() { }); }); - it('can reject client asynchronously w/ custom headers', function(done) { + it('can reject client asynchronously w/ custom headers', (done) => { const wss = new WebSocket.Server( { verifyClient: (info, cb) => { @@ -666,7 +666,7 @@ describe('WebSocketServer', function() { }); }); - it("doesn't emit the 'connection' event if socket is closed prematurely", function(done) { + it("doesn't emit the 'connection' event if socket is closed prematurely", (done) => { const server = http.createServer(); server.listen(0, () => { @@ -715,7 +715,7 @@ describe('WebSocketServer', function() { }); }); - it('handles data passed along with the upgrade request', function(done) { + it('handles data passed along with the upgrade request', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.request({ port: wss.address().port, @@ -739,8 +739,8 @@ describe('WebSocketServer', function() { }); }); - describe('`handleProtocols`', function() { - it('allows to select a subprotocol', function(done) { + describe('`handleProtocols`', () => { + it('allows to select a subprotocol', (done) => { const handleProtocols = (protocols, request) => { assert.ok(request instanceof http.IncomingMessage); assert.strictEqual(request.url, '/'); @@ -760,7 +760,7 @@ describe('WebSocketServer', function() { }); }); - it("emits the 'headers' event", function(done) { + it("emits the 'headers' event", (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -779,8 +779,8 @@ describe('WebSocketServer', function() { }); }); - describe('permessage-deflate', function() { - it('is disabled by default', function(done) { + describe('permessage-deflate', () => { + it('is disabled by default', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); }); @@ -795,7 +795,7 @@ describe('WebSocketServer', function() { }); }); - it('uses configuration options', function(done) { + it('uses configuration options', (done) => { const wss = new WebSocket.Server( { perMessageDeflate: { clientMaxWindowBits: 8 }, diff --git a/test/websocket.integration.js b/test/websocket.integration.js index cc78af220..5ff87a640 100644 --- a/test/websocket.integration.js +++ b/test/websocket.integration.js @@ -4,8 +4,8 @@ const assert = require('assert'); const WebSocket = require('..'); -describe('WebSocket', function() { - it('communicates successfully with echo service (ws)', function(done) { +describe('WebSocket', () => { + it('communicates successfully with echo service (ws)', (done) => { const ws = new WebSocket('ws://echo.websocket.org/', { origin: 'http://www.websocket.org', protocolVersion: 13 @@ -26,7 +26,7 @@ describe('WebSocket', function() { }); }); - it('communicates successfully with echo service (wss)', function(done) { + it('communicates successfully with echo service (wss)', (done) => { const ws = new WebSocket('wss://echo.websocket.org/', { origin: 'https://www.websocket.org', protocolVersion: 13 diff --git a/test/websocket.test.js b/test/websocket.test.js index 91aa9ebbf..b2a2777d2 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -16,16 +16,16 @@ class CustomAgent extends http.Agent { addRequest() {} } -describe('WebSocket', function() { - describe('#ctor', function() { - it('throws an error when using an invalid url', function() { +describe('WebSocket', () => { + describe('#ctor', () => { + it('throws an error when using an invalid url', () => { assert.throws( () => new WebSocket('ws+unix:'), /^Error: Invalid URL: ws\+unix:$/ ); }); - it('accepts `url.Url` objects as url', function(done) { + it('accepts `url.Url` objects as url', (done) => { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -50,8 +50,8 @@ describe('WebSocket', function() { const ws = new WebSocket(new url.URL('ws://[::1]'), { agent }); }); - describe('options', function() { - it('accepts the `options` object as 3rd argument', function() { + describe('options', () => { + it('accepts the `options` object as 3rd argument', () => { const agent = new CustomAgent(); let count = 0; let ws; @@ -65,7 +65,7 @@ describe('WebSocket', function() { assert.strictEqual(count, 3); }); - it('accepts the `maxPayload` option', function(done) { + it('accepts the `maxPayload` option', (done) => { const maxPayload = 20480; const wss = new WebSocket.Server( { @@ -90,7 +90,7 @@ describe('WebSocket', function() { ); }); - it('throws an error when using an invalid `protocolVersion`', function() { + it('throws an error when using an invalid `protocolVersion`', () => { const options = { agent: new CustomAgent(), protocolVersion: 1000 }; assert.throws( @@ -101,7 +101,7 @@ describe('WebSocket', function() { }); }); - describe('Constants', function() { + describe('Constants', () => { const readyStates = { CONNECTING: 0, OPEN: 1, @@ -110,8 +110,8 @@ describe('WebSocket', function() { }; Object.keys(readyStates).forEach((state) => { - describe(`\`${state}\``, function() { - it('is enumerable property of class', function() { + describe(`\`${state}\``, () => { + it('is enumerable property of class', () => { const propertyDescripter = Object.getOwnPropertyDescriptor( WebSocket, state @@ -121,7 +121,7 @@ describe('WebSocket', function() { assert.strictEqual(propertyDescripter.enumerable, true); }); - it('is property of instance', function() { + it('is property of instance', () => { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -132,9 +132,9 @@ describe('WebSocket', function() { }); }); - describe('Attributes', function() { - describe('`binaryType`', function() { - it("defaults to 'nodebuffer'", function() { + describe('Attributes', () => { + describe('`binaryType`', () => { + it("defaults to 'nodebuffer'", () => { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -142,7 +142,7 @@ describe('WebSocket', function() { assert.strictEqual(ws.binaryType, 'nodebuffer'); }); - it("can be changed to 'arraybuffer' or 'fragments'", function() { + it("can be changed to 'arraybuffer' or 'fragments'", () => { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -164,8 +164,8 @@ describe('WebSocket', function() { }); }); - describe('`bufferedAmount`', function() { - it('defaults to zero', function() { + describe('`bufferedAmount`', () => { + it('defaults to zero', () => { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -173,7 +173,7 @@ describe('WebSocket', function() { assert.strictEqual(ws.bufferedAmount, 0); }); - it('defaults to zero upon "open"', function(done) { + it('defaults to zero upon "open"', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -184,7 +184,7 @@ describe('WebSocket', function() { }); }); - it('takes into account the data in the sender queue', function(done) { + it('takes into account the data in the sender queue', (done) => { const wss = new WebSocket.Server( { perMessageDeflate: true, @@ -209,7 +209,7 @@ describe('WebSocket', function() { ); }); - it('takes into account the data in the socket queue', function(done) { + it('takes into account the data in the socket queue', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); }); @@ -230,8 +230,8 @@ describe('WebSocket', function() { }); }); - describe('`extensions`', function() { - it('exposes the negotiated extensions names (1/2)', function(done) { + describe('`extensions`', () => { + it('exposes the negotiated extensions names (1/2)', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -249,7 +249,7 @@ describe('WebSocket', function() { }); }); - it('exposes the negotiated extensions names (2/2)', function(done) { + it('exposes the negotiated extensions names (2/2)', (done) => { const wss = new WebSocket.Server( { perMessageDeflate: true, @@ -274,8 +274,8 @@ describe('WebSocket', function() { }); }); - describe('`protocol`', function() { - it('exposes the subprotocol selected by the server', function(done) { + describe('`protocol`', () => { + it('exposes the subprotocol selected by the server', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss.address().port; const ws = new WebSocket(`ws://localhost:${port}`, 'foo'); @@ -295,8 +295,8 @@ describe('WebSocket', function() { }); }); - describe('`readyState`', function() { - it('defaults to `CONNECTING`', function() { + describe('`readyState`', () => { + it('defaults to `CONNECTING`', () => { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -304,7 +304,7 @@ describe('WebSocket', function() { assert.strictEqual(ws.readyState, WebSocket.CONNECTING); }); - it('is set to `OPEN` once connection is established', function(done) { + it('is set to `OPEN` once connection is established', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -317,7 +317,7 @@ describe('WebSocket', function() { }); }); - it('is set to `CLOSED` once connection is closed', function(done) { + it('is set to `CLOSED` once connection is closed', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -330,7 +330,7 @@ describe('WebSocket', function() { }); }); - it('is set to `CLOSED` once connection is terminated', function(done) { + it('is set to `CLOSED` once connection is terminated', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -344,8 +344,8 @@ describe('WebSocket', function() { }); }); - describe('`url`', function() { - it('exposes the server url', function() { + describe('`url`', () => { + it('exposes the server url', () => { const url = 'ws://localhost'; const ws = new WebSocket(url, { agent: new CustomAgent() }); @@ -354,8 +354,8 @@ describe('WebSocket', function() { }); }); - describe('Events', function() { - it("emits an 'error' event if an error occurs", function(done) { + describe('Events', () => { + it("emits an 'error' event if an error occurs", (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -379,7 +379,7 @@ describe('WebSocket', function() { }); }); - it('does not re-emit `net.Socket` errors', function(done) { + it('does not re-emit `net.Socket` errors', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -401,7 +401,7 @@ describe('WebSocket', function() { }); }); - it("emits an 'upgrade' event", function(done) { + it("emits an 'upgrade' event", (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('upgrade', (res) => { @@ -411,7 +411,7 @@ describe('WebSocket', function() { }); }); - it("emits a 'ping' event", function(done) { + it("emits a 'ping' event", (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('ping', () => wss.close(done)); @@ -420,7 +420,7 @@ describe('WebSocket', function() { wss.on('connection', (ws) => ws.ping()); }); - it("emits a 'pong' event", function(done) { + it("emits a 'pong' event", (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('pong', () => wss.close(done)); @@ -430,13 +430,13 @@ describe('WebSocket', function() { }); }); - describe('Connection establishing', function() { + describe('Connection establishing', () => { const server = http.createServer(); beforeEach((done) => server.listen(0, done)); afterEach((done) => server.close(done)); - it('fails if the Sec-WebSocket-Accept header is invalid', function(done) { + it('fails if the Sec-WebSocket-Accept header is invalid', (done) => { server.once('upgrade', (req, socket) => { socket.on('end', socket.end); socket.write( @@ -457,7 +457,7 @@ describe('WebSocket', function() { }); }); - it('close event is raised when server closes connection', function(done) { + it('close event is raised when server closes connection', (done) => { server.once('upgrade', (req, socket) => { const key = crypto .createHash('sha1') @@ -482,7 +482,7 @@ describe('WebSocket', function() { }); }); - it('error is emitted if server aborts connection', function(done) { + it('error is emitted if server aborts connection', (done) => { server.once('upgrade', (req, socket) => { socket.end( `HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` + @@ -503,7 +503,7 @@ describe('WebSocket', function() { }); }); - it('unexpected response can be read when sent by server', function(done) { + it('unexpected response can be read when sent by server', (done) => { server.once('upgrade', (req, socket) => { socket.end( `HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` + @@ -535,7 +535,7 @@ describe('WebSocket', function() { }); }); - it('request can be aborted when unexpected response is sent by server', function(done) { + it('request can be aborted when unexpected response is sent by server', (done) => { server.once('upgrade', (req, socket) => { socket.end( `HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` + @@ -559,7 +559,7 @@ describe('WebSocket', function() { }); }); - it('fails if the opening handshake timeout expires', function(done) { + it('fails if the opening handshake timeout expires', (done) => { server.once('upgrade', (req, socket) => socket.on('end', socket.end)); const port = server.address().port; @@ -575,7 +575,7 @@ describe('WebSocket', function() { }); }); - it('fails if the Sec-WebSocket-Extensions response header is invalid', function(done) { + it('fails if the Sec-WebSocket-Extensions response header is invalid', (done) => { server.once('upgrade', (req, socket) => { const key = crypto .createHash('sha1') @@ -605,7 +605,7 @@ describe('WebSocket', function() { }); }); - it('fails if server sends a subprotocol when none was requested', function(done) { + it('fails if server sends a subprotocol when none was requested', (done) => { const wss = new WebSocket.Server({ server }); wss.on('headers', (headers) => { @@ -625,7 +625,7 @@ describe('WebSocket', function() { }); }); - it('fails if server sends an invalid subprotocol', function(done) { + it('fails if server sends an invalid subprotocol', (done) => { const wss = new WebSocket.Server({ handleProtocols: () => 'baz', server @@ -644,7 +644,7 @@ describe('WebSocket', function() { }); }); - it('fails if server sends no subprotocol', function(done) { + it('fails if server sends no subprotocol', (done) => { const wss = new WebSocket.Server({ handleProtocols: () => {}, server @@ -663,7 +663,7 @@ describe('WebSocket', function() { }); }); - it('does not follow redirects by default', function(done) { + it('does not follow redirects by default', (done) => { server.once('upgrade', (req, socket) => { socket.end( 'HTTP/1.1 301 Moved Permanently\r\n' + @@ -683,7 +683,7 @@ describe('WebSocket', function() { }); }); - it('honors the `followRedirects` option', function(done) { + it('honors the `followRedirects` option', (done) => { const wss = new WebSocket.Server({ noServer: true, path: '/foo' }); server.once('upgrade', (req, socket) => { @@ -706,7 +706,7 @@ describe('WebSocket', function() { }); }); - it('honors the `maxRedirects` option', function(done) { + it('honors the `maxRedirects` option', (done) => { const onUpgrade = (req, socket) => { socket.end('HTTP/1.1 302 Found\r\nLocation: /\r\n\r\n'); }; @@ -730,8 +730,8 @@ describe('WebSocket', function() { }); }); - describe('Connection with query string', function() { - it('connects when pathname is not null', function(done) { + describe('Connection with query string', () => { + it('connects when pathname is not null', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss.address().port; const ws = new WebSocket(`ws://localhost:${port}/?token=qwerty`); @@ -740,7 +740,7 @@ describe('WebSocket', function() { }); }); - it('connects when pathname is null', function(done) { + it('connects when pathname is null', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const port = wss.address().port; const ws = new WebSocket(`ws://localhost:${port}?token=qwerty`); @@ -750,8 +750,8 @@ describe('WebSocket', function() { }); }); - describe('#ping', function() { - it('throws an error if `readyState` is not `OPEN`', function(done) { + describe('#ping', () => { + it('throws an error if `readyState` is not `OPEN`', (done) => { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -771,7 +771,7 @@ describe('WebSocket', function() { }); }); - it('can send a ping with no data', function(done) { + it('can send a ping with no data', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -790,7 +790,7 @@ describe('WebSocket', function() { }); }); - it('can send a ping with data', function(done) { + it('can send a ping with data', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -808,7 +808,7 @@ describe('WebSocket', function() { }); }); - it('can send numbers as ping payload', function(done) { + it('can send numbers as ping payload', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -824,7 +824,7 @@ describe('WebSocket', function() { }); }); - describe('#pong', function() { + describe('#pong', () => { it('throws an error if `readyState` is not `OPEN`', (done) => { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() @@ -845,7 +845,7 @@ describe('WebSocket', function() { }); }); - it('can send a pong with no data', function(done) { + it('can send a pong with no data', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -864,7 +864,7 @@ describe('WebSocket', function() { }); }); - it('can send a pong with data', function(done) { + it('can send a pong with data', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -882,7 +882,7 @@ describe('WebSocket', function() { }); }); - it('can send numbers as pong payload', function(done) { + it('can send numbers as pong payload', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -898,8 +898,8 @@ describe('WebSocket', function() { }); }); - describe('#send', function() { - it('can send a big binary message', function(done) { + describe('#send', () => { + it('can send a big binary message', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const array = new Float32Array(5 * 1024 * 1024); @@ -921,7 +921,7 @@ describe('WebSocket', function() { }); }); - it('can send text data', function(done) { + it('can send text data', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -937,7 +937,7 @@ describe('WebSocket', function() { }); }); - it('does not override the `fin` option', function(done) { + it('does not override the `fin` option', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -955,7 +955,7 @@ describe('WebSocket', function() { }); }); - it('sends numbers as strings', function(done) { + it('sends numbers as strings', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -970,7 +970,7 @@ describe('WebSocket', function() { }); }); - it('can send binary data as an array', function(done) { + it('can send binary data as an array', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const array = new Float32Array(6); @@ -998,7 +998,7 @@ describe('WebSocket', function() { }); }); - it('can send binary data as a buffer', function(done) { + it('can send binary data as a buffer', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const buf = Buffer.from('foobar'); const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1015,7 +1015,7 @@ describe('WebSocket', function() { }); }); - it('can send an `ArrayBuffer`', function(done) { + it('can send an `ArrayBuffer`', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const array = new Float32Array(5); @@ -1037,7 +1037,7 @@ describe('WebSocket', function() { }); }); - it('can send a `Buffer`', function(done) { + it('can send a `Buffer`', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const buf = Buffer.from('foobar'); const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1055,7 +1055,7 @@ describe('WebSocket', function() { }); }); - it('throws an error if `readyState` is not `OPEN`', function() { + it('throws an error if `readyState` is not `OPEN`', () => { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1066,7 +1066,7 @@ describe('WebSocket', function() { ); }); - it('passes errors to the callback, if present', function() { + it('passes errors to the callback, if present', () => { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1080,7 +1080,7 @@ describe('WebSocket', function() { }); }); - it('calls the optional callback when data is written out', function(done) { + it('calls the optional callback when data is written out', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1093,7 +1093,7 @@ describe('WebSocket', function() { }); }); - it('works when the `data` argument is falsy', function(done) { + it('works when the `data` argument is falsy', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1108,7 +1108,7 @@ describe('WebSocket', function() { }); }); - it('can send text data with `mask` option set to `false`', function(done) { + it('can send text data with `mask` option set to `false`', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1123,7 +1123,7 @@ describe('WebSocket', function() { }); }); - it('can send binary data with `mask` option set to `false`', function(done) { + it('can send binary data with `mask` option set to `false`', (done) => { const array = new Float32Array(5); for (let i = 0; i < array.length; ++i) { @@ -1145,8 +1145,8 @@ describe('WebSocket', function() { }); }); - describe('#close', function() { - it('closes the connection if called while connecting (1/2)', function(done) { + describe('#close', () => { + it('closes the connection if called while connecting (1/2)', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1163,7 +1163,7 @@ describe('WebSocket', function() { }); }); - it('closes the connection if called while connecting (2/2)', function(done) { + it('closes the connection if called while connecting (2/2)', (done) => { const wss = new WebSocket.Server( { verifyClient: (info, cb) => setTimeout(cb, 300, true), @@ -1186,7 +1186,7 @@ describe('WebSocket', function() { ); }); - it('can be called from an error listener while connecting', function(done) { + it('can be called from an error listener while connecting', (done) => { const ws = new WebSocket('ws://localhost:1337'); ws.on('open', () => done(new Error("Unexpected 'open' event"))); @@ -1198,7 +1198,7 @@ describe('WebSocket', function() { }); }); - it("can be called from a listener of the 'upgrade' event", function(done) { + it("can be called from a listener of the 'upgrade' event", (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1215,7 +1215,7 @@ describe('WebSocket', function() { }); }); - it('throws an error if the first argument is invalid (1/2)', function(done) { + it('throws an error if the first argument is invalid (1/2)', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1230,7 +1230,7 @@ describe('WebSocket', function() { }); }); - it('throws an error if the first argument is invalid (2/2)', function(done) { + it('throws an error if the first argument is invalid (2/2)', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1245,7 +1245,7 @@ describe('WebSocket', function() { }); }); - it('sends the close status code only when necessary', function(done) { + it('sends the close status code only when necessary', (done) => { let sent; const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1272,7 +1272,7 @@ describe('WebSocket', function() { }); }); - it('works when close reason is not specified', function(done) { + it('works when close reason is not specified', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1288,7 +1288,7 @@ describe('WebSocket', function() { }); }); - it('works when close reason is specified', function(done) { + it('works when close reason is specified', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1304,7 +1304,7 @@ describe('WebSocket', function() { }); }); - it('ends connection to the server', function(done) { + it('ends connection to the server', (done) => { const wss = new WebSocket.Server( { clientTracking: false, @@ -1325,7 +1325,7 @@ describe('WebSocket', function() { ); }); - it('permits all buffered data to be delivered', function(done) { + it('permits all buffered data to be delivered', (done) => { const wss = new WebSocket.Server( { perMessageDeflate: { threshold: 0 }, @@ -1355,7 +1355,7 @@ describe('WebSocket', function() { }); }); - it('allows close code 1013', function(done) { + it('allows close code 1013', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1368,7 +1368,7 @@ describe('WebSocket', function() { wss.on('connection', (ws) => ws.close(1013)); }); - it('does nothing if `readyState` is `CLOSED`', function(done) { + it('does nothing if `readyState` is `CLOSED`', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1384,8 +1384,8 @@ describe('WebSocket', function() { }); }); - describe('#terminate', function() { - it('closes the connection if called while connecting (1/2)', function(done) { + describe('#terminate', () => { + it('closes the connection if called while connecting (1/2)', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1402,7 +1402,7 @@ describe('WebSocket', function() { }); }); - it('closes the connection if called while connecting (2/2)', function(done) { + it('closes the connection if called while connecting (2/2)', (done) => { const wss = new WebSocket.Server( { verifyClient: (info, cb) => setTimeout(cb, 300, true), @@ -1425,7 +1425,7 @@ describe('WebSocket', function() { ); }); - it('can be called from an error listener while connecting', function(done) { + it('can be called from an error listener while connecting', (done) => { const ws = new WebSocket('ws://localhost:1337'); ws.on('open', () => done(new Error("Unexpected 'open' event"))); @@ -1437,7 +1437,7 @@ describe('WebSocket', function() { }); }); - it("can be called from a listener of the 'upgrade' event", function(done) { + it("can be called from a listener of the 'upgrade' event", (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1454,7 +1454,7 @@ describe('WebSocket', function() { }); }); - it('does nothing if `readyState` is `CLOSED`', function(done) { + it('does nothing if `readyState` is `CLOSED`', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1470,8 +1470,8 @@ describe('WebSocket', function() { }); }); - describe('WHATWG API emulation', function() { - it('supports the `on{close,error,message,open}` attributes', function() { + describe('WHATWG API emulation', () => { + it('supports the `on{close,error,message,open}` attributes', () => { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1491,7 +1491,7 @@ describe('WebSocket', function() { assert.strictEqual(ws.onopen, listener); }); - it('works like the `EventEmitter` interface', function(done) { + it('works like the `EventEmitter` interface', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1514,7 +1514,7 @@ describe('WebSocket', function() { }); }); - it("doesn't return listeners added with `on`", function() { + it("doesn't return listeners added with `on`", () => { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1524,7 +1524,7 @@ describe('WebSocket', function() { assert.strictEqual(ws.onopen, undefined); }); - it("doesn't remove listeners added with `on`", function() { + it("doesn't remove listeners added with `on`", () => { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1546,7 +1546,7 @@ describe('WebSocket', function() { assert.strictEqual(listeners[1]._listener, listener); }); - it('adds listeners for custom events with `addEventListener`', function() { + it('adds listeners for custom events with `addEventListener`', () => { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1560,7 +1560,7 @@ describe('WebSocket', function() { assert.strictEqual(ws.listeners('bar').length, 0); }); - it('supports the `removeEventListener` method', function() { + it('supports the `removeEventListener` method', () => { const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); @@ -1585,7 +1585,7 @@ describe('WebSocket', function() { assert.strictEqual(ws.listenerCount('foo'), 0); }); - it('wraps text data in a `MessageEvent`', function(done) { + it('wraps text data in a `MessageEvent`', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1601,7 +1601,7 @@ describe('WebSocket', function() { }); }); - it('receives a `CloseEvent` when server closes (1000)', function(done) { + it('receives a `CloseEvent` when server closes (1000)', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1616,7 +1616,7 @@ describe('WebSocket', function() { wss.on('connection', (ws) => ws.close(1000)); }); - it('receives a `CloseEvent` when server closes (4000)', function(done) { + it('receives a `CloseEvent` when server closes (4000)', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1631,7 +1631,7 @@ describe('WebSocket', function() { wss.on('connection', (ws) => ws.close(4000, 'some daft reason')); }); - it('sets `target` and `type` on events', function(done) { + it('sets `target` and `type` on events', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const err = new Error('forced'); const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1663,7 +1663,7 @@ describe('WebSocket', function() { wss.on('connection', (client) => client.send('hi')); }); - it('passes binary data as a Node.js `Buffer` by default', function(done) { + it('passes binary data as a Node.js `Buffer` by default', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1676,7 +1676,7 @@ describe('WebSocket', function() { wss.on('connection', (ws) => ws.send(new Uint8Array(4096))); }); - it('ignores `binaryType` for text messages', function(done) { + it('ignores `binaryType` for text messages', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1691,7 +1691,7 @@ describe('WebSocket', function() { wss.on('connection', (ws) => ws.send('foo')); }); - it('allows to update `binaryType` on the fly', function(done) { + it('allows to update `binaryType` on the fly', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1730,8 +1730,8 @@ describe('WebSocket', function() { }); }); - describe('SSL', function() { - it('connects to secure websocket server', function(done) { + describe('SSL', () => { + it('connects to secure websocket server', (done) => { const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), key: fs.readFileSync('test/fixtures/key.pem') @@ -1750,7 +1750,7 @@ describe('WebSocket', function() { }); }); - it('connects to secure websocket server with client side certificate', function(done) { + it('connects to secure websocket server with client side certificate', (done) => { const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), ca: [fs.readFileSync('test/fixtures/ca1-cert.pem')], @@ -1782,7 +1782,7 @@ describe('WebSocket', function() { }); }); - it('cannot connect to secure websocket server via ws://', function(done) { + it('cannot connect to secure websocket server via ws://', (done) => { const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), key: fs.readFileSync('test/fixtures/key.pem') @@ -1801,7 +1801,7 @@ describe('WebSocket', function() { }); }); - it('can send and receive text data', function(done) { + it('can send and receive text data', (done) => { const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), key: fs.readFileSync('test/fixtures/key.pem') @@ -1825,9 +1825,7 @@ describe('WebSocket', function() { }); }); - it('can send a big binary message', function(done) { - this.timeout(4000); - + it('can send a big binary message', (done) => { const buf = crypto.randomBytes(5 * 1024 * 1024); const server = https.createServer({ cert: fs.readFileSync('test/fixtures/certificate.pem'), @@ -1852,11 +1850,11 @@ describe('WebSocket', function() { wss.close(); }); }); - }); + }).timeout(4000); }); - describe('Request headers', function() { - it('adds the authorization header if the url has userinfo (1/2)', function(done) { + describe('Request headers', () => { + it('adds the authorization header if the url has userinfo (1/2)', (done) => { const agent = new CustomAgent(); const auth = 'test:testpass'; @@ -1890,7 +1888,7 @@ describe('WebSocket', function() { }); }); - it('adds custom headers', function(done) { + it('adds custom headers', (done) => { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -1904,7 +1902,7 @@ describe('WebSocket', function() { }); }); - it('excludes default ports from host header', function() { + it('excludes default ports from host header', () => { const options = { lookup() {} }; const variants = [ ['wss://localhost:8443', 'localhost:8443'], @@ -1919,7 +1917,7 @@ describe('WebSocket', function() { } }); - it("doesn't add the origin header by default", function(done) { + it("doesn't add the origin header by default", (done) => { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -1930,7 +1928,7 @@ describe('WebSocket', function() { const ws = new WebSocket('ws://localhost', { agent }); }); - it('honors the `origin` option (1/2)', function(done) { + it('honors the `origin` option (1/2)', (done) => { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -1944,7 +1942,7 @@ describe('WebSocket', function() { }); }); - it('honors the `origin` option (2/2)', function(done) { + it('honors the `origin` option (2/2)', (done) => { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -1963,7 +1961,7 @@ describe('WebSocket', function() { }); }); - describe('permessage-deflate', function() { + describe('permessage-deflate', () => { it('is enabled by default', (done) => { const agent = new CustomAgent(); @@ -1978,7 +1976,7 @@ describe('WebSocket', function() { const ws = new WebSocket('ws://localhost', { agent }); }); - it('can be disabled', function(done) { + it('can be disabled', (done) => { const agent = new CustomAgent(); agent.addRequest = (req) => { @@ -1992,7 +1990,7 @@ describe('WebSocket', function() { }); }); - it('can send extension parameters', function(done) { + it('can send extension parameters', (done) => { const agent = new CustomAgent(); const value = @@ -2016,7 +2014,7 @@ describe('WebSocket', function() { }); }); - it('can send and receive text data', function(done) { + it('can send and receive text data', (done) => { const wss = new WebSocket.Server( { perMessageDeflate: { threshold: 0 }, @@ -2040,7 +2038,7 @@ describe('WebSocket', function() { }); }); - it('can send and receive a `TypedArray`', function(done) { + it('can send and receive a `TypedArray`', (done) => { const array = new Float32Array(5); for (let i = 0; i < array.length; i++) { @@ -2070,7 +2068,7 @@ describe('WebSocket', function() { }); }); - it('can send and receive an `ArrayBuffer`', function(done) { + it('can send and receive an `ArrayBuffer`', (done) => { const array = new Float32Array(5); for (let i = 0; i < array.length; i++) { @@ -2100,7 +2098,7 @@ describe('WebSocket', function() { }); }); - it('consumes all received data when connection is closed abnormally', function(done) { + it('consumes all received data when connection is closed abnormally', (done) => { const wss = new WebSocket.Server( { perMessageDeflate: { threshold: 0 }, @@ -2127,8 +2125,8 @@ describe('WebSocket', function() { }); }); - describe('#send', function() { - it('ignores the `compress` option if the extension is disabled', function(done) { + describe('#send', () => { + it('ignores the `compress` option if the extension is disabled', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { perMessageDeflate: false @@ -2147,8 +2145,8 @@ describe('WebSocket', function() { }); }); - describe('#terminate', function() { - it('can be used while data is being compressed', function(done) { + describe('#terminate', () => { + it('can be used while data is being compressed', (done) => { const wss = new WebSocket.Server( { perMessageDeflate: { threshold: 0 }, @@ -2175,7 +2173,7 @@ describe('WebSocket', function() { }); }); - it('can be used while data is being decompressed', function(done) { + it('can be used while data is being decompressed', (done) => { const wss = new WebSocket.Server( { perMessageDeflate: true, From 7f5025ddc43be633395ebe7741777424bb45a148 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 19 Mar 2019 18:19:47 +0100 Subject: [PATCH 618/669] [test] Fix flaky test --- test/websocket.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/websocket.test.js b/test/websocket.test.js index b2a2777d2..a1fd98b6f 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -380,13 +380,14 @@ describe('WebSocket', () => { }); it('does not re-emit `net.Socket` errors', (done) => { + const codes = ['EPIPE', 'ECONNABORTED', 'ECANCELED', 'ECONNRESET']; const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); ws.on('open', () => { ws._socket.on('error', (err) => { assert.ok(err instanceof Error); - assert.ok(err.message.startsWith('write E')); + assert.ok(codes.includes(err.code), `Unexpected code: ${err.code}`); ws.on('close', (code, message) => { assert.strictEqual(message, ''); assert.strictEqual(code, 1006); From 9a89e5d5164415021db86da665d3b691321656c7 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 20 Mar 2019 08:10:06 +0100 Subject: [PATCH 619/669] [ci] Cache dependencies --- .travis.yml | 1 + appveyor.yml | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1cf7887f6..639d7e4d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,5 +8,6 @@ os: - linux - osx - windows +cache: npm after_success: - nyc report --reporter=text-lcov | coveralls diff --git a/appveyor.yml b/appveyor.yml index a316bb770..d45c90e27 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,10 +4,12 @@ environment: - nodejs_version: '10' - nodejs_version: '8' - nodejs_version: '6' -platform: - - x86 matrix: fast_finish: true +platform: + - x86 +cache: + - node_modules install: - ps: Install-Product node $env:nodejs_version $env:platform - npm install From 3a5a20aebfd5b9b717f6470d3e5bbcf72183f4b3 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 20 Mar 2019 08:53:12 +0100 Subject: [PATCH 620/669] Revert "[ci] Cache dependencies" This reverts commit 9a89e5d5164415021db86da665d3b691321656c7. --- .travis.yml | 1 - appveyor.yml | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 639d7e4d5..1cf7887f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,5 @@ os: - linux - osx - windows -cache: npm after_success: - nyc report --reporter=text-lcov | coveralls diff --git a/appveyor.yml b/appveyor.yml index d45c90e27..a316bb770 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,12 +4,10 @@ environment: - nodejs_version: '10' - nodejs_version: '8' - nodejs_version: '6' -matrix: - fast_finish: true platform: - x86 -cache: - - node_modules +matrix: + fast_finish: true install: - ps: Install-Product node $env:nodejs_version $env:platform - npm install From bcab373b2b05342029db878872f95cc8f870350f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 20 Mar 2019 12:37:14 +0100 Subject: [PATCH 621/669] [test] Increase code coverage --- test/receiver.test.js | 29 ++++++++++++++++++++++++++++- test/websocket.test.js | 6 ++---- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/test/receiver.test.js b/test/receiver.test.js index 76ad6a927..a2c9ce41d 100644 --- a/test/receiver.test.js +++ b/test/receiver.test.js @@ -709,7 +709,7 @@ describe('Receiver', () => { ); }); - it('emits an error if a text frame contains invalid UTF-8 data', (done) => { + it('emits an error if a text frame contains invalid UTF-8 data (1/2)', (done) => { const receiver = new Receiver(); receiver.on('error', (err) => { @@ -725,6 +725,33 @@ describe('Receiver', () => { receiver.write(Buffer.from([0x81, 0x04, 0xce, 0xba, 0xe1, 0xbd])); }); + it('emits an error if a text frame contains invalid UTF-8 data (2/2)', (done) => { + const perMessageDeflate = new PerMessageDeflate(); + perMessageDeflate.accept([{}]); + + const receiver = new Receiver(undefined, { + 'permessage-deflate': perMessageDeflate + }); + const buf = Buffer.from([0xce, 0xba, 0xe1, 0xbd]); + + receiver.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid UTF-8 sequence' + ); + assert.strictEqual(err[kStatusCode], 1007); + done(); + }); + + perMessageDeflate.compress(buf, true, (err, data) => { + if (err) return done(err); + + receiver.write(Buffer.from([0xc1, data.length])); + receiver.write(data); + }); + }); + it('emits an error if a close frame has a payload of 1 B', (done) => { const receiver = new Receiver(); diff --git a/test/websocket.test.js b/test/websocket.test.js index a1fd98b6f..8e63d0757 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -1870,9 +1870,7 @@ describe('WebSocket', () => { const ws = new WebSocket(`ws://${auth}@localhost`, { agent }); }); - it('adds the authorization header if the url has userinfo (2/2)', function(done) { - if (!url.URL) return this.skip(); - + it('adds the authorization header if the url has userinfo (2/2)', (done) => { const agent = new CustomAgent(); const auth = 'test:testpass'; @@ -1884,7 +1882,7 @@ describe('WebSocket', () => { done(); }; - const ws = new WebSocket(new url.URL(`ws://${auth}@localhost`), { + const ws = new WebSocket(url.parse(`ws://${auth}@localhost`), { agent }); }); From 297f56df79f6bf4757465fb6b59884faf5b75337 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 21 Mar 2019 07:35:33 +0100 Subject: [PATCH 622/669] [minor] Remove unneeded `if` statement The `'error'` event can be emitted after the `'close'` event only if `socket.write()` is called on a closed socket. If this happens, it's a bug. --- lib/websocket.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 0dd5fb27b..32f092f81 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -893,8 +893,6 @@ function socketOnError() { this.removeListener('error', socketOnError); this.on('error', NOOP); - if (websocket) { - websocket.readyState = WebSocket.CLOSING; - this.destroy(); - } + websocket.readyState = WebSocket.CLOSING; + this.destroy(); } From aa1dcd506543b8979053589a8d5fc67482f0f9ed Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 26 Mar 2019 18:23:27 +0100 Subject: [PATCH 623/669] [fix] Make `WebSocket#close()` set the close timer immediately If there is buffered data to write and the stream is not flowing, it is possible that the callback of `Sender.prototype.close()` is never called. --- lib/websocket.js | 29 +++++++++++------------ test/websocket.test.js | 52 +++++++++++++++++++++++++----------------- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 32f092f81..af8108400 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -24,7 +24,7 @@ const { const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']; const protocolVersions = [8, 13]; -const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly. +const closeTimeout = 30 * 1000; /** * Class representing a WebSocket. @@ -87,8 +87,9 @@ class WebSocket extends EventEmitter { } /** - * This deviates from the WHATWG interface since ws doesn't support the required - * default "blob" type (instead we define a custom "nodebuffer" type). + * This deviates from the WHATWG interface since ws doesn't support the + * required default "blob" type (instead we define a custom "nodebuffer" + * type). * * @type {String} */ @@ -230,20 +231,16 @@ class WebSocket extends EventEmitter { if (err) return; this._closeFrameSent = true; - - if (this._socket.writable) { - if (this._closeFrameReceived) this._socket.end(); - - // - // Ensure that the connection is closed even if the closing handshake - // fails. - // - this._closeTimer = setTimeout( - this._socket.destroy.bind(this._socket), - closeTimeout - ); - } + if (this._closeFrameReceived) this._socket.end(); }); + + // + // Specify a timeout for the closing handshake to complete. + // + this._closeTimer = setTimeout( + this._socket.destroy.bind(this._socket), + closeTimeout + ); } /** diff --git a/test/websocket.test.js b/test/websocket.test.js index 8e63d0757..49edd1ce2 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -1305,27 +1305,6 @@ describe('WebSocket', () => { }); }); - it('ends connection to the server', (done) => { - const wss = new WebSocket.Server( - { - clientTracking: false, - port: 0 - }, - () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => { - ws.on('close', (code, reason) => { - assert.strictEqual(reason, 'some reason'); - assert.strictEqual(code, 1000); - wss.close(done); - }); - ws.close(1000, 'some reason'); - }); - } - ); - }); - it('permits all buffered data to be delivered', (done) => { const wss = new WebSocket.Server( { @@ -1383,6 +1362,37 @@ describe('WebSocket', () => { wss.on('connection', (ws) => ws.close()); }); + + it('sets a timer for the closing handshake to complete', (done) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + + ws.on('close', (code, reason) => { + assert.strictEqual(code, 1000); + assert.strictEqual(reason, 'some reason'); + wss.close(done); + }); + + ws.on('open', () => { + let callbackCalled = false; + + assert.strictEqual(ws._closeTimer, null); + + ws.send('foo', () => { + callbackCalled = true; + }); + + ws.close(1000, 'some reason'); + + // + // Check that the close timer is set even if the `Sender.close()` + // callback is not called. + // + assert.strictEqual(callbackCalled, false); + assert.strictEqual(ws._closeTimer._idleTimeout, 30000); + }); + }); + }); }); describe('#terminate', () => { From 0556f314a01543ba4e706d8892b288b613df25c6 Mon Sep 17 00:00:00 2001 From: Teddy Toussaint Date: Tue, 26 Mar 2019 21:48:50 +0100 Subject: [PATCH 624/669] [doc] Add TOC to ws.md (#1539) --- doc/ws.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index c23f1d6c8..d569d02b9 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -1,5 +1,49 @@ # ws +## Table of Contents + +- [Class: WebSocket.Server](#class-websocketserver) + - [new WebSocket.Server(options[, callback])](#new-websocketserveroptions-callback) + - [Event: 'close'](#event-close) + - [Event: 'connection'](#event-connection) + - [Event: 'error'](#event-error) + - [Event: 'headers'](#event-headers) + - [Event: 'listening'](#event-listening) + - [server.address()](#serveraddress) + - [server.clients](#serverclients) + - [server.close([callback])](#serverclosecallback) + - [server.handleUpgrade(request, socket, head, callback)](#serverhandleupgraderequest-socket-head-callback) + - [server.shouldHandle(request)](#servershouldhandlerequest) +- [Class: WebSocket](#class-websocket) + - [Ready state constants](#ready-state-constants) + - [new WebSocket(address[, protocols][, options])](#new-websocketaddress-protocols-options) + - [UNIX Domain Sockets](#unix-domain-sockets) + - [Event: 'close'](#event-close-1) + - [Event: 'error'](#event-error-1) + - [Event: 'message'](#event-message) + - [Event: 'open'](#event-open) + - [Event: 'ping'](#event-ping) + - [Event: 'pong'](#event-pong) + - [Event: 'unexpected-response'](#event-unexpected-response) + - [Event: 'upgrade'](#event-upgrade) + - [websocket.addEventListener(type, listener)](#websocketaddeventlistenertype-listener) + - [websocket.binaryType](#websocketbinarytype) + - [websocket.bufferedAmount](#websocketbufferedamount) + - [websocket.close([code[, reason]])](#websocketclosecode-reason) + - [websocket.extensions](#websocketextensions) + - [websocket.onclose](#websocketonclose) + - [websocket.onerror](#websocketonerror) + - [websocket.onmessage](#websocketonmessage) + - [websocket.onopen](#websocketonopen) + - [websocket.ping([data[, mask]][, callback])](#websocketpingdata-mask-callback) + - [websocket.pong([data[, mask]][, callback])](#websocketpongdata-mask-callback) + - [websocket.protocol](#websocketprotocol) + - [websocket.readyState](#websocketreadystate) + - [websocket.removeEventListener(type, listener)](#websocketremoveeventlistenertype-listener) + - [websocket.send(data[, options][, callback])](#websocketsenddata-options-callback) + - [websocket.terminate()](#websocketterminate) + - [websocket.url](#websocketurl) + ## Class: WebSocket.Server This class represents a WebSocket server. It extends the `EventEmitter`. @@ -129,13 +173,6 @@ handshake. This allows you to inspect/modify the headers before they are sent. Emitted when the underlying server has been bound. -### server.clients - -- {Set} - -A set that stores all connected clients. Please note that this property is only -added when the `clientTracking` is truthy. - ### server.address() Returns an object with `port`, `family`, and `address` properties specifying the @@ -143,6 +180,13 @@ bound address, the address family name, and port of the server as reported by the operating system if listening on an IP socket. If the server is listening on a pipe or UNIX domain socket, the name is returned as a string. +### server.clients + +- {Set} + +A set that stores all connected clients. Please note that this property is only +added when the `clientTracking` is truthy. + ### server.close([callback]) Close the HTTP server if created internally, terminate all clients and call From 40734d83dc0837c0e101f385d000781734c0906d Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 26 Mar 2019 22:12:27 +0100 Subject: [PATCH 625/669] [minor] Add missing option in JSDoc comment --- lib/websocket-server.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 991b50992..9b061d981 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -21,16 +21,20 @@ class WebSocketServer extends EventEmitter { * Create a `WebSocketServer` instance. * * @param {Object} options Configuration options + * @param {Number} options.backlog The maximum length of the queue of pending + * connections + * @param {Boolean} options.clientTracking Specifies whether or not to track + * clients + * @param {Function} options.handleProtocols An hook to handle protocols * @param {String} options.host The hostname where to bind the server + * @param {Number} options.maxPayload The maximum allowed message size + * @param {Boolean} options.noServer Enable no server mode + * @param {String} options.path Accept only connections matching this path + * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable + * permessage-deflate * @param {Number} options.port The port where to bind the server * @param {http.Server} options.server A pre-created HTTP/S server to use * @param {Function} options.verifyClient An hook to reject connections - * @param {Function} options.handleProtocols An hook to handle protocols - * @param {String} options.path Accept only connections matching this path - * @param {Boolean} options.noServer Enable no server mode - * @param {Boolean} options.clientTracking Specifies whether or not to track clients - * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate - * @param {Number} options.maxPayload The maximum allowed message size * @param {Function} callback A listener for the `listening` event */ constructor(options, callback) { From d57db27daf0e610590e3168266a214201a8c1d3a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 27 Mar 2019 09:34:10 +0100 Subject: [PATCH 626/669] [dist] 6.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2815effc8..4e0af1399 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "6.2.0", + "version": "6.2.1", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 736c082931c108f613b55c811663182dfdaddfce Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 30 Mar 2019 19:43:59 +0100 Subject: [PATCH 627/669] chore(package): update eslint to version 5.16.0 (#1541) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4e0af1399..034ca032b 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "benchmark": "~2.1.4", "bufferutil": "~4.0.0", "coveralls": "~3.0.3", - "eslint": "~5.15.0", + "eslint": "~5.16.0", "eslint-config-prettier": "~4.1.0", "eslint-plugin-prettier": "~3.0.0", "mocha": "~6.0.0", From 7e7c8d6da353bb5e00847f05ea3a3fef512281c0 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 8 Apr 2019 07:25:37 +0200 Subject: [PATCH 628/669] chore(package): update mocha to version 6.1.0 (#1544) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 034ca032b..f233fa0bd 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "eslint": "~5.16.0", "eslint-config-prettier": "~4.1.0", "eslint-plugin-prettier": "~3.0.0", - "mocha": "~6.0.0", + "mocha": "~6.1.0", "nyc": "~13.3.0", "prettier": "~1.16.1", "utf-8-validate": "~5.0.0" From e8e39ea839a715759f7672d1538c9821ed98c281 Mon Sep 17 00:00:00 2001 From: Marcin K Date: Fri, 12 Apr 2019 21:11:05 +0100 Subject: [PATCH 629/669] [doc] Update HTTPS example, remove new createServer (#1548) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2b5c6115a..ccb09f947 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ const fs = require('fs'); const https = require('https'); const WebSocket = require('ws'); -const server = new https.createServer({ +const server = https.createServer({ cert: fs.readFileSync('/path/to/cert.pem'), key: fs.readFileSync('/path/to/key.pem') }); From 8a5a2cf04ec3b0fb9067bd9918202971820c9c5e Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 13 Apr 2019 07:07:58 +0200 Subject: [PATCH 630/669] chore(package): update prettier to version 1.17.0 (#1549) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f233fa0bd..26569668d 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "eslint-plugin-prettier": "~3.0.0", "mocha": "~6.1.0", "nyc": "~13.3.0", - "prettier": "~1.16.1", + "prettier": "~1.17.0", "utf-8-validate": "~5.0.0" } } From 078336e29a3d5f45afd2bfee10a58a79ff085811 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 13 Apr 2019 08:09:39 +0200 Subject: [PATCH 631/669] [pkg] Add package-lock.json --- package-lock.json | 3246 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 10 +- 2 files changed, 3251 insertions(+), 5 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..051ee55c8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3246 @@ +{ + "name": "ws", + "version": "6.2.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/generator": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.0.tgz", + "integrity": "sha512-/v5I+a1jhGSKLgZDcmAUZ4K/VePi43eRkUs3yePW1HB1iANOD5tqJXwGSG4BZhSksP8J9ejSlwGeTiiOFZOrXQ==", + "dev": true, + "requires": { + "@babel/types": "^7.4.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.0.tgz", + "integrity": "sha512-7Cuc6JZiYShaZnybDmfwhY4UYHzI6rlqhWjaIqbsJGsIqPimEYy5uh3akSRLMg65LSdSEnJ8a8/bWQN6u2oMGw==", + "dev": true, + "requires": { + "@babel/types": "^7.4.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.3.tgz", + "integrity": "sha512-gxpEUhTS1sGA63EGQGuA+WESPR/6tz6ng7tSHFCmaTJK/cGK8y37cBTspX+U2xCAue2IQVvF6Z0oigmjwD8YGQ==", + "dev": true + }, + "@babel/template": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.0.tgz", + "integrity": "sha512-SOWwxxClTTh5NdbbYZ0BmaBVzxzTh2tO/TeLTbF6MO6EzVhHTnff8CdBXx3mEtazFBoysmEM6GU/wF+SuSx4Fw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.4.0", + "@babel/types": "^7.4.0" + } + }, + "@babel/traverse": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.3.tgz", + "integrity": "sha512-HmA01qrtaCwwJWpSKpA948cBvU5BrmviAief/b3AVw936DtcdsTexlbyzNuDnthwhOQ37xshn7hvQaEQk7ISYQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/types": "^7.4.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + } + }, + "@babel/types": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.0.tgz", + "integrity": "sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "acorn": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", + "dev": true + }, + "acorn-jsx": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", + "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "dev": true + }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "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==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "benchmark": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", + "integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=", + "dev": true, + "requires": { + "lodash": "^4.17.4", + "platform": "^1.3.3" + } + }, + "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 + }, + "bufferutil": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.1.tgz", + "integrity": "sha512-xowrxvpxojqkagPcWRQVXZl0YXhRhAtBEIq3VoER1NH5Mw1n1o0ojdspp+GS2J//2gCVyrzQDApQ4unGF+QOoA==", + "dev": true, + "requires": { + "node-gyp-build": "~3.7.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "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 + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "coveralls": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.3.tgz", + "integrity": "sha512-viNfeGlda2zJr8Gj1zqXpDMRjw9uM54p7wzZdvLRyOgnAfCe974Dq4veZkjJdxQXbmdppu6flEajFYseHYaUhg==", + "dev": true, + "requires": { + "growl": "~> 1.10.0", + "js-yaml": "^3.11.0", + "lcov-parse": "^0.0.10", + "log-driver": "^1.2.7", + "minimist": "^1.2.0", + "request": "^2.86.0" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "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 + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.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 + }, + "eslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", + "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.9.1", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^4.0.3", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.2.2", + "js-yaml": "^3.13.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0" + } + }, + "eslint-config-prettier": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-4.1.0.tgz", + "integrity": "sha512-zILwX9/Ocz4SV2vX7ox85AsrAgXV3f2o2gpIicdMIOra48WYqgUnWNH/cR/iHtmD2Vb3dLSC3LiEJnS05Gkw7w==", + "dev": true, + "requires": { + "get-stdin": "^6.0.0" + } + }, + "eslint-plugin-prettier": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.0.1.tgz", + "integrity": "sha512-/PMttrarPAY78PLvV3xfWibMOdMDl57hmlQ2XqFeA37wd+CJ7WSxV7txqjVPHi/AAFKd2lX0ZqfsOc/i5yFCSQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "dev": true, + "requires": { + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz", + "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", + "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", + "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "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.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inquirer": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz", + "integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.11", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz", + "integrity": "sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==", + "dev": true, + "requires": { + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "istanbul-lib-coverage": "^2.0.3", + "semver": "^5.5.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "lcov-parse": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", + "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + } + } + }, + "mime-db": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", + "dev": true + }, + "mime-types": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "dev": true, + "requires": { + "mime-db": "~1.38.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "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.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "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" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "mocha": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.3.tgz", + "integrity": "sha512-QdE/w//EPHrqgT5PNRUjRVHy6IJAzAf1R8n2O8W8K2RZ+NbPfOD5cBDp+PGa2Gptep37C/TdBiaNwakppEzEbg==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.0", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.2.2", + "yargs-parser": "13.0.0", + "yargs-unparser": "1.5.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "js-yaml": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.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==", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "node-gyp-build": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.7.0.tgz", + "integrity": "sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==", + "dev": true + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "nyc": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-13.3.0.tgz", + "integrity": "sha512-P+FwIuro2aFG6B0Esd9ZDWUd51uZrAEoGutqZxzrVmYl3qSfkLgcQpBPBjtDFsUQLFY1dvTQJPOyeqr8S9GF8w==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "arrify": "^1.0.1", + "caching-transform": "^3.0.1", + "convert-source-map": "^1.6.0", + "find-cache-dir": "^2.0.0", + "find-up": "^3.0.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.3", + "istanbul-lib-coverage": "^2.0.3", + "istanbul-lib-hook": "^2.0.3", + "istanbul-lib-instrument": "^3.1.0", + "istanbul-lib-report": "^2.0.4", + "istanbul-lib-source-maps": "^3.0.2", + "istanbul-reports": "^2.1.1", + "make-dir": "^1.3.0", + "merge-source-map": "^1.1.0", + "resolve-from": "^4.0.0", + "rimraf": "^2.6.3", + "signal-exit": "^3.0.2", + "spawn-wrap": "^1.4.2", + "test-exclude": "^5.1.0", + "uuid": "^3.3.2", + "yargs": "^12.0.5", + "yargs-parser": "^11.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "append-transform": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "default-require-extensions": "^2.0.0" + } + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "arrify": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "async": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "caching-transform": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "hasha": "^3.0.0", + "make-dir": "^1.3.0", + "package-hash": "^3.0.0", + "write-file-atomic": "^2.3.0" + } + }, + "camelcase": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "cliui": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "commander": { + "version": "2.17.1", + "bundled": true, + "dev": true, + "optional": true + }, + "commondir": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cross-spawn": { + "version": "4.0.2", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "default-require-extensions": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "bundled": true, + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es6-error": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "execa": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "bundled": true, + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "find-cache-dir": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "foreground-child": { + "version": "1.5.6", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^4", + "signal-exit": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "bundled": true, + "dev": true + }, + "handlebars": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "async": "^2.5.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + } + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "hasha": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-stream": "^1.0.1" + } + }, + "hosted-git-info": { + "version": "2.7.1", + "bundled": true, + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "invert-kv": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "istanbul-lib-coverage": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-report": { + "version": "2.0.4", + "bundled": true, + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.3", + "make-dir": "^1.3.0", + "supports-color": "^6.0.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.3", + "make-dir": "^1.3.0", + "rimraf": "^2.6.2", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + } + } + }, + "istanbul-reports": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "handlebars": "^4.1.0" + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "lcid": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "bundled": true, + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "bundled": true, + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "bundled": true, + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "mem": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^2.0.0" + } + }, + "merge-source-map": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + } + } + }, + "mimic-fn": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.10", + "bundled": true, + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + } + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optimist": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-locale": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-is-promise": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "p-limit": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "package-hash": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^3.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "bundled": true, + "dev": true + }, + "path-type": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "pump": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "read-pkg": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "release-zalgo": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "resolve": { + "version": "1.10.0", + "bundled": true, + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true + }, + "semver": { + "version": "5.6.0", + "bundled": true, + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "spawn-wrap": { + "version": "1.4.2", + "bundled": true, + "dev": true, + "requires": { + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" + } + }, + "spdx-correct": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "bundled": true, + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.3", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "test-exclude": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "arrify": "^1.0.1", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^1.0.1" + } + }, + "uglify-js": { + "version": "3.4.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "uuid": { + "version": "3.3.2", + "bundled": true, + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "bundled": true, + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "write-file-atomic": { + "version": "2.4.2", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "y18n": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "yargs": { + "version": "12.0.5", + "bundled": true, + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "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 + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "platform": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", + "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prettier": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.17.0.tgz", + "integrity": "sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "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==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/table/-/table-5.2.3.tgz", + "integrity": "sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==", + "dev": true, + "requires": { + "ajv": "^6.9.1", + "lodash": "^4.17.11", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "utf-8-validate": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.2.tgz", + "integrity": "sha512-SwV++i2gTD5qh2XqaPzBnNX88N6HdyhQrNNRykvcS0QKvItV9u3vPEJr+X5Hhfb1JC0r0e1alL0iB09rY8+nmw==", + "dev": true, + "requires": { + "node-gyp-build": "~3.7.0" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", + "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", + "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", + "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.11", + "yargs": "^12.0.5" + }, + "dependencies": { + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + } + } +} diff --git a/package.json b/package.json index 26569668d..079b60bfa 100644 --- a/package.json +++ b/package.json @@ -25,21 +25,21 @@ "scripts": { "test": "npm run lint && nyc --reporter=html --reporter=text mocha test/*.test.js", "integration": "npm run lint && mocha test/*.integration.js", - "lint": "eslint --ignore-path .gitignore . && prettier --check --ignore-path .gitignore \"**/*.{json,md,yml}\"" + "lint": "eslint --ignore-path .gitignore . && prettier --check --ignore-path .gitignore \"**/*.{json,md,yaml,yml}\"" }, "dependencies": { "async-limiter": "~1.0.0" }, "devDependencies": { "benchmark": "~2.1.4", - "bufferutil": "~4.0.0", + "bufferutil": "~4.0.1", "coveralls": "~3.0.3", "eslint": "~5.16.0", "eslint-config-prettier": "~4.1.0", - "eslint-plugin-prettier": "~3.0.0", - "mocha": "~6.1.0", + "eslint-plugin-prettier": "~3.0.1", + "mocha": "~6.1.3", "nyc": "~13.3.0", "prettier": "~1.17.0", - "utf-8-validate": "~5.0.0" + "utf-8-validate": "~5.0.2" } } From 3a7faf7b8ee75cf09ad5ad79aeeb231c468f5807 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 13 Apr 2019 09:03:19 +0200 Subject: [PATCH 632/669] [pkg] Add greenkeeper.json --- greenkeeper.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 greenkeeper.json diff --git a/greenkeeper.json b/greenkeeper.json new file mode 100644 index 000000000..6c07bed10 --- /dev/null +++ b/greenkeeper.json @@ -0,0 +1,9 @@ +{ + "commitMessages": { + "dependencyUpdate": "[pkg] Update ${dependency} to version ${version}", + "devDependencyUpdate": "[pkg] Update ${dependency} to version ${version}" + }, + "lockfiles": { + "outOfRangeUpdatesOnly": true + } +} From ebdf0f7dfdbacd2facb9a4afdea0db4995f81e14 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 13 Apr 2019 09:29:24 +0200 Subject: [PATCH 633/669] [pkg] Move Greenkeeper configuration to package.json Fixes #1550 --- greenkeeper.json | 9 --------- package.json | 9 +++++++++ 2 files changed, 9 insertions(+), 9 deletions(-) delete mode 100644 greenkeeper.json diff --git a/greenkeeper.json b/greenkeeper.json deleted file mode 100644 index 6c07bed10..000000000 --- a/greenkeeper.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "commitMessages": { - "dependencyUpdate": "[pkg] Update ${dependency} to version ${version}", - "devDependencyUpdate": "[pkg] Update ${dependency} to version ${version}" - }, - "lockfiles": { - "outOfRangeUpdatesOnly": true - } -} diff --git a/package.json b/package.json index 079b60bfa..0480405c4 100644 --- a/package.json +++ b/package.json @@ -41,5 +41,14 @@ "nyc": "~13.3.0", "prettier": "~1.17.0", "utf-8-validate": "~5.0.2" + }, + "greenkeeper": { + "commitMessages": { + "dependencyUpdate": "[pkg] Update ${dependency} to version ${version}", + "devDependencyUpdate": "[pkg] Update ${dependency} to version ${version}" + }, + "lockfiles": { + "outOfRangeUpdatesOnly": true + } } } From beccc795ce9b1f01a9ebd3bd50547b21cacceb73 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Wed, 17 Apr 2019 08:43:15 +0200 Subject: [PATCH 634/669] [pkg] Remove package-lock.json --- .npmrc | 1 + package-lock.json | 3246 --------------------------------------------- package.json | 3 - 3 files changed, 1 insertion(+), 3249 deletions(-) create mode 100644 .npmrc delete mode 100644 package-lock.json diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..43c97e719 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 051ee55c8..000000000 --- a/package-lock.json +++ /dev/null @@ -1,3246 +0,0 @@ -{ - "name": "ws", - "version": "6.2.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/generator": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.0.tgz", - "integrity": "sha512-/v5I+a1jhGSKLgZDcmAUZ4K/VePi43eRkUs3yePW1HB1iANOD5tqJXwGSG4BZhSksP8J9ejSlwGeTiiOFZOrXQ==", - "dev": true, - "requires": { - "@babel/types": "^7.4.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.11", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.0.tgz", - "integrity": "sha512-7Cuc6JZiYShaZnybDmfwhY4UYHzI6rlqhWjaIqbsJGsIqPimEYy5uh3akSRLMg65LSdSEnJ8a8/bWQN6u2oMGw==", - "dev": true, - "requires": { - "@babel/types": "^7.4.0" - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.3.tgz", - "integrity": "sha512-gxpEUhTS1sGA63EGQGuA+WESPR/6tz6ng7tSHFCmaTJK/cGK8y37cBTspX+U2xCAue2IQVvF6Z0oigmjwD8YGQ==", - "dev": true - }, - "@babel/template": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.0.tgz", - "integrity": "sha512-SOWwxxClTTh5NdbbYZ0BmaBVzxzTh2tO/TeLTbF6MO6EzVhHTnff8CdBXx3mEtazFBoysmEM6GU/wF+SuSx4Fw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.4.0", - "@babel/types": "^7.4.0" - } - }, - "@babel/traverse": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.3.tgz", - "integrity": "sha512-HmA01qrtaCwwJWpSKpA948cBvU5BrmviAief/b3AVw936DtcdsTexlbyzNuDnthwhOQ37xshn7hvQaEQk7ISYQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.4.0", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.4.0", - "@babel/parser": "^7.4.3", - "@babel/types": "^7.4.0", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.11" - } - }, - "@babel/types": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.0.tgz", - "integrity": "sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.11", - "to-fast-properties": "^2.0.0" - } - }, - "acorn": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", - "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", - "dev": true - }, - "acorn-jsx": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", - "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", - "dev": true - }, - "ajv": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "dev": true - }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "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==" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "benchmark": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", - "integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=", - "dev": true, - "requires": { - "lodash": "^4.17.4", - "platform": "^1.3.3" - } - }, - "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 - }, - "bufferutil": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.1.tgz", - "integrity": "sha512-xowrxvpxojqkagPcWRQVXZl0YXhRhAtBEIq3VoER1NH5Mw1n1o0ojdspp+GS2J//2gCVyrzQDApQ4unGF+QOoA==", - "dev": true, - "requires": { - "node-gyp-build": "~3.7.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "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 - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "coveralls": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.3.tgz", - "integrity": "sha512-viNfeGlda2zJr8Gj1zqXpDMRjw9uM54p7wzZdvLRyOgnAfCe974Dq4veZkjJdxQXbmdppu6flEajFYseHYaUhg==", - "dev": true, - "requires": { - "growl": "~> 1.10.0", - "js-yaml": "^3.11.0", - "lcov-parse": "^0.0.10", - "log-driver": "^1.2.7", - "minimist": "^1.2.0", - "request": "^2.86.0" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "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 - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "es-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", - "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.0", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", - "object-keys": "^1.0.12" - } - }, - "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.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 - }, - "eslint": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", - "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.9.1", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^4.0.3", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^5.0.1", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^6.2.2", - "js-yaml": "^3.13.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.11", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0" - } - }, - "eslint-config-prettier": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-4.1.0.tgz", - "integrity": "sha512-zILwX9/Ocz4SV2vX7ox85AsrAgXV3f2o2gpIicdMIOra48WYqgUnWNH/cR/iHtmD2Vb3dLSC3LiEJnS05Gkw7w==", - "dev": true, - "requires": { - "get-stdin": "^6.0.0" - } - }, - "eslint-plugin-prettier": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.0.1.tgz", - "integrity": "sha512-/PMttrarPAY78PLvV3xfWibMOdMDl57hmlQ2XqFeA37wd+CJ7WSxV7txqjVPHi/AAFKd2lX0ZqfsOc/i5yFCSQ==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", - "dev": true - }, - "eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", - "dev": true - }, - "espree": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", - "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", - "dev": true, - "requires": { - "acorn": "^6.0.7", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", - "dev": true, - "requires": { - "estraverse": "^4.0.0" - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "external-editor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", - "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, - "requires": { - "flat-cache": "^2.0.1" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", - "dev": true, - "requires": { - "is-buffer": "~2.0.3" - } - }, - "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - } - }, - "flatted": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz", - "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==", - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", - "dev": true - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "globals": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", - "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==", - "dev": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "import-fresh": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", - "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "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.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "inquirer": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz", - "integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==", - "dev": true, - "requires": { - "ansi-escapes": "^3.2.0", - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.11", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", - "strip-ansi": "^5.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, - "is-buffer": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", - "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", - "dev": true - }, - "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", - "dev": true - }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, - "requires": { - "has": "^1.0.1" - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-symbol": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz", - "integrity": "sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==", - "dev": true, - "requires": { - "@babel/generator": "^7.0.0", - "@babel/parser": "^7.0.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0", - "istanbul-lib-coverage": "^2.0.3", - "semver": "^5.5.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, - "lcov-parse": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", - "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, - "log-driver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "dev": true - }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "requires": { - "chalk": "^2.0.1" - } - }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - }, - "dependencies": { - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - } - } - }, - "mime-db": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", - "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", - "dev": true - }, - "mime-types": { - "version": "2.1.22", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", - "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", - "dev": true, - "requires": { - "mime-db": "~1.38.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "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.7" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "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" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - } - } - }, - "mocha": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.3.tgz", - "integrity": "sha512-QdE/w//EPHrqgT5PNRUjRVHy6IJAzAf1R8n2O8W8K2RZ+NbPfOD5cBDp+PGa2Gptep37C/TdBiaNwakppEzEbg==", - "dev": true, - "requires": { - "ansi-colors": "3.2.3", - "browser-stdout": "1.3.1", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.0", - "log-symbols": "2.2.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "ms": "2.1.1", - "node-environment-flags": "1.0.5", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.2.2", - "yargs-parser": "13.0.0", - "yargs-unparser": "1.5.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "js-yaml": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", - "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dev": true, - "requires": { - "has-flag": "^3.0.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==", - "dev": true - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node-environment-flags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", - "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - } - }, - "node-gyp-build": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.7.0.tgz", - "integrity": "sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==", - "dev": true - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "nyc": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-13.3.0.tgz", - "integrity": "sha512-P+FwIuro2aFG6B0Esd9ZDWUd51uZrAEoGutqZxzrVmYl3qSfkLgcQpBPBjtDFsUQLFY1dvTQJPOyeqr8S9GF8w==", - "dev": true, - "requires": { - "archy": "^1.0.0", - "arrify": "^1.0.1", - "caching-transform": "^3.0.1", - "convert-source-map": "^1.6.0", - "find-cache-dir": "^2.0.0", - "find-up": "^3.0.0", - "foreground-child": "^1.5.6", - "glob": "^7.1.3", - "istanbul-lib-coverage": "^2.0.3", - "istanbul-lib-hook": "^2.0.3", - "istanbul-lib-instrument": "^3.1.0", - "istanbul-lib-report": "^2.0.4", - "istanbul-lib-source-maps": "^3.0.2", - "istanbul-reports": "^2.1.1", - "make-dir": "^1.3.0", - "merge-source-map": "^1.1.0", - "resolve-from": "^4.0.0", - "rimraf": "^2.6.3", - "signal-exit": "^3.0.2", - "spawn-wrap": "^1.4.2", - "test-exclude": "^5.1.0", - "uuid": "^3.3.2", - "yargs": "^12.0.5", - "yargs-parser": "^11.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "bundled": true, - "dev": true - }, - "append-transform": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "default-require-extensions": "^2.0.0" - } - }, - "archy": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "arrify": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "async": { - "version": "2.6.2", - "bundled": true, - "dev": true, - "requires": { - "lodash": "^4.17.11" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "caching-transform": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "hasha": "^3.0.0", - "make-dir": "^1.3.0", - "package-hash": "^3.0.0", - "write-file-atomic": "^2.3.0" - } - }, - "camelcase": { - "version": "5.0.0", - "bundled": true, - "dev": true - }, - "cliui": { - "version": "4.1.0", - "bundled": true, - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "commander": { - "version": "2.17.1", - "bundled": true, - "dev": true, - "optional": true - }, - "commondir": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "convert-source-map": { - "version": "1.6.0", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "cross-spawn": { - "version": "4.0.2", - "bundled": true, - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" - } - }, - "debug": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "decamelize": { - "version": "1.2.0", - "bundled": true, - "dev": true - }, - "default-require-extensions": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "requires": { - "strip-bom": "^3.0.0" - } - }, - "end-of-stream": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "error-ex": { - "version": "1.3.2", - "bundled": true, - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es6-error": { - "version": "4.1.1", - "bundled": true, - "dev": true - }, - "execa": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "bundled": true, - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - } - } - }, - "find-cache-dir": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^1.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "foreground-child": { - "version": "1.5.6", - "bundled": true, - "dev": true, - "requires": { - "cross-spawn": "^4", - "signal-exit": "^3.0.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "get-caller-file": { - "version": "1.0.3", - "bundled": true, - "dev": true - }, - "get-stream": { - "version": "4.1.0", - "bundled": true, - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "glob": { - "version": "7.1.3", - "bundled": true, - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.1.15", - "bundled": true, - "dev": true - }, - "handlebars": { - "version": "4.1.0", - "bundled": true, - "dev": true, - "requires": { - "async": "^2.5.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "bundled": true, - "dev": true - } - } - }, - "has-flag": { - "version": "3.0.0", - "bundled": true, - "dev": true - }, - "hasha": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "is-stream": "^1.0.1" - } - }, - "hosted-git-info": { - "version": "2.7.1", - "bundled": true, - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "bundled": true, - "dev": true - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "invert-kv": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "bundled": true, - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "is-stream": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "isexe": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "istanbul-lib-coverage": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "istanbul-lib-hook": { - "version": "2.0.3", - "bundled": true, - "dev": true, - "requires": { - "append-transform": "^1.0.0" - } - }, - "istanbul-lib-report": { - "version": "2.0.4", - "bundled": true, - "dev": true, - "requires": { - "istanbul-lib-coverage": "^2.0.3", - "make-dir": "^1.3.0", - "supports-color": "^6.0.0" - }, - "dependencies": { - "supports-color": { - "version": "6.1.0", - "bundled": true, - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.3", - "make-dir": "^1.3.0", - "rimraf": "^2.6.2", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "bundled": true, - "dev": true - } - } - }, - "istanbul-reports": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "requires": { - "handlebars": "^4.1.0" - } - }, - "json-parse-better-errors": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "lcid": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, - "load-json-file": { - "version": "4.0.0", - "bundled": true, - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.11", - "bundled": true, - "dev": true - }, - "lodash.flattendeep": { - "version": "4.4.0", - "bundled": true, - "dev": true - }, - "lru-cache": { - "version": "4.1.5", - "bundled": true, - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "make-dir": { - "version": "1.3.0", - "bundled": true, - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "map-age-cleaner": { - "version": "0.1.3", - "bundled": true, - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, - "mem": { - "version": "4.1.0", - "bundled": true, - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^1.0.0", - "p-is-promise": "^2.0.0" - } - }, - "merge-source-map": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "requires": { - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "bundled": true, - "dev": true - } - } - }, - "mimic-fn": { - "version": "1.2.0", - "bundled": true, - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.10", - "bundled": true, - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - } - } - }, - "ms": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "bundled": true, - "dev": true - }, - "normalize-package-data": { - "version": "2.5.0", - "bundled": true, - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "npm-run-path": { - "version": "2.0.2", - "bundled": true, - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "optimist": { - "version": "0.6.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "os-locale": { - "version": "3.1.0", - "bundled": true, - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "p-defer": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "p-is-promise": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "p-limit": { - "version": "2.1.0", - "bundled": true, - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "package-hash": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "graceful-fs": "^4.1.15", - "hasha": "^3.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "bundled": true, - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "path-exists": { - "version": "3.0.0", - "bundled": true, - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "path-key": { - "version": "2.0.1", - "bundled": true, - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "bundled": true, - "dev": true - }, - "path-type": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "pify": { - "version": "3.0.0", - "bundled": true, - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - }, - "pseudomap": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "pump": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "read-pkg": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "read-pkg-up": { - "version": "4.0.0", - "bundled": true, - "dev": true, - "requires": { - "find-up": "^3.0.0", - "read-pkg": "^3.0.0" - } - }, - "release-zalgo": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "es6-error": "^4.0.1" - } - }, - "require-directory": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "resolve": { - "version": "1.10.0", - "bundled": true, - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-from": { - "version": "4.0.0", - "bundled": true, - "dev": true - }, - "rimraf": { - "version": "2.6.3", - "bundled": true, - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true - }, - "semver": { - "version": "5.6.0", - "bundled": true, - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true - }, - "spawn-wrap": { - "version": "1.4.2", - "bundled": true, - "dev": true, - "requires": { - "foreground-child": "^1.5.6", - "mkdirp": "^0.5.0", - "os-homedir": "^1.0.1", - "rimraf": "^2.6.2", - "signal-exit": "^3.0.2", - "which": "^1.3.0" - } - }, - "spdx-correct": { - "version": "3.1.0", - "bundled": true, - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.2.0", - "bundled": true, - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.3", - "bundled": true, - "dev": true - }, - "string-width": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "bundled": true, - "dev": true - }, - "strip-eof": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "test-exclude": { - "version": "5.1.0", - "bundled": true, - "dev": true, - "requires": { - "arrify": "^1.0.1", - "minimatch": "^3.0.4", - "read-pkg-up": "^4.0.0", - "require-main-filename": "^1.0.1" - } - }, - "uglify-js": { - "version": "3.4.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "commander": "~2.17.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "uuid": { - "version": "3.3.2", - "bundled": true, - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "which": { - "version": "1.3.1", - "bundled": true, - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "wordwrap": { - "version": "0.0.3", - "bundled": true, - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "bundled": true, - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "write-file-atomic": { - "version": "2.4.2", - "bundled": true, - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "y18n": { - "version": "4.0.0", - "bundled": true, - "dev": true - }, - "yallist": { - "version": "2.1.2", - "bundled": true, - "dev": true - }, - "yargs": { - "version": "12.0.5", - "bundled": true, - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" - } - }, - "yargs-parser": { - "version": "11.1.1", - "bundled": true, - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.getownpropertydescriptors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - } - }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - }, - "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "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 - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "platform": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", - "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "prettier": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.17.0.tgz", - "integrity": "sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw==", - "dev": true - }, - "prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "psl": { - "version": "1.1.31", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", - "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", - "dev": true - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } - }, - "rxjs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", - "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "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==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "table": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/table/-/table-5.2.3.tgz", - "integrity": "sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==", - "dev": true, - "requires": { - "ajv": "^6.9.1", - "lodash": "^4.17.11", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "dev": true, - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - } - } - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, - "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "utf-8-validate": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.2.tgz", - "integrity": "sha512-SwV++i2gTD5qh2XqaPzBnNX88N6HdyhQrNNRykvcS0QKvItV9u3vPEJr+X5Hhfb1JC0r0e1alL0iB09rY8+nmw==", - "dev": true, - "requires": { - "node-gyp-build": "~3.7.0" - } - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "yargs": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", - "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "os-locale": "^3.1.0", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "yargs-parser": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", - "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "yargs-unparser": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", - "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", - "dev": true, - "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.11", - "yargs": "^12.0.5" - }, - "dependencies": { - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" - } - }, - "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - } - } -} diff --git a/package.json b/package.json index 0480405c4..0a20c5f8c 100644 --- a/package.json +++ b/package.json @@ -46,9 +46,6 @@ "commitMessages": { "dependencyUpdate": "[pkg] Update ${dependency} to version ${version}", "devDependencyUpdate": "[pkg] Update ${dependency} to version ${version}" - }, - "lockfiles": { - "outOfRangeUpdatesOnly": true } } } From 1a15120fa8175f95d2136ec795d240b6a1c4f8e8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 18 Apr 2019 07:01:22 +0200 Subject: [PATCH 635/669] [pkg] Update nyc to version 14.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0a20c5f8c..94c3b9a0e 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "eslint-config-prettier": "~4.1.0", "eslint-plugin-prettier": "~3.0.1", "mocha": "~6.1.3", - "nyc": "~13.3.0", + "nyc": "~14.0.0", "prettier": "~1.17.0", "utf-8-validate": "~5.0.2" }, From 5479eaedff6d92bf1f2cac8f269c474344f88749 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 18 Apr 2019 07:26:31 +0200 Subject: [PATCH 636/669] [pkg] Use caret ranges for all dependencies --- package.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 94c3b9a0e..cbdbd3d59 100644 --- a/package.json +++ b/package.json @@ -28,19 +28,19 @@ "lint": "eslint --ignore-path .gitignore . && prettier --check --ignore-path .gitignore \"**/*.{json,md,yaml,yml}\"" }, "dependencies": { - "async-limiter": "~1.0.0" + "async-limiter": "^1.0.0" }, "devDependencies": { - "benchmark": "~2.1.4", - "bufferutil": "~4.0.1", - "coveralls": "~3.0.3", - "eslint": "~5.16.0", - "eslint-config-prettier": "~4.1.0", - "eslint-plugin-prettier": "~3.0.1", - "mocha": "~6.1.3", - "nyc": "~14.0.0", - "prettier": "~1.17.0", - "utf-8-validate": "~5.0.2" + "benchmark": "^2.1.4", + "bufferutil": "^4.0.1", + "coveralls": "^3.0.3", + "eslint": "^5.16.0", + "eslint-config-prettier": "^4.1.0", + "eslint-plugin-prettier": "^3.0.1", + "mocha": "^6.1.3", + "nyc": "^14.0.0", + "prettier": "^1.17.0", + "utf-8-validate": "^5.0.2" }, "greenkeeper": { "commitMessages": { From 5d751fbd4c0ab3478a6de4194d4d06908bc8ac00 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 25 Apr 2019 08:45:10 +0200 Subject: [PATCH 637/669] [major] Change `WebSocket#{p{i,o}ng,send}()` behavior (#1532) - If the `readyState` attribute is `CONNECTING`, throw an exception. - If the `readyState` attribute is `CLOSING` or `CLOSED` - Increase the `bufferedAmount` attribute by the length of the `data` argument in bytes. - If specified, call the `callback` function with an error. Fixes #1515 --- README.md | 25 --- lib/websocket.js | 88 +++++++---- test/websocket.test.js | 337 ++++++++++++++++++++++++++++++++--------- 3 files changed, 325 insertions(+), 125 deletions(-) diff --git a/README.md b/README.md index ccb09f947..1dc16754a 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,6 @@ can use one of the many wrappers available on npm, like - [Server broadcast](#server-broadcast) - [echo.websocket.org demo](#echowebsocketorg-demo) - [Other examples](#other-examples) -- [Error handling best practices](#error-handling-best-practices) - [FAQ](#faq) - [How to get the IP address of the client?](#how-to-get-the-ip-address-of-the-client) - [How to detect and close broken connections?](#how-to-detect-and-close-broken-connections) @@ -309,30 +308,6 @@ examples folder. Otherwise, see the test cases. -## Error handling best practices - -```js -// If the WebSocket is closed before the following send is attempted -ws.send('something'); - -// Errors (both immediate and async write errors) can be detected in an optional -// callback. The callback is also the only way of being notified that data has -// actually been sent. -ws.send('something', function ack(error) { - // If error is not defined, the send has been completed, otherwise the error - // object will indicate what failed. -}); - -// Immediate errors can also be handled with `try...catch`, but **note** that -// since sends are inherently asynchronous, socket write failures will *not* be -// captured when this technique is used. -try { - ws.send('something'); -} catch (e) { - /* handle error */ -} -``` - ## FAQ ### How to get the IP address of the client? diff --git a/lib/websocket.js b/lib/websocket.js index af8108400..ce4317f4c 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -21,6 +21,7 @@ const { kWebSocket, NOOP } = require('./constants'); +const { toBuffer } = require('./buffer-util'); const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']; const protocolVersions = [8, 13]; @@ -57,6 +58,7 @@ class WebSocket extends EventEmitter { this._socket = null; if (address !== null) { + this._bufferedAmount = 0; this._isServer = false; this._redirects = 0; @@ -112,7 +114,7 @@ class WebSocket extends EventEmitter { * @type {Number} */ get bufferedAmount() { - if (!this._socket) return 0; + if (!this._socket) return this._bufferedAmount; // // `socket.bufferSize` is `undefined` if the socket is closed. @@ -252,6 +254,10 @@ class WebSocket extends EventEmitter { * @public */ ping(data, mask, cb) { + if (this.readyState === WebSocket.CONNECTING) { + throw new Error('WebSocket is not open: readyState 0 (CONNECTING)'); + } + if (typeof data === 'function') { cb = data; data = mask = undefined; @@ -260,17 +266,13 @@ class WebSocket extends EventEmitter { mask = undefined; } - if (this.readyState !== WebSocket.OPEN) { - const err = new Error( - `WebSocket is not open: readyState ${this.readyState} ` + - `(${readyStates[this.readyState]})` - ); + if (typeof data === 'number') data = data.toString(); - if (cb) return cb(err); - throw err; + if (this.readyState !== WebSocket.OPEN) { + sendAfterClose(this, data, cb); + return; } - if (typeof data === 'number') data = data.toString(); if (mask === undefined) mask = !this._isServer; this._sender.ping(data || EMPTY_BUFFER, mask, cb); } @@ -284,6 +286,10 @@ class WebSocket extends EventEmitter { * @public */ pong(data, mask, cb) { + if (this.readyState === WebSocket.CONNECTING) { + throw new Error('WebSocket is not open: readyState 0 (CONNECTING)'); + } + if (typeof data === 'function') { cb = data; data = mask = undefined; @@ -292,17 +298,13 @@ class WebSocket extends EventEmitter { mask = undefined; } - if (this.readyState !== WebSocket.OPEN) { - const err = new Error( - `WebSocket is not open: readyState ${this.readyState} ` + - `(${readyStates[this.readyState]})` - ); + if (typeof data === 'number') data = data.toString(); - if (cb) return cb(err); - throw err; + if (this.readyState !== WebSocket.OPEN) { + sendAfterClose(this, data, cb); + return; } - if (typeof data === 'number') data = data.toString(); if (mask === undefined) mask = !this._isServer; this._sender.pong(data || EMPTY_BUFFER, mask, cb); } @@ -312,7 +314,8 @@ class WebSocket extends EventEmitter { * * @param {*} data The message to send * @param {Object} options Options object - * @param {Boolean} options.compress Specifies whether or not to compress `data` + * @param {Boolean} options.compress Specifies whether or not to compress + * `data` * @param {Boolean} options.binary Specifies whether `data` is binary or text * @param {Boolean} options.fin Specifies whether the fragment is the last one * @param {Boolean} options.mask Specifies whether or not to mask `data` @@ -320,23 +323,22 @@ class WebSocket extends EventEmitter { * @public */ send(data, options, cb) { + if (this.readyState === WebSocket.CONNECTING) { + throw new Error('WebSocket is not open: readyState 0 (CONNECTING)'); + } + if (typeof options === 'function') { cb = options; options = {}; } - if (this.readyState !== WebSocket.OPEN) { - const err = new Error( - `WebSocket is not open: readyState ${this.readyState} ` + - `(${readyStates[this.readyState]})` - ); + if (typeof data === 'number') data = data.toString(); - if (cb) return cb(err); - throw err; + if (this.readyState !== WebSocket.OPEN) { + sendAfterClose(this, data, cb); + return; } - if (typeof data === 'number') data = data.toString(); - const opts = Object.assign( { binary: typeof data !== 'string', @@ -723,6 +725,38 @@ function abortHandshake(websocket, stream, message) { } } +/** + * Handle cases where the `ping()`, `pong()`, or `send()` methods are called + * when the `readyState` attribute is `CLOSING` or `CLOSED`. + * + * @param {WebSocket} websocket The WebSocket instance + * @param {*} data The data to send + * @param {Function} cb Callback + * @private + */ +function sendAfterClose(websocket, data, cb) { + if (data) { + const length = toBuffer(data).length; + + // + // The `_bufferedAmount` property is used only when the peer is a client and + // the opening handshake fails. Under these circumstances, in fact, the + // `setSocket()` method is not called, so the `_socket` and `_sender` + // properties are set to `null`. + // + if (websocket._socket) websocket._sender._bufferedBytes += length; + else websocket._bufferedAmount += length; + } + + if (cb) { + const err = new Error( + `WebSocket is not open: readyState ${websocket.readyState} ` + + `(${readyStates[websocket.readyState]})` + ); + cb(err); + } +} + /** * The listener of the `Receiver` `'conclude'` event. * diff --git a/test/websocket.test.js b/test/websocket.test.js index 49edd1ce2..6c23a15ea 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -10,7 +10,7 @@ const url = require('url'); const fs = require('fs'); const WebSocket = require('..'); -const { GUID } = require('../lib/constants'); +const { GUID, NOOP } = require('../lib/constants'); class CustomAgent extends http.Agent { addRequest() {} @@ -647,7 +647,7 @@ describe('WebSocket', () => { it('fails if server sends no subprotocol', (done) => { const wss = new WebSocket.Server({ - handleProtocols: () => {}, + handleProtocols() {}, server }); @@ -690,7 +690,7 @@ describe('WebSocket', () => { server.once('upgrade', (req, socket) => { socket.end('HTTP/1.1 302 Found\r\nLocation: /foo\r\n\r\n'); server.once('upgrade', (req, socket, head) => { - wss.handleUpgrade(req, socket, head, () => {}); + wss.handleUpgrade(req, socket, head, NOOP); }); }); @@ -752,9 +752,9 @@ describe('WebSocket', () => { }); describe('#ping', () => { - it('throws an error if `readyState` is not `OPEN`', (done) => { + it('throws an error if `readyState` is `CONNECTING`', () => { const ws = new WebSocket('ws://localhost', { - agent: new CustomAgent() + lookup() {} }); assert.throws( @@ -762,13 +762,80 @@ describe('WebSocket', () => { /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ ); - ws.ping((err) => { + assert.throws( + () => ws.ping(NOOP), + /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ + ); + }); + + it('increases `bufferedAmount` if `readyState` is 2 or 3', (done) => { + const ws = new WebSocket('ws://localhost', { + lookup() {} + }); + + ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( err.message, - 'WebSocket is not open: readyState 0 (CONNECTING)' + 'WebSocket was closed before the connection was established' ); - done(); + + assert.strictEqual(ws.readyState, WebSocket.CLOSING); + assert.strictEqual(ws.bufferedAmount, 0); + + ws.ping('hi'); + assert.strictEqual(ws.bufferedAmount, 2); + + ws.ping(); + assert.strictEqual(ws.bufferedAmount, 2); + + ws.on('close', () => { + assert.strictEqual(ws.readyState, WebSocket.CLOSED); + + ws.ping('hi'); + assert.strictEqual(ws.bufferedAmount, 4); + + ws.ping(); + assert.strictEqual(ws.bufferedAmount, 4); + + done(); + }); + }); + + ws.close(); + }); + + it('calls the callback w/ an error if `readyState` is 2 or 3', (done) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + }); + + wss.on('connection', (ws) => { + ws.close(); + + assert.strictEqual(ws.bufferedAmount, 0); + + ws.ping('hi', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'WebSocket is not open: readyState 2 (CLOSING)' + ); + assert.strictEqual(ws.bufferedAmount, 2); + + ws.on('close', () => { + ws.ping((err) => { + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'WebSocket is not open: readyState 3 (CLOSED)' + ); + assert.strictEqual(ws.bufferedAmount, 2); + + wss.close(done); + }); + }); + }); }); }); @@ -826,9 +893,9 @@ describe('WebSocket', () => { }); describe('#pong', () => { - it('throws an error if `readyState` is not `OPEN`', (done) => { + it('throws an error if `readyState` is `CONNECTING`', () => { const ws = new WebSocket('ws://localhost', { - agent: new CustomAgent() + lookup() {} }); assert.throws( @@ -836,13 +903,80 @@ describe('WebSocket', () => { /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ ); - ws.pong((err) => { + assert.throws( + () => ws.pong(NOOP), + /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ + ); + }); + + it('increases `bufferedAmount` if `readyState` is 2 or 3', (done) => { + const ws = new WebSocket('ws://localhost', { + lookup() {} + }); + + ws.on('error', (err) => { assert.ok(err instanceof Error); assert.strictEqual( err.message, - 'WebSocket is not open: readyState 0 (CONNECTING)' + 'WebSocket was closed before the connection was established' ); - done(); + + assert.strictEqual(ws.readyState, WebSocket.CLOSING); + assert.strictEqual(ws.bufferedAmount, 0); + + ws.pong('hi'); + assert.strictEqual(ws.bufferedAmount, 2); + + ws.pong(); + assert.strictEqual(ws.bufferedAmount, 2); + + ws.on('close', () => { + assert.strictEqual(ws.readyState, WebSocket.CLOSED); + + ws.pong('hi'); + assert.strictEqual(ws.bufferedAmount, 4); + + ws.pong(); + assert.strictEqual(ws.bufferedAmount, 4); + + done(); + }); + }); + + ws.close(); + }); + + it('calls the callback w/ an error if `readyState` is 2 or 3', (done) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + }); + + wss.on('connection', (ws) => { + ws.close(); + + assert.strictEqual(ws.bufferedAmount, 0); + + ws.pong('hi', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'WebSocket is not open: readyState 2 (CLOSING)' + ); + assert.strictEqual(ws.bufferedAmount, 2); + + ws.on('close', () => { + ws.pong((err) => { + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'WebSocket is not open: readyState 3 (CLOSED)' + ); + assert.strictEqual(ws.bufferedAmount, 2); + + wss.close(done); + }); + }); + }); }); }); @@ -900,6 +1034,93 @@ describe('WebSocket', () => { }); describe('#send', () => { + it('throws an error if `readyState` is `CONNECTING`', () => { + const ws = new WebSocket('ws://localhost', { + lookup() {} + }); + + assert.throws( + () => ws.send('hi'), + /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ + ); + + assert.throws( + () => ws.send('hi', NOOP), + /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ + ); + }); + + it('increases `bufferedAmount` if `readyState` is 2 or 3', (done) => { + const ws = new WebSocket('ws://localhost', { + lookup() {} + }); + + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'WebSocket was closed before the connection was established' + ); + + assert.strictEqual(ws.readyState, WebSocket.CLOSING); + assert.strictEqual(ws.bufferedAmount, 0); + + ws.send('hi'); + assert.strictEqual(ws.bufferedAmount, 2); + + ws.send(); + assert.strictEqual(ws.bufferedAmount, 2); + + ws.on('close', () => { + assert.strictEqual(ws.readyState, WebSocket.CLOSED); + + ws.send('hi'); + assert.strictEqual(ws.bufferedAmount, 4); + + ws.send(); + assert.strictEqual(ws.bufferedAmount, 4); + + done(); + }); + }); + + ws.close(); + }); + + it('calls the callback w/ an error if `readyState` is 2 or 3', (done) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + }); + + wss.on('connection', (ws) => { + ws.close(); + + assert.strictEqual(ws.bufferedAmount, 0); + + ws.send('hi', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'WebSocket is not open: readyState 2 (CLOSING)' + ); + assert.strictEqual(ws.bufferedAmount, 2); + + ws.on('close', () => { + ws.send('hi', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'WebSocket is not open: readyState 3 (CLOSED)' + ); + assert.strictEqual(ws.bufferedAmount, 4); + + wss.close(done); + }); + }); + }); + }); + }); + it('can send a big binary message', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const array = new Float32Array(5 * 1024 * 1024); @@ -1056,32 +1277,7 @@ describe('WebSocket', () => { }); }); - it('throws an error if `readyState` is not `OPEN`', () => { - const ws = new WebSocket('ws://localhost', { - agent: new CustomAgent() - }); - - assert.throws( - () => ws.send('hi'), - /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ - ); - }); - - it('passes errors to the callback, if present', () => { - const ws = new WebSocket('ws://localhost', { - agent: new CustomAgent() - }); - - ws.send('hi', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual( - err.message, - 'WebSocket is not open: readyState 0 (CONNECTING)' - ); - }); - }); - - it('calls the optional callback when data is written out', (done) => { + it('calls the callback when data is written out', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const ws = new WebSocket(`ws://localhost:${wss.address().port}`); @@ -1483,7 +1679,6 @@ describe('WebSocket', () => { describe('WHATWG API emulation', () => { it('supports the `on{close,error,message,open}` attributes', () => { - const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); assert.strictEqual(ws.onmessage, undefined); @@ -1491,15 +1686,15 @@ describe('WebSocket', () => { assert.strictEqual(ws.onerror, undefined); assert.strictEqual(ws.onopen, undefined); - ws.onmessage = listener; - ws.onerror = listener; - ws.onclose = listener; - ws.onopen = listener; + ws.onmessage = NOOP; + ws.onerror = NOOP; + ws.onclose = NOOP; + ws.onopen = NOOP; - assert.strictEqual(ws.onmessage, listener); - assert.strictEqual(ws.onclose, listener); - assert.strictEqual(ws.onerror, listener); - assert.strictEqual(ws.onopen, listener); + assert.strictEqual(ws.onmessage, NOOP); + assert.strictEqual(ws.onclose, NOOP); + assert.strictEqual(ws.onerror, NOOP); + assert.strictEqual(ws.onopen, NOOP); }); it('works like the `EventEmitter` interface', (done) => { @@ -1526,43 +1721,40 @@ describe('WebSocket', () => { }); it("doesn't return listeners added with `on`", () => { - const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - ws.on('open', listener); + ws.on('open', NOOP); - assert.deepStrictEqual(ws.listeners('open'), [listener]); + assert.deepStrictEqual(ws.listeners('open'), [NOOP]); assert.strictEqual(ws.onopen, undefined); }); it("doesn't remove listeners added with `on`", () => { - const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - ws.on('close', listener); - ws.onclose = listener; + ws.on('close', NOOP); + ws.onclose = NOOP; let listeners = ws.listeners('close'); assert.strictEqual(listeners.length, 2); - assert.strictEqual(listeners[0], listener); - assert.strictEqual(listeners[1]._listener, listener); + assert.strictEqual(listeners[0], NOOP); + assert.strictEqual(listeners[1]._listener, NOOP); - ws.onclose = listener; + ws.onclose = NOOP; listeners = ws.listeners('close'); assert.strictEqual(listeners.length, 2); - assert.strictEqual(listeners[0], listener); - assert.strictEqual(listeners[1]._listener, listener); + assert.strictEqual(listeners[0], NOOP); + assert.strictEqual(listeners[1]._listener, NOOP); }); it('adds listeners for custom events with `addEventListener`', () => { - const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - ws.addEventListener('foo', listener); - assert.strictEqual(ws.listeners('foo')[0], listener); + ws.addEventListener('foo', NOOP); + assert.strictEqual(ws.listeners('foo')[0], NOOP); // // Fails silently when the `listener` is not a function. @@ -1572,24 +1764,23 @@ describe('WebSocket', () => { }); it('supports the `removeEventListener` method', () => { - const listener = () => {}; const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - ws.addEventListener('message', listener); - ws.addEventListener('open', listener); - ws.addEventListener('foo', listener); + ws.addEventListener('message', NOOP); + ws.addEventListener('open', NOOP); + ws.addEventListener('foo', NOOP); - assert.strictEqual(ws.listeners('message')[0]._listener, listener); - assert.strictEqual(ws.listeners('open')[0]._listener, listener); - assert.strictEqual(ws.listeners('foo')[0], listener); + assert.strictEqual(ws.listeners('message')[0]._listener, NOOP); + assert.strictEqual(ws.listeners('open')[0]._listener, NOOP); + assert.strictEqual(ws.listeners('foo')[0], NOOP); ws.removeEventListener('message', () => {}); - assert.strictEqual(ws.listeners('message')[0]._listener, listener); + assert.strictEqual(ws.listeners('message')[0]._listener, NOOP); - ws.removeEventListener('message', listener); - ws.removeEventListener('open', listener); - ws.removeEventListener('foo', listener); + ws.removeEventListener('message', NOOP); + ws.removeEventListener('open', NOOP); + ws.removeEventListener('foo', NOOP); assert.strictEqual(ws.listenerCount('message'), 0); assert.strictEqual(ws.listenerCount('open'), 0); From 1e6999bb67e86d486da0b61f5bc71ed9f8417e65 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 25 Apr 2019 10:15:12 +0200 Subject: [PATCH 638/669] [major] Drop support for Node.js 6 --- .travis.yml | 1 - appveyor.yml | 1 - lib/websocket.js | 26 +++++++------------------- test/websocket.test.js | 2 -- 4 files changed, 7 insertions(+), 23 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1cf7887f6..96ffe98b5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ node_js: - '11' - '10' - '8' - - '6' os: - linux - osx diff --git a/appveyor.yml b/appveyor.yml index a316bb770..d1f521e60 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,7 +3,6 @@ environment: - nodejs_version: '11' - nodejs_version: '10' - nodejs_version: '8' - - nodejs_version: '6' platform: - x86 matrix: diff --git a/lib/websocket.js b/lib/websocket.js index ce4317f4c..b7b704c84 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -1,12 +1,12 @@ 'use strict'; const EventEmitter = require('events'); -const crypto = require('crypto'); const https = require('https'); const http = require('http'); const net = require('net'); const tls = require('tls'); -const url = require('url'); +const { randomBytes, createHash } = require('crypto'); +const { URL } = require('url'); const PerMessageDeflate = require('./permessage-deflate'); const EventTarget = require('./event-target'); @@ -480,10 +480,7 @@ function initAsClient(websocket, address, protocols, options) { parsedUrl = address; websocket.url = address.href; } else { - // - // The WHATWG URL constructor is not available on Node.js < 6.13.0 - // - parsedUrl = url.URL ? new url.URL(address) : url.parse(address); + parsedUrl = new URL(address); websocket.url = address; } @@ -496,7 +493,7 @@ function initAsClient(websocket, address, protocols, options) { const isSecure = parsedUrl.protocol === 'wss:' || parsedUrl.protocol === 'https:'; const defaultPort = isSecure ? 443 : 80; - const key = crypto.randomBytes(16).toString('base64'); + const key = randomBytes(16).toString('base64'); const get = isSecure ? https.get : http.get; const path = parsedUrl.search ? `${parsedUrl.pathname || '/'}${parsedUrl.search}` @@ -588,9 +585,7 @@ function initAsClient(websocket, address, protocols, options) { req.abort(); - const addr = url.URL - ? new url.URL(location, address) - : url.resolve(address, location); + const addr = new URL(location, address); initAsClient(websocket, addr, protocols, options); } else if (!websocket.emit('unexpected-response', req, res)) { @@ -613,8 +608,7 @@ function initAsClient(websocket, address, protocols, options) { req = websocket._req = null; - const digest = crypto - .createHash('sha1') + const digest = createHash('sha1') .update(key + GUID) .digest('base64'); @@ -676,13 +670,7 @@ function initAsClient(websocket, address, protocols, options) { * @private */ function netConnect(options) { - // - // Override `options.path` only if `options` is a copy of the original options - // object. This is always true on Node.js >= 8 but not on Node.js 6 where - // `options.socketPath` might be `undefined` even if the `socketPath` option - // was originally set. - // - if (options.protocolVersion) options.path = options.socketPath; + options.path = options.socketPath; return net.connect(options); } diff --git a/test/websocket.test.js b/test/websocket.test.js index 6c23a15ea..f1e1b1746 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -37,8 +37,6 @@ describe('WebSocket', () => { }); it('accepts `url.URL` objects as url', function(done) { - if (!url.URL) return this.skip(); - const agent = new CustomAgent(); agent.addRequest = (req, opts) => { From 692d7b47624c5392d78fe4ea1351b5daa17bad94 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 25 Apr 2019 10:21:40 +0200 Subject: [PATCH 639/669] [major] Drop support for `url.Url` in the `WebSocket` constructor --- doc/ws.md | 2 +- lib/websocket.js | 17 ++++++----------- test/websocket.test.js | 34 +++------------------------------- 3 files changed, 10 insertions(+), 43 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index d569d02b9..fe7f482d7 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -234,7 +234,7 @@ This class represents a WebSocket. It extends the `EventEmitter`. ### new WebSocket(address[, protocols][, options]) -- `address` {String|url.Url|url.URL} The URL to which to connect. +- `address` {String|url.URL} The URL to which to connect. - `protocols` {String|Array} The list of subprotocols. - `options` {Object} - `followRedirects` {Boolean} Whether or not to follow redirects. Defaults to diff --git a/lib/websocket.js b/lib/websocket.js index b7b704c84..cc5dc598b 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -36,7 +36,7 @@ class WebSocket extends EventEmitter { /** * Create a new `WebSocket`. * - * @param {(String|url.Url|url.URL)} address The URL to which to connect + * @param {(String|url.URL)} address The URL to which to connect * @param {(String|String[])} protocols The subprotocols * @param {Object} options Connection options */ @@ -427,7 +427,7 @@ module.exports = WebSocket; * Initialize a WebSocket client. * * @param {WebSocket} websocket The client to initialize - * @param {(String|url.Url|url.URL)} address The URL to which to connect + * @param {(String|url.URL)} address The URL to which to connect * @param {String} protocols The subprotocols * @param {Object} options Connection options * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable @@ -476,7 +476,7 @@ function initAsClient(websocket, address, protocols, options) { var parsedUrl; - if (typeof address === 'object' && address.href !== undefined) { + if (address instanceof URL) { parsedUrl = address; websocket.url = address.href; } else { @@ -495,9 +495,6 @@ function initAsClient(websocket, address, protocols, options) { const defaultPort = isSecure ? 443 : 80; const key = randomBytes(16).toString('base64'); const get = isSecure ? https.get : http.get; - const path = parsedUrl.search - ? `${parsedUrl.pathname || '/'}${parsedUrl.search}` - : parsedUrl.pathname || '/'; var perMessageDeflate; opts.createConnection = isSecure ? tlsConnect : netConnect; @@ -515,7 +512,7 @@ function initAsClient(websocket, address, protocols, options) { }, opts.headers ); - opts.path = path; + opts.path = parsedUrl.pathname + parsedUrl.search; opts.timeout = opts.handshakeTimeout; if (opts.perMessageDeflate) { @@ -538,14 +535,12 @@ function initAsClient(websocket, address, protocols, options) { opts.headers.Origin = opts.origin; } } - if (parsedUrl.auth) { - opts.auth = parsedUrl.auth; - } else if (parsedUrl.username || parsedUrl.password) { + if (parsedUrl.username || parsedUrl.password) { opts.auth = `${parsedUrl.username}:${parsedUrl.password}`; } if (isUnixSocket) { - const parts = path.split(':'); + const parts = opts.path.split(':'); opts.socketPath = parts[0]; opts.path = parts[1]; diff --git a/test/websocket.test.js b/test/websocket.test.js index f1e1b1746..b51efc9ba 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -6,8 +6,8 @@ const assert = require('assert'); const crypto = require('crypto'); const https = require('https'); const http = require('http'); -const url = require('url'); const fs = require('fs'); +const { URL } = require('url'); const WebSocket = require('..'); const { GUID, NOOP } = require('../lib/constants'); @@ -25,17 +25,6 @@ describe('WebSocket', () => { ); }); - it('accepts `url.Url` objects as url', (done) => { - const agent = new CustomAgent(); - - agent.addRequest = (req) => { - assert.strictEqual(req.path, '/'); - done(); - }; - - const ws = new WebSocket(url.parse('ws://localhost'), { agent }); - }); - it('accepts `url.URL` objects as url', function(done) { const agent = new CustomAgent(); @@ -45,7 +34,7 @@ describe('WebSocket', () => { done(); }; - const ws = new WebSocket(new url.URL('ws://[::1]'), { agent }); + const ws = new WebSocket(new URL('ws://[::1]'), { agent }); }); describe('options', () => { @@ -2054,7 +2043,7 @@ describe('WebSocket', () => { }); describe('Request headers', () => { - it('adds the authorization header if the url has userinfo (1/2)', (done) => { + it('adds the authorization header if the url has userinfo', (done) => { const agent = new CustomAgent(); const auth = 'test:testpass'; @@ -2069,23 +2058,6 @@ describe('WebSocket', () => { const ws = new WebSocket(`ws://${auth}@localhost`, { agent }); }); - it('adds the authorization header if the url has userinfo (2/2)', (done) => { - const agent = new CustomAgent(); - const auth = 'test:testpass'; - - agent.addRequest = (req) => { - assert.strictEqual( - req._headers.authorization, - `Basic ${Buffer.from(auth).toString('base64')}` - ); - done(); - }; - - const ws = new WebSocket(url.parse(`ws://${auth}@localhost`), { - agent - }); - }); - it('adds custom headers', (done) => { const agent = new CustomAgent(); From ddf0acaae43d5e5cf7f5d838bfd4350699c5406a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 25 Apr 2019 12:04:51 +0200 Subject: [PATCH 640/669] [minor] Use object spread syntax instead of `Object.assign()` --- lib/permessage-deflate.js | 14 ++++--- lib/websocket-server.js | 65 ++++++++++++++--------------- lib/websocket.js | 86 +++++++++++++++++---------------------- 3 files changed, 76 insertions(+), 89 deletions(-) diff --git a/lib/permessage-deflate.js b/lib/permessage-deflate.js index 9c887647f..8501492b9 100644 --- a/lib/permessage-deflate.js +++ b/lib/permessage-deflate.js @@ -335,9 +335,10 @@ class PerMessageDeflate { ? zlib.Z_DEFAULT_WINDOWBITS : this.params[key]; - this._inflate = zlib.createInflateRaw( - Object.assign({}, this._options.zlibInflateOptions, { windowBits }) - ); + this._inflate = zlib.createInflateRaw({ + ...this._options.zlibInflateOptions, + windowBits + }); this._inflate[kPerMessageDeflate] = this; this._inflate[kTotalLength] = 0; this._inflate[kBuffers] = []; @@ -400,9 +401,10 @@ class PerMessageDeflate { ? zlib.Z_DEFAULT_WINDOWBITS : this.params[key]; - this._deflate = zlib.createDeflateRaw( - Object.assign({}, this._options.zlibDeflateOptions, { windowBits }) - ); + this._deflate = zlib.createDeflateRaw({ + ...this._options.zlibDeflateOptions, + windowBits + }); this._deflate[kTotalLength] = 0; this._deflate[kBuffers] = []; diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 9b061d981..bc9aa1e8a 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -1,12 +1,12 @@ 'use strict'; const EventEmitter = require('events'); -const crypto = require('crypto'); -const http = require('http'); +const { createHash } = require('crypto'); +const { createServer, STATUS_CODES } = require('http'); const PerMessageDeflate = require('./permessage-deflate'); -const extension = require('./extension'); const WebSocket = require('./websocket'); +const { format, parse } = require('./extension'); const { GUID } = require('./constants'); const keyRegex = /^[+/0-9A-Za-z]{22}==$/; @@ -40,22 +40,20 @@ class WebSocketServer extends EventEmitter { constructor(options, callback) { super(); - options = Object.assign( - { - maxPayload: 100 * 1024 * 1024, - perMessageDeflate: false, - handleProtocols: null, - clientTracking: true, - verifyClient: null, - noServer: false, - backlog: null, // use default (511 as implemented in net.js) - server: null, - host: null, - path: null, - port: null - }, - options - ); + options = { + maxPayload: 100 * 1024 * 1024, + perMessageDeflate: false, + handleProtocols: null, + clientTracking: true, + verifyClient: null, + noServer: false, + backlog: null, // use default (511 as implemented in net.js) + server: null, + host: null, + path: null, + port: null, + ...options + }; if (options.port == null && !options.server && !options.noServer) { throw new TypeError( @@ -64,8 +62,8 @@ class WebSocketServer extends EventEmitter { } if (options.port != null) { - this._server = http.createServer((req, res) => { - const body = http.STATUS_CODES[426]; + this._server = createServer((req, res) => { + const body = STATUS_CODES[426]; res.writeHead(426, { 'Content-Length': body.length, @@ -208,7 +206,7 @@ class WebSocketServer extends EventEmitter { ); try { - const offers = extension.parse(req.headers['sec-websocket-extensions']); + const offers = parse(req.headers['sec-websocket-extensions']); if (offers[PerMessageDeflate.extensionName]) { perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]); @@ -264,8 +262,7 @@ class WebSocketServer extends EventEmitter { // if (!socket.readable || !socket.writable) return socket.destroy(); - const digest = crypto - .createHash('sha1') + const digest = createHash('sha1') .update(key + GUID) .digest('base64'); @@ -299,7 +296,7 @@ class WebSocketServer extends EventEmitter { if (extensions[PerMessageDeflate.extensionName]) { const params = extensions[PerMessageDeflate.extensionName].params; - const value = extension.format({ + const value = format({ [PerMessageDeflate.extensionName]: [params] }); headers.push(`Sec-WebSocket-Extensions: ${value}`); @@ -376,18 +373,16 @@ function socketOnError() { */ function abortHandshake(socket, code, message, headers) { if (socket.writable) { - message = message || http.STATUS_CODES[code]; - headers = Object.assign( - { - Connection: 'close', - 'Content-type': 'text/html', - 'Content-Length': Buffer.byteLength(message) - }, - headers - ); + message = message || STATUS_CODES[code]; + headers = { + Connection: 'close', + 'Content-type': 'text/html', + 'Content-Length': Buffer.byteLength(message), + ...headers + }; socket.write( - `HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` + + `HTTP/1.1 ${code} ${STATUS_CODES[code]}\r\n` + Object.keys(headers) .map((h) => `${h}: ${headers[h]}`) .join('\r\n') + diff --git a/lib/websocket.js b/lib/websocket.js index cc5dc598b..4bab8fdba 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -9,8 +9,6 @@ const { randomBytes, createHash } = require('crypto'); const { URL } = require('url'); const PerMessageDeflate = require('./permessage-deflate'); -const EventTarget = require('./event-target'); -const extension = require('./extension'); const Receiver = require('./receiver'); const Sender = require('./sender'); const { @@ -21,6 +19,8 @@ const { kWebSocket, NOOP } = require('./constants'); +const { addEventListener, removeEventListener } = require('./event-target'); +const { format, parse } = require('./extension'); const { toBuffer } = require('./buffer-util'); const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']; @@ -339,15 +339,13 @@ class WebSocket extends EventEmitter { return; } - const opts = Object.assign( - { - binary: typeof data !== 'string', - mask: !this._isServer, - compress: true, - fin: true - }, - options - ); + const opts = { + binary: typeof data !== 'string', + mask: !this._isServer, + compress: true, + fin: true, + ...options + }; if (!this._extensions[PerMessageDeflate.extensionName]) { opts.compress = false; @@ -418,8 +416,8 @@ readyStates.forEach((readyState, i) => { }); }); -WebSocket.prototype.addEventListener = EventTarget.addEventListener; -WebSocket.prototype.removeEventListener = EventTarget.removeEventListener; +WebSocket.prototype.addEventListener = addEventListener; +WebSocket.prototype.removeEventListener = removeEventListener; module.exports = WebSocket; @@ -444,28 +442,24 @@ module.exports = WebSocket; * @private */ function initAsClient(websocket, address, protocols, options) { - const opts = Object.assign( - { - protocolVersion: protocolVersions[1], - maxPayload: 100 * 1024 * 1024, - perMessageDeflate: true, - followRedirects: false, - maxRedirects: 10 - }, - options, - { - createConnection: undefined, - socketPath: undefined, - hostname: undefined, - protocol: undefined, - timeout: undefined, - method: undefined, - auth: undefined, - host: undefined, - path: undefined, - port: undefined - } - ); + const opts = { + protocolVersion: protocolVersions[1], + maxPayload: 100 * 1024 * 1024, + perMessageDeflate: true, + followRedirects: false, + maxRedirects: 10, + ...options, + createConnection: undefined, + socketPath: undefined, + hostname: undefined, + protocol: undefined, + timeout: undefined, + method: undefined, + auth: undefined, + host: undefined, + path: undefined, + port: undefined + }; if (!protocolVersions.includes(opts.protocolVersion)) { throw new RangeError( @@ -503,15 +497,13 @@ function initAsClient(websocket, address, protocols, options) { opts.host = parsedUrl.hostname.startsWith('[') ? parsedUrl.hostname.slice(1, -1) : parsedUrl.hostname; - opts.headers = Object.assign( - { - 'Sec-WebSocket-Version': opts.protocolVersion, - 'Sec-WebSocket-Key': key, - Connection: 'Upgrade', - Upgrade: 'websocket' - }, - opts.headers - ); + opts.headers = { + 'Sec-WebSocket-Version': opts.protocolVersion, + 'Sec-WebSocket-Key': key, + Connection: 'Upgrade', + Upgrade: 'websocket', + ...opts.headers + }; opts.path = parsedUrl.pathname + parsedUrl.search; opts.timeout = opts.handshakeTimeout; @@ -521,7 +513,7 @@ function initAsClient(websocket, address, protocols, options) { false, opts.maxPayload ); - opts.headers['Sec-WebSocket-Extensions'] = extension.format({ + opts.headers['Sec-WebSocket-Extensions'] = format({ [PerMessageDeflate.extensionName]: perMessageDeflate.offer() }); } @@ -633,9 +625,7 @@ function initAsClient(websocket, address, protocols, options) { if (perMessageDeflate) { try { - const extensions = extension.parse( - res.headers['sec-websocket-extensions'] - ); + const extensions = parse(res.headers['sec-websocket-extensions']); if (extensions[PerMessageDeflate.extensionName]) { perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]); From 6824e8c04aa1764fd4beee620200cd2ead18ef72 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 25 Apr 2019 12:12:34 +0200 Subject: [PATCH 641/669] [minor] Use `Reflect.apply()` It is faster than `Function.prototype.apply()`. --- lib/sender.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sender.js b/lib/sender.js index 51158b152..c2dbfe2e9 100644 --- a/lib/sender.js +++ b/lib/sender.js @@ -321,7 +321,7 @@ class Sender { const params = this._queue.shift(); this._bufferedBytes -= params[1].length; - params[0].apply(this, params.slice(1)); + Reflect.apply(params[0], this, params.slice(1)); } } From 01bb91d607670d5869ac191ca14f624ebeffcaca Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 25 Apr 2019 14:47:39 +0200 Subject: [PATCH 642/669] [ci] Test on node 12 --- .travis.yml | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 96ffe98b5..853202f67 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - '11' + - '12' - '10' - '8' os: diff --git a/appveyor.yml b/appveyor.yml index d1f521e60..5756a7946 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ environment: matrix: - - nodejs_version: '11' + - nodejs_version: '12' - nodejs_version: '10' - nodejs_version: '8' platform: From 379def63c8d51516b99344bd5a780281853a6808 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 25 Apr 2019 17:14:46 +0200 Subject: [PATCH 643/669] [lint] enable no-var rule --- .eslintrc.yaml | 1 + bench/speed.js | 10 +++--- examples/fileapi/public/app.js | 47 ++++++++++++++++------------- examples/fileapi/public/uploader.js | 15 ++++----- examples/fileapi/server.js | 46 ++++++++++++++-------------- examples/serverstats/server.js | 14 ++++----- lib/buffer-util.js | 10 +++--- lib/event-target.js | 2 +- lib/extension.js | 27 +++++++++-------- lib/permessage-deflate.js | 4 +-- lib/receiver.js | 6 ++-- lib/sender.js | 10 +++--- lib/websocket-server.js | 2 +- lib/websocket.js | 12 ++++---- 14 files changed, 107 insertions(+), 99 deletions(-) diff --git a/.eslintrc.yaml b/.eslintrc.yaml index d370e3d2e..4942593ff 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -12,5 +12,6 @@ plugins: - prettier rules: no-console: off + no-var: error prefer-const: error prettier/prettier: error diff --git a/bench/speed.js b/bench/speed.js index c87dc0b78..32ec0fb81 100644 --- a/bench/speed.js +++ b/bench/speed.js @@ -60,7 +60,7 @@ if (cluster.isMaster) { console.log('Generating %s of test data...', humanSize(largest)); const randomBytes = Buffer.allocUnsafe(largest); - for (var i = 0; i < largest; ++i) { + for (let i = 0; i < largest; ++i) { randomBytes[i] = ~~(Math.random() * 127); } @@ -72,8 +72,8 @@ if (cluster.isMaster) { const ws = new WebSocket(url, { maxPayload: 600 * 1024 * 1024 }); - var roundtrip = 0; - var time; + let roundtrip = 0; + let time; ws.on('error', (err) => { console.error(err.stack); @@ -87,7 +87,7 @@ if (cluster.isMaster) { if (++roundtrip !== roundtrips) return ws.send(data, { binary: useBinary }); - var elapsed = process.hrtime(time); + let elapsed = process.hrtime(time); elapsed = elapsed[0] * 1e9 + elapsed[1]; console.log( @@ -106,7 +106,7 @@ if (cluster.isMaster) { (function run() { if (configs.length === 0) return cluster.worker.disconnect(); - var config = configs.shift(); + const config = configs.shift(); config.push(run); runConfig.apply(null, config); })(); diff --git a/examples/fileapi/public/app.js b/examples/fileapi/public/app.js index 80300db52..73b3cb41a 100644 --- a/examples/fileapi/public/app.js +++ b/examples/fileapi/public/app.js @@ -1,31 +1,36 @@ /* global Uploader */ function onFilesSelected(e) { - var button = e.srcElement; + const files = e.target.files; + let totalFiles = files.length; + + if (!totalFiles) return; + + const button = e.srcElement; button.disabled = true; - var progress = document.querySelector('div#progress'); + + const progress = document.querySelector('div#progress'); progress.innerHTML = '0%'; - var files = e.target.files; - var totalFiles = files.length; - var filesSent = 0; - if (totalFiles) { - var uploader = new Uploader('ws://localhost:8080', function() { - Array.prototype.slice.call(files, 0).forEach(function(file) { - if (file.name === '.') { - --totalFiles; + + let filesSent = 0; + + const uploader = new Uploader('ws://localhost:8080', function() { + Array.prototype.slice.call(files, 0).forEach(function(file) { + if (file.name === '.') { + --totalFiles; + return; + } + uploader.sendFile(file, function(error) { + if (error) { + console.log(error); return; } - uploader.sendFile(file, function(error) { - if (error) { - console.log(error); - return; - } - ++filesSent; - progress.innerHTML = ~~((filesSent / totalFiles) * 100) + '%'; - console.log('Sent: ' + file.name); - }); + ++filesSent; + progress.innerHTML = ~~((filesSent / totalFiles) * 100) + '%'; + console.log('Sent: ' + file.name); }); }); - } + }); + uploader.ondone = function() { uploader.close(); progress.innerHTML = '100% done, ' + totalFiles + ' files sent.'; @@ -33,7 +38,7 @@ function onFilesSelected(e) { } window.onload = function() { - var importButtons = document.querySelectorAll('[type="file"]'); + const importButtons = document.querySelectorAll('[type="file"]'); Array.prototype.slice.call(importButtons, 0).forEach(function(importButton) { importButton.addEventListener('change', onFilesSelected, false); }); diff --git a/examples/fileapi/public/uploader.js b/examples/fileapi/public/uploader.js index 11332d43c..f5dbd8d18 100644 --- a/examples/fileapi/public/uploader.js +++ b/examples/fileapi/public/uploader.js @@ -1,4 +1,3 @@ -/* global WebSocket */ function Uploader(url, cb) { this.ws = new WebSocket(url); if (cb) this.ws.onopen = cb; @@ -6,10 +5,10 @@ function Uploader(url, cb) { this.sending = null; this.sendCallback = null; this.ondone = null; - var self = this; + const self = this; this.ws.onmessage = function(event) { - var data = JSON.parse(event.data); - var callback; + const data = JSON.parse(event.data); + let callback; if (data.event === 'complete') { if (data.path !== self.sending.path) { self.sendQueue = []; @@ -23,7 +22,7 @@ function Uploader(url, cb) { if (callback) callback(); if (self.sendQueue.length === 0 && self.ondone) self.ondone(null); if (self.sendQueue.length > 0) { - var args = self.sendQueue.pop(); + const args = self.sendQueue.pop(); setTimeout(function() { self.sendFile.apply(self, args); }, 0); @@ -33,7 +32,9 @@ function Uploader(url, cb) { self.sending = null; callback = self.sendCallback; self.sendCallback = null; - var error = new Error('Server reported send error for file ' + data.path); + const error = new Error( + 'Server reported send error for file ' + data.path + ); if (callback) callback(error); if (self.ondone) self.ondone(error); } @@ -46,7 +47,7 @@ Uploader.prototype.sendFile = function(file, cb) { this.sendQueue.push(arguments); return; } - var fileData = { name: file.name, path: file.webkitRelativePath }; + const fileData = { name: file.name, path: file.webkitRelativePath }; this.sending = fileData; this.sendCallback = cb; this.ws.send(JSON.stringify(fileData)); diff --git a/examples/fileapi/server.js b/examples/fileapi/server.js index 4bb1b61e6..c90f04c0b 100644 --- a/examples/fileapi/server.js +++ b/examples/fileapi/server.js @@ -1,21 +1,21 @@ -var WebSocketServer = require('../../').Server; -var express = require('express'); -var fs = require('fs'); -var util = require('util'); -var path = require('path'); -var app = express(); -var server = require('http').Server(app); -var events = require('events'); -var ansi = require('ansi'); -var cursor = ansi(process.stdout); +const WebSocketServer = require('../../').Server; +const express = require('express'); +const fs = require('fs'); +const util = require('util'); +const path = require('path'); +const app = express(); +const server = require('http').Server(app); +const events = require('events'); +const ansi = require('ansi'); +const cursor = ansi(process.stdout); function BandwidthSampler(ws, interval) { interval = interval || 2000; - var previousByteCount = 0; - var self = this; - var intervalId = setInterval(function() { - var byteCount = ws.bytesReceived; - var bytesPerSec = (byteCount - previousByteCount) / (interval / 1000); + let previousByteCount = 0; + const self = this; + const intervalId = setInterval(function() { + const byteCount = ws.bytesReceived; + const bytesPerSec = (byteCount - previousByteCount) / (interval / 1000); previousByteCount = byteCount; self.emit('sample', bytesPerSec); }, interval); @@ -28,8 +28,8 @@ util.inherits(BandwidthSampler, events.EventEmitter); function makePathForFile(filePath, prefix, cb) { if (typeof cb !== 'function') throw new Error('callback is required'); filePath = path.dirname(path.normalize(filePath)).replace(/^(\/|\\)+/, ''); - var pieces = filePath.split(/(\\|\/)/); - var incrementalPath = prefix; + const pieces = filePath.split(/(\\|\/)/); + let incrementalPath = prefix; function step(error) { if (error) return cb(error); if (pieces.length === 0) return cb(null, incrementalPath); @@ -45,14 +45,14 @@ function makePathForFile(filePath, prefix, cb) { cursor.eraseData(2).goto(1, 1); app.use(express.static(path.join(__dirname, '/public'))); -var clientId = 0; -var wss = new WebSocketServer({ server: server }); +let clientId = 0; +const wss = new WebSocketServer({ server: server }); wss.on('connection', function(ws) { - var thisId = ++clientId; + const thisId = ++clientId; cursor.goto(1, 4 + thisId).eraseLine(); console.log('Client #%d connected', thisId); - var sampler = new BandwidthSampler(ws); + const sampler = new BandwidthSampler(ws); sampler.on('sample', function(bps) { cursor.goto(1, 4 + thisId).eraseLine(); console.log( @@ -62,8 +62,8 @@ wss.on('connection', function(ws) { ); }); - var filesReceived = 0; - var currentFile = null; + let filesReceived = 0; + let currentFile = null; ws.on('message', function(data) { if (typeof data === 'string') { currentFile = JSON.parse(data); diff --git a/examples/serverstats/server.js b/examples/serverstats/server.js index 7f7f23c0f..5028dc182 100644 --- a/examples/serverstats/server.js +++ b/examples/serverstats/server.js @@ -1,14 +1,14 @@ -var WebSocketServer = require('../../').Server; -var express = require('express'); -var path = require('path'); -var app = express(); -var server = require('http').createServer(); +const WebSocketServer = require('../../').Server; +const express = require('express'); +const path = require('path'); +const app = express(); +const server = require('http').createServer(); app.use(express.static(path.join(__dirname, '/public'))); -var wss = new WebSocketServer({ server: server }); +const wss = new WebSocketServer({ server: server }); wss.on('connection', function(ws) { - var id = setInterval(function() { + const id = setInterval(function() { ws.send(JSON.stringify(process.memoryUsage()), function() { /* ignore errors */ }); diff --git a/lib/buffer-util.js b/lib/buffer-util.js index 8fcb88f4a..02973ae39 100644 --- a/lib/buffer-util.js +++ b/lib/buffer-util.js @@ -15,9 +15,9 @@ function concat(list, totalLength) { if (list.length === 1) return list[0]; const target = Buffer.allocUnsafe(totalLength); - var offset = 0; + let offset = 0; - for (var i = 0; i < list.length; i++) { + for (let i = 0; i < list.length; i++) { const buf = list[i]; buf.copy(target, offset); offset += buf.length; @@ -37,7 +37,7 @@ function concat(list, totalLength) { * @public */ function _mask(source, mask, output, offset, length) { - for (var i = 0; i < length; i++) { + for (let i = 0; i < length; i++) { output[offset + i] = source[i] ^ mask[i & 3]; } } @@ -52,7 +52,7 @@ function _mask(source, mask, output, offset, length) { function _unmask(buffer, mask) { // Required until https://github.com/nodejs/node/issues/9006 is resolved. const length = buffer.length; - for (var i = 0; i < length; i++) { + for (let i = 0; i < length; i++) { buffer[i] ^= mask[i & 3]; } } @@ -85,7 +85,7 @@ function toBuffer(data) { if (Buffer.isBuffer(data)) return data; - var buf; + let buf; if (data instanceof ArrayBuffer) { buf = Buffer.from(data); diff --git a/lib/event-target.js b/lib/event-target.js index 44c81d991..c060d63a8 100644 --- a/lib/event-target.js +++ b/lib/event-target.js @@ -159,7 +159,7 @@ const EventTarget = { removeEventListener(method, listener) { const listeners = this.listeners(method); - for (var i = 0; i < listeners.length; i++) { + for (let i = 0; i < listeners.length; i++) { if (listeners[i] === listener || listeners[i]._listener === listener) { this.removeListener(method, listeners[i]); } diff --git a/lib/extension.js b/lib/extension.js index 47096b973..fd250726f 100644 --- a/lib/extension.js +++ b/lib/extension.js @@ -50,16 +50,17 @@ function parse(header) { if (header === undefined || header === '') return offers; - var params = {}; - var mustUnescape = false; - var isEscaping = false; - var inQuotes = false; - var extensionName; - var paramName; - var start = -1; - var end = -1; - - for (var i = 0; i < header.length; i++) { + let params = {}; + let mustUnescape = false; + let isEscaping = false; + let inQuotes = false; + let extensionName; + let paramName; + let start = -1; + let end = -1; + let i = 0; + + for (; i < header.length; i++) { const code = header.charCodeAt(i); if (extensionName === undefined) { @@ -146,7 +147,7 @@ function parse(header) { } if (end === -1) end = i; - var value = header.slice(start, end); + let value = header.slice(start, end); if (mustUnescape) { value = value.replace(/\\/g, ''); mustUnescape = false; @@ -198,14 +199,14 @@ function parse(header) { function format(extensions) { return Object.keys(extensions) .map((extension) => { - var configurations = extensions[extension]; + let configurations = extensions[extension]; if (!Array.isArray(configurations)) configurations = [configurations]; return configurations .map((params) => { return [extension] .concat( Object.keys(params).map((k) => { - var values = params[k]; + let values = params[k]; if (!Array.isArray(values)) values = [values]; return values .map((v) => (v === true ? k : `${k}=${v}`)) diff --git a/lib/permessage-deflate.js b/lib/permessage-deflate.js index 8501492b9..29881b313 100644 --- a/lib/permessage-deflate.js +++ b/lib/permessage-deflate.js @@ -233,7 +233,7 @@ class PerMessageDeflate { normalizeParams(configurations) { configurations.forEach((params) => { Object.keys(params).forEach((key) => { - var value = params[key]; + let value = params[key]; if (value.length > 1) { throw new Error(`Parameter "${key}" must have only a single value`); @@ -431,7 +431,7 @@ class PerMessageDeflate { return; } - var data = bufferUtil.concat( + let data = bufferUtil.concat( this._deflate[kBuffers], this._deflate[kTotalLength] ); diff --git a/lib/receiver.js b/lib/receiver.js index 0a8d76df7..28ffca0f5 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -117,7 +117,7 @@ class Receiver extends Writable { * @private */ startLoop(cb) { - var err; + let err; this._loop = true; do { @@ -320,7 +320,7 @@ class Receiver extends Writable { * @private */ getData(cb) { - var data = EMPTY_BUFFER; + let data = EMPTY_BUFFER; if (this._payloadLength) { if (this._bufferedBytes < this._payloadLength) { @@ -400,7 +400,7 @@ class Receiver extends Writable { this._fragments = []; if (this._opcode === 2) { - var data; + let data; if (this._binaryType === 'nodebuffer') { data = concat(fragments, messageLength); diff --git a/lib/sender.js b/lib/sender.js index c2dbfe2e9..088cc2a7a 100644 --- a/lib/sender.js +++ b/lib/sender.js @@ -44,8 +44,8 @@ class Sender { */ static frame(data, options) { const merge = options.mask && options.readOnly; - var offset = options.mask ? 6 : 2; - var payloadLength = data.length; + let offset = options.mask ? 6 : 2; + let payloadLength = data.length; if (data.length >= 65536) { offset += 8; @@ -98,7 +98,7 @@ class Sender { * @public */ close(code, data, mask, cb) { - var buf; + let buf; if (code === undefined) { buf = EMPTY_BUFFER; @@ -236,8 +236,8 @@ class Sender { send(data, options, cb) { const buf = toBuffer(data); const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; - var opcode = options.binary ? 2 : 1; - var rsv1 = options.compress; + let opcode = options.binary ? 2 : 1; + let rsv1 = options.compress; if (this._firstFragment) { this._firstFragment = false; diff --git a/lib/websocket-server.js b/lib/websocket-server.js index bc9aa1e8a..6835a21ed 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -274,7 +274,7 @@ class WebSocketServer extends EventEmitter { ]; const ws = new WebSocket(null); - var protocol = req.headers['sec-websocket-protocol']; + let protocol = req.headers['sec-websocket-protocol']; if (protocol) { protocol = protocol.trim().split(/ *, */); diff --git a/lib/websocket.js b/lib/websocket.js index 4bab8fdba..070b3a0eb 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -391,7 +391,7 @@ readyStates.forEach((readyState, i) => { */ get() { const listeners = this.listeners(method); - for (var i = 0; i < listeners.length; i++) { + for (let i = 0; i < listeners.length; i++) { if (listeners[i]._listener) return listeners[i]._listener; } @@ -405,7 +405,7 @@ readyStates.forEach((readyState, i) => { */ set(listener) { const listeners = this.listeners(method); - for (var i = 0; i < listeners.length; i++) { + for (let i = 0; i < listeners.length; i++) { // // Remove only the listeners added via `addEventListener`. // @@ -468,7 +468,7 @@ function initAsClient(websocket, address, protocols, options) { ); } - var parsedUrl; + let parsedUrl; if (address instanceof URL) { parsedUrl = address; @@ -489,7 +489,7 @@ function initAsClient(websocket, address, protocols, options) { const defaultPort = isSecure ? 443 : 80; const key = randomBytes(16).toString('base64'); const get = isSecure ? https.get : http.get; - var perMessageDeflate; + let perMessageDeflate; opts.createConnection = isSecure ? tlsConnect : netConnect; opts.defaultPort = opts.defaultPort || defaultPort; @@ -538,7 +538,7 @@ function initAsClient(websocket, address, protocols, options) { opts.path = parts[1]; } - var req = (websocket._req = get(opts)); + let req = (websocket._req = get(opts)); if (opts.timeout) { req.on('timeout', () => { @@ -606,7 +606,7 @@ function initAsClient(websocket, address, protocols, options) { const serverProt = res.headers['sec-websocket-protocol']; const protList = (protocols || '').split(/, */); - var protError; + let protError; if (!protocols && serverProt) { protError = 'Server sent a subprotocol but none was requested'; From 3eff077ce81ad7950dcedbd6b57ac25ec3bafb63 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 25 Apr 2019 17:19:48 +0200 Subject: [PATCH 644/669] [example] Remove unmaintained fileapi example --- examples/fileapi/.gitignore | 1 - examples/fileapi/package.json | 10 --- examples/fileapi/public/app.js | 45 ---------- examples/fileapi/public/index.html | 22 ----- examples/fileapi/public/uploader.js | 59 ------------ examples/fileapi/server.js | 134 ---------------------------- 6 files changed, 271 deletions(-) delete mode 100644 examples/fileapi/.gitignore delete mode 100644 examples/fileapi/package.json delete mode 100644 examples/fileapi/public/app.js delete mode 100644 examples/fileapi/public/index.html delete mode 100644 examples/fileapi/public/uploader.js delete mode 100644 examples/fileapi/server.js diff --git a/examples/fileapi/.gitignore b/examples/fileapi/.gitignore deleted file mode 100644 index dcd575688..000000000 --- a/examples/fileapi/.gitignore +++ /dev/null @@ -1 +0,0 @@ -uploaded diff --git a/examples/fileapi/package.json b/examples/fileapi/package.json deleted file mode 100644 index 770b5ebc1..000000000 --- a/examples/fileapi/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "author": "", - "name": "fileapi", - "version": "0.0.0", - "repository": "websockets/ws", - "dependencies": { - "express": "~4.16.3", - "ansi": "https://github.com/einaros/ansi.js/tarball/master" - } -} diff --git a/examples/fileapi/public/app.js b/examples/fileapi/public/app.js deleted file mode 100644 index 73b3cb41a..000000000 --- a/examples/fileapi/public/app.js +++ /dev/null @@ -1,45 +0,0 @@ -/* global Uploader */ -function onFilesSelected(e) { - const files = e.target.files; - let totalFiles = files.length; - - if (!totalFiles) return; - - const button = e.srcElement; - button.disabled = true; - - const progress = document.querySelector('div#progress'); - progress.innerHTML = '0%'; - - let filesSent = 0; - - const uploader = new Uploader('ws://localhost:8080', function() { - Array.prototype.slice.call(files, 0).forEach(function(file) { - if (file.name === '.') { - --totalFiles; - return; - } - uploader.sendFile(file, function(error) { - if (error) { - console.log(error); - return; - } - ++filesSent; - progress.innerHTML = ~~((filesSent / totalFiles) * 100) + '%'; - console.log('Sent: ' + file.name); - }); - }); - }); - - uploader.ondone = function() { - uploader.close(); - progress.innerHTML = '100% done, ' + totalFiles + ' files sent.'; - }; -} - -window.onload = function() { - const importButtons = document.querySelectorAll('[type="file"]'); - Array.prototype.slice.call(importButtons, 0).forEach(function(importButton) { - importButton.addEventListener('change', onFilesSelected, false); - }); -}; diff --git a/examples/fileapi/public/index.html b/examples/fileapi/public/index.html deleted file mode 100644 index 0d463dd5a..000000000 --- a/examples/fileapi/public/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - -

This example will upload an entire directory tree to the node.js server via a fast and persistent WebSocket connection.

-

Note that the example is Chrome only for now.

-

- Upload status: -
Please select a directory to upload.
- - diff --git a/examples/fileapi/public/uploader.js b/examples/fileapi/public/uploader.js deleted file mode 100644 index f5dbd8d18..000000000 --- a/examples/fileapi/public/uploader.js +++ /dev/null @@ -1,59 +0,0 @@ -function Uploader(url, cb) { - this.ws = new WebSocket(url); - if (cb) this.ws.onopen = cb; - this.sendQueue = []; - this.sending = null; - this.sendCallback = null; - this.ondone = null; - const self = this; - this.ws.onmessage = function(event) { - const data = JSON.parse(event.data); - let callback; - if (data.event === 'complete') { - if (data.path !== self.sending.path) { - self.sendQueue = []; - self.sending = null; - self.sendCallback = null; - throw new Error('Got message for wrong file!'); - } - self.sending = null; - callback = self.sendCallback; - self.sendCallback = null; - if (callback) callback(); - if (self.sendQueue.length === 0 && self.ondone) self.ondone(null); - if (self.sendQueue.length > 0) { - const args = self.sendQueue.pop(); - setTimeout(function() { - self.sendFile.apply(self, args); - }, 0); - } - } else if (data.event === 'error') { - self.sendQueue = []; - self.sending = null; - callback = self.sendCallback; - self.sendCallback = null; - const error = new Error( - 'Server reported send error for file ' + data.path - ); - if (callback) callback(error); - if (self.ondone) self.ondone(error); - } - }; -} - -Uploader.prototype.sendFile = function(file, cb) { - if (this.ws.readyState !== WebSocket.OPEN) throw new Error('Not connected'); - if (this.sending) { - this.sendQueue.push(arguments); - return; - } - const fileData = { name: file.name, path: file.webkitRelativePath }; - this.sending = fileData; - this.sendCallback = cb; - this.ws.send(JSON.stringify(fileData)); - this.ws.send(file); -}; - -Uploader.prototype.close = function() { - this.ws.close(); -}; diff --git a/examples/fileapi/server.js b/examples/fileapi/server.js deleted file mode 100644 index c90f04c0b..000000000 --- a/examples/fileapi/server.js +++ /dev/null @@ -1,134 +0,0 @@ -const WebSocketServer = require('../../').Server; -const express = require('express'); -const fs = require('fs'); -const util = require('util'); -const path = require('path'); -const app = express(); -const server = require('http').Server(app); -const events = require('events'); -const ansi = require('ansi'); -const cursor = ansi(process.stdout); - -function BandwidthSampler(ws, interval) { - interval = interval || 2000; - let previousByteCount = 0; - const self = this; - const intervalId = setInterval(function() { - const byteCount = ws.bytesReceived; - const bytesPerSec = (byteCount - previousByteCount) / (interval / 1000); - previousByteCount = byteCount; - self.emit('sample', bytesPerSec); - }, interval); - ws.on('close', function() { - clearInterval(intervalId); - }); -} -util.inherits(BandwidthSampler, events.EventEmitter); - -function makePathForFile(filePath, prefix, cb) { - if (typeof cb !== 'function') throw new Error('callback is required'); - filePath = path.dirname(path.normalize(filePath)).replace(/^(\/|\\)+/, ''); - const pieces = filePath.split(/(\\|\/)/); - let incrementalPath = prefix; - function step(error) { - if (error) return cb(error); - if (pieces.length === 0) return cb(null, incrementalPath); - incrementalPath += '/' + pieces.shift(); - fs.access(incrementalPath, function(err) { - if (err) fs.mkdir(incrementalPath, step); - else process.nextTick(step); - }); - } - step(); -} - -cursor.eraseData(2).goto(1, 1); -app.use(express.static(path.join(__dirname, '/public'))); - -let clientId = 0; -const wss = new WebSocketServer({ server: server }); -wss.on('connection', function(ws) { - const thisId = ++clientId; - cursor.goto(1, 4 + thisId).eraseLine(); - console.log('Client #%d connected', thisId); - - const sampler = new BandwidthSampler(ws); - sampler.on('sample', function(bps) { - cursor.goto(1, 4 + thisId).eraseLine(); - console.log( - 'WebSocket #%d incoming bandwidth: %d MB/s', - thisId, - Math.round(bps / (1024 * 1024)) - ); - }); - - let filesReceived = 0; - let currentFile = null; - ws.on('message', function(data) { - if (typeof data === 'string') { - currentFile = JSON.parse(data); - // note: a real-world app would want to sanity check the data - } else { - if (currentFile == null) return; - makePathForFile( - currentFile.path, - path.join(__dirname, '/uploaded'), - function(error, path) { - if (error) { - console.log(error); - ws.send( - JSON.stringify({ - event: 'error', - path: currentFile.path, - message: error.message - }) - ); - return; - } - fs.writeFile(path + '/' + currentFile.name, data, function(error) { - if (error) { - console.log(error); - ws.send( - JSON.stringify({ - event: 'error', - path: currentFile.path, - message: error.message - }) - ); - return; - } - ++filesReceived; - // console.log('received %d bytes long file, %s', data.length, currentFile.path); - ws.send( - JSON.stringify({ event: 'complete', path: currentFile.path }) - ); - currentFile = null; - }); - } - ); - } - }); - - ws.on('close', function() { - cursor.goto(1, 4 + thisId).eraseLine(); - console.log( - 'Client #%d disconnected. %d files received.', - thisId, - filesReceived - ); - }); - - ws.on('error', function(e) { - cursor.goto(1, 4 + thisId).eraseLine(); - console.log('Client #%d error: %s', thisId, e.message); - }); -}); - -fs.mkdir(path.join(__dirname, '/uploaded'), function() { - // ignore errors, most likely means directory exists - console.log('Uploaded files will be saved to %s/uploaded.', __dirname); - console.log('Remember to wipe this directory if you upload lots and lots.'); - server.listen(8080, function() { - console.log('Listening on http://localhost:8080'); - }); -}); From 4a9a7732ebdc40a56e02f52766ab3a4ac76862c6 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 25 Apr 2019 18:04:07 +0200 Subject: [PATCH 645/669] [example] Rename serverstats to server-stats --- examples/{serverstats => server-stats}/package.json | 0 examples/{serverstats => server-stats}/public/index.html | 0 examples/{serverstats => server-stats}/server.js | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename examples/{serverstats => server-stats}/package.json (100%) rename examples/{serverstats => server-stats}/public/index.html (100%) rename examples/{serverstats => server-stats}/server.js (100%) diff --git a/examples/serverstats/package.json b/examples/server-stats/package.json similarity index 100% rename from examples/serverstats/package.json rename to examples/server-stats/package.json diff --git a/examples/serverstats/public/index.html b/examples/server-stats/public/index.html similarity index 100% rename from examples/serverstats/public/index.html rename to examples/server-stats/public/index.html diff --git a/examples/serverstats/server.js b/examples/server-stats/server.js similarity index 100% rename from examples/serverstats/server.js rename to examples/server-stats/server.js From aca385806934f5cf7d27ff8a3063619731f0c36c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 25 Apr 2019 18:34:41 +0200 Subject: [PATCH 646/669] [example] Clean up examples --- examples/express-session-parse/index.js | 21 +++--- examples/express-session-parse/package.json | 6 +- examples/express-session-parse/public/app.js | 37 ++++++---- examples/server-stats/{server.js => index.js} | 19 +++-- examples/server-stats/package.json | 2 +- examples/server-stats/public/index.html | 72 +++++++++++++------ 6 files changed, 103 insertions(+), 54 deletions(-) rename examples/server-stats/{server.js => index.js} (71%) diff --git a/examples/express-session-parse/index.js b/examples/express-session-parse/index.js index 5250b2cb6..761987dc9 100644 --- a/examples/express-session-parse/index.js +++ b/examples/express-session-parse/index.js @@ -25,7 +25,7 @@ const sessionParser = session({ app.use(express.static('public')); app.use(sessionParser); -app.post('/login', (req, res) => { +app.post('/login', function(req, res) { // // "Log in" user and set userId to session. // @@ -36,10 +36,11 @@ app.post('/login', (req, res) => { res.send({ result: 'OK', message: 'Session updated' }); }); -app.delete('/logout', (request, response) => { +app.delete('/logout', function(request, response) { console.log('Destroying session'); - request.session.destroy(); - response.send({ result: 'OK', message: 'Session destroyed' }); + request.session.destroy(function() { + response.send({ result: 'OK', message: 'Session destroyed' }); + }); }); // @@ -48,7 +49,7 @@ app.delete('/logout', (request, response) => { const server = http.createServer(app); const wss = new WebSocket.Server({ - verifyClient: (info, done) => { + verifyClient: function(info, done) { console.log('Parsing session from request...'); sessionParser(info.req, {}, () => { console.log('Session is parsed!'); @@ -63,16 +64,18 @@ const wss = new WebSocket.Server({ server }); -wss.on('connection', (ws, req) => { - ws.on('message', (message) => { +wss.on('connection', function(ws, request) { + ws.on('message', function(message) { // // Here we can now use session parameters. // - console.log(`WS message ${message} from user ${req.session.userId}`); + console.log(`WS message ${message} from user ${request.session.userId}`); }); }); // // Start the server. // -server.listen(8080, () => console.log('Listening on http://localhost:8080')); +server.listen(8080, function() { + console.log('Listening on http://localhost:8080'); +}); diff --git a/examples/express-session-parse/package.json b/examples/express-session-parse/package.json index 5390e7e0a..f8cd22e30 100644 --- a/examples/express-session-parse/package.json +++ b/examples/express-session-parse/package.json @@ -4,8 +4,8 @@ "version": "0.0.0", "repository": "websockets/ws", "dependencies": { - "express": "~4.16.3", - "express-session": "~1.15.6", - "uuid": "~3.3.2" + "express": "^4.16.4", + "express-session": "^1.16.1", + "uuid": "^3.3.2" } } diff --git a/examples/express-session-parse/public/app.js b/examples/express-session-parse/public/app.js index 916fafd41..56abc591a 100644 --- a/examples/express-session-parse/public/app.js +++ b/examples/express-session-parse/public/app.js @@ -1,46 +1,55 @@ -/* global fetch, WebSocket, location */ -(() => { +(function() { const messages = document.querySelector('#messages'); const wsButton = document.querySelector('#wsButton'); const logout = document.querySelector('#logout'); const login = document.querySelector('#login'); - const showMessage = (message) => { + function showMessage(message) { messages.textContent += `\n${message}`; messages.scrollTop = messages.scrollHeight; - }; + } - const handleResponse = (response) => { + function handleResponse(response) { return response.ok ? response.json().then((data) => JSON.stringify(data, null, 2)) : Promise.reject(new Error('Unexpected response')); - }; + } - login.onclick = () => { + login.onclick = function() { fetch('/login', { method: 'POST', credentials: 'same-origin' }) .then(handleResponse) .then(showMessage) - .catch((err) => showMessage(err.message)); + .catch(function(err) { + showMessage(err.message); + }); }; - logout.onclick = () => { + logout.onclick = function() { fetch('/logout', { method: 'DELETE', credentials: 'same-origin' }) .then(handleResponse) .then(showMessage) - .catch((err) => showMessage(err.message)); + .catch(function(err) { + showMessage(err.message); + }); }; let ws; - wsButton.onclick = () => { + wsButton.onclick = function() { if (ws) { ws.onerror = ws.onopen = ws.onclose = null; ws.close(); } ws = new WebSocket(`ws://${location.host}`); - ws.onerror = () => showMessage('WebSocket error'); - ws.onopen = () => showMessage('WebSocket connection established'); - ws.onclose = () => showMessage('WebSocket connection closed'); + ws.onerror = function() { + showMessage('WebSocket error'); + }; + ws.onopen = function() { + showMessage('WebSocket connection established'); + }; + ws.onclose = function() { + showMessage('WebSocket connection closed'); + }; }; })(); diff --git a/examples/server-stats/server.js b/examples/server-stats/index.js similarity index 71% rename from examples/server-stats/server.js rename to examples/server-stats/index.js index 5028dc182..85e3ddb8c 100644 --- a/examples/server-stats/server.js +++ b/examples/server-stats/index.js @@ -1,26 +1,33 @@ -const WebSocketServer = require('../../').Server; +'use strict'; + const express = require('express'); const path = require('path'); -const app = express(); -const server = require('http').createServer(); +const { createServer } = require('http'); + +const WebSocket = require('../../'); +const app = express(); app.use(express.static(path.join(__dirname, '/public'))); -const wss = new WebSocketServer({ server: server }); +const server = createServer(app); +const wss = new WebSocket.Server({ server }); + wss.on('connection', function(ws) { const id = setInterval(function() { ws.send(JSON.stringify(process.memoryUsage()), function() { - /* ignore errors */ + // + // Ignore errors. + // }); }, 100); console.log('started client interval'); + ws.on('close', function() { console.log('stopping client interval'); clearInterval(id); }); }); -server.on('request', app); server.listen(8080, function() { console.log('Listening on http://localhost:8080'); }); diff --git a/examples/server-stats/package.json b/examples/server-stats/package.json index dbc86aff0..20e202913 100644 --- a/examples/server-stats/package.json +++ b/examples/server-stats/package.json @@ -4,6 +4,6 @@ "version": "0.0.0", "repository": "websockets/ws", "dependencies": { - "express": "~4.16.3" + "express": "^4.16.4" } } diff --git a/examples/server-stats/public/index.html b/examples/server-stats/public/index.html index 24d84e120..a82815af6 100644 --- a/examples/server-stats/public/index.html +++ b/examples/server-stats/public/index.html @@ -1,33 +1,63 @@ - + + + Server stats + + +

Server stats

+ + + + + + + + + + + + + + + + + + + + + + + + +
Memory usage
RSS
Heap total
Heap used
External
- - - Server Stats
- RSS:

- Heap total:

- Heap used:

From 993b0cd46c39456ffc3bb772a94f9b0b38cc7fc5 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 26 Apr 2019 12:24:58 +0200 Subject: [PATCH 647/669] [minor] Make `extension.parse()` use null-prototype objects --- lib/extension.js | 16 +++---- test/extension.test.js | 84 +++++++++++++++++---------------- test/permessage-deflate.test.js | 12 +++-- 3 files changed, 60 insertions(+), 52 deletions(-) diff --git a/lib/extension.js b/lib/extension.js index fd250726f..87a421329 100644 --- a/lib/extension.js +++ b/lib/extension.js @@ -34,8 +34,8 @@ const tokenChars = [ * @private */ function push(dest, name, elem) { - if (Object.prototype.hasOwnProperty.call(dest, name)) dest[name].push(elem); - else dest[name] = [elem]; + if (dest[name] === undefined) dest[name] = [elem]; + else dest[name].push(elem); } /** @@ -46,11 +46,11 @@ function push(dest, name, elem) { * @public */ function parse(header) { - const offers = {}; + const offers = Object.create(null); if (header === undefined || header === '') return offers; - let params = {}; + let params = Object.create(null); let mustUnescape = false; let isEscaping = false; let inQuotes = false; @@ -77,7 +77,7 @@ function parse(header) { const name = header.slice(start, end); if (code === 0x2c) { push(offers, name, params); - params = {}; + params = Object.create(null); } else { extensionName = name; } @@ -100,7 +100,7 @@ function parse(header) { push(params, header.slice(start, end), true); if (code === 0x2c) { push(offers, extensionName, params); - params = {}; + params = Object.create(null); extensionName = undefined; } @@ -155,7 +155,7 @@ function parse(header) { push(params, paramName, value); if (code === 0x2c) { push(offers, extensionName, params); - params = {}; + params = Object.create(null); extensionName = undefined; } @@ -174,7 +174,7 @@ function parse(header) { if (end === -1) end = i; const token = header.slice(start, end); if (extensionName === undefined) { - push(offers, token, {}); + push(offers, token, params); } else { if (paramName === undefined) { push(params, token, true); diff --git a/test/extension.test.js b/test/extension.test.js index 1179f05c3..6cfbc1b23 100644 --- a/test/extension.test.js +++ b/test/extension.test.js @@ -2,79 +2,83 @@ const assert = require('assert'); -const extension = require('../lib/extension'); +const { format, parse } = require('../lib/extension'); describe('extension', () => { describe('parse', () => { it('returns an empty object if the argument is `undefined`', () => { - assert.deepStrictEqual(extension.parse(), {}); - assert.deepStrictEqual(extension.parse(''), {}); + assert.deepStrictEqual(parse(), { __proto__: null }); + assert.deepStrictEqual(parse(''), { __proto__: null }); }); it('parses a single extension', () => { - const extensions = extension.parse('foo'); - - assert.deepStrictEqual(extensions, { foo: [{}] }); + assert.deepStrictEqual(parse('foo'), { + foo: [{ __proto__: null }], + __proto__: null + }); }); it('parses params', () => { - const extensions = extension.parse('foo;bar;baz=1;bar=2'); - - assert.deepStrictEqual(extensions, { - foo: [{ bar: [true, '2'], baz: ['1'] }] + assert.deepStrictEqual(parse('foo;bar;baz=1;bar=2'), { + foo: [{ bar: [true, '2'], baz: ['1'], __proto__: null }], + __proto__: null }); }); it('parses multiple extensions', () => { - const extensions = extension.parse('foo,bar;baz,foo;baz'); - - assert.deepStrictEqual(extensions, { - foo: [{}, { baz: [true] }], - bar: [{ baz: [true] }] + assert.deepStrictEqual(parse('foo,bar;baz,foo;baz'), { + foo: [{ __proto__: null }, { baz: [true], __proto__: null }], + bar: [{ baz: [true], __proto__: null }], + __proto__: null }); }); it('parses quoted params', () => { - assert.deepStrictEqual(extension.parse('foo;bar="hi"'), { - foo: [{ bar: ['hi'] }] + assert.deepStrictEqual(parse('foo;bar="hi"'), { + foo: [{ bar: ['hi'], __proto__: null }], + __proto__: null }); - assert.deepStrictEqual(extension.parse('foo;bar="\\0"'), { - foo: [{ bar: ['0'] }] + assert.deepStrictEqual(parse('foo;bar="\\0"'), { + foo: [{ bar: ['0'], __proto__: null }], + __proto__: null }); - assert.deepStrictEqual(extension.parse('foo;bar="b\\a\\z"'), { - foo: [{ bar: ['baz'] }] + assert.deepStrictEqual(parse('foo;bar="b\\a\\z"'), { + foo: [{ bar: ['baz'], __proto__: null }], + __proto__: null }); - assert.deepStrictEqual(extension.parse('foo;bar="b\\az";bar'), { - foo: [{ bar: ['baz', true] }] + assert.deepStrictEqual(parse('foo;bar="b\\az";bar'), { + foo: [{ bar: ['baz', true], __proto__: null }], + __proto__: null }); assert.throws( - () => extension.parse('foo;bar="baz"qux'), + () => parse('foo;bar="baz"qux'), /^SyntaxError: Unexpected character at index 13$/ ); assert.throws( - () => extension.parse('foo;bar="baz" qux'), + () => parse('foo;bar="baz" qux'), /^SyntaxError: Unexpected character at index 14$/ ); }); it('works with names that match `Object.prototype` property names', () => { - const parse = extension.parse; - assert.deepStrictEqual(parse('hasOwnProperty, toString'), { - hasOwnProperty: [{}], - toString: [{}] + hasOwnProperty: [{ __proto__: null }], + toString: [{ __proto__: null }], + __proto__: null }); assert.deepStrictEqual(parse('foo;constructor'), { - foo: [{ constructor: [true] }] + foo: [{ constructor: [true], __proto__: null }], + __proto__: null }); }); it('ignores the optional white spaces', () => { const header = 'foo; bar\t; \tbaz=1\t ; bar="1"\t\t, \tqux\t ;norf '; - assert.deepStrictEqual(extension.parse(header), { - foo: [{ bar: [true, '1'], baz: ['1'] }], - qux: [{ norf: [true] }] + assert.deepStrictEqual(parse(header), { + foo: [{ bar: [true, '1'], baz: ['1'], __proto__: null }], + qux: [{ norf: [true], __proto__: null }], + __proto__: null }); }); @@ -91,7 +95,7 @@ describe('extension', () => { ['foo;bar=""', 9] ].forEach((element) => { assert.throws( - () => extension.parse(element[0]), + () => parse(element[0]), new RegExp( `^SyntaxError: Unexpected character at index ${element[1]}$` ) @@ -107,7 +111,7 @@ describe('extension', () => { ['foo;bar= ', 8] ].forEach((element) => { assert.throws( - () => extension.parse(element[0]), + () => parse(element[0]), new RegExp( `^SyntaxError: Unexpected character at index ${element[1]}$` ) @@ -133,7 +137,7 @@ describe('extension', () => { ['foo;bar="\\\\"', 10] ].forEach((element) => { assert.throws( - () => extension.parse(element[0]), + () => parse(element[0]), new RegExp( `^SyntaxError: Unexpected character at index ${element[1]}$` ) @@ -152,7 +156,7 @@ describe('extension', () => { 'foo;bar="1\\' ].forEach((header) => { assert.throws( - () => extension.parse(header), + () => parse(header), /^SyntaxError: Unexpected end of input$/ ); }); @@ -161,19 +165,19 @@ describe('extension', () => { describe('format', () => { it('formats a single extension', () => { - const extensions = extension.format({ foo: {} }); + const extensions = format({ foo: {} }); assert.strictEqual(extensions, 'foo'); }); it('formats params', () => { - const extensions = extension.format({ foo: { bar: [true, 2], baz: 1 } }); + const extensions = format({ foo: { bar: [true, 2], baz: 1 } }); assert.strictEqual(extensions, 'foo; bar; bar=2; baz=1'); }); it('formats multiple extensions', () => { - const extensions = extension.format({ + const extensions = format({ foo: [{}, { baz: true }], bar: { baz: true } }); diff --git a/test/permessage-deflate.test.js b/test/permessage-deflate.test.js index 6af5bf56f..b50fd6eda 100644 --- a/test/permessage-deflate.test.js +++ b/test/permessage-deflate.test.js @@ -120,7 +120,8 @@ describe('PerMessageDeflate', () => { server_no_context_takeover: true, client_no_context_takeover: true, server_max_window_bits: 10, - client_max_window_bits: 11 + client_max_window_bits: 11, + __proto__: null } ); }); @@ -145,7 +146,8 @@ describe('PerMessageDeflate', () => { server_no_context_takeover: true, client_no_context_takeover: true, server_max_window_bits: 12, - client_max_window_bits: 11 + client_max_window_bits: 11, + __proto__: null } ); }); @@ -162,7 +164,8 @@ describe('PerMessageDeflate', () => { assert.deepStrictEqual( perMessageDeflate.accept(extensions['permessage-deflate']), { - server_max_window_bits: 11 + server_max_window_bits: 11, + __proto__: null } ); }); @@ -259,7 +262,8 @@ describe('PerMessageDeflate', () => { server_no_context_takeover: true, client_no_context_takeover: true, server_max_window_bits: 10, - client_max_window_bits: 11 + client_max_window_bits: 11, + __proto__: null } ); }); From 1b85466a993561d6c7db793ab6d6df849164235e Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 26 Apr 2019 14:20:43 +0200 Subject: [PATCH 648/669] [minor] Use `crypto.randomFillSync()` to generate the masking key --- lib/sender.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/sender.js b/lib/sender.js index 088cc2a7a..a9bcef201 100644 --- a/lib/sender.js +++ b/lib/sender.js @@ -1,12 +1,14 @@ 'use strict'; -const { randomBytes } = require('crypto'); +const { randomFillSync } = require('crypto'); const PerMessageDeflate = require('./permessage-deflate'); const { EMPTY_BUFFER } = require('./constants'); const { isValidStatusCode } = require('./validation'); const { mask: applyMask, toBuffer } = require('./buffer-util'); +const mask = Buffer.alloc(4); + /** * HyBi Sender implementation. */ @@ -71,7 +73,7 @@ class Sender { if (!options.mask) return [target, data]; - const mask = randomBytes(4); + randomFillSync(mask, 0, 4); target[1] |= 0x80; target[offset - 4] = mask[0]; From 092a822a41eb22f6d6745c18bc29b9c40715680f Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 30 Apr 2019 18:01:37 +0200 Subject: [PATCH 649/669] [dist] 7.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cbdbd3d59..c45c1e845 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "6.2.1", + "version": "7.0.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From fbc077bddbe9ae9aff26f8a2ba20d0ababe93bfc Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Thu, 2 May 2019 18:15:24 +0200 Subject: [PATCH 650/669] [test] Do not use the deprecated `outgoingMessage._headers` property --- test/websocket.test.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/test/websocket.test.js b/test/websocket.test.js index b51efc9ba..90ff96124 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -2049,7 +2049,7 @@ describe('WebSocket', () => { agent.addRequest = (req) => { assert.strictEqual( - req._headers.authorization, + req.getHeader('authorization'), `Basic ${Buffer.from(auth).toString('base64')}` ); done(); @@ -2062,7 +2062,7 @@ describe('WebSocket', () => { const agent = new CustomAgent(); agent.addRequest = (req) => { - assert.strictEqual(req._headers.cookie, 'foo=bar'); + assert.strictEqual(req.getHeader('cookie'), 'foo=bar'); done(); }; @@ -2083,7 +2083,7 @@ describe('WebSocket', () => { for (const [url, host] of variants) { const ws = new WebSocket(url, options); - assert.strictEqual(ws._req._headers.host, host); + assert.strictEqual(ws._req.getHeader('host'), host); } }); @@ -2091,7 +2091,7 @@ describe('WebSocket', () => { const agent = new CustomAgent(); agent.addRequest = (req) => { - assert.strictEqual(req._headers.origin, undefined); + assert.strictEqual(req.getHeader('origin'), undefined); done(); }; @@ -2102,7 +2102,7 @@ describe('WebSocket', () => { const agent = new CustomAgent(); agent.addRequest = (req) => { - assert.strictEqual(req._headers.origin, 'https://example.com:8000'); + assert.strictEqual(req.getHeader('origin'), 'https://example.com:8000'); done(); }; @@ -2117,7 +2117,7 @@ describe('WebSocket', () => { agent.addRequest = (req) => { assert.strictEqual( - req._headers['sec-websocket-origin'], + req.getHeader('sec-websocket-origin'), 'https://example.com:8000' ); done(); @@ -2137,7 +2137,7 @@ describe('WebSocket', () => { agent.addRequest = (req) => { assert.strictEqual( - req._headers['sec-websocket-extensions'], + req.getHeader('sec-websocket-extensions'), 'permessage-deflate; client_max_window_bits' ); done(); @@ -2150,7 +2150,10 @@ describe('WebSocket', () => { const agent = new CustomAgent(); agent.addRequest = (req) => { - assert.strictEqual(req._headers['sec-websocket-extensions'], undefined); + assert.strictEqual( + req.getHeader('sec-websocket-extensions'), + undefined + ); done(); }; @@ -2169,7 +2172,7 @@ describe('WebSocket', () => { ' client_max_window_bits'; agent.addRequest = (req) => { - assert.strictEqual(req._headers['sec-websocket-extensions'], value); + assert.strictEqual(req.getHeader('sec-websocket-extensions'), value); done(); }; From 995c527c87d0d4833d8093c18dcfa2e4a41d9582 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 4 May 2019 09:10:00 +0200 Subject: [PATCH 651/669] [test] Enable --throw-deprecation --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c45c1e845..acd975880 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,8 @@ "lib/*.js" ], "scripts": { - "test": "npm run lint && nyc --reporter=html --reporter=text mocha test/*.test.js", - "integration": "npm run lint && mocha test/*.integration.js", + "test": "npm run lint && nyc --reporter=html --reporter=text mocha --throw-deprecation test/*.test.js", + "integration": "npm run lint && mocha --throw-deprecation test/*.integration.js", "lint": "eslint --ignore-path .gitignore . && prettier --check --ignore-path .gitignore \"**/*.{json,md,yaml,yml}\"" }, "dependencies": { From 8050d5f395146e060140defa2119132c9f0f3ba2 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 12 May 2019 19:20:54 +0200 Subject: [PATCH 652/669] [lint] Enable quotes rule --- .eslintrc.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 4942593ff..70f32c8d4 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -15,3 +15,7 @@ rules: no-var: error prefer-const: error prettier/prettier: error + quotes: + - error + - single + - avoidEscape: true From 911bb6fb1145c9b09ae4b1cf4c993b3b8ce9e1cf Mon Sep 17 00:00:00 2001 From: Rishabh Kohli Date: Tue, 14 May 2019 18:34:20 +0530 Subject: [PATCH 653/669] [minor] Fix typo in JSDoc comment (#1565) --- lib/websocket-server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 6835a21ed..4fc5bad24 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -25,7 +25,7 @@ class WebSocketServer extends EventEmitter { * connections * @param {Boolean} options.clientTracking Specifies whether or not to track * clients - * @param {Function} options.handleProtocols An hook to handle protocols + * @param {Function} options.handleProtocols A hook to handle protocols * @param {String} options.host The hostname where to bind the server * @param {Number} options.maxPayload The maximum allowed message size * @param {Boolean} options.noServer Enable no server mode @@ -34,7 +34,7 @@ class WebSocketServer extends EventEmitter { * permessage-deflate * @param {Number} options.port The port where to bind the server * @param {http.Server} options.server A pre-created HTTP/S server to use - * @param {Function} options.verifyClient An hook to reject connections + * @param {Function} options.verifyClient A hook to reject connections * @param {Function} callback A listener for the `listening` event */ constructor(options, callback) { From 36ef7570786d423038627877034cb866da8f8901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82=C4=99biowski?= Date: Sat, 8 Jun 2019 11:55:55 +0200 Subject: [PATCH 654/669] [doc] Add missing dependency in code snippet (#1581) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1dc16754a..aa19e3920 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,7 @@ server.listen(8080); ```js const http = require('http'); const WebSocket = require('ws'); +const url = require('url'); const server = http.createServer(); const wss1 = new WebSocket.Server({ noServer: true }); From e9e8ba53bc259bf7e34195df05d348717c185542 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 15 Jun 2019 11:53:23 +0200 Subject: [PATCH 655/669] [pkg] Update eslint-config-prettier to version 5.0.0 (#1588) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index acd975880..b7128330b 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "bufferutil": "^4.0.1", "coveralls": "^3.0.3", "eslint": "^5.16.0", - "eslint-config-prettier": "^4.1.0", + "eslint-config-prettier": "^5.0.0", "eslint-plugin-prettier": "^3.0.1", "mocha": "^6.1.3", "nyc": "^14.0.0", From b08617910daa3dfcd7fd79cdb004ef383d9a35a1 Mon Sep 17 00:00:00 2001 From: Holger Koser Date: Sat, 15 Jun 2019 22:08:01 +0200 Subject: [PATCH 656/669] [fix] Allow to disable sending the SNI extension (#1587) --- lib/websocket.js | 6 +++++- test/websocket.test.js | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/websocket.js b/lib/websocket.js index 070b3a0eb..e17d6c1a7 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -668,7 +668,11 @@ function netConnect(options) { */ function tlsConnect(options) { options.path = undefined; - options.servername = options.servername || options.host; + + if (!options.servername && options.servername !== '') { + options.servername = options.host; + } + return tls.connect(options); } diff --git a/test/websocket.test.js b/test/websocket.test.js index 90ff96124..647550216 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -6,6 +6,7 @@ const assert = require('assert'); const crypto = require('crypto'); const https = require('https'); const http = require('http'); +const tls = require('tls'); const fs = require('fs'); const { URL } = require('url'); @@ -2040,6 +2041,18 @@ describe('WebSocket', () => { }); }); }).timeout(4000); + + it('allows to disable sending the SNI extension', (done) => { + const original = tls.connect; + + tls.connect = (options) => { + assert.strictEqual(options.servername, ''); + tls.connect = original; + done(); + }; + + const ws = new WebSocket('wss://127.0.0.1', { servername: '' }); + }); }); describe('Request headers', () => { From a0af76440eb11c7a567581141554585e3a6e7409 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 17 Jun 2019 18:03:49 +0200 Subject: [PATCH 657/669] [test] Use the correct value for the Content-Length header --- test/websocket.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/websocket.test.js b/test/websocket.test.js index 647550216..66a6e9f22 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -498,7 +498,7 @@ describe('WebSocket', () => { `HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` + 'Connection: close\r\n' + 'Content-type: text/html\r\n' + - `Content-Length: ${http.STATUS_CODES[401].length}\r\n` + + 'Content-Length: 3\r\n' + '\r\n' + 'foo' ); @@ -530,7 +530,7 @@ describe('WebSocket', () => { `HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` + 'Connection: close\r\n' + 'Content-type: text/html\r\n' + - `Content-Length: ${http.STATUS_CODES[401].length}\r\n` + + 'Content-Length: 3\r\n' + '\r\n' + 'foo' ); From 38d3bf24a0caa2f504361926582ed679a22e08f8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 17 Jun 2019 18:13:43 +0200 Subject: [PATCH 658/669] [dist] 7.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b7128330b..a80f99f79 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "7.0.0", + "version": "7.0.1", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From d9b5562e2b9be191abb5c58a15ae006bd5c347fd Mon Sep 17 00:00:00 2001 From: Amit Novick <43674238+amitnovick@users.noreply.github.com> Date: Wed, 19 Jun 2019 08:39:53 +0300 Subject: [PATCH 659/669] [doc] Improve server broadcast example (#1590) --- README.md | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index aa19e3920..c5b41b265 100644 --- a/README.md +++ b/README.md @@ -249,23 +249,35 @@ server.listen(8080); ### Server broadcast +A client WebSocket broadcasting to all connected WebSocket clients, including +itself. + ```js const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 }); -// Broadcast to all. -wss.broadcast = function broadcast(data) { - wss.clients.forEach(function each(client) { - if (client.readyState === WebSocket.OPEN) { - client.send(data); - } +wss.on('connection', function connection(ws) { + ws.on('message', function incoming(data) { + wss.clients.forEach(function each(client) { + if (client.readyState === WebSocket.OPEN) { + client.send(data); + } + }); }); -}; +}); +``` + +A client WebSocket broadcasting to every other connected WebSocket clients, +excluding itself. + +```js +const WebSocket = require('ws'); + +const wss = new WebSocket.Server({ port: 8080 }); wss.on('connection', function connection(ws) { ws.on('message', function incoming(data) { - // Broadcast to everyone else. wss.clients.forEach(function each(client) { if (client !== ws && client.readyState === WebSocket.OPEN) { client.send(data); From 47e7d64f2a78f160b7d5dab0086f2c5444faf613 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" <23040076+greenkeeper[bot]@users.noreply.github.com> Date: Sat, 22 Jun 2019 10:52:31 +0200 Subject: [PATCH 660/669] [pkg] Update eslint to version 6.0.0 (#1595) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a80f99f79..7492b9152 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "benchmark": "^2.1.4", "bufferutil": "^4.0.1", "coveralls": "^3.0.3", - "eslint": "^5.16.0", + "eslint": "^6.0.0", "eslint-config-prettier": "^5.0.0", "eslint-plugin-prettier": "^3.0.1", "mocha": "^6.1.3", From c62ea9fb5ab5991d47ac4d2632a6f93b6617098c Mon Sep 17 00:00:00 2001 From: Goktug Yilmaz Date: Sun, 23 Jun 2019 12:59:34 -0400 Subject: [PATCH 661/669] [doc] Improve comment in code snippet (#1597) --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c5b41b265..0699f5160 100644 --- a/README.md +++ b/README.md @@ -394,9 +394,10 @@ const WebSocket = require('ws'); function heartbeat() { clearTimeout(this.pingTimeout); - // Use `WebSocket#terminate()` and not `WebSocket#close()`. Delay should be - // equal to the interval at which your server sends out pings plus a - // conservative assumption of the latency. + // Use `WebSocket#terminate()`, which immediately destroys the connection, + // instead of `WebSocket#close()`, which waits for the close timer. + // Delay should be equal to the interval at which your server + // sends out pings plus a conservative assumption of the latency. this.pingTimeout = setTimeout(() => { this.terminate(); }, 30000 + 1000); From db1486492a00f5cd6b43bed06773668d54994593 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" <23040076+greenkeeper[bot]@users.noreply.github.com> Date: Tue, 25 Jun 2019 21:27:50 +0200 Subject: [PATCH 662/669] [pkg] Update eslint-config-prettier to version 6.0.0 (#1599) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7492b9152..6d77b5b22 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "bufferutil": "^4.0.1", "coveralls": "^3.0.3", "eslint": "^6.0.0", - "eslint-config-prettier": "^5.0.0", + "eslint-config-prettier": "^6.0.0", "eslint-plugin-prettier": "^3.0.1", "mocha": "^6.1.3", "nyc": "^14.0.0", From dbacf582a5907af75c6d424f017af39096ab0b93 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 5 Jul 2019 21:43:33 +0200 Subject: [PATCH 663/669] Revert "[minor] Remove unneeded `if` statement" This reverts commit 297f56df79f6bf4757465fb6b59884faf5b75337. --- lib/websocket.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index e17d6c1a7..cd8929bdc 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -901,6 +901,8 @@ function socketOnError() { this.removeListener('error', socketOnError); this.on('error', NOOP); - websocket.readyState = WebSocket.CLOSING; - this.destroy(); + if (websocket) { + websocket.readyState = WebSocket.CLOSING; + this.destroy(); + } } From a49a827b4eabb4c720993ee1a0c45b737cc148bb Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 8 Jul 2019 17:48:57 +0200 Subject: [PATCH 664/669] [feature] Add utility to wrap a `WebSocket` in a `Duplex` stream (#1589) Fixes #113 --- README.md | 19 +- doc/ws.md | 18 +- index.js | 1 + lib/stream.js | 150 ++++++++++ test/create-websocket-stream.test.js | 414 +++++++++++++++++++++++++++ 5 files changed, 598 insertions(+), 4 deletions(-) create mode 100644 lib/stream.js create mode 100644 test/create-websocket-stream.test.js diff --git a/README.md b/README.md index 0699f5160..db7b6280d 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ can use one of the many wrappers available on npm, like - [Multiple servers sharing a single HTTP/S server](#multiple-servers-sharing-a-single-https-server) - [Server broadcast](#server-broadcast) - [echo.websocket.org demo](#echowebsocketorg-demo) + - [Use the Node.js streams API](#use-the-nodejs-streams-api) - [Other examples](#other-examples) - [FAQ](#faq) - [How to get the IP address of the client?](#how-to-get-the-ip-address-of-the-client) @@ -69,7 +70,8 @@ necessarily need to have a C++ compiler installed on your machine. ## API docs -See [`/doc/ws.md`](./doc/ws.md) for Node.js-like docs for the ws classes. +See [`/doc/ws.md`](./doc/ws.md) for Node.js-like documentation of ws classes and +utility functions. ## WebSocket compression @@ -314,6 +316,21 @@ ws.on('message', function incoming(data) { }); ``` +### Use the Node.js streams API + +```js +const WebSocket = require('ws'); + +const ws = new WebSocket('wss://echo.websocket.org/', { + origin: 'https://websocket.org' +}); + +const duplex = WebSocket.createWebSocketStream(ws, { encoding: 'utf8' }); + +duplex.pipe(process.stdout); +process.stdin.pipe(duplex); +``` + ### Other examples For a full example with a browser client communicating with a ws server, see the diff --git a/doc/ws.md b/doc/ws.md index fe7f482d7..77b3e42e0 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -43,6 +43,7 @@ - [websocket.send(data[, options][, callback])](#websocketsenddata-options-callback) - [websocket.terminate()](#websocketterminate) - [websocket.url](#websocketurl) +- [WebSocket.createWebSocketStream(websocket[, options])](#websocketcreatewebsocketstreamwebsocket-options) ## Class: WebSocket.Server @@ -463,11 +464,22 @@ Forcibly close the connection. The URL of the WebSocket server. Server clients don't have this attribute. +## WebSocket.createWebSocketStream(websocket[, options]) + +- `websocket` {WebSocket} A `WebSocket` object. +- `options` {Object} [Options][duplex-options] to pass to the `Duplex` + constructor. + +Returns a `Duplex` stream that allows to use the Node.js streams API on top of a +given `WebSocket`. + [concurrency-limit]: https://github.com/websockets/ws/issues/1202 -[permessage-deflate]: - https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19 -[zlib-options]: https://nodejs.org/api/zlib.html#zlib_class_options +[duplex-options]: + https://nodejs.org/api/stream.html#stream_new_stream_duplex_options [http.request()]: https://nodejs.org/api/http.html#http_http_request_options_callback [https.request()]: https://nodejs.org/api/https.html#https_https_request_options_callback +[permessage-deflate]: + https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19 +[zlib-options]: https://nodejs.org/api/zlib.html#zlib_class_options diff --git a/index.js b/index.js index b8d6be1c9..722c78676 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ const WebSocket = require('./lib/websocket'); +WebSocket.createWebSocketStream = require('./lib/stream'); WebSocket.Server = require('./lib/websocket-server'); WebSocket.Receiver = require('./lib/receiver'); WebSocket.Sender = require('./lib/sender'); diff --git a/lib/stream.js b/lib/stream.js new file mode 100644 index 000000000..adfcf3193 --- /dev/null +++ b/lib/stream.js @@ -0,0 +1,150 @@ +'use strict'; + +const { Duplex } = require('stream'); + +/** + * Emits the `'close'` event on a stream. + * + * @param {stream.Duplex} The stream. + * @private + */ +function emitClose(stream) { + stream.emit('close'); +} + +/** + * The listener of the `'end'` event. + * + * @private + */ +function duplexOnEnd() { + if (!this.destroyed && this._writableState.finished) { + this.destroy(); + } +} + +/** + * The listener of the `'error'` event. + * + * @private + */ +function duplexOnError(err) { + this.removeListener('error', duplexOnError); + this.destroy(); + if (this.listenerCount('error') === 0) { + // Do not suppress the throwing behavior. + this.emit('error', err); + } +} + +/** + * Wraps a `WebSocket` in a duplex stream. + * + * @param {WebSocket} ws The `WebSocket` to wrap + * @param {Object} options The options for the `Duplex` constructor + * @return {stream.Duplex} The duplex stream + * @public + */ +function createWebSocketStream(ws, options) { + let resumeOnReceiverDrain = true; + + function receiverOnDrain() { + if (resumeOnReceiverDrain) ws._socket.resume(); + } + + if (ws.readyState === ws.CONNECTING) { + ws.once('open', function open() { + ws._receiver.removeAllListeners('drain'); + ws._receiver.on('drain', receiverOnDrain); + }); + } else { + ws._receiver.removeAllListeners('drain'); + ws._receiver.on('drain', receiverOnDrain); + } + + const duplex = new Duplex({ + ...options, + autoDestroy: false, + emitClose: false, + objectMode: false, + readableObjectMode: false, + writableObjectMode: false + }); + + ws.on('message', function message(msg) { + if (!duplex.push(msg)) { + resumeOnReceiverDrain = false; + ws._socket.pause(); + } + }); + + ws.once('error', function error(err) { + duplex.destroy(err); + }); + + ws.once('close', function close() { + if (duplex.destroyed) return; + + duplex.push(null); + }); + + duplex._destroy = function(err, callback) { + if (ws.readyState === ws.CLOSED) { + callback(err); + process.nextTick(emitClose, duplex); + return; + } + + ws.once('close', function close() { + callback(err); + process.nextTick(emitClose, duplex); + }); + ws.terminate(); + }; + + duplex._final = function(callback) { + if (ws.readyState === ws.CONNECTING) { + ws.once('open', function open() { + duplex._final(callback); + }); + return; + } + + if (ws._socket._writableState.finished) { + if (duplex._readableState.endEmitted) duplex.destroy(); + callback(); + } else { + ws._socket.once('finish', function finish() { + // `duplex` is not destroyed here because the `'end'` event will be + // emitted on `duplex` after this `'finish'` event. The EOF signaling + // `null` chunk is, in fact, pushed when the WebSocket emits `'close'`. + callback(); + }); + ws.close(); + } + }; + + duplex._read = function() { + if (ws.readyState === ws.OPEN && !resumeOnReceiverDrain) { + resumeOnReceiverDrain = true; + if (!ws._receiver._writableState.needDrain) ws._socket.resume(); + } + }; + + duplex._write = function(chunk, encoding, callback) { + if (ws.readyState === ws.CONNECTING) { + ws.once('open', function open() { + duplex._write(chunk, encoding, callback); + }); + return; + } + + ws.send(chunk, callback); + }; + + duplex.on('end', duplexOnEnd); + duplex.on('error', duplexOnError); + return duplex; +} + +module.exports = createWebSocketStream; diff --git a/test/create-websocket-stream.test.js b/test/create-websocket-stream.test.js new file mode 100644 index 000000000..6aa388e53 --- /dev/null +++ b/test/create-websocket-stream.test.js @@ -0,0 +1,414 @@ +'use strict'; + +const assert = require('assert'); +const EventEmitter = require('events'); +const { Duplex } = require('stream'); +const { randomBytes } = require('crypto'); + +const createWebSocketStream = require('../lib/stream'); +const Sender = require('../lib/sender'); +const WebSocket = require('..'); + +describe('createWebSocketStream', () => { + it('is exposed as a property of the `WebSocket` class', () => { + assert.strictEqual(WebSocket.createWebSocketStream, createWebSocketStream); + }); + + it('returns a `Duplex` stream', () => { + const duplex = createWebSocketStream(new EventEmitter()); + + assert.ok(duplex instanceof Duplex); + }); + + it('passes the options object to the `Duplex` constructor', (done) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + const duplex = createWebSocketStream(ws, { + allowHalfOpen: false, + encoding: 'utf8' + }); + + duplex.on('data', (chunk) => { + assert.strictEqual(chunk, 'hi'); + + duplex.on('close', () => { + wss.close(done); + }); + }); + }); + + wss.on('connection', (ws) => { + ws.send(Buffer.from('hi')); + ws.close(); + }); + }); + + describe('The returned stream', () => { + it('buffers writes if `readyState` is `CONNECTING`', (done) => { + const chunk = randomBytes(1024); + const wss = new WebSocket.Server({ port: 0 }, () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + + assert.strictEqual(ws.readyState, 0); + + const duplex = createWebSocketStream(ws); + + duplex.write(chunk); + }); + + wss.on('connection', (ws) => { + ws.on('message', (message) => { + ws.on('close', (code, reason) => { + assert.ok(message.equals(chunk)); + assert.strictEqual(code, 1005); + assert.strictEqual(reason, ''); + wss.close(done); + }); + }); + + ws.close(); + }); + }); + + it('errors if a write occurs when `readyState` is `CLOSING`', (done) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + const duplex = createWebSocketStream(ws); + + duplex.on('error', (err) => { + assert.ok(duplex.destroyed); + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'WebSocket is not open: readyState 2 (CLOSING)' + ); + + duplex.on('close', () => { + wss.close(done); + }); + }); + + ws.on('open', () => { + ws._receiver.on('conclude', () => { + duplex.write('hi'); + }); + }); + }); + + wss.on('connection', (ws) => { + ws.close(); + }); + }); + + it('errors if a write occurs when `readyState` is `CLOSED`', (done) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + const duplex = createWebSocketStream(ws); + + duplex.on('error', (err) => { + assert.ok(duplex.destroyed); + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'WebSocket is not open: readyState 3 (CLOSED)' + ); + + duplex.on('close', () => { + wss.close(done); + }); + }); + + ws.on('close', () => { + duplex.write('hi'); + }); + }); + + wss.on('connection', (ws) => { + ws.close(); + }); + }); + + it('does not error if `_final()` is called while connecting', (done) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + + assert.strictEqual(ws.readyState, 0); + + const duplex = createWebSocketStream(ws); + + duplex.on('close', () => { + wss.close(done); + }); + + duplex.resume(); + duplex.end(); + }); + }); + + it('reemits errors', (done) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + const duplex = createWebSocketStream(ws); + + duplex.on('error', (err) => { + assert.ok(err instanceof RangeError); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid opcode 5' + ); + + duplex.on('close', () => { + wss.close(done); + }); + }); + }); + + wss.on('connection', (ws) => { + ws._socket.write(Buffer.from([0x85, 0x00])); + }); + }); + + it("does not suppress the throwing behavior of 'error' events", (done) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + createWebSocketStream(ws); + }); + + wss.on('connection', (ws) => { + ws._socket.write(Buffer.from([0x85, 0x00])); + }); + + assert.strictEqual(process.listenerCount('uncaughtException'), 1); + + const [listener] = process.listeners('uncaughtException'); + + process.removeAllListeners('uncaughtException'); + process.once('uncaughtException', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual( + err.message, + 'Invalid WebSocket frame: invalid opcode 5' + ); + + process.on('uncaughtException', listener); + wss.close(done); + }); + }); + + it("is destroyed after 'end' and 'finish' are emitted (1/2)", (done) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const events = []; + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + const duplex = createWebSocketStream(ws); + + duplex.on('end', () => { + events.push('end'); + assert.ok(duplex.destroyed); + }); + + duplex.on('close', () => { + assert.deepStrictEqual(events, ['finish', 'end']); + wss.close(done); + }); + + duplex.on('finish', () => { + events.push('finish'); + assert.ok(!duplex.destroyed); + assert.ok(duplex.readable); + + duplex.resume(); + }); + + ws.on('close', () => { + duplex.end(); + }); + }); + + wss.on('connection', (ws) => { + ws.send('foo'); + ws.close(); + }); + }); + + it("is destroyed after 'end' and 'finish' are emitted (2/2)", (done) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const events = []; + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + const duplex = createWebSocketStream(ws); + + duplex.on('end', () => { + events.push('end'); + assert.ok(!duplex.destroyed); + assert.ok(duplex.writable); + + duplex.end(); + }); + + duplex.on('close', () => { + assert.deepStrictEqual(events, ['end', 'finish']); + wss.close(done); + }); + + duplex.on('finish', () => { + events.push('finish'); + assert.ok(duplex.destroyed); + }); + + duplex.resume(); + }); + + wss.on('connection', (ws) => { + ws.close(); + }); + }); + + it('handles backpressure (1/3)', (done) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + // eslint-disable-next-line no-unused-vars + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + }); + + wss.on('connection', (ws) => { + const duplex = createWebSocketStream(ws); + + duplex.resume(); + + duplex.on('drain', () => { + duplex.on('close', () => { + wss.close(done); + }); + + duplex.end(); + }); + + const chunk = randomBytes(1024); + let ret; + + do { + ret = duplex.write(chunk); + } while (ret !== false); + }); + }); + + it('handles backpressure (2/3)', (done) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const called = []; + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + const duplex = createWebSocketStream(ws); + const read = duplex._read; + + duplex._read = () => { + called.push('read'); + assert.ok(ws._receiver._writableState.needDrain); + read(); + assert.ok(ws._socket.isPaused()); + }; + + ws.on('open', () => { + ws._socket.on('pause', () => { + duplex.resume(); + }); + + ws._receiver.on('drain', () => { + called.push('drain'); + assert.ok(!ws._socket.isPaused()); + }); + + const list = Sender.frame(randomBytes(16 * 1024), { + fin: true, + rsv1: false, + opcode: 0x02, + mask: false, + readOnly: false + }); + + // This hack is used because there is no guarantee that more than + // 16KiB will be sent as a single TCP packet. + ws._socket.push(Buffer.concat(list)); + }); + + duplex.on('resume', duplex.end); + duplex.on('close', () => { + assert.deepStrictEqual(called, ['read', 'drain']); + wss.close(done); + }); + }); + }); + + it('handles backpressure (3/3)', (done) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const called = []; + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + const duplex = createWebSocketStream(ws); + + const read = duplex._read; + + duplex._read = () => { + called.push('read'); + assert.ok(!ws._receiver._writableState.needDrain); + read(); + assert.ok(!ws._socket.isPaused()); + }; + + ws.on('open', () => { + ws._receiver.on('drain', () => { + called.push('drain'); + assert.ok(ws._socket.isPaused()); + duplex.resume(); + }); + + const list = Sender.frame(randomBytes(16 * 1024), { + fin: true, + rsv1: false, + opcode: 0x02, + mask: false, + readOnly: false + }); + + ws._socket.push(Buffer.concat(list)); + }); + + duplex.on('resume', duplex.end); + duplex.on('close', () => { + assert.deepStrictEqual(called, ['drain', 'read']); + wss.close(done); + }); + }); + }); + + it('can be destroyed (1/2)', (done) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const error = new Error('Oops'); + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + const duplex = createWebSocketStream(ws); + + duplex.on('error', (err) => { + assert.strictEqual(err, error); + + duplex.on('close', () => { + wss.close(done); + }); + }); + + ws.on('open', () => { + duplex.destroy(error); + }); + }); + }); + + it('can be destroyed (2/2)', (done) => { + const wss = new WebSocket.Server({ port: 0 }, () => { + const ws = new WebSocket(`ws://localhost:${wss.address().port}`); + const duplex = createWebSocketStream(ws); + + duplex.on('close', () => { + wss.close(done); + }); + + ws.on('open', () => { + duplex.destroy(); + }); + }); + }); + }); +}); From dd42c8bc3ac215668bbfe1788b3dab197219df2a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 8 Jul 2019 17:54:05 +0200 Subject: [PATCH 665/669] [dist] 7.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6d77b5b22..3ae5cfadc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "7.0.1", + "version": "7.1.0", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 364126620bf3fe4e6e0042d3b74ec53b5ccbbb08 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sun, 14 Jul 2019 14:34:26 +0200 Subject: [PATCH 666/669] [minor] Throw an error on invalid usage Throw an error if the same `server` option is used for multiple WebSocket servers at the same time. --- lib/websocket-server.js | 11 +++++++++++ test/websocket-server.test.js | 31 ++++++++++++++++++++++++------- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 4fc5bad24..7e992c2a2 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -10,6 +10,7 @@ const { format, parse } = require('./extension'); const { GUID } = require('./constants'); const keyRegex = /^[+/0-9A-Za-z]{22}==$/; +const kUsedByWebSocketServer = Symbol('kUsedByWebSocketServer'); /** * Class representing a WebSocket server. @@ -82,6 +83,14 @@ class WebSocketServer extends EventEmitter { } if (this._server) { + if (this._server[kUsedByWebSocketServer]) { + throw new Error( + 'The HTTP/S server is already being used by another WebSocket server' + ); + } else { + this._server[kUsedByWebSocketServer] = true; + } + this._removeListeners = addListeners(this._server, { listening: this.emit.bind(this, 'listening'), error: this.emit.bind(this, 'error'), @@ -135,6 +144,8 @@ class WebSocketServer extends EventEmitter { const server = this._server; if (server) { + server[kUsedByWebSocketServer] = false; + this._removeListeners(); this._removeListeners = this._server = null; diff --git a/test/websocket-server.test.js b/test/websocket-server.test.js index 5d152ab73..b9c460ce4 100644 --- a/test/websocket-server.test.js +++ b/test/websocket-server.test.js @@ -17,11 +17,22 @@ describe('WebSocketServer', () => { assert.throws(() => new WebSocket.Server()); }); - it('throws an error if no port or server is specified', () => { - assert.throws(() => new WebSocket.Server({})); - }); - describe('options', () => { + it('throws an error if no `port` or `server` option is specified', () => { + assert.throws(() => new WebSocket.Server({})); + }); + + it('throws an error if the server is already being used', () => { + const server = http.createServer(); + + new WebSocket.Server({ server }); + + assert.throws( + () => new WebSocket.Server({ server }), + /^Error: The HTTP\/S server is already being used by another WebSocket server$/ + ); + }); + it('exposes options passed to constructor', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { assert.strictEqual(wss.options.port, 0); @@ -225,15 +236,21 @@ describe('WebSocketServer', () => { it('cleans event handlers on precreated server', (done) => { const server = http.createServer(); - const wss = new WebSocket.Server({ server }); + const wss1 = new WebSocket.Server({ server }); server.listen(0, () => { - wss.close(() => { + wss1.close(() => { assert.strictEqual(server.listenerCount('listening'), 0); assert.strictEqual(server.listenerCount('upgrade'), 0); assert.strictEqual(server.listenerCount('error'), 0); - server.close(done); + // Check that no error is thrown if `server` is resued now as there + // are no other `WebSocketServer`s using it. + const wss2 = new WebSocket.Server({ server }); + + wss2.close(() => { + server.close(done); + }); }); }); }); From cf467dbed312e7dc6e7318c08666b72ea3213b76 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 19 Jul 2019 16:41:37 +0200 Subject: [PATCH 667/669] [dist] 7.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3ae5cfadc..48ec9a2f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ws", - "version": "7.1.0", + "version": "7.1.1", "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", "keywords": [ "HyBi", From 91b5173c776881dc1a229ac5b0ba928dcb775602 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 22 Jul 2019 17:11:12 +0200 Subject: [PATCH 668/669] [minor] Set the flag to detect invalid API usage only when needed --- lib/websocket-server.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/websocket-server.js b/lib/websocket-server.js index 7e992c2a2..057a12be5 100644 --- a/lib/websocket-server.js +++ b/lib/websocket-server.js @@ -79,18 +79,17 @@ class WebSocketServer extends EventEmitter { callback ); } else if (options.server) { - this._server = options.server; - } - - if (this._server) { - if (this._server[kUsedByWebSocketServer]) { + if (options.server[kUsedByWebSocketServer]) { throw new Error( 'The HTTP/S server is already being used by another WebSocket server' ); - } else { - this._server[kUsedByWebSocketServer] = true; } + options.server[kUsedByWebSocketServer] = true; + this._server = options.server; + } + + if (this._server) { this._removeListeners = addListeners(this._server, { listening: this.emit.bind(this, 'listening'), error: this.emit.bind(this, 'error'), @@ -144,8 +143,6 @@ class WebSocketServer extends EventEmitter { const server = this._server; if (server) { - server[kUsedByWebSocketServer] = false; - this._removeListeners(); this._removeListeners = this._server = null; @@ -156,6 +153,8 @@ class WebSocketServer extends EventEmitter { server.close(() => this.emit('close')); return; } + + delete server[kUsedByWebSocketServer]; } process.nextTick(emitClose, this); From 0a612364e69fc07624b8010c6873f7766743a8e3 Mon Sep 17 00:00:00 2001 From: Adrian Hope-Bailie Date: Sun, 4 Aug 2019 21:13:09 +0200 Subject: [PATCH 669/669] [doc] Discourage use of `verifyClient` hook (#1613) --- README.md | 35 +++++++++++++++++++ doc/ws.md | 7 +++- examples/express-session-parse/index.js | 24 ++++++------- examples/express-session-parse/public/app.js | 11 ++++++ .../express-session-parse/public/index.html | 3 ++ 5 files changed, 67 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index db7b6280d..2b422be3a 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ can use one of the many wrappers available on npm, like - [Simple server](#simple-server) - [External HTTP/S server](#external-https-server) - [Multiple servers sharing a single HTTP/S server](#multiple-servers-sharing-a-single-https-server) + - [Client authentication](#client-authentication) - [Server broadcast](#server-broadcast) - [echo.websocket.org demo](#echowebsocketorg-demo) - [Use the Node.js streams API](#use-the-nodejs-streams-api) @@ -249,6 +250,40 @@ server.on('upgrade', function upgrade(request, socket, head) { server.listen(8080); ``` +### Client authentication + +```js +const http = require('http'); +const WebSocket = require('ws'); +const url = require('url'); + +const server = http.createServer(); +const wss = new WebSocket.Server({ noServer: true }); + +wss.on('connection', function(ws, request, client) { + ws.on('message', function(message) { + console.log(`WS message ${message} from user ${client}`); + }); +}); + +server.on('upgrade', function upgrade(request, socket, head) { + authenticate(request, (err, client) => { + if (err || !client) { + socket.destroy(); + return; + } + wss.handleUpgrade(request, socket, head, function done(ws) { + wss.emit('connection', ws, request, client); + }); + }); +}); + +server.listen(8080); +``` + +Also see the provided [example](./examples/express-session-parse) using +`express-session`. + ### Server broadcast A client WebSocket broadcasting to all connected WebSocket clients, including diff --git a/doc/ws.md b/doc/ws.md index 77b3e42e0..ef4ec0fbd 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -57,7 +57,8 @@ This class represents a WebSocket server. It extends the `EventEmitter`. - `backlog` {Number} The maximum length of the queue of pending connections. - `server` {http.Server|https.Server} A pre-created Node.js HTTP/S server. - `verifyClient` {Function} A function which can be used to validate incoming - connections. See description below. + connections. See description below. (Usage is discouraged: see + [Issue #337](https://github.com/websockets/ws/issues/377#issuecomment-462152231)) - `handleProtocols` {Function} A function which can be used to handle the WebSocket subprotocols. See description below. - `path` {String} Accept only connections matching this path. @@ -75,6 +76,10 @@ started manually. The "noServer" mode allows the WebSocket server to be completly detached from the HTTP/S server. This makes it possible, for example, to share a single HTTP/S server between multiple WebSocket servers. +> **NOTE:** Use of `verifyClient` is discouraged. Rather handle client +> authentication in the `upgrade` event of the HTTP server. See examples for +> more details. + If `verifyClient` is not set then the handshake is automatically accepted. If it is provided with a single argument then that is: diff --git a/examples/express-session-parse/index.js b/examples/express-session-parse/index.js index 761987dc9..4e4c4789c 100644 --- a/examples/express-session-parse/index.js +++ b/examples/express-session-parse/index.js @@ -48,20 +48,20 @@ app.delete('/logout', function(request, response) { // const server = http.createServer(app); -const wss = new WebSocket.Server({ - verifyClient: function(info, done) { - console.log('Parsing session from request...'); - sessionParser(info.req, {}, () => { - console.log('Session is parsed!'); +const wss = new WebSocket.Server({ noServer: true }); - // - // We can reject the connection by returning false to done(). For example, - // reject here if user is unknown. - // - done(info.req.session.userId); +server.on('upgrade', function upgrade(request, socket, head) { + console.log('Parsing session from request...'); + sessionParser(request, {}, () => { + if (!request.session.userId) { + socket.destroy(); + return; + } + console.log('Session is parsed!'); + wss.handleUpgrade(request, socket, head, function done(ws) { + wss.emit('connection', ws, request); }); - }, - server + }); }); wss.on('connection', function(ws, request) { diff --git a/examples/express-session-parse/public/app.js b/examples/express-session-parse/public/app.js index 56abc591a..99a6e5cf2 100644 --- a/examples/express-session-parse/public/app.js +++ b/examples/express-session-parse/public/app.js @@ -1,6 +1,7 @@ (function() { const messages = document.querySelector('#messages'); const wsButton = document.querySelector('#wsButton'); + const wsSendButton = document.querySelector('#wsSendButton'); const logout = document.querySelector('#logout'); const login = document.querySelector('#login'); @@ -50,6 +51,16 @@ }; ws.onclose = function() { showMessage('WebSocket connection closed'); + ws = null; }; }; + + wsSendButton.onclick = function() { + if (!ws) { + showMessage('No WebSocket connection'); + return; + } + ws.send('Hello World!'); + showMessage('Sent "Hello World!"'); + }; })(); diff --git a/examples/express-session-parse/public/index.html b/examples/express-session-parse/public/index.html index c99949c77..c07aa2e87 100644 --- a/examples/express-session-parse/public/index.html +++ b/examples/express-session-parse/public/index.html @@ -15,6 +15,9 @@

Choose an action.

+