polling.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.Request = exports.Polling = void 0;
  7. const transport_js_1 = require("../transport.js");
  8. const debug_1 = __importDefault(require("debug")); // debug()
  9. const yeast_js_1 = require("../contrib/yeast.js");
  10. const engine_io_parser_1 = require("engine.io-parser");
  11. const xmlhttprequest_js_1 = require("./xmlhttprequest.js");
  12. const component_emitter_1 = require("@socket.io/component-emitter");
  13. const util_js_1 = require("../util.js");
  14. const globalThis_js_1 = require("../globalThis.js");
  15. const debug = (0, debug_1.default)("engine.io-client:polling"); // debug()
  16. function empty() { }
  17. const hasXHR2 = (function () {
  18. const xhr = new xmlhttprequest_js_1.XHR({
  19. xdomain: false,
  20. });
  21. return null != xhr.responseType;
  22. })();
  23. class Polling extends transport_js_1.Transport {
  24. /**
  25. * XHR Polling constructor.
  26. *
  27. * @param {Object} opts
  28. * @package
  29. */
  30. constructor(opts) {
  31. super(opts);
  32. this.polling = false;
  33. if (typeof location !== "undefined") {
  34. const isSSL = "https:" === location.protocol;
  35. let port = location.port;
  36. // some user agents have empty `location.port`
  37. if (!port) {
  38. port = isSSL ? "443" : "80";
  39. }
  40. this.xd =
  41. (typeof location !== "undefined" &&
  42. opts.hostname !== location.hostname) ||
  43. port !== opts.port;
  44. }
  45. /**
  46. * XHR supports binary
  47. */
  48. const forceBase64 = opts && opts.forceBase64;
  49. this.supportsBinary = hasXHR2 && !forceBase64;
  50. if (this.opts.withCredentials) {
  51. this.cookieJar = (0, xmlhttprequest_js_1.createCookieJar)();
  52. }
  53. }
  54. get name() {
  55. return "polling";
  56. }
  57. /**
  58. * Opens the socket (triggers polling). We write a PING message to determine
  59. * when the transport is open.
  60. *
  61. * @protected
  62. */
  63. doOpen() {
  64. this.poll();
  65. }
  66. /**
  67. * Pauses polling.
  68. *
  69. * @param {Function} onPause - callback upon buffers are flushed and transport is paused
  70. * @package
  71. */
  72. pause(onPause) {
  73. this.readyState = "pausing";
  74. const pause = () => {
  75. debug("paused");
  76. this.readyState = "paused";
  77. onPause();
  78. };
  79. if (this.polling || !this.writable) {
  80. let total = 0;
  81. if (this.polling) {
  82. debug("we are currently polling - waiting to pause");
  83. total++;
  84. this.once("pollComplete", function () {
  85. debug("pre-pause polling complete");
  86. --total || pause();
  87. });
  88. }
  89. if (!this.writable) {
  90. debug("we are currently writing - waiting to pause");
  91. total++;
  92. this.once("drain", function () {
  93. debug("pre-pause writing complete");
  94. --total || pause();
  95. });
  96. }
  97. }
  98. else {
  99. pause();
  100. }
  101. }
  102. /**
  103. * Starts polling cycle.
  104. *
  105. * @private
  106. */
  107. poll() {
  108. debug("polling");
  109. this.polling = true;
  110. this.doPoll();
  111. this.emitReserved("poll");
  112. }
  113. /**
  114. * Overloads onData to detect payloads.
  115. *
  116. * @protected
  117. */
  118. onData(data) {
  119. debug("polling got data %s", data);
  120. const callback = (packet) => {
  121. // if its the first message we consider the transport open
  122. if ("opening" === this.readyState && packet.type === "open") {
  123. this.onOpen();
  124. }
  125. // if its a close packet, we close the ongoing requests
  126. if ("close" === packet.type) {
  127. this.onClose({ description: "transport closed by the server" });
  128. return false;
  129. }
  130. // otherwise bypass onData and handle the message
  131. this.onPacket(packet);
  132. };
  133. // decode payload
  134. (0, engine_io_parser_1.decodePayload)(data, this.socket.binaryType).forEach(callback);
  135. // if an event did not trigger closing
  136. if ("closed" !== this.readyState) {
  137. // if we got data we're not polling
  138. this.polling = false;
  139. this.emitReserved("pollComplete");
  140. if ("open" === this.readyState) {
  141. this.poll();
  142. }
  143. else {
  144. debug('ignoring poll - transport state "%s"', this.readyState);
  145. }
  146. }
  147. }
  148. /**
  149. * For polling, send a close packet.
  150. *
  151. * @protected
  152. */
  153. doClose() {
  154. const close = () => {
  155. debug("writing close packet");
  156. this.write([{ type: "close" }]);
  157. };
  158. if ("open" === this.readyState) {
  159. debug("transport open - closing");
  160. close();
  161. }
  162. else {
  163. // in case we're trying to close while
  164. // handshaking is in progress (GH-164)
  165. debug("transport not open - deferring close");
  166. this.once("open", close);
  167. }
  168. }
  169. /**
  170. * Writes a packets payload.
  171. *
  172. * @param {Array} packets - data packets
  173. * @protected
  174. */
  175. write(packets) {
  176. this.writable = false;
  177. (0, engine_io_parser_1.encodePayload)(packets, (data) => {
  178. this.doWrite(data, () => {
  179. this.writable = true;
  180. this.emitReserved("drain");
  181. });
  182. });
  183. }
  184. /**
  185. * Generates uri for connection.
  186. *
  187. * @private
  188. */
  189. uri() {
  190. const schema = this.opts.secure ? "https" : "http";
  191. const query = this.query || {};
  192. // cache busting is forced
  193. if (false !== this.opts.timestampRequests) {
  194. query[this.opts.timestampParam] = (0, yeast_js_1.yeast)();
  195. }
  196. if (!this.supportsBinary && !query.sid) {
  197. query.b64 = 1;
  198. }
  199. return this.createUri(schema, query);
  200. }
  201. /**
  202. * Creates a request.
  203. *
  204. * @param {String} method
  205. * @private
  206. */
  207. request(opts = {}) {
  208. Object.assign(opts, { xd: this.xd, cookieJar: this.cookieJar }, this.opts);
  209. return new Request(this.uri(), opts);
  210. }
  211. /**
  212. * Sends data.
  213. *
  214. * @param {String} data to send.
  215. * @param {Function} called upon flush.
  216. * @private
  217. */
  218. doWrite(data, fn) {
  219. const req = this.request({
  220. method: "POST",
  221. data: data,
  222. });
  223. req.on("success", fn);
  224. req.on("error", (xhrStatus, context) => {
  225. this.onError("xhr post error", xhrStatus, context);
  226. });
  227. }
  228. /**
  229. * Starts a poll cycle.
  230. *
  231. * @private
  232. */
  233. doPoll() {
  234. debug("xhr poll");
  235. const req = this.request();
  236. req.on("data", this.onData.bind(this));
  237. req.on("error", (xhrStatus, context) => {
  238. this.onError("xhr poll error", xhrStatus, context);
  239. });
  240. this.pollXhr = req;
  241. }
  242. }
  243. exports.Polling = Polling;
  244. class Request extends component_emitter_1.Emitter {
  245. /**
  246. * Request constructor
  247. *
  248. * @param {Object} options
  249. * @package
  250. */
  251. constructor(uri, opts) {
  252. super();
  253. (0, util_js_1.installTimerFunctions)(this, opts);
  254. this.opts = opts;
  255. this.method = opts.method || "GET";
  256. this.uri = uri;
  257. this.data = undefined !== opts.data ? opts.data : null;
  258. this.create();
  259. }
  260. /**
  261. * Creates the XHR object and sends the request.
  262. *
  263. * @private
  264. */
  265. create() {
  266. var _a;
  267. const opts = (0, util_js_1.pick)(this.opts, "agent", "pfx", "key", "passphrase", "cert", "ca", "ciphers", "rejectUnauthorized", "autoUnref");
  268. opts.xdomain = !!this.opts.xd;
  269. const xhr = (this.xhr = new xmlhttprequest_js_1.XHR(opts));
  270. try {
  271. debug("xhr open %s: %s", this.method, this.uri);
  272. xhr.open(this.method, this.uri, true);
  273. try {
  274. if (this.opts.extraHeaders) {
  275. xhr.setDisableHeaderCheck && xhr.setDisableHeaderCheck(true);
  276. for (let i in this.opts.extraHeaders) {
  277. if (this.opts.extraHeaders.hasOwnProperty(i)) {
  278. xhr.setRequestHeader(i, this.opts.extraHeaders[i]);
  279. }
  280. }
  281. }
  282. }
  283. catch (e) { }
  284. if ("POST" === this.method) {
  285. try {
  286. xhr.setRequestHeader("Content-type", "text/plain;charset=UTF-8");
  287. }
  288. catch (e) { }
  289. }
  290. try {
  291. xhr.setRequestHeader("Accept", "*/*");
  292. }
  293. catch (e) { }
  294. (_a = this.opts.cookieJar) === null || _a === void 0 ? void 0 : _a.addCookies(xhr);
  295. // ie6 check
  296. if ("withCredentials" in xhr) {
  297. xhr.withCredentials = this.opts.withCredentials;
  298. }
  299. if (this.opts.requestTimeout) {
  300. xhr.timeout = this.opts.requestTimeout;
  301. }
  302. xhr.onreadystatechange = () => {
  303. var _a;
  304. if (xhr.readyState === 3) {
  305. (_a = this.opts.cookieJar) === null || _a === void 0 ? void 0 : _a.parseCookies(xhr);
  306. }
  307. if (4 !== xhr.readyState)
  308. return;
  309. if (200 === xhr.status || 1223 === xhr.status) {
  310. this.onLoad();
  311. }
  312. else {
  313. // make sure the `error` event handler that's user-set
  314. // does not throw in the same tick and gets caught here
  315. this.setTimeoutFn(() => {
  316. this.onError(typeof xhr.status === "number" ? xhr.status : 0);
  317. }, 0);
  318. }
  319. };
  320. debug("xhr data %s", this.data);
  321. xhr.send(this.data);
  322. }
  323. catch (e) {
  324. // Need to defer since .create() is called directly from the constructor
  325. // and thus the 'error' event can only be only bound *after* this exception
  326. // occurs. Therefore, also, we cannot throw here at all.
  327. this.setTimeoutFn(() => {
  328. this.onError(e);
  329. }, 0);
  330. return;
  331. }
  332. if (typeof document !== "undefined") {
  333. this.index = Request.requestsCount++;
  334. Request.requests[this.index] = this;
  335. }
  336. }
  337. /**
  338. * Called upon error.
  339. *
  340. * @private
  341. */
  342. onError(err) {
  343. this.emitReserved("error", err, this.xhr);
  344. this.cleanup(true);
  345. }
  346. /**
  347. * Cleans up house.
  348. *
  349. * @private
  350. */
  351. cleanup(fromError) {
  352. if ("undefined" === typeof this.xhr || null === this.xhr) {
  353. return;
  354. }
  355. this.xhr.onreadystatechange = empty;
  356. if (fromError) {
  357. try {
  358. this.xhr.abort();
  359. }
  360. catch (e) { }
  361. }
  362. if (typeof document !== "undefined") {
  363. delete Request.requests[this.index];
  364. }
  365. this.xhr = null;
  366. }
  367. /**
  368. * Called upon load.
  369. *
  370. * @private
  371. */
  372. onLoad() {
  373. const data = this.xhr.responseText;
  374. if (data !== null) {
  375. this.emitReserved("data", data);
  376. this.emitReserved("success");
  377. this.cleanup();
  378. }
  379. }
  380. /**
  381. * Aborts the request.
  382. *
  383. * @package
  384. */
  385. abort() {
  386. this.cleanup();
  387. }
  388. }
  389. exports.Request = Request;
  390. Request.requestsCount = 0;
  391. Request.requests = {};
  392. /**
  393. * Aborts pending requests when unloading the window. This is needed to prevent
  394. * memory leaks (e.g. when using IE) and to ensure that no spurious error is
  395. * emitted.
  396. */
  397. if (typeof document !== "undefined") {
  398. // @ts-ignore
  399. if (typeof attachEvent === "function") {
  400. // @ts-ignore
  401. attachEvent("onunload", unloadHandler);
  402. }
  403. else if (typeof addEventListener === "function") {
  404. const terminationEvent = "onpagehide" in globalThis_js_1.globalThisShim ? "pagehide" : "unload";
  405. addEventListener(terminationEvent, unloadHandler, false);
  406. }
  407. }
  408. function unloadHandler() {
  409. for (let i in Request.requests) {
  410. if (Request.requests.hasOwnProperty(i)) {
  411. Request.requests[i].abort();
  412. }
  413. }
  414. }