Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9e073ca
feat: initial fixes for deeplinking
MarioAslau Mar 18, 2026
aa488a5
[skip ci] Bump version number to 4063
metamaskbot Mar 18, 2026
5f67307
[skip ci] Bump version number to 4065
metamaskbot Mar 18, 2026
80c527a
feat: shortlink fix
MarioAslau Mar 18, 2026
8f78e49
[skip ci] Bump version number to 4067
metamaskbot Mar 18, 2026
4f15ab3
[skip ci] Bump version number to 4088
metamaskbot Mar 19, 2026
b245f0d
[skip ci] Bump version number to 4091
metamaskbot Mar 19, 2026
7ac15c9
feat: addressing bugbot concerns
MarioAslau Mar 19, 2026
a6d572d
[skip ci] Bump version number to 4092
metamaskbot Mar 19, 2026
2f4c80d
feat: more test coverage
MarioAslau Mar 19, 2026
bb6bc74
feat: fix deeplinking issue
MarioAslau Mar 19, 2026
3de37a9
[skip ci] Bump version number to 4097
metamaskbot Mar 19, 2026
456b59d
[skip ci] Bump version number to 4099
metamaskbot Mar 19, 2026
1184a3d
feat: small changes
MarioAslau Mar 19, 2026
044dc99
feat: bugbot
MarioAslau Mar 19, 2026
92a0ab0
feat: removed logs
MarioAslau Mar 19, 2026
7d5bd3b
feat: fix x deeplink issue
MarioAslau Mar 19, 2026
bc43794
[skip ci] Bump version number to 4108
metamaskbot Mar 19, 2026
6d59a24
feat: revert version numbers
MarioAslau Mar 20, 2026
3306ca2
Merge branch 'feat/deeplink-cleanup' of https://github.com/MetaMask/m…
MarioAslau Mar 20, 2026
f0418e4
feat: fix lint
MarioAslau Mar 20, 2026
ac8de52
Merge branch 'main' into feat/deeplink-cleanup
MarioAslau Mar 20, 2026
d09c731
feat: timeout in finally block
MarioAslau Mar 20, 2026
5994941
feat : bugbot
MarioAslau Mar 20, 2026
17a788d
Merge branch 'main' into feat/deeplink-cleanup
MarioAslau Mar 20, 2026
e7ecd63
feat: addressing cursor bot comment
MarioAslau Mar 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionName "7.71.0"
versionCode 3607
versionCode 4088
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
manifestPlaceholders.MM_BRANCH_KEY_TEST = "$System.env.MM_BRANCH_KEY_TEST"
Expand Down
131 changes: 112 additions & 19 deletions app/core/DeeplinkManager/DeeplinkManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import NavigationService from '../NavigationService';
import SharedDeeplinkManager, {
DeeplinkManager,
rewriteBranchUri,
isBranchDomainUrl,
} from './DeeplinkManager';
import type { BranchParams } from './types/deepLinkAnalytics.types';
import { handleDeeplink } from './handlers/legacy/handleDeeplink';
Expand Down Expand Up @@ -299,18 +300,33 @@ describe('rewriteBranchUri', () => {
);
});

it('returns uri unchanged when +clicked_branch_link is false', () => {
it('returns undefined when +clicked_branch_link is false', () => {
const uri = 'https://metamask.app.link/swap';
expect(
rewriteBranchUri(uri, { '+clicked_branch_link': false } as BranchParams),
).toBe(uri);
).toBeUndefined();
});

it('returns uri unchanged when $deeplink_path is missing', () => {
it('returns undefined when $deeplink_path is missing', () => {
const uri = 'https://metamask.app.link/swap';
expect(
rewriteBranchUri(uri, { '+clicked_branch_link': true } as BranchParams),
).toBe(uri);
).toBeUndefined();
});

it('returns undefined when uri is undefined', () => {
expect(
rewriteBranchUri(undefined, {
'+clicked_branch_link': true,
$deeplink_path: 'swap',
} as BranchParams),
).toBeUndefined();
});

it('returns undefined when params is undefined', () => {
expect(
rewriteBranchUri('https://metamask.app.link/swap', undefined),
).toBeUndefined();
});
});

Expand All @@ -326,14 +342,14 @@ describe('DeeplinkManager.start Branch deeplink handling', () => {
expect(branch.getLatestReferringParams).toHaveBeenCalledTimes(1);
});

