engine.io icon indicating copy to clipboard operation
engine.io copied to clipboard

The cache busting t query parameter does not work if multiple connections to the server are made

Open koush opened this issue 2 years ago • 2 comments

This bug seems unique to Safari deduplicating identical concurrent network requests across embedded iframes.

The t query parameter is a timestamp that is intended for use with cache busting. This cache busting does not work if multiple iframe connections are made to the same server within the same page at the same time. I think this issue is specifically limited to limited to yeast generation in iframes, in Safari.

My use case creates a connection per iframe (for example, lets say 4). These 4 iframes all load at the same time and t value is the same for each iframe. Safari uses the same network executor for each iframe within the main page. Since the same request is for each iframe (including the t value), Safari decides to execute the request once and return the same sid response to all 4 frames. This causes three of the engine io connections to fail with a 400 error on subsequent xhr.

I am adding my own cache busting query parameter to fix this (as well as confirm the underlying pseudorandom seed issue in iframes).

Expected behavior The cache busting parameter t should be actually random and not a time based deterministic value, since that fails in iframe scenarios.

Platform:

  • Safari on Mac and iOS

koush avatar Oct 10 '23 02:10 koush

Hi!

That's an... interesting behavior, thanks for the detailed analysis. Do you have a suggestion for how to fix this?

darrachequesne avatar Oct 11 '23 13:10 darrachequesne

I can't confidently suggest a fix without a deeper understanding of why engine.io uses yeast, which creates the sid based on the timestamp (or seeded by the timestamp). engine.io server and client do not seem to actually need to decode that timestamp and it's only for cache busting. I think yeast is unnecessary here.

Here's my understanding: https://stackoverflow.com/questions/48633145/what-is-the-t-query-parameter-in-a-socket-io-handshake

Here's the yeast implementation: https://github.com/unshiftio/yeast/blob/443a08ea08a10133b6f8a9db536b0d6e52c6bfe1/index.js#L51

My take is that this is not ideal. yeast will generate guaranteed unique values within a single js context, but not within multiple js contexts like my issue, multiple iframes loading all at once and timestamp lucking into the same sid. Addressing this means some sort of pseudorandom value should also be used.

I'd replace yeast altogether with something like:

// yeast-replacement.ts

// guarantee unique within a single js context, like yeast
let suffixUnique = 0;
// probably unique within multiple js contexts
const jsUnique = Math.random().toString(36).replace('.', '');

export function generateId() {
  // time based cache bust, like yeast
  const timeUnique = Date.now().toString(36);
  return timeUnique + jsUnique + (suffixUnique++).toString();
}

Or continue using yeast, and append a one time created jsUnique to every yeast generated id. That's probably the best approach.

koush avatar Oct 11 '23 16:10 koush

The format of the query parameter has been updated: https://github.com/socketio/engine.io-client/commit/b624c508325615fe5f0ba82293d14831d8861324 (included in version 6.6.0).

It now uses:

export function randomString() {
  return (
    Date.now().toString(36).substring(3) +
    Math.random().toString(36).substring(2, 5)
  );
}

Thanks!

darrachequesne avatar Jun 22 '24 07:06 darrachequesne