fix(web): bound dial retries and close failed transfers
Summary
- Validate mc tokens and require a browser relay before starting network retries; do not echo token payloads in validation errors.
- Add cancellation-aware 250 ms backoff to immediate ping failures, preventing the WASM event loop from being monopolized.
- Always close an acquired send connection after read/write/half-close errors, and clear stale success state before retries.
- Run dependency-free Node send-lifecycle regression tests in the WASM CI job.
Evidence and tests
- Red/green Go regression: baseline made 19,784 immediate-error attempts in 60 ms and incorrectly called ping for an already-cancelled context; patched tests pass.
go build ./...,go vet ./...,go test -race ./web/... -count=1,go run ./cmd/tailcat-webdist, and all 7 Node lifecycle tests pass on the submitted tree 93cb433a16fa2752b884a64655b9803b447c1c7b.- Actual cloud Chrome + compiled WASM UI: malformed mc token previously froze the page for over 60 seconds; patched page rejects it immediately and remains usable after repeated clicks, clearing inputs, and a missing-DERP listener error.
- Actual browser WebTransport failure using a synthetic token for an unavailable loopback relay: UI remained editable during connection wait; at the 60-second deadline it reported timeout and reenabled Send text.
- Portable dial helpers live under web/internal/browserdial so native build ./... remains valid. Independent review also reran native build, js/wasm build, and browserdial race tests successfully.
Scope and limitations
Separate from PR #9; based directly on main 0178c542. No native netmon change included. Connection-cleanup assertions are Node fixtures, not a successful real WebTransport transfer or mid-transfer disconnect test. No live relay/peer end-to-end success claimed. The UI has no cancel control; cancellation coverage here is Go context behavior only. No deployment, release, merge, security-setting change, or real secrets involved.
Summary by cubic
Binds retry and authentication timeouts in browser transfers so malformed tokens, ping bursts, and stalled WebTransport handshakes no longer lock the UI, and failed sends always close their connections.
- Validates mc tokens and requires a relay URL before retrying; rejects invalid tokens without echoing payloads.
- Adds a 250 ms backoff between ping retries and respects context cancellation.
- Applies the authentication deadline to WebTransport control reads and writes, clearing it once the session is ready.
- Closes the send connection after read/write/half-close errors and clears stale success state on retries.
- Adds Go, Node, and WebTransport integration tests covering retry bounding, token validation, connection cleanup, and authenticated datagrams.
Summary by Sourcery
Harden browser transfers and WebTransport authentication by bounding retries, cleaning up failed connections, and enforcing handshake timeouts.
Bug Fixes:
- Prevent malformed browser connection tokens and missing relay URLs from entering retry loops, while avoiding token payloads in validation errors.
- Bound browser ping retries with cancellation-aware backoff so immediate failures cannot monopolize the WASM event loop.
- Ensure failed web transfers close acquired connections and clear stale success state before reporting failure.
- Enforce the authentication deadline across the full WebTransport control handshake and preserve authenticated sessions after handshake expiry.
Enhancements:
- Add loopback WebTransport coverage for authenticated bidirectional datagrams, disconnect cleanup, and partial handshake timeout behavior.
CI:
- Run dependency-free browser send-lifecycle tests in the WASM CI job.
Documentation:
- Document WebTransport relay regression coverage and its acceptance-test limitations.
Tests:
- Add Go regression tests for bounded and cancellable browser dialing, token validation, and relay requirements.
- Add Node lifecycle tests covering connection cleanup, failed transfers, retry state reset, and successful EOF handling.
WebTransport authentication lifecycle follow-up (2026-10-21)
- Apply the existing 15-second authentication budget to control-stream reads and writes, not only
AcceptStream; clear that deadline and cancel the authentication timer afterready. - Reproduced against real loopback QUIC/WebTransport: partial hello and partial proof both kept unauthenticated sessions alive beyond 18 seconds before the fix. With the fix both close at approximately 15 seconds.
- Added authenticated bidirectional datagram and unregister-on-disconnect coverage, including successful datagrams after the authentication budget expires. These are Go WebTransport peers, not browser/WASM or WireGuard/TCP file-transfer acceptance.
- Local verification on tree
81ea4e571fb7ca563894ba7f9d1459610b451eca: transport-source subsetgo test -racepassed (31.203s), subsetgo vetpassed, all 7 existing Node lifecycle tests passed, andgit diff --checkpassed. Independent read-only review found no blocker. - Local full-package build/tests remain unverified: official Go module downloads for compress, u-root, and lz4 returned Forbidden. The isolated transport subset used unchanged production transport files plus the exact certificate helper, without replacing dependencies or disabling TLS verification. No download-denial workaround used.
- New root-package tests use the
TestMasqueBrowserWebTransportprefix so normal tests and the existing MASQUE race CI filter both include them. Exact-head CI remains the full-suite gate.
Final exact-head validation
- Head
0347fafa4c1b5e90e2ae98e9b59fc555642a0ad0: Test run 39532383408 succeeded with all 10 jobs green: Linux/macOS/Windows build-test-vet, race on all three platforms, lint, tidy, WASM, and release-tag cross-build checks. - The first follow-up head exposed two test cleanup
errcheckfindings. These were corrected without lint suppressions; local race tests passed again (31.225s) and the final remote run above verifies the corrected head. - Browser-to-native successful file transfer, mid-transfer disconnect/retry, and public network/NAT acceptance remain unverified. The PR remains Draft.