abstract-xhr.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. 'use strict';
  2. var EventEmitter = require('events').EventEmitter
  3. , inherits = require('inherits')
  4. , utils = require('../../utils/event')
  5. , urlUtils = require('../../utils/url')
  6. , XHR = global.XMLHttpRequest
  7. ;
  8. var debug = function() {};
  9. if (process.env.NODE_ENV !== 'production') {
  10. debug = require('debug')('sockjs-client:browser:xhr');
  11. }
  12. function AbstractXHRObject(method, url, payload, opts) {
  13. debug(method, url);
  14. var self = this;
  15. EventEmitter.call(this);
  16. setTimeout(function () {
  17. self._start(method, url, payload, opts);
  18. }, 0);
  19. }
  20. inherits(AbstractXHRObject, EventEmitter);
  21. AbstractXHRObject.prototype._start = function(method, url, payload, opts) {
  22. var self = this;
  23. try {
  24. this.xhr = new XHR();
  25. } catch (x) {
  26. // intentionally empty
  27. }
  28. if (!this.xhr) {
  29. debug('no xhr');
  30. this.emit('finish', 0, 'no xhr support');
  31. this._cleanup();
  32. return;
  33. }
  34. // several browsers cache POSTs
  35. url = urlUtils.addQuery(url, 't=' + (+new Date()));
  36. // Explorer tends to keep connection open, even after the
  37. // tab gets closed: http://bugs.jquery.com/ticket/5280
  38. this.unloadRef = utils.unloadAdd(function() {
  39. debug('unload cleanup');
  40. self._cleanup(true);
  41. });
  42. try {
  43. this.xhr.open(method, url, true);
  44. if (this.timeout && 'timeout' in this.xhr) {
  45. this.xhr.timeout = this.timeout;
  46. this.xhr.ontimeout = function() {
  47. debug('xhr timeout');
  48. self.emit('finish', 0, '');
  49. self._cleanup(false);
  50. };
  51. }
  52. } catch (e) {
  53. debug('exception', e);
  54. // IE raises an exception on wrong port.
  55. this.emit('finish', 0, '');
  56. this._cleanup(false);
  57. return;
  58. }
  59. if ((!opts || !opts.noCredentials) && AbstractXHRObject.supportsCORS) {
  60. debug('withCredentials');
  61. // Mozilla docs says https://developer.mozilla.org/en/XMLHttpRequest :
  62. // "This never affects same-site requests."
  63. this.xhr.withCredentials = 'true';
  64. }
  65. if (opts && opts.headers) {
  66. for (var key in opts.headers) {
  67. this.xhr.setRequestHeader(key, opts.headers[key]);
  68. }
  69. }
  70. this.xhr.onreadystatechange = function() {
  71. if (self.xhr) {
  72. var x = self.xhr;
  73. var text, status;
  74. debug('readyState', x.readyState);
  75. switch (x.readyState) {
  76. case 3:
  77. // IE doesn't like peeking into responseText or status
  78. // on Microsoft.XMLHTTP and readystate=3
  79. try {
  80. status = x.status;
  81. text = x.responseText;
  82. } catch (e) {
  83. // intentionally empty
  84. }
  85. debug('status', status);
  86. // IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450
  87. if (status === 1223) {
  88. status = 204;
  89. }
  90. // IE does return readystate == 3 for 404 answers.
  91. if (status === 200 && text && text.length > 0) {
  92. debug('chunk');
  93. self.emit('chunk', status, text);
  94. }
  95. break;
  96. case 4:
  97. status = x.status;
  98. debug('status', status);
  99. // IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450
  100. if (status === 1223) {
  101. status = 204;
  102. }
  103. // IE returns this for a bad port
  104. // http://msdn.microsoft.com/en-us/library/windows/desktop/aa383770(v=vs.85).aspx
  105. if (status === 12005 || status === 12029) {
  106. status = 0;
  107. }
  108. debug('finish', status, x.responseText);
  109. self.emit('finish', status, x.responseText);
  110. self._cleanup(false);
  111. break;
  112. }
  113. }
  114. };
  115. try {
  116. self.xhr.send(payload);
  117. } catch (e) {
  118. self.emit('finish', 0, '');
  119. self._cleanup(false);
  120. }
  121. };
  122. AbstractXHRObject.prototype._cleanup = function(abort) {
  123. debug('cleanup');
  124. if (!this.xhr) {
  125. return;
  126. }
  127. this.removeAllListeners();
  128. utils.unloadDel(this.unloadRef);
  129. // IE needs this field to be a function
  130. this.xhr.onreadystatechange = function() {};
  131. if (this.xhr.ontimeout) {
  132. this.xhr.ontimeout = null;
  133. }
  134. if (abort) {
  135. try {
  136. this.xhr.abort();
  137. } catch (x) {
  138. // intentionally empty
  139. }
  140. }
  141. this.unloadRef = this.xhr = null;
  142. };
  143. AbstractXHRObject.prototype.close = function() {
  144. debug('close');
  145. this._cleanup(true);
  146. };
  147. AbstractXHRObject.enabled = !!XHR;
  148. // override XMLHttpRequest for IE6/7
  149. // obfuscate to avoid firewalls
  150. var axo = ['Active'].concat('Object').join('X');
  151. if (!AbstractXHRObject.enabled && (axo in global)) {
  152. debug('overriding xmlhttprequest');
  153. XHR = function() {
  154. try {
  155. return new global[axo]('Microsoft.XMLHTTP');
  156. } catch (e) {
  157. return null;
  158. }
  159. };
  160. AbstractXHRObject.enabled = !!new XHR();
  161. }
  162. var cors = false;
  163. try {
  164. cors = 'withCredentials' in new XHR();
  165. } catch (ignored) {
  166. // intentionally empty
  167. }
  168. AbstractXHRObject.supportsCORS = cors;
  169. module.exports = AbstractXHRObject;