uniapp,h5

socket.js 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757
  1. import { transports as DEFAULT_TRANSPORTS } from "./transports/index.js";
  2. import { installTimerFunctions, byteLength } from "./util.js";
  3. import { decode } from "./contrib/parseqs.js";
  4. import { parse } from "./contrib/parseuri.js";
  5. import { Emitter } from "@socket.io/component-emitter";
  6. import { protocol } from "engine.io-parser";
  7. import { createCookieJar, defaultBinaryType, nextTick, } from "./globals.node.js";
  8. import debugModule from "debug"; // debug()
  9. const debug = debugModule("engine.io-client:socket"); // debug()
  10. const withEventListeners = typeof addEventListener === "function" &&
  11. typeof removeEventListener === "function";
  12. const OFFLINE_EVENT_LISTENERS = [];
  13. if (withEventListeners) {
  14. // within a ServiceWorker, any event handler for the 'offline' event must be added on the initial evaluation of the
  15. // script, so we create one single event listener here which will forward the event to the socket instances
  16. addEventListener("offline", () => {
  17. debug("closing %d connection(s) because the network was lost", OFFLINE_EVENT_LISTENERS.length);
  18. OFFLINE_EVENT_LISTENERS.forEach((listener) => listener());
  19. }, false);
  20. }
  21. /**
  22. * This class provides a WebSocket-like interface to connect to an Engine.IO server. The connection will be established
  23. * with one of the available low-level transports, like HTTP long-polling, WebSocket or WebTransport.
  24. *
  25. * This class comes without upgrade mechanism, which means that it will keep the first low-level transport that
  26. * successfully establishes the connection.
  27. *
  28. * In order to allow tree-shaking, there are no transports included, that's why the `transports` option is mandatory.
  29. *
  30. * @example
  31. * import { SocketWithoutUpgrade, WebSocket } from "engine.io-client";
  32. *
  33. * const socket = new SocketWithoutUpgrade({
  34. * transports: [WebSocket]
  35. * });
  36. *
  37. * socket.on("open", () => {
  38. * socket.send("hello");
  39. * });
  40. *
  41. * @see SocketWithUpgrade
  42. * @see Socket
  43. */
  44. export class SocketWithoutUpgrade extends Emitter {
  45. /**
  46. * Socket constructor.
  47. *
  48. * @param {String|Object} uri - uri or options
  49. * @param {Object} opts - options
  50. */
  51. constructor(uri, opts) {
  52. super();
  53. this.binaryType = defaultBinaryType;
  54. this.writeBuffer = [];
  55. this._prevBufferLen = 0;
  56. this._pingInterval = -1;
  57. this._pingTimeout = -1;
  58. this._maxPayload = -1;
  59. /**
  60. * The expiration timestamp of the {@link _pingTimeoutTimer} object is tracked, in case the timer is throttled and the
  61. * callback is not fired on time. This can happen for example when a laptop is suspended or when a phone is locked.
  62. */
  63. this._pingTimeoutTime = Infinity;
  64. if (uri && "object" === typeof uri) {
  65. opts = uri;
  66. uri = null;
  67. }
  68. if (uri) {
  69. const parsedUri = parse(uri);
  70. opts.hostname = parsedUri.host;
  71. opts.secure =
  72. parsedUri.protocol === "https" || parsedUri.protocol === "wss";
  73. opts.port = parsedUri.port;
  74. if (parsedUri.query)
  75. opts.query = parsedUri.query;
  76. }
  77. else if (opts.host) {
  78. opts.hostname = parse(opts.host).host;
  79. }
  80. installTimerFunctions(this, opts);
  81. this.secure =
  82. null != opts.secure
  83. ? opts.secure
  84. : typeof location !== "undefined" && "https:" === location.protocol;
  85. if (opts.hostname && !opts.port) {
  86. // if no port is specified manually, use the protocol default
  87. opts.port = this.secure ? "443" : "80";
  88. }
  89. this.hostname =
  90. opts.hostname ||
  91. (typeof location !== "undefined" ? location.hostname : "localhost");
  92. this.port =
  93. opts.port ||
  94. (typeof location !== "undefined" && location.port
  95. ? location.port
  96. : this.secure
  97. ? "443"
  98. : "80");
  99. this.transports = [];
  100. this._transportsByName = {};
  101. opts.transports.forEach((t) => {
  102. const transportName = t.prototype.name;
  103. this.transports.push(transportName);
  104. this._transportsByName[transportName] = t;
  105. });
  106. this.opts = Object.assign({
  107. path: "/engine.io",
  108. agent: false,
  109. withCredentials: false,
  110. upgrade: true,
  111. timestampParam: "t",
  112. rememberUpgrade: false,
  113. addTrailingSlash: true,
  114. rejectUnauthorized: true,
  115. perMessageDeflate: {
  116. threshold: 1024,
  117. },
  118. transportOptions: {},
  119. closeOnBeforeunload: false,
  120. }, opts);
  121. this.opts.path =
  122. this.opts.path.replace(/\/$/, "") +
  123. (this.opts.addTrailingSlash ? "/" : "");
  124. if (typeof this.opts.query === "string") {
  125. this.opts.query = decode(this.opts.query);
  126. }
  127. if (withEventListeners) {
  128. if (this.opts.closeOnBeforeunload) {
  129. // Firefox closes the connection when the "beforeunload" event is emitted but not Chrome. This event listener
  130. // ensures every browser behaves the same (no "disconnect" event at the Socket.IO level when the page is
  131. // closed/reloaded)
  132. this._beforeunloadEventListener = () => {
  133. if (this.transport) {
  134. // silently close the transport
  135. this.transport.removeAllListeners();
  136. this.transport.close();
  137. }
  138. };
  139. addEventListener("beforeunload", this._beforeunloadEventListener, false);
  140. }
  141. if (this.hostname !== "localhost") {
  142. debug("adding listener for the 'offline' event");
  143. this._offlineEventListener = () => {
  144. this._onClose("transport close", {
  145. description: "network connection lost",
  146. });
  147. };
  148. OFFLINE_EVENT_LISTENERS.push(this._offlineEventListener);
  149. }
  150. }
  151. if (this.opts.withCredentials) {
  152. this._cookieJar = createCookieJar();
  153. }
  154. this._open();
  155. }
  156. /**
  157. * Creates transport of the given type.
  158. *
  159. * @param {String} name - transport name
  160. * @return {Transport}
  161. * @private
  162. */
  163. createTransport(name) {
  164. debug('creating transport "%s"', name);
  165. const query = Object.assign({}, this.opts.query);
  166. // append engine.io protocol identifier
  167. query.EIO = protocol;
  168. // transport name
  169. query.transport = name;
  170. // session id if we already have one
  171. if (this.id)
  172. query.sid = this.id;
  173. const opts = Object.assign({}, this.opts, {
  174. query,
  175. socket: this,
  176. hostname: this.hostname,
  177. secure: this.secure,
  178. port: this.port,
  179. }, this.opts.transportOptions[name]);
  180. debug("options: %j", opts);
  181. return new this._transportsByName[name](opts);
  182. }
  183. /**
  184. * Initializes transport to use and starts probe.
  185. *
  186. * @private
  187. */
  188. _open() {
  189. if (this.transports.length === 0) {
  190. // Emit error on next tick so it can be listened to
  191. this.setTimeoutFn(() => {
  192. this.emitReserved("error", "No transports available");
  193. }, 0);
  194. return;
  195. }
  196. const transportName = this.opts.rememberUpgrade &&
  197. SocketWithoutUpgrade.priorWebsocketSuccess &&
  198. this.transports.indexOf("websocket") !== -1
  199. ? "websocket"
  200. : this.transports[0];
  201. this.readyState = "opening";
  202. const transport = this.createTransport(transportName);
  203. transport.open();
  204. this.setTransport(transport);
  205. }
  206. /**
  207. * Sets the current transport. Disables the existing one (if any).
  208. *
  209. * @private
  210. */
  211. setTransport(transport) {
  212. debug("setting transport %s", transport.name);
  213. if (this.transport) {
  214. debug("clearing existing transport %s", this.transport.name);
  215. this.transport.removeAllListeners();
  216. }
  217. // set up transport
  218. this.transport = transport;
  219. // set up transport listeners
  220. transport
  221. .on("drain", this._onDrain.bind(this))
  222. .on("packet", this._onPacket.bind(this))
  223. .on("error", this._onError.bind(this))
  224. .on("close", (reason) => this._onClose("transport close", reason));
  225. }
  226. /**
  227. * Called when connection is deemed open.
  228. *
  229. * @private
  230. */
  231. onOpen() {
  232. debug("socket open");
  233. this.readyState = "open";
  234. SocketWithoutUpgrade.priorWebsocketSuccess =
  235. "websocket" === this.transport.name;
  236. this.emitReserved("open");
  237. this.flush();
  238. }
  239. /**
  240. * Handles a packet.
  241. *
  242. * @private
  243. */
  244. _onPacket(packet) {
  245. if ("opening" === this.readyState ||
  246. "open" === this.readyState ||
  247. "closing" === this.readyState) {
  248. debug('socket receive: type "%s", data "%s"', packet.type, packet.data);
  249. this.emitReserved("packet", packet);
  250. // Socket is live - any packet counts
  251. this.emitReserved("heartbeat");
  252. switch (packet.type) {
  253. case "open":
  254. this.onHandshake(JSON.parse(packet.data));
  255. break;
  256. case "ping":
  257. this._sendPacket("pong");
  258. this.emitReserved("ping");
  259. this.emitReserved("pong");
  260. this._resetPingTimeout();
  261. break;
  262. case "error":
  263. const err = new Error("server error");
  264. // @ts-ignore
  265. err.code = packet.data;
  266. this._onError(err);
  267. break;
  268. case "message":
  269. this.emitReserved("data", packet.data);
  270. this.emitReserved("message", packet.data);
  271. break;
  272. }
  273. }
  274. else {
  275. debug('packet received with socket readyState "%s"', this.readyState);
  276. }
  277. }
  278. /**
  279. * Called upon handshake completion.
  280. *
  281. * @param {Object} data - handshake obj
  282. * @private
  283. */
  284. onHandshake(data) {
  285. this.emitReserved("handshake", data);
  286. this.id = data.sid;
  287. this.transport.query.sid = data.sid;
  288. this._pingInterval = data.pingInterval;
  289. this._pingTimeout = data.pingTimeout;
  290. this._maxPayload = data.maxPayload;
  291. this.onOpen();
  292. // In case open handler closes socket
  293. if ("closed" === this.readyState)
  294. return;
  295. this._resetPingTimeout();
  296. }
  297. /**
  298. * Sets and resets ping timeout timer based on server pings.
  299. *
  300. * @private
  301. */
  302. _resetPingTimeout() {
  303. this.clearTimeoutFn(this._pingTimeoutTimer);
  304. const delay = this._pingInterval + this._pingTimeout;
  305. this._pingTimeoutTime = Date.now() + delay;
  306. this._pingTimeoutTimer = this.setTimeoutFn(() => {
  307. this._onClose("ping timeout");
  308. }, delay);
  309. if (this.opts.autoUnref) {
  310. this._pingTimeoutTimer.unref();
  311. }
  312. }
  313. /**
  314. * Called on `drain` event
  315. *
  316. * @private
  317. */
  318. _onDrain() {
  319. this.writeBuffer.splice(0, this._prevBufferLen);
  320. // setting prevBufferLen = 0 is very important
  321. // for example, when upgrading, upgrade packet is sent over,
  322. // and a nonzero prevBufferLen could cause problems on `drain`
  323. this._prevBufferLen = 0;
  324. if (0 === this.writeBuffer.length) {
  325. this.emitReserved("drain");
  326. }
  327. else {
  328. this.flush();
  329. }
  330. }
  331. /**
  332. * Flush write buffers.
  333. *
  334. * @private
  335. */
  336. flush() {
  337. if ("closed" !== this.readyState &&
  338. this.transport.writable &&
  339. !this.upgrading &&
  340. this.writeBuffer.length) {
  341. const packets = this._getWritablePackets();
  342. debug("flushing %d packets in socket", packets.length);
  343. this.transport.send(packets);
  344. // keep track of current length of writeBuffer
  345. // splice writeBuffer and callbackBuffer on `drain`
  346. this._prevBufferLen = packets.length;
  347. this.emitReserved("flush");
  348. }
  349. }
  350. /**
  351. * Ensure the encoded size of the writeBuffer is below the maxPayload value sent by the server (only for HTTP
  352. * long-polling)
  353. *
  354. * @private
  355. */
  356. _getWritablePackets() {
  357. const shouldCheckPayloadSize = this._maxPayload &&
  358. this.transport.name === "polling" &&
  359. this.writeBuffer.length > 1;
  360. if (!shouldCheckPayloadSize) {
  361. return this.writeBuffer;
  362. }
  363. let payloadSize = 1; // first packet type
  364. for (let i = 0; i < this.writeBuffer.length; i++) {
  365. const data = this.writeBuffer[i].data;
  366. if (data) {
  367. payloadSize += byteLength(data);
  368. }
  369. if (i > 0 && payloadSize > this._maxPayload) {
  370. debug("only send %d out of %d packets", i, this.writeBuffer.length);
  371. return this.writeBuffer.slice(0, i);
  372. }
  373. payloadSize += 2; // separator + packet type
  374. }
  375. debug("payload size is %d (max: %d)", payloadSize, this._maxPayload);
  376. return this.writeBuffer;
  377. }
  378. /**
  379. * Checks whether the heartbeat timer has expired but the socket has not yet been notified.
  380. *
  381. * Note: this method is private for now because it does not really fit the WebSocket API, but if we put it in the
  382. * `write()` method then the message would not be buffered by the Socket.IO client.
  383. *
  384. * @return {boolean}
  385. * @private
  386. */
  387. /* private */ _hasPingExpired() {
  388. if (!this._pingTimeoutTime)
  389. return true;
  390. const hasExpired = Date.now() > this._pingTimeoutTime;
  391. if (hasExpired) {
  392. debug("throttled timer detected, scheduling connection close");
  393. this._pingTimeoutTime = 0;
  394. nextTick(() => {
  395. this._onClose("ping timeout");
  396. }, this.setTimeoutFn);
  397. }
  398. return hasExpired;
  399. }
  400. /**
  401. * Sends a message.
  402. *
  403. * @param {String} msg - message.
  404. * @param {Object} options.
  405. * @param {Function} fn - callback function.
  406. * @return {Socket} for chaining.
  407. */
  408. write(msg, options, fn) {
  409. this._sendPacket("message", msg, options, fn);
  410. return this;
  411. }
  412. /**
  413. * Sends a message. Alias of {@link Socket#write}.
  414. *
  415. * @param {String} msg - message.
  416. * @param {Object} options.
  417. * @param {Function} fn - callback function.
  418. * @return {Socket} for chaining.
  419. */
  420. send(msg, options, fn) {
  421. this._sendPacket("message", msg, options, fn);
  422. return this;
  423. }
  424. /**
  425. * Sends a packet.
  426. *
  427. * @param {String} type: packet type.
  428. * @param {String} data.
  429. * @param {Object} options.
  430. * @param {Function} fn - callback function.
  431. * @private
  432. */
  433. _sendPacket(type, data, options, fn) {
  434. if ("function" === typeof data) {
  435. fn = data;
  436. data = undefined;
  437. }
  438. if ("function" === typeof options) {
  439. fn = options;
  440. options = null;
  441. }
  442. if ("closing" === this.readyState || "closed" === this.readyState) {
  443. return;
  444. }
  445. options = options || {};
  446. options.compress = false !== options.compress;
  447. const packet = {
  448. type: type,
  449. data: data,
  450. options: options,
  451. };
  452. this.emitReserved("packetCreate", packet);
  453. this.writeBuffer.push(packet);
  454. if (fn)
  455. this.once("flush", fn);
  456. this.flush();
  457. }
  458. /**
  459. * Closes the connection.
  460. */
  461. close() {
  462. const close = () => {
  463. this._onClose("forced close");
  464. debug("socket closing - telling transport to close");
  465. this.transport.close();
  466. };
  467. const cleanupAndClose = () => {
  468. this.off("upgrade", cleanupAndClose);
  469. this.off("upgradeError", cleanupAndClose);
  470. close();
  471. };
  472. const waitForUpgrade = () => {
  473. // wait for upgrade to finish since we can't send packets while pausing a transport
  474. this.once("upgrade", cleanupAndClose);
  475. this.once("upgradeError", cleanupAndClose);
  476. };
  477. if ("opening" === this.readyState || "open" === this.readyState) {
  478. this.readyState = "closing";
  479. if (this.writeBuffer.length) {
  480. this.once("drain", () => {
  481. if (this.upgrading) {
  482. waitForUpgrade();
  483. }
  484. else {
  485. close();
  486. }
  487. });
  488. }
  489. else if (this.upgrading) {
  490. waitForUpgrade();
  491. }
  492. else {
  493. close();
  494. }
  495. }
  496. return this;
  497. }
  498. /**
  499. * Called upon transport error
  500. *
  501. * @private
  502. */
  503. _onError(err) {
  504. debug("socket error %j", err);
  505. SocketWithoutUpgrade.priorWebsocketSuccess = false;
  506. if (this.opts.tryAllTransports &&
  507. this.transports.length > 1 &&
  508. this.readyState === "opening") {
  509. debug("trying next transport");
  510. this.transports.shift();
  511. return this._open();
  512. }
  513. this.emitReserved("error", err);
  514. this._onClose("transport error", err);
  515. }
  516. /**
  517. * Called upon transport close.
  518. *
  519. * @private
  520. */
  521. _onClose(reason, description) {
  522. if ("opening" === this.readyState ||
  523. "open" === this.readyState ||
  524. "closing" === this.readyState) {
  525. debug('socket close with reason: "%s"', reason);
  526. // clear timers
  527. this.clearTimeoutFn(this._pingTimeoutTimer);
  528. // stop event from firing again for transport
  529. this.transport.removeAllListeners("close");
  530. // ensure transport won't stay open
  531. this.transport.close();
  532. // ignore further transport communication
  533. this.transport.removeAllListeners();
  534. if (withEventListeners) {
  535. if (this._beforeunloadEventListener) {
  536. removeEventListener("beforeunload", this._beforeunloadEventListener, false);
  537. }
  538. if (this._offlineEventListener) {
  539. const i = OFFLINE_EVENT_LISTENERS.indexOf(this._offlineEventListener);
  540. if (i !== -1) {
  541. debug("removing listener for the 'offline' event");
  542. OFFLINE_EVENT_LISTENERS.splice(i, 1);
  543. }
  544. }
  545. }
  546. // set ready state
  547. this.readyState = "closed";
  548. // clear session id
  549. this.id = null;
  550. // emit close event
  551. this.emitReserved("close", reason, description);
  552. // clean buffers after, so users can still
  553. // grab the buffers on `close` event
  554. this.writeBuffer = [];
  555. this._prevBufferLen = 0;
  556. }
  557. }
  558. }
  559. SocketWithoutUpgrade.protocol = protocol;
  560. /**
  561. * This class provides a WebSocket-like interface to connect to an Engine.IO server. The connection will be established
  562. * with one of the available low-level transports, like HTTP long-polling, WebSocket or WebTransport.
  563. *
  564. * This class comes with an upgrade mechanism, which means that once the connection is established with the first
  565. * low-level transport, it will try to upgrade to a better transport.
  566. *
  567. * In order to allow tree-shaking, there are no transports included, that's why the `transports` option is mandatory.
  568. *
  569. * @example
  570. * import { SocketWithUpgrade, WebSocket } from "engine.io-client";
  571. *
  572. * const socket = new SocketWithUpgrade({
  573. * transports: [WebSocket]
  574. * });
  575. *
  576. * socket.on("open", () => {
  577. * socket.send("hello");
  578. * });
  579. *
  580. * @see SocketWithoutUpgrade
  581. * @see Socket
  582. */
  583. export class SocketWithUpgrade extends SocketWithoutUpgrade {
  584. constructor() {
  585. super(...arguments);
  586. this._upgrades = [];
  587. }
  588. onOpen() {
  589. super.onOpen();
  590. if ("open" === this.readyState && this.opts.upgrade) {
  591. debug("starting upgrade probes");
  592. for (let i = 0; i < this._upgrades.length; i++) {
  593. this._probe(this._upgrades[i]);
  594. }
  595. }
  596. }
  597. /**
  598. * Probes a transport.
  599. *
  600. * @param {String} name - transport name
  601. * @private
  602. */
  603. _probe(name) {
  604. debug('probing transport "%s"', name);
  605. let transport = this.createTransport(name);
  606. let failed = false;
  607. SocketWithoutUpgrade.priorWebsocketSuccess = false;
  608. const onTransportOpen = () => {
  609. if (failed)
  610. return;
  611. debug('probe transport "%s" opened', name);
  612. transport.send([{ type: "ping", data: "probe" }]);
  613. transport.once("packet", (msg) => {
  614. if (failed)
  615. return;
  616. if ("pong" === msg.type && "probe" === msg.data) {
  617. debug('probe transport "%s" pong', name);
  618. this.upgrading = true;
  619. this.emitReserved("upgrading", transport);
  620. if (!transport)
  621. return;
  622. SocketWithoutUpgrade.priorWebsocketSuccess =
  623. "websocket" === transport.name;
  624. debug('pausing current transport "%s"', this.transport.name);
  625. this.transport.pause(() => {
  626. if (failed)
  627. return;
  628. if ("closed" === this.readyState)
  629. return;
  630. debug("changing transport and sending upgrade packet");
  631. cleanup();
  632. this.setTransport(transport);
  633. transport.send([{ type: "upgrade" }]);
  634. this.emitReserved("upgrade", transport);
  635. transport = null;
  636. this.upgrading = false;
  637. this.flush();
  638. });
  639. }
  640. else {
  641. debug('probe transport "%s" failed', name);
  642. const err = new Error("probe error");
  643. // @ts-ignore
  644. err.transport = transport.name;
  645. this.emitReserved("upgradeError", err);
  646. }
  647. });
  648. };
  649. function freezeTransport() {
  650. if (failed)
  651. return;
  652. // Any callback called by transport should be ignored since now
  653. failed = true;
  654. cleanup();
  655. transport.close();
  656. transport = null;
  657. }
  658. // Handle any error that happens while probing
  659. const onerror = (err) => {
  660. const error = new Error("probe error: " + err);
  661. // @ts-ignore
  662. error.transport = transport.name;
  663. freezeTransport();
  664. debug('probe transport "%s" failed because of error: %s', name, err);
  665. this.emitReserved("upgradeError", error);
  666. };
  667. function onTransportClose() {
  668. onerror("transport closed");
  669. }
  670. // When the socket is closed while we're probing
  671. function onclose() {
  672. onerror("socket closed");
  673. }
  674. // When the socket is upgraded while we're probing
  675. function onupgrade(to) {
  676. if (transport && to.name !== transport.name) {
  677. debug('"%s" works - aborting "%s"', to.name, transport.name);
  678. freezeTransport();
  679. }
  680. }
  681. // Remove all listeners on the transport and on self
  682. const cleanup = () => {
  683. transport.removeListener("open", onTransportOpen);
  684. transport.removeListener("error", onerror);
  685. transport.removeListener("close", onTransportClose);
  686. this.off("close", onclose);
  687. this.off("upgrading", onupgrade);
  688. };
  689. transport.once("open", onTransportOpen);
  690. transport.once("error", onerror);
  691. transport.once("close", onTransportClose);
  692. this.once("close", onclose);
  693. this.once("upgrading", onupgrade);
  694. if (this._upgrades.indexOf("webtransport") !== -1 &&
  695. name !== "webtransport") {
  696. // favor WebTransport
  697. this.setTimeoutFn(() => {
  698. if (!failed) {
  699. transport.open();
  700. }
  701. }, 200);
  702. }
  703. else {
  704. transport.open();
  705. }
  706. }
  707. onHandshake(data) {
  708. this._upgrades = this._filterUpgrades(data.upgrades);
  709. super.onHandshake(data);
  710. }
  711. /**
  712. * Filters upgrades, returning only those matching client transports.
  713. *
  714. * @param {Array} upgrades - server upgrades
  715. * @private
  716. */
  717. _filterUpgrades(upgrades) {
  718. const filteredUpgrades = [];
  719. for (let i = 0; i < upgrades.length; i++) {
  720. if (~this.transports.indexOf(upgrades[i]))
  721. filteredUpgrades.push(upgrades[i]);
  722. }
  723. return filteredUpgrades;
  724. }
  725. }
  726. /**
  727. * This class provides a WebSocket-like interface to connect to an Engine.IO server. The connection will be established
  728. * with one of the available low-level transports, like HTTP long-polling, WebSocket or WebTransport.
  729. *
  730. * This class comes with an upgrade mechanism, which means that once the connection is established with the first
  731. * low-level transport, it will try to upgrade to a better transport.
  732. *
  733. * @example
  734. * import { Socket } from "engine.io-client";
  735. *
  736. * const socket = new Socket();
  737. *
  738. * socket.on("open", () => {
  739. * socket.send("hello");
  740. * });
  741. *
  742. * @see SocketWithoutUpgrade
  743. * @see SocketWithUpgrade
  744. */
  745. export class Socket extends SocketWithUpgrade {
  746. constructor(uri, opts = {}) {
  747. const o = typeof uri === "object" ? uri : opts;
  748. if (!o.transports ||
  749. (o.transports && typeof o.transports[0] === "string")) {
  750. o.transports = (o.transports || ["polling", "websocket", "webtransport"])
  751. .map((transportName) => DEFAULT_TRANSPORTS[transportName])
  752. .filter((t) => !!t);
  753. }
  754. super(uri, o);
  755. }
  756. }