fix(deeplink): browsers can never launch 4da:// — rename the scheme to fourda:// - #491
Merged
Merged
Conversation
…o fourda:// A URL scheme must start with a LETTER (RFC 3986 / WHATWG). Browsers therefore parse `4da://activate?key=...` as a RELATIVE path: the activate page's "Open in 4DA" button navigated to https://4da.ai/4da://activate?key=... — a 404 that also put the licence key in the server-visible URL path. The OS registry happily registers digit-first scheme names, which is why the handler side always looked healthy while no browser could ever reach it. Verified empirically: location.href='4da://x' resolves relative; 'fourda://x' attempts a protocol launch. Renamed everywhere together: tauri.conf.json, validate_deep_link_url (old scheme now explicitly rejected, with a regression test), the /activate and /signal/success handoff pages, the VS Code extension, and NETWORK.md. The paddle-webhook emails stop emitting a raw custom-scheme link entirely and use the https bridge, matching the Stripe flow. Two more latent breaks in the same chain, fixed so the button works in every app state: - running app: single-instance gains the deep-link feature, so a second instance's argv URL is forwarded into the existing validated listener (previously the callback dropped args — links only worked when the app was closed, via installer registration); - cold start: the launch URL exists before any listener attaches, so it is validated, parked, and collected by the frontend via the new take_pending_deep_link command (consumed once; webview reload cannot re-activate it). register_all() now runs at startup so dev builds and the renamed scheme self-heal instead of depending on the last installer. The frontend's new URL(payload) parsing — which would itself have thrown on a digit-first scheme — now works as written. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EnyENPW2GcG8GU7x1rJ8wN
runyourempire
enabled auto-merge (squash)
August 18, 2026 13:53
…R today The advisory (unbounded empty DATA frames) published 2026-08-17 and fails the Security audit step for ANY branch until the lockfile moves; main carries 0.4.14 too. Transitive patch bump, lockfile-only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EnyENPW2GcG8GU7x1rJ8wN
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EnyENPW2GcG8GU7x1rJ8wN
runyourempire
added a commit
that referenced
this pull request
Aug 18, 2026
… — relay them (#492) ## The file lock ate deep links while the app was running Found during live end-to-end verification of #491 on the canonical layout. With 4DA running, "Open in 4DA" spawns a second process carrying the `fourda://` URL in argv — and the **pre-Tauri single-instance file lock** exits it long before `tauri_plugin_single_instance` (the designed warm-path forwarder, wired up in #491) ever initializes. The URL was dropped silently; the app log showed no "Deep-link received". The isolated-worktree test in #491's verification had passed only by accident of layout: its two processes resolved *different* data dirs, so the file lock never collided and the plugin got its chance. In production both processes resolve the same data dir — so **every** warm deep link died at the lock. (The engine coexists by design: `initialize_pre_tauri(false)` skips the lock, so the lock holder is always a GUI instance.) ## Fix: relay through the data dir, keep the lock's guarantee The lock exists to stop a second process reaching the SQLite WAL — that property is untouched. The rejected instance now validates the URL, parks it in `data_dir/.deeplink-relay` (atomic temp+rename, timestamped), and exits without touching the database. The primary polls the path once per second: consume-at-most-once, stale entries (>60s) discarded so a crashed primary can't replay an old activation, window brought to front the way the plugin callback would have, then emitted through the same validated `deep-link-activate` path. The plugin's own forwarding stays registered — it still covers the soft-fail case where the file lock errors on I/O and the second instance reaches Tauri. ## Verification - 8/8 `single_instance` tests including 3 new relay tests (roundtrip + consume-once, stale discard, garbage tolerance) - clippy clean on the plain lib target; file-size and ghost gates pass - Post-merge plan: rebuild root binaries, live-fire `fourda://activate?key=<real key>` against the running canonical app — the exact scenario that failed — and confirm `activate_license` success via Victauri IPC trace before calling the feature production-ready. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01EnyENPW2GcG8GU7x1rJ8wN Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Browsers can never launch
4da://— the scheme itself was the bugOperator clicked Open in 4DA on the /activate bridge page and landed on
https://4da.ai/4da://activate?key=...— a 404, with the licence key in the server-visible URL path.A URL scheme must start with a letter (RFC 3986 / WHATWG URL). Browsers therefore parse
4da://...as a relative path — the handoff never reached the OS at all, in any browser. The Windows registry happily registers digit-first scheme names (the installed build had registered4da→fourda.exe), which is why the handler side always looked healthy while no browser could ever reach it.Verified empirically in Chromium before changing anything:
location.href='4da://x'resolves relative and 404s;location.href='fourda://x'attempts a real protocol launch ("scheme does not have a registered handler").The rename:
fourda://end to end, in one committauri.conf.json·validate_deep_link_url(old scheme now explicitly rejected, with a regression test explaining why) ·/activate+/signal/successhandoff pages · VS Code extension ·NETWORK.md. The paddle-webhook emails stop emitting a raw custom-scheme link entirely and link the https bridge instead, matching the Stripe flow (Gmail strips custom-scheme hrefs anyway).Two more latent breaks in the same chain, fixed so the button works in every app state
argson the floor. It now uses the plugin'sdeep-linkfeature, so the URL flows into the existing validateddeep-link://new-urllistener.app_setupnow validates and parks it; the frontend collects it on mount via the newtake_pending_deep_linkcommand (consuming — a webview reload cannot re-activate).register_all()runs at startup so dev builds and the renamed scheme self-heal instead of depending on whatever exe the last installer wrote.The frontend's
new URL(payload)parsing — which would itself have thrown on a digit-first scheme — now works exactly as written.Verification
utils::urlsuite 20/20 including the newtest_reject_retired_digit_scheme.isBrowserModelike its siblings),tsc --noEmitclean.fourda://and the retired4da://.fourdain HKCU), then live-fire the full email → /activate → Open in 4DA → licence-accepted chain.🤖 Generated with Claude Code
https://claude.ai/code/session_01EnyENPW2GcG8GU7x1rJ8wN