| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 | /************************************************************************ *  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();    }}
 |