braid/node_modules/websocket/lib/W3CWebSocket.js

258 lines
7.3 KiB
JavaScript

/************************************************************************
* Copyright 2010-2015 Brian McKelvey.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***********************************************************************/
var WebSocketClient = require('./WebSocketClient');
var toBuffer = require('typedarray-to-buffer');
var yaeti = require('yaeti');
const CONNECTING = 0;
const OPEN = 1;
const CLOSING = 2;
const CLOSED = 3;
module.exports = W3CWebSocket;
function W3CWebSocket(url, protocols, origin, headers, requestOptions, clientConfig) {
// Make this an EventTarget.
yaeti.EventTarget.call(this);
// Sanitize clientConfig.
clientConfig = clientConfig || {};
clientConfig.assembleFragments = true; // Required in the W3C API.
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);
this._client.on('connect', function(connection) {
onConnect.call(self, connection);
});
this._client.on('connectFailed', function() {
onConnectFailed.call(self);
});
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();
}
}