Skip to content

fix: persist deep link across launcher self-update restart#244

Open
mihakrajnc wants to merge 1 commit into
mainfrom
fix/update_args_handling
Open

fix: persist deep link across launcher self-update restart#244
mihakrajnc wants to merge 1 commit into
mainfrom
fix/update_args_handling

Conversation

@mihakrajnc
Copy link
Copy Markdown

@mihakrajnc mihakrajnc commented Mar 30, 2026

Summary

  • Fixes deep link loss on Windows during launcher self-update: update.install() calls std::process::exit(0) in the Tauri updater, so the deep link preservation code added in dee45b5 never executes — it's dead code on Windows.
  • Persists the deep link URL to a file (deeplink-state.txt) before update.install() is called, and reads it back on startup as a fallback when no deep link is found in command-line args.
  • Cleans up the persisted file after the deep link is consumed in the launch flow to prevent stale deep links on subsequent launches.

Fixes the following issue in Explorer: decentraland/unity-explorer#7692

Context

When a user opens a deep link (e.g. decentraland://...?local-scene=true&realm=localhost:8000) and the launcher also needs to self-update:

Scenario Before this PR After this PR
Windows Deep link lost — exit(0) in updater prevents preservation code from running. NSIS /ARGS passthrough may also mangle URL-like strings with ?, &, =. Deep link persisted to file before install(), recovered on restart.
macOS Works (updater returns normally), but minor race condition if on_open_url hasn't fired yet. File-based fallback adds an extra safety net.

Changes

  1. core/src/protocols.rs — Added save_to_file(), load_from_file(), clear_file() to Protocol
  2. core/src/installs.rs — Added deeplink_state_path() helper
  3. src-tauri/src/lib.rs — Save deep link before update.install(); load from file as fallback in setup_deeplink
  4. core/src/flow.rs — Clear persisted file after deep link is consumed

Test plan

  • Verify cargo clippy and cargo test pass on core/ and src-tauri/
  • Manual test: trigger a launcher self-update while opening a deep link, verify the explorer receives the deep link after restart
  • Verify the deeplink-state.txt file is cleaned up after the deep link is consumed
  • Verify normal (non-update) deep link flow is unaffected

🤖 Generated with Claude Code

On Windows, `update.install()` calls `std::process::exit(0)` inside
the Tauri updater after launching the NSIS installer. This means the
deep link preservation code added in dee45b5 (which pushes the URL
into `env.args_os` and calls `tauri::process::restart`) never
executes — it is dead code on Windows.

The NSIS installer does pass current args via `/ARGS`, but URL-like
deep links containing `?`, `&`, `=` may get mangled when passed
unquoted through the NSIS command line.

Fix: write the deep link URL to a plain text file (`deeplink-state.txt`)
**before** calling `update.install()`, and read it back on startup as
a fallback when no deep link is found in command-line args. The file
is cleaned up after the deep link is consumed in the launch flow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 30, 2026

badge

Windows and Mac build successful in Launcher Rust!! You can find a link to the downloadable artifact below.

Name Link
Commit 530a14f
Download Plain Windows S3 dcl_launcher.exe
Download Windows S3 Decentraland_installer.exe
Download Mac S3 Decentraland_installer.dmg
Built on 2026-03-30T14:15:50Z

Copy link
Copy Markdown
Collaborator

@NickKhalow NickKhalow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address the comments

Comment thread core/src/flow.rs
};

// Clear persisted deep link file after consumption
Protocol::clear_file();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you delete the file only if deeplink opens via an existing instance ? Why do you keep the file if a new explorer instance is opened?

Comment thread core/src/protocols.rs
}
}

pub fn save_to_file(deeplink: &DeepLink) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

those implementation specific functioned don't need to be public. Let's proceed with public interface consume_deeplink() -> Option<DeepLink> and try_assign_value will write to file. Public API surface can be much simplier.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

method should be named with try_ prefix based on it's intent. Since method doesn't return an explicit Result<T, E> that needs an explanation via naming.

Comment thread src-tauri/src/lib.rs
channel.send_silent(LauncherUpdate::InstallingUpdate.into());

// Persist deeplink to file BEFORE install (install may call exit(0) on Windows)
if let Some(deeplink) = Protocol::value() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be redundant with new API surface

Comment thread CLAUDE.md

## Commit Convention

Conventional Commits: `feat:`, `fix:`, `chore:`, `style:` with optional scopes like `(release)`, `(windows)`, `(macos)`, `(auto-auth)`.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use style: convention. Where did you get that?

Comment thread core/src/protocols.rs
info!("Loaded persisted deep link from {}", path.display());
Some(content)
}
Ok(_) | Err(_) => None,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't ignore errors and better to log them

Comment thread core/src/protocols.rs
}
}

pub fn clear_file() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

method should be named with try_ prefix based on it's intent

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.

2 participants