Skip to content

Commit cc4cc5c

Browse files
authored
feat: add deeplink handler for leaderboard links (#29591)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> ## **Description** Adding a deeplink handler for leaderboard links. <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### 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](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] 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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: adds a new whitelisted deeplink action that only triggers an in-app navigation, plus unit tests and analytics route mapping updates. > > **Overview** > Adds support for the `social-leaderboard` universal link by introducing `ACTIONS.SOCIAL_LEADERBOARD` and routing it through `handleUniversalLink` to a new `handleSocialLeaderboardUrl` handler that navigates to `Routes.SOCIAL_LEADERBOARD.VIEW` (skipping the interstitial like other whitelisted actions). > > Extends deeplink typing and analytics to recognize/extract the new route (`DeepLinkRoute.SOCIAL_LEADERBOARD`), and adds unit tests covering both the handler navigation and universal-link dispatch/route extraction. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f3d2bdc. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 6684df3 commit cc4cc5c

9 files changed

Lines changed: 81 additions & 0 deletions

File tree

app/constants/deeplinks.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export enum ACTIONS {
4646
PREDICT = 'predict',
4747
ONBOARDING = 'onboarding',
4848
TRENDING = 'trending',
49+
SOCIAL_LEADERBOARD = 'social-leaderboard',
4950
SOCIAL_TRADER_POSITION = 'social-trader-position',
5051
EARN_MUSD = 'earn-musd',
5152
NFT = 'nft',
@@ -81,6 +82,7 @@ export const PREFIXES = {
8182
[ACTIONS.CARD_HOME]: '',
8283
[ACTIONS.CARD_KYC_NOTIFICATION]: '',
8384
[ACTIONS.TRENDING]: '',
85+
[ACTIONS.SOCIAL_LEADERBOARD]: '',
8486
[ACTIONS.SOCIAL_TRADER_POSITION]: '',
8587
[ACTIONS.EARN_MUSD]: '',
8688
[ACTIONS.NFT]: '',
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import NavigationService from '../../../../NavigationService';
2+
import Routes from '../../../../../constants/navigation/Routes';
3+
import { handleSocialLeaderboardUrl } from '../handleSocialLeaderboardUrl';
4+
5+
jest.mock('../../../../NavigationService', () => ({
6+
navigation: {
7+
navigate: jest.fn(),
8+
},
9+
}));
10+
11+
describe('handleSocialLeaderboardUrl', () => {
12+
const mockNavigate = NavigationService.navigation.navigate as jest.Mock;
13+
14+
beforeEach(() => {
15+
jest.clearAllMocks();
16+
});
17+
18+
it('navigates to the social leaderboard view', () => {
19+
handleSocialLeaderboardUrl();
20+
21+
expect(mockNavigate).toHaveBeenCalledWith(Routes.SOCIAL_LEADERBOARD.VIEW);
22+
});
23+
});

app/core/DeeplinkManager/handlers/legacy/__tests__/handleUniversalLink.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import handleBrowserUrl from '../handleBrowserUrl';
1616
import { DeepLinkModalLinkType } from '../../../../../components/UI/DeepLinkModal';
1717
import handleMetaMaskDeeplink from '../handleMetaMaskDeeplink';
1818
import { SHIELD_WEBSITE_URL } from '../../../../../constants/shield';
19+
import { handleSocialLeaderboardUrl } from '../handleSocialLeaderboardUrl';
1920
import { handleSocialTraderPositionUrl } from '../handleSocialTraderPositionUrl';
2021
// eslint-disable-next-line import-x/no-namespace
2122
import * as signatureUtils from '../../../utils/verifySignature';
@@ -41,6 +42,7 @@ jest.mock('../handleRewardsUrl');
4142
jest.mock('../handlePredictUrl');
4243
jest.mock('../handleFastOnboarding');
4344
jest.mock('../handleTrendingUrl');
45+
jest.mock('../handleSocialLeaderboardUrl');
4446
jest.mock('../handleSocialTraderPositionUrl');
4547
jest.mock('../../../../redux', () => ({
4648
__esModule: true,
@@ -861,6 +863,32 @@ describe('handleUniversalLink', () => {
861863
});
862864
});
863865

866+
describe('ACTIONS.SOCIAL_LEADERBOARD', () => {
867+
it('navigates to social leaderboard without showing interstitial', async () => {
868+
const leaderboardUrl = `${PROTOCOLS.HTTPS}://${AppConstants.MM_UNIVERSAL_LINK_HOST}/${ACTIONS.SOCIAL_LEADERBOARD}?ignored=true`;
869+
const leaderboardUrlObj = {
870+
...urlObj,
871+
hostname: AppConstants.MM_UNIVERSAL_LINK_HOST,
872+
href: leaderboardUrl,
873+
pathname: `/${ACTIONS.SOCIAL_LEADERBOARD}`,
874+
search: '?ignored=true',
875+
};
876+
877+
await handleUniversalLink({
878+
instance,
879+
handled,
880+
urlObj: leaderboardUrlObj,
881+
browserCallBack: mockBrowserCallBack,
882+
url: leaderboardUrl,
883+
source: 'test-source',
884+
});
885+
886+
expect(mockHandleDeepLinkModalDisplay).not.toHaveBeenCalled();
887+
expect(handleSocialLeaderboardUrl).toHaveBeenCalledTimes(1);
888+
expect(handled).toHaveBeenCalled();
889+
});
890+
});
891+
864892
describe('ACTIONS.WC', () => {
865893
const testCases = [
866894
{
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Routes from '../../../../constants/navigation/Routes';
2+
import NavigationService from '../../../NavigationService';
3+
4+
export const handleSocialLeaderboardUrl = () => {
5+
NavigationService.navigation.navigate(Routes.SOCIAL_LEADERBOARD.VIEW);
6+
};

app/core/DeeplinkManager/handlers/legacy/handleUniversalLink.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { handleCardOnboarding } from './handleCardOnboarding';
3030
import { handleCardHome } from './handleCardHome';
3131
import { handleCardKycNotification } from './handleCardKycNotification';
3232
import { handleTrendingUrl } from './handleTrendingUrl';
33+
import { handleSocialLeaderboardUrl } from './handleSocialLeaderboardUrl';
3334
import { handleSocialTraderPositionUrl } from './handleSocialTraderPositionUrl';
3435
import { handleEarnMusd } from './handleEarnMusd';
3536
import { handleAssetUrl } from './handleAssetUrl';
@@ -85,6 +86,7 @@ const SUPPORTED_ACTIONS = {
8586
CARD_HOME: ACTIONS.CARD_HOME,
8687
CARD_KYC_NOTIFICATION: ACTIONS.CARD_KYC_NOTIFICATION,
8788
TRENDING: ACTIONS.TRENDING,
89+
SOCIAL_LEADERBOARD: ACTIONS.SOCIAL_LEADERBOARD,
8890
SOCIAL_TRADER_POSITION: ACTIONS.SOCIAL_TRADER_POSITION,
8991
SHIELD: ACTIONS.SHIELD,
9092
EARN_MUSD: ACTIONS.EARN_MUSD,
@@ -118,6 +120,7 @@ const WHITELISTED_ACTIONS: SUPPORTED_ACTIONS[] = [
118120
SUPPORTED_ACTIONS.SELL,
119121
SUPPORTED_ACTIONS.SELL_CRYPTO,
120122
SUPPORTED_ACTIONS.TRENDING,
123+
SUPPORTED_ACTIONS.SOCIAL_LEADERBOARD,
121124
SUPPORTED_ACTIONS.SOCIAL_TRADER_POSITION,
122125
SUPPORTED_ACTIONS.SHIELD,
123126
SUPPORTED_ACTIONS.EARN_MUSD,
@@ -636,6 +639,10 @@ async function handleUniversalLink({
636639
});
637640
break;
638641
}
642+
case SUPPORTED_ACTIONS.SOCIAL_LEADERBOARD: {
643+
handleSocialLeaderboardUrl();
644+
break;
645+
}
639646
case SUPPORTED_ACTIONS.SOCIAL_TRADER_POSITION: {
640647
handleSocialTraderPositionUrl({
641648
actionPath: actionBasedRampPath,

app/core/DeeplinkManager/types/deepLink.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ export const SUPPORTED_ACTIONS = [
132132
ACTIONS.ONBOARDING,
133133
ACTIONS.PREDICT,
134134
ACTIONS.TRENDING,
135+
ACTIONS.SOCIAL_LEADERBOARD,
135136
ACTIONS.SOCIAL_TRADER_POSITION,
136137
ACTIONS.CARD_ONBOARDING,
137138
ACTIONS.CARD_HOME,

app/core/DeeplinkManager/types/deepLinkAnalytics.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export enum DeepLinkRoute {
5151
PREDICT = 'predict',
5252
SHIELD = 'shield',
5353
TRENDING = 'trending',
54+
SOCIAL_LEADERBOARD = 'social-leaderboard',
5455
SOCIAL_TRADER_POSITION = 'social-trader-position',
5556
CARD_ONBOARDING = 'card-onboarding',
5657
CARD_HOME = 'card-home',

app/core/DeeplinkManager/util/deeplinks/deepLinkAnalytics.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ describe('deepLinkAnalytics', () => {
455455
[ACTIONS.DAPP, DeepLinkRoute.DAPP],
456456
[ACTIONS.WC, DeepLinkRoute.WC],
457457
[ACTIONS.CREATE_ACCOUNT, DeepLinkRoute.CREATE_ACCOUNT],
458+
[ACTIONS.SOCIAL_LEADERBOARD, DeepLinkRoute.SOCIAL_LEADERBOARD],
458459
[ACTIONS.SOCIAL_TRADER_POSITION, DeepLinkRoute.SOCIAL_TRADER_POSITION],
459460
] as const)(
460461
'maps action %s to its corresponding route',
@@ -527,6 +528,13 @@ describe('deepLinkAnalytics', () => {
527528
expect(result).toBe(DeepLinkRoute.SOCIAL_TRADER_POSITION);
528529
});
529530

531+
it('extract social leaderboard route', () => {
532+
const result = extractRouteFromUrl(
533+
'https://link.metamask.io/social-leaderboard?ignored=true',
534+
);
535+
expect(result).toBe(DeepLinkRoute.SOCIAL_LEADERBOARD);
536+
});
537+
530538
it('extract home route for empty path', () => {
531539
const result = extractRouteFromUrl('https://link.metamask.io/');
532540
expect(result).toBe(DeepLinkRoute.HOME);

app/core/DeeplinkManager/util/deeplinks/deepLinkAnalytics.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ const routeExtractors: Record<
463463
[DeepLinkRoute.PREDICT]: extractPredictProperties,
464464
[DeepLinkRoute.SHIELD]: extractShieldProperties,
465465
[DeepLinkRoute.TRENDING]: extractTrendingProperties,
466+
[DeepLinkRoute.SOCIAL_LEADERBOARD]: extractInvalidProperties,
466467
[DeepLinkRoute.SOCIAL_TRADER_POSITION]: extractInvalidProperties,
467468
[DeepLinkRoute.CARD_ONBOARDING]: extractCardOnboardingProperties,
468469
[DeepLinkRoute.CARD_HOME]: extractCardHomeProperties,
@@ -594,6 +595,8 @@ export const mapSupportedActionToRoute = (
594595
return DeepLinkRoute.SHIELD;
595596
case ACTIONS.TRENDING:
596597
return DeepLinkRoute.TRENDING;
598+
case ACTIONS.SOCIAL_LEADERBOARD:
599+
return DeepLinkRoute.SOCIAL_LEADERBOARD;
597600
case ACTIONS.SOCIAL_TRADER_POSITION:
598601
return DeepLinkRoute.SOCIAL_TRADER_POSITION;
599602
case ACTIONS.CARD_ONBOARDING:
@@ -650,6 +653,8 @@ export const extractRouteFromUrl = (url: string): DeepLinkRoute => {
650653
return DeepLinkRoute.SHIELD;
651654
case 'trending':
652655
return DeepLinkRoute.TRENDING;
656+
case 'social-leaderboard':
657+
return DeepLinkRoute.SOCIAL_LEADERBOARD;
653658
case 'social-trader-position':
654659
return DeepLinkRoute.SOCIAL_TRADER_POSITION;
655660
case 'card-onboarding':

0 commit comments

Comments
 (0)