Repository navigation
fix(#160): a failed reconnect no longer latches HTTP mode forever - #161
Conversation
A live page whose socket dropped would, if the reconnect ran while the network was still down (phone wake, Wi-Fi blip), fall back to HTTP and never try the WebSocket again: heartbeatTick and the visibility paths all skip HTTP mode / a missing transport, and HTTP gets no server push, so the page froze until a reload. WebSocketManager.connect() now says why it fell back (reason: "unavailable" from the probe, "failed" from the open error/timeout). A RE-connect that fails with "failed" marks the socket lost: actions keep going over HTTP, but the heartbeat tick (next tick, then doubling, capped at 5 min, never stopping), a return to visible, pageshow and the window `online` event all retry it. A server with no WebSocket, or a first-connect failure, still falls back for good. Tests count constructed sockets (an observable retry) per trigger; mutation-checked against the old client (9/10 fail) and per gate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H17jLWktjd8RmZim6vf8RJ
ReviewOverall this looks solid. The lost-socket state is explicit ( Minor points, none blocking:
LGTM. |
Review nits: the first timed wait is 2× the heartbeat (failure 2), not 1×; apps with data-lvt-heartbeat-ms="0" recover on events only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H17jLWktjd8RmZim6vf8RJ
ReviewOverall this looks good. The fix is well-scoped: No blocking issues. A few things to consider:
The CHANGELOG entry is clear. |
Fixes #160.
Problem
A page that was live over WebSocket loses its socket, and the reconnect runs while the network is still down: a phone waking from sleep, a Wi-Fi or tailnet blip.
WebSocketManager.connect()falls into its catch ("WebSocket open failed, falling back to HTTP"), the client setsuseHTTP = true, and the transport is nulled. Every self-healing path skips that state:heartbeatTickreturns early onuseHTTPand on a missing transport.scheduleVisibilityReconnecttimer require!useHTTP && readyState !== undefined.HTTP mode gets no server push, so the page freezes until a reload.
Fix
WebSocketManager.connect()now setsreasonon its result:"unavailable"when the probe says the server has no WebSocket,"failed"when the open errored, closed before opening, or timed out."failed"marks the socket lost. Actions keep going over HTTP as before, but every reconnect trigger now retries the socket instead of skipping it:pageshow(bfcache): retries.online: new; retries immediately.Tests
tests/heartbeat.test.tshas a new "Lost-socket recovery (A failed reconnect latches the client into HTTP mode forever — live updates stop until a full reload #160)" block with 10 tests. Every test counts constructedWebSockets, i.e. an observable retry, so a gate that still blocks the lost state fails the test instead of passing on a flag check.tests/websocket.test.tsasserts thereasonvalues.scheduleVisibilityReconnectgate fails the 4 event-trigger tests.npm testpasses, 43 suites and 897 tests.online. It lands in prereview after this is released.Test-harness note
The existing jest fetch stubs build
new Response(...), but jsdom has no globalResponse. So the HEAD probe threw, its catch assumed WebSocket was available, and no test ever readX-LiveTemplate-WebSocket. The new block's stub returns a plain object withheaders.get. I left the existing stubs alone; they're still passing by accident.Follow-up (not in this PR)
The transport has its own backoff (
scheduleReconnect: 16s cap, 10 attempts, used only withautoReconnect), and this PR adds a second one. The deeper fix is to keep the transport alive across reconnects instead of rebuilding it. That's a design change for another issue.🤖 Generated with Claude Code
https://claude.ai/code/session_01H17jLWktjd8RmZim6vf8RJ