1 /* global attachEvent */
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');
19 module.exports.Request = Request;
28 * XHR Polling constructor.
30 * @param {Object} opts
35 Polling.call(this, opts);
36 this.requestTimeout = opts.requestTimeout;
37 this.extraHeaders = opts.extraHeaders;
39 if (typeof location !== 'undefined') {
40 var isSSL = 'https:' === location.protocol;
41 var port = location.port;
43 // some user agents have empty `location.port`
45 port = isSSL ? 443 : 80;
48 this.xd = (typeof location !== 'undefined' && opts.hostname !== location.hostname) ||
50 this.xs = opts.secure !== isSSL;
55 * Inherits from Polling.
58 inherit(XHR, Polling);
64 XHR.prototype.supportsBinary = true;
69 * @param {String} method
73 XHR.prototype.request = function (opts) {
75 opts.uri = this.uri();
78 opts.agent = this.agent || false;
79 opts.supportsBinary = this.supportsBinary;
80 opts.enablesXDR = this.enablesXDR;
81 opts.withCredentials = this.withCredentials;
83 // SSL options for Node.js client
86 opts.passphrase = this.passphrase;
87 opts.cert = this.cert;
89 opts.ciphers = this.ciphers;
90 opts.rejectUnauthorized = this.rejectUnauthorized;
91 opts.requestTimeout = this.requestTimeout;
93 // other options for Node.js client
94 opts.extraHeaders = this.extraHeaders;
96 return new Request(opts);
102 * @param {String} data to send.
103 * @param {Function} called upon flush.
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 });
111 req.on('success', fn);
112 req.on('error', function (err) {
113 self.onError('xhr post error', err);
119 * Starts a poll cycle.
124 XHR.prototype.doPoll = function () {
126 var req = this.request();
128 req.on('data', function (data) {
131 req.on('error', function (err) {
132 self.onError('xhr poll error', err);
138 * Request constructor
140 * @param {Object} options
144 function Request (opts) {
145 this.method = opts.method || 'GET';
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;
158 // SSL options for Node.js client
161 this.passphrase = opts.passphrase;
162 this.cert = opts.cert;
164 this.ciphers = opts.ciphers;
165 this.rejectUnauthorized = opts.rejectUnauthorized;
167 // other options for Node.js client
168 this.extraHeaders = opts.extraHeaders;
177 Emitter(Request.prototype);
180 * Creates the XHR object and sends the request.
185 Request.prototype.create = function () {
186 var opts = { agent: this.agent, xdomain: this.xd, xscheme: this.xs, enablesXDR: this.enablesXDR };
188 // SSL options for Node.js client
191 opts.passphrase = this.passphrase;
192 opts.cert = this.cert;
194 opts.ciphers = this.ciphers;
195 opts.rejectUnauthorized = this.rejectUnauthorized;
197 var xhr = this.xhr = new XMLHttpRequest(opts);
201 debug('xhr open %s: %s', this.method, this.uri);
202 xhr.open(this.method, this.uri, this.async);
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]);
214 if ('POST' === this.method) {
217 xhr.setRequestHeader('Content-type', 'application/octet-stream');
219 xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8');
225 xhr.setRequestHeader('Accept', '*/*');
229 if ('withCredentials' in xhr) {
230 xhr.withCredentials = this.withCredentials;
233 if (this.requestTimeout) {
234 xhr.timeout = this.requestTimeout;
238 xhr.onload = function () {
241 xhr.onerror = function () {
242 self.onError(xhr.responseText);
245 xhr.onreadystatechange = function () {
246 if (xhr.readyState === 2) {
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';
254 if (4 !== xhr.readyState) return;
255 if (200 === xhr.status || 1223 === xhr.status) {
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);
267 debug('xhr data %s', this.data);
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 () {
279 if (typeof document !== 'undefined') {
280 this.index = Request.requestsCount++;
281 Request.requests[this.index] = this;
286 * Called upon successful response.
291 Request.prototype.onSuccess = function () {
292 this.emit('success');
297 * Called if we have data.
302 Request.prototype.onData = function (data) {
303 this.emit('data', data);
313 Request.prototype.onError = function (err) {
314 this.emit('error', err);
324 Request.prototype.cleanup = function (fromError) {
325 if ('undefined' === typeof this.xhr || null === this.xhr) {
330 this.xhr.onload = this.xhr.onerror = empty;
332 this.xhr.onreadystatechange = empty;
341 if (typeof document !== 'undefined') {
342 delete Request.requests[this.index];
354 Request.prototype.onLoad = function () {
359 contentType = this.xhr.getResponseHeader('Content-Type');
361 if (contentType === 'application/octet-stream' || contentType === 'application/octet-stream; charset=UTF-8') {
362 data = this.xhr.response || this.xhr.responseText;
364 data = this.xhr.responseText;
375 * Check if it has XDomainRequest.
380 Request.prototype.hasXDR = function () {
381 return typeof XDomainRequest !== 'undefined' && !this.xs && this.enablesXDR;
385 * Aborts the request.
390 Request.prototype.abort = function () {
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
400 Request.requestsCount = 0;
401 Request.requests = {};
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);
412 function unloadHandler () {
413 for (var i in Request.requests) {
414 if (Request.requests.hasOwnProperty(i)) {
415 Request.requests[i].abort();