Skip to content

feat: add WebSocket auto-reconnection with exponential backoff#133

Merged
mahata merged 2 commits into
mainfrom
feat/ws-reconnection
Mar 26, 2026
Merged

feat: add WebSocket auto-reconnection with exponential backoff#133
mahata merged 2 commits into
mainfrom
feat/ws-reconnection

Conversation

@mahata
Copy link
Copy Markdown
Owner

@mahata mahata commented Mar 26, 2026

Summary

  • Add automatic WebSocket reconnection with exponential backoff (1s → 2s → 4s → ... → 30s cap) when the connection drops, with immediate reconnection on browser tab focus (visibilitychange) and network recovery (online event)
  • Skip reconnection when the server closes with code 1008 (unauthorized) since retrying would be pointless
  • Add a "Reconnecting in Xs..." status indicator with orange styling to give users visibility into the connection state

Changes

File What changed
hono/static/ChatPage.ts Extracted connect() function, added scheduleReconnect() / reconnectNow() / cancelScheduledReconnect() helpers, added visibilitychange and online event listeners
hono/components/ChatPage.css Added .reconnecting class (orange #ff9800)
e2e/ws-reconnection.spec.ts New E2E tests: verifies auto-reconnection after forced close and message sending after reconnection

Testing

  • pnpm lint — passed
  • pnpm build:check — passed
  • pnpm test:run — 44/44 tests passed
  • E2E tests require a running Postgres instance (run via pnpm test:e2e)

Copilot AI review requested due to automatic review settings March 26, 2026 07:28
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds client-side resiliency to the chat WebSocket by automatically reconnecting with exponential backoff, surfacing reconnect status in the UI, and validating behavior via Playwright E2E tests.

Changes:

  • Refactors the WebSocket setup into a connect() flow with reconnect scheduling (exponential backoff) and “reconnect now” triggers (visibilitychange, online).
  • Adds a “Reconnecting…” UI state and styling.
  • Introduces E2E coverage for forced close → auto-reconnect and sending messages after reconnect.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
hono/static/ChatPage.ts Implements the reconnect/backoff logic and reconnect triggers; updates connection status UI states.
hono/components/ChatPage.css Adds .reconnecting style for the new reconnecting status indicator.
e2e/ws-reconnection.spec.ts Adds Playwright tests to validate automatic reconnection and post-reconnect messaging.
Comments suppressed due to low confidence (2)

hono/static/ChatPage.ts:117

  • After a 1008 (unauthorized) close, the onclose handler returns early, but visibilitychange/online still call reconnectNow(), which will reconnect because ws.readyState is CLOSED. This defeats the intent of “skip reconnection on 1008” and can cause repeated reconnect attempts on focus/network changes. Consider tracking the last close code (or a reconnectDisabled flag) and short-circuiting reconnectNow() (and/or removing those listeners) when the last close was 1008.
    if (event.code === UNAUTHORIZED_CLOSE_CODE) {
      statusDiv.textContent = "Disconnected (unauthorized)";
      statusDiv.className = `${STATUS_CLASS} ${DISCONNECTED_CLASS}`;
      return;
    }

hono/static/ChatPage.ts:116

  • The “don’t reconnect on 1008 (unauthorized)” behavior is new and security-sensitive, but the new E2E spec only covers generic reconnect-after-close. Please add an E2E case that forces a 1008 close and asserts no automatic reconnect occurs (including via visibilitychange/online), and that the UI stays in an unauthorized/disconnected state.
    if (event.code === UNAUTHORIZED_CLOSE_CODE) {
      statusDiv.textContent = "Disconnected (unauthorized)";
      statusDiv.className = `${STATUS_CLASS} ${DISCONNECTED_CLASS}`;
      return;

Comment thread hono/static/ChatPage.ts Outdated
Comment on lines +83 to +87
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
return;
}
cancelScheduledReconnect();
reconnectDelay = INITIAL_RECONNECT_DELAY_MS;
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reconnectNow() only guards against OPEN/CONNECTING. If the current socket is in CLOSING, reconnectNow() will create a new WebSocket while the old one is still closing; when that old socket finally fires onclose, it will still run scheduleReconnect()/UI updates and can interfere with the newer connection. Consider treating CLOSING as “don’t reconnect yet”, and/or capturing the created socket in connect() and ignoring onclose/onerror events from stale socket instances (e.g., if (ws !== socket) return).

This issue also appears in the following locations of the same file:

  • line 113
  • line 113

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Reconnect automatically when the WebSocket connection drops, using
exponential backoff (1s to 30s). Also reconnect immediately when the
browser tab becomes visible or the network comes back online. Skip
reconnection attempts when the server rejects with 1008 (unauthorized).

Add E2E tests that verify reconnection behavior after a forced close.
@mahata mahata force-pushed the feat/ws-reconnection branch from b3698b8 to 41421f1 Compare March 26, 2026 07:49
Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 26, 2026

@mahata I've opened a new pull request, #135, to work on those changes. Once the pull request is ready, I'll request review from you.

…ation (#135)

* Initial plan

* fix: guard against CLOSING state and ignore stale WebSocket events

Agent-Logs-Url: https://github.com/mahata/mlack/sessions/04bf024c-81f8-4edc-9d89-c064aaec3da4

Co-authored-by: mahata <23497+mahata@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mahata <23497+mahata@users.noreply.github.com>
@mahata mahata merged commit a42cea6 into main Mar 26, 2026
3 checks passed
@mahata mahata deleted the feat/ws-reconnection branch March 26, 2026 08:03
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.

3 participants