it('processes cold start deeplink when non-branch link is found', async () => {
const mockDeeplink = 'https://link.metamask.io/home';
it('does not process cold start deeplink when no rewrite is possible', async () => {
(branch.getLatestReferringParams as jest.Mock).mockResolvedValue({
'+non_branch_link': mockDeeplink,
'~referring_link': 'https://metamask-alternate.app.link/abc123',
'+clicked_branch_link': false,
});
DeeplinkManager.start();
await new Promise((resolve) => setImmediate(resolve));
expect(handleDeeplink).toHaveBeenCalledWith({ uri: mockDeeplink });
expect(handleDeeplink).not.toHaveBeenCalled();
});

it('rewrites cold start Branch link using $deeplink_path from getLatestReferringParams', async () => {
Expand All @@ -350,17 +366,6 @@ describe('DeeplinkManager.start Branch deeplink handling', () => {
});
});

it('falls back to +non_branch_link on cold start when +clicked_branch_link is false', async () => {
const mockDeeplink = 'https://link.metamask.io/home';
(branch.getLatestReferringParams as jest.Mock).mockResolvedValue({
'+clicked_branch_link': false,
'+non_branch_link': mockDeeplink,
});
DeeplinkManager.start();
await new Promise((resolve) => setImmediate(resolve));
expect(handleDeeplink).toHaveBeenCalledWith({ uri: mockDeeplink });
});

it('subscribes to Branch deeplink events', async () => {
DeeplinkManager.start();
expect(branch.subscribe).toHaveBeenCalled();
Expand Down Expand Up @@ -440,3 +445,91 @@ describe('DeeplinkManager.start Branch deeplink handling', () => {
});
});
});

Comment thread
cursor[bot] marked this conversation as resolved.
describe('isBranchDomainUrl', () => {
it('returns true for metamask.app.link URLs', () => {
expect(isBranchDomainUrl('https://metamask.app.link/abc123')).toBe(true);
});

it('returns true for metamask-alternate.app.link URLs', () => {
expect(
isBranchDomainUrl('https://metamask-alternate.app.link/abc123'),
).toBe(true);
});

it('returns false for link.metamask.io URLs', () => {
expect(isBranchDomainUrl('https://link.metamask.io/swap')).toBe(false);
});

it('returns false for link-test.metamask.io URLs', () => {
expect(isBranchDomainUrl('https://link-test.metamask.io/buy')).toBe(false);
});

it('returns false for metamask:// custom scheme', () => {
expect(isBranchDomainUrl('metamask://swap')).toBe(false);
});

it('returns false for invalid URLs', () => {
expect(isBranchDomainUrl('not-a-url')).toBe(false);
});
});

describe('DeeplinkManager.start Linking API filters Branch domain URLs', () => {
let mockGetInitialURL: jest.Mock;
let mockAddEventListener: jest.Mock;

beforeEach(() => {
jest.clearAllMocks();
(branch.getLatestReferringParams as jest.Mock).mockResolvedValue({});

const { Linking } = jest.requireMock('react-native');
mockGetInitialURL = Linking.getInitialURL as jest.Mock;
mockAddEventListener = Linking.addEventListener as jest.Mock;
});

it('skips Branch domain URLs from Linking.getInitialURL', async () => {
mockGetInitialURL.mockResolvedValue('https://metamask.app.link/abc123');

DeeplinkManager.start();
await new Promise((resolve) => setImmediate(resolve));

expect(handleDeeplink).not.toHaveBeenCalled();
});

it('processes non-Branch URLs from Linking.getInitialURL', async () => {
mockGetInitialURL.mockResolvedValue(
'https://link.metamask.io/swap?from=ETH',
);

DeeplinkManager.start();
await new Promise((resolve) => setImmediate(resolve));

expect(handleDeeplink).toHaveBeenCalledWith({
uri: 'https://link.metamask.io/swap?from=ETH',
});
});

it('skips Branch domain URLs from Linking.addEventListener', () => {
DeeplinkManager.start();

const urlCallback = mockAddEventListener.mock.calls.find(
(call: unknown[]) => call[0] === 'url',
)?.[1];
expect(urlCallback).toBeDefined();

urlCallback({ url: 'https://metamask-alternate.app.link/xyz' });
expect(handleDeeplink).not.toHaveBeenCalled();
});

it('processes custom scheme URLs from Linking.addEventListener', () => {
DeeplinkManager.start();

const urlCallback = mockAddEventListener.mock.calls.find(
(call: unknown[]) => call[0] === 'url',
)?.[1];
expect(urlCallback).toBeDefined();

urlCallback({ url: 'metamask://buy' });
expect(handleDeeplink).toHaveBeenCalledWith({ uri: 'metamask://buy' });
});
});
Loading
Loading