The issue is that once the token expires or the socket is killed, the connection cannot be re-established.
My setup: React SPA using https://www.npmjs.com/package/@ughuuu/gamend
WebSocket reconnect loops forever on expired JWT — no token-refresh hook in GameRealtime
Problem
GameRealtime bakes the access JWT into the Phoenix.Socket connect params at construction time and never updates it:
// dist/realtime.js (GameRealtime constructor)
this._token = token; // stored once, never updated
var params = { token: token }; // static object, not a function
this._socket = new Phoenix.Socket(wsUrl, { params: params });
Phoenix JS replays these same params on every internal reconnect (via endPointURL()), but there is no way to update the token after construction. So when the 15-minute access JWT expires and the client reconnects (e.g. after Bandit's 5-minute idle timeout closes the transport on a backgrounded tab), Phoenix JS retries with the same expired JWT → server rejects with a 403 handshake rejection → WS onerror → reconnect loop.
Because the frontend has no gamend-level hook to refresh the token on reconnect, it must instead destroy and rebuild the entire GameRealtime instance whenever the token changes. This pushes complex recovery logic (stale-token detection, background-tab suppression, dedup/cooldown) into every consuming app's GamendRealtimeProvider.
How to fix
Phoenix JS already supports params as a function (it wraps the value in closure(), which calls it fresh on every transportConnect()). Expose this:
// In the GameRealtime constructor, accept an optional tokenProvider:
function GameRealtime(serverUrl, token, socketOpts = {}, tokenProvider) {
this._token = token;
const params = tokenProvider
? () => ({ token: tokenProvider() }) // ← called on every reconnect
: { token: token };
this._socket = new Phoenix.Socket(wsUrl, { params, ...socketOpts });
}
When tokenProvider is supplied, a reconnecting socket automatically picks up the current (refreshed) token — no instance rebuild needed. The consumer just registers a callback that returns the live access token, and Phoenix's built-in reconnect self-heals from a stale-JWT 403 without any app-level recovery heuristic.
Impact
This single capability would eliminate the need for every gamend consumer to implement tokenSig-driven socket teardown, attemptRecovery() heuristics, stale-socket generation guards, and the auth_token_accessor registration seam — all of which exist today solely because GameRealtime cannot update its token post-construction.
The issue is that once the token expires or the socket is killed, the connection cannot be re-established.
My setup: React SPA using https://www.npmjs.com/package/@ughuuu/gamend
WebSocket reconnect loops forever on expired JWT — no token-refresh hook in GameRealtime
Problem
GameRealtimebakes the access JWT into thePhoenix.Socketconnect params at construction time and never updates it:Phoenix JS replays these same
paramson every internal reconnect (viaendPointURL()), but there is no way to update the token after construction. So when the 15-minute access JWT expires and the client reconnects (e.g. after Bandit's 5-minute idle timeout closes the transport on a backgrounded tab), Phoenix JS retries with the same expired JWT → server rejects with a 403 handshake rejection → WSonerror→ reconnect loop.Because the frontend has no gamend-level hook to refresh the token on reconnect, it must instead destroy and rebuild the entire
GameRealtimeinstance whenever the token changes. This pushes complex recovery logic (stale-token detection, background-tab suppression, dedup/cooldown) into every consuming app'sGamendRealtimeProvider.How to fix
Phoenix JS already supports
paramsas a function (it wraps the value inclosure(), which calls it fresh on everytransportConnect()). Expose this:When
tokenProvideris supplied, a reconnecting socket automatically picks up the current (refreshed) token — no instance rebuild needed. The consumer just registers a callback that returns the live access token, and Phoenix's built-in reconnect self-heals from a stale-JWT 403 without any app-level recovery heuristic.Impact
This single capability would eliminate the need for every gamend consumer to implement
tokenSig-driven socket teardown,attemptRecovery()heuristics, stale-socket generation guards, and theauth_token_accessorregistration seam — all of which exist today solely becauseGameRealtimecannot update its token post-construction.