From b896e5c6a0402bd4eb56e2092116f32b897d31a9 Mon Sep 17 00:00:00 2001 From: Samuel Tobia Date: Thu, 24 Feb 2022 18:44:08 -0800 Subject: [PATCH 1/8] Server Feat: connection2w3cwebsocket --- lib/W3CWebSocket.js | 218 ++----------------------------- lib/W3CWebSocketWrapper.js | 242 +++++++++++++++++++++++++++++++++++ lib/websocket.js | 1 + test/unit/connectionToW3c.js | 118 +++++++++++++++++ 4 files changed, 371 insertions(+), 208 deletions(-) create mode 100644 lib/W3CWebSocketWrapper.js create mode 100644 test/unit/connectionToW3c.js diff --git a/lib/W3CWebSocket.js b/lib/W3CWebSocket.js index 44a4ac98..75457612 100644 --- a/lib/W3CWebSocket.js +++ b/lib/W3CWebSocket.js @@ -15,22 +15,23 @@ ***********************************************************************/ var WebSocketClient = require('./WebSocketClient'); -var toBuffer = require('typedarray-to-buffer'); -var yaeti = require('yaeti'); +var util = require('util'); -const CONNECTING = 0; -const OPEN = 1; -const CLOSING = 2; -const CLOSED = 3; +var wrapper = require('./W3CWebSocketWrapper'); +var W3CWebSocketWrapper = wrapper.W3CWebSocketWrapper; +var onConnect = wrapper.onConnect; +var onConnectFailed = wrapper.onConnectFailed; module.exports = W3CWebSocket; function W3CWebSocket(url, protocols, origin, headers, requestOptions, clientConfig) { - // Make this an EventTarget. - yaeti.EventTarget.call(this); + W3CWebSocketWrapper.call(this); + this.addEventListener('close', function(){ + this._client.removeAllListeners(); + }); // Sanitize clientConfig. clientConfig = clientConfig || {}; @@ -39,14 +40,7 @@ function W3CWebSocket(url, protocols, origin, headers, requestOptions, clientCon var self = this; this._url = url; - this._readyState = CONNECTING; this._protocol = undefined; - this._extensions = ''; - this._bufferedAmount = 0; // Hack, always 0. - this._binaryType = 'arraybuffer'; // TODO: Should be 'blob' by default, but Node has no Blob. - - // The WebSocketConnection instance. - this._connection = undefined; // WebSocketClient instance. this._client = new WebSocketClient(clientConfig); @@ -62,196 +56,4 @@ function W3CWebSocket(url, protocols, origin, headers, requestOptions, clientCon this._client.connect(url, protocols, origin, headers, requestOptions); } - -// Expose W3C read only attributes. -Object.defineProperties(W3CWebSocket.prototype, { - url: { get: function() { return this._url; } }, - readyState: { get: function() { return this._readyState; } }, - protocol: { get: function() { return this._protocol; } }, - extensions: { get: function() { return this._extensions; } }, - bufferedAmount: { get: function() { return this._bufferedAmount; } } -}); - - -// Expose W3C write/read attributes. -Object.defineProperties(W3CWebSocket.prototype, { - binaryType: { - get: function() { - return this._binaryType; - }, - set: function(type) { - // TODO: Just 'arraybuffer' supported. - if (type !== 'arraybuffer') { - throw new SyntaxError('just "arraybuffer" type allowed for "binaryType" attribute'); - } - this._binaryType = type; - } - } -}); - - -// Expose W3C readyState constants into the WebSocket instance as W3C states. -[['CONNECTING',CONNECTING], ['OPEN',OPEN], ['CLOSING',CLOSING], ['CLOSED',CLOSED]].forEach(function(property) { - Object.defineProperty(W3CWebSocket.prototype, property[0], { - get: function() { return property[1]; } - }); -}); - -// Also expose W3C readyState constants into the WebSocket class (not defined by the W3C, -// but there are so many libs relying on them). -[['CONNECTING',CONNECTING], ['OPEN',OPEN], ['CLOSING',CLOSING], ['CLOSED',CLOSED]].forEach(function(property) { - Object.defineProperty(W3CWebSocket, property[0], { - get: function() { return property[1]; } - }); -}); - - -W3CWebSocket.prototype.send = function(data) { - if (this._readyState !== OPEN) { - throw new Error('cannot call send() while not connected'); - } - - // Text. - if (typeof data === 'string' || data instanceof String) { - this._connection.sendUTF(data); - } - // Binary. - else { - // Node Buffer. - if (data instanceof Buffer) { - this._connection.sendBytes(data); - } - // If ArrayBuffer or ArrayBufferView convert it to Node Buffer. - else if (data.byteLength || data.byteLength === 0) { - data = toBuffer(data); - this._connection.sendBytes(data); - } - else { - throw new Error('unknown binary data:', data); - } - } -}; - - -W3CWebSocket.prototype.close = function(code, reason) { - switch(this._readyState) { - case CONNECTING: - // NOTE: We don't have the WebSocketConnection instance yet so no - // way to close the TCP connection. - // Artificially invoke the onConnectFailed event. - onConnectFailed.call(this); - // And close if it connects after a while. - this._client.on('connect', function(connection) { - if (code) { - connection.close(code, reason); - } else { - connection.close(); - } - }); - break; - case OPEN: - this._readyState = CLOSING; - if (code) { - this._connection.close(code, reason); - } else { - this._connection.close(); - } - break; - case CLOSING: - case CLOSED: - break; - } -}; - - -/** - * Private API. - */ - - -function createCloseEvent(code, reason) { - var event = new yaeti.Event('close'); - - event.code = code; - event.reason = reason; - event.wasClean = (typeof code === 'undefined' || code === 1000); - - return event; -} - - -function createMessageEvent(data) { - var event = new yaeti.Event('message'); - - event.data = data; - - return event; -} - - -function onConnect(connection) { - var self = this; - - this._readyState = OPEN; - this._connection = connection; - this._protocol = connection.protocol; - this._extensions = connection.extensions; - - this._connection.on('close', function(code, reason) { - onClose.call(self, code, reason); - }); - - this._connection.on('message', function(msg) { - onMessage.call(self, msg); - }); - - this.dispatchEvent(new yaeti.Event('open')); -} - - -function onConnectFailed() { - destroy.call(this); - this._readyState = CLOSED; - - try { - this.dispatchEvent(new yaeti.Event('error')); - } finally { - this.dispatchEvent(createCloseEvent(1006, 'connection failed')); - } -} - - -function onClose(code, reason) { - destroy.call(this); - this._readyState = CLOSED; - - this.dispatchEvent(createCloseEvent(code, reason || '')); -} - - -function onMessage(message) { - if (message.utf8Data) { - this.dispatchEvent(createMessageEvent(message.utf8Data)); - } - else if (message.binaryData) { - // Must convert from Node Buffer to ArrayBuffer. - // TODO: or to a Blob (which does not exist in Node!). - if (this.binaryType === 'arraybuffer') { - var buffer = message.binaryData; - var arraybuffer = new ArrayBuffer(buffer.length); - var view = new Uint8Array(arraybuffer); - for (var i=0, len=buffer.length; i{ + stopServer(); + }); + server.prepare(function(err, wsServer) { + if (err) { + t.fail('Unable to start test server'); + return t.end(); + } + wsServer.once('request', function(request){ + var connection = request.accept(); + var sw3c = connToW3C(connection); + // open gets called within the connToW3C. It cannot be listened to + ++counter; + sw3c.send(message); + sw3c.onerror = function(event) { + t.fail('No errors are expected: ' + event); + }; + sw3c.onmessage = function(event) { + t.equal(++counter, 2, 'onmessage should be called second'); + + t.equal(event.data, message, 'Server received message data should match sent message data.'); + sw3c.close(); + }; + sw3c.onclose = function(event) { + t.equal(++counter, 3, 'onclose should be called last'); + t.end(); + }; + }); + + var cw3c = new WebSocket('ws://localhost:64321/'); + cw3c.onmessage = function(event){ + t.equal(event.data, message, 'Client received message data should match sent message data.'); + cw3c.send(event.data); + }; + }); +}); + +test('conn2W3C adding event listeners with ws.addEventListener', function(t) { + var counter = 0; + var message = 'This is a test message.'; + t.on('end', function(){ + stopServer(); + }); + server.prepare(function(err, wsServer) { + if (err) { + t.fail('Unable to start test server'); + return t.end(); + } + wsServer.once('request', function(request){ + var connection = request.accept(); + var sw3c = connToW3C(connection); + // open gets called within the connToW3C. It cannot be listened to + ++counter; + sw3c.send(message); + sw3c.addEventListener('error', function(event) { + t.fail('No errors are expected: ' + event); + }); + sw3c.addEventListener('message', function(event) { + t.equal(++counter, 2, 'onmessage should be called second'); + + t.equal(event.data, message, 'Server received message data should match sent message data.'); + sw3c.close(); + }); + sw3c.addEventListener('close', function(event) { + t.equal(++counter, 3, 'onclose should be called last'); + t.end(); + }); + }); + + var cw3c = new WebSocket('ws://localhost:64321/'); + cw3c.addEventListener('message', function(event){ + t.equal(event.data, message, 'Client received message data should match sent message data.'); + cw3c.send(event.data); + }); + }); +}); + +test('conn2W3C open event doesn\'t get emitted externally', function(t) { + var message = 'This is a test message.'; + t.on('end', function(){ + stopServer(); + }); + server.prepare(function(err, wsServer) { + if (err) { + t.fail('Unable to start test server'); + return t.end(); + } + wsServer.once('request', function(request){ + var connection = request.accept(); + var sw3c = connToW3C(connection); + sw3c.addEventListener('open', function(){ + t.fail('open event fired'); + }); + sw3c.addEventListener('message', function(event){ + t.equal(event.data, message, 'Server received message data should match sent message data.'); + sw3c.close(); + }); + sw3c.addEventListener('close', function() { + t.end(); + }); + }); + var cw3c = new WebSocket('ws://localhost:64321/'); + setTimeout(function(){ + cw3c.send(message); + }, 100); + }); +}); From d106d440260a26f449d5bc0b70a472d01d59c301 Mon Sep 17 00:00:00 2001 From: Samuel Tobia Date: Thu, 24 Feb 2022 19:04:52 -0800 Subject: [PATCH 2/8] Chore: 2 spaces should be 4 --- lib/W3CWebSocketWrapper.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/W3CWebSocketWrapper.js b/lib/W3CWebSocketWrapper.js index f93a59ae..3477abec 100644 --- a/lib/W3CWebSocketWrapper.js +++ b/lib/W3CWebSocketWrapper.js @@ -24,28 +24,28 @@ const CLOSING = 2; const CLOSED = 3; module.exports = { - W3CWebSocketWrapper, - onConnect, - onConnectFailed, - connToW3C + W3CWebSocketWrapper: W3CWebSocketWrapper, + onConnect: onConnect, + onConnectFailed: onConnectFailed, + connToW3C: connToW3C }; function connToW3C(connection){ - const w3c = new W3CWebSocketWrapper(); - onConnect.call(w3c, connection); - return w3c; + const w3c = new W3CWebSocketWrapper(); + onConnect.call(w3c, connection); + return w3c; } function W3CWebSocketWrapper(){ - // Make this an EventTarget. - yaeti.EventTarget.call(this); - this._readyState = CONNECTING; - this._extensions = ''; - this._bufferedAmount = 0; // Hack, always 0. - this._binaryType = 'arraybuffer'; // TODO: Should be 'blob' by default, but Node has no Blob. - - // The WebSocketConnection instance. - this._connection = undefined; + // Make this an EventTarget. + yaeti.EventTarget.call(this); + this._readyState = CONNECTING; + this._extensions = ''; + this._bufferedAmount = 0; // Hack, always 0. + this._binaryType = 'arraybuffer'; // TODO: Should be 'blob' by default, but Node has no Blob. + + // The WebSocketConnection instance. + this._connection = undefined; } From 29c8f8f2d8d58ea353262bb2d71b6c8c6aa1a177 Mon Sep 17 00:00:00 2001 From: Sam Tobia Date: Thu, 24 Feb 2022 23:02:57 -0500 Subject: [PATCH 3/8] Create connection2W3CWebSocket.md --- docs/connection2W3CWebSocket.md | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 docs/connection2W3CWebSocket.md diff --git a/docs/connection2W3CWebSocket.md b/docs/connection2W3CWebSocket.md new file mode 100644 index 00000000..4c4a95dc --- /dev/null +++ b/docs/connection2W3CWebSocket.md @@ -0,0 +1,41 @@ +connection2w3cwebsocket +============ + +* [Constructor](#constructor) +* [Limitations](#limitations) + +`var connToW3C = require('websocket').connection2w3cwebsocket` + +Casting a WebSocketConnection into an Implementation of the [W3C WebSocket API](http://www.w3.org/TR/websockets/) for browsers. + +The exposed function lets the developer use the browser *W3C WebSocket API* with a serverside websocket connection: + +```javascript +var connToW3C = require('websocket').connection2w3cwebsocket + +server.on("request", function(request){ + const connection = request.accept(); + const wc3 = connToW3C(connection); + w3c.onmessage = function(event){ console.log(event.data) }; + w3c.send("hello world"); +}); +``` + + +Usage +----------- + +```javascript +connection2w3cwebsocket(connection) +``` + +**connection** is a [WebSocketConnection](./WebSocketConnection.md) + +Limitations +----------- + +The same as the W3CWebSocket client + +* `bufferedAmount` attribute is always 0. +* `binaryType` is "arraybuffer" by default given that "blob" is not supported (Node does not implement the `Blob` class). +* `send()` method allows arguments of type `DOMString`, `ArrayBuffer`, `ArrayBufferView` (`Int8Array`, etc) or Node `Buffer`, but does not allow `Blob`. From e1ea1babd1ce78e3da127d879178c4947558b52c Mon Sep 17 00:00:00 2001 From: Sam Tobia Date: Thu, 24 Feb 2022 23:03:22 -0500 Subject: [PATCH 4/8] Update index.md --- docs/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.md b/docs/index.md index d42a2508..1312729d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,3 +11,4 @@ Click on one of the classes below to view its API documentation. * [WebSocketRequest](./WebSocketRequest.md) * [WebSocketServer](./WebSocketServer.md) * [W3CWebSocket](./W3CWebSocket.md) +* [connection2W3CWebSocket](./connection2W3CWebSocket.md) From 8cb7c7622ddad5aafe6d62a021fbcc46df75e373 Mon Sep 17 00:00:00 2001 From: Sam Tobia Date: Thu, 24 Feb 2022 23:03:43 -0500 Subject: [PATCH 5/8] Update connection2W3CWebSocket.md --- docs/connection2W3CWebSocket.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/connection2W3CWebSocket.md b/docs/connection2W3CWebSocket.md index 4c4a95dc..d714b517 100644 --- a/docs/connection2W3CWebSocket.md +++ b/docs/connection2W3CWebSocket.md @@ -1,7 +1,7 @@ connection2w3cwebsocket ============ -* [Constructor](#constructor) +* [Usage](#usage) * [Limitations](#limitations) `var connToW3C = require('websocket').connection2w3cwebsocket` From b748086e3c981382e8dac6ddcae27cb7b3082a45 Mon Sep 17 00:00:00 2001 From: Sam Tobia Date: Thu, 24 Feb 2022 23:04:36 -0500 Subject: [PATCH 6/8] Update connection2W3CWebSocket.md --- docs/connection2W3CWebSocket.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/connection2W3CWebSocket.md b/docs/connection2W3CWebSocket.md index d714b517..38e59d31 100644 --- a/docs/connection2W3CWebSocket.md +++ b/docs/connection2W3CWebSocket.md @@ -34,6 +34,8 @@ connection2w3cwebsocket(connection) Limitations ----------- +* This resulting W3CWebSocket does not fire an open event + The same as the W3CWebSocket client * `bufferedAmount` attribute is always 0. From 89732147feb010b9c6fba8198fb281288bf706cd Mon Sep 17 00:00:00 2001 From: Sam Tobia Date: Thu, 24 Feb 2022 23:05:20 -0500 Subject: [PATCH 7/8] Update connection2W3CWebSocket.md --- docs/connection2W3CWebSocket.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/connection2W3CWebSocket.md b/docs/connection2W3CWebSocket.md index 38e59d31..5c54102f 100644 --- a/docs/connection2W3CWebSocket.md +++ b/docs/connection2W3CWebSocket.md @@ -36,7 +36,7 @@ Limitations * This resulting W3CWebSocket does not fire an open event -The same as the W3CWebSocket client +The same as the [W3CWebSocket client](./W3CWebSocketClient.md) * `bufferedAmount` attribute is always 0. * `binaryType` is "arraybuffer" by default given that "blob" is not supported (Node does not implement the `Blob` class). From 2402e43232d3d48fa137f9d22650f7d4d6d5592c Mon Sep 17 00:00:00 2001 From: Sam Tobia Date: Thu, 24 Feb 2022 23:05:51 -0500 Subject: [PATCH 8/8] Update connection2W3CWebSocket.md --- docs/connection2W3CWebSocket.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/connection2W3CWebSocket.md b/docs/connection2W3CWebSocket.md index 5c54102f..4477b37e 100644 --- a/docs/connection2W3CWebSocket.md +++ b/docs/connection2W3CWebSocket.md @@ -36,7 +36,7 @@ Limitations * This resulting W3CWebSocket does not fire an open event -The same as the [W3CWebSocket client](./W3CWebSocketClient.md) +The same as the [W3CWebSocket client](./W3CWebSocket.md) * `bufferedAmount` attribute is always 0. * `binaryType` is "arraybuffer" by default given that "blob" is not supported (Node does not implement the `Blob` class).