ActionCable support for async url generation

Hi, ActionCable allows you to create a consumer and pass either a string or a function to generate the websocket URL. Refer to guide here: ActionCable Connect Consumer

A Rails service I work on is behind a Gateway that supports websocket connections but requires our Rails service to retrieve a presigned URL that the client then uses to connect. These URLs expire and so if a disconnection occurs currently ActionCable will just try to reconnect with the same URL which will fail. One workaround would be to provide a function to createConsumer that references a URL that can be updated:

let url = "presigned-url-1";
const consumer = createConsumer(() => url);

async function updateWebSocketURL() {
  const response = await fetch("getSignedUrl");
  const { signedUrl } = await response.json();
  url = signedUrl;
}

consumer.subscriptions.create("SomeChannel", {
  // Called when the WebSocket connection is closed.
  disconnected() {
    updateWebSocketURL(); // Check if actually expired or just blindly regenerate?
  },
);

This is pretty obtuse. If createConsumer supported async it could look something like:

const consumer = createConsumer(async () => {
  const response = await fetch("getSignedUrl");
  const { signedUrl } = await response.json();
  return signedUrl;
});

This is a real use case and I think others could benefit if they’re running their Rails service behind a Gateway.

As far as research this was the PR that initially introduced synchronous but dynamic URLs but other than that I didn’t find much discussion around ActionCable.

Biggest unknowns for me are:

  • There doesn’t appear to be any existing usage of promises or async/await syntax in the Rails JS code. ActionCable seems to specify ES6 support which should include promises so is there something else I’m missing around style or version targeting here?
  • I’m not sure how complex the changes would get - it’s simple enough to detect if what is passed to createConsumer is a function that returns a promise that needs to be awaited but maybe it’s not obvious it will call this function each time it tries to reconnect. It’s also not clear how many changes will need to happen internally to support URL generation becoming async.
  • Possible Public API change because consumer.url is a getter that callers would always expect to return a string. Unclear what the best API change would be.
  • Maybe there’s another neat solution/strategy I haven’t thought of that gets around this in a better way using the existing API!