+++ /dev/null
-/************************************************************************
- * 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();
- }
-}