Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server Feat: connection2w3cwebsocket #431

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions docs/connection2W3CWebSocket.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
connection2w3cwebsocket
============

* [Usage](#usage)
* [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
-----------

* This resulting W3CWebSocket does not fire an open event

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).
* `send()` method allows arguments of type `DOMString`, `ArrayBuffer`, `ArrayBufferView` (`Int8Array`, etc) or Node `Buffer`, but does not allow `Blob`.
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
218 changes: 10 additions & 208 deletions lib/W3CWebSocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 || {};
Expand All @@ -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);
Expand All @@ -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<len; ++i) {
view[i] = buffer[i];
}
this.dispatchEvent(createMessageEvent(arraybuffer));
}
}
}


function destroy() {
this._client.removeAllListeners();
if (this._connection) {
this._connection.removeAllListeners();
}
}
util.inherits(W3CWebSocket, W3CWebSocketWrapper);
Loading