From b624c508325615fe5f0ba82293d14831d8861324 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Wed, 5 Jun 2024 13:44:55 +0200 Subject: [PATCH] fix: add some randomness to the cache busting string generator The yeast() method could generate the same string twice when used in two different iframes, which can cause Safari to only send one HTTP request (deduplication) and trigger an HTTP 400 error afterwards since the two iframes share the same session ID. This new method, combining 5 chars from the timestamp and 3 chars from Math.random() should be sufficient for our use case. Related: https://github.com/socketio/engine.io/issues/690 See also: https://github.com/socketio/engine.io-client/commit/874484cc1e6a12a3083f2cf6d74a3f28813ef809 --- lib/contrib/yeast.ts | 62 ------------------------------------- lib/transports/polling.ts | 4 +-- lib/transports/websocket.ts | 5 ++- lib/util.ts | 10 ++++++ test/engine.io-client.js | 11 +++++++ 5 files changed, 25 insertions(+), 67 deletions(-) delete mode 100644 lib/contrib/yeast.ts diff --git a/lib/contrib/yeast.ts b/lib/contrib/yeast.ts deleted file mode 100644 index 71ad33eaf..000000000 --- a/lib/contrib/yeast.ts +++ /dev/null @@ -1,62 +0,0 @@ -// imported from https://github.com/unshiftio/yeast -'use strict'; - -const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_'.split('') - , length = 64 - , map = {}; -let seed = 0 - , i = 0 - , prev; - -/** - * Return a string representing the specified number. - * - * @param {Number} num The number to convert. - * @returns {String} The string representation of the number. - * @api public - */ -export function encode(num) { - let encoded = ''; - - do { - encoded = alphabet[num % length] + encoded; - num = Math.floor(num / length); - } while (num > 0); - - return encoded; -} - -/** - * Return the integer value specified by the given string. - * - * @param {String} str The string to convert. - * @returns {Number} The integer value represented by the string. - * @api public - */ -export function decode(str) { - let decoded = 0; - - for (i = 0; i < str.length; i++) { - decoded = decoded * length + map[str.charAt(i)]; - } - - return decoded; -} - -/** - * Yeast: A tiny growing id generator. - * - * @returns {String} A unique id. - * @api public - */ -export function yeast() { - const now = encode(+new Date()); - - if (now !== prev) return seed = 0, prev = now; - return now +'.'+ encode(seed++); -} - -// -// Map each character to its index. -// -for (; i < length; i++) map[alphabet[i]] = i; diff --git a/lib/transports/polling.ts b/lib/transports/polling.ts index 12e270ffd..2f09e8021 100644 --- a/lib/transports/polling.ts +++ b/lib/transports/polling.ts @@ -1,5 +1,5 @@ import { Transport } from "../transport.js"; -import { yeast } from "../contrib/yeast.js"; +import { randomString } from "../util.js"; import { encodePayload, decodePayload } from "engine.io-parser"; import debugModule from "debug"; // debug() @@ -164,7 +164,7 @@ export abstract class Polling extends Transport { // cache busting is forced if (false !== this.opts.timestampRequests) { - query[this.opts.timestampParam] = yeast(); + query[this.opts.timestampParam] = randomString(); } if (!this.supportsBinary && !query.sid) { diff --git a/lib/transports/websocket.ts b/lib/transports/websocket.ts index 7e4a0a9ce..d8b1d9505 100644 --- a/lib/transports/websocket.ts +++ b/lib/transports/websocket.ts @@ -1,6 +1,5 @@ import { Transport } from "../transport.js"; -import { yeast } from "../contrib/yeast.js"; -import { pick } from "../util.js"; +import { pick, randomString } from "../util.js"; import { encodePacket } from "engine.io-parser"; import type { Packet, RawData } from "engine.io-parser"; import { globalThisShim as globalThis, nextTick } from "../globals.node.js"; @@ -140,7 +139,7 @@ export abstract class BaseWS extends Transport { // append timestamp to URI if (this.opts.timestampRequests) { - query[this.opts.timestampParam] = yeast(); + query[this.opts.timestampParam] = randomString(); } // communicate binary support capabilities diff --git a/lib/util.ts b/lib/util.ts index e918e2b7c..bfb6a59aa 100644 --- a/lib/util.ts +++ b/lib/util.ts @@ -53,3 +53,13 @@ function utf8Length(str) { } return length; } + +/** + * Generates a random 8-characters string. + */ +export function randomString() { + return ( + Date.now().toString(36).substring(3) + + Math.random().toString(36).substring(2, 5) + ); +} diff --git a/test/engine.io-client.js b/test/engine.io-client.js index b4a290043..7f526afd4 100644 --- a/test/engine.io-client.js +++ b/test/engine.io-client.js @@ -1,5 +1,6 @@ const expect = require("expect.js"); const { Socket, protocol } = require(".."); +const { randomString } = require("../build/cjs/util.js"); const expectedPort = typeof location !== "undefined" && "https:" === location.protocol @@ -99,4 +100,14 @@ describe("engine.io-client", () => { expect(client.hostname).to.be("::1"); expect(client.port).to.be(expectedPort); }); + + it("should generate a random string", () => { + const a = randomString(); + const b = randomString(); + const c = randomString(); + + expect(a.length).to.eql(8); + expect(a).to.not.equal(b); + expect(b).to.not.equal(c); + }); });