'use strict'; var EventEmitter = require('events').EventEmitter , inherits = require('inherits') , utils = require('../../utils/event') , urlUtils = require('../../utils/url') , XHR = global.XMLHttpRequest ; var debug = function() {}; if (process.env.NODE_ENV !== 'production') { debug = require('debug')('sockjs-client:browser:xhr'); } function AbstractXHRObject(method, url, payload, opts) { debug(method, url); var self = this; EventEmitter.call(this); setTimeout(function () { self._start(method, url, payload, opts); }, 0); } inherits(AbstractXHRObject, EventEmitter); AbstractXHRObject.prototype._start = function(method, url, payload, opts) { var self = this; try { this.xhr = new XHR(); } catch (x) { // intentionally empty } if (!this.xhr) { debug('no xhr'); this.emit('finish', 0, 'no xhr support'); this._cleanup(); return; } // several browsers cache POSTs url = urlUtils.addQuery(url, 't=' + (+new Date())); // Explorer tends to keep connection open, even after the // tab gets closed: http://bugs.jquery.com/ticket/5280 this.unloadRef = utils.unloadAdd(function() { debug('unload cleanup'); self._cleanup(true); }); try { this.xhr.open(method, url, true); if (this.timeout && 'timeout' in this.xhr) { this.xhr.timeout = this.timeout; this.xhr.ontimeout = function() { debug('xhr timeout'); self.emit('finish', 0, ''); self._cleanup(false); }; } } catch (e) { debug('exception', e); // IE raises an exception on wrong port. this.emit('finish', 0, ''); this._cleanup(false); return; } if ((!opts || !opts.noCredentials) && AbstractXHRObject.supportsCORS) { debug('withCredentials'); // Mozilla docs says https://developer.mozilla.org/en/XMLHttpRequest : // "This never affects same-site requests." this.xhr.withCredentials = 'true'; } if (opts && opts.headers) { for (var key in opts.headers) { this.xhr.setRequestHeader(key, opts.headers[key]); } } this.xhr.onreadystatechange = function() { if (self.xhr) { var x = self.xhr; var text, status; debug('readyState', x.readyState); switch (x.readyState) { case 3: // IE doesn't like peeking into responseText or status // on Microsoft.XMLHTTP and readystate=3 try { status = x.status; text = x.responseText; } catch (e) { // intentionally empty } debug('status', status); // IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450 if (status === 1223) { status = 204; } // IE does return readystate == 3 for 404 answers. if (status === 200 && text && text.length > 0) { debug('chunk'); self.emit('chunk', status, text); } break; case 4: status = x.status; debug('status', status); // IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450 if (status === 1223) { status = 204; } // IE returns this for a bad port // http://msdn.microsoft.com/en-us/library/windows/desktop/aa383770(v=vs.85).aspx if (status === 12005 || status === 12029) { status = 0; } debug('finish', status, x.responseText); self.emit('finish', status, x.responseText); self._cleanup(false); break; } } }; try { self.xhr.send(payload); } catch (e) { self.emit('finish', 0, ''); self._cleanup(false); } }; AbstractXHRObject.prototype._cleanup = function(abort) { debug('cleanup'); if (!this.xhr) { return; } this.removeAllListeners(); utils.unloadDel(this.unloadRef); // IE needs this field to be a function this.xhr.onreadystatechange = function() {}; if (this.xhr.ontimeout) { this.xhr.ontimeout = null; } if (abort) { try { this.xhr.abort(); } catch (x) { // intentionally empty } } this.unloadRef = this.xhr = null; }; AbstractXHRObject.prototype.close = function() { debug('close'); this._cleanup(true); }; AbstractXHRObject.enabled = !!XHR; // override XMLHttpRequest for IE6/7 // obfuscate to avoid firewalls var axo = ['Active'].concat('Object').join('X'); if (!AbstractXHRObject.enabled && (axo in global)) { debug('overriding xmlhttprequest'); XHR = function() { try { return new global[axo]('Microsoft.XMLHTTP'); } catch (e) { return null; } }; AbstractXHRObject.enabled = !!new XHR(); } var cors = false; try { cors = 'withCredentials' in new XHR(); } catch (ignored) { // intentionally empty } AbstractXHRObject.supportsCORS = cors; module.exports = AbstractXHRObject;