Skip to content

fix: prevent dapp deeplinks opening twice on Android (#29475)#31665

Open
joaoloureirop wants to merge 2 commits into
mainfrom
fix/dapp-deeplink-dupp
Open

fix: prevent dapp deeplinks opening twice on Android (#29475)#31665
joaoloureirop wants to merge 2 commits into
mainfrom
fix/dapp-deeplink-dupp

Conversation

@joaoloureirop

Copy link
Copy Markdown
Contributor

Description

When a dapp deeplink is tapped while the MetaMask app is open in the background on Android, the dapp opens twice in the in-app browser (two tabs).

Reason for the change: On Android, a single deeplink click is delivered to the JS layer twice when the app is resumed from the background — once through React Native's Linking (url event) and once through the Branch SDK (branch.subscribe). Both deliveries flow through handleDeeplinkcheckForDeeplink → the deeplink saga → handleBrowserUrlnewTab, with no de-duplication anywhere in the chain. Each pass generates a fresh timestamp, so the Browser view opens a new tab for each one. iOS routes external URL opens through Branch only, so it is unaffected (matching the bug report: not reproducible on iOS, and not reproducible when the app is closed/cold-started).

Solution: De-duplicate at the earliest shared JS entry point (handleDeeplink). An identical URI received within a short 3s window is ignored, which collapses the back-to-back duplicate delivery into a single handling without interfering with genuine repeat navigations (the same link can still be re-opened once the window elapses). The change is platform-agnostic and also guards any cold-start getInitialURL/Branch duplicate delivery.

Changelog

CHANGELOG entry: Fixed a bug on Android where opening a dapp deep link while the app was in the background opened the dapp in two browser tabs.

Related issues

Fixes: #29475

Manual testing steps

Feature: Dapp deep link opens a single browser tab

  Scenario: User opens a dapp deep link while the app is backgrounded
    Given the MetaMask app is open and running in the background on an Android device
    And the app is unlocked

    When the user taps a dapp deep link (e.g. https://metamask.app.link/dapp/app.uniswap.org)
    Then the dapp opens in exactly one browser tab
    And no duplicate tab is created

Note: on a locally-signed dev build, App Links for metamask.app.link / link.metamask.io are not verified, so tapping a link bounces through the browser. To reproduce the exact intent delivery, background the app and inject the link directly:
adb shell am start -W -a android.intent.action.VIEW -d "https://metamask.app.link/dapp/app.uniswap.org" io.metamask

Screenshots/Recordings

Before

Screen.Recording.2026-06-12.at.20.07.59.mov

After

Screen.Recording.2026-06-12.at.20.10.29.mov

Pre-merge author checklist

Performance checks (if applicable)

  • I've tested on Android
    • Ideally on a mid-range device; emulator is acceptable
  • I've tested with a power user scenario
    • Use these power-user SRPs to import wallets with many accounts and tokens
  • I've instrumented key operations with Sentry traces for production performance metrics

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

On Android, when the app is resumed from the background, a single deeplink
click is delivered to JS twice: once via React Native `Linking` (`url` event)
and once via the Branch SDK (`branch.subscribe`). With no de-duplication, both
deliveries are processed and a dapp link opens two browser tabs. iOS routes
external URL opens through Branch only, so it is unaffected.
De-duplicate at the earliest shared JS entry point (`handleDeeplink`) by
ignoring an identical URI received within a short 3s window, which collapses
the back-to-back duplicate delivery without affecting genuine repeat
navigations.
@joaoloureirop joaoloureirop requested a review from a team as a code owner June 12, 2026 19:14
@github-actions

Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@mm-token-exchange-service mm-token-exchange-service Bot added the team-mobile-platform Mobile Platform team label Jun 12, 2026
@mm-token-exchange-service

Copy link
Copy Markdown

PR template — items to address before "Ready for review"

Warnings — informational, address before merging:

  • Pre-merge author checklist has unchecked items (e.g. "I've applied the right labels on the PR (see labeling guidelines). Not required for external contributors."). Every box must be consciously checked — see docs/readme/ready-for-review.md.

See docs/readme/ready-for-review.md for the full Definition of Ready for Review.

@github-actions github-actions Bot added size-S risk:medium AI analysis: medium risk labels Jun 12, 2026
@github-actions github-actions Bot added risk:low AI analysis: low risk and removed risk:medium AI analysis: medium risk labels Jun 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeWalletPlatform
  • Selected Performance tags: None (no tests recommended)
  • Risk Level: medium
  • AI Confidence: 88%
click to see 🤖 AI reasoning details

E2E Test Selection:
The PR adds a duplicate deeplink deduplication mechanism to handleDeeplink.ts. The change introduces module-level state (lastHandledDeeplinkUri, lastHandledDeeplinkAt) and a 3-second dedup window to prevent Android from processing the same deeplink twice (once via RN Linking, once via Branch SDK).

The only E2E test file that directly exercises deeplink navigation is tests/smoke/deeplinks/deeplink-navigation.spec.ts, which is tagged with SmokeWalletPlatform. This test covers swap deeplinks, send deeplinks, home deeplinks, and NFT deeplinks — all of which go through the modified handleDeeplink function.

The deduplication logic is an early-return guard: if the same URI arrives within 3 seconds, the second call is dropped. This is a behavioral change that could affect any deeplink-triggered flow. The SmokeWalletPlatform tag covers the deeplink navigation spec and is the appropriate tag to validate this fix.

No other Detox E2E smoke test files reference deeplinks or openURL. The performance test files that use deeplinks are WDIO-based (tests/performance/mm-connect/) and are a separate test framework — not Detox tags.

SmokeSwap and SmokeConfirmations are not selected because the deeplink-navigation spec itself is tagged only with SmokeWalletPlatform, and the change is to the deduplication guard rather than the swap/confirmation logic itself.

Performance Test Selection:
The change adds a simple timestamp comparison and early-return guard in the deeplink handler. This is not a performance-sensitive code path — it's a correctness fix for duplicate delivery on Android. No performance test tags are relevant: the WDIO-based mm-connect performance specs use deeplinks but those are not Detox performance tags, and the change does not affect app launch, login, onboarding, asset loading, swaps, account list, or any other measured performance scenario.

View GitHub Actions results

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:low AI analysis: low risk size-S team-mobile-platform Mobile Platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Dapp deep links open twice in browser

1 participant