Last active
August 29, 2015 14:20
-
-
Save runspired/dfeb76053868b1441280 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Module requirements. | |
*/ | |
var XMLHttpRequest = require('xmlhttprequest'); | |
var Polling = require('./polling'); | |
var Emitter = require('component-emitter'); | |
var inherit = require('component-inherit'); | |
var debug = require('debug')('engine.io-client:polling-xhr'); | |
/** | |
* Module exports. | |
*/ | |
module.exports = XHR; | |
module.exports.Request = Request; | |
/** | |
* Empty function | |
*/ | |
function empty(){} | |
/** | |
* XHR Polling constructor. | |
* | |
* @param {Object} opts | |
* @api public | |
*/ | |
function XHR(opts){ | |
Polling.call(this, opts); | |
if (global.location) { | |
var isSSL = 'https:' == location.protocol; | |
var port = location.port; | |
// some user agents have empty `location.port` | |
if (!port) { | |
port = isSSL ? 443 : 80; | |
} | |
this.xd = opts.hostname != global.location.hostname || | |
port != opts.port; | |
this.xs = opts.secure != isSSL; | |
} | |
} | |
/** | |
* Inherits from Polling. | |
*/ | |
inherit(XHR, Polling); | |
/** | |
* XHR supports binary | |
*/ | |
XHR.prototype.supportsBinary = true; | |
/** | |
* Creates a request. | |
* | |
* @param {String} method | |
* @api private | |
*/ | |
XHR.prototype.request = function(opts){ | |
opts = opts || {}; | |
opts.uri = this.uri(); | |
opts.xd = this.xd; | |
opts.xs = this.xs; | |
opts.agent = this.agent || false; | |
opts.supportsBinary = this.supportsBinary; | |
opts.enablesXDR = this.enablesXDR; | |
// SSL options for Node.js client | |
opts.pfx = this.pfx; | |
opts.key = this.key; | |
opts.passphrase = this.passphrase; | |
opts.cert = this.cert; | |
opts.ca = this.ca; | |
opts.ciphers = this.ciphers; | |
opts.rejectUnauthorized = this.rejectUnauthorized; | |
return new Request(opts); | |
}; | |
/** | |
* To save the cookies of the last response. | |
*/ | |
var lastCookies; | |
/** | |
* Sends data. | |
* | |
* @param {String} data to send. | |
* @param {Function} called upon flush. | |
* @api private | |
*/ | |
XHR.prototype.doWrite = function(data, fn){ | |
var isBinary = typeof data !== 'string' && data !== undefined; | |
var req = this.request({ method: 'POST', data: data, isBinary: isBinary, cookies: lastCookies }); | |
var self = this; | |
req.on('success', fn); | |
req.on('error', function(err){ | |
self.onError('xhr post error', err); | |
}); | |
this.sendXhr = req; | |
}; | |
/** | |
* Starts a poll cycle. | |
* | |
* @api private | |
*/ | |
XHR.prototype.doPoll = function(){ | |
debug('xhr poll'); | |
var req = this.request({ cookies: lastCookies }); | |
var self = this; | |
req.on('data', function(data){ | |
self.onData(data); | |
}); | |
req.on('error', function(err){ | |
self.onError('xhr poll error', err); | |
}); | |
this.pollXhr = req; | |
req.on('cookies', function(cookies){ | |
lastCookies = cookies; | |
}); | |
}; | |
/** | |
* Request constructor | |
* | |
* @param {Object} options | |
* @api public | |
*/ | |
function Request(opts){ | |
this.method = opts.method || 'GET'; | |
this.uri = opts.uri; | |
this.xd = !!opts.xd; | |
this.xs = !!opts.xs; | |
this.async = false !== opts.async; | |
this.data = undefined != opts.data ? opts.data : null; | |
this.agent = opts.agent; | |
this.isBinary = opts.isBinary; | |
this.supportsBinary = opts.supportsBinary; | |
this.enablesXDR = opts.enablesXDR; | |
// SSL options for Node.js client | |
this.pfx = opts.pfx; | |
this.key = opts.key; | |
this.passphrase = opts.passphrase; | |
this.cert = opts.cert; | |
this.ca = opts.ca; | |
this.ciphers = opts.ciphers; | |
this.rejectUnauthorized = opts.rejectUnauthorized; | |
this.cookies = opts.cookies; | |
this.create(); | |
} | |
/** | |
* Mix in `Emitter`. | |
*/ | |
Emitter(Request.prototype); | |
/** | |
* Creates the XHR object and sends the request. | |
* | |
* @api private | |
*/ | |
Request.prototype.create = function(){ | |
var opts = { agent: this.agent, xdomain: this.xd, xscheme: this.xs, enablesXDR: this.enablesXDR }; | |
// SSL options for Node.js client | |
opts.pfx = this.pfx; | |
opts.key = this.key; | |
opts.passphrase = this.passphrase; | |
opts.cert = this.cert; | |
opts.ca = this.ca; | |
opts.ciphers = this.ciphers; | |
opts.rejectUnauthorized = this.rejectUnauthorized; | |
var xhr = this.xhr = new XMLHttpRequest(opts); | |
var self = this; | |
try { | |
debug('xhr open %s: %s', this.method, this.uri); | |
xhr.open(this.method, this.uri, this.async); | |
try { | |
if (this.cookies) { | |
xhr.setDisableHeaderCheck(true); | |
xhr.setRequestHeader('Cookie', this.cookies); | |
} | |
} catch (e) {} | |
if (this.supportsBinary) { | |
// This has to be done after open because Firefox is stupid | |
// http://stackoverflow.com/questions/13216903/get-binary-data-with-xmlhttprequest-in-a-firefox-extension | |
xhr.responseType = 'arraybuffer'; | |
} | |
if ('POST' == this.method) { | |
try { | |
if (this.isBinary) { | |
xhr.setRequestHeader('Content-type', 'application/octet-stream'); | |
} else { | |
xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8'); | |
} | |
} catch (e) {} | |
} | |
// ie6 check | |
if ('withCredentials' in xhr) { | |
xhr.withCredentials = true; | |
} | |
if (this.hasXDR()) { | |
xhr.onload = function(){ | |
self.onLoad(); | |
}; | |
xhr.onerror = function(){ | |
self.onError(xhr.responseText); | |
}; | |
} else { | |
xhr.onreadystatechange = function(){ | |
if (4 != xhr.readyState) return; | |
if (200 == xhr.status || 1223 == xhr.status) { | |
self.onLoad(); | |
} else { | |
// make sure the `error` event handler that's user-set | |
// does not throw in the same tick and gets caught here | |
setTimeout(function(){ | |
self.onError(xhr.status); | |
}, 0); | |
} | |
}; | |
} | |
debug('xhr data %s', this.data); | |
xhr.send(this.data); | |
} catch (e) { | |
// Need to defer since .create() is called directly fhrom the constructor | |
// and thus the 'error' event can only be only bound *after* this exception | |
// occurs. Therefore, also, we cannot throw here at all. | |
setTimeout(function() { | |
self.onError(e); | |
}, 0); | |
return; | |
} | |
if (global.document) { | |
this.index = Request.requestsCount++; | |
Request.requests[this.index] = this; | |
} | |
}; | |
/** | |
* Called upon successful response. | |
* | |
* @api private | |
*/ | |
Request.prototype.onSuccess = function(){ | |
this.emit('success'); | |
this.cleanup(); | |
}; | |
/** | |
* Called if we have data. | |
* | |
* @api private | |
*/ | |
Request.prototype.onData = function(data){ | |
this.emit('data', data); | |
this.onSuccess(); | |
}; | |
/** | |
* Called if we have cookies. | |
* | |
* @api private | |
*/ | |
Request.prototype.onCookies = function(cookies){ | |
this.emit('cookies', cookies); | |
}; | |
/** | |
* Called upon error. | |
* | |
* @api private | |
*/ | |
Request.prototype.onError = function(err){ | |
this.emit('error', err); | |
this.cleanup(true); | |
}; | |
/** | |
* Cleans up house. | |
* | |
* @api private | |
*/ | |
Request.prototype.cleanup = function(fromError){ | |
if ('undefined' == typeof this.xhr || null === this.xhr) { | |
return; | |
} | |
// xmlhttprequest | |
if (this.hasXDR()) { | |
this.xhr.onload = this.xhr.onerror = empty; | |
} else { | |
this.xhr.onreadystatechange = empty; | |
} | |
if (fromError) { | |
try { | |
this.xhr.abort(); | |
} catch(e) {} | |
} | |
if (global.document) { | |
delete Request.requests[this.index]; | |
} | |
this.xhr = null; | |
}; | |
/** | |
* Called upon load. | |
* | |
* @api private | |
*/ | |
Request.prototype.onLoad = function(){ | |
var data; | |
try { | |
var contentType; | |
var cookies; | |
try { | |
contentType = this.xhr.getResponseHeader('Content-Type').split(';')[0]; | |
cookies = this.xhr.getResponseHeader('Set-Cookie'); | |
} catch (e) {} | |
if (contentType === 'application/octet-stream') { | |
data = this.xhr.response; | |
} else { | |
if (!this.supportsBinary) { | |
data = this.xhr.responseText; | |
} else { | |
data = 'ok'; | |
} | |
} | |
} catch (e) { | |
this.onError(e); | |
} | |
if (null != cookies) { | |
this.cookies = cookies.join(';'); | |
this.onCookies(cookies.join(';')); | |
} | |
if (null != data) { | |
this.onData(data); | |
} | |
}; | |
/** | |
* Check if it has XDomainRequest. | |
* | |
* @api private | |
*/ | |
Request.prototype.hasXDR = function(){ | |
return 'undefined' !== typeof global.XDomainRequest && !this.xs && this.enablesXDR; | |
}; | |
/** | |
* Aborts the request. | |
* | |
* @api public | |
*/ | |
Request.prototype.abort = function(){ | |
this.cleanup(); | |
}; | |
/** | |
* Aborts pending requests when unloading the window. This is needed to prevent | |
* memory leaks (e.g. when using IE) and to ensure that no spurious error is | |
* emitted. | |
*/ | |
if (global.document) { | |
Request.requestsCount = 0; | |
Request.requests = {}; | |
if (global.attachEvent) { | |
global.attachEvent('onunload', unloadHandler); | |
} else if (global.addEventListener) { | |
global.addEventListener('beforeunload', unloadHandler, false); | |
} | |
} | |
function unloadHandler() { | |
for (var i in Request.requests) { | |
if (Request.requests.hasOwnProperty(i)) { | |
Request.requests[i].abort(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment