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