Rev 911 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
/************************************************************************* 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 expone 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();}}