polling-xhr.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. "use strict";
  2. /* global attachEvent */
  3. var __importDefault = (this && this.__importDefault) || function (mod) {
  4. return (mod && mod.__esModule) ? mod : { "default": mod };
  5. };
  6. Object.defineProperty(exports, "__esModule", { value: true });
  7. exports.Request = exports.XHR = void 0;
  8. const xmlhttprequest_js_1 = __importDefault(require("./xmlhttprequest.js"));
  9. const debug_1 = __importDefault(require("debug")); // debug()
  10. const globalThis_js_1 = __importDefault(require("../globalThis.js"));
  11. const util_js_1 = require("../util.js");
  12. const component_emitter_1 = require("@socket.io/component-emitter");
  13. const polling_js_1 = require("./polling.js");
  14. const debug = (0, debug_1.default)("engine.io-client:polling-xhr"); // debug()
  15. /**
  16. * Empty function
  17. */
  18. function empty() { }
  19. const hasXHR2 = (function () {
  20. const xhr = new xmlhttprequest_js_1.default({
  21. xdomain: false
  22. });
  23. return null != xhr.responseType;
  24. })();
  25. class XHR extends polling_js_1.Polling {
  26. /**
  27. * XHR Polling constructor.
  28. *
  29. * @param {Object} opts
  30. * @api public
  31. */
  32. constructor(opts) {
  33. super(opts);
  34. if (typeof location !== "undefined") {
  35. const isSSL = "https:" === location.protocol;
  36. let port = location.port;
  37. // some user agents have empty `location.port`
  38. if (!port) {
  39. port = isSSL ? "443" : "80";
  40. }
  41. this.xd =
  42. (typeof location !== "undefined" &&
  43. opts.hostname !== location.hostname) ||
  44. port !== opts.port;
  45. this.xs = opts.secure !== isSSL;
  46. }
  47. /**
  48. * XHR supports binary
  49. */
  50. const forceBase64 = opts && opts.forceBase64;
  51. this.supportsBinary = hasXHR2 && !forceBase64;
  52. }
  53. /**
  54. * Creates a request.
  55. *
  56. * @param {String} method
  57. * @api private
  58. */
  59. request(opts = {}) {
  60. Object.assign(opts, { xd: this.xd, xs: this.xs }, this.opts);
  61. return new Request(this.uri(), opts);
  62. }
  63. /**
  64. * Sends data.
  65. *
  66. * @param {String} data to send.
  67. * @param {Function} called upon flush.
  68. * @api private
  69. */
  70. doWrite(data, fn) {
  71. const req = this.request({
  72. method: "POST",
  73. data: data
  74. });
  75. req.on("success", fn);
  76. req.on("error", err => {
  77. this.onError("xhr post error", err);
  78. });
  79. }
  80. /**
  81. * Starts a poll cycle.
  82. *
  83. * @api private
  84. */
  85. doPoll() {
  86. debug("xhr poll");
  87. const req = this.request();
  88. req.on("data", this.onData.bind(this));
  89. req.on("error", err => {
  90. this.onError("xhr poll error", err);
  91. });
  92. this.pollXhr = req;
  93. }
  94. }
  95. exports.XHR = XHR;
  96. class Request extends component_emitter_1.Emitter {
  97. /**
  98. * Request constructor
  99. *
  100. * @param {Object} options
  101. * @api public
  102. */
  103. constructor(uri, opts) {
  104. super();
  105. (0, util_js_1.installTimerFunctions)(this, opts);
  106. this.opts = opts;
  107. this.method = opts.method || "GET";
  108. this.uri = uri;
  109. this.async = false !== opts.async;
  110. this.data = undefined !== opts.data ? opts.data : null;
  111. this.create();
  112. }
  113. /**
  114. * Creates the XHR object and sends the request.
  115. *
  116. * @api private
  117. */
  118. create() {
  119. const opts = (0, util_js_1.pick)(this.opts, "agent", "pfx", "key", "passphrase", "cert", "ca", "ciphers", "rejectUnauthorized", "autoUnref");
  120. opts.xdomain = !!this.opts.xd;
  121. opts.xscheme = !!this.opts.xs;
  122. const xhr = (this.xhr = new xmlhttprequest_js_1.default(opts));
  123. try {
  124. debug("xhr open %s: %s", this.method, this.uri);
  125. xhr.open(this.method, this.uri, this.async);
  126. try {
  127. if (this.opts.extraHeaders) {
  128. xhr.setDisableHeaderCheck && xhr.setDisableHeaderCheck(true);
  129. for (let i in this.opts.extraHeaders) {
  130. if (this.opts.extraHeaders.hasOwnProperty(i)) {
  131. xhr.setRequestHeader(i, this.opts.extraHeaders[i]);
  132. }
  133. }
  134. }
  135. }
  136. catch (e) { }
  137. if ("POST" === this.method) {
  138. try {
  139. xhr.setRequestHeader("Content-type", "text/plain;charset=UTF-8");
  140. }
  141. catch (e) { }
  142. }
  143. try {
  144. xhr.setRequestHeader("Accept", "*/*");
  145. }
  146. catch (e) { }
  147. // ie6 check
  148. if ("withCredentials" in xhr) {
  149. xhr.withCredentials = this.opts.withCredentials;
  150. }
  151. if (this.opts.requestTimeout) {
  152. xhr.timeout = this.opts.requestTimeout;
  153. }
  154. xhr.onreadystatechange = () => {
  155. if (4 !== xhr.readyState)
  156. return;
  157. if (200 === xhr.status || 1223 === xhr.status) {
  158. this.onLoad();
  159. }
  160. else {
  161. // make sure the `error` event handler that's user-set
  162. // does not throw in the same tick and gets caught here
  163. this.setTimeoutFn(() => {
  164. this.onError(typeof xhr.status === "number" ? xhr.status : 0);
  165. }, 0);
  166. }
  167. };
  168. debug("xhr data %s", this.data);
  169. xhr.send(this.data);
  170. }
  171. catch (e) {
  172. // Need to defer since .create() is called directly from the constructor
  173. // and thus the 'error' event can only be only bound *after* this exception
  174. // occurs. Therefore, also, we cannot throw here at all.
  175. this.setTimeoutFn(() => {
  176. this.onError(e);
  177. }, 0);
  178. return;
  179. }
  180. if (typeof document !== "undefined") {
  181. this.index = Request.requestsCount++;
  182. Request.requests[this.index] = this;
  183. }
  184. }
  185. /**
  186. * Called upon successful response.
  187. *
  188. * @api private
  189. */
  190. onSuccess() {
  191. this.emit("success");
  192. this.cleanup();
  193. }
  194. /**
  195. * Called if we have data.
  196. *
  197. * @api private
  198. */
  199. onData(data) {
  200. this.emit("data", data);
  201. this.onSuccess();
  202. }
  203. /**
  204. * Called upon error.
  205. *
  206. * @api private
  207. */
  208. onError(err) {
  209. this.emit("error", err);
  210. this.cleanup(true);
  211. }
  212. /**
  213. * Cleans up house.
  214. *
  215. * @api private
  216. */
  217. cleanup(fromError) {
  218. if ("undefined" === typeof this.xhr || null === this.xhr) {
  219. return;
  220. }
  221. this.xhr.onreadystatechange = empty;
  222. if (fromError) {
  223. try {
  224. this.xhr.abort();
  225. }
  226. catch (e) { }
  227. }
  228. if (typeof document !== "undefined") {
  229. delete Request.requests[this.index];
  230. }
  231. this.xhr = null;
  232. }
  233. /**
  234. * Called upon load.
  235. *
  236. * @api private
  237. */
  238. onLoad() {
  239. const data = this.xhr.responseText;
  240. if (data !== null) {
  241. this.onData(data);
  242. }
  243. }
  244. /**
  245. * Aborts the request.
  246. *
  247. * @api public
  248. */
  249. abort() {
  250. this.cleanup();
  251. }
  252. }
  253. exports.Request = Request;
  254. Request.requestsCount = 0;
  255. Request.requests = {};
  256. /**
  257. * Aborts pending requests when unloading the window. This is needed to prevent
  258. * memory leaks (e.g. when using IE) and to ensure that no spurious error is
  259. * emitted.
  260. */
  261. if (typeof document !== "undefined") {
  262. // @ts-ignore
  263. if (typeof attachEvent === "function") {
  264. // @ts-ignore
  265. attachEvent("onunload", unloadHandler);
  266. }
  267. else if (typeof addEventListener === "function") {
  268. const terminationEvent = "onpagehide" in globalThis_js_1.default ? "pagehide" : "unload";
  269. addEventListener(terminationEvent, unloadHandler, false);
  270. }
  271. }
  272. function unloadHandler() {
  273. for (let i in Request.requests) {
  274. if (Request.requests.hasOwnProperty(i)) {
  275. Request.requests[i].abort();
  276. }
  277. }
  278. }