Skip to content

core+qt: add setConnectHandler/setDisconnectHandler to IBackend - #39

Merged
Yaraslaut merged 3 commits into
masterfrom
feature/29-connect-disconnect-notifications
Aug 5, 2026
Merged

core+qt: add setConnectHandler/setDisconnectHandler to IBackend#39
Yaraslaut merged 3 commits into
masterfrom
feature/29-connect-disconnect-notifications

Conversation

@Yaraslaut

Copy link
Copy Markdown
Member

Summary

  • setReconnectHandler deliberately fires only on the second and later connects — correct for its purpose (Bridge re-registering handlers after a drop), but it leaves no notification for the first successful connect (waitForConnected() answers this but blocks — unusable on a browser/WASM main thread) or for a disconnect at all (previously only discoverable indirectly, via a later failed action).
  • Adds setConnectHandler (fires on every successful connect, first included) and setDisconnectHandler (fires whenever the transport drops, before any reconnect is scheduled, so an observer sees the disconnected state even when a retry follows immediately).
  • The issue raised an open design question — should these live on IBackend or only on QtWebSocketBackend? — and leaned toward the interface, citing setReconnectHandler's exact existing precedent (already on IBackend with a no-op default). Followed that lean directly, since the precedent is exact: connection state is a property of any transport-backed backend, a UI observing it shouldn't have to downcast to a concrete type, and a purely local backend simply never invokes either (inert, zero behavior change).
  • QtWebSocketBackend's connected/disconnected signal slots invoke the new handlers (if installed) at the same points they already invoke _reconnectHandler/schedule a reconnect. Purely additive — setReconnectHandler keeps its current semantics and every existing embedder is unaffected.

Test plan

  • tests/qt/test_qt_websocket.cpp: new end-to-end tests against a real Qt WebSocket server — setConnectHandler fires exactly once on the first connect while setReconnectHandler does not; setDisconnectHandler fires when the server is torn down mid-session (reconnect disabled to isolate the assertion).
  • Full Qt suite: 52 test cases / 344 assertions — the 50/334 pre-change baseline plus the 2 new cases, unmodified otherwise.
  • Full main suite: ./build/tests/morph_tests — all 811 test cases / 8284 assertions pass, confirming the IBackend interface addition is a no-op for every backend that doesn't override it.

Closes #29

🤖 Generated with Claude Code

@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Yaraslaut added a commit that referenced this pull request Aug 3, 2026
… no-ops

QtWebSocketBackend overrides both hooks added for #29, so the base
IBackend "store-and-ignore" bodies (backend.hpp:273,288) were never
exercised, tripping the codecov/patch gate at 0% on PR #39. LocalBackend
does not override either, so a direct call through it hits the real
default implementation.

Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com>
setReconnectHandler deliberately fires only on the second and later
connects -- it exists so Bridge can re-register handlers after a drop, and
skipping the initial connect is correct for that purpose. There was no
notification for the first successful connect (waitForConnected() answers
this but blocks, unusable on a browser/WASM main thread) or for a
disconnect at all (a client learned the socket dropped only indirectly,
when a later action failed) -- both gaps a connection-state UI needs
closed.

Add setConnectHandler (fires on every successful connect, first included)
and setDisconnectHandler (fires whenever the transport drops, before any
reconnect is scheduled -- so an observer sees the disconnected state even
when a retry follows immediately) to IBackend itself, with the same
no-op-default pattern setReconnectHandler already established, rather than
only on QtWebSocketBackend: connection state is a property of any
transport-backed backend, and a UI observing it shouldn't have to downcast
to a concrete type. A purely local backend has no meaningful connection
state, so the base-class hook is simply inert for it -- matching precedent
exactly, since setReconnectHandler already works this way.

QtWebSocketBackend's connected/disconnected QWebSocket signal slots invoke
the new handlers (if installed) at the same points they already invoke
_reconnectHandler/schedule a reconnect. Purely additive: setReconnectHandler
keeps its current semantics and every existing embedder is unaffected.

Closes #29

Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com>
… no-ops

QtWebSocketBackend overrides both hooks added for #29, so the base
IBackend "store-and-ignore" bodies (backend.hpp:273,288) were never
exercised, tripping the codecov/patch gate at 0% on PR #39. LocalBackend
does not override either, so a direct call through it hits the real
default implementation.

Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com>
@Yaraslaut
Yaraslaut force-pushed the feature/29-connect-disconnect-notifications branch from 766c651 to a5fed12 Compare August 4, 2026 16:51
Two gaps flagged during code review and left as "worth noting" rather
than fixed at the time:

1. setConnectHandler/setDisconnectHandler/setReconnectHandler's
   documented nullptr-to-clear behavior had zero test coverage. Add a
   test that installs all three, drives one full disconnect/reconnect
   cycle to confirm each fires, clears all three via nullptr, drives a
   second cycle, and confirms none of the counters move.

2. The class doc comment's claim that the disconnect handler "fires
   before any reconnect is scheduled" was unverified -- the existing
   test sidesteps it via reconnectEnabled = false. Add a test where the
   disconnect handler itself observes reconnectCount == 0 at the moment
   it runs, then confirms the reconnect handler does eventually fire
   once a fresh server comes up on the same port.

Verified locally: all 4 [issue29]-tagged cases pass across 3 repeated
runs (timing-sensitive reconnect assertions), full Qt suite (57 cases)
passes.

Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com>
@Yaraslaut
Yaraslaut merged commit 2dea490 into master Aug 5, 2026
23 checks passed
Yaraslaut added a commit that referenced this pull request Aug 7, 2026
ExportBookmarks entity-escapes rec.url via escapeHtml() (necessary --
HREF="..." is attribute-quoted, so a literal '&', '<', '>', or '"' in a
URL must be escaped to keep the output well-formed). But
parseNetscapeChunk() never decoded the HREF value back on import, only
the title -- so a URL containing '&' (an extremely common case in real
query strings, e.g. https://example.com/search?a=1&b=2) got corrupted
on any export/reimport cycle: the literal text "&amp;b=2" ended up
baked into the reimported URL instead of decoding back to "&b=2".

Fix: apply the same decodeEntities() call already used for the title
to the parsed HREF value, making import symmetric with export (option
1 from the two documented alternatives). This is simpler than making
export stop escaping the URL, and it fixes the header's other
disclosed out-of-scope case (a URL containing a literal quote) for
free, since the quote now round-trips through &quot; instead of
breaking the attribute boundary.

Also drop a dead, confusing "&#39;;" branch in decodeEntities that
could only match a stray extra ';' in the input and produced the same
output as the "&#39;" branch beneath it anyway.

Add a regression test proving the round trip: a bookmark with URL
https://example.com/search?a=1&b=2 survives ExportBookmarks ->
ImportBookmarks unchanged, plus a direct parseNetscapeChunk test for
HREF entity-decoding.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HffixfknmXdAjnM5hSGRBb
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.

No notification for first connect or for disconnect (only reconnect)

1 participant