twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

websocket.js (6366B)


      1 /**
      2  * Module dependencies.
      3  */
      4 
      5 var Transport = require('../transport');
      6 var parser = require('engine.io-parser');
      7 var parseqs = require('parseqs');
      8 var inherit = require('component-inherit');
      9 var yeast = require('yeast');
     10 var debug = require('debug')('engine.io-client:websocket');
     11 
     12 var BrowserWebSocket, NodeWebSocket;
     13 
     14 if (typeof WebSocket !== 'undefined') {
     15   BrowserWebSocket = WebSocket;
     16 } else if (typeof self !== 'undefined') {
     17   BrowserWebSocket = self.WebSocket || self.MozWebSocket;
     18 }
     19 
     20 if (typeof window === 'undefined') {
     21   try {
     22     NodeWebSocket = require('ws');
     23   } catch (e) { }
     24 }
     25 
     26 /**
     27  * Get either the `WebSocket` or `MozWebSocket` globals
     28  * in the browser or try to resolve WebSocket-compatible
     29  * interface exposed by `ws` for Node-like environment.
     30  */
     31 
     32 var WebSocketImpl = BrowserWebSocket || NodeWebSocket;
     33 
     34 /**
     35  * Module exports.
     36  */
     37 
     38 module.exports = WS;
     39 
     40 /**
     41  * WebSocket transport constructor.
     42  *
     43  * @api {Object} connection options
     44  * @api public
     45  */
     46 
     47 function WS (opts) {
     48   var forceBase64 = (opts && opts.forceBase64);
     49   if (forceBase64) {
     50     this.supportsBinary = false;
     51   }
     52   this.perMessageDeflate = opts.perMessageDeflate;
     53   this.usingBrowserWebSocket = BrowserWebSocket && !opts.forceNode;
     54   this.protocols = opts.protocols;
     55   if (!this.usingBrowserWebSocket) {
     56     WebSocketImpl = NodeWebSocket;
     57   }
     58   Transport.call(this, opts);
     59 }
     60 
     61 /**
     62  * Inherits from Transport.
     63  */
     64 
     65 inherit(WS, Transport);
     66 
     67 /**
     68  * Transport name.
     69  *
     70  * @api public
     71  */
     72 
     73 WS.prototype.name = 'websocket';
     74 
     75 /*
     76  * WebSockets support binary
     77  */
     78 
     79 WS.prototype.supportsBinary = true;
     80 
     81 /**
     82  * Opens socket.
     83  *
     84  * @api private
     85  */
     86 
     87 WS.prototype.doOpen = function () {
     88   if (!this.check()) {
     89     // let probe timeout
     90     return;
     91   }
     92 
     93   var uri = this.uri();
     94   var protocols = this.protocols;
     95   var opts = {
     96     agent: this.agent,
     97     perMessageDeflate: this.perMessageDeflate
     98   };
     99 
    100   // SSL options for Node.js client
    101   opts.pfx = this.pfx;
    102   opts.key = this.key;
    103   opts.passphrase = this.passphrase;
    104   opts.cert = this.cert;
    105   opts.ca = this.ca;
    106   opts.ciphers = this.ciphers;
    107   opts.rejectUnauthorized = this.rejectUnauthorized;
    108   if (this.extraHeaders) {
    109     opts.headers = this.extraHeaders;
    110   }
    111   if (this.localAddress) {
    112     opts.localAddress = this.localAddress;
    113   }
    114 
    115   try {
    116     this.ws =
    117       this.usingBrowserWebSocket && !this.isReactNative
    118         ? protocols
    119           ? new WebSocketImpl(uri, protocols)
    120           : new WebSocketImpl(uri)
    121         : new WebSocketImpl(uri, protocols, opts);
    122   } catch (err) {
    123     return this.emit('error', err);
    124   }
    125 
    126   if (this.ws.binaryType === undefined) {
    127     this.supportsBinary = false;
    128   }
    129 
    130   if (this.ws.supports && this.ws.supports.binary) {
    131     this.supportsBinary = true;
    132     this.ws.binaryType = 'nodebuffer';
    133   } else {
    134     this.ws.binaryType = 'arraybuffer';
    135   }
    136 
    137   this.addEventListeners();
    138 };
    139 
    140 /**
    141  * Adds event listeners to the socket
    142  *
    143  * @api private
    144  */
    145 
    146 WS.prototype.addEventListeners = function () {
    147   var self = this;
    148 
    149   this.ws.onopen = function () {
    150     self.onOpen();
    151   };
    152   this.ws.onclose = function () {
    153     self.onClose();
    154   };
    155   this.ws.onmessage = function (ev) {
    156     self.onData(ev.data);
    157   };
    158   this.ws.onerror = function (e) {
    159     self.onError('websocket error', e);
    160   };
    161 };
    162 
    163 /**
    164  * Writes data to socket.
    165  *
    166  * @param {Array} array of packets.
    167  * @api private
    168  */
    169 
    170 WS.prototype.write = function (packets) {
    171   var self = this;
    172   this.writable = false;
    173 
    174   // encodePacket efficient as it uses WS framing
    175   // no need for encodePayload
    176   var total = packets.length;
    177   for (var i = 0, l = total; i < l; i++) {
    178     (function (packet) {
    179       parser.encodePacket(packet, self.supportsBinary, function (data) {
    180         if (!self.usingBrowserWebSocket) {
    181           // always create a new object (GH-437)
    182           var opts = {};
    183           if (packet.options) {
    184             opts.compress = packet.options.compress;
    185           }
    186 
    187           if (self.perMessageDeflate) {
    188             var len = 'string' === typeof data ? Buffer.byteLength(data) : data.length;
    189             if (len < self.perMessageDeflate.threshold) {
    190               opts.compress = false;
    191             }
    192           }
    193         }
    194 
    195         // Sometimes the websocket has already been closed but the browser didn't
    196         // have a chance of informing us about it yet, in that case send will
    197         // throw an error
    198         try {
    199           if (self.usingBrowserWebSocket) {
    200             // TypeError is thrown when passing the second argument on Safari
    201             self.ws.send(data);
    202           } else {
    203             self.ws.send(data, opts);
    204           }
    205         } catch (e) {
    206           debug('websocket closed before onclose event');
    207         }
    208 
    209         --total || done();
    210       });
    211     })(packets[i]);
    212   }
    213 
    214   function done () {
    215     self.emit('flush');
    216 
    217     // fake drain
    218     // defer to next tick to allow Socket to clear writeBuffer
    219     setTimeout(function () {
    220       self.writable = true;
    221       self.emit('drain');
    222     }, 0);
    223   }
    224 };
    225 
    226 /**
    227  * Called upon close
    228  *
    229  * @api private
    230  */
    231 
    232 WS.prototype.onClose = function () {
    233   Transport.prototype.onClose.call(this);
    234 };
    235 
    236 /**
    237  * Closes socket.
    238  *
    239  * @api private
    240  */
    241 
    242 WS.prototype.doClose = function () {
    243   if (typeof this.ws !== 'undefined') {
    244     this.ws.close();
    245   }
    246 };
    247 
    248 /**
    249  * Generates uri for connection.
    250  *
    251  * @api private
    252  */
    253 
    254 WS.prototype.uri = function () {
    255   var query = this.query || {};
    256   var schema = this.secure ? 'wss' : 'ws';
    257   var port = '';
    258 
    259   // avoid port if default for schema
    260   if (this.port && (('wss' === schema && Number(this.port) !== 443) ||
    261     ('ws' === schema && Number(this.port) !== 80))) {
    262     port = ':' + this.port;
    263   }
    264 
    265   // append timestamp to URI
    266   if (this.timestampRequests) {
    267     query[this.timestampParam] = yeast();
    268   }
    269 
    270   // communicate binary support capabilities
    271   if (!this.supportsBinary) {
    272     query.b64 = 1;
    273   }
    274 
    275   query = parseqs.encode(query);
    276 
    277   // prepend ? to query
    278   if (query.length) {
    279     query = '?' + query;
    280   }
    281 
    282   var ipv6 = this.hostname.indexOf(':') !== -1;
    283   return schema + '://' + (ipv6 ? '[' + this.hostname + ']' : this.hostname) + port + this.path + query;
    284 };
    285 
    286 /**
    287  * Feature detection for WebSocket.
    288  *
    289  * @return {Boolean} whether this transport is available.
    290  * @api public
    291  */
    292 
    293 WS.prototype.check = function () {
    294   return !!WebSocketImpl && !('__initialize' in WebSocketImpl && this.name === WS.prototype.name);
    295 };