Skip to content

feat: add websocket auto-reconnect, resubscribe, and re-auth - #6

Closed
harley-poly wants to merge 3 commits into
mainfrom
harley/sdk-ws-reliability
Closed

feat: add websocket auto-reconnect, resubscribe, and re-auth#6
harley-poly wants to merge 3 commits into
mainfrom
harley/sdk-ws-reliability

Conversation

@harley-poly

@harley-poly harley-poly commented Jun 2, 2026

Copy link
Copy Markdown
Member

summary

makes the private and markets websockets resilient to dropped connections:

  • automatic reconnect with exponential backoff + jitter on unexpected drops (autoReconnect: true by default; reconnectMaxAttempts configurable).
  • each reconnect re-signs the auth handshake with a fresh timestamp/signature (the api authenticates only at upgrade), and replays every active subscription.
  • subscriptions are tracked on subscribe/unsubscribe; a reconnect event fires after a successful reconnect.
  • reconnect stops on fatal upgrade failures (401/403/429, parsed from the ws error); transient/network failures keep retrying.
  • connection lifecycle (open/close/reconnect/error) is now owned by the base class.

note: per the server protocol, order/position/trade streams don't replay history on resubscribe (market-data and account-balance snapshots are re-sent automatically); use SUBSCRIPTION_TYPE_ORDER_SNAPSHOT to refetch open orders after a reconnect.

test plan

  • pnpm lint (biome)
  • pnpm typecheck / pnpm build
  • pnpm test (137 passed; new tests/websocket-reconnect.test.ts covers subscription tracking, resubscribe replay, transient-then-success reconnect, fatal-auth stop, and max-attempts)

Note

Medium Risk
Changes real-time connection lifecycle and auth on reconnect; incorrect behavior could miss updates or retry invalid credentials, though fatal auth stops and tests mitigate common cases.

Overview
Adds automatic WebSocket recovery for private and markets streams: on unexpected drops, BaseWebSocket reconnects with exponential backoff + jitter, re-signs the upgrade with fresh auth headers, replays tracked subscribe calls, and emits a new reconnect event. Options autoReconnect (default on) and reconnectMaxAttempts control behavior; 401/403/429 upgrade failures stop retries and surface error/close.

Connection open/close/error handling moves into the base class (subclasses drop duplicate handleError/handleClose). subscribe/unsubscribe maintain a subscription map for replay. README documents reconnect semantics and notes that order/position/trade history is not replayed on resubscribe (use order snapshot where needed).

New tests/websocket-reconnect.test.ts covers tracking, resubscribe, reconnect loop, guards, and edge cases.

Reviewed by Cursor Bugbot for commit b760be9. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread src/websocket/base.ts
Comment thread src/websocket/base.ts
Comment thread src/websocket/base.ts Outdated
Comment thread src/websocket/base.ts
Comment thread src/websocket/base.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b760be9. Configure here.

Comment thread src/websocket/base.ts
this.socket.addEventListener('error', (event: unknown) => {
this.handleError(event);
socket.addEventListener('error', (event: unknown) => {
this.emitter._emit('error', this.toError(event));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reconnect emits spurious error events per failed attempt

Medium Severity

Every call to openSocket registers a permanent error listener that emits to the user. During reconnect, each failed attempt fires this listener before the promise rejects, so users see a user-visible error event for every transient failure the reconnect loop is silently recovering from. For fatal auth failures (401/403/429), users get two error events—one from the permanent listener and another from the explicit _emit('error', …) in the reconnect catch block. The onClose handler is properly guarded by this.reconnecting, but the error listener has no such guard. The test for fatal auth expects exactly 1 onError call, which only passes because openSocket is mocked away entirely.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b760be9. Configure here.

@harley-poly

Copy link
Copy Markdown
Member Author

Closing at the author’s request as part of open-PR backlog cleanup. The branch is being retained if this work needs to be revisited.

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.

2 participants