Skip to content

fix: add WebSocket readyState guard to prevent send on non-OPEN socket - #827

Merged
2ndalpha merged 5 commits into
masterfrom
fix/websocket-readystate-guard
Mar 19, 2026
Merged

fix: add WebSocket readyState guard to prevent send on non-OPEN socket#827
2ndalpha merged 5 commits into
masterfrom
fix/websocket-readystate-guard

Conversation

@2ndalpha

@2ndalpha 2ndalpha commented Mar 17, 2026

Copy link
Copy Markdown
Member

Summary

Closes #826 (duplicate of #740)

  • sendRawMessage() checks readyState === WebSocket.OPEN before calling socket.send(), preventing the "Still in CONNECTING state" error
  • sendMessage() triggers immediate reconnection via handleConnectionDrop() when a send fails, instead of waiting up to 32s for the ping/pong cycle
  • sendPing() checks readyState before sending ping messages (pong timeout is still always set as a reconnection safety net)
  • Re-subscription loop in connection_ack handler logs per-subscription warnings when messages are dropped

Without this fix, the thrown error could break the reconnection loop entirely, leaving the client permanently disconnected.

Test plan

  • All existing tests pass
  • 3 new tests cover: immediate reconnection on failed send, ping guard behavior, re-subscription warning logging during reconnect
  • Manual: verify reconnection works after network interruption

Closes #826

sendRawMessage() called socket.send() without checking readyState,
which throws when the WebSocket is still in CONNECTING state. This
could break the reconnection loop entirely, leaving the client
permanently disconnected.

Changes:
- sendRawMessage() now checks readyState === OPEN before sending
- sendPing() checks readyState before sending ping messages
- Re-subscription loop in connection_ack handler logs per-subscription
  warnings when messages are dropped
… socket

Instead of silently dropping messages and waiting up to 32s for the
ping/pong cycle to detect the issue, sendMessage now calls
handleConnectionDrop() immediately when sendRawMessage fails.
…e reconnect on re-subscription failure

- Remove logging from sendRawMessage so callers provide context-specific messages
- Remove redundant readyState guard in sendPing (sendRawMessage already checks)
- Trigger handleConnectionDrop() in connection_ack when re-subscriptions fail
- Use ConnectionStatus enum instead of raw string in tests
…mments

- Switch forEach to for...of with early break when first re-subscription
  fails (all subsequent sends would also fail on same dead socket)
- Wrap connection_ack case body in braces for proper let scoping
- Add comments explaining Object.defineProperty readyState overrides
  and mock-socket message delivery flush in tests
@2ndalpha
2ndalpha requested a review from KaarlisCaune March 18, 2026 07:48

@KaarlisCaune KaarlisCaune left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tested by binding to dashboard and manually triggering socket.close() - not really what this PR is fixing, but can confirm reconnection works as expected. The tests seem to do a good job verifying the non-open socket case.

@2ndalpha
2ndalpha added this pull request to the merge queue Mar 19, 2026
Merged via the queue into master with commit 755b5fd Mar 19, 2026
10 checks passed
@2ndalpha
2ndalpha deleted the fix/websocket-readystate-guard branch March 19, 2026 10:38
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.

WebSocket "Still in CONNECTING state" error

2 participants