Skip to content

fix(realtime): restore polling→upgrade transport so live displays update - #2944

Merged
jung-thomas merged 1 commit into
DEVfrom
worktree-fix-ws-polling-transport
Oct 27, 2026
Merged

jung-thomas merged 1 commit into
DEVfrom
worktree-fix-ws-polling-transport

Conversation

@jung-thomas

Copy link
Copy Markdown
Contributor

Summary

Event/kiosk displays (display-app, event-display, gameboard, app-space) stopped updating live — they only refreshed when the browser was manually reloaded. Root cause: all four socket.io clients pinned transports: ['websocket'], forcing a cold websocket-only handshake that the prod approuter rejects with engine.io TRANSPORT_HANDSHAKE_ERROR (code 3). The socket never connects, so completionTick / tutorialCompleted events never arrive. The manual-refresh "fix" is the unrelated getEventBuckets REST fetch.

This is server-side, not Akamai — confirmed in live cf logs tutorials-srv on the direct .cfapps host (CDN bypassed): every GET /socket.io/?EIO=4&transport=websocket returns 400, on both srv instances.

Fix

Revert the four clients to socket.io's default transports: ['polling', 'websocket']. Polling handshake is a normal HTTP GET/POST the approuter proxies fine; socket.io then auto-upgrades to websocket where it succeeds and silently stays on polling where it doesn't.

  • app/display-app/src/event-stream.ts (the reported kiosk)
  • hugo-apps/src/event-display/useEventStream.ts
  • hugo-apps/src/gameboard/useGameboardStream.ts
  • hugo-apps/src/app-space/useRealtimeProgress.ts

Why the old pin is obsolete

The websocket-only pin came from #2306 to dodge a multi-instance polling-POST 400 (follow-up POST hit a different srv instance lacking the sid). That was real then — but #2524 fixed it properly: JSESSIONID Gorouter affinity (srv/server.js) pins the handshake to one instance, and @socket.io/redis-adapter (tutorials-redis-websocket, bound) bridges cross-instance fan-out. So polling→upgrade is instance-safe again, and the pin now only causes the cold-upgrade 400.

Mechanism (for reviewers)

engine.io raises TRANSPORT_HANDSHAKE_ERROR at engine.io/build/server.js:148 — if (transport === "websocket" && !upgrade) — when a transport=websocket handshake with no prior sid reaches the plain-HTTP handler instead of arriving as a real HTTP Upgrade. Websocket-only with no polling pre-handshake forces exactly that path.

Verification

  • display-app tsc --noEmit: clean (only the pre-existing style.css side-effect-import stub).
  • Prod logs captured the code-3 400 directly; see commit body.
  • Runtime verification is post-deploy — needs a browser against a DEV deploy to confirm the socket reaches connected and the counter ticks without refresh. Oracle: test/load/scenarios/05-websocket-handshake.js.

Test plan

  • Deploy to DEV; open /display-app/?eventId=<live>&goalTarget=…, confirm no TRANSPORT_HANDSHAKE_ERROR in cf logs and the counter increments live without reload
  • Spot-check event-display, gameboard, app-space still connect
  • cf logs shows a successful upgrade (101) or a sustained polling session, not 400s

All four socket.io clients pinned transports:['websocket'], forcing a cold
websocket-only handshake that the prod approuter rejects with engine.io
TRANSPORT_HANDSHAKE_ERROR (code 3) — the socket never connects, so event/kiosk
displays stop updating live and only refresh when the browser is reloaded
(the reload triggers the unrelated getEventBuckets REST fetch).

Confirmed in prod cf logs on the direct .cfapps host (not Akamai): every
GET /socket.io/?EIO=4&transport=websocket returns 400, on both srv instances.

The websocket-only pin (#2306) dodged a multi-instance polling-POST 400, but
#2524 fixed that properly via JSESSIONID Gorouter affinity + the socket.io
Redis adapter, so polling→upgrade is instance-safe again. Reverting to the
socket.io default (polling, then auto-upgrade to websocket where it succeeds)
restores live updates and degrades gracefully to polling where upgrade fails.

Clients fixed: display-app, event-display, gameboard, app-space.
@jung-thomas
jung-thomas merged commit 042298e into DEV Oct 27, 2026
4 of 5 checks passed
@jung-thomas
jung-thomas deleted the worktree-fix-ws-polling-transport branch October 27, 2026 20:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant