twitst4tz

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

polling-xhr.js (9285B)


      1 /* global attachEvent */
      2 
      3 /**
      4  * Module requirements.
      5  */
      6 
      7 var XMLHttpRequest = require('xmlhttprequest-ssl');
      8 var Polling = require('./polling');
      9 var Emitter = require('component-emitter');
     10 var inherit = require('component-inherit');
     11 var debug = require('debug')('engine.io-client:polling-xhr');
     12 var globalThis = require('../globalThis');
     13 
     14 /**
     15  * Module exports.
     16  */
     17 
     18 module.exports = XHR;
     19 module.exports.Request = Request;
     20 
     21 /**
     22  * Empty function
     23  */
     24 
     25 function empty () {}
     26 
     27 /**
     28  * XHR Polling constructor.
     29  *
     30  * @param {Object} opts
     31  * @api public
     32  */
     33 
     34 function XHR (opts) {
     35   Polling.call(this, opts);
     36   this.requestTimeout = opts.requestTimeout;
     37   this.extraHeaders = opts.extraHeaders;
     38 
     39   if (typeof location !== 'undefined') {
     40     var isSSL = 'https:' === location.protocol;
     41     var port = location.port;
     42 
     43     // some user agents have empty `location.port`
     44     if (!port) {
     45       port = isSSL ? 443 : 80;
     46     }
     47 
     48     this.xd = (typeof location !== 'undefined' && opts.hostname !== location.hostname) ||
     49       port !== opts.port;
     50     this.xs = opts.secure !== isSSL;
     51   }
     52 }
     53 
     54 /**
     55  * Inherits from Polling.
     56  */
     57 
     58 inherit(XHR, Polling);
     59 
     60 /**
     61  * XHR supports binary
     62  */
     63 
     64 XHR.prototype.supportsBinary = true;
     65 
     66 /**
     67  * Creates a request.
     68  *
     69  * @param {String} method
     70  * @api private
     71  */
     72 
     73 XHR.prototype.request = function (opts) {
     74   opts = opts || {};
     75   opts.uri = this.uri();
     76   opts.xd = this.xd;
     77   opts.xs = this.xs;
     78   opts.agent = this.agent || false;
     79   opts.supportsBinary = this.supportsBinary;
     80   opts.enablesXDR = this.enablesXDR;
     81   opts.withCredentials = this.withCredentials;
     82 
     83   // SSL options for Node.js client
     84   opts.pfx = this.pfx;
     85   opts.key = this.key;
     86   opts.passphrase = this.passphrase;
     87   opts.cert = this.cert;
     88   opts.ca = this.ca;
     89   opts.ciphers = this.ciphers;
     90   opts.rejectUnauthorized = this.rejectUnauthorized;
     91   opts.requestTimeout = this.requestTimeout;
     92 
     93   // other options for Node.js client
     94   opts.extraHeaders = this.extraHeaders;
     95 
     96   return new Request(opts);
     97 };
     98 
     99 /**
    100  * Sends data.
    101  *
    102  * @param {String} data to send.
    103  * @param {Function} called upon flush.
    104  * @api private
    105  */
    106 
    107 XHR.prototype.doWrite = function (data, fn) {
    108   var isBinary = typeof data !== 'string' && data !== undefined;
    109   var req = this.request({ method: 'POST', data: data, isBinary: isBinary });
    110   var self = this;
    111   req.on('success', fn);
    112   req.on('error', function (err) {
    113     self.onError('xhr post error', err);
    114   });
    115   this.sendXhr = req;
    116 };
    117 
    118 /**
    119  * Starts a poll cycle.
    120  *
    121  * @api private
    122  */
    123 
    124 XHR.prototype.doPoll = function () {
    125   debug('xhr poll');
    126   var req = this.request();
    127   var self = this;
    128   req.on('data', function (data) {
    129     self.onData(data);
    130   });
    131   req.on('error', function (err) {
    132     self.onError('xhr poll error', err);
    133   });
    134   this.pollXhr = req;
    135 };
    136 
    137 /**
    138  * Request constructor
    139  *
    140  * @param {Object} options
    141  * @api public
    142  */
    143 
    144 function Request (opts) {
    145   this.method = opts.method || 'GET';
    146   this.uri = opts.uri;
    147   this.xd = !!opts.xd;
    148   this.xs = !!opts.xs;
    149   this.async = false !== opts.async;
    150   this.data = undefined !== opts.data ? opts.data : null;
    151   this.agent = opts.agent;
    152   this.isBinary = opts.isBinary;
    153   this.supportsBinary = opts.supportsBinary;
    154   this.enablesXDR = opts.enablesXDR;
    155   this.withCredentials = opts.withCredentials;
    156   this.requestTimeout = opts.requestTimeout;
    157 
    158   // SSL options for Node.js client
    159   this.pfx = opts.pfx;
    160   this.key = opts.key;
    161   this.passphrase = opts.passphrase;
    162   this.cert = opts.cert;
    163   this.ca = opts.ca;
    164   this.ciphers = opts.ciphers;
    165   this.rejectUnauthorized = opts.rejectUnauthorized;
    166 
    167   // other options for Node.js client
    168   this.extraHeaders = opts.extraHeaders;
    169 
    170   this.create();
    171 }
    172 
    173 /**
    174  * Mix in `Emitter`.
    175  */
    176 
    177 Emitter(Request.prototype);
    178 
    179 /**
    180  * Creates the XHR object and sends the request.
    181  *
    182  * @api private
    183  */
    184 
    185 Request.prototype.create = function () {
    186   var opts = { agent: this.agent, xdomain: this.xd, xscheme: this.xs, enablesXDR: this.enablesXDR };
    187 
    188   // SSL options for Node.js client
    189   opts.pfx = this.pfx;
    190   opts.key = this.key;
    191   opts.passphrase = this.passphrase;
    192   opts.cert = this.cert;
    193   opts.ca = this.ca;
    194   opts.ciphers = this.ciphers;
    195   opts.rejectUnauthorized = this.rejectUnauthorized;
    196 
    197   var xhr = this.xhr = new XMLHttpRequest(opts);
    198   var self = this;
    199 
    200   try {
    201     debug('xhr open %s: %s', this.method, this.uri);
    202     xhr.open(this.method, this.uri, this.async);
    203     try {
    204       if (this.extraHeaders) {
    205         xhr.setDisableHeaderCheck && xhr.setDisableHeaderCheck(true);
    206         for (var i in this.extraHeaders) {
    207           if (this.extraHeaders.hasOwnProperty(i)) {
    208             xhr.setRequestHeader(i, this.extraHeaders[i]);
    209           }
    210         }
    211       }
    212     } catch (e) {}
    213 
    214     if ('POST' === this.method) {
    215       try {
    216         if (this.isBinary) {
    217           xhr.setRequestHeader('Content-type', 'application/octet-stream');
    218         } else {
    219           xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8');
    220         }
    221       } catch (e) {}
    222     }
    223 
    224     try {
    225       xhr.setRequestHeader('Accept', '*/*');
    226     } catch (e) {}
    227 
    228     // ie6 check
    229     if ('withCredentials' in xhr) {
    230       xhr.withCredentials = this.withCredentials;
    231     }
    232 
    233     if (this.requestTimeout) {
    234       xhr.timeout = this.requestTimeout;
    235     }
    236 
    237     if (this.hasXDR()) {
    238       xhr.onload = function () {
    239         self.onLoad();
    240       };
    241       xhr.onerror = function () {
    242         self.onError(xhr.responseText);
    243       };
    244     } else {
    245       xhr.onreadystatechange = function () {
    246         if (xhr.readyState === 2) {
    247           try {
    248             var contentType = xhr.getResponseHeader('Content-Type');
    249             if (self.supportsBinary && contentType === 'application/octet-stream' || contentType === 'application/octet-stream; charset=UTF-8') {
    250               xhr.responseType = 'arraybuffer';
    251             }
    252           } catch (e) {}
    253         }
    254         if (4 !== xhr.readyState) return;
    255         if (200 === xhr.status || 1223 === xhr.status) {
    256           self.onLoad();
    257         } else {
    258           // make sure the `error` event handler that's user-set
    259           // does not throw in the same tick and gets caught here
    260           setTimeout(function () {
    261             self.onError(typeof xhr.status === 'number' ? xhr.status : 0);
    262           }, 0);
    263         }
    264       };
    265     }
    266 
    267     debug('xhr data %s', this.data);
    268     xhr.send(this.data);
    269   } catch (e) {
    270     // Need to defer since .create() is called directly fhrom the constructor
    271     // and thus the 'error' event can only be only bound *after* this exception
    272     // occurs.  Therefore, also, we cannot throw here at all.
    273     setTimeout(function () {
    274       self.onError(e);
    275     }, 0);
    276     return;
    277   }
    278 
    279   if (typeof document !== 'undefined') {
    280     this.index = Request.requestsCount++;
    281     Request.requests[this.index] = this;
    282   }
    283 };
    284 
    285 /**
    286  * Called upon successful response.
    287  *
    288  * @api private
    289  */
    290 
    291 Request.prototype.onSuccess = function () {
    292   this.emit('success');
    293   this.cleanup();
    294 };
    295 
    296 /**
    297  * Called if we have data.
    298  *
    299  * @api private
    300  */
    301 
    302 Request.prototype.onData = function (data) {
    303   this.emit('data', data);
    304   this.onSuccess();
    305 };
    306 
    307 /**
    308  * Called upon error.
    309  *
    310  * @api private
    311  */
    312 
    313 Request.prototype.onError = function (err) {
    314   this.emit('error', err);
    315   this.cleanup(true);
    316 };
    317 
    318 /**
    319  * Cleans up house.
    320  *
    321  * @api private
    322  */
    323 
    324 Request.prototype.cleanup = function (fromError) {
    325   if ('undefined' === typeof this.xhr || null === this.xhr) {
    326     return;
    327   }
    328   // xmlhttprequest
    329   if (this.hasXDR()) {
    330     this.xhr.onload = this.xhr.onerror = empty;
    331   } else {
    332     this.xhr.onreadystatechange = empty;
    333   }
    334 
    335   if (fromError) {
    336     try {
    337       this.xhr.abort();
    338     } catch (e) {}
    339   }
    340 
    341   if (typeof document !== 'undefined') {
    342     delete Request.requests[this.index];
    343   }
    344 
    345   this.xhr = null;
    346 };
    347 
    348 /**
    349  * Called upon load.
    350  *
    351  * @api private
    352  */
    353 
    354 Request.prototype.onLoad = function () {
    355   var data;
    356   try {
    357     var contentType;
    358     try {
    359       contentType = this.xhr.getResponseHeader('Content-Type');
    360     } catch (e) {}
    361     if (contentType === 'application/octet-stream' || contentType === 'application/octet-stream; charset=UTF-8') {
    362       data = this.xhr.response || this.xhr.responseText;
    363     } else {
    364       data = this.xhr.responseText;
    365     }
    366   } catch (e) {
    367     this.onError(e);
    368   }
    369   if (null != data) {
    370     this.onData(data);
    371   }
    372 };
    373 
    374 /**
    375  * Check if it has XDomainRequest.
    376  *
    377  * @api private
    378  */
    379 
    380 Request.prototype.hasXDR = function () {
    381   return typeof XDomainRequest !== 'undefined' && !this.xs && this.enablesXDR;
    382 };
    383 
    384 /**
    385  * Aborts the request.
    386  *
    387  * @api public
    388  */
    389 
    390 Request.prototype.abort = function () {
    391   this.cleanup();
    392 };
    393 
    394 /**
    395  * Aborts pending requests when unloading the window. This is needed to prevent
    396  * memory leaks (e.g. when using IE) and to ensure that no spurious error is
    397  * emitted.
    398  */
    399 
    400 Request.requestsCount = 0;
    401 Request.requests = {};
    402 
    403 if (typeof document !== 'undefined') {
    404   if (typeof attachEvent === 'function') {
    405     attachEvent('onunload', unloadHandler);
    406   } else if (typeof addEventListener === 'function') {
    407     var terminationEvent = 'onpagehide' in globalThis ? 'pagehide' : 'unload';
    408     addEventListener(terminationEvent, unloadHandler, false);
    409   }
    410 }
    411 
    412 function unloadHandler () {
    413   for (var i in Request.requests) {
    414     if (Request.requests.hasOwnProperty(i)) {
    415       Request.requests[i].abort();
    416     }
    417   }
    418 }