Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 0 deletions app/constants/deeplinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export enum ACTIONS {
ONBOARDING = 'onboarding',
TRENDING = 'trending',
EARN_MUSD = 'earn-musd',
NFT = 'nft',
}

export const PREFIXES = {
Expand Down Expand Up @@ -77,5 +78,6 @@ export const PREFIXES = {
[ACTIONS.CARD_HOME]: '',
[ACTIONS.TRENDING]: '',
[ACTIONS.EARN_MUSD]: '',
[ACTIONS.NFT]: '',
METAMASK: 'metamask://',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { handleNftUrl } from '../handleNftUrl';
import NavigationService from '../../../../NavigationService';
import Routes from '../../../../../constants/navigation/Routes';
import DevLogger from '../../../../SDKConnect/utils/DevLogger';
import Logger from '../../../../../util/Logger';

jest.mock('../../../../NavigationService');
jest.mock('../../../../SDKConnect/utils/DevLogger');
jest.mock('../../../../../util/Logger');

describe('handleNftUrl', () => {
let mockNavigate: jest.Mock;

beforeEach(() => {
jest.clearAllMocks();

mockNavigate = jest.fn();
NavigationService.navigation = {
navigate: mockNavigate,
} as unknown as typeof NavigationService.navigation;

(DevLogger.log as jest.Mock) = jest.fn();
(Logger.error as jest.Mock) = jest.fn();
});

it('navigates to NFTS_FULL_VIEW', () => {
handleNftUrl();

expect(mockNavigate).toHaveBeenCalledWith(Routes.WALLET.NFTS_FULL_VIEW);
});

it('logs start of deeplink handling', () => {
handleNftUrl();

expect(DevLogger.log).toHaveBeenCalledWith(
'[handleNftUrl] Starting NFT deeplink handling',
);
});

it('falls back to WALLET.HOME on navigation error', () => {
mockNavigate.mockImplementationOnce(() => {
throw new Error('Navigation error');
});

handleNftUrl();

expect(mockNavigate).toHaveBeenCalledTimes(2);
expect(mockNavigate).toHaveBeenLastCalledWith(Routes.WALLET.HOME);
});

it('logs error when navigation fails', () => {
const error = new Error('Navigation error');
mockNavigate.mockImplementationOnce(() => {
throw error;
});

handleNftUrl();

expect(DevLogger.log).toHaveBeenCalledWith(
'[handleNftUrl] Failed to handle NFT deeplink:',
error,
);
expect(Logger.error).toHaveBeenCalledWith(
error,
'[handleNftUrl] Error handling NFT deeplink',
);
});

it('logs error when fallback navigation also fails', () => {
const primaryError = new Error('Primary navigation error');
const fallbackError = new Error('Fallback navigation error');
mockNavigate
.mockImplementationOnce(() => {
throw primaryError;
})
.mockImplementationOnce(() => {
throw fallbackError;
});

handleNftUrl();

expect(Logger.error).toHaveBeenCalledWith(
primaryError,
'[handleNftUrl] Error handling NFT deeplink',
);
expect(Logger.error).toHaveBeenCalledWith(
fallbackError,
'[handleNftUrl] Failed to navigate to fallback screen',
);
});
});
31 changes: 31 additions & 0 deletions app/core/DeeplinkManager/handlers/legacy/handleNftUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import NavigationService from '../../../NavigationService';
import Routes from '../../../../constants/navigation/Routes';
import DevLogger from '../../../SDKConnect/utils/DevLogger';
import Logger from '../../../../util/Logger';

/**
* NFT deeplink handler
*
* Supported URL formats:
* - https://link.metamask.io/nft
* - https://metamask.io/nft (mapped via Branch)
*/
export const handleNftUrl = () => {
DevLogger.log('[handleNftUrl] Starting NFT deeplink handling');

try {
NavigationService.navigation.navigate(Routes.WALLET.NFTS_FULL_VIEW);
} catch (error) {
DevLogger.log('[handleNftUrl] Failed to handle NFT deeplink:', error);
Logger.error(error as Error, '[handleNftUrl] Error handling NFT deeplink');

try {
NavigationService.navigation.navigate(Routes.WALLET.HOME);
} catch (navError) {
Logger.error(
navError as Error,
'[handleNftUrl] Failed to navigate to fallback screen',
);
}
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { handleCardOnboarding } from './handleCardOnboarding';
import { handleCardHome } from './handleCardHome';
import { handleTrendingUrl } from './handleTrendingUrl';
import { handleEarnMusd } from './handleEarnMusd';
import { handleNftUrl } from './handleNftUrl';
import { RampType } from '../../../../reducers/fiatOrders/types';
import { SHIELD_WEBSITE_URL } from '../../../../constants/shield';
import {
Expand Down Expand Up @@ -82,6 +83,7 @@ const SUPPORTED_ACTIONS = {
TRENDING: ACTIONS.TRENDING,
SHIELD: ACTIONS.SHIELD,
EARN_MUSD: ACTIONS.EARN_MUSD,
NFT: ACTIONS.NFT,
// MetaMask SDK specific actions
ANDROID_SDK: ACTIONS.ANDROID_SDK,
CONNECT: ACTIONS.CONNECT,
Expand Down Expand Up @@ -597,6 +599,10 @@ async function handleUniversalLink({
handleEarnMusd();
break;
}
case SUPPORTED_ACTIONS.NFT: {
handleNftUrl();
break;
}
}
}

Expand Down
1 change: 1 addition & 0 deletions app/core/DeeplinkManager/types/deepLink.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export const SUPPORTED_ACTIONS = [
ACTIONS.CARD_ONBOARDING,
ACTIONS.CARD_HOME,
ACTIONS.SHIELD,
ACTIONS.NFT,
] as const satisfies readonly ACTIONS[];

export type SupportedAction = (typeof SUPPORTED_ACTIONS)[number];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export enum DeepLinkRoute {
ENABLE_CARD_BUTTON = 'enable-card-button',
CARD_ONBOARDING = 'card-onboarding',
CARD_HOME = 'card-home',
NFT = 'nft',
INVALID = 'invalid',
}

Expand Down
17 changes: 17 additions & 0 deletions app/core/DeeplinkManager/util/deeplinks/deepLinkAnalytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,18 @@ const extractShieldProperties = (
// SHIELD route doesn't have sensitive parameters to extract
};

/**
* Extract properties for NFT route
* @param urlParams - URL parameters
* @param sensitiveProps - Object to add properties to
*/
const extractNftProperties = (
_urlParams: UrlParamValues,
_sensitiveProps: Record<string, string>,
): void => {
// NFT route doesn't have sensitive parameters to extract
};

/**
* Extract properties for INVALID route
* No properties to extract, this function is a placeholder
Expand Down Expand Up @@ -483,6 +495,7 @@ const routeExtractors: Record<
[DeepLinkRoute.ENABLE_CARD_BUTTON]: extractEnableCardButtonProperties,
[DeepLinkRoute.CARD_ONBOARDING]: extractCardOnboardingProperties,
[DeepLinkRoute.CARD_HOME]: extractCardHomeProperties,
[DeepLinkRoute.NFT]: extractNftProperties,
[DeepLinkRoute.INVALID]: extractInvalidProperties,
};

Expand Down Expand Up @@ -615,6 +628,8 @@ export const mapSupportedActionToRoute = (
return DeepLinkRoute.CARD_ONBOARDING;
case ACTIONS.CARD_HOME:
return DeepLinkRoute.CARD_HOME;
case ACTIONS.NFT:
return DeepLinkRoute.NFT;
default:
return DeepLinkRoute.INVALID;
}
Expand Down Expand Up @@ -667,6 +682,8 @@ export const extractRouteFromUrl = (url: string): DeepLinkRoute => {
return DeepLinkRoute.CARD_ONBOARDING;
case 'card-home':
return DeepLinkRoute.CARD_HOME;
case 'nft':
return DeepLinkRoute.NFT;
case undefined: // Empty path (no segments after filtering)
return DeepLinkRoute.HOME;
default:
Expand Down
Loading