Lifecycle hardening for IRC client behavior#179
Conversation
📝 WalkthroughWalkthroughThe changes add per-server intentional-disconnect tracking to IRCClient. When a server disconnects intentionally, the client clears the flag on reconnection and prevents reconnection logic after intentional socket closure. The Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
753542d to
316eaae
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/lib/irc/IRCClient.ts (2)
657-663: ClearintentionalDisconnectsinside the early-return branch.When
onclosetakes the intentional path, the flag is never removed — it's only cleared later by a subsequentconnect(serverId)orremoveServer(serverId). If neither call ever happens for thatserverId, the entry lingers in the set. Also, if the server later re-enters a normal connect flow through a different path, a stale flag could short-circuit a legitimateonclose. Cheap to fix:♻️ Proposed fix
socket.onclose = () => { if (this.intentionalDisconnects.has(server.id)) { + this.intentionalDisconnects.delete(server.id); this.stopWebSocketPing(server.id); this.sockets.delete(server.id); this.pendingConnections.delete(connectionKey); return; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/irc/IRCClient.ts` around lines 657 - 663, The onclose handler's early-return path uses the intentionalDisconnects set but never removes the server id, causing stale flags; update the onclose branch that checks this.intentionalDisconnects.has(server.id) to also this.intentionalDisconnects.delete(server.id) before calling this.stopWebSocketPing(server.id), this.sockets.delete(server.id), and this.pendingConnections.delete(connectionKey) so the intentional flag is cleared immediately and cannot short-circuit future connects; reference the onclose handler, intentionalDisconnects, stopWebSocketPing, sockets.delete, pendingConnections.delete, connectionKey, and server.id when locating the change.
620-622: Redundant clear — line 496 already covers reconnection; new connections get a fresh UUID.When
serverIdis provided (reconnection), the flag was already cleared at lines 495–497 before the pending-connection check. WhenserverIdis not provided,server.idis a freshly minted UUID at line 582, so the set can't contain it. This line is effectively dead code. Safe to drop, or at minimum collapse both clears into a single location after the server object exists.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/irc/IRCClient.ts` around lines 620 - 622, The call to this.intentionalDisconnects.delete(server.id) is redundant because when serverId is provided the flag is already cleared earlier via this.intentionalDisconnects.delete(serverId), and when serverId is absent server.id is a new UUID that can't be present; remove the extra delete or collapse both clears into one spot after the Server object exists (use serverId if present else server.id) to ensure intentionalDisconnects is cleared exactly once; update the block that sets this.nicks.set(server.id, nickname) accordingly so the delete occurs immediately before or after setting the nick to keep logic grouped.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/irc/IRCClient.ts`:
- Around line 730-748: The disconnect() logic fails to mark intent when a socket
exists but is CLOSING, allowing onclose to treat it as unintentional; update
disconnect() so that as soon as a socket object is present you add serverId to
this.intentionalDisconnects unconditionally, while still only sending the QUIT
and calling socket.close() when socket.readyState is CONNECTING or OPEN; ensure
you keep the existing checks around socket.send(`QUIT ...`) and socket.close()
but move the intentionalDisconnects.add(serverId) to run whenever socket is
truthy, then continue to this.sockets.delete(serverId) as before so onclose will
not trigger reconnection.
---
Nitpick comments:
In `@src/lib/irc/IRCClient.ts`:
- Around line 657-663: The onclose handler's early-return path uses the
intentionalDisconnects set but never removes the server id, causing stale flags;
update the onclose branch that checks this.intentionalDisconnects.has(server.id)
to also this.intentionalDisconnects.delete(server.id) before calling
this.stopWebSocketPing(server.id), this.sockets.delete(server.id), and
this.pendingConnections.delete(connectionKey) so the intentional flag is cleared
immediately and cannot short-circuit future connects; reference the onclose
handler, intentionalDisconnects, stopWebSocketPing, sockets.delete,
pendingConnections.delete, connectionKey, and server.id when locating the
change.
- Around line 620-622: The call to this.intentionalDisconnects.delete(server.id)
is redundant because when serverId is provided the flag is already cleared
earlier via this.intentionalDisconnects.delete(serverId), and when serverId is
absent server.id is a new UUID that can't be present; remove the extra delete or
collapse both clears into one spot after the Server object exists (use serverId
if present else server.id) to ensure intentionalDisconnects is cleared exactly
once; update the block that sets this.nicks.set(server.id, nickname) accordingly
so the delete occurs immediately before or after setting the nick to keep logic
grouped.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 81ff2dfd-e4a1-45c9-92e2-4d79c629970a
📒 Files selected for processing (2)
src/lib/irc/IRCClient.tstests/lib/ircClient.test.ts
Summary
This PR has grown beyond the original disconnect-only hardening and now prepares ObsidianIRC's transport stack for a future native backend without changing the current default behavior.
In practical terms, this PR:
IRCClientsrc/lib/socket.tssrc-tauri/src/socket.rsWhy
ObsidianIRC already has a split transport model:
wss://irc:///ircs://through the Tauri socket bridgeThe previous code still mixed transport lifecycle concerns across layers, which made a few things harder than they should be:
IRCClientThis PR fixes those lifecycle edges first, then opens a seam for the next step.
What Changed
1.
IRCClientlifecycle hardeningIn
src/lib/irc/IRCClient.ts:CLOSINGare still treated as intentional disconnectsIRCClient2. Frontend socket seam + lifecycle hardening
In
src/lib/socket.ts:TCPSocketclose handling is now idempotentCONNECTINGno longer results in a lateonopenresolveSocketProtocol()resolveSocketTarget()SocketFactorysetSocketFactory()/resetSocketFactory()This keeps the current routing intact while making future transport injection cleaner:
wss://->WebSocketWrapperirc:///ircs://->TCPSocket3. Tauri Rust bridge hardening
In
src-tauri/src/socket.rs:take_connection()disconnect()is now idempotent from the bridge's point of view4. Architecture documentation
In
ARCHITECTURE.md:Transport Layout
Tests / Validation
TypeScript / frontend transport slice
npm run test -- tests/lib/ircClient.test.ts tests/lib/socket.test.tsnpm run buildRust bridge slice
cd src-tauri && cargo testFocused coverage added in this PR
tests/lib/ircClient.test.tstests/lib/socket.test.tssrc-tauri/src/socket.rsScope Boundary
This PR does not introduce a new native transport backend yet.
Instead, it does the prep work needed so the next branch/PR can implement one more safely:
Follow-Up
The natural next branch after this one is a focused native transport backend experiment that plugs in behind the seam added here, rather than changing
IRCClientprotocol logic again.That follow-up should be able to build directly on:
resolveSocketTarget()SocketFactoryTCPSocket/bridge lifecycle hardening