feat: Clean up incoming deeplink Logic for Branch SDK and Linking API#27645
feat: Clean up incoming deeplink Logic for Branch SDK and Linking API#27645MarioAslau wants to merge 26 commits into
Conversation
|
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. |
NicolasMassart
left a comment
There was a problem hiding this comment.
Strong unit coverage. I’d clear the AbortController timeout in a finally though.
Also I’d still skim Sonar’s new issues on the PR even though the quality gate passed.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
🔍 Smart E2E Test Selection
click to see 🤖 AI reasoning detailsE2E Test Selection: Performance Test Selection: |
|
✅ E2E Fixture Validation — Schema is up to date |
|
tommasini
left a comment
There was a problem hiding this comment.
It seems that we should put this on hold for now https://consensys.slack.com/archives/C02U025CVU4/p1774278680610519?thread_ts=1772742579.747479&cid=C02U025CVU4




Description
Cleans up the incoming deeplink logic for the Branch SDK and React Native Linking API, and fixes the X (Twitter) iOS deeplink bug where tapping a deeplink from X lands on "this page doesn't exist" or does nothing instead of navigating to the correct screen.
Why: The deeplink entry pipeline had redundancies, dead code, a bug in
rewriteBranchUri, and a race condition where both the Linking API and Branch SDK could fire for the same incoming link. Additionally, when deeplinks arrive from X (Twitter) via Branch's Deepview intermediate page, the Branch SDK fails to resolve the short link — returning+clicked_branch_link=falseand no$deeplink_path. This left the app with an unroutable Branch short link URL (e.g.metamask-alternate.app.link/1WkF6GmE40b) that matched no supported action. For links with query params (e.g.link.metamask.io/buy?chainId=59144&amount=25), X wraps them in a generic Branch link whose Deepview returns$deeplink_path: "open"— losing both the correct path and all query params.What changed:
Fix
rewriteBranchUrireturn value — Returnsundefinedinstead of the original URI when it cannot rewrite (when+clicked_branch_linkis false or$deeplink_pathis absent). Previously returned a truthy, unresolvable Branch short link that was dispatched as a deeplink and caused 404s.Remove
.filter(Boolean)dead code — Removed unnecessary.filter(Boolean)from theMETAMASK_HOSTSarray inutil/deeplinks/index.ts. Every entry already has an||fallback to a string literal, so no entry can ever be falsy.Eliminate redundant
getLatestReferringParamscall —handleUniversalLinkwas callingbranch.getLatestReferringParams()a second time (with a 500ms timeout/Promise.race) for analytics context. Now Branch params are cached on theDeeplinkManagerinstance when first fetched and read directly inhandleUniversalLink. Removed thereact-native-branchandLoggerimports fromhandleUniversalLink.ts.Resolve Linking API / Branch SDK race condition — Added
isBranchDomainUrl()helper to identify Branch domain URLs (metamask.app.link,metamask-alternate.app.link). BothLinking.getInitialURL()andLinking.addEventListener('url')now skip Branch domain URLs, since those are exclusively handled by the Branch SDK. This prevents duplicate processing where both entry points would fire for the same link.Fix
branch.subscribefallback for unresolved short links — Previously, whenrewriteBranchUrireturnedundefinedin thebranch.subscribehandler, the code fell back toopts.uri(the raw Branch short link). This caused the app to try routing a short link path like/1WkF6GmE40bas an action, which failed. Now the fallback only usesopts.uriwhen it's NOT a Branch domain URL.Add
resolveBranchShortLinkfallback for Deepview flows — When the Branch SDK returns+non_branch_linkpointing to a Branch domain (which happens with X/Twitter Deepview links), the SDK has failed to resolve the short link. A newresolveBranchShortLink()function fetches the short link URL directly (using a crawler User-Agent so Branch returns the metadata page), follows redirects, and resolves the original deep link using a tiered fallback chain. This handles both thebranch.subscribewarm-start path and thegetBranchDeeplinkcold-start path. Includes a 3-secondAbortControllertimeout, domain validation on+non_branch_link, and.catch()on the promise chain.Add
extractBranchUrlFromDeepviewfor reliable Deepview resolution — Branch Deepview HTML embeds the full original URL in theal:ios:urlmeta tag as a base64-encoded JSON blob inside thelink_click_idparameter. This new function decodes that blob to extract the+urlfield (e.g.https://link.metamask.io/buy), which is the authoritative original URL configured in the Branch dashboard. This is the primary resolution method and is more reliable than regex-matching$deeplink_pathor parsing CTA button hrefs from the Deepview HTML. Query params from the original Branch URL (e.g.chainId,address,amount) are merged onto the resolved+url.Add
stripBranchDeepviewParamshelper — Strips Branch Deepview query params (__branch_flow_type,__branch_flow_id,__branch_mobile_deepview_type,_referrer) from URLs before resolution. Preserves MetaMask's own signed deeplink params (sig,sig_params) and user-facing params. These Deepview params are appended by Branch's intermediate page and can interfere with link resolution.Add
extractDeepviewPathfallback — Parses the Deepview CTA button href orwindow.top.locationassignment for the deeplink path. Handlesnull-prefixed paths (e.g.nulltrending),metamask://scheme, and fullhttps://link.metamask.io/URLs. Used as a last-resort fallback whenlink_click_iddecoding and$deeplink_pathregex both fail.Use crawler User-Agent for Branch metadata fetch — Branch serves different HTML based on User-Agent. Browser/app UAs get the visual Deepview page (no
al:ios:url), while crawler UAs get the metadata page withal:ios:urlcontaining thelink_click_id. Changed the fetch User-Agent tofacebookexternalhit/1.1 (MetaMask-LinkResolver)to ensure we receive the metadata page needed forlink_click_iddecoding.Changelog
CHANGELOG entry: Fixed deeplinks from X (Twitter) on iOS not landing on the correct screen by decoding Branch Deepview metadata to recover the original deep link URL with full path and query params; resolved duplicate processing race condition between Branch SDK and Linking API; added 3-second timeout on Branch link resolution fetch
Related issues
Fixes: #27140
Refs: #27139
Jira Ticket: https://consensyssoftware.atlassian.net/browse/MCWP-386?atlOrigin=eyJpIjoiNjUyYjFmMmVkYWM3NDk1MDliNTNiYzFhYTE3YTcwZWUiLCJwIjoiaiJ9
Manual testing steps
Screenshots/Recordings
Before
N/A (logic-only changes, no UI)
After
ScreenRecording_03-19-2026.20-31-28_1.1.MP4
N/A (logic-only changes, no UI)
Pre-merge author checklist
Pre-merge reviewer checklist
Note
Medium Risk
Changes core deeplink entry logic (Branch + React Native Linking) and adds network-based short-link resolution, which could affect navigation/attribution and introduce edge-case regressions despite extensive tests.
Overview
Improves incoming deeplink processing by preventing unroutable Branch short links from being dispatched:
rewriteBranchUrinow returnsundefinedwhen it can’t rewrite, andDeeplinkManager.startno longer falls back to handling raw Branch-domain URLs.Adds Branch-specific utilities to avoid duplicate processing and recover Deepview/X (Twitter) links:
isBranchDomainUrlfilters Branch domains out of the React NativeLinkingpipeline, andresolveBranchShortLinkfetches/decodes Deepview metadata (includinglink_click_id→+url) with param merging, Deepview-param stripping, and timeouts.Branch params are now cached on
DeeplinkManagerand consumed byhandleUniversalLinkfor analytics (removing extra Branch SDK calls). Tests were expanded significantly to cover cold/warm start flows, error handling, and Deepview resolution;bitrise.ymlbuild numbers were bumped.Written by Cursor Bugbot for commit e7ecd63. This will update automatically on new commits. Configure here.