Repository navigation
internal(coin-app): Harden StreamManager reconnect and subscriptions - #4123
Conversation
- Cancel pending reconnect timer in cleanup() so it can't reopen a socket after unmount - Re-subscribe active products on every (re)connect; previously reconnects received nothing - Ref-count subscriptions per product so one unmount doesn't kill a shared feed - Ignore events from replaced sockets (stale onerror could close the new socket) - Fix send() param type (tsc error) and drop never-drained msgQueue - Remove unused handleMessage ctrl param and ActionTypes import Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MhCkU7NpfSuvJiu4NyRNB6
|
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4123 +/- ##
=======================================
Coverage 97.93% 97.93%
=======================================
Files 158 158
Lines 3095 3095
Branches 619 619
=======================================
Hits 3031 3031
Misses 18 18
Partials 46 46 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
- Replace pending product_ids batch with a debounced sync() that diffs active subscriptions against what the socket was sent; unsubscribes are now batched too - Detach all socket handlers in cleanup() instead of guarding each one - Spell the subscribe/unsubscribe message once in sendChannel() Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MhCkU7NpfSuvJiu4NyRNB6
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MhCkU7NpfSuvJiu4NyRNB6
Requested by Nathaniel · project thread
Follow-up to #4103.
Motivation
The coin app's
StreamManagerhad several pre-existing problems:cleanup()didn't cancel a pendingreconnect()timer, so it could open a new socket after unmount.openon the first socket.onerrorcalledthis.evtSource.close(), closing the new socket.tscerror insend()(ArrayBufferView<ArrayBufferLike>vsArrayBufferView<ArrayBuffer>), plus an unusedctrlparam and amsgQueuethat was never drained.Solution
Before:
After:
useSubscriptiondispatches SUBSCRIBE/UNSUBSCRIBE per hook mount, so a count is the right model.sync()sends the difference between active subscriptions and what the current socket was sent, so subscribes and unsubscribes are batched.onopenresets the sent set and syncs, so every reconnect re-subscribes. This replaces the never-drainedmsgQueueand thesend()with the type error.handleMessage(msg)drops the unused controller.The docs snippet in
docs/core/concepts/managers.mdhas no reconnect,send()orhandleMessage, so it needs no change. This is a private example, so there's no changeset.Validated with
tsc --noEmitinexamples/coin-app(thesend()error reproduced before the change and is gone after), eslint, prettier, and a mock-WebSocket simulation of subscribe/unsubscribe batching, reconnect re-subscription and cleanup.🤖 Generated with Claude Code
https://claude.ai/code/session_01MhCkU7NpfSuvJiu4NyRNB6
Note
Low Risk
Changes are confined to the coin-app example’s WebSocket manager; no auth, data persistence, or shared library API surface.
Overview
Hardens the coin-app
StreamManagerWebSocket lifecycle and subscription handling so reconnects, multi-component usage, and teardown behave correctly.Subscription state moves from a flat
product_idslist and never-drainedmsgQueue/send()to ref-counted products plus a debouncedsync()that batches subscribe/unsubscribe deltas against what the current socket already has;onopenclearssentand re-syncs so every reconnect restores active feeds. UNSUBSCRIBE goes throughunsubscribe()instead of a one-off wire message, so one hook unmounting no longer drops a product other components still use.cleanup()now nulls all socket handlers (avoiding a stale socket closing a replacement after init), cancels pending reconnect and sync timers, andreconnect()stores its timeout id for that cancellation.handleMessagedrops the unused controller argument; unusedActionTypesimport is removed.Reviewed by Cursor Bugbot for commit b9e3d22. Bugbot is set up for automated code reviews on this repo. Configure here.