From 275926e88a2ca36e9b0833635ea5319182a1a217 Mon Sep 17 00:00:00 2001 From: Nicholas Ellul Date: Wed, 26 Jun 2024 16:52:00 -0400 Subject: [PATCH 01/16] refactor: update bitrise e2e check to not run on forks (#10128) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This pull request updates the bitrise e2e check workflow to only trigger from branches within the MetaMask Mobile repository. This brings it in line with other patterns we see across MetaMask such as in [core](https://github.com/MetaMask/core/blob/4fe6141a24aa11bb6672ea31a6e33efe041ffcc1/.github/workflows/publish-preview.yml#L13). ## **Manual testing steps** **Pull request on repository (non-fork)** 1. Look at CI on this pull request 2. See the successful run of `Run Bitrise E2E Check`. **Pull Request From Fork** 1. Visit https://github.com/NicholasEllul/metamask-mobile/pull/3 2. See in the CI output that the `run-bitrise-e2e-check` was [skipped](https://github.com/NicholasEllul/metamask-mobile/actions/runs/9686283967/job/26728373509?pr=3). ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [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. ## **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. --- .github/workflows/run-bitrise-e2e-check.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-bitrise-e2e-check.yml b/.github/workflows/run-bitrise-e2e-check.yml index 5dd0ca3c20e..23340257179 100644 --- a/.github/workflows/run-bitrise-e2e-check.yml +++ b/.github/workflows/run-bitrise-e2e-check.yml @@ -12,9 +12,24 @@ env: WORKFLOW_NAME: 'run-bitrise-e2e-check' jobs: + is-fork-pull-request: + name: Determine pull request source + if: ${{ github.event.issue.pull_request || github.event_name == 'pull_request' }} + runs-on: ubuntu-latest + outputs: + IS_FORK: ${{ steps.is-fork.outputs.IS_FORK }} + steps: + - uses: actions/checkout@v3 + - name: Determine whether this PR is from a fork + id: is-fork + run: echo "IS_FORK=$(gh pr view --json isCrossRepository --jq '.isCrossRepository' "${{ github.event.number }}" )" >> "$GITHUB_OUTPUT" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run-bitrise-e2e-check: + needs: is-fork-pull-request runs-on: ubuntu-latest - if: ${{ github.event.issue.pull_request || github.event_name == 'pull_request' }} + if: ${{ needs.is-fork-pull-request.outputs.IS_FORK == 'false' }} permissions: pull-requests: write contents: write From cf218134d69c90026e3ded87733ec5d6171f2db0 Mon Sep 17 00:00:00 2001 From: Curtis David Date: Wed, 26 Jun 2024 21:21:26 -0400 Subject: [PATCH 02/16] test: disable privacy policy toast and whats new modal using fixtures (#10134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Within the E2E tests that use fixtures, we want to disable the what's new modal in addition to the privacy policy toast message that appears as soon as you land on the wallet view. This shaves off ~7 mins of E2E run time on CI. A new state (legalNotices) was added to the default fixture for the privacy policy. Furthermore, the Whats New modal version used in the default fixture was updated to match the most recent release: 7.24.3. ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** See main before the PR is merged: https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/9f51cf6c-ffd3-4a7a-8aa4-f420a81547f9 ### **After** Here is main after: https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/f15f1200-4164-4d56-a740-32687b3f5134 ## **Pre-merge author checklist** - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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. ## **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. --- e2e/fixtures/fixture-builder.js | 6 +++++- e2e/viewHelper.js | 2 -- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/e2e/fixtures/fixture-builder.js b/e2e/fixtures/fixture-builder.js index 1e2d747162f..a39cce3ec0f 100644 --- a/e2e/fixtures/fixture-builder.js +++ b/e2e/fixtures/fixture-builder.js @@ -48,6 +48,10 @@ class FixtureBuilder { withDefaultFixture() { this.fixture = { state: { + legalNotices: { + newPrivacyPolicyToastClickedOrClosed: true, + newPrivacyPolicyToastShownDate: Date.now(), + }, collectibles: { favorites: {}, }, @@ -619,7 +623,7 @@ class FixtureBuilder { '@MetaMask:existingUser': 'true', '@MetaMask:onboardingWizard': 'explored', '@MetaMask:UserTermsAcceptedv1.0': 'true', - '@MetaMask:WhatsNewAppVersionSeen': '6.5.0', + '@MetaMask:WhatsNewAppVersionSeen': '7.24.3', }, }; return this; diff --git a/e2e/viewHelper.js b/e2e/viewHelper.js index eb016bb6ff6..7cc2de054b6 100644 --- a/e2e/viewHelper.js +++ b/e2e/viewHelper.js @@ -218,6 +218,4 @@ export const loginToApp = async () => { const PASSWORD = '123123123'; await LoginView.isVisible(); await LoginView.enterPassword(PASSWORD); - - await this.closeOnboardingModals(); }; From 81bcdb24f36390c8f614b53fdacbbf6ffd7aa243 Mon Sep 17 00:00:00 2001 From: Omri Dan <61094771+omridan159@users.noreply.github.com> Date: Thu, 27 Jun 2024 12:41:23 +0400 Subject: [PATCH 03/16] chore: fix the incorrect URL displayed during transaction confirmation (#9914) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** ## **Related issues** https://github.com/MetaMask/metamask-sdk/issues/862 Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/MetaMask/metamask-mobile/assets/61094771/cbead00e-f411-40d7-9c0b-0572361e6796 ## **Pre-merge author checklist** - [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. ## **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. --- app/components/UI/AccountInfoCard/index.js | 60 +++++++--- .../UI/AccountInfoCard/index.test.tsx | 15 +++ .../Views/AccountConnect/AccountConnect.tsx | 78 +++++++++++- .../ApproveTransactionHeader.tsx | 24 +++- .../ApproveTransactionHeader.types.ts | 4 + .../ApproveTransactionHeader.test.tsx.snap | 6 +- .../components/MessageSign/MessageSign.tsx | 1 + .../__snapshots__/index.test.tsx.snap | 1 + .../components/PersonalSign/PersonalSign.tsx | 31 ++--- .../components/SignatureRequest/index.js | 23 +++- .../components/TransactionReview/index.js | 111 ++++++++++-------- .../__snapshots__/index.test.tsx.snap | 20 +++- .../handlers/handleConnectionReady.ts | 25 +++- locales/languages/en.json | 1 + 14 files changed, 290 insertions(+), 110 deletions(-) diff --git a/app/components/UI/AccountInfoCard/index.js b/app/components/UI/AccountInfoCard/index.js index e8f3f21fecf..246c905e22d 100644 --- a/app/components/UI/AccountInfoCard/index.js +++ b/app/components/UI/AccountInfoCard/index.js @@ -1,35 +1,37 @@ -import React, { PureComponent } from 'react'; +import isUrl from 'is-url'; import PropTypes from 'prop-types'; +import React, { PureComponent } from 'react'; import { StyleSheet, View } from 'react-native'; -import { fontStyles } from '../../../styles/common'; -import { renderFromWei, weiToFiat, hexToBN } from '../../../util/number'; -import Identicon from '../Identicon'; -import { strings } from '../../../../locales/i18n'; import { connect } from 'react-redux'; +import { strings } from '../../../../locales/i18n'; +import Text, { + TextVariant, +} from '../../../component-library/components/Texts/Text'; +import SDKConnect from '../../../core/SDKConnect/SDKConnect'; +import { selectAccounts } from '../../../selectors/accountTrackerController'; import { + selectConversionRate, + selectCurrentCurrency, +} from '../../../selectors/currencyRateController'; +import { selectTicker } from '../../../selectors/networkController'; +import { selectIdentities } from '../../../selectors/preferencesController'; +import { fontStyles } from '../../../styles/common'; +import { + getLabelTextByAddress, renderAccountName, renderShortAddress, safeToChecksumAddress, - getLabelTextByAddress, } from '../../../util/address'; +import Device from '../../../util/device'; +import { hexToBN, renderFromWei, weiToFiat } from '../../../util/number'; +import { ThemeContext, mockTheme } from '../../../util/theme'; import { getActiveTabUrl, getNormalizedTxState, getTicker, } from '../../../util/transactions'; -import Device from '../../../util/device'; -import { ThemeContext, mockTheme } from '../../../util/theme'; -import { selectTicker } from '../../../selectors/networkController'; -import { - selectConversionRate, - selectCurrentCurrency, -} from '../../../selectors/currencyRateController'; -import { selectAccounts } from '../../../selectors/accountTrackerController'; -import { selectIdentities } from '../../../selectors/preferencesController'; import ApproveTransactionHeader from '../../Views/confirmations/components/ApproveTransactionHeader'; -import Text, { - TextVariant, -} from '../../../component-library/components/Texts/Text'; +import Identicon from '../Identicon'; const createStyles = (colors) => StyleSheet.create({ @@ -165,11 +167,31 @@ class AccountInfoCard extends PureComponent { const dollarBalance = showFiatBalance ? weiToFiat(weiBalance, conversionRate, currentCurrency, 2)?.toUpperCase() : undefined; + + const sdkConnections = SDKConnect.getInstance().getConnections(); + + const currentConnection = sdkConnections[origin ?? '']; + + const isOriginUrl = isUrl(origin); + + const originatorInfo = currentConnection?.originatorInfo; + + const sdkDappMetadata = { + url: isOriginUrl ? origin : originatorInfo?.url ?? strings('sdk.unknown'), + icon: originatorInfo?.icon, + }; + return operation === 'signing' && transaction !== undefined ? ( ) : ( diff --git a/app/components/UI/AccountInfoCard/index.test.tsx b/app/components/UI/AccountInfoCard/index.test.tsx index 6674befdd29..d8500ab9d38 100644 --- a/app/components/UI/AccountInfoCard/index.test.tsx +++ b/app/components/UI/AccountInfoCard/index.test.tsx @@ -69,6 +69,20 @@ jest.mock('react-redux', () => ({ .mockImplementation((callback) => callback(mockInitialState)), })); +jest.mock('is-url', () => jest.fn()); +jest.mock('../../../core/SDKConnect/SDKConnect', () => ({ + getInstance: () => ({ + getConnections: jest.fn().mockReturnValue({ + 'https://metamask.io': { + originatorInfo: { + url: 'https://metamask.io', + icon: 'https://metamask.io/icon.png', + }, + }, + }), + }), +})); + describe('AccountInfoCard', () => { it('should match snapshot', async () => { const container = renderWithProvider( @@ -94,6 +108,7 @@ describe('AccountInfoCard', () => { , { state: mockInitialState }, ); diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index 774e6b12fa2..2e20f151bcf 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -27,8 +27,8 @@ import { USER_INTENT } from '../../../constants/permissions'; import { MetaMetricsEvents } from '../../../core/Analytics'; import UntypedEngine from '../../../core/Engine'; import { selectAccountsLength } from '../../../selectors/accountTrackerController'; -import { selectIdentities } from '../../../selectors/preferencesController'; import { selectSelectedInternalAccountChecksummedAddress } from '../../../selectors/accountsController'; +import { selectIdentities } from '../../../selectors/preferencesController'; import { isDefaultAccountName } from '../../../util/ENSUtils'; import Logger from '../../../util/Logger'; import getAccountNameWithENS from '../../../util/accounts'; @@ -51,11 +51,14 @@ import { MM_ETHERSCAN_URL, MM_PHISH_DETECT_URL, } from '../../../constants/urls'; +import AppConstants from '../../../core/AppConstants'; import SDKConnect from '../../../core/SDKConnect/SDKConnect'; import DevLogger from '../../../core/SDKConnect/utils/DevLogger'; import { trackDappViewedEvent } from '../../../util/metrics'; import { useTheme } from '../../../util/theme'; +import { WALLET_CONNECT_ORIGIN } from '../../../util/walletconnect'; import useFavicon from '../../hooks/useFavicon/useFavicon'; +import { SourceType } from '../../hooks/useMetrics/useMetrics.types'; import { AccountConnectProps, AccountConnectScreens, @@ -63,7 +66,6 @@ import { import AccountConnectMultiSelector from './AccountConnectMultiSelector'; import AccountConnectSingle from './AccountConnectSingle'; import AccountConnectSingleSelector from './AccountConnectSingleSelector'; -import { SourceType } from '../../hooks/useMetrics/useMetrics.types'; const createStyles = () => StyleSheet.create({ @@ -84,6 +86,9 @@ const AccountConnect = (props: AccountConnectProps) => { const navigation = useNavigation(); const { trackEvent } = useMetrics(); + const [isOriginWalletConnect, setIsOriginWalletConnect] = useState(false); + const [isOriginMMSDKRemoteConn, setIsOriginMMSDKRemoteConn] = useState(false); + const [blockedUrl, setBlockedUrl] = useState(''); const selectedWalletAddress = useSelector( @@ -126,6 +131,30 @@ const AccountConnect = (props: AccountConnectProps) => { origin: string; }; + const isUUID = (str: string) => { + const uuidRegex = + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + return uuidRegex.test(str); + }; + + const isChannelId = isUUID(channelIdOrHostname); + + useEffect(() => { + if (!channelIdOrHostname) { + setIsOriginWalletConnect(false); + setIsOriginMMSDKRemoteConn(false); + + return; + } + + setIsOriginWalletConnect( + channelIdOrHostname.startsWith(WALLET_CONNECT_ORIGIN), + ); + setIsOriginMMSDKRemoteConn( + channelIdOrHostname.startsWith(AppConstants.MM_SDK.SDK_REMOTE_ORIGIN), + ); + }, [channelIdOrHostname]); + const sdkConnection = SDKConnect.getInstance().getConnection({ channelId: channelIdOrHostname, }); @@ -136,11 +165,41 @@ const AccountConnect = (props: AccountConnectProps) => { : sdkConnection?.originatorInfo?.url ?? '' : inappBrowserOrigin; - const urlWithProtocol = prefixUrlWithProtocol(hostname); - const dappIconUrl = sdkConnection?.originatorInfo?.icon; const dappUrl = sdkConnection?.originatorInfo?.url ?? ''; + const domainTitle = useMemo(() => { + let title = ''; + + if (isOriginWalletConnect) { + title = getUrlObj( + (channelIdOrHostname as string).split(WALLET_CONNECT_ORIGIN)[1], + ).origin; + } else if (isOriginMMSDKRemoteConn) { + title = getUrlObj( + (channelIdOrHostname as string).split( + AppConstants.MM_SDK.SDK_REMOTE_ORIGIN, + )[1], + ).origin; + } else if (!isChannelId && (dappUrl || channelIdOrHostname)) { + title = prefixUrlWithProtocol(dappUrl || channelIdOrHostname); + } else { + title = strings('sdk.unknown'); + } + + return title; + }, [ + isOriginWalletConnect, + isOriginMMSDKRemoteConn, + isChannelId, + dappUrl, + channelIdOrHostname, + ]); + + const urlWithProtocol = hostname + ? prefixUrlWithProtocol(hostname) + : domainTitle; + const isAllowedUrl = useCallback( (url: string) => { const { PhishingController } = Engine.context; @@ -170,10 +229,17 @@ const AccountConnect = (props: AccountConnectProps) => { } }, [isAllowedUrl, dappUrl, channelIdOrHostname]); - const faviconSource = useFavicon(inappBrowserOrigin); + const faviconSource = useFavicon( + inappBrowserOrigin || (!isChannelId ? channelIdOrHostname : ''), + ); const actualIcon = useMemo( - () => (dappIconUrl ? { uri: dappIconUrl } : faviconSource), + () => + faviconSource?.uri + ? faviconSource + : dappIconUrl + ? { uri: dappIconUrl } + : { uri: '' }, [dappIconUrl, faviconSource], ); diff --git a/app/components/Views/confirmations/components/ApproveTransactionHeader/ApproveTransactionHeader.tsx b/app/components/Views/confirmations/components/ApproveTransactionHeader/ApproveTransactionHeader.tsx index f2bca2db1ae..26e616e27bd 100644 --- a/app/components/Views/confirmations/components/ApproveTransactionHeader/ApproveTransactionHeader.tsx +++ b/app/components/Views/confirmations/components/ApproveTransactionHeader/ApproveTransactionHeader.tsx @@ -3,18 +3,18 @@ import React, { useEffect, useMemo, useState } from 'react'; import { View } from 'react-native'; import { useSelector } from 'react-redux'; -import AppConstants from '../../../../../core/AppConstants'; import { strings } from '../../../../../../locales/i18n'; import AccountBalance from '../../../../../component-library/components-temp/Accounts/AccountBalance'; import { BadgeVariant } from '../../../../../component-library/components/Badges/Badge'; import TagUrl from '../../../../../component-library/components/Tags/TagUrl'; import { useStyles } from '../../../../../component-library/hooks'; +import AppConstants from '../../../../../core/AppConstants'; +import { selectAccountsByChainId } from '../../../../../selectors/accountTrackerController'; import { - selectNetworkName, selectNetworkImageSource, + selectNetworkName, } from '../../../../../selectors/networkInfos'; import { selectIdentities } from '../../../../../selectors/preferencesController'; -import { selectAccountsByChainId } from '../../../../../selectors/accountTrackerController'; import { getLabelTextByAddress, renderAccountName, @@ -35,6 +35,7 @@ const ApproveTransactionHeader = ({ from, origin, url, + sdkDappMetadata, currentEnsName, asset, dontWatchAsset, @@ -89,6 +90,7 @@ const ApproveTransactionHeader = ({ const domainTitle = useMemo(() => { let title = ''; + if (isOriginWalletConnect) { title = getUrlObj( (origin as string).split(WALLET_CONNECT_ORIGIN)[1], @@ -97,8 +99,10 @@ const ApproveTransactionHeader = ({ title = getUrlObj( (origin as string).split(AppConstants.MM_SDK.SDK_REMOTE_ORIGIN)[1], ).origin; + } else if (url || currentEnsName) { + title = prefixUrlWithProtocol(currentEnsName || url || ''); } else { - title = prefixUrlWithProtocol(currentEnsName || origin || url); + title = ''; } return title; @@ -124,13 +128,21 @@ const ApproveTransactionHeader = ({ const accountTypeLabel = getLabelTextByAddress(activeAddress); + const imageSource = faviconSource?.uri + ? faviconSource + : sdkDappMetadata?.icon + ? { uri: sdkDappMetadata.icon } + : { + uri: '', + }; + return ( {origin && !isOriginDeepLink ? ( ) : null} diff --git a/app/components/Views/confirmations/components/ApproveTransactionHeader/ApproveTransactionHeader.types.ts b/app/components/Views/confirmations/components/ApproveTransactionHeader/ApproveTransactionHeader.types.ts index c58baf8f77a..5fef8a97f4d 100644 --- a/app/components/Views/confirmations/components/ApproveTransactionHeader/ApproveTransactionHeader.types.ts +++ b/app/components/Views/confirmations/components/ApproveTransactionHeader/ApproveTransactionHeader.types.ts @@ -16,4 +16,8 @@ export interface ApproveTransactionHeaderI { from: string; asset: Asset; dontWatchAsset?: boolean; + sdkDappMetadata?: { + url: string; + icon: string; + }; } diff --git a/app/components/Views/confirmations/components/ApproveTransactionHeader/__snapshots__/ApproveTransactionHeader.test.tsx.snap b/app/components/Views/confirmations/components/ApproveTransactionHeader/__snapshots__/ApproveTransactionHeader.test.tsx.snap index e73adc1c15a..1a99dd78182 100644 --- a/app/components/Views/confirmations/components/ApproveTransactionHeader/__snapshots__/ApproveTransactionHeader.test.tsx.snap +++ b/app/components/Views/confirmations/components/ApproveTransactionHeader/__snapshots__/ApproveTransactionHeader.test.tsx.snap @@ -42,7 +42,11 @@ exports[`ApproveTransactionHeader should render correctly 1`] = ` {renderMessageText()} diff --git a/app/components/Views/confirmations/components/MessageSign/__snapshots__/index.test.tsx.snap b/app/components/Views/confirmations/components/MessageSign/__snapshots__/index.test.tsx.snap index 29ae3aae824..81fd2eaa2cf 100644 --- a/app/components/Views/confirmations/components/MessageSign/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/confirmations/components/MessageSign/__snapshots__/index.test.tsx.snap @@ -50,6 +50,7 @@ exports[`MessageSign SignatureRequest should render correctly 1`] = ` } onConfirm={[Function]} onReject={[Function]} + origin="example.com" showExpandedMessage={false} showWarning={true} testID="SignatureRequest" diff --git a/app/components/Views/confirmations/components/PersonalSign/PersonalSign.tsx b/app/components/Views/confirmations/components/PersonalSign/PersonalSign.tsx index 6cefc97575f..432dd7d8ecc 100644 --- a/app/components/Views/confirmations/components/PersonalSign/PersonalSign.tsx +++ b/app/components/Views/confirmations/components/PersonalSign/PersonalSign.tsx @@ -1,35 +1,35 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import { useNavigation } from '@react-navigation/native'; +import React, { useCallback, useEffect, useState } from 'react'; +import { InteractionManager, Text, View } from 'react-native'; import { useSelector } from 'react-redux'; -import { View, Text, InteractionManager } from 'react-native'; -import Engine from '../../../../../core/Engine'; -import SignatureRequest from '../SignatureRequest'; -import ExpandedMessage from '../SignatureRequest/ExpandedMessage'; -import NotificationManager from '../../../../../core/NotificationManager'; import { strings } from '../../../../../../locales/i18n'; -import { WALLET_CONNECT_ORIGIN } from '../../../../../util/walletconnect'; +import { KEYSTONE_TX_CANCELED } from '../../../../../constants/error'; import { MetaMetricsEvents } from '../../../../../core/Analytics'; +import Engine from '../../../../../core/Engine'; +import NotificationManager from '../../../../../core/NotificationManager'; import { getAddressAccountType, isExternalHardwareAccount, stripHexPrefix, } from '../../../../../util/address'; import { sanitizeString } from '../../../../../util/string'; -import { KEYSTONE_TX_CANCELED } from '../../../../../constants/error'; import { useTheme } from '../../../../../util/theme'; -import { PersonalSignProps } from './types'; -import { useNavigation } from '@react-navigation/native'; +import { WALLET_CONNECT_ORIGIN } from '../../../../../util/walletconnect'; +import SignatureRequest from '../SignatureRequest'; +import ExpandedMessage from '../SignatureRequest/ExpandedMessage'; import createStyles from './styles'; +import { PersonalSignProps } from './types'; +import { SigningModalSelectorsIDs } from '../../../../../../e2e/selectors/Modals/SigningModal.selectors'; +import { useMetrics } from '../../../../../components/hooks/useMetrics'; import AppConstants from '../../../../../core/AppConstants'; -import createExternalSignModelNav from '../../../../../util/hardwareWallet/signatureUtils'; import { selectChainId } from '../../../../../selectors/networkController'; import { store } from '../../../../../store'; +import Logger from '../../../../../util/Logger'; import { getBlockaidMetricsParams } from '../../../../../util/blockaid'; -import { SecurityAlertResponse } from '../BlockaidBanner/BlockaidBanner.types'; -import { SigningModalSelectorsIDs } from '../../../../../../e2e/selectors/Modals/SigningModal.selectors'; +import createExternalSignModelNav from '../../../../../util/hardwareWallet/signatureUtils'; import { getDecimalChainId } from '../../../../../util/networks'; -import Logger from '../../../../../util/Logger'; -import { useMetrics } from '../../../../../components/hooks/useMetrics'; +import { SecurityAlertResponse } from '../BlockaidBanner/BlockaidBanner.types'; /** * Converts a hexadecimal string to a utf8 string. @@ -237,6 +237,7 @@ const PersonalSign = ({ truncateMessage={truncateMessage} type="personal_sign" fromAddress={messageParams.from} + origin={messageParams.origin} testID={SigningModalSelectorsIDs.PERSONAL_REQUEST} > {renderMessageText()} diff --git a/app/components/Views/confirmations/components/SignatureRequest/index.js b/app/components/Views/confirmations/components/SignatureRequest/index.js index 37cd5807c04..592eb58da5c 100644 --- a/app/components/Views/confirmations/components/SignatureRequest/index.js +++ b/app/components/Views/confirmations/components/SignatureRequest/index.js @@ -5,25 +5,34 @@ import Ionicons from 'react-native-vector-icons/Ionicons'; import { connect } from 'react-redux'; import { SigningModalSelectorsIDs } from '../../../../../../e2e/selectors/Modals/SigningModal.selectors'; import { strings } from '../../../../../../locales/i18n'; +import { withMetricsAwareness } from '../../../../../components/hooks/useMetrics'; import ExtendedKeyringTypes from '../../../../../constants/keyringTypes'; import { MetaMetricsEvents } from '../../../../../core/Analytics'; +import { selectSelectedInternalAccountChecksummedAddress } from '../../../../../selectors/accountsController'; import { selectProviderType } from '../../../../../selectors/networkController'; import { fontStyles } from '../../../../../styles/common'; import { isHardwareAccount } from '../../../../../util/address'; -import { getHost } from '../../../../../util/browser'; import { getAnalyticsParams } from '../../../../../util/confirmation/signatureUtils'; import Device from '../../../../../util/device'; import { ThemeContext, mockTheme } from '../../../../../util/theme'; -import WarningMessage from '../../SendFlow/WarningMessage'; import AccountInfoCard from '../../../../UI/AccountInfoCard'; import ActionView, { ConfirmButtonState } from '../../../../UI/ActionView'; -import BlockaidBanner from '../BlockaidBanner/BlockaidBanner'; import QRSigningDetails from '../../../../UI/QRHardware/QRSigningDetails'; import withQRHardwareAwareness from '../../../../UI/QRHardware/withQRHardwareAwareness'; import WebsiteIcon from '../../../../UI/WebsiteIcon'; +import WarningMessage from '../../SendFlow/WarningMessage'; +import BlockaidBanner from '../BlockaidBanner/BlockaidBanner'; import { ResultType } from '../BlockaidBanner/BlockaidBanner.types'; -import { withMetricsAwareness } from '../../../../../components/hooks/useMetrics'; -import { selectSelectedInternalAccountChecksummedAddress } from '../../../../../selectors/accountsController'; + +const getCleanUrl = (url) => { + try { + const urlObject = new URL(url); + + return urlObject.origin; + } catch (error) { + return ''; + } +}; const createStyles = (colors) => StyleSheet.create({ @@ -254,8 +263,10 @@ class SignatureRequest extends PureComponent { const styles = this.getStyles(); const url = currentPageInformation.url; const icon = currentPageInformation.icon; - const title = getHost(url); + + const title = getCleanUrl(url); const arrowIcon = truncateMessage ? this.renderArrowIcon() : null; + return ( diff --git a/app/components/Views/confirmations/components/TransactionReview/index.js b/app/components/Views/confirmations/components/TransactionReview/index.js index de924772852..14bf5aaced1 100644 --- a/app/components/Views/confirmations/components/TransactionReview/index.js +++ b/app/components/Views/confirmations/components/TransactionReview/index.js @@ -1,66 +1,67 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import { StyleSheet, View, Animated, ScrollView } from 'react-native'; +import { withNavigation } from '@react-navigation/compat'; import Eth from 'ethjs-query'; -import { - isMultiLayerFeeNetwork, - fetchEstimatedMultiLayerL1Fee, -} from '../../../../../util/networks'; -import Engine from '../../../../../core/Engine'; -import Logger from '../../../../../util/Logger'; -import { fontStyles } from '../../../../../styles/common'; +import PropTypes from 'prop-types'; +import React, { PureComponent } from 'react'; +import { Animated, ScrollView, StyleSheet, View } from 'react-native'; import { connect } from 'react-redux'; import { strings } from '../../../../../../locales/i18n'; +import { withMetricsAwareness } from '../../../../../components/hooks/useMetrics'; +import { MetaMetricsEvents } from '../../../../../core/Analytics'; +import AppConstants from '../../../../../core/AppConstants'; +import Engine from '../../../../../core/Engine'; +import { SDKConnect } from '../../../../../core/SDKConnect/SDKConnect'; +import { selectCurrentTransactionMetadata } from '../../../../../selectors/confirmTransaction'; import { - getTransactionReviewActionKey, - getNormalizedTxState, - decodeTransferData, - getTicker, - isApprovalTransaction, -} from '../../../../../util/transactions'; + selectConversionRate, + selectCurrentCurrency, +} from '../../../../../selectors/currencyRateController'; +import { + selectChainId, + selectTicker, +} from '../../../../../selectors/networkController'; +import { selectUseTransactionSimulations } from '../../../../../selectors/preferencesController'; +import { selectShouldUseSmartTransaction } from '../../../../../selectors/smartTransactionsController'; +import { selectTokenList } from '../../../../../selectors/tokenListController'; +import { selectContractExchangeRates } from '../../../../../selectors/tokenRatesController'; +import { selectTokens } from '../../../../../selectors/tokensController'; +import { fontStyles } from '../../../../../styles/common'; +import Logger from '../../../../../util/Logger'; +import { safeToChecksumAddress } from '../../../../../util/address'; +import { getBlockaidMetricsParams } from '../../../../../util/blockaid'; +import Device from '../../../../../util/device'; +import { + fetchEstimatedMultiLayerL1Fee, + isMultiLayerFeeNetwork, +} from '../../../../../util/networks'; import { - weiToFiat, balanceToFiat, - renderFromTokenMinimalUnit, - renderFromWei, fromTokenMinimalUnit, isZeroValue, + renderFromTokenMinimalUnit, + renderFromWei, + weiToFiat, } from '../../../../../util/number'; -import { safeToChecksumAddress } from '../../../../../util/address'; -import Device from '../../../../../util/device'; -import { getBlockaidMetricsParams } from '../../../../../util/blockaid'; -import TransactionReviewInformation from './TransactionReviewInformation'; -import TransactionReviewSummary from './TransactionReviewSummary'; -import TransactionReviewData from './TransactionReviewData'; -import { MetaMetricsEvents } from '../../../../../core/Analytics'; -import TransactionHeader from '../../../../UI/TransactionHeader'; +import { ThemeContext, mockTheme } from '../../../../../util/theme'; +import { + decodeTransferData, + getNormalizedTxState, + getTicker, + getTransactionReviewActionKey, + isApprovalTransaction, +} from '../../../../../util/transactions'; +import { WALLET_CONNECT_ORIGIN } from '../../../../../util/walletconnect'; import AccountFromToInfoCard from '../../../../UI/AccountFromToInfoCard'; import ActionView, { ConfirmButtonState } from '../../../../UI/ActionView'; -import { WALLET_CONNECT_ORIGIN } from '../../../../../util/walletconnect'; -import { ThemeContext, mockTheme } from '../../../../../util/theme'; -import withQRHardwareAwareness from '../../../../UI/QRHardware/withQRHardwareAwareness'; import QRSigningDetails from '../../../../UI/QRHardware/QRSigningDetails'; -import { withNavigation } from '@react-navigation/compat'; -import { - selectChainId, - selectTicker, -} from '../../../../../selectors/networkController'; -import { - selectConversionRate, - selectCurrentCurrency, -} from '../../../../../selectors/currencyRateController'; -import { selectTokenList } from '../../../../../selectors/tokenListController'; -import { selectTokens } from '../../../../../selectors/tokensController'; -import { selectCurrentTransactionMetadata } from '../../../../../selectors/confirmTransaction'; -import { selectContractExchangeRates } from '../../../../../selectors/tokenRatesController'; +import withQRHardwareAwareness from '../../../../UI/QRHardware/withQRHardwareAwareness'; +import SimulationDetails from '../../../../UI/SimulationDetails/SimulationDetails'; +import TransactionHeader from '../../../../UI/TransactionHeader'; import ApproveTransactionHeader from '../ApproveTransactionHeader'; -import AppConstants from '../../../../../core/AppConstants'; -import TransactionBlockaidBanner from '../TransactionBlockaidBanner/TransactionBlockaidBanner'; import { ResultType } from '../BlockaidBanner/BlockaidBanner.types'; -import { withMetricsAwareness } from '../../../../../components/hooks/useMetrics'; -import { selectShouldUseSmartTransaction } from '../../../../../selectors/smartTransactionsController'; -import SimulationDetails from '../../../../UI/SimulationDetails/SimulationDetails'; -import { selectUseTransactionSimulations } from '../../../../../selectors/preferencesController'; +import TransactionBlockaidBanner from '../TransactionBlockaidBanner/TransactionBlockaidBanner'; +import TransactionReviewData from './TransactionReviewData'; +import TransactionReviewInformation from './TransactionReviewInformation'; +import TransactionReviewSummary from './TransactionReviewSummary'; const POLLING_INTERVAL_ESTIMATED_L1_FEE = 30000; @@ -529,8 +530,19 @@ class TransactionReview extends PureComponent { multiLayerL1FeeTotal, } = this.state; const url = this.getUrlFromBrowser(); + + const sdkConnections = SDKConnect.getInstance().getConnections(); + + const currentConnection = sdkConnections[origin ?? '']; + const styles = this.getStyles(); + const originatorInfo = currentConnection?.originatorInfo; + const sdkDappMetadata = { + url: originatorInfo?.url ?? strings('sdk.unknown'), + icon: originatorInfo?.icon, + }; + return ( <> )} diff --git a/app/components/Views/confirmations/components/TypedSign/__snapshots__/index.test.tsx.snap b/app/components/Views/confirmations/components/TypedSign/__snapshots__/index.test.tsx.snap index 6e84f0d6501..cbe02c8942f 100644 --- a/app/components/Views/confirmations/components/TypedSign/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/confirmations/components/TypedSign/__snapshots__/index.test.tsx.snap @@ -167,7 +167,11 @@ exports[`TypedSign onConfirm signs message 1`] = ` - https://localhost + http://localhost:8545 - l + h @@ -834,7 +838,11 @@ exports[`TypedSign onReject rejects message 1`] = ` - https://localhost + http://localhost:8545 - l + h diff --git a/app/core/SDKConnect/handlers/handleConnectionReady.ts b/app/core/SDKConnect/handlers/handleConnectionReady.ts index ce024cc6252..9cff1197d44 100644 --- a/app/core/SDKConnect/handlers/handleConnectionReady.ts +++ b/app/core/SDKConnect/handlers/handleConnectionReady.ts @@ -62,10 +62,31 @@ export const handleConnectionReady = async ({ return; } - connection.originatorInfo = originatorInfo; + let dappUrl = ''; + + try { + const urlObj = new URL(originatorInfo?.url); + const hasPort = !!urlObj.port; + if (hasPort) { + dappUrl = `${urlObj.protocol}//${urlObj.hostname}:${urlObj.port}`; + } else { + dappUrl = `${urlObj.protocol}//${urlObj.hostname}`; + } + } catch (e) { + DevLogger.log('Invalid URL:', originatorInfo?.url); + } + + connection.originatorInfo = { + ...originatorInfo, + url: dappUrl, + }; + updateOriginatorInfos({ channelId: connection.channelId, - originatorInfo, + originatorInfo: { + ...originatorInfo, + url: dappUrl, + }, }); DevLogger.log( `SDKConnect::CLIENTS_READY originatorInfo updated`, diff --git a/locales/languages/en.json b/locales/languages/en.json index c9a9a20a22a..4a6875a0bc8 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -1033,6 +1033,7 @@ "cancel": "Cancel", "loading": "Connecting to MetaMask...", "unkown_dapp": "DAPP name not available", + "unknown": "Unknown", "no_connections": "No connections", "no_connections_desc": "If you connect an account to a site or an app, you’ll see it here." }, From 3e27936b79faf3d782ebc430167b38a2f855a797 Mon Sep 17 00:00:00 2001 From: Jonathan Ferreira <44679989+Jonathansoufer@users.noreply.github.com> Date: Thu, 27 Jun 2024 11:02:38 +0100 Subject: [PATCH 04/16] feat: enables snaps feature flag on mobile (#10121) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR implements a new feature flag specific for snaps to enable the usage of snaps (embedded) while avoid users to install it (for now). This replaces the old `snaps` tag with `preinstalled-snaps` and `external-snaps` tags. The whole installation flow is under `external-snaps`. ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [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. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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. --- .../InstallSnapApproval.constants.ts | 2 +- .../InstallSnapApproval.styles.ts | 2 +- .../InstallSnapApproval.tsx | 2 +- .../InstallSnapApproval.types.ts | 2 +- .../InstallSnapConnectionRequest.constants.ts | 2 +- .../InstallSnapConnectionRequest.tsx | 2 +- .../InstallSnapConnectionRequest/index.ts | 2 +- .../InstallSnapConnectionRequest.test.tsx | 2 +- .../InstallSnapError.constants.ts | 2 +- .../InstallSnapError/InstallSnapError.tsx | 2 +- .../components/InstallSnapError/index.ts | 2 +- .../test/InstallSnapError.test.tsx | 2 +- ...InstallSnapPermissionsRequest.constants.ts | 2 +- .../InstallSnapPermissionsRequest.tsx | 2 +- .../InstallSnapPermissionsRequest/index.ts | 2 +- .../InstallSnapPermissionsRequest.test.tsx | 2 +- .../InstallSnapSuccess.constants.ts | 2 +- .../InstallSnapSuccess/InstallSnapSuccess.tsx | 2 +- .../components/InstallSnapSuccess/index.ts | 2 +- .../test/InstallSnapSuccess.test.tsx | 2 +- .../InstallSnapApproval/components/index.ts | 2 +- .../Approvals/InstallSnapApproval/index.ts | 2 +- .../test/InstallSnapApproval.test.tsx | 2 +- app/components/Nav/Main/MainNavigator.js | 6 +- app/components/Nav/Main/RootRPCMethodsUI.js | 4 +- app/components/Nav/Main/index.js | 4 +- app/components/Views/Settings/index.tsx | 6 +- .../SnapSettings/SnapSettings.constants.ts | 2 +- .../Snaps/SnapSettings/SnapSettings.styles.ts | 2 +- .../Views/Snaps/SnapSettings/SnapSettings.tsx | 2 +- .../Views/Snaps/SnapSettings/index.ts | 2 +- .../SnapsSettingsList.styles.ts | 2 +- .../SnapsSettingsList/SnapsSettingsList.tsx | 2 +- .../Views/Snaps/SnapsSettingsList/index.ts | 2 +- .../SnapDescription.constants.ts | 2 +- .../SnapDescription.styles.tsx | 2 +- .../SnapDescription/SnapDescription.tsx | 2 +- .../Snaps/components/SnapDescription/index.ts | 2 +- .../test/SnapDescription.test.tsx | 2 +- .../SnapDetails/SnapDetails.constants.ts | 2 +- .../SnapDetails/SnapDetails.styles.ts | 2 +- .../components/SnapDetails/SnapDetails.tsx | 2 +- .../Snaps/components/SnapDetails/index.ts | 2 +- .../SnapElement/SnapElement.constants.ts | 2 +- .../SnapElement/SnapElement.styles.ts | 2 +- .../components/SnapElement/SnapElement.tsx | 2 +- .../Snaps/components/SnapElement/index.ts | 2 +- .../SnapPermissionCell.constants.ts | 2 +- .../SnapPermissionCell.styles.ts | 2 +- .../SnapPermissionCell/SnapPermissionCell.tsx | 2 +- .../components/SnapPermissionCell/index.ts | 2 +- .../SnapPermissions.contants.ts | 2 +- .../SnapPermissions/SnapPermissions.styles.ts | 2 +- .../SnapPermissions/SnapPermissions.tsx | 2 +- .../Snaps/components/SnapPermissions/index.ts | 2 +- .../SnapVersionTag.constants.ts | 2 +- .../SnapVersionTag/SnapVersionTag.styles.ts | 2 +- .../SnapVersionTag/SnapVersionTag.tsx | 2 +- .../Snaps/components/SnapVersionTag/index.ts | 2 +- app/constants/navigation/Routes.ts | 2 +- app/core/BackgroundBridge/BackgroundBridge.js | 4 +- app/core/Engine.ts | 43 ++--- app/core/EngineService/EngineService.ts | 2 +- app/core/Permissions/constants.ts | 2 +- app/core/Permissions/specifications.js | 6 +- app/core/RPCMethods/RPCMethodMiddleware.ts | 2 +- app/core/Snaps/SnapBridge.ts | 2 +- app/core/Snaps/SnapsMethodMiddleware.ts | 2 +- app/core/Snaps/index.ts | 2 +- app/core/Snaps/location/fetch.ts | 2 +- app/core/Snaps/location/index.ts | 2 +- app/core/Snaps/location/location.ts | 2 +- app/core/Snaps/location/npm.ts | 2 +- app/core/Snaps/permissions/permissions.ts | 2 +- app/lib/snaps/SnapsExecutionWebView.tsx | 2 +- app/lib/snaps/index.ts | 2 +- app/lib/snaps/styles.ts | 2 +- app/selectors/types.ts | 4 +- metro.transform.js | 15 +- package.json | 10 +- yarn.lock | 150 ++++++++++++++++-- 81 files changed, 264 insertions(+), 128 deletions(-) diff --git a/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.constants.ts b/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.constants.ts index 1dbe05b68b8..7cca5eabcff 100644 --- a/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.constants.ts +++ b/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.constants.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) export const SNAP_INSTALL_FLOW = 'snap-install-flow'; export const SNAP_INSTALL_OK = 'snap-install-ok'; ///: END:ONLY_INCLUDE_IF diff --git a/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.styles.ts b/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.styles.ts index a3fd646662f..0788c1ba427 100644 --- a/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.styles.ts +++ b/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.styles.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import { StyleSheet } from 'react-native'; import { Theme } from '../../../util/theme/models'; import Device from '../../../util/device'; diff --git a/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.tsx b/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.tsx index a9abea4aa60..f30f44189e5 100644 --- a/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.tsx +++ b/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React, { useEffect, useState } from 'react'; import ApprovalModal from '../ApprovalModal'; import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; diff --git a/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.types.ts b/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.types.ts index 59b2c10f49f..00315095b33 100644 --- a/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.types.ts +++ b/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.types.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) interface InstallSnapFlowProps { // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/InstallSnapConnectionRequest.constants.ts b/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/InstallSnapConnectionRequest.constants.ts index fd998e68828..ba0576d2b71 100644 --- a/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/InstallSnapConnectionRequest.constants.ts +++ b/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/InstallSnapConnectionRequest.constants.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) export const SNAP_INSTALL_CONNECTION_REQUEST = 'snap-install-connection-request'; export const SNAP_INSTALL_CANCEL = 'snap-install-cancel'; diff --git a/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/InstallSnapConnectionRequest.tsx b/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/InstallSnapConnectionRequest.tsx index 66fd5373698..ad646e4b063 100644 --- a/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/InstallSnapConnectionRequest.tsx +++ b/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/InstallSnapConnectionRequest.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React, { useMemo } from 'react'; import { ImageSourcePropType, View } from 'react-native'; import { InstallSnapFlowProps } from '../../InstallSnapApproval.types'; diff --git a/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/index.ts b/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/index.ts index e70372b40d0..df76ec14358 100644 --- a/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/index.ts +++ b/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/index.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) /* eslint-disable import/prefer-default-export */ import InstallSnapConnectionRequest from './InstallSnapConnectionRequest'; diff --git a/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/test/InstallSnapConnectionRequest.test.tsx b/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/test/InstallSnapConnectionRequest.test.tsx index 0bb3dadccec..102123a88de 100644 --- a/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/test/InstallSnapConnectionRequest.test.tsx +++ b/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/test/InstallSnapConnectionRequest.test.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React from 'react'; import { render, fireEvent } from '@testing-library/react-native'; import InstallSnapConnectionRequest from '../InstallSnapConnectionRequest'; diff --git a/app/components/Approvals/InstallSnapApproval/components/InstallSnapError/InstallSnapError.constants.ts b/app/components/Approvals/InstallSnapApproval/components/InstallSnapError/InstallSnapError.constants.ts index 53edf1d94b4..0a4aaeff565 100644 --- a/app/components/Approvals/InstallSnapApproval/components/InstallSnapError/InstallSnapError.constants.ts +++ b/app/components/Approvals/InstallSnapApproval/components/InstallSnapError/InstallSnapError.constants.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) const SNAP_INSTALL_ERROR = 'snap-install-error'; export default SNAP_INSTALL_ERROR; ///: END:ONLY_INCLUDE_IF diff --git a/app/components/Approvals/InstallSnapApproval/components/InstallSnapError/InstallSnapError.tsx b/app/components/Approvals/InstallSnapApproval/components/InstallSnapError/InstallSnapError.tsx index 84998cbcc38..a34218959a6 100644 --- a/app/components/Approvals/InstallSnapApproval/components/InstallSnapError/InstallSnapError.tsx +++ b/app/components/Approvals/InstallSnapApproval/components/InstallSnapError/InstallSnapError.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React, { useMemo } from 'react'; import { View } from 'react-native'; import styleSheet from '../../InstallSnapApproval.styles'; diff --git a/app/components/Approvals/InstallSnapApproval/components/InstallSnapError/index.ts b/app/components/Approvals/InstallSnapApproval/components/InstallSnapError/index.ts index ff821f0dab5..0cd446d0392 100644 --- a/app/components/Approvals/InstallSnapApproval/components/InstallSnapError/index.ts +++ b/app/components/Approvals/InstallSnapApproval/components/InstallSnapError/index.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) /* eslint-disable import/prefer-default-export */ import InstallSnapError from './InstallSnapError'; diff --git a/app/components/Approvals/InstallSnapApproval/components/InstallSnapError/test/InstallSnapError.test.tsx b/app/components/Approvals/InstallSnapApproval/components/InstallSnapError/test/InstallSnapError.test.tsx index 9d7cfefcf24..3283ae6588a 100644 --- a/app/components/Approvals/InstallSnapApproval/components/InstallSnapError/test/InstallSnapError.test.tsx +++ b/app/components/Approvals/InstallSnapApproval/components/InstallSnapError/test/InstallSnapError.test.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React from 'react'; import { render, fireEvent } from '@testing-library/react-native'; import InstallSnapError from '../InstallSnapError'; diff --git a/app/components/Approvals/InstallSnapApproval/components/InstallSnapPermissionsRequest/InstallSnapPermissionsRequest.constants.ts b/app/components/Approvals/InstallSnapApproval/components/InstallSnapPermissionsRequest/InstallSnapPermissionsRequest.constants.ts index f78a0f48da8..aca88321c67 100644 --- a/app/components/Approvals/InstallSnapApproval/components/InstallSnapPermissionsRequest/InstallSnapPermissionsRequest.constants.ts +++ b/app/components/Approvals/InstallSnapApproval/components/InstallSnapPermissionsRequest/InstallSnapPermissionsRequest.constants.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) export const SNAP_INSTALL_CANCEL = 'snap-install-cancel'; export const SNAP_INSTALL_PERMISSIONS_REQUEST_APPROVE = 'snap-install-connection-permissions-request-approve'; diff --git a/app/components/Approvals/InstallSnapApproval/components/InstallSnapPermissionsRequest/InstallSnapPermissionsRequest.tsx b/app/components/Approvals/InstallSnapApproval/components/InstallSnapPermissionsRequest/InstallSnapPermissionsRequest.tsx index 533ea26ab2a..ceed6d7ca92 100644 --- a/app/components/Approvals/InstallSnapApproval/components/InstallSnapPermissionsRequest/InstallSnapPermissionsRequest.tsx +++ b/app/components/Approvals/InstallSnapApproval/components/InstallSnapPermissionsRequest/InstallSnapPermissionsRequest.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React, { useMemo } from 'react'; import { ScrollView, View } from 'react-native'; import styleSheet from '../../InstallSnapApproval.styles'; diff --git a/app/components/Approvals/InstallSnapApproval/components/InstallSnapPermissionsRequest/index.ts b/app/components/Approvals/InstallSnapApproval/components/InstallSnapPermissionsRequest/index.ts index b06402460be..a7070c8e222 100644 --- a/app/components/Approvals/InstallSnapApproval/components/InstallSnapPermissionsRequest/index.ts +++ b/app/components/Approvals/InstallSnapApproval/components/InstallSnapPermissionsRequest/index.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) /* eslint-disable import/prefer-default-export */ import InstallSnapPermissionsRequest from './InstallSnapPermissionsRequest'; diff --git a/app/components/Approvals/InstallSnapApproval/components/InstallSnapPermissionsRequest/test/InstallSnapPermissionsRequest.test.tsx b/app/components/Approvals/InstallSnapApproval/components/InstallSnapPermissionsRequest/test/InstallSnapPermissionsRequest.test.tsx index b23214303dd..3210c026195 100644 --- a/app/components/Approvals/InstallSnapApproval/components/InstallSnapPermissionsRequest/test/InstallSnapPermissionsRequest.test.tsx +++ b/app/components/Approvals/InstallSnapApproval/components/InstallSnapPermissionsRequest/test/InstallSnapPermissionsRequest.test.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React from 'react'; import { render } from '@testing-library/react-native'; import InstallSnapPermissionsRequest from '../InstallSnapPermissionsRequest'; diff --git a/app/components/Approvals/InstallSnapApproval/components/InstallSnapSuccess/InstallSnapSuccess.constants.ts b/app/components/Approvals/InstallSnapApproval/components/InstallSnapSuccess/InstallSnapSuccess.constants.ts index 352e4f3c3d6..f162da49c3a 100644 --- a/app/components/Approvals/InstallSnapApproval/components/InstallSnapSuccess/InstallSnapSuccess.constants.ts +++ b/app/components/Approvals/InstallSnapApproval/components/InstallSnapSuccess/InstallSnapSuccess.constants.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) const SNAP_INSTALL_SUCCESS = 'snap-install-success'; export default SNAP_INSTALL_SUCCESS; ///: END:ONLY_INCLUDE_IF diff --git a/app/components/Approvals/InstallSnapApproval/components/InstallSnapSuccess/InstallSnapSuccess.tsx b/app/components/Approvals/InstallSnapApproval/components/InstallSnapSuccess/InstallSnapSuccess.tsx index 096081191e5..f9da872debb 100644 --- a/app/components/Approvals/InstallSnapApproval/components/InstallSnapSuccess/InstallSnapSuccess.tsx +++ b/app/components/Approvals/InstallSnapApproval/components/InstallSnapSuccess/InstallSnapSuccess.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React from 'react'; import { View } from 'react-native'; import styleSheet from '../../InstallSnapApproval.styles'; diff --git a/app/components/Approvals/InstallSnapApproval/components/InstallSnapSuccess/index.ts b/app/components/Approvals/InstallSnapApproval/components/InstallSnapSuccess/index.ts index 92a640185b1..0f09c54d7b2 100644 --- a/app/components/Approvals/InstallSnapApproval/components/InstallSnapSuccess/index.ts +++ b/app/components/Approvals/InstallSnapApproval/components/InstallSnapSuccess/index.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) /* eslint-disable import/prefer-default-export */ import InstallSnapSuccess from './InstallSnapSuccess'; diff --git a/app/components/Approvals/InstallSnapApproval/components/InstallSnapSuccess/test/InstallSnapSuccess.test.tsx b/app/components/Approvals/InstallSnapApproval/components/InstallSnapSuccess/test/InstallSnapSuccess.test.tsx index 7e8d8fa78bb..bcd560e01c5 100644 --- a/app/components/Approvals/InstallSnapApproval/components/InstallSnapSuccess/test/InstallSnapSuccess.test.tsx +++ b/app/components/Approvals/InstallSnapApproval/components/InstallSnapSuccess/test/InstallSnapSuccess.test.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React from 'react'; import { render, fireEvent } from '@testing-library/react-native'; import InstallSnapSuccess from '../InstallSnapSuccess'; diff --git a/app/components/Approvals/InstallSnapApproval/components/index.ts b/app/components/Approvals/InstallSnapApproval/components/index.ts index cb49e5b7727..bf7c5e92a05 100644 --- a/app/components/Approvals/InstallSnapApproval/components/index.ts +++ b/app/components/Approvals/InstallSnapApproval/components/index.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import { InstallSnapConnectionRequest } from './InstallSnapConnectionRequest'; import { InstallSnapSuccess } from './InstallSnapSuccess'; import { InstallSnapError } from './InstallSnapError'; diff --git a/app/components/Approvals/InstallSnapApproval/index.ts b/app/components/Approvals/InstallSnapApproval/index.ts index d509654669c..fab24aeafb5 100644 --- a/app/components/Approvals/InstallSnapApproval/index.ts +++ b/app/components/Approvals/InstallSnapApproval/index.ts @@ -1,3 +1,3 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) export { default } from './InstallSnapApproval'; ///: END:ONLY_INCLUDE_IF diff --git a/app/components/Approvals/InstallSnapApproval/test/InstallSnapApproval.test.tsx b/app/components/Approvals/InstallSnapApproval/test/InstallSnapApproval.test.tsx index a67acb452a7..941d1f51b7e 100644 --- a/app/components/Approvals/InstallSnapApproval/test/InstallSnapApproval.test.tsx +++ b/app/components/Approvals/InstallSnapApproval/test/InstallSnapApproval.test.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React from 'react'; import { render, fireEvent, waitFor } from '@testing-library/react-native'; import InstallSnapApproval from '../InstallSnapApproval'; diff --git a/app/components/Nav/Main/MainNavigator.js b/app/components/Nav/Main/MainNavigator.js index 70810f5ad67..d979dd1ce8b 100644 --- a/app/components/Nav/Main/MainNavigator.js +++ b/app/components/Nav/Main/MainNavigator.js @@ -60,7 +60,7 @@ import OrderDetails from '../../UI/Ramp/Views/OrderDetails'; import SendTransaction from '../../UI/Ramp/Views/SendTransaction'; import TabBar from '../../../component-library/components/Navigation/TabBar'; import BrowserUrlModal from '../../Views/BrowserUrlModal'; -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import { SnapsSettingsList } from '../../Views/Snaps/SnapsSettingsList'; import { SnapSettings } from '../../Views/Snaps/SnapSettings'; ///: END:ONLY_INCLUDE_IF @@ -215,7 +215,7 @@ const BrowserFlow = () => ( export const DrawerContext = React.createContext({ drawerRef: null }); -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) const SnapsSettingsStack = () => ( ( options={NotificationsSettings.navigationOptions} /> { - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(external-snaps) } { { - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(external-snaps) } { diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js index 1fa0bf6329f..c51dd6e6ff3 100644 --- a/app/components/Nav/Main/index.js +++ b/app/components/Nav/Main/index.js @@ -81,7 +81,7 @@ import { startIncomingTransactionPolling, stopIncomingTransactionPolling, } from '../../../util/transaction-controller'; -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import { SnapsExecutionWebView } from '../../../lib/snaps'; ///: END:ONLY_INCLUDE_IF @@ -366,7 +366,7 @@ const Main = (props) => { renderLoader() )} { - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) } diff --git a/app/components/Views/Settings/index.tsx b/app/components/Views/Settings/index.tsx index c791f2e3e4e..b27a0394131 100644 --- a/app/components/Views/Settings/index.tsx +++ b/app/components/Views/Settings/index.tsx @@ -11,7 +11,7 @@ import Routes from '../../../constants/navigation/Routes'; import { Authentication } from '../../../core/'; import { Colors } from '../../../util/theme/models'; import { SettingsViewSelectorsIDs } from '../../../../e2e/selectors/Settings/SettingsView.selectors'; -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import { createSnapsSettingsListNavDetails } from '../Snaps/SnapsSettingsList/SnapsSettingsList'; ///: END:ONLY_INCLUDE_IF import { TextColor } from '../../../component-library/components/Texts/Text'; @@ -112,7 +112,7 @@ const Settings = () => { }); }; - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(external-snaps) const onPressSnaps = () => { navigation.navigate(...createSnapsSettingsListNavDetails()); }; @@ -222,7 +222,7 @@ const Settings = () => { testID={SettingsViewSelectorsIDs.NETWORKS} /> { - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(external-snaps) } diff --git a/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx b/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx index 9e3952cce04..0ad1d8516ae 100644 --- a/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx +++ b/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React, { useCallback, useEffect } from 'react'; import { View, ScrollView, SafeAreaView } from 'react-native'; diff --git a/app/components/Views/Snaps/SnapSettings/index.ts b/app/components/Views/Snaps/SnapSettings/index.ts index 656ee64e7b1..cdef0b73e11 100644 --- a/app/components/Views/Snaps/SnapSettings/index.ts +++ b/app/components/Views/Snaps/SnapSettings/index.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) /* eslint-disable import/prefer-default-export */ import SnapSettings from './SnapSettings'; diff --git a/app/components/Views/Snaps/SnapsSettingsList/SnapsSettingsList.styles.ts b/app/components/Views/Snaps/SnapsSettingsList/SnapsSettingsList.styles.ts index e405aefd2f8..d889308be91 100644 --- a/app/components/Views/Snaps/SnapsSettingsList/SnapsSettingsList.styles.ts +++ b/app/components/Views/Snaps/SnapsSettingsList/SnapsSettingsList.styles.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import { StyleSheet } from 'react-native'; import { Theme } from '../../../../util/theme/models'; diff --git a/app/components/Views/Snaps/SnapsSettingsList/SnapsSettingsList.tsx b/app/components/Views/Snaps/SnapsSettingsList/SnapsSettingsList.tsx index da7637b53c2..80d981f3e2f 100644 --- a/app/components/Views/Snaps/SnapsSettingsList/SnapsSettingsList.tsx +++ b/app/components/Views/Snaps/SnapsSettingsList/SnapsSettingsList.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React, { useEffect } from 'react'; import { View, ScrollView } from 'react-native'; import { useSelector } from 'react-redux'; diff --git a/app/components/Views/Snaps/SnapsSettingsList/index.ts b/app/components/Views/Snaps/SnapsSettingsList/index.ts index 5d55abd28ed..8259f82aef6 100644 --- a/app/components/Views/Snaps/SnapsSettingsList/index.ts +++ b/app/components/Views/Snaps/SnapsSettingsList/index.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) /* eslint-disable import/prefer-default-export */ import SnapsSettingsList from './SnapsSettingsList'; diff --git a/app/components/Views/Snaps/components/SnapDescription/SnapDescription.constants.ts b/app/components/Views/Snaps/components/SnapDescription/SnapDescription.constants.ts index 7ed6bb390c6..4d7ebc143b9 100644 --- a/app/components/Views/Snaps/components/SnapDescription/SnapDescription.constants.ts +++ b/app/components/Views/Snaps/components/SnapDescription/SnapDescription.constants.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) export const SNAP_DESCRIPTION_TITLE = 'snap-description-title'; export const SNAP_DESCRIPTION = 'snap-description'; ///: END:ONLY_INCLUDE_IF diff --git a/app/components/Views/Snaps/components/SnapDescription/SnapDescription.styles.tsx b/app/components/Views/Snaps/components/SnapDescription/SnapDescription.styles.tsx index 1bc9c6eefac..ef13bea85b1 100644 --- a/app/components/Views/Snaps/components/SnapDescription/SnapDescription.styles.tsx +++ b/app/components/Views/Snaps/components/SnapDescription/SnapDescription.styles.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import { StyleSheet } from 'react-native'; import { Theme } from '../../../../../util/theme/models'; diff --git a/app/components/Views/Snaps/components/SnapDescription/SnapDescription.tsx b/app/components/Views/Snaps/components/SnapDescription/SnapDescription.tsx index 34e6667dbc0..c8859960f5e 100644 --- a/app/components/Views/Snaps/components/SnapDescription/SnapDescription.tsx +++ b/app/components/Views/Snaps/components/SnapDescription/SnapDescription.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React from 'react'; import { View } from 'react-native'; import Text, { diff --git a/app/components/Views/Snaps/components/SnapDescription/index.ts b/app/components/Views/Snaps/components/SnapDescription/index.ts index 5725174bc9c..25ac400e26e 100644 --- a/app/components/Views/Snaps/components/SnapDescription/index.ts +++ b/app/components/Views/Snaps/components/SnapDescription/index.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) /* eslint-disable import/prefer-default-export */ import SnapDescription from './SnapDescription'; diff --git a/app/components/Views/Snaps/components/SnapDescription/test/SnapDescription.test.tsx b/app/components/Views/Snaps/components/SnapDescription/test/SnapDescription.test.tsx index 1c0b5afe035..798f14a7278 100644 --- a/app/components/Views/Snaps/components/SnapDescription/test/SnapDescription.test.tsx +++ b/app/components/Views/Snaps/components/SnapDescription/test/SnapDescription.test.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React from 'react'; import { render } from '@testing-library/react-native'; import SnapDescription from '../SnapDescription'; diff --git a/app/components/Views/Snaps/components/SnapDetails/SnapDetails.constants.ts b/app/components/Views/Snaps/components/SnapDetails/SnapDetails.constants.ts index c8396b5c66d..4d89dae85e6 100644 --- a/app/components/Views/Snaps/components/SnapDetails/SnapDetails.constants.ts +++ b/app/components/Views/Snaps/components/SnapDetails/SnapDetails.constants.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) export const SNAP_DETAILS_CELL = 'snap-details-cell'; export const SNAP_DETAILS_SWITCH = 'snap-details-switch'; export const SNAP_DETAILS_INSTALL_ORIGIN = 'snap-details-install-origin'; diff --git a/app/components/Views/Snaps/components/SnapDetails/SnapDetails.styles.ts b/app/components/Views/Snaps/components/SnapDetails/SnapDetails.styles.ts index d24e6e45e8e..86e011cafd7 100644 --- a/app/components/Views/Snaps/components/SnapDetails/SnapDetails.styles.ts +++ b/app/components/Views/Snaps/components/SnapDetails/SnapDetails.styles.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import { StyleSheet } from 'react-native'; import { Theme } from '../../../../../util/theme/models'; diff --git a/app/components/Views/Snaps/components/SnapDetails/SnapDetails.tsx b/app/components/Views/Snaps/components/SnapDetails/SnapDetails.tsx index e8534d68be8..fa44df615a6 100644 --- a/app/components/Views/Snaps/components/SnapDetails/SnapDetails.tsx +++ b/app/components/Views/Snaps/components/SnapDetails/SnapDetails.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React, { useCallback, useMemo, useState } from 'react'; import { View, Switch } from 'react-native'; diff --git a/app/components/Views/Snaps/components/SnapDetails/index.ts b/app/components/Views/Snaps/components/SnapDetails/index.ts index 9ae8124c228..dfb9b5fe547 100644 --- a/app/components/Views/Snaps/components/SnapDetails/index.ts +++ b/app/components/Views/Snaps/components/SnapDetails/index.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) /* eslint-disable import/prefer-default-export */ import SnapDetails from './SnapDetails'; diff --git a/app/components/Views/Snaps/components/SnapElement/SnapElement.constants.ts b/app/components/Views/Snaps/components/SnapElement/SnapElement.constants.ts index 73b689d8d51..03e9d5e6edc 100644 --- a/app/components/Views/Snaps/components/SnapElement/SnapElement.constants.ts +++ b/app/components/Views/Snaps/components/SnapElement/SnapElement.constants.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) const SNAP_ElEMENT = 'snap-element'; export default SNAP_ElEMENT; ///: END:ONLY_INCLUDE_IF diff --git a/app/components/Views/Snaps/components/SnapElement/SnapElement.styles.ts b/app/components/Views/Snaps/components/SnapElement/SnapElement.styles.ts index 6e880f5e5ae..8c681c5d754 100644 --- a/app/components/Views/Snaps/components/SnapElement/SnapElement.styles.ts +++ b/app/components/Views/Snaps/components/SnapElement/SnapElement.styles.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import { StyleSheet } from 'react-native'; const styleSheet = () => diff --git a/app/components/Views/Snaps/components/SnapElement/SnapElement.tsx b/app/components/Views/Snaps/components/SnapElement/SnapElement.tsx index 1e55449e041..02cfbb50051 100644 --- a/app/components/Views/Snaps/components/SnapElement/SnapElement.tsx +++ b/app/components/Views/Snaps/components/SnapElement/SnapElement.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React from 'react'; import { View } from 'react-native'; import Cell, { diff --git a/app/components/Views/Snaps/components/SnapElement/index.ts b/app/components/Views/Snaps/components/SnapElement/index.ts index ff44e2fc1db..b97cd3c3316 100644 --- a/app/components/Views/Snaps/components/SnapElement/index.ts +++ b/app/components/Views/Snaps/components/SnapElement/index.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) /* eslint-disable import/prefer-default-export */ import SnapElement from './SnapElement'; diff --git a/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.constants.ts b/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.constants.ts index 1eb1f8c1ade..c90f3673f74 100644 --- a/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.constants.ts +++ b/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.constants.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) export const SNAP_PERMISSIONS_DATE = 'snap-permissions-date'; export const SNAP_PERMISSION_CELL = 'snap-permission-cell'; export const SNAP_PERMISSIONS_TITLE = 'snap-permissions-title'; diff --git a/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.styles.ts b/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.styles.ts index 563d059d123..637de70817e 100644 --- a/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.styles.ts +++ b/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.styles.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import { StyleSheet } from 'react-native'; import { Theme } from '../../../../../util/theme/models'; diff --git a/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.tsx b/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.tsx index 8f898fa17f6..e5bddf49d80 100644 --- a/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.tsx +++ b/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React, { useMemo } from 'react'; import { View } from 'react-native'; import { useStyles } from '../../../../hooks/useStyles'; diff --git a/app/components/Views/Snaps/components/SnapPermissionCell/index.ts b/app/components/Views/Snaps/components/SnapPermissionCell/index.ts index 00578bd3175..2a67b5af26b 100644 --- a/app/components/Views/Snaps/components/SnapPermissionCell/index.ts +++ b/app/components/Views/Snaps/components/SnapPermissionCell/index.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) /* eslint-disable import/prefer-default-export */ import SnapPermissionCell from './SnapPermissionCell'; diff --git a/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.contants.ts b/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.contants.ts index bba0173aff0..90bebf277f3 100644 --- a/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.contants.ts +++ b/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.contants.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) const SNAP_PERMISSIONS = 'snap-permissions'; export default SNAP_PERMISSIONS; ///: END:ONLY_INCLUDE_IF diff --git a/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.styles.ts b/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.styles.ts index 9f2db5fade3..ff17ff14225 100644 --- a/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.styles.ts +++ b/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.styles.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import { StyleSheet } from 'react-native'; /** diff --git a/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.tsx b/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.tsx index 22814f0ad56..ad6f858a0b7 100644 --- a/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.tsx +++ b/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React, { useCallback, useMemo } from 'react'; import { View } from 'react-native'; import slip44 from '@metamask/slip44'; diff --git a/app/components/Views/Snaps/components/SnapPermissions/index.ts b/app/components/Views/Snaps/components/SnapPermissions/index.ts index 5807ed33f0d..954ae53806e 100644 --- a/app/components/Views/Snaps/components/SnapPermissions/index.ts +++ b/app/components/Views/Snaps/components/SnapPermissions/index.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) /* eslint-disable import/prefer-default-export */ import SnapPermissions from './SnapPermissions'; diff --git a/app/components/Views/Snaps/components/SnapVersionTag/SnapVersionTag.constants.ts b/app/components/Views/Snaps/components/SnapVersionTag/SnapVersionTag.constants.ts index 31bb246ded7..d86e90da30f 100644 --- a/app/components/Views/Snaps/components/SnapVersionTag/SnapVersionTag.constants.ts +++ b/app/components/Views/Snaps/components/SnapVersionTag/SnapVersionTag.constants.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) export const SNAP_VERSION_BADGE = 'snap-version-badge'; export const SNAP_VERSION_BADGE_VALUE = 'snap-version-badge-value'; ///: END:ONLY_INCLUDE_IF diff --git a/app/components/Views/Snaps/components/SnapVersionTag/SnapVersionTag.styles.ts b/app/components/Views/Snaps/components/SnapVersionTag/SnapVersionTag.styles.ts index 2d7dd0b0bce..a23f347bfe3 100644 --- a/app/components/Views/Snaps/components/SnapVersionTag/SnapVersionTag.styles.ts +++ b/app/components/Views/Snaps/components/SnapVersionTag/SnapVersionTag.styles.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import { StyleSheet } from 'react-native'; import { Theme } from '../../../../../util/theme/models'; diff --git a/app/components/Views/Snaps/components/SnapVersionTag/SnapVersionTag.tsx b/app/components/Views/Snaps/components/SnapVersionTag/SnapVersionTag.tsx index b92322824a1..8cbfb54c0b5 100644 --- a/app/components/Views/Snaps/components/SnapVersionTag/SnapVersionTag.tsx +++ b/app/components/Views/Snaps/components/SnapVersionTag/SnapVersionTag.tsx @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React from 'react'; import { View } from 'react-native'; import Text, { diff --git a/app/components/Views/Snaps/components/SnapVersionTag/index.ts b/app/components/Views/Snaps/components/SnapVersionTag/index.ts index 81702755e8a..2485b2be4b9 100644 --- a/app/components/Views/Snaps/components/SnapVersionTag/index.ts +++ b/app/components/Views/Snaps/components/SnapVersionTag/index.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) /* eslint-disable import/prefer-default-export */ import SnapVersionBadge from './SnapVersionTag'; diff --git a/app/constants/navigation/Routes.ts b/app/constants/navigation/Routes.ts index 49144f33c2c..81b2182ba31 100644 --- a/app/constants/navigation/Routes.ts +++ b/app/constants/navigation/Routes.ts @@ -130,7 +130,7 @@ const Routes = { OPT_IN_STACK: 'OptInStack', DETAILS: 'NotificationsDetails', }, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(external-snaps) SNAPS: { SNAPS_SETTINGS_LIST: 'SnapsSettingsList', SNAP_SETTINGS: 'SnapSettings', diff --git a/app/core/BackgroundBridge/BackgroundBridge.js b/app/core/BackgroundBridge/BackgroundBridge.js index dbb12204ea0..fa2f3fce29a 100644 --- a/app/core/BackgroundBridge/BackgroundBridge.js +++ b/app/core/BackgroundBridge/BackgroundBridge.js @@ -22,7 +22,7 @@ import { selectProviderConfig, } from '../../selectors/networkController'; import { store } from '../../store'; -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import snapMethodMiddlewareBuilder from '../Snaps/SnapsMethodMiddleware'; import { SubjectType } from '@metamask/permission-controller'; ///: END:ONLY_INCLUDE_IF @@ -408,7 +408,7 @@ export class BackgroundBridge extends EventEmitter { }), ); - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) // Snaps middleware engine.push( snapMethodMiddlewareBuilder( diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 8f666a0333e..4aacc5f82ec 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -28,7 +28,7 @@ import { TokenListControllerEvents, TokenBalancesControllerState, } from '@metamask/assets-controllers'; -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import { AppState } from 'react-native'; import PREINSTALLED_SNAPS from '../lib/snaps/preinstalled-snaps'; ///: END:ONLY_INCLUDE_IF @@ -43,7 +43,7 @@ import { KeyringControllerState, KeyringControllerActions, KeyringControllerEvents, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) KeyringTypes, ///: END:ONLY_INCLUDE_IF } from '@metamask/keyring-controller'; @@ -86,7 +86,7 @@ import { PermissionControllerActions, PermissionControllerEvents, PermissionControllerState, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) SubjectMetadataController, SubjectMetadataControllerActions, SubjectMetadataControllerEvents, @@ -99,7 +99,7 @@ import { PPOMControllerEvents, PPOMState, } from '@metamask/ppom-validator'; -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import { JsonSnapsRegistry, AllowedActions as SnapsAllowedActions, @@ -152,7 +152,7 @@ import Logger from '../util/Logger'; import { isZero } from '../util/lodash'; import { MetaMetricsEvents, MetaMetrics } from './Analytics'; -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import { SnapBridge, ExcludedSnapEndowments, @@ -213,7 +213,7 @@ const encryptor = new Encryptor({ // eslint-disable-next-line @typescript-eslint/no-explicit-any let currentChainId: any; -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) // TODO remove these custom types when the PhishingController is to version >= 7.0.0 interface MaybeUpdateState { type: `${PhishingController['name']}:maybeUpdateState`; @@ -249,7 +249,7 @@ type GlobalActions = | PermissionControllerActions | SignatureControllerActions | LoggingControllerActions - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) | SnapsGlobalActions ///: END:ONLY_INCLUDE_IF | KeyringControllerActions @@ -265,7 +265,7 @@ type GlobalEvents = | TokenListStateChange | NetworkControllerEvents | PermissionControllerEvents - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) | SnapsGlobalEvents ///: END:ONLY_INCLUDE_IF | SignatureControllerEvents @@ -299,7 +299,7 @@ export interface EngineState { TokensController: TokensState; TokenDetectionController: BaseState; NftDetectionController: BaseState; - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) SnapController: PersistedSnapControllerState; SnapsRegistry: SnapsRegistryState; SubjectMetadataController: SubjectMetadataControllerState; @@ -342,7 +342,7 @@ interface Controllers { TransactionController: TransactionController; SmartTransactionsController: SmartTransactionsController; SignatureController: SignatureController; - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) SnapController: SnapController; SubjectMetadataController: SubjectMetadataController; ///: END:ONLY_INCLUDE_IF @@ -391,7 +391,7 @@ class Engine { // eslint-disable-next-line @typescript-eslint/no-explicit-any lastIncomingTxBlockInfo: any; - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) /** * Object that runs and manages the execution of Snaps */ @@ -684,7 +684,7 @@ class Engine { keyringBuilders: [qrKeyringBuilder, ledgerKeyringBuilder], }); - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) /** * Gets the mnemonic of the user's primary keyring. */ @@ -802,7 +802,7 @@ class Engine { `${approvalController.name}:hasRequest`, `${approvalController.name}:acceptRequest`, `${approvalController.name}:rejectRequest`, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) `SnapController:getPermitted`, `SnapController:install`, `SubjectMetadataController:getSubjectMetadata`, @@ -849,14 +849,14 @@ class Engine { ); }, }), - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) ...getSnapPermissionSpecifications(), ///: END:ONLY_INCLUDE_IF }, unrestrictedMethods, }); - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) const subjectMetadataController = new SubjectMetadataController({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore TODO: Resolve/patch mismatch between base-controller versions. Before: never, never. Now: string, string, which expects 3rd and 4th args to be informed for restrictedControllerMessengers @@ -907,7 +907,11 @@ class Engine { }; const requireAllowlist = process.env.METAMASK_BUILD_TYPE === 'main'; + const disableSnapInstallation = process.env.METAMASK_BUILD_TYPE === 'main'; + const allowLocalSnaps = process.env.METAMASK_BUILD_TYPE === 'flask'; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore TODO: Resolve/patch mismatch between base-controller versions. const snapsRegistryMessenger: SnapsRegistryMessenger = this.controllerMessenger.getRestricted({ name: 'SnapsRegistry', @@ -927,6 +931,8 @@ class Engine { }); this.snapExecutionService = new WebViewExecutionService({ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore TODO: Resolve/patch mismatch between base-controller versions. messenger: this.controllerMessenger.getRestricted({ name: 'ExecutionService', allowedActions: [], @@ -981,6 +987,7 @@ class Engine { featureFlags: { requireAllowlist, allowLocalSnaps, + disableSnapInstallation, }, state: initialState.SnapController || undefined, // TODO: Replace "any" with type @@ -1286,7 +1293,7 @@ class Engine { getCurrentChainId: () => networkController.state.providerConfig.chainId, }), loggingController, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) snapController, subjectMetadataController, ///: END:ONLY_INCLUDE_IF @@ -1781,7 +1788,7 @@ export default { TokensController, TokenDetectionController, NftDetectionController, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) SnapController, SubjectMetadataController, ///: END:ONLY_INCLUDE_IF @@ -1822,7 +1829,7 @@ export default { GasFeeController, TokenDetectionController, NftDetectionController, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) SnapController, SubjectMetadataController, ///: END:ONLY_INCLUDE_IF diff --git a/app/core/EngineService/EngineService.ts b/app/core/EngineService/EngineService.ts index 4c96e318595..acebf290499 100644 --- a/app/core/EngineService/EngineService.ts +++ b/app/core/EngineService/EngineService.ts @@ -93,7 +93,7 @@ class EngineService { name: 'ApprovalController', key: `${engine.context.ApprovalController.name}:stateChange`, }, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) { name: 'SnapController', key: `${engine.context.SnapController.name}:stateChange`, diff --git a/app/core/Permissions/constants.ts b/app/core/Permissions/constants.ts index 2c43dfce34b..4ee3ab20c49 100644 --- a/app/core/Permissions/constants.ts +++ b/app/core/Permissions/constants.ts @@ -4,7 +4,7 @@ export const CaveatTypes = Object.freeze({ export const RestrictedMethods = Object.freeze({ eth_accounts: 'eth_accounts', - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) // Snap Specific Restricted Methods snap_notify: 'snap_notify', snap_dialog: 'snap_dialog', diff --git a/app/core/Permissions/specifications.js b/app/core/Permissions/specifications.js index d0ed3b08c9c..00b1666944d 100644 --- a/app/core/Permissions/specifications.js +++ b/app/core/Permissions/specifications.js @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import { caveatSpecifications as snapsCaveatsSpecifications, endowmentCaveatSpecifications as snapsEndowmentCaveatSpecifications, @@ -74,7 +74,7 @@ export const getCaveatSpecifications = ({ getInternalAccounts }) => ({ validator: (caveat, _origin, _target) => validateCaveatAccounts(caveat.value, getInternalAccounts), }, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) ...snapsCaveatsSpecifications, ...snapsEndowmentCaveatSpecifications, ///: END:ONLY_INCLUDE_IF @@ -305,7 +305,7 @@ export const unrestrictedMethods = Object.freeze([ 'metamask_logWeb3ShimUsage', 'wallet_switchEthereumChain', 'wallet_addEthereumChain', - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) 'wallet_getAllSnaps', 'wallet_getSnaps', 'wallet_requestSnaps', diff --git a/app/core/RPCMethods/RPCMethodMiddleware.ts b/app/core/RPCMethods/RPCMethodMiddleware.ts index ce7c88c8c3c..9bb2c02f463 100644 --- a/app/core/RPCMethods/RPCMethodMiddleware.ts +++ b/app/core/RPCMethods/RPCMethodMiddleware.ts @@ -62,7 +62,7 @@ export enum ApprovalTypes { RESULT_ERROR = 'result_error', RESULT_SUCCESS = 'result_success', SMART_TRANSACTION_STATUS = 'smart_transaction_status', - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(external-snaps) INSTALL_SNAP = 'wallet_installSnap', UPDATE_SNAP = 'wallet_updateSnap', ///: END:ONLY_INCLUDE_IF diff --git a/app/core/Snaps/SnapBridge.ts b/app/core/Snaps/SnapBridge.ts index 19544f66123..858bcdcd9fc 100644 --- a/app/core/Snaps/SnapBridge.ts +++ b/app/core/Snaps/SnapBridge.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) /* eslint-disable import/no-commonjs */ /* eslint-disable @typescript-eslint/no-require-imports */ /* eslint-disable @typescript-eslint/no-var-requires */ diff --git a/app/core/Snaps/SnapsMethodMiddleware.ts b/app/core/Snaps/SnapsMethodMiddleware.ts index b3cf8334d3a..47bc9ae7372 100644 --- a/app/core/Snaps/SnapsMethodMiddleware.ts +++ b/app/core/Snaps/SnapsMethodMiddleware.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import { createSnapsMethodMiddleware } from '@metamask/snaps-rpc-methods'; import { RequestedPermissions, diff --git a/app/core/Snaps/index.ts b/app/core/Snaps/index.ts index b92480b3468..82ee015000f 100644 --- a/app/core/Snaps/index.ts +++ b/app/core/Snaps/index.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import SnapBridge from './SnapBridge'; import { ExcludedSnapPermissions, diff --git a/app/core/Snaps/location/fetch.ts b/app/core/Snaps/location/fetch.ts index 9593013a4bc..5bc167bab40 100644 --- a/app/core/Snaps/location/fetch.ts +++ b/app/core/Snaps/location/fetch.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) /* eslint-disable import/prefer-default-export */ import ReactNativeBlobUtil, { FetchBlobResponse } from 'react-native-blob-util'; import Logger from '../../../util/Logger'; diff --git a/app/core/Snaps/location/index.ts b/app/core/Snaps/location/index.ts index c8c91970b5c..317b3bab9ae 100644 --- a/app/core/Snaps/location/index.ts +++ b/app/core/Snaps/location/index.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) export * from './location'; export * from './fetch'; ///: END:ONLY_INCLUDE_IF diff --git a/app/core/Snaps/location/location.ts b/app/core/Snaps/location/location.ts index 4b225702fff..ebdc7299824 100644 --- a/app/core/Snaps/location/location.ts +++ b/app/core/Snaps/location/location.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import { NpmLocation } from './npm'; import { HttpLocation, diff --git a/app/core/Snaps/location/npm.ts b/app/core/Snaps/location/npm.ts index 2154aeec751..3de9b343977 100644 --- a/app/core/Snaps/location/npm.ts +++ b/app/core/Snaps/location/npm.ts @@ -1,5 +1,5 @@ /* eslint-disable import/prefer-default-export */ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import { VirtualFile } from '@metamask/snaps-utils'; import { stringToBytes } from '@metamask/utils'; import { NativeModules } from 'react-native'; diff --git a/app/core/Snaps/permissions/permissions.ts b/app/core/Snaps/permissions/permissions.ts index ece72bfe5b4..acf658902d2 100644 --- a/app/core/Snaps/permissions/permissions.ts +++ b/app/core/Snaps/permissions/permissions.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) // TODO: Figure out which permissions should be disabled at this point export const ExcludedSnapPermissions = Object.freeze([]); export const ExcludedSnapEndowments = Object.freeze([]); diff --git a/app/lib/snaps/SnapsExecutionWebView.tsx b/app/lib/snaps/SnapsExecutionWebView.tsx index 6a98e818322..db1aa6db3e2 100644 --- a/app/lib/snaps/SnapsExecutionWebView.tsx +++ b/app/lib/snaps/SnapsExecutionWebView.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable @typescript-eslint/no-require-imports */ /* eslint-disable import/no-commonjs */ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import React, { Component, RefObject } from 'react'; import { View, ScrollView, NativeSyntheticEvent } from 'react-native'; import WebView, { WebViewMessageEvent } from 'react-native-webview'; diff --git a/app/lib/snaps/index.ts b/app/lib/snaps/index.ts index e6450e80762..30785e1e2dc 100644 --- a/app/lib/snaps/index.ts +++ b/app/lib/snaps/index.ts @@ -1,3 +1,3 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) export * from './SnapsExecutionWebView'; ///: END:ONLY_INCLUDE_IF diff --git a/app/lib/snaps/styles.ts b/app/lib/snaps/styles.ts index 38a89029d6d..1b24fde664f 100644 --- a/app/lib/snaps/styles.ts +++ b/app/lib/snaps/styles.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) /* eslint-disable react-native/no-color-literals */ import { StyleSheet } from 'react-native'; diff --git a/app/selectors/types.ts b/app/selectors/types.ts index 01ce67457a3..500a30f099b 100644 --- a/app/selectors/types.ts +++ b/app/selectors/types.ts @@ -21,7 +21,7 @@ import { GasFeeController } from '@metamask/gas-fee-controller'; import { PPOMState } from '@metamask/ppom-validator'; import { ApprovalControllerState } from '@metamask/approval-controller'; import { AccountsControllerState } from '@metamask/accounts-controller'; -///: BEGIN:ONLY_INCLUDE_IF(snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import { SnapController } from '@metamask/snaps-controllers'; ///: END:ONLY_INCLUDE_IF export interface EngineState { @@ -42,7 +42,7 @@ export interface EngineState { TokenRatesController: TokenRatesState; TransactionController: TransactionState; SwapsController: SwapsController; - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) SnapController: SnapController; ///: END:ONLY_INCLUDE_IF GasFeeController: GasFeeController; diff --git a/metro.transform.js b/metro.transform.js index eaccce80e11..06560242cf5 100644 --- a/metro.transform.js +++ b/metro.transform.js @@ -12,10 +12,19 @@ const svgTransformer = require('react-native-svg-transformer'); // Code fence removal variables const fileExtsToScan = ['.js', '.jsx', '.cjs', '.mjs', '.ts', '.tsx']; -const availableFeatures = new Set(['flask', 'snaps', 'beta']); +const availableFeatures = new Set([ + 'flask', + 'preinstalled-snaps', + 'external-snaps', + 'beta', +]); -const mainFeatureSet = new Set([]); -const flaskFeatureSet = new Set(['flask', 'snaps']); +const mainFeatureSet = new Set(['preinstalled-snaps']); +const flaskFeatureSet = new Set([ + 'flask', + 'preinstalled-snaps', + 'external-snaps', +]); /** * Gets the features for the current build type, used to determine which code diff --git a/package.json b/package.json index 7ec87ef1b84..552e94579b0 100644 --- a/package.json +++ b/package.json @@ -179,10 +179,10 @@ "@metamask/signature-controller": "^16.0.0", "@metamask/slip44": "3.1.0", "@metamask/smart-transactions-controller": "10.1.1", - "@metamask/snaps-controllers": "^8.3.1", - "@metamask/snaps-rpc-methods": "^9.0.0", - "@metamask/snaps-sdk": "^4.2.0", - "@metamask/snaps-utils": "^7.4.0", + "@metamask/snaps-controllers": "^9.2.0", + "@metamask/snaps-rpc-methods": "^9.1.4", + "@metamask/snaps-sdk": "^6.0.0", + "@metamask/snaps-utils": "^7.7.0", "@metamask/swappable-obj-proxy": "^2.1.0", "@metamask/swaps-controller": "^9.0.0", "@metamask/transaction-controller": "^13.0.0", @@ -572,4 +572,4 @@ } }, "packageManager": "yarn@1.22.22" -} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 0096d71536d..3e03fa82aef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3692,6 +3692,16 @@ "@metamask/utils" "^8.3.0" nanoid "^3.1.31" +"@metamask/approval-controller@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@metamask/approval-controller/-/approval-controller-7.0.0.tgz#9b872df23c8dcfb30fe3f5f4502850ba350c1ec7" + integrity sha512-8/OogfKqe7IRXqjOVrc2fiiqzmdRKZ/AU2oWQXSl+vMfm3TQkzW9hd9jMpXsypg7qeQQ1OmckzkSJMAynNe4YA== + dependencies: + "@metamask/base-controller" "^6.0.0" + "@metamask/rpc-errors" "^6.2.1" + "@metamask/utils" "^8.3.0" + nanoid "^3.1.31" + "@metamask/assets-controllers@^30.0.0": version "30.0.0" resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-30.0.0.tgz#f8c4094692f184a57f2587e0e71e06c349583e74" @@ -3750,6 +3760,14 @@ "@metamask/utils" "^8.3.0" immer "^9.0.6" +"@metamask/base-controller@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@metamask/base-controller/-/base-controller-6.0.0.tgz#c21563c3f887ab00da6a29c1dc258460e66698bf" + integrity sha512-N1fyGh76Kouf3jnb8oPEKTgtU/rxkbv9KTJqwp6Wr3HwZQCGvDkoz+iS3N2apYwstIGuMd7NMrcs8v6SSWunIQ== + dependencies: + "@metamask/utils" "^8.3.0" + immer "^9.0.6" + "@metamask/browser-passworder@^4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@metamask/browser-passworder/-/browser-passworder-4.3.0.tgz#62c200750efcea864bd31d685120331859e1ab1e" @@ -3799,6 +3817,21 @@ eth-ens-namehash "^2.0.8" fast-deep-equal "^3.1.3" +"@metamask/controller-utils@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-11.0.0.tgz#d72e69bc48eddff1b3a6329132d170e86f787047" + integrity sha512-DQrl1VzLHaLGzhXWjS/r4+cRIxC8Ro87Wco22TKDWtZ6GfQeJOuiDAU8ZKMEBVKDJCG+YXtyECWmK7zaThjVyA== + dependencies: + "@ethereumjs/util" "^8.1.0" + "@metamask/eth-query" "^4.0.0" + "@metamask/ethjs-unit" "^0.3.0" + "@metamask/utils" "^8.3.0" + "@spruceid/siwe-parser" "2.1.0" + "@types/bn.js" "^5.1.5" + bn.js "^5.2.1" + eth-ens-namehash "^2.0.8" + fast-deep-equal "^3.1.3" + "@metamask/controller-utils@^5.0.1": version "5.0.2" resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-5.0.2.tgz#f6e848d9b80aca7943e1edae927324982305d1f8" @@ -4153,6 +4186,15 @@ "@metamask/safe-event-emitter" "^3.0.0" "@metamask/utils" "^8.3.0" +"@metamask/json-rpc-engine@^9.0.0": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@metamask/json-rpc-engine/-/json-rpc-engine-9.0.0.tgz#41224ee7dae6b58befb883fb643fe56f47682fd4" + integrity sha512-/zVBHbDG2VsJMUxKIcNGwg/f9NbMcJxee1qWRtC5HHmyCv5uqfArxOK6yHpjW02d9ENtrj18uWUtP7/ofSoeoQ== + dependencies: + "@metamask/rpc-errors" "^6.2.1" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^8.3.0" + "@metamask/json-rpc-middleware-stream@^7.0.1": version "7.0.1" resolved "https://registry.yarnpkg.com/@metamask/json-rpc-middleware-stream/-/json-rpc-middleware-stream-7.0.1.tgz#3e10c93c88507b1a55eea5d125ebf87db0f8fead" @@ -4163,6 +4205,16 @@ "@metamask/utils" "^8.3.0" readable-stream "^3.6.2" +"@metamask/json-rpc-middleware-stream@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@metamask/json-rpc-middleware-stream/-/json-rpc-middleware-stream-8.0.0.tgz#ba70a3e3ae793e2153761ec15b54b479ad47f79c" + integrity sha512-ZSoOnOnTO212FnXRnjCBfoiwv3IBnprbc9C2yPeXXpDzoc0NrH5MGYL5S8pcTVVDHjjCPJS5J6EDTx7KE3VSdg== + dependencies: + "@metamask/json-rpc-engine" "^9.0.0" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^8.3.0" + readable-stream "^3.6.2" + "@metamask/key-tree@^9.0.0", "@metamask/key-tree@^9.1.1": version "9.1.1" resolved "https://registry.yarnpkg.com/@metamask/key-tree/-/key-tree-9.1.1.tgz#0a604e7ee147d0f2433adb87c8be92e8c8613bb5" @@ -4351,6 +4403,21 @@ "@metamask/safe-event-emitter" "^2.0.0" through2 "^2.0.3" +"@metamask/permission-controller@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@metamask/permission-controller/-/permission-controller-10.0.0.tgz#821280763cc37e9597fe7d207b5da00a881ad32a" + integrity sha512-gwoDmSsUnAFIzSeJ9FqUmoUYNofdhA0buMkH1AVXC5i/eOsEZGVUU6dYThrmUSzBK/wyNNIUGjaUSj4eMMtR6Q== + dependencies: + "@metamask/base-controller" "^6.0.0" + "@metamask/controller-utils" "^11.0.0" + "@metamask/json-rpc-engine" "^9.0.0" + "@metamask/rpc-errors" "^6.2.1" + "@metamask/utils" "^8.3.0" + "@types/deep-freeze-strict" "^1.1.0" + deep-freeze-strict "^1.1.1" + immer "^9.0.6" + nanoid "^3.1.31" + "@metamask/permission-controller@^9.0.0", "@metamask/permission-controller@^9.0.2": version "9.1.1" resolved "https://registry.yarnpkg.com/@metamask/permission-controller/-/permission-controller-9.1.1.tgz#de8c15438afa4ef65e419c20f358799924fc3f2c" @@ -4366,6 +4433,17 @@ immer "^9.0.6" nanoid "^3.1.31" +"@metamask/phishing-controller@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@metamask/phishing-controller/-/phishing-controller-10.0.0.tgz#9d15b369e07d6e17aff2037525b188a87c9237d6" + integrity sha512-kB5ElmnncTm46XXi/4/b7bGN7KTWmYQy1Bdls9NL9DBNeTDpv/bgtAUnle/pVPLO0i05GXl8Z/eEyTAFa4G7Dw== + dependencies: + "@metamask/base-controller" "^6.0.0" + "@metamask/controller-utils" "^11.0.0" + "@types/punycode" "^2.1.0" + eth-phishing-detect "^1.2.0" + punycode "^2.1.1" + "@metamask/phishing-controller@^9.0.0", "@metamask/phishing-controller@^9.0.1": version "9.0.2" resolved "https://registry.yarnpkg.com/@metamask/phishing-controller/-/phishing-controller-9.0.2.tgz#d140b6a8a05947c59b04bf161a0d9055e6b711b2" @@ -4601,7 +4679,7 @@ fast-json-patch "^3.1.0" lodash "^4.17.21" -"@metamask/snaps-controllers@^8.1.1", "@metamask/snaps-controllers@^8.3.1": +"@metamask/snaps-controllers@^8.1.1": version "8.3.1" resolved "https://registry.yarnpkg.com/@metamask/snaps-controllers/-/snaps-controllers-8.3.1.tgz#a509a020aa4a2eac88ecc6ec84c6bc488282f143" integrity sha512-kGyywksj7VyEuQXRKdFZBSAcTYPjr22J21FLhIBiiSlBI3lbHEqb/hr/q8b+m27iA1JGtTrvJL1JMpRFGQAOEA== @@ -4631,6 +4709,36 @@ readable-web-to-node-stream "^3.0.2" tar-stream "^3.1.7" +"@metamask/snaps-controllers@^9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@metamask/snaps-controllers/-/snaps-controllers-9.2.0.tgz#02c524028afdb943f4054a59b0ffb075585e289c" + integrity sha512-4UP1aS4Yes4PfANLwal3fg7Inx/hZO41NJjWwfpfgSfVweuf6zLYQAW2+vsdDoHmWDnWACE7tQdt5FjcP293lw== + dependencies: + "@metamask/approval-controller" "^7.0.0" + "@metamask/base-controller" "^6.0.0" + "@metamask/json-rpc-engine" "^9.0.0" + "@metamask/json-rpc-middleware-stream" "^8.0.0" + "@metamask/object-multiplex" "^2.0.0" + "@metamask/permission-controller" "^10.0.0" + "@metamask/phishing-controller" "^10.0.0" + "@metamask/post-message-stream" "^8.1.0" + "@metamask/rpc-errors" "^6.2.1" + "@metamask/snaps-registry" "^3.1.0" + "@metamask/snaps-rpc-methods" "^9.1.4" + "@metamask/snaps-sdk" "^6.0.0" + "@metamask/snaps-utils" "^7.7.0" + "@metamask/utils" "^8.3.0" + "@xstate/fsm" "^2.0.0" + browserify-zlib "^0.2.0" + concat-stream "^2.0.0" + fast-deep-equal "^3.1.3" + get-npm-tarball-url "^2.0.3" + immer "^9.0.6" + nanoid "^3.1.31" + readable-stream "^3.6.2" + readable-web-to-node-stream "^3.0.2" + tar-stream "^3.1.7" + "@metamask/snaps-registry@^3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@metamask/snaps-registry/-/snaps-registry-3.1.0.tgz#05635a09971f1e106deef73ef3181f17005b1750" @@ -4641,16 +4749,16 @@ "@noble/hashes" "^1.3.2" superstruct "^1.0.3" -"@metamask/snaps-rpc-methods@^9.0.0", "@metamask/snaps-rpc-methods@^9.1.2": - version "9.1.2" - resolved "https://registry.yarnpkg.com/@metamask/snaps-rpc-methods/-/snaps-rpc-methods-9.1.2.tgz#fda8a6f8fcc4fca50b1ed71fb8c9dc88a9f9c3d8" - integrity sha512-mh/cSM4rLebRC0rGprvFBybk2fxSCJxCw0Co4jbE2TH9vdUK6z23HWB+CCeGASA978Nbpi3Gqbiz3jTCNKkU7A== +"@metamask/snaps-rpc-methods@^9.1.2", "@metamask/snaps-rpc-methods@^9.1.4": + version "9.1.4" + resolved "https://registry.yarnpkg.com/@metamask/snaps-rpc-methods/-/snaps-rpc-methods-9.1.4.tgz#d0aa695b89326eece7b23d188d8dce7d35a78d9c" + integrity sha512-eYcfJDYlXE4RqtRTyp8n7SsJ0aAQlz5H335/5jch3L3CwQwC3z8KlKm258iXPiyT7fa9VVxdZqGs/rBaivx0Iw== dependencies: "@metamask/key-tree" "^9.1.1" - "@metamask/permission-controller" "^9.0.2" + "@metamask/permission-controller" "^10.0.0" "@metamask/rpc-errors" "^6.2.1" - "@metamask/snaps-sdk" "^4.4.1" - "@metamask/snaps-utils" "^7.4.1" + "@metamask/snaps-sdk" "^6.0.0" + "@metamask/snaps-utils" "^7.7.0" "@metamask/utils" "^8.3.0" "@noble/hashes" "^1.3.1" superstruct "^1.0.3" @@ -4679,20 +4787,31 @@ fast-xml-parser "^4.3.4" superstruct "^1.0.3" -"@metamask/snaps-utils@^7.1.0", "@metamask/snaps-utils@^7.4.0", "@metamask/snaps-utils@^7.4.1": - version "7.4.1" - resolved "https://registry.yarnpkg.com/@metamask/snaps-utils/-/snaps-utils-7.4.1.tgz#54a2b40ba30cc05a0862f4662e5cd6ef5beba42e" - integrity sha512-IPoZTh90Bg4LJJCmkRig1QIyXOgdziuZqdx3c521gHaKpMlcOFl1fvII17X9yVF9SUtTSDoj1R4//4e5rnLZnw== +"@metamask/snaps-sdk@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@metamask/snaps-sdk/-/snaps-sdk-6.0.0.tgz#35e20f39100d9e662a3757deba08cb61f9cb6a59" + integrity sha512-DC7MHkjn/YXBaIX8xCUqkOIxPV9UK7k3oHFLsJI+4KclFYIk0tR4VJ7D1E1SfrNyL0BPftkuiQmlgHmS4wginw== + dependencies: + "@metamask/key-tree" "^9.1.1" + "@metamask/providers" "^17.0.0" + "@metamask/rpc-errors" "^6.2.1" + "@metamask/utils" "^8.3.0" + superstruct "^1.0.3" + +"@metamask/snaps-utils@^7.1.0", "@metamask/snaps-utils@^7.4.0", "@metamask/snaps-utils@^7.4.1", "@metamask/snaps-utils@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@metamask/snaps-utils/-/snaps-utils-7.7.0.tgz#06c890026b8b6998f170129447a18e2349f70d0c" + integrity sha512-phVFda2Pz6v9TYUGdbgk40/BTX4/hJC+sQNU5jvmb94lcGDgxKln6xwyeH1LG0PzC44STFn9Y3zRT12TANS7Ow== dependencies: "@babel/core" "^7.23.2" "@babel/types" "^7.23.0" - "@metamask/base-controller" "^5.0.2" + "@metamask/base-controller" "^6.0.0" "@metamask/key-tree" "^9.1.1" - "@metamask/permission-controller" "^9.0.2" + "@metamask/permission-controller" "^10.0.0" "@metamask/rpc-errors" "^6.2.1" "@metamask/slip44" "^3.1.0" "@metamask/snaps-registry" "^3.1.0" - "@metamask/snaps-sdk" "^4.4.1" + "@metamask/snaps-sdk" "^6.0.0" "@metamask/utils" "^8.3.0" "@noble/hashes" "^1.3.1" "@scure/base" "^1.1.1" @@ -4700,6 +4819,7 @@ cron-parser "^4.5.0" fast-deep-equal "^3.1.3" fast-json-stable-stringify "^2.1.0" + fast-xml-parser "^4.3.4" marked "^12.0.1" rfdc "^1.3.0" semver "^7.5.4" From 0703383e92f597ed6a67941292886150a40fe3f1 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 27 Jun 2024 09:43:47 -0230 Subject: [PATCH 05/16] fix: Fix BaseControllerV1 state rehydration (#10133) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** We were failing to properly restore persisted state for some BaseControllerV1 controllers. This resulted in state being lost when the application was restarted. The only confirmed affected controller so far is the `NftController`. The context here is that for some older controllers, rather than passing the initial state into the controller directly when it is constructed, we call the `update` function directly. This has always been a bad practice (controllers should update their own state), and we're forced to fix this for controllers that we update to `BaseControllerV2` (because directly updating state like this is no longer possible. But this method of rehydrating controller state was still relied upon in some cases. This was broken recently in #9570 when a condition was added to fix a type error. The condition was meant to check that the controller had a `subscribe` function. Unfortunately this `hasProperty` check only looks at owned properties, not inherited properties, and the `subscribe` function was inherited from the base class. It has been updated to use the `in` operator instead, which does look up the entire prototype chain. ## **Related issues** Fixes #10057 ## **Manual testing steps** See #10057 ## **Screenshots/Recordings** ### **Before** See #10057 ### **After** https://github.com/MetaMask/metamask-mobile/assets/2459287/b7cf3e09-086b-4964-8180-e58195969e17 ## **Pre-merge author checklist** - [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. ## **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. --- app/core/Engine.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 4aacc5f82ec..0330aa4a14d 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -1347,7 +1347,8 @@ class Engine { for (const controller of controllers) { if ( hasProperty(initialState, controller.name) && - hasProperty(controller, 'subscribe') && + // Use `in` operator here because the `subscribe` function is one level up the prototype chain + 'subscribe' in controller && controller.subscribe !== undefined ) { // The following type error can be addressed by passing initial state into controller constructors instead From 7b0aab433f8d097ae25bda3a2a631fc565a36384 Mon Sep 17 00:00:00 2001 From: Jonathan Ferreira <44679989+Jonathansoufer@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:19:28 +0100 Subject: [PATCH 06/16] feat: Integrate Firebase libraries and initial config to enable Push Notifications FCM. (#10085) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR aims to handle ONLY the addition of Firebase related libraries to our codebase as well implements iOS and Android specific setup to enable Push Notifications FCM on MetaMask Mobile. No changes on consuming Push Notifications will take place on THIS PR since we're breaking this implementation down. No visual changes are introduced as well nor ways to test it, since the video updated is just to increase the understanding of what the changes will empower. Documentation used for implementing it, [here](https://rnfirebase.io/) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/MetaMask/metamask-mobile/assets/44679989/dd9f7570-a4cb-4831-9cb2-23bc5ce920a4 ## **Pre-merge author checklist** - [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. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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. --- .android.env.example | 8 ++ .ios.env.example | 8 ++ .js.env.example | 9 ++ android/app/build.gradle | 1 + android/build.gradle | 1 + .../notifications/methods/fcmHelper.test.ts | 83 ++++++++++++++++ app/util/notifications/methods/fcmHelper.ts | 99 +++++++++++++++++++ firebase.json | 7 ++ ios/GoogleService-Info.plist | 30 ++++++ ios/MetaMask/AppDelegate.m | 5 +- ios/Podfile | 6 +- package.json | 2 + scripts/build.sh | 15 ++- yarn.lock | 77 ++++++++++++++- 14 files changed, 344 insertions(+), 7 deletions(-) create mode 100644 app/util/notifications/methods/fcmHelper.test.ts create mode 100644 app/util/notifications/methods/fcmHelper.ts create mode 100644 firebase.json create mode 100644 ios/GoogleService-Info.plist diff --git a/.android.env.example b/.android.env.example index aceaabefa76..b542cac8819 100644 --- a/.android.env.example +++ b/.android.env.example @@ -1,3 +1,11 @@ export MM_FOX_CODE="EXAMPLE_FOX_CODE" export MM_BRANCH_KEY_TEST= export MM_BRANCH_KEY_LIVE= +# Firebase +export FCM_CONFIG_API_KEY= +export FCM_CONFIG_AUTH_DOMAIN= +export FCM_CONFIG_PROJECT_ID= +export FCM_CONFIG_STORAGE_BUCKET= +export FCM_CONFIG_MESSAGING_SENDER_ID= +export FCM_CONFIG_APP_ID= +export GOOGLE_SERVICES_B64= diff --git a/.ios.env.example b/.ios.env.example index bd49b067660..ee287ea38fd 100644 --- a/.ios.env.example +++ b/.ios.env.example @@ -1,3 +1,11 @@ MM_FOX_CODE = EXAMPLE_FOX_CODE MM_BRANCH_KEY_TEST = MM_BRANCH_KEY_LIVE = +# Firebase +FCM_CONFIG_API_KEY= +FCM_CONFIG_AUTH_DOMAIN= +FCM_CONFIG_PROJECT_ID= +FCM_CONFIG_STORAGE_BUCKET= +FCM_CONFIG_MESSAGING_SENDER_ID= +FCM_CONFIG_APP_ID= +GOOGLE_SERVICES_B64= diff --git a/.js.env.example b/.js.env.example index 4648ae1b482..091257f7620 100644 --- a/.js.env.example +++ b/.js.env.example @@ -73,3 +73,12 @@ export SECURITY_ALERTS_API_URL="http://localhost:3000" # Temporary mechanism to enable security alerts API prior to release. export SECURITY_ALERTS_API_ENABLED="true" + +# Firebase +export FCM_CONFIG_API_KEY="" +export FCM_CONFIG_AUTH_DOMAIN="" +export FCM_CONFIG_PROJECT_ID="" +export FCM_CONFIG_STORAGE_BUCKET="" +export FCM_CONFIG_MESSAGING_SENDER_ID="" +export FCM_CONFIG_APP_ID="" +export GOOGLE_SERVICES_B64="" diff --git a/android/app/build.gradle b/android/app/build.gradle index b9d2c2f58e5..5c51605b76e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,6 +1,7 @@ apply plugin: "com.android.application" apply plugin: "com.facebook.react" apply plugin: "io.sentry.android.gradle" +apply plugin: "com.google.gms.google-services" import com.android.build.OutputFile diff --git a/android/build.gradle b/android/build.gradle index 54950802185..5e557a92d88 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -24,6 +24,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath("com.facebook.react:react-native-gradle-plugin") classpath("io.sentry:sentry-android-gradle-plugin:4.2.0") + classpath("com.google.gms:google-services:4.3.15") } allprojects { repositories { diff --git a/app/util/notifications/methods/fcmHelper.test.ts b/app/util/notifications/methods/fcmHelper.test.ts new file mode 100644 index 00000000000..1dc2eca655b --- /dev/null +++ b/app/util/notifications/methods/fcmHelper.test.ts @@ -0,0 +1,83 @@ +import { + checkPlayServices, + registerAppWithFCM, + unRegisterAppWithFCM, + checkApplicationNotificationPermission, + getFcmToken, +} from './fcmHelper'; + +jest.mock('@react-native-firebase/app', () => ({ + utils: () => ({ + playServicesAvailability: { + status: 1, + isAvailable: false, + hasResolution: true, + isUserResolvableError: true, + }, + makePlayServicesAvailable: jest.fn(() => Promise.resolve()), + resolutionForPlayServices: jest.fn(() => Promise.resolve()), + promptForPlayServices: jest.fn(() => Promise.resolve()), + }), +})); + +jest.mock('@react-native-firebase/messaging', () => ({ + __esModule: true, + default: () => ({ + hasPermission: jest.fn(() => Promise.resolve(true)), + subscribeToTopic: jest.fn(), + unsubscribeFromTopic: jest.fn(), + isDeviceRegisteredForRemoteMessages: false, + registerDeviceForRemoteMessages: jest.fn(() => + Promise.resolve('registered'), + ), + unregisterDeviceForRemoteMessages: jest.fn(() => + Promise.resolve('unregistered'), + ), + deleteToken: jest.fn(() => Promise.resolve()), + requestPermission: jest.fn(() => Promise.resolve(1)), + getToken: jest.fn(() => Promise.resolve('fcm-token')), + }), + FirebaseMessagingTypes: { + AuthorizationStatus: { + AUTHORIZED: 1, + PROVISIONAL: 2, + }, + }, +})); + +jest.mock('react-native-permissions', () => ({ + PERMISSIONS: { + ANDROID: { + POST_NOTIFICATIONS: 'android.permission.POST_NOTIFICATIONS', + }, + }, + request: jest.fn(() => Promise.resolve('granted')), +})); + +describe('Firebase and Permission Functions', () => { + it('should check checkPlayServices function call for coverage', async () => { + await checkPlayServices(); + const token = await getFcmToken(); + + expect(token).toBe('fcm-token'); + }); + it('should check registerAppWithFCM function call for coverage', async () => { + await registerAppWithFCM(); + + const token = await getFcmToken(); + + expect(token).toBe('fcm-token'); + }); + it('should check unRegisterAppWithFCM function call for coverage', async () => { + await unRegisterAppWithFCM(); + const token = await getFcmToken(); + + expect(token).toBe('fcm-token'); + }); + it('should check checkApplicationNotificationPermission function call for coverage', async () => { + await checkApplicationNotificationPermission(); + const token = await getFcmToken(); + + expect(token).toBe('fcm-token'); + }); +}); diff --git a/app/util/notifications/methods/fcmHelper.ts b/app/util/notifications/methods/fcmHelper.ts new file mode 100644 index 00000000000..ad8fec8fc1a --- /dev/null +++ b/app/util/notifications/methods/fcmHelper.ts @@ -0,0 +1,99 @@ +import { utils } from '@react-native-firebase/app'; +import messaging, { + FirebaseMessagingTypes, +} from '@react-native-firebase/messaging'; +import Logger from '../../../util/Logger'; +import { PERMISSIONS, request } from 'react-native-permissions'; + +export async function checkPlayServices() { + const { status, isAvailable, hasResolution, isUserResolvableError } = + utils().playServicesAvailability; + if (isAvailable) return Promise.resolve(); + + if (isUserResolvableError || hasResolution) { + switch (status) { + case 1: + return utils().makePlayServicesAvailable(); + case 2: + return utils().resolutionForPlayServices(); + default: + if (isUserResolvableError) return utils().promptForPlayServices(); + if (hasResolution) return utils().resolutionForPlayServices(); + } + } + return Promise.reject( + new Error('Unable to find a valid play services version.'), + ); +} + +export async function registerAppWithFCM() { + Logger.log( + 'registerAppWithFCM status', + messaging().isDeviceRegisteredForRemoteMessages, + ); + if (!messaging().isDeviceRegisteredForRemoteMessages) { + await messaging() + .registerDeviceForRemoteMessages() + .then((status: unknown) => { + Logger.log('registerDeviceForRemoteMessages status', status); + }) + .catch((error: unknown) => { + Logger.log('registerDeviceForRemoteMessages error ', error); + }); + } +} + +export async function unRegisterAppWithFCM() { + Logger.log( + 'unRegisterAppWithFCM status', + messaging().isDeviceRegisteredForRemoteMessages, + ); + + if (messaging().isDeviceRegisteredForRemoteMessages) { + await messaging() + .unregisterDeviceForRemoteMessages() + .then((status: unknown) => { + Logger.log('unregisterDeviceForRemoteMessages status', status); + }) + .catch((error: unknown) => { + Logger.log('unregisterDeviceForRemoteMessages error ', error); + }); + } + await messaging().deleteToken(); + Logger.log( + 'unRegisterAppWithFCM status', + messaging().isDeviceRegisteredForRemoteMessages, + ); +} + +export const checkApplicationNotificationPermission = async () => { + const authStatus = await messaging().requestPermission(); + + const enabled = + authStatus === FirebaseMessagingTypes.AuthorizationStatus.AUTHORIZED || + authStatus === FirebaseMessagingTypes.AuthorizationStatus.PROVISIONAL; + + if (enabled) { + Logger.log('Authorization status:', authStatus); + } + request(PERMISSIONS.ANDROID.POST_NOTIFICATIONS) + .then((result) => { + Logger.log('POST_NOTIFICATIONS status:', result); + }) + .catch((error: unknown) => { + Logger.log('POST_NOTIFICATIONS error ', error); + }); +}; + +export const getFcmToken = async () => { + let token = null; + await checkApplicationNotificationPermission(); + await registerAppWithFCM(); + try { + token = await messaging().getToken(); + Logger.log('getFcmToken-->', token); + } catch (error) { + Logger.log('getFcmToken Device Token error ', error); + } + return token; +}; diff --git a/firebase.json b/firebase.json new file mode 100644 index 00000000000..ccdd1ba1508 --- /dev/null +++ b/firebase.json @@ -0,0 +1,7 @@ +{ + "react-native": { + "analytics_auto_collection_enabled": false, + "messaging_auto_init_enabled": false, + "messaging_ios_auto_register_for_remote_messages": true + } +} \ No newline at end of file diff --git a/ios/GoogleService-Info.plist b/ios/GoogleService-Info.plist new file mode 100644 index 00000000000..163a74206df --- /dev/null +++ b/ios/GoogleService-Info.plist @@ -0,0 +1,30 @@ + + + + + API_KEY + $(FCM_CONFIG_API_KEY) + GCM_SENDER_ID + $(FCM_CONFIG_MESSAGING_SENDER_ID) + PLIST_VERSION + 1 + BUNDLE_ID + io.metamask.MetaMask + PROJECT_ID + notifications-dev-e4e6d + STORAGE_BUCKET + $(FCM_CONFIG_STORAGE_BUCKET) + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + $(FCM_CONFIG_APP_ID) + + diff --git a/ios/MetaMask/AppDelegate.m b/ios/MetaMask/AppDelegate.m index b22d5678257..22e333bfa4a 100644 --- a/ios/MetaMask/AppDelegate.m +++ b/ios/MetaMask/AppDelegate.m @@ -4,6 +4,7 @@ #import #import #import +#import #if DEBUG #ifdef FB_SONARKIT_ENABLED #import @@ -29,6 +30,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( } else { foxCode = @"debug"; } + // Implements Firebase + [FIRApp configure]; // Uncomment this line to use the test key instead of the live one. // [RNBranch useTestInstance]; @@ -55,7 +58,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( UIView* launchScreenView = [[[NSBundle mainBundle] loadNibNamed:@"LaunchScreen" owner:self options:nil] objectAtIndex:0]; launchScreenView.frame = self.window.bounds; rootView.loadingView = launchScreenView; - + [self initializeFlipper:application]; //Uncomment the following line to enable the splashscreen on ios diff --git a/ios/Podfile b/ios/Podfile index c292007f659..e0dc4c57971 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -75,7 +75,8 @@ def common_target_logic # # Note that if you have use_frameworks! enabled, Flipper will not work and # you should disable the next line. - :flipper_configuration => flipper_config, + # 27/06/2024 - Disabled in favor of Firebase Messaging usage due incompatibility. Source: https://rnfirebase.io/#altering-cocoapods-to-use-frameworks + #:flipper_configuration => flipper_config, # An absolute path to your application root. :app_path => "#{Pod::Config.instance.installation_root}/..", ) @@ -104,6 +105,9 @@ end target 'MetaMask' do common_target_logic + # https://rnfirebase.io/ + use_frameworks! :linkage => :static + $RNFirebaseAsStaticFramework = true end target 'MetaMask-QA' do diff --git a/package.json b/package.json index 552e94579b0..a090113c61d 100644 --- a/package.json +++ b/package.json @@ -195,6 +195,8 @@ "@react-native-community/checkbox": "^0.5.12", "@react-native-community/netinfo": "6.0.0", "@react-native-cookies/cookies": "^6.2.1", + "@react-native-firebase/app": "^20.1.0", + "@react-native-firebase/messaging": "^20.1.0", "@react-native-masked-view/masked-view": "^0.2.6", "@react-native-picker/picker": "^2.2.1", "@react-navigation/bottom-tabs": "^5.11.11", diff --git a/scripts/build.sh b/scripts/build.sh index 1022d574ca6..82fbdd74414 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -53,7 +53,6 @@ printTitle(){ echo '' } - printError(){ ERROR_ICON=$'\342\235\214' echo '' @@ -160,7 +159,6 @@ loadJSEnv(){ export SENTRY_DISABLE_AUTO_UPLOAD=${SENTRY_DISABLE_AUTO_UPLOAD:-"true"} } - prebuild(){ # Import provider yarn --ignore-engines build:static-logos @@ -189,6 +187,16 @@ prebuild_android(){ # Copy fonts with iconset yes | cp -rf ./app/fonts/Metamask.ttf ./android/app/src/main/assets/fonts/Metamask.ttf + #Create google-services.json file to be used by the Firebase services. + # Check if GOOGLE_SERVICES_B64 is set + if [ ! -z "$GOOGLE_SERVICES_B64" ]; then + echo -n $GOOGLE_SERVICES_B64 | base64 -d > ./android/app/google-services.json + echo "google-services.json has been created successfully." + else + echo "GOOGLE_SERVICES_B64 is not set in the .env file." + exit 1 + fi + if [ "$PRE_RELEASE" = false ] ; then if [ -e $ANDROID_ENV_FILE ] then @@ -370,10 +378,9 @@ buildIosQA(){ fi } - buildAndroidQA(){ remapEnvVariableQA - + if [ "$PRE_RELEASE" = false ] ; then adb uninstall io.metamask.qa fi diff --git a/yarn.lock b/yarn.lock index 3e03fa82aef..956b4c5d038 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5963,6 +5963,19 @@ dependencies: invariant "^2.2.4" +"@react-native-firebase/app@^20.1.0": + version "20.1.0" + resolved "https://registry.yarnpkg.com/@react-native-firebase/app/-/app-20.1.0.tgz#86b9371290f92d51821b7299eede95336949f214" + integrity sha512-FCcTtmfz/Bk2laOEKOiUrQUkAnzerkRml7d3kZzJSxaBWLFxpWJQnnXqGZmD8hNWio2QEauB8llUD71KiDk+sw== + dependencies: + opencollective-postinstall "^2.0.3" + superstruct "^0.6.2" + +"@react-native-firebase/messaging@^20.1.0": + version "20.1.0" + resolved "https://registry.yarnpkg.com/@react-native-firebase/messaging/-/messaging-20.1.0.tgz#02026259c74d1725dfc5216158b05bc6655e7951" + integrity sha512-y9FtQ9dIQSyueuLeJghvfLYnay5BqPVgl9T94p+HtUlkxinOgNDjquQFtV/QlzVOyVpLrVPmknMohvBj/fvBzg== + "@react-native-masked-view/masked-view@^0.2.6": version "0.2.6" resolved "https://registry.yarnpkg.com/@react-native-masked-view/masked-view/-/masked-view-0.2.6.tgz#b26c52d5db3ad0926b13deea79c69620966a9221" @@ -13395,6 +13408,16 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" +clone-deep@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713" + integrity sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ== + dependencies: + for-own "^1.0.0" + is-plain-object "^2.0.4" + kind-of "^6.0.0" + shallow-clone "^1.0.0" + clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -17253,6 +17276,23 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +for-in@^0.1.3: + version "0.1.8" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" + integrity sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g== + +for-in@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== + +for-own@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" + integrity sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg== + dependencies: + for-in "^1.0.1" + foreach@~2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" @@ -18824,6 +18864,11 @@ is-docker@^2.0.0, is-docker@^2.1.1: resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== +is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -20252,7 +20297,12 @@ kind-of@^1.1.0: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" integrity sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ= -kind-of@^6.0.2: +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.1, kind-of@^6.0.2: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== @@ -21720,6 +21770,14 @@ mitt@3.0.1, mitt@^3.0.1: resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1" integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== +mixin-object@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e" + integrity sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA== + dependencies: + for-in "^0.1.3" + is-extendable "^0.1.1" + mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" @@ -26153,6 +26211,15 @@ shaka-player@^2.5.9: dependencies: eme-encryption-scheme-polyfill "^2.0.1" +shallow-clone@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-1.0.0.tgz#4480cd06e882ef68b2ad88a3ea54832e2c48b571" + integrity sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA== + dependencies: + is-extendable "^0.1.1" + kind-of "^5.0.0" + mixin-object "^2.0.1" + shallow-clone@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" @@ -26980,6 +27047,14 @@ suffix@^0.1.0: resolved "https://registry.yarnpkg.com/suffix/-/suffix-0.1.1.tgz#cc58231646a0ef1102f79478ef3a9248fd9c842f" integrity sha512-j5uf6MJtMCfC4vBe5LFktSe4bGyNTBk7I2Kdri0jeLrcv5B9pWfxVa5JQpoxgtR8vaVB7bVxsWgnfQbX5wkhAA== +superstruct@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.6.2.tgz#c5eb034806a17ff98d036674169ef85e4c7f6a1c" + integrity sha512-lvA97MFAJng3rfjcafT/zGTSWm6Tbpk++DP6It4Qg7oNaeM+2tdJMuVgGje21/bIpBEs6iQql1PJH6dKTjl4Ig== + dependencies: + clone-deep "^2.0.1" + kind-of "^6.0.1" + superstruct@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.3.tgz#de626a5b49c6641ff4d37da3c7598e7a87697046" From c11c6098a290039a540465c160b35b01c4ca99a7 Mon Sep 17 00:00:00 2001 From: Jonathan Ferreira <44679989+Jonathansoufer@users.noreply.github.com> Date: Thu, 27 Jun 2024 15:37:12 +0100 Subject: [PATCH 07/16] chore: Revert "feat: Integrate Firebase libraries and initial config to enable Push Notifications FCM." (#10143) Reverts MetaMask/metamask-mobile#10085 due the lack of `GOOGLE_SERVICES_B64` on our CI. --- .android.env.example | 8 -- .ios.env.example | 8 -- .js.env.example | 9 -- android/app/build.gradle | 1 - android/build.gradle | 1 - .../notifications/methods/fcmHelper.test.ts | 83 ---------------- app/util/notifications/methods/fcmHelper.ts | 99 ------------------- firebase.json | 7 -- ios/GoogleService-Info.plist | 30 ------ ios/MetaMask/AppDelegate.m | 5 +- ios/Podfile | 6 +- package.json | 2 - scripts/build.sh | 15 +-- yarn.lock | 77 +-------------- 14 files changed, 7 insertions(+), 344 deletions(-) delete mode 100644 app/util/notifications/methods/fcmHelper.test.ts delete mode 100644 app/util/notifications/methods/fcmHelper.ts delete mode 100644 firebase.json delete mode 100644 ios/GoogleService-Info.plist diff --git a/.android.env.example b/.android.env.example index b542cac8819..aceaabefa76 100644 --- a/.android.env.example +++ b/.android.env.example @@ -1,11 +1,3 @@ export MM_FOX_CODE="EXAMPLE_FOX_CODE" export MM_BRANCH_KEY_TEST= export MM_BRANCH_KEY_LIVE= -# Firebase -export FCM_CONFIG_API_KEY= -export FCM_CONFIG_AUTH_DOMAIN= -export FCM_CONFIG_PROJECT_ID= -export FCM_CONFIG_STORAGE_BUCKET= -export FCM_CONFIG_MESSAGING_SENDER_ID= -export FCM_CONFIG_APP_ID= -export GOOGLE_SERVICES_B64= diff --git a/.ios.env.example b/.ios.env.example index ee287ea38fd..bd49b067660 100644 --- a/.ios.env.example +++ b/.ios.env.example @@ -1,11 +1,3 @@ MM_FOX_CODE = EXAMPLE_FOX_CODE MM_BRANCH_KEY_TEST = MM_BRANCH_KEY_LIVE = -# Firebase -FCM_CONFIG_API_KEY= -FCM_CONFIG_AUTH_DOMAIN= -FCM_CONFIG_PROJECT_ID= -FCM_CONFIG_STORAGE_BUCKET= -FCM_CONFIG_MESSAGING_SENDER_ID= -FCM_CONFIG_APP_ID= -GOOGLE_SERVICES_B64= diff --git a/.js.env.example b/.js.env.example index 091257f7620..4648ae1b482 100644 --- a/.js.env.example +++ b/.js.env.example @@ -73,12 +73,3 @@ export SECURITY_ALERTS_API_URL="http://localhost:3000" # Temporary mechanism to enable security alerts API prior to release. export SECURITY_ALERTS_API_ENABLED="true" - -# Firebase -export FCM_CONFIG_API_KEY="" -export FCM_CONFIG_AUTH_DOMAIN="" -export FCM_CONFIG_PROJECT_ID="" -export FCM_CONFIG_STORAGE_BUCKET="" -export FCM_CONFIG_MESSAGING_SENDER_ID="" -export FCM_CONFIG_APP_ID="" -export GOOGLE_SERVICES_B64="" diff --git a/android/app/build.gradle b/android/app/build.gradle index 5c51605b76e..b9d2c2f58e5 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,7 +1,6 @@ apply plugin: "com.android.application" apply plugin: "com.facebook.react" apply plugin: "io.sentry.android.gradle" -apply plugin: "com.google.gms.google-services" import com.android.build.OutputFile diff --git a/android/build.gradle b/android/build.gradle index 5e557a92d88..54950802185 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -24,7 +24,6 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath("com.facebook.react:react-native-gradle-plugin") classpath("io.sentry:sentry-android-gradle-plugin:4.2.0") - classpath("com.google.gms:google-services:4.3.15") } allprojects { repositories { diff --git a/app/util/notifications/methods/fcmHelper.test.ts b/app/util/notifications/methods/fcmHelper.test.ts deleted file mode 100644 index 1dc2eca655b..00000000000 --- a/app/util/notifications/methods/fcmHelper.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { - checkPlayServices, - registerAppWithFCM, - unRegisterAppWithFCM, - checkApplicationNotificationPermission, - getFcmToken, -} from './fcmHelper'; - -jest.mock('@react-native-firebase/app', () => ({ - utils: () => ({ - playServicesAvailability: { - status: 1, - isAvailable: false, - hasResolution: true, - isUserResolvableError: true, - }, - makePlayServicesAvailable: jest.fn(() => Promise.resolve()), - resolutionForPlayServices: jest.fn(() => Promise.resolve()), - promptForPlayServices: jest.fn(() => Promise.resolve()), - }), -})); - -jest.mock('@react-native-firebase/messaging', () => ({ - __esModule: true, - default: () => ({ - hasPermission: jest.fn(() => Promise.resolve(true)), - subscribeToTopic: jest.fn(), - unsubscribeFromTopic: jest.fn(), - isDeviceRegisteredForRemoteMessages: false, - registerDeviceForRemoteMessages: jest.fn(() => - Promise.resolve('registered'), - ), - unregisterDeviceForRemoteMessages: jest.fn(() => - Promise.resolve('unregistered'), - ), - deleteToken: jest.fn(() => Promise.resolve()), - requestPermission: jest.fn(() => Promise.resolve(1)), - getToken: jest.fn(() => Promise.resolve('fcm-token')), - }), - FirebaseMessagingTypes: { - AuthorizationStatus: { - AUTHORIZED: 1, - PROVISIONAL: 2, - }, - }, -})); - -jest.mock('react-native-permissions', () => ({ - PERMISSIONS: { - ANDROID: { - POST_NOTIFICATIONS: 'android.permission.POST_NOTIFICATIONS', - }, - }, - request: jest.fn(() => Promise.resolve('granted')), -})); - -describe('Firebase and Permission Functions', () => { - it('should check checkPlayServices function call for coverage', async () => { - await checkPlayServices(); - const token = await getFcmToken(); - - expect(token).toBe('fcm-token'); - }); - it('should check registerAppWithFCM function call for coverage', async () => { - await registerAppWithFCM(); - - const token = await getFcmToken(); - - expect(token).toBe('fcm-token'); - }); - it('should check unRegisterAppWithFCM function call for coverage', async () => { - await unRegisterAppWithFCM(); - const token = await getFcmToken(); - - expect(token).toBe('fcm-token'); - }); - it('should check checkApplicationNotificationPermission function call for coverage', async () => { - await checkApplicationNotificationPermission(); - const token = await getFcmToken(); - - expect(token).toBe('fcm-token'); - }); -}); diff --git a/app/util/notifications/methods/fcmHelper.ts b/app/util/notifications/methods/fcmHelper.ts deleted file mode 100644 index ad8fec8fc1a..00000000000 --- a/app/util/notifications/methods/fcmHelper.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { utils } from '@react-native-firebase/app'; -import messaging, { - FirebaseMessagingTypes, -} from '@react-native-firebase/messaging'; -import Logger from '../../../util/Logger'; -import { PERMISSIONS, request } from 'react-native-permissions'; - -export async function checkPlayServices() { - const { status, isAvailable, hasResolution, isUserResolvableError } = - utils().playServicesAvailability; - if (isAvailable) return Promise.resolve(); - - if (isUserResolvableError || hasResolution) { - switch (status) { - case 1: - return utils().makePlayServicesAvailable(); - case 2: - return utils().resolutionForPlayServices(); - default: - if (isUserResolvableError) return utils().promptForPlayServices(); - if (hasResolution) return utils().resolutionForPlayServices(); - } - } - return Promise.reject( - new Error('Unable to find a valid play services version.'), - ); -} - -export async function registerAppWithFCM() { - Logger.log( - 'registerAppWithFCM status', - messaging().isDeviceRegisteredForRemoteMessages, - ); - if (!messaging().isDeviceRegisteredForRemoteMessages) { - await messaging() - .registerDeviceForRemoteMessages() - .then((status: unknown) => { - Logger.log('registerDeviceForRemoteMessages status', status); - }) - .catch((error: unknown) => { - Logger.log('registerDeviceForRemoteMessages error ', error); - }); - } -} - -export async function unRegisterAppWithFCM() { - Logger.log( - 'unRegisterAppWithFCM status', - messaging().isDeviceRegisteredForRemoteMessages, - ); - - if (messaging().isDeviceRegisteredForRemoteMessages) { - await messaging() - .unregisterDeviceForRemoteMessages() - .then((status: unknown) => { - Logger.log('unregisterDeviceForRemoteMessages status', status); - }) - .catch((error: unknown) => { - Logger.log('unregisterDeviceForRemoteMessages error ', error); - }); - } - await messaging().deleteToken(); - Logger.log( - 'unRegisterAppWithFCM status', - messaging().isDeviceRegisteredForRemoteMessages, - ); -} - -export const checkApplicationNotificationPermission = async () => { - const authStatus = await messaging().requestPermission(); - - const enabled = - authStatus === FirebaseMessagingTypes.AuthorizationStatus.AUTHORIZED || - authStatus === FirebaseMessagingTypes.AuthorizationStatus.PROVISIONAL; - - if (enabled) { - Logger.log('Authorization status:', authStatus); - } - request(PERMISSIONS.ANDROID.POST_NOTIFICATIONS) - .then((result) => { - Logger.log('POST_NOTIFICATIONS status:', result); - }) - .catch((error: unknown) => { - Logger.log('POST_NOTIFICATIONS error ', error); - }); -}; - -export const getFcmToken = async () => { - let token = null; - await checkApplicationNotificationPermission(); - await registerAppWithFCM(); - try { - token = await messaging().getToken(); - Logger.log('getFcmToken-->', token); - } catch (error) { - Logger.log('getFcmToken Device Token error ', error); - } - return token; -}; diff --git a/firebase.json b/firebase.json deleted file mode 100644 index ccdd1ba1508..00000000000 --- a/firebase.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "react-native": { - "analytics_auto_collection_enabled": false, - "messaging_auto_init_enabled": false, - "messaging_ios_auto_register_for_remote_messages": true - } -} \ No newline at end of file diff --git a/ios/GoogleService-Info.plist b/ios/GoogleService-Info.plist deleted file mode 100644 index 163a74206df..00000000000 --- a/ios/GoogleService-Info.plist +++ /dev/null @@ -1,30 +0,0 @@ - - - - - API_KEY - $(FCM_CONFIG_API_KEY) - GCM_SENDER_ID - $(FCM_CONFIG_MESSAGING_SENDER_ID) - PLIST_VERSION - 1 - BUNDLE_ID - io.metamask.MetaMask - PROJECT_ID - notifications-dev-e4e6d - STORAGE_BUCKET - $(FCM_CONFIG_STORAGE_BUCKET) - IS_ADS_ENABLED - - IS_ANALYTICS_ENABLED - - IS_APPINVITE_ENABLED - - IS_GCM_ENABLED - - IS_SIGNIN_ENABLED - - GOOGLE_APP_ID - $(FCM_CONFIG_APP_ID) - - diff --git a/ios/MetaMask/AppDelegate.m b/ios/MetaMask/AppDelegate.m index 22e333bfa4a..b22d5678257 100644 --- a/ios/MetaMask/AppDelegate.m +++ b/ios/MetaMask/AppDelegate.m @@ -4,7 +4,6 @@ #import #import #import -#import #if DEBUG #ifdef FB_SONARKIT_ENABLED #import @@ -30,8 +29,6 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( } else { foxCode = @"debug"; } - // Implements Firebase - [FIRApp configure]; // Uncomment this line to use the test key instead of the live one. // [RNBranch useTestInstance]; @@ -58,7 +55,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( UIView* launchScreenView = [[[NSBundle mainBundle] loadNibNamed:@"LaunchScreen" owner:self options:nil] objectAtIndex:0]; launchScreenView.frame = self.window.bounds; rootView.loadingView = launchScreenView; - + [self initializeFlipper:application]; //Uncomment the following line to enable the splashscreen on ios diff --git a/ios/Podfile b/ios/Podfile index e0dc4c57971..c292007f659 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -75,8 +75,7 @@ def common_target_logic # # Note that if you have use_frameworks! enabled, Flipper will not work and # you should disable the next line. - # 27/06/2024 - Disabled in favor of Firebase Messaging usage due incompatibility. Source: https://rnfirebase.io/#altering-cocoapods-to-use-frameworks - #:flipper_configuration => flipper_config, + :flipper_configuration => flipper_config, # An absolute path to your application root. :app_path => "#{Pod::Config.instance.installation_root}/..", ) @@ -105,9 +104,6 @@ end target 'MetaMask' do common_target_logic - # https://rnfirebase.io/ - use_frameworks! :linkage => :static - $RNFirebaseAsStaticFramework = true end target 'MetaMask-QA' do diff --git a/package.json b/package.json index a090113c61d..552e94579b0 100644 --- a/package.json +++ b/package.json @@ -195,8 +195,6 @@ "@react-native-community/checkbox": "^0.5.12", "@react-native-community/netinfo": "6.0.0", "@react-native-cookies/cookies": "^6.2.1", - "@react-native-firebase/app": "^20.1.0", - "@react-native-firebase/messaging": "^20.1.0", "@react-native-masked-view/masked-view": "^0.2.6", "@react-native-picker/picker": "^2.2.1", "@react-navigation/bottom-tabs": "^5.11.11", diff --git a/scripts/build.sh b/scripts/build.sh index 82fbdd74414..1022d574ca6 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -53,6 +53,7 @@ printTitle(){ echo '' } + printError(){ ERROR_ICON=$'\342\235\214' echo '' @@ -159,6 +160,7 @@ loadJSEnv(){ export SENTRY_DISABLE_AUTO_UPLOAD=${SENTRY_DISABLE_AUTO_UPLOAD:-"true"} } + prebuild(){ # Import provider yarn --ignore-engines build:static-logos @@ -187,16 +189,6 @@ prebuild_android(){ # Copy fonts with iconset yes | cp -rf ./app/fonts/Metamask.ttf ./android/app/src/main/assets/fonts/Metamask.ttf - #Create google-services.json file to be used by the Firebase services. - # Check if GOOGLE_SERVICES_B64 is set - if [ ! -z "$GOOGLE_SERVICES_B64" ]; then - echo -n $GOOGLE_SERVICES_B64 | base64 -d > ./android/app/google-services.json - echo "google-services.json has been created successfully." - else - echo "GOOGLE_SERVICES_B64 is not set in the .env file." - exit 1 - fi - if [ "$PRE_RELEASE" = false ] ; then if [ -e $ANDROID_ENV_FILE ] then @@ -378,9 +370,10 @@ buildIosQA(){ fi } + buildAndroidQA(){ remapEnvVariableQA - + if [ "$PRE_RELEASE" = false ] ; then adb uninstall io.metamask.qa fi diff --git a/yarn.lock b/yarn.lock index 956b4c5d038..3e03fa82aef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5963,19 +5963,6 @@ dependencies: invariant "^2.2.4" -"@react-native-firebase/app@^20.1.0": - version "20.1.0" - resolved "https://registry.yarnpkg.com/@react-native-firebase/app/-/app-20.1.0.tgz#86b9371290f92d51821b7299eede95336949f214" - integrity sha512-FCcTtmfz/Bk2laOEKOiUrQUkAnzerkRml7d3kZzJSxaBWLFxpWJQnnXqGZmD8hNWio2QEauB8llUD71KiDk+sw== - dependencies: - opencollective-postinstall "^2.0.3" - superstruct "^0.6.2" - -"@react-native-firebase/messaging@^20.1.0": - version "20.1.0" - resolved "https://registry.yarnpkg.com/@react-native-firebase/messaging/-/messaging-20.1.0.tgz#02026259c74d1725dfc5216158b05bc6655e7951" - integrity sha512-y9FtQ9dIQSyueuLeJghvfLYnay5BqPVgl9T94p+HtUlkxinOgNDjquQFtV/QlzVOyVpLrVPmknMohvBj/fvBzg== - "@react-native-masked-view/masked-view@^0.2.6": version "0.2.6" resolved "https://registry.yarnpkg.com/@react-native-masked-view/masked-view/-/masked-view-0.2.6.tgz#b26c52d5db3ad0926b13deea79c69620966a9221" @@ -13408,16 +13395,6 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" -clone-deep@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713" - integrity sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ== - dependencies: - for-own "^1.0.0" - is-plain-object "^2.0.4" - kind-of "^6.0.0" - shallow-clone "^1.0.0" - clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -17276,23 +17253,6 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" -for-in@^0.1.3: - version "0.1.8" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" - integrity sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g== - -for-in@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== - -for-own@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" - integrity sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg== - dependencies: - for-in "^1.0.1" - foreach@~2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" @@ -18864,11 +18824,6 @@ is-docker@^2.0.0, is-docker@^2.1.1: resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== -is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -20297,12 +20252,7 @@ kind-of@^1.1.0: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" integrity sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ= -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.1, kind-of@^6.0.2: +kind-of@^6.0.2: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== @@ -21770,14 +21720,6 @@ mitt@3.0.1, mitt@^3.0.1: resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1" integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== -mixin-object@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e" - integrity sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA== - dependencies: - for-in "^0.1.3" - is-extendable "^0.1.1" - mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" @@ -26211,15 +26153,6 @@ shaka-player@^2.5.9: dependencies: eme-encryption-scheme-polyfill "^2.0.1" -shallow-clone@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-1.0.0.tgz#4480cd06e882ef68b2ad88a3ea54832e2c48b571" - integrity sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA== - dependencies: - is-extendable "^0.1.1" - kind-of "^5.0.0" - mixin-object "^2.0.1" - shallow-clone@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" @@ -27047,14 +26980,6 @@ suffix@^0.1.0: resolved "https://registry.yarnpkg.com/suffix/-/suffix-0.1.1.tgz#cc58231646a0ef1102f79478ef3a9248fd9c842f" integrity sha512-j5uf6MJtMCfC4vBe5LFktSe4bGyNTBk7I2Kdri0jeLrcv5B9pWfxVa5JQpoxgtR8vaVB7bVxsWgnfQbX5wkhAA== -superstruct@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.6.2.tgz#c5eb034806a17ff98d036674169ef85e4c7f6a1c" - integrity sha512-lvA97MFAJng3rfjcafT/zGTSWm6Tbpk++DP6It4Qg7oNaeM+2tdJMuVgGje21/bIpBEs6iQql1PJH6dKTjl4Ig== - dependencies: - clone-deep "^2.0.1" - kind-of "^6.0.1" - superstruct@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.3.tgz#de626a5b49c6641ff4d37da3c7598e7a87697046" From bfa52bbd3669cca5dbae85c2a0969abb41a940db Mon Sep 17 00:00:00 2001 From: Shane Date: Thu, 27 Jun 2024 10:58:18 -0400 Subject: [PATCH 08/16] fix: add API Spec Tests section in testing.md (#10146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Adds a new section to testing.md that explains how to run the api spec tests ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [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. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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. --- docs/readme/testing.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/readme/testing.md b/docs/readme/testing.md index d5ceb080777..a16eca1df86 100644 --- a/docs/readme/testing.md +++ b/docs/readme/testing.md @@ -133,3 +133,17 @@ If you want to run a specific test, you can include the `--spec` flag in the afo ```bash yarn test:wdio:android --spec ./wdio/features/Onboarding/CreateNewWallet.feature ``` + +##### API Spec Tests + +The API Spec tests use the `@open-rpc/test-coverage` tool to generate tests from our [api-specs](https://github.com/MetaMask/api-specs) OpenRPC Document. +Currently, the API Spec tests only run on iOS and uses the same build as the Detox tests for iOS. + +The `test-coverage` tool uses `Rules` and `Reporters` to generate tests and report the results. The `Rules` and `Reporters` are passed in via params to the test coverage tool call in [e2e/api-specs/json-rpc-coverage.js](../../e2e/api-specs/json-rpc-coverage.js). You can read more about the `Rules` and `Reporters` [here](https://github.com/open-rpc/test-coverage?tab=readme-ov-file#extending-with-a-rule). + +To run the API Spec tests, run these commands: + +```bash +yarn test:e2e:ios:debug:build +yarn test:api-specs +```` From ea14ef75de9bf7291a22d7b4286e4652209d5389 Mon Sep 17 00:00:00 2001 From: EtherWizard33 <165834542+EtherWizard33@users.noreply.github.com> Date: Thu, 27 Jun 2024 19:43:42 +0300 Subject: [PATCH 09/16] feat: edit networks UI redesign (#10040) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR adds the ability to edit networks. This PR also includes Salim's work from the feat-edit-delete-network-menu branch. For a short demo please view this loom: https://www.loom.com/share/1192d8d7846845e69da9894def13aeeb ## **Related issues** Fixes: ## **Manual testing steps** 1. Click the network selector at the center top of the screen, a bottom sheet comes up with a list of networks 2. On the right side of enabled networks, a 3 dot icon is displayed as a 'more' menu, tap it to edit the network 3. The edit form shows up pre-filled with the network details to be edited ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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. ## **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. --------- Co-authored-by: salimtb --- .storybook/storybook.requires.js | 1 + .../CellSelectWithMenu.constants.ts | 34 ++ .../CellSelectWithMenu.stories.tsx | 41 +++ .../CellSelectWithMenu.styles.ts | 35 ++ .../CellSelectWithMenu.test.tsx | 24 ++ .../CellSelectWithMenu/CellSelectWithMenu.tsx | 50 +++ .../CellSelectWithMenu.types.ts | 18 + .../CellSelectWithMenu.test.tsx.snap | 317 +++++++++++++++++ .../CellSelectWithMenu/index.ts | 1 + .../ListItemMultiSelectButton.constants.ts | 19 + .../ListItemMultiSelectButton.stories.tsx | 53 +++ .../ListItemMultiSelectButton.styles.ts | 65 ++++ .../ListItemMultiSelectButton.test.tsx | 75 ++++ .../ListItemMultiSelectButton.tsx | 71 ++++ .../ListItemMultiSelectButton.types.ts | 43 +++ .../ListItemMultiSelectButton.test.tsx.snap | 79 +++++ .../ListItemMultiSelectButton/index.ts | 1 + .../components/Cells/Cell/Cell.tsx | 8 + .../components/Cells/Cell/Cell.types.ts | 3 + .../NetworkSearchTextInput.styles.ts | 18 + .../NetworkSearchTextInput.tsx | 37 +- .../NetworkSelector/NetworkSelector.styles.ts | 50 +++ .../Views/NetworkSelector/NetworkSelector.tsx | 333 ++++++++++++++++-- .../CustomNetworkView/CustomNetwork.tsx | 5 +- .../CustomNetworkView/CustomNetwork.types.ts | 11 + e2e/selectors/Modals/CellModal.selectors.js | 1 + .../Modals/NetworkListModal.selectors.js | 1 + storybook/storyLoader.js | 2 + 28 files changed, 1369 insertions(+), 27 deletions(-) create mode 100644 app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.constants.ts create mode 100644 app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.stories.tsx create mode 100644 app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.styles.ts create mode 100644 app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.test.tsx create mode 100644 app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.tsx create mode 100644 app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.types.ts create mode 100644 app/component-library/components-temp/CellSelectWithMenu/__snapshots__/CellSelectWithMenu.test.tsx.snap create mode 100644 app/component-library/components-temp/CellSelectWithMenu/index.ts create mode 100644 app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.constants.ts create mode 100644 app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.stories.tsx create mode 100644 app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.styles.ts create mode 100644 app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.test.tsx create mode 100644 app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.tsx create mode 100644 app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.types.ts create mode 100644 app/component-library/components-temp/ListItemMultiSelectButton/__snapshots__/ListItemMultiSelectButton.test.tsx.snap create mode 100644 app/component-library/components-temp/ListItemMultiSelectButton/index.ts diff --git a/.storybook/storybook.requires.js b/.storybook/storybook.requires.js index b2fdc79d50a..8c929c9fe7d 100644 --- a/.storybook/storybook.requires.js +++ b/.storybook/storybook.requires.js @@ -116,6 +116,7 @@ const getStories = () => { './app/component-library/components-temp/TagColored/TagColored.stories.tsx': require('../app/component-library/components-temp/TagColored/TagColored.stories.tsx'), './app/components/UI/Name/Name.stories.tsx': require('../app/components/UI/Name/Name.stories.tsx'), "./app/components/UI/SimulationDetails/SimulationDetails.stories.tsx": require("../app/components/UI/SimulationDetails/SimulationDetails.stories.tsx"), + "./app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.stories.tsx": require("../app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.stories.tsx"), }; }; diff --git a/app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.constants.ts b/app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.constants.ts new file mode 100644 index 00000000000..05abf2a0d46 --- /dev/null +++ b/app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.constants.ts @@ -0,0 +1,34 @@ +// External dependencies. +import { IconName } from '../../../component-library/components/Icons/Icon'; +import { + AvatarVariant, + AvatarAccountType, +} from '../../../component-library/components/Avatars/Avatar'; +import { AvatarProps } from '../../../component-library/components/Avatars/Avatar/Avatar.types'; + +// Internal dependencies. +import { CellSelectWithMenuProps } from './CellSelectWithMenu.types'; + +// Sample consts +const SAMPLE_CELLSELECT_WITH_BUTTON_TITLE = 'Orangefox.eth'; +const SAMPLE_CELLSELECT_WITH_BUTTON_SECONDARYTEXT = + '0x2990079bcdEe240329a520d2444386FC119da21a'; +const SAMPLE_CELLSELECT_WITH_BUTTON_TERTIARY_TEXT = 'Updated 1 sec ago'; +const SAMPLE_CELLSELECT_WITH_BUTTON_TAGLABEL = 'Imported'; +const SAMPLE_CELLSELECT_WITH_BUTTON_AVATARPROPS: AvatarProps = { + variant: AvatarVariant.Account, + accountAddress: '0x2990079bcdEe240329a520d2444386FC119da21a', + type: AvatarAccountType.JazzIcon, +}; + +// eslint-disable-next-line import/prefer-default-export +export const SAMPLE_CELLSELECT_WITH_BUTTON_PROPS: CellSelectWithMenuProps = { + title: SAMPLE_CELLSELECT_WITH_BUTTON_TITLE, + secondaryText: SAMPLE_CELLSELECT_WITH_BUTTON_SECONDARYTEXT, + tertiaryText: SAMPLE_CELLSELECT_WITH_BUTTON_TERTIARY_TEXT, + tagLabel: SAMPLE_CELLSELECT_WITH_BUTTON_TAGLABEL, + avatarProps: SAMPLE_CELLSELECT_WITH_BUTTON_AVATARPROPS, + isSelected: false, + isDisabled: false, + buttonIcon: IconName.MoreVertical, +}; diff --git a/app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.stories.tsx b/app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.stories.tsx new file mode 100644 index 00000000000..46e8a658e40 --- /dev/null +++ b/app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.stories.tsx @@ -0,0 +1,41 @@ +// Internal dependencies. +import { default as CellSelectWithMenu } from './CellSelectWithMenu'; +import { SAMPLE_CELLSELECT_WITH_BUTTON_PROPS } from './CellSelectWithMenu.constants'; + +const CellSelectWithMenuMeta = { + title: 'Component Library / Cells', + component: CellSelectWithMenu, + argTypes: { + title: { + control: { type: 'text' }, + defaultValue: SAMPLE_CELLSELECT_WITH_BUTTON_PROPS.title, + }, + secondaryText: { + control: { type: 'text' }, + defaultValue: SAMPLE_CELLSELECT_WITH_BUTTON_PROPS.secondaryText, + }, + tertiaryText: { + control: { type: 'text' }, + defaultValue: SAMPLE_CELLSELECT_WITH_BUTTON_PROPS.tertiaryText, + }, + tagLabel: { + control: { type: 'text' }, + defaultValue: SAMPLE_CELLSELECT_WITH_BUTTON_PROPS.tagLabel, + }, + isSelected: { + control: { type: 'boolean' }, + defaultValue: SAMPLE_CELLSELECT_WITH_BUTTON_PROPS.isSelected, + }, + isDisabled: { + control: { type: 'boolean' }, + defaultValue: SAMPLE_CELLSELECT_WITH_BUTTON_PROPS.isDisabled, + }, + }, +}; +export default CellSelectWithMenuMeta; + +export const CellMultiSelectWithMenu = { + args: { + avatarProps: SAMPLE_CELLSELECT_WITH_BUTTON_PROPS.avatarProps, + }, +}; diff --git a/app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.styles.ts b/app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.styles.ts new file mode 100644 index 00000000000..9f4933dfc18 --- /dev/null +++ b/app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.styles.ts @@ -0,0 +1,35 @@ +// Third library dependencies. +import { StyleSheet, ViewStyle } from 'react-native'; + +// External dependencies. +import { CellSelectWithMenuStyleSheetVars } from './CellSelectWithMenu.types'; + +// Internal dependencies. +import { Theme } from '../../../util/theme/models'; + +/** + * Style sheet function for CellSelect component. + * + * @param params Style sheet params. + * @param params.theme App theme from ThemeContext. + * @param params.vars Inputs that the style sheet depends on. + * @returns StyleSheet object. + */ +const styleSheet = (params: { + theme: Theme; + vars: CellSelectWithMenuStyleSheetVars; +}) => { + const { vars } = params; + const { style } = vars; + + return StyleSheet.create({ + base: Object.assign( + { + padding: 16, + } as ViewStyle, + style, + ) as ViewStyle, + }); +}; + +export default styleSheet; diff --git a/app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.test.tsx b/app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.test.tsx new file mode 100644 index 00000000000..280df813ba8 --- /dev/null +++ b/app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.test.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { render } from '@testing-library/react-native'; + +import CellSelectWithMenu from './CellSelectWithMenu'; +import { CellModalSelectorsIDs } from '../../../../e2e/selectors/Modals/CellModal.selectors'; + +import { SAMPLE_CELLSELECT_WITH_BUTTON_PROPS } from './CellSelectWithMenu.constants'; + +describe('CellSelectWithMenu', () => { + it('should render with default settings correctly', () => { + const wrapper = render( + , + ); + expect(wrapper).toMatchSnapshot(); + }); + + it('should render CellSelectWithMenu', () => { + const { queryByTestId } = render( + , + ); + // Adjust the testID to match the one used in CellSelectWithMenu, if different + expect(queryByTestId(CellModalSelectorsIDs.MULTISELECT)).not.toBe(null); + }); +}); diff --git a/app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.tsx b/app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.tsx new file mode 100644 index 00000000000..d349d01ad4f --- /dev/null +++ b/app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.tsx @@ -0,0 +1,50 @@ +/* eslint-disable react/prop-types */ + +// Third library dependencies. +import React from 'react'; + +// External dependencies. +import { useStyles } from '../../hooks'; +import CellBase from '../../../component-library/components/Cells/Cell/foundation/CellBase'; + +// Internal dependencies. +import styleSheet from './CellSelectWithMenu.styles'; +import { CellSelectWithMenuProps } from './CellSelectWithMenu.types'; +import { CellModalSelectorsIDs } from '../../../../e2e/selectors/Modals/CellModal.selectors'; +import ListItemMultiSelectButton from '../ListItemMultiSelectButton/ListItemMultiSelectButton'; + +const CellSelectWithMenu = ({ + style, + avatarProps, + title, + secondaryText, + tertiaryText, + tagLabel, + isSelected = false, + children, + ...props +}: CellSelectWithMenuProps) => { + const { styles } = useStyles(styleSheet, { style }); + + return ( + + + {children} + + + ); +}; + +export default CellSelectWithMenu; diff --git a/app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.types.ts b/app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.types.ts new file mode 100644 index 00000000000..d7b8282bfdb --- /dev/null +++ b/app/component-library/components-temp/CellSelectWithMenu/CellSelectWithMenu.types.ts @@ -0,0 +1,18 @@ +// External dependencies. +import { CellBaseProps } from '../../../component-library/components/Cells/Cell/foundation/CellBase/CellBase.types'; +import { ListItemMultiSelectButtonProps } from '../ListItemMultiSelectButton/ListItemMultiSelectButton.types'; + +/** + * Cell Account Select component props. + */ +export interface CellSelectWithMenuProps + extends CellBaseProps, + Omit {} + +/** + * Style sheet input parameters. + */ +export type CellSelectWithMenuStyleSheetVars = Pick< + CellSelectWithMenuProps, + 'style' +>; diff --git a/app/component-library/components-temp/CellSelectWithMenu/__snapshots__/CellSelectWithMenu.test.tsx.snap b/app/component-library/components-temp/CellSelectWithMenu/__snapshots__/CellSelectWithMenu.test.tsx.snap new file mode 100644 index 00000000000..d091087055c --- /dev/null +++ b/app/component-library/components-temp/CellSelectWithMenu/__snapshots__/CellSelectWithMenu.test.tsx.snap @@ -0,0 +1,317 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CellSelectWithMenu should render with default settings correctly 1`] = ` + + + + + + + + + + + + + + + + + + Orangefox.eth + + + 0x2990079bcdEe240329a520d2444386FC119da21a + + + Updated 1 sec ago + + + + Imported + + + + + + + + + + + + +`; diff --git a/app/component-library/components-temp/CellSelectWithMenu/index.ts b/app/component-library/components-temp/CellSelectWithMenu/index.ts new file mode 100644 index 00000000000..814c64969c4 --- /dev/null +++ b/app/component-library/components-temp/CellSelectWithMenu/index.ts @@ -0,0 +1 @@ +export { default } from './CellSelectWithMenu'; diff --git a/app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.constants.ts b/app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.constants.ts new file mode 100644 index 00000000000..fa1de756de1 --- /dev/null +++ b/app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.constants.ts @@ -0,0 +1,19 @@ +// External dependencies. +import { IconName } from '../../../component-library/components/Icons/Icon'; +import { SAMPLE_LISTITEM_PROPS } from '../../../component-library/components/List/ListItem/ListItem.constants'; + +// Internal dependencies. +import { ListItemMultiSelectButtonProps } from './ListItemMultiSelectButton.types'; + +// Defaults +export const DEFAULT_LISTITEMMULTISELECT_GAP = 16; +export const BUTTON_TEST_ID = 'button-menu-select-test-id'; + +// Sample consts +export const SAMPLE_LISTITEMMULTISELECT_PROPS: ListItemMultiSelectButtonProps = + { + isSelected: false, + isDisabled: false, + buttonIcon: IconName.Arrow2Right, + ...SAMPLE_LISTITEM_PROPS, + }; diff --git a/app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.stories.tsx b/app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.stories.tsx new file mode 100644 index 00000000000..f1db9cbef12 --- /dev/null +++ b/app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.stories.tsx @@ -0,0 +1,53 @@ +/* eslint-disable react/display-name */ +import React from 'react'; + +// External dependencies. +import ListItemColumn, { + WidthType, +} from '../../../component-library/components/List/ListItemColumn'; +import Icon, { + IconName, +} from '../../../component-library/components/Icons/Icon'; +import Text, { + TextVariant, +} from '../../../component-library/components/Texts/Text'; + +// Internal dependencies. +import { default as ListItemSelectWithButtonComponent } from './ListItemMultiSelectButton'; +import { SAMPLE_LISTITEMMULTISELECT_PROPS } from './ListItemMultiSelectButton.constants'; +import { ListItemMultiSelectButtonProps } from './ListItemMultiSelectButton.types'; + +const ListItemSelectWithButtonMeta = { + title: 'Component Library / List', + component: ListItemSelectWithButtonComponent, + argTypes: { + isSelected: { + control: { type: 'boolean' }, + defaultValue: SAMPLE_LISTITEMMULTISELECT_PROPS.isSelected, + }, + isDisabled: { + control: { type: 'boolean' }, + defaultValue: SAMPLE_LISTITEMMULTISELECT_PROPS.isDisabled, + }, + }, +}; +export default ListItemSelectWithButtonMeta; + +export const ListItemWithButtonSelect = { + render: (args: JSX.IntrinsicAttributes & ListItemMultiSelectButtonProps) => ( + + + + + + + {'Sample Title'} + + {'Sample Description'} + + + + + + ), +}; diff --git a/app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.styles.ts b/app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.styles.ts new file mode 100644 index 00000000000..3e647f27cfe --- /dev/null +++ b/app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.styles.ts @@ -0,0 +1,65 @@ +// Third party dependencies. +import { StyleSheet, ViewStyle } from 'react-native'; + +// External dependencies. +import { Theme } from '../../../util/theme/models'; + +// Internal dependencies. +import { ListItemMultiSelectButtonStyleSheetVars } from './ListItemMultiSelectButton.types'; + +/** + * Style sheet function for ListItemSelect component. + * + * @param params Style sheet params. + * @param params.theme App theme from ThemeContext. + * @param params.vars Inputs that the style sheet depends on. + * @returns StyleSheet object. + */ +const styleSheet = (params: { + theme: Theme; + vars: ListItemMultiSelectButtonStyleSheetVars; +}) => { + const { vars, theme } = params; + const { colors } = theme; + const { style, isDisabled, isSelected } = vars; + return StyleSheet.create({ + base: Object.assign( + { + position: 'relative', + opacity: isDisabled ? 0.5 : 1, + padding: 16, + backgroundColor: colors.background.default, + width: '95%', + } as ViewStyle, + style, + ) as ViewStyle, + underlay: { + ...StyleSheet.absoluteFillObject, + flexDirection: 'row', + backgroundColor: colors.primary.muted, + }, + underlayBar: { + marginVertical: 4, + marginLeft: 4, + width: 4, + borderRadius: 2, + backgroundColor: colors.primary.default, + }, + listItem: { + paddingRight: 0, + paddingTop: 0, + paddingBottom: 0, + paddingLeft: 0, + }, + container: { + backgroundColor: isSelected + ? colors.primary.muted + : colors.background.default, + paddingRight: 20, + flexDirection: 'row', + alignItems: 'center', + }, + }); +}; + +export default styleSheet; diff --git a/app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.test.tsx b/app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.test.tsx new file mode 100644 index 00000000000..7e73e164d40 --- /dev/null +++ b/app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.test.tsx @@ -0,0 +1,75 @@ +// Third party dependencies. +import React from 'react'; +import { render, fireEvent } from '@testing-library/react-native'; +import { View } from 'react-native'; + +// Internal dependencies. +import ListItemMultiSelectButton from './ListItemMultiSelectButton'; +import { IconName } from '../../../component-library/components/Icons/Icon'; // Adjust the import path as necessary +import { BUTTON_TEST_ID } from './ListItemMultiSelectButton.constants'; + +describe('ListItemMultiSelectButton', () => { + it('should render correctly with default props', () => { + const wrapper = render( + + + , + ); + expect(wrapper).toMatchSnapshot(); + }); + + it('should not render the underlay view if isSelected is false', () => { + const { queryByRole } = render( + + + , + ); + expect(queryByRole('checkbox')).toBeNull(); + }); + + it('should render the underlay view if isSelected is true', () => { + const { queryByRole } = render( + + + , + ); + expect(queryByRole('checkbox')).not.toBeNull(); + }); + + it('should call onPress when the button is pressed', () => { + const mockOnPress = jest.fn(); + const { getByRole } = render( + + + , + ); + fireEvent.press(getByRole('button')); + expect(mockOnPress).toHaveBeenCalled(); + }); + + it('should render the button icon with the correct name', () => { + const { getByTestId } = render( + + + , + ); + expect(getByTestId(BUTTON_TEST_ID)).not.toBeNull(); + }); + + it('should call onButtonClick when the button icon is pressed', () => { + const mockOnButtonClick = jest.fn(); + const { getByTestId } = render( + + + , + ); + fireEvent.press(getByTestId(BUTTON_TEST_ID)); + expect(mockOnButtonClick).toHaveBeenCalled(); + }); +}); diff --git a/app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.tsx b/app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.tsx new file mode 100644 index 00000000000..e335843c3a9 --- /dev/null +++ b/app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.tsx @@ -0,0 +1,71 @@ +/* eslint-disable react/prop-types */ + +// Third party dependencies. +import React from 'react'; +import { TouchableOpacity, View } from 'react-native'; + +// External dependencies. +import { useStyles } from '../../hooks'; +import ListItem from '../../../component-library/components/List/ListItem/ListItem'; + +// Internal dependencies. +import styleSheet from './ListItemMultiSelectButton.styles'; +import { ListItemMultiSelectButtonProps } from './ListItemMultiSelectButton.types'; +import { + BUTTON_TEST_ID, + DEFAULT_LISTITEMMULTISELECT_GAP, +} from './ListItemMultiSelectButton.constants'; +import ButtonIcon from '../../../component-library/components/Buttons/ButtonIcon'; +import { + IconColor, + IconName, +} from '../../../component-library/components/Icons/Icon'; + +const ListItemMultiSelectButton: React.FC = ({ + style, + isSelected = false, + isDisabled = false, + children, + gap = DEFAULT_LISTITEMMULTISELECT_GAP, + buttonIcon = IconName.MoreVertical, + ...props +}) => { + const { styles } = useStyles(styleSheet, { + style, + gap, + isDisabled, + isSelected, + }); + + return ( + + + + {children} + + {isSelected && ( + + + + )} + + + + + + ); +}; + +export default ListItemMultiSelectButton; diff --git a/app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.types.ts b/app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.types.ts new file mode 100644 index 00000000000..c9943e6e45a --- /dev/null +++ b/app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.types.ts @@ -0,0 +1,43 @@ +// Third party dependencies. +import { TouchableOpacityProps } from 'react-native'; + +// External dependencies. +import { ListItemProps } from '../ListItem/ListItem.types'; +import { IconName } from '../../Icons/Icon'; +import { GestureResponderEvent } from 'react-native-modal'; + +/** + * ListItemMultiSelect component props. + */ +export interface ListItemMultiSelectButtonProps + extends TouchableOpacityProps, + Omit { + /** + * Optional prop to determine if the item is selected. + */ + isSelected?: boolean; + /** + * Optional prop to determine if the item is disabled. + */ + isDisabled?: boolean; + + /** + * Optional Button icon type. + */ + buttonIcon?: IconName; + + /** + * Optional button onClick function + */ + onButtonClick?: ((event: GestureResponderEvent) => void) | undefined; +} + +/** + * Style sheet input parameters. + */ +export type ListItemMultiSelectButtonStyleSheetVars = Pick< + ListItemMultiSelectButtonProps, + 'style' | 'isDisabled' | 'isSelected' +> & { + gap: number | string | undefined; +}; diff --git a/app/component-library/components-temp/ListItemMultiSelectButton/__snapshots__/ListItemMultiSelectButton.test.tsx.snap b/app/component-library/components-temp/ListItemMultiSelectButton/__snapshots__/ListItemMultiSelectButton.test.tsx.snap new file mode 100644 index 00000000000..49f9a1b0c2d --- /dev/null +++ b/app/component-library/components-temp/ListItemMultiSelectButton/__snapshots__/ListItemMultiSelectButton.test.tsx.snap @@ -0,0 +1,79 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ListItemMultiSelectButton should render correctly with default props 1`] = ` + + + + + + + + + + + + +`; diff --git a/app/component-library/components-temp/ListItemMultiSelectButton/index.ts b/app/component-library/components-temp/ListItemMultiSelectButton/index.ts new file mode 100644 index 00000000000..b44be2a7359 --- /dev/null +++ b/app/component-library/components-temp/ListItemMultiSelectButton/index.ts @@ -0,0 +1 @@ +export { default } from './ListItemMultiSelectButton'; diff --git a/app/component-library/components/Cells/Cell/Cell.tsx b/app/component-library/components/Cells/Cell/Cell.tsx index 60c209e5e77..127b3d23015 100644 --- a/app/component-library/components/Cells/Cell/Cell.tsx +++ b/app/component-library/components/Cells/Cell/Cell.tsx @@ -5,6 +5,7 @@ import React from 'react'; import CellDisplay from './variants/CellDisplay'; import CellMultiSelect from './variants/CellMultiSelect'; import CellSelect from './variants/CellSelect'; +import CellSelectWithMenu from '../../../components-temp/CellSelectWithMenu'; import { CellModalSelectorsIDs } from '../../../../../e2e/selectors/Modals/CellModal.selectors'; // Internal dependencies. @@ -23,6 +24,13 @@ const Cell = ({ variant, ...props }: CellProps) => { ); case CellVariant.Select: return ; + case CellVariant.SelectWithMenu: + return ( + + ); default: throw new Error('Invalid Cell Variant'); } diff --git a/app/component-library/components/Cells/Cell/Cell.types.ts b/app/component-library/components/Cells/Cell/Cell.types.ts index 71e06149847..4ffe31fa279 100644 --- a/app/component-library/components/Cells/Cell/Cell.types.ts +++ b/app/component-library/components/Cells/Cell/Cell.types.ts @@ -2,6 +2,7 @@ import { CellDisplayProps } from './variants/CellDisplay/CellDisplay.types'; import { CellMultiSelectProps } from './variants/CellMultiSelect/CellMultiSelect.types'; import { CellSelectProps } from './variants/CellSelect/CellSelect.types'; +import { CellSelectWithMenuProps } from '../../../components-temp/CellSelectWithMenu/CellSelectWithMenu.types'; /** * Cell variants. @@ -10,6 +11,7 @@ export enum CellVariant { Select = 'Select', MultiSelect = 'MultiSelect', Display = 'Display', + SelectWithMenu = 'SelectWithMenu', } /** @@ -19,6 +21,7 @@ export type CellProps = ( | CellDisplayProps | CellMultiSelectProps | CellSelectProps + | CellSelectWithMenuProps ) & { /** * Variant of Cell diff --git a/app/components/Views/NetworkSelector/NetworkSearchTextInput/NetworkSearchTextInput.styles.ts b/app/components/Views/NetworkSelector/NetworkSearchTextInput/NetworkSearchTextInput.styles.ts index 584db48d9e0..9590cf40794 100644 --- a/app/components/Views/NetworkSelector/NetworkSearchTextInput/NetworkSearchTextInput.styles.ts +++ b/app/components/Views/NetworkSelector/NetworkSearchTextInput/NetworkSearchTextInput.styles.ts @@ -14,6 +14,16 @@ const createStyles = (colors: Colors) => borderColor: colors.border.default, color: colors.text.default, }, + focusedInputWrapper: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 10, + paddingVertical: 10, + borderRadius: 5, + borderWidth: 1, + borderColor: colors.primary.default, + color: colors.text.default, + }, input: { flex: 1, fontSize: 14, @@ -21,6 +31,14 @@ const createStyles = (colors: Colors) => ...fontStyles.normal, paddingLeft: 10, }, + unfocusedInput: { + flex: 1, + fontSize: 14, + color: colors.text.default, + ...fontStyles.normal, + paddingLeft: 10, + borderColor: colors.border.default, + }, }); export default createStyles; diff --git a/app/components/Views/NetworkSelector/NetworkSearchTextInput/NetworkSearchTextInput.tsx b/app/components/Views/NetworkSelector/NetworkSearchTextInput/NetworkSearchTextInput.tsx index fca39434c03..2f595433ab8 100644 --- a/app/components/Views/NetworkSelector/NetworkSearchTextInput/NetworkSearchTextInput.tsx +++ b/app/components/Views/NetworkSelector/NetworkSearchTextInput/NetworkSearchTextInput.tsx @@ -1,10 +1,11 @@ // Third party dependencies. -import React from 'react'; +import React, { useState } from 'react'; import { TextInput, View } from 'react-native'; // External dependencies. import { strings } from '../../../../../locales/i18n'; import { mockTheme, useTheme } from '../../../../util/theme'; +import { isNetworkUiRedesignEnabled } from '../../../../util/networks'; // Internal dependencies import Icon from 'react-native-vector-icons/Ionicons'; @@ -26,17 +27,45 @@ function NetworkSearchTextInput({ const theme = useTheme(); const { colors } = theme; const styles = createStyles(colors || mockTheme.colors); + const [isSearchFieldFocused, setIsSearchFieldFocused] = useState(false); + const searchPlaceHolder = isNetworkUiRedesignEnabled + ? 'search-short' + : 'search'; + + const propsWhichAreFeatureFlagged = isNetworkUiRedesignEnabled + ? { + onFocus: () => { + isNetworkUiRedesignEnabled && setIsSearchFieldFocused(true); + }, + onBlur: () => { + isNetworkUiRedesignEnabled && setIsSearchFieldFocused(false); + }, + } + : {}; + + const inputStylesWhichAreFeatureFlagged = !isNetworkUiRedesignEnabled + ? styles.input + : isSearchFieldFocused + ? styles.input + : styles.unfocusedInput; + + const containerInputStylesWhichAreFeatureFlagged = !isNetworkUiRedesignEnabled + ? styles.inputWrapper + : isSearchFieldFocused + ? styles.focusedInputWrapper + : styles.inputWrapper; return ( - + {searchString.length > 0 && ( ? 12 : 0, }, + networkMenu: { + alignItems: 'center', + }, + containerDeleteText: { + paddingLeft: 16, + paddingRight: 8, + alignItems: 'center', + }, + textCentred: { + textAlign: 'center', + }, + buttonWrapper: { + flexDirection: 'row', + flex: 1, + width: '80%', + }, + button: { + width: '100%', + }, + container: { + flexDirection: 'row', + justifyContent: 'space-around', + alignItems: 'center', + backgroundColor: colors.background.default, + }, + item: { + paddingLeft: 8, + }, + buttonMenu: { + backgroundColor: colors.background.alternative, + }, switchContainer: { flexDirection: 'row', justifyContent: 'space-between', @@ -26,6 +57,13 @@ const createStyles = (colors: Colors) => marginVertical: 16, marginHorizontal: 16, }, + popularNetworkTitleContainer: { + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + marginVertical: 16, + marginHorizontal: 16, + }, addtionalNetworksContainer: { marginHorizontal: 16, }, @@ -102,6 +140,18 @@ const createStyles = (colors: Colors) => marginRight: 16, marginBottom: 8, }, + gasInfoContainer: { + paddingHorizontal: 4, + }, + gasInfoIcon: { + color: colors.icon.alternative, + }, + hitSlop: { + top: 10, + left: 10, + bottom: 10, + right: 10, + }, }); export default createStyles; diff --git a/app/components/Views/NetworkSelector/NetworkSelector.tsx b/app/components/Views/NetworkSelector/NetworkSelector.tsx index d508c671c2c..3a2d2431d9c 100644 --- a/app/components/Views/NetworkSelector/NetworkSelector.tsx +++ b/app/components/Views/NetworkSelector/NetworkSelector.tsx @@ -1,7 +1,6 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ // Third party dependencies. -import React, { useRef, useState } from 'react'; -import { Linking, Switch, View } from 'react-native'; +import { Linking, Switch, TouchableOpacity, View } from 'react-native'; +import React, { useCallback, useRef, useState } from 'react'; import { ScrollView } from 'react-native-gesture-handler'; import images from 'images/image-icons'; import { useNavigation } from '@react-navigation/native'; @@ -20,6 +19,7 @@ import { strings } from '../../../../locales/i18n'; import BottomSheet, { BottomSheetRef, } from '../../../component-library/components/BottomSheets/BottomSheet'; +import { IconName } from '../../../component-library/components/Icons/Icon'; import { useSelector } from 'react-redux'; import { selectNetworkConfigurations, @@ -61,17 +61,27 @@ import { useMetrics } from '../../../components/hooks/useMetrics'; // Internal dependencies import createStyles from './NetworkSelector.styles'; -import { TESTNET_TICKER_SYMBOLS } from '@metamask/controller-utils'; +import { + InfuraNetworkType, + TESTNET_TICKER_SYMBOLS, +} from '@metamask/controller-utils'; import InfoModal from '../../../../app/components/UI/Swaps/components/InfoModal'; import hideKeyFromUrl from '../../../util/hideKeyFromUrl'; import CustomNetwork from '../Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork'; import { NetworksSelectorSelectorsIDs } from '../../../../e2e/selectors/Settings/NetworksView.selectors'; import { PopularList } from '../../../util/networks/customNetworks'; import NetworkSearchTextInput from './NetworkSearchTextInput'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import BottomSheetHeader from '../../../component-library/components/BottomSheets/BottomSheetHeader'; +import AccountAction from '../AccountAction'; +import { ButtonsAlignment } from '../../../component-library/components/BottomSheets/BottomSheetFooter'; +import { ButtonProps } from '../../../component-library/components/Buttons/Button/Button.types'; +import BottomSheetFooter from '../../../component-library/components/BottomSheets/BottomSheetFooter/BottomSheetFooter'; +import { ExtendedNetwork } from '../Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork.types'; const NetworkSelector = () => { const [showPopularNetworkModal, setShowPopularNetworkModal] = useState(false); - const [popularNetwork, setPopularNetwork] = useState(undefined); + const [popularNetwork, setPopularNetwork] = useState(); const [showWarningModal, setShowWarningModal] = useState(false); const [searchString, setSearchString] = useState(''); const { navigate } = useNavigation(); @@ -86,12 +96,35 @@ const NetworkSelector = () => { const networkConfigurations = useSelector(selectNetworkConfigurations); const avatarSize = isNetworkUiRedesignEnabled ? AvatarSize.Sm : undefined; + const modalTitle = isNetworkUiRedesignEnabled + ? 'networks.additional_network_information_title' + : 'networks.network_warning_title'; + const modalDescription = isNetworkUiRedesignEnabled + ? 'networks.additonial_network_information_desc' + : 'networks.network_warning_desc'; const buttonLabelAddNetwork = isNetworkUiRedesignEnabled ? 'app_settings.network_add_custom_network' : 'app_settings.network_add_network'; + const [showConfirmDeleteModal, setShowConfirmDeleteModal] = useState({ + isVisible: false, + networkName: '', + entry: {}, + }); + + const [showNetworkMenuModal, setNetworkMenuModal] = useState({ + isVisible: false, + chainId: '', + displayEdit: false, + networkTypeOrRpcUrl: '', + isReadOnly: false, + }); + + const networkMenuSheetRef = useRef(null); + + const deleteModalSheetRef = useRef(null); // The only possible value types are mainnet, linea-mainnet, sepolia and linea-sepolia - const onNetworkChange = (type: string) => { + const onNetworkChange = (type: InfuraNetworkType) => { const { NetworkController, CurrencyRateController, @@ -150,9 +183,41 @@ const NetworkSelector = () => { } }; - // TODO: Replace "any" with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const showNetworkModal = (networkConfiguration: any) => { + const openModal = useCallback( + (chainId, displayEdit, networkTypeOrRpcUrl, isReadOnly) => { + setNetworkMenuModal({ + isVisible: true, + chainId, + displayEdit, + networkTypeOrRpcUrl, + isReadOnly, + }); + networkMenuSheetRef.current?.onOpenBottomSheet(); + }, + [], + ); + + const closeModal = useCallback(() => { + setNetworkMenuModal(() => ({ + chainId: '', + isVisible: false, + displayEdit: false, + networkTypeOrRpcUrl: '', + isReadOnly: false, + })); + networkMenuSheetRef.current?.onCloseBottomSheet(); + }, []); + + const closeDeleteModal = useCallback(() => { + setShowConfirmDeleteModal(() => ({ + networkName: '', + isVisible: false, + entry: {}, + })); + networkMenuSheetRef.current?.onCloseBottomSheet(); + }, []); + + const showNetworkModal = (networkConfiguration: ExtendedNetwork) => { setShowPopularNetworkModal(true); setPopularNetwork({ ...networkConfiguration, @@ -170,12 +235,16 @@ const NetworkSelector = () => { const toggleWarningModal = () => { setShowWarningModal(!showWarningModal); }; + const goToLearnMore = () => { Linking.openURL(strings('networks.learn_more_url')); }; - const filterNetworksByName = (networks: any[], networkName: string) => { - const searchResult: any = networks.filter(({ name }) => + const filterNetworksByName = ( + networks: ExtendedNetwork[], + networkName: string, + ) => { + const searchResult: ExtendedNetwork[] = networks.filter(({ name }) => name.toLowerCase().includes(networkName.toLowerCase()), ); @@ -202,6 +271,31 @@ const NetworkSelector = () => { if (isNetworkUiRedesignEnabled && isNoSearchResults(MAINNET)) return null; + if (isNetworkUiRedesignEnabled) { + return ( + onNetworkChange(MAINNET)} + style={styles.networkCell} + buttonIcon={IconName.MoreVertical} + onButtonClick={() => { + openModal(chainId, false, MAINNET, true); + }} + /> + ); + } + return ( { if (isNetworkUiRedesignEnabled && isNoSearchResults('linea-mainnet')) return null; + if (isNetworkUiRedesignEnabled) { + return ( + onNetworkChange(LINEA_MAINNET)} + style={styles.networkCell} + buttonIcon={IconName.MoreVertical} + onButtonClick={() => { + openModal(chainId, false, LINEA_MAINNET, true); + }} + /> + ); + } + return ( { //@ts-expect-error - The utils/network file is still JS and this function expects a networkType, and should be optional const image = getNetworkImageSource({ chainId: chainId?.toString() }); + if (isNetworkUiRedesignEnabled) { + return ( + onSetRpcTarget(rpcUrl)} + style={styles.networkCell} + buttonIcon={IconName.MoreVertical} + onButtonClick={() => { + openModal(chainId, true, rpcUrl, false); + }} + /> + ); + } + return ( { const renderOtherNetworks = () => { const getOtherNetworks = () => getAllNetworks().slice(2); return getOtherNetworks().map((networkType) => { - // TODO: Provide correct types for network. - // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any const { name, imageSource, chainId } = (Networks as any)[networkType]; if (isNetworkUiRedesignEnabled && isNoSearchResults(name)) return null; + if (isNetworkUiRedesignEnabled) { + return ( + onNetworkChange(networkType)} + style={styles.networkCell} + buttonIcon={IconName.MoreVertical} + onButtonClick={() => { + openModal(chainId, false, networkType, true); + }} + /> + ); + } + return ( { searchString.length > 0 ? filteredNetworks : undefined } showCompletionMessage={false} + hideWarningIcons /> ); }; - const renderTitle = (title: string) => ( + const renderPopularNetworksTitle = () => ( + + + {strings('networks.additional_networks')} + + + + + + ); + + const renderEnabledNetworksTitle = () => ( - {strings(title)} + {strings('networks.enabled_networks')} ); - const handleSearchTextChange = (text: any) => { + const handleSearchTextChange = (text: string) => { setSearchString(text); }; @@ -379,6 +563,54 @@ const NetworkSelector = () => { setSearchString(''); }; + const removeRpcUrl = (networkId: string) => { + const entry = Object.entries(networkConfigurations).find( + ([, { chainId }]) => chainId === networkId, + ); + + if (!entry) { + throw new Error(`Unable to find network with chain id ${networkId}`); + } + + const [, { nickname }] = entry; + + closeModal(); + + setShowConfirmDeleteModal({ + isVisible: true, + networkName: nickname, + entry, + }); + }; + + const confirmRemoveRpc = () => { + const [networkConfigurationId] = showConfirmDeleteModal.entry; + + const { NetworkController } = Engine.context; + + NetworkController.removeNetworkConfiguration(networkConfigurationId); + + setShowConfirmDeleteModal({ + isVisible: false, + networkName: '', + entry: {}, + }); + }; + + const cancelButtonProps: ButtonProps = { + variant: ButtonVariants.Secondary, + label: strings('accountApproval.cancel'), + size: ButtonSize.Lg, + onPress: () => closeDeleteModal(), + }; + + const deleteButtonProps: ButtonProps = { + variant: ButtonVariants.Primary, + label: strings('app_settings.delete'), + size: ButtonSize.Lg, + onPress: () => confirmRemoveRpc(), + }; + const renderBottomSheetContent = () => ( <> @@ -398,13 +630,13 @@ const NetworkSelector = () => { )} {isNetworkUiRedesignEnabled && searchString.length === 0 && - renderTitle('networks.enabled_networks')} + renderEnabledNetworksTitle()} {renderMainnet()} {renderLineaMainnet()} {renderRpcNetworks()} {isNetworkUiRedesignEnabled && searchString.length === 0 && - renderTitle('networks.additional_networks')} + renderPopularNetworksTitle()} {isNetworkUiRedesignEnabled && renderAdditonalNetworks()} {searchString.length === 0 && renderTestNetworksSwitch()} {showTestNetworks && renderOtherNetworks()} @@ -435,12 +667,10 @@ const NetworkSelector = () => { {showWarningModal ? ( - - {strings('networks.network_warning_desc')} - {' '} + {strings(modalDescription)}{' '} {strings('networks.learn_more')} @@ -449,6 +679,65 @@ const NetworkSelector = () => { toggleModal={toggleWarningModal} /> ) : null} + + {showNetworkMenuModal.isVisible ? ( + + + { + navigate(Routes.ADD_NETWORK, { + shouldNetworkSwitchPopToWallet: false, + shouldShowPopularNetworks: false, + network: showNetworkMenuModal.networkTypeOrRpcUrl, + }); + }} + /> + {showNetworkMenuModal.chainId !== providerConfig.chainId && + showNetworkMenuModal.displayEdit ? ( + removeRpcUrl(showNetworkMenuModal.chainId)} + /> + ) : null} + + + ) : null} + + {showConfirmDeleteModal.isVisible ? ( + + + + {strings('app_settings.delete')}{' '} + {showConfirmDeleteModal.networkName}{' '} + {strings('asset_details.network')} + + + + + {strings('app_settings.network_delete')} + + + + + ) : null} ); }; diff --git a/app/components/Views/Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork.tsx b/app/components/Views/Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork.tsx index a2bfb722ab5..612ff8e0de9 100644 --- a/app/components/Views/Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork.tsx +++ b/app/components/Views/Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork.tsx @@ -28,6 +28,7 @@ const CustomNetwork = ({ showAddedNetworks, customNetworksList, showCompletionMessage = true, + hideWarningIcons = false, }: CustomNetworkProps) => { const networkConfigurations = useSelector(selectNetworkConfigurations); @@ -97,7 +98,9 @@ const CustomNetwork = ({ - {toggleWarningModal && networkConfiguration.warning ? ( + {!hideWarningIcons && + toggleWarningModal && + networkConfiguration.warning ? ( Date: Thu, 27 Jun 2024 19:07:22 +0200 Subject: [PATCH 10/16] fix: fix checksum address for balance check (#10135) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** When a user disables Batch account balance in settings, and go back to wallet view, they should be able to still see their balance. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-mobile/issues/10132 ## **Manual testing steps** 1. Go to Settings>Security & Privacy and disable Batch account balance 2. Go back to wallet view 3. Relaunch the app 4. Unlock the app and you should be able to see your balance correctly ## **Screenshots/Recordings** ### **Before** Check video in linked issue 🙏 ### **After** https://github.com/MetaMask/metamask-mobile/assets/10994169/b26f20fd-d594-47ed-a689-106dd74de741 ## **Pre-merge author checklist** - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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. ## **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. --- patches/@metamask+assets-controllers+30.0.0.patch | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/patches/@metamask+assets-controllers+30.0.0.patch b/patches/@metamask+assets-controllers+30.0.0.patch index b18b4e3a312..dc9b7fdfeba 100644 --- a/patches/@metamask+assets-controllers+30.0.0.patch +++ b/patches/@metamask+assets-controllers+30.0.0.patch @@ -668,9 +668,18 @@ index 8c506d9..d1ec2d2 100644 }, {} diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-V4ZO3F2S.js b/node_modules/@metamask/assets-controllers/dist/chunk-V4ZO3F2S.js -index 0430e5c..dee32be 100644 +index 0430e5c..038398c 100644 --- a/node_modules/@metamask/assets-controllers/dist/chunk-V4ZO3F2S.js +++ b/node_modules/@metamask/assets-controllers/dist/chunk-V4ZO3F2S.js +@@ -61,7 +61,7 @@ var AccountTrackerController = class extends _pollingcontroller.StaticIntervalPo + this.syncAccounts(chainId); + const { accounts, accountsByChainId } = this.state; + const isMultiAccountBalancesEnabled = this.getMultiAccountBalancesEnabled(); +- const accountsToUpdate = isMultiAccountBalancesEnabled ? Object.keys(accounts) : [this.getSelectedAddress()]; ++ const accountsToUpdate = isMultiAccountBalancesEnabled ? Object.keys(accounts) : [_controllerutils.toChecksumHexAddress.call(void 0, this.getSelectedAddress())]; + const accountsForChain = { ...accountsByChainId[chainId] }; + for (const address of accountsToUpdate) { + const balance = await this.getBalanceFromChain(address, ethQuery); @@ -80,9 +80,11 @@ var AccountTrackerController = class extends _pollingcontroller.StaticIntervalPo [chainId]: accountsForChain } From d540b757d5230b317edfafa6dc059c3400859b11 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Fri, 28 Jun 2024 13:13:44 +0200 Subject: [PATCH 11/16] fix: Stop running fencing logic on `node_modules` (#10164) ## **Description** Stops running fencing logic on `node_modules`, mirroring the extension implementation. There is no reason for us to run fencing on code coming from `node_modules` anyway. This may have a potential positive performance impact as well during build-time. --- metro.transform.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/metro.transform.js b/metro.transform.js index 06560242cf5..0fc4f1dc32f 100644 --- a/metro.transform.js +++ b/metro.transform.js @@ -59,7 +59,10 @@ module.exports.transform = async ({ src, filename, options }) => { * Params based on builds we're code splitting * i.e: flavorDimensions "version" productFlavors from android/app/build.gradle */ - if (fileExtsToScan.includes(path.extname(filename))) { + if ( + !path.normalize(filename).split(path.sep).includes('node_modules') && + fileExtsToScan.includes(path.extname(filename)) + ) { const [processedSource, didModify] = removeFencedCode(filename, src, { all: availableFeatures, active: getBuildTypeFeatures(), From fe986fe650d3dac7ec2d359e3820cc3bb49fe403 Mon Sep 17 00:00:00 2001 From: OGPoyraz Date: Fri, 28 Jun 2024 13:29:45 +0200 Subject: [PATCH 12/16] fix: memoize token list (#10142) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR aims to address performance issue on simulations happens only on mainnet. ## **Related issues** ## **Manual testing steps** There should be no performance discrepancies for simulations between mainnet and any other network. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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. ## **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. --- .../hooks/DisplayName/useTokenList.test.ts | 12 ++++++++++++ app/components/hooks/DisplayName/useTokenList.ts | 15 +++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/app/components/hooks/DisplayName/useTokenList.test.ts b/app/components/hooks/DisplayName/useTokenList.test.ts index d012ef581f1..856ed1c042d 100644 --- a/app/components/hooks/DisplayName/useTokenList.test.ts +++ b/app/components/hooks/DisplayName/useTokenList.test.ts @@ -1,3 +1,4 @@ +import React from 'react'; import { type TokenListMap } from '@metamask/assets-controllers'; import { selectChainId } from '../../../selectors/networkController'; import { selectUseTokenDetection } from '../../../selectors/preferencesController'; @@ -69,6 +70,17 @@ describe('useTokenList', () => { selectUseTokenDetectionMock.mockReturnValue(true); selectTokenListMock.mockReturnValue(TOKEN_LIST_MOCK); isMainnetByChainIdMock.mockReturnValue(true); + + const memoizedValues = new Map(); + jest.spyOn(React, 'useMemo').mockImplementation((factory, deps) => { + const depsKey = (deps as []).join('|'); + if (memoizedValues.has(depsKey)) { + return memoizedValues.get(depsKey); + } + const newValue = factory(); + memoizedValues.set(depsKey, newValue); + return newValue; + }); }); it('returns normalized STATIC_MAINNET_TOKEN_LIST if token detection is disabled and chain is mainnet', () => { diff --git a/app/components/hooks/DisplayName/useTokenList.ts b/app/components/hooks/DisplayName/useTokenList.ts index 6488c95f7fd..6726fffbbf3 100644 --- a/app/components/hooks/DisplayName/useTokenList.ts +++ b/app/components/hooks/DisplayName/useTokenList.ts @@ -1,3 +1,4 @@ +import { useMemo } from 'react'; import { type TokenListMap } from '@metamask/assets-controllers'; import contractMap from '@metamask/contract-metadata'; @@ -25,11 +26,13 @@ export default function useTokenList(): TokenListMap { const chainId = useSelector(selectChainId); const isMainnet = isMainnetByChainId(chainId); const isTokenDetectionEnabled = useSelector(selectUseTokenDetection); - const tokenList = useSelector(selectTokenList) || []; + const tokenList = useSelector(selectTokenList); + const shouldUseStaticList = !isTokenDetectionEnabled && isMainnet; - if (!isTokenDetectionEnabled && isMainnet) { - return NORMALIZED_MAINNET_TOKEN_LIST; - } - - return normalizeTokenAddresses(tokenList); + return useMemo(() => { + if (shouldUseStaticList) { + return NORMALIZED_MAINNET_TOKEN_LIST; + } + return normalizeTokenAddresses(tokenList); + }, [shouldUseStaticList, tokenList]); } From ad1023a4e7cca2ccdb4c9ec37d635bf8f8ea75ba Mon Sep 17 00:00:00 2001 From: Nico MASSART Date: Sat, 29 Jun 2024 00:32:52 +0200 Subject: [PATCH 13/16] fix: convert Sentry messages to log (#10168) - Change call to `Logger.message` for `Logger.log` - Delete `Logger.message` see MetaMask/mobile-planning/issues/1814 --- app/components/Views/BrowserTab/index.js | 2 +- app/core/EngineService/EngineService.ts | 6 ++-- app/util/Logger/index.test.ts | 26 +--------------- app/util/Logger/index.ts | 38 ++++-------------------- 4 files changed, 9 insertions(+), 63 deletions(-) diff --git a/app/components/Views/BrowserTab/index.js b/app/components/Views/BrowserTab/index.js index b7c87e1615a..49b55be4097 100644 --- a/app/components/Views/BrowserTab/index.js +++ b/app/components/Views/BrowserTab/index.js @@ -873,7 +873,7 @@ export const BrowserTab = (props) => { // Continue request loading it the protocol is whitelisted const { protocol } = new URL(url); if (protocolAllowList.includes(protocol)) return true; - Logger.message(`Protocol not allowed ${protocol}`); + Logger.log(`Protocol not allowed ${protocol}`); // If it is a trusted deeplink protocol, do not show the // warning alert. Allow the OS to deeplink the URL diff --git a/app/core/EngineService/EngineService.ts b/app/core/EngineService/EngineService.ts index acebf290499..5698e99ab44 100644 --- a/app/core/EngineService/EngineService.ts +++ b/app/core/EngineService/EngineService.ts @@ -126,7 +126,7 @@ class EngineService { engine?.datamodel?.subscribe?.(() => { if (!engine.context.KeyringController.metadata.vault) { - Logger.message('keyringController vault missing for INIT_BG_STATE_KEY'); + Logger.log('keyringController vault missing for INIT_BG_STATE_KEY'); } if (!this.engineInitialized) { store.dispatch({ type: INIT_BG_STATE_KEY }); @@ -138,9 +138,7 @@ class EngineService { const { name, key = undefined } = controller; const update_bg_state_cb = () => { if (!engine.context.KeyringController.metadata.vault) { - Logger.message( - 'keyringController vault missing for UPDATE_BG_STATE_KEY', - ); + Logger.log('keyringController vault missing for UPDATE_BG_STATE_KEY'); } store.dispatch({ type: UPDATE_BG_STATE_KEY, payload: { key: name } }); }; diff --git a/app/util/Logger/index.test.ts b/app/util/Logger/index.test.ts index a8618900c28..0b0ec078a4c 100644 --- a/app/util/Logger/index.test.ts +++ b/app/util/Logger/index.test.ts @@ -1,9 +1,5 @@ import Logger from '.'; -import { - captureException, - withScope, - captureMessage, -} from '@sentry/react-native'; +import { captureException, withScope } from '@sentry/react-native'; import { AGREED, METRICS_OPT_IN } from '../../constants/storage'; import DefaultPreference from 'react-native-default-preference'; @@ -13,7 +9,6 @@ jest.mock('@sentry/react-native', () => ({ withScope: jest.fn(), })); const mockedCaptureException = jest.mocked(captureException); -const mockedCaptureMessage = jest.mocked(captureMessage); const mockedWithScope = jest.mocked(withScope); describe('Logger', () => { @@ -75,23 +70,4 @@ describe('Logger', () => { expect(mockedCaptureException).toHaveBeenCalledWith(expect.any(Error)); }); }); - - describe('message', () => { - it('skips captureMessage if metrics is opted out', async () => { - DefaultPreference.get = jest.fn((key: string) => { - switch (key) { - case METRICS_OPT_IN: - return Promise.resolve(''); - default: - return Promise.resolve(''); - } - }); - await Logger.message('testMessage'); - expect(mockedCaptureMessage).not.toHaveBeenCalled(); - }); - it('calls captureMessage if metrics is opted in', async () => { - await Logger.message('testMessage'); - expect(mockedCaptureMessage).toHaveBeenCalledTimes(1); - }); - }); }); diff --git a/app/util/Logger/index.ts b/app/util/Logger/index.ts index df0771dd54f..5b23c94788f 100644 --- a/app/util/Logger/index.ts +++ b/app/util/Logger/index.ts @@ -1,7 +1,6 @@ import { addBreadcrumb, captureException, - captureMessage, withScope, } from '@sentry/react-native'; import DefaultPreference from 'react-native-default-preference'; @@ -17,6 +16,11 @@ interface ExtraInfo { * console.log and console.error and in the future * we will have flags to do different actions based on * the environment, for ex. log to a remote server if prod + * + * The previously available message function has been removed + * favoring the use of the error or log function: + * - error: for logging errors that you want to see in Sentry, + * - log: for logging general information and sending breadcrumbs only with the next Sentry event. */ export class AsyncLogger { /** @@ -95,26 +99,6 @@ export class AsyncLogger { } } } - - /** - * captureMessage wrapper - * - * @param {object} args - data to be logged - * @returns - void - */ - static async message(...args: unknown[]): Promise { - if (__DEV__) { - args.unshift('[MetaMask DEBUG]:'); - // console.log.apply(null, args); // eslint-disable-line no-console - return; - } - - // Check if user passed accepted opt-in to metrics - const metricsOptIn = await DefaultPreference.get(METRICS_OPT_IN); - if (metricsOptIn === 'agreed') { - captureMessage(JSON.stringify(args)); - } - } } export default class Logger { @@ -146,16 +130,4 @@ export default class Logger { // ignore error but avoid dangling promises }); } - - /** - * captureMessage wrapper - * - * @param {object} args - data to be logged - * @returns - void - */ - static message(...args: unknown[]) { - AsyncLogger.message(...args).catch(() => { - // ignore error but avoid dangling promises - }); - } } From f451c929cb94e8a115d2325d03695ea4db7c8a83 Mon Sep 17 00:00:00 2001 From: CW Date: Sat, 29 Jun 2024 05:39:30 -0700 Subject: [PATCH 14/16] test: add connect to Portfolio (#10015) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Automate scenario for connecting wallet to Portfolio by tapping on Portfolio button on Wallet view. ## **Related issues** Related: https://github.com/MetaMask/mobile-planning/issues/1728 ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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. ## **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. --------- Co-authored-by: SamuelSalas --- app/components/UI/Tokens/index.test.tsx | 18 ++--- app/components/UI/Tokens/index.tsx | 12 +-- app/components/Views/Wallet/index.tsx | 6 +- e2e/pages/Browser/BrowserView.js | 33 ++++++-- e2e/pages/Browser/PortfolioHomePage.js | 44 +++++++++++ e2e/pages/Send/SendView.js | 10 +-- e2e/pages/WalletView.js | 78 +++++++++---------- .../Browser/BrowserView.selectors.js | 3 + .../Browser/PortfolioPage.selectors.js | 7 ++ e2e/selectors/wallet/WalletView.selectors.js | 3 + e2e/specs/browser/browser-tests.spec.js | 4 +- .../quarantine/contract-nickname.failing.js | 5 +- e2e/specs/settings/addressbook-tests.spec.js | 6 +- e2e/specs/settings/fiat-on-testnets.spec.js | 6 +- .../wallet/portfolio-connect-account.spec.js | 66 ++++++++++++++++ e2e/utils/Assertions.js | 2 +- e2e/utils/Matchers.js | 16 ++-- .../testIDs/Components/Tokens.testIds.js | 5 -- 18 files changed, 227 insertions(+), 97 deletions(-) create mode 100644 e2e/pages/Browser/PortfolioHomePage.js create mode 100644 e2e/selectors/Browser/PortfolioPage.selectors.js create mode 100644 e2e/specs/wallet/portfolio-connect-account.spec.js diff --git a/app/components/UI/Tokens/index.test.tsx b/app/components/UI/Tokens/index.test.tsx index 4a7e22aaabf..bd18705d789 100644 --- a/app/components/UI/Tokens/index.test.tsx +++ b/app/components/UI/Tokens/index.test.tsx @@ -11,15 +11,11 @@ import { IMPORT_TOKEN_BUTTON_ID, MAIN_WALLET_VIEW_VIA_TOKENS_ID, } from '../../../../wdio/screen-objects/testIDs/Screens/WalletView.testIds'; -import { - PORTFOLIO_BUTTON, - STAKE_BUTTON, - TOTAL_BALANCE_TEXT, -} from '../../../../wdio/screen-objects/testIDs/Components/Tokens.testIds'; import initialBackgroundState from '../../../util/test/initial-background-state.json'; import { strings } from '../../../../locales/i18n'; import AppConstants from '../../../../app/core/AppConstants'; import Routes from '../../../../app/constants/navigation/Routes'; +import { WalletViewSelectorsIDs } from '../../../../e2e/selectors/wallet/WalletView.selectors'; const mockEngine = Engine; @@ -190,17 +186,19 @@ describe('Tokens', () => { it('fiat balance must be defined', () => { const { getByTestId } = renderComponent(initialState); - expect(getByTestId(TOTAL_BALANCE_TEXT)).toBeDefined(); + expect( + getByTestId(WalletViewSelectorsIDs.TOTAL_BALANCE_TEXT), + ).toBeDefined(); }); it('portfolio button should render correctly', () => { const { getByTestId } = renderComponent(initialState); - expect(getByTestId(PORTFOLIO_BUTTON)).toBeDefined(); + expect(getByTestId(WalletViewSelectorsIDs.PORTFOLIO_BUTTON)).toBeDefined(); }); it('navigates to Portfolio url when portfolio button is pressed', () => { const { getByTestId } = renderComponent(initialState); - fireEvent.press(getByTestId(PORTFOLIO_BUTTON)); + fireEvent.press(getByTestId(WalletViewSelectorsIDs.PORTFOLIO_BUTTON)); expect(mockNavigate).toHaveBeenCalledWith(Routes.BROWSER.HOME, { params: { newTabUrl: `${AppConstants.PORTFOLIO.URL}/?metamaskEntry=mobile`, @@ -258,12 +256,12 @@ describe('Tokens', () => { it('renders stake button correctly', () => { const { getByTestId } = renderComponent(initialState); - expect(getByTestId(STAKE_BUTTON)).toBeDefined(); + expect(getByTestId(WalletViewSelectorsIDs.STAKE_BUTTON)).toBeDefined(); }); it('navigates to Portfolio Stake url when stake button is pressed', () => { const { getByTestId } = renderComponent(initialState); - fireEvent.press(getByTestId(STAKE_BUTTON)); + fireEvent.press(getByTestId(WalletViewSelectorsIDs.STAKE_BUTTON)); expect(mockNavigate).toHaveBeenCalledWith(Routes.BROWSER.HOME, { params: { newTabUrl: `${AppConstants.STAKE.URL}?metamaskEntry=mobile`, diff --git a/app/components/UI/Tokens/index.tsx b/app/components/UI/Tokens/index.tsx index 5e81b74159e..e182cc29e5f 100644 --- a/app/components/UI/Tokens/index.tsx +++ b/app/components/UI/Tokens/index.tsx @@ -78,12 +78,6 @@ import Icon, { IconSize, } from '../../../component-library/components/Icons/Icon'; -import { - PORTFOLIO_BUTTON, - STAKE_BUTTON, - TOTAL_BALANCE_TEXT, -} from '../../../../wdio/screen-objects/testIDs/Components/Tokens.testIds'; - import { BrowserTab, TokenI, TokensI } from './types'; import useRampNetwork from '../Ramp/hooks/useRampNetwork'; import Badge from '../../../component-library/components/Badges/Badge/Badge'; @@ -257,7 +251,7 @@ const Tokens: React.FC = ({ tokens }) => { return ( @@ -624,7 +618,7 @@ const Tokens: React.FC = ({ tokens }) => { {fiatBalance} @@ -635,7 +629,7 @@ const Tokens: React.FC = ({ tokens }) => { style={styles.buyButton} onPress={onOpenPortfolio} label={strings('asset_overview.portfolio_button')} - {...generateTestId(Platform, PORTFOLIO_BUTTON)} + testID={WalletViewSelectorsIDs.PORTFOLIO_BUTTON} endIconName={IconName.Export} /> diff --git a/app/components/Views/Wallet/index.tsx b/app/components/Views/Wallet/index.tsx index 597391cce20..a77d9d97d81 100644 --- a/app/components/Views/Wallet/index.tsx +++ b/app/components/Views/Wallet/index.tsx @@ -82,6 +82,7 @@ import { showNftFetchingLoadingIndicator as showNftFetchingLoadingIndicatorAction, } from '../../../reducers/collectibles'; import { getCurrentRoute } from '../../../reducers/navigation'; +import { WalletViewSelectorsIDs } from '../../../../e2e/selectors/wallet/WalletView.selectors'; const createStyles = ({ colors, typography }: Theme) => StyleSheet.create({ @@ -502,7 +503,10 @@ const Wallet = ({ assets = tokens; } return ( - + {!basicFunctionalityEnabled ? ( { await TabBarComponent.tapBrowser(); // Check that we are on the browser screen - await Assertions.checkIfVisible(await Browser.browserScreenID); + await Assertions.checkIfVisible(Browser.browserScreenID); }); it('should connect to the test dapp', async () => { @@ -91,7 +91,7 @@ describe(SmokeCore('Browser Tests'), () => { // Clear text & Navigate to URL await Browser.navigateToURL(ExternalSites.PHISHING_SITE); await Browser.waitForBrowserPageToLoad(); - await Assertions.checkIfVisible(await Browser.backToSafetyButton); + await Assertions.checkIfVisible(Browser.backToSafetyButton); await Browser.tapBackToSafetyButton(); // Check that we are on the browser screen diff --git a/e2e/specs/quarantine/contract-nickname.failing.js b/e2e/specs/quarantine/contract-nickname.failing.js index af2ae09bd88..cb055fe407a 100644 --- a/e2e/specs/quarantine/contract-nickname.failing.js +++ b/e2e/specs/quarantine/contract-nickname.failing.js @@ -189,7 +189,7 @@ describe('Adding Contract Nickname', () => { await TabBarComponent.tapActions(); await WalletActionsModal.tapSendButton(); // Make sure view with my accounts visible - await Assertions.checkIfVisible(await SendView.CurrentAccountElement); + await Assertions.checkIfVisible(SendView.CurrentAccountElement); }); it('should verify the contract nickname does not appear in send flow', async () => { @@ -209,7 +209,8 @@ describe('Adding Contract Nickname', () => { await TabBarComponent.tapActions(); await WalletActionsModal.tapSendButton(); // Make sure view with my accounts visible - await SendView.isMyAccountsVisible(); + //TODO: Update SendView.isMyAccountsVisible, this method does not exist + //await SendView.isMyAccountsVisible(); }); it('should verify the contract nickname does not appear in recents', async () => { diff --git a/e2e/specs/settings/addressbook-tests.spec.js b/e2e/specs/settings/addressbook-tests.spec.js index dad43cf0b10..87048797d12 100644 --- a/e2e/specs/settings/addressbook-tests.spec.js +++ b/e2e/specs/settings/addressbook-tests.spec.js @@ -52,20 +52,20 @@ describe(SmokeCore('Addressbook Tests'), () => { await TabBarComponent.tapActions(); await WalletActionsModal.tapSendButton(); // Make sure view with my accounts visible - await Assertions.checkIfVisible(await SendView.CurrentAccountElement); + await Assertions.checkIfVisible(SendView.CurrentAccountElement); }); it('should show invalid address error message', async () => { await SendView.inputAddress(TETHER_ADDRESS); //Input token address to test for error - await Assertions.checkIfVisible(await SendView.contractWarning); + await Assertions.checkIfVisible(SendView.contractWarning); await SendView.removeAddress(); }); it('should input a valid address to send to', async () => { await SendView.inputAddress(MYTH_ADDRESS); - await Assertions.checkIfVisible(await SendView.zeroBalanceWarning); + await Assertions.checkIfVisible(SendView.zeroBalanceWarning); }); it('should add a new address to address book via send flow', async () => { diff --git a/e2e/specs/settings/fiat-on-testnets.spec.js b/e2e/specs/settings/fiat-on-testnets.spec.js index 063198e0f3d..0b308dc8c88 100644 --- a/e2e/specs/settings/fiat-on-testnets.spec.js +++ b/e2e/specs/settings/fiat-on-testnets.spec.js @@ -10,11 +10,11 @@ import NetworkListModal from '../../pages/modals/NetworkListModal'; import WalletView from '../../pages/WalletView'; import NetworkEducationModal from '../../pages/modals/NetworkEducationModal'; import AdvancedSettingsView from '../../pages/Settings/AdvancedView'; -import { TOTAL_BALANCE_TEXT } from '../../../wdio/screen-objects/testIDs/Components/Tokens.testIds.js'; import FiatOnTestnetsModal from '../../pages/modals/FiatOnTestnetsModal.js'; import Assertions from '../../utils/Assertions.js'; import Matchers from '../../utils/Matchers.js'; import TestHelpers from '../../helpers.js'; +import { WalletViewSelectorsIDs } from '../../selectors/wallet/WalletView.selectors'; const SEPOLIA = CustomNetworks.Sepolia.providerConfig.nickname; @@ -40,7 +40,7 @@ describe(SmokeCore('Fiat On Testnets Setting'), () => { // Verify no fiat values displayed await Assertions.checkIfHasText( - Matchers.getElementByID(TOTAL_BALANCE_TEXT), + Matchers.getElementByID(WalletViewSelectorsIDs.TOTAL_BALANCE_TEXT), '$0', ); @@ -57,7 +57,7 @@ describe(SmokeCore('Fiat On Testnets Setting'), () => { // Verify fiat values are displayed await TabBarComponent.tapWallet(); await Assertions.checkIfElementNotToHaveText( - Matchers.getElementByID(TOTAL_BALANCE_TEXT), + Matchers.getElementByID(WalletViewSelectorsIDs.TOTAL_BALANCE_TEXT), '$0', ); }, diff --git a/e2e/specs/wallet/portfolio-connect-account.spec.js b/e2e/specs/wallet/portfolio-connect-account.spec.js new file mode 100644 index 00000000000..719b8d42bab --- /dev/null +++ b/e2e/specs/wallet/portfolio-connect-account.spec.js @@ -0,0 +1,66 @@ +'use strict'; +import { SmokeCore } from '../../tags'; +import TabBarComponent from '../../pages/TabBarComponent'; +import { loginToApp } from '../../viewHelper'; +import { + loadFixture, + startFixtureServer, + stopFixtureServer, +} from '../../fixtures/fixture-helper'; +import FixtureBuilder from '../../fixtures/fixture-builder'; +import TestHelpers from '../../helpers'; +import WalletView from '../../pages/WalletView'; +import { getFixturesServerPort } from '../../fixtures/utils'; +import FixtureServer from '../../fixtures/fixture-server'; +import BrowserView from '../../pages/Browser/BrowserView'; +import PortfolioHomePage from '../../pages/Browser/PortfolioHomePage'; +import Assertions from '../../utils/Assertions'; +import ConnectModal from '../../pages/modals/ConnectModal'; +const fixtureServer = new FixtureServer(); +describe(SmokeCore('Connect account to Portfolio'), () => { + beforeAll(async () => { + await TestHelpers.reverseServerPort(); + const fixture = new FixtureBuilder().withKeyringController().build(); + fixture.state.user.seedphraseBackedUp = false; + await startFixtureServer(fixtureServer); + await loadFixture(fixtureServer, { fixture }); + await device.launchApp({ + permissions: { notifications: 'YES' }, + launchArgs: { fixtureServerPort: `${getFixturesServerPort()}` }, + }); + }); + afterAll(async () => { + await stopFixtureServer(fixtureServer); + }); + + it('should connect wallet account to portfolio', async () => { + await loginToApp(); + await Assertions.checkIfVisible(WalletView.container); + await TabBarComponent.tapBrowser(); + await BrowserView.tapOpenAllTabsButton(); + await BrowserView.tapCloseTabsButton(); + await Assertions.checkIfVisible(BrowserView.noTabsMessage); + await TabBarComponent.tapWallet(); + await WalletView.tapPortfolio(); + await BrowserView.waitForBrowserPageToLoad(); + + try { + await PortfolioHomePage.closePrivacyModal(); + } catch { + /* eslint-disable no-console */ + console.log('The Portfolio privacy modal is not visible'); + } + await PortfolioHomePage.tapConnectMetaMask(); + await device.disableSynchronization(); + await ConnectModal.tapConnectButton(); + await device.enableSynchronization(); + }); + + it('should not open additional browser tabs to portfolio', async () => { + await Assertions.checkIfHasText(BrowserView.tabsNumber, '1'); + await TabBarComponent.tapWallet(); + await WalletView.tapPortfolio(); + await BrowserView.waitForBrowserPageToLoad(); + await Assertions.checkIfHasText(BrowserView.tabsNumber, '1'); + }); +}); diff --git a/e2e/utils/Assertions.js b/e2e/utils/Assertions.js index 6168e28076d..3b19d651385 100644 --- a/e2e/utils/Assertions.js +++ b/e2e/utils/Assertions.js @@ -99,7 +99,7 @@ class Assertions { /** * Check if an element with the specified ID does not have the specified label. * @param {Promise} elementId - The ID of the element to check. - * @param {string} text - The label content to check. + * @param {string} label - The label content to check. * @param {number} [timeout=TIMEOUT] - Timeout in milliseconds. */ static async checkIfElementDoesNotHaveLabel( diff --git a/e2e/utils/Matchers.js b/e2e/utils/Matchers.js index 8f9c1afc6aa..413a67c3433 100644 --- a/e2e/utils/Matchers.js +++ b/e2e/utils/Matchers.js @@ -89,7 +89,7 @@ class Matchers { * Get element by CSS selector. * @param {string} webviewID - The web ID of the browser webview * @param {string} selector - CSS selector to locate the element - * @return {Promise} - Resolves to the located element + * @return {Promise} - Resolves to the located element */ static async getElementByCSS(webviewID, selector) { @@ -101,17 +101,21 @@ class Matchers { * Get element by XPath. * @param {string} webviewID - The web ID of the browser webview * @param {string} xpath - XPath expression to locate the element - * @return {Promise} - Resolves to the located element + * @param {number} index - index to locate the webview (iOS only) + * @return {Promise} - Resolves to the located element */ - static async getElementByXPath(webviewID, xpath) { - const myWebView = web(by.id(webviewID)); + static async getElementByXPath(webviewID, xpath, index = 0) { + const myWebView = + device.getPlatform() === 'ios' + ? web(by.id(webviewID)).atIndex(index) + : web(by.id(webviewID)); return myWebView.element(by.web.xpath(xpath)).atIndex(0); } /** * Get element by href. * @param {string} webviewID - The web ID of the browser webview - * @param {string} xpath - XPath expression to locate the element - * @return {Promise} - Resolves to the located element + * @param {string} url - URL string to locate the element + * @return {Promise} - Resolves to the located element */ static async getElementByHref(webviewID, url) { const myWebView = web(by.id(webviewID)); diff --git a/wdio/screen-objects/testIDs/Components/Tokens.testIds.js b/wdio/screen-objects/testIDs/Components/Tokens.testIds.js index 430a9c8cd9d..e69de29bb2d 100644 --- a/wdio/screen-objects/testIDs/Components/Tokens.testIds.js +++ b/wdio/screen-objects/testIDs/Components/Tokens.testIds.js @@ -1,5 +0,0 @@ -export const TOTAL_BALANCE_TEXT = 'total-balance-text'; - -export const PORTFOLIO_BUTTON = 'portfolio-button'; - -export const STAKE_BUTTON = 'stake-button'; From d356b7e0baf9998d672f883ce6cf053f0cb769a7 Mon Sep 17 00:00:00 2001 From: Jonathan Ferreira <44679989+Jonathansoufer@users.noreply.github.com> Date: Mon, 1 Jul 2024 09:40:01 +0100 Subject: [PATCH 15/16] chore: bump snaps exec env version (#10187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Updates the Snaps execution environment version to the latest version. This had been missed in the previous PR to bump all of the other Snaps packages. ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [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. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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. --- app/lib/snaps/SnapsExecutionWebView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/snaps/SnapsExecutionWebView.tsx b/app/lib/snaps/SnapsExecutionWebView.tsx index db1aa6db3e2..4959a6cce2a 100644 --- a/app/lib/snaps/SnapsExecutionWebView.tsx +++ b/app/lib/snaps/SnapsExecutionWebView.tsx @@ -21,7 +21,7 @@ interface SnapsExecutionWebViewProps { let resolveGetWebView: (arg0: SnapsExecutionWebViewProps) => void; let rejectGetWebView: (error: NativeSyntheticEvent) => void; -const SNAPS_EE_URL = 'https://execution.metamask.io/webview/4.0.0/index.html'; +const SNAPS_EE_URL = 'https://execution.metamask.io/webview/6.5.0/index.html'; export const getSnapsWebViewPromise = new Promise( (resolve, reject) => { From 2478527e69cf3fe1278e0f6e059ca22062f50902 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Mon, 1 Jul 2024 15:15:46 +0200 Subject: [PATCH 16/16] feat: Revamp Snap connection screen (#10189) ## **Description** This PR reworks the Snaps connection screen to add the Snap icon and simplify the implementation. It also tweaks the fencing to allow connecting to Snaps with only the `preinstalled-snaps` flag on. ## **Screenshots/Recordings** --- .../InstallSnapApproval.constants.ts | 2 +- .../InstallSnapApproval.styles.ts | 6 +- .../InstallSnapApproval.tsx | 78 +++++++++++-------- .../InstallSnapApproval.types.ts | 3 +- .../InstallSnapConnectionRequest.tsx | 32 +++----- .../InstallSnapConnectionRequest/index.ts | 2 +- .../InstallSnapConnectionRequest.test.tsx | 34 +++++++- .../InstallSnapApproval/components/index.ts | 9 ++- .../Approvals/InstallSnapApproval/index.ts | 2 +- .../test/InstallSnapApproval.test.tsx | 52 ++++++++++--- app/components/Nav/Main/RootRPCMethodsUI.js | 4 +- .../UI/Snaps/SnapAvatar/SnapAvatar.styles.ts | 39 ++++++++++ .../UI/Snaps/SnapAvatar/SnapAvatar.test.tsx | 63 +++++++++++++++ .../UI/Snaps/SnapAvatar/SnapAvatar.tsx | 78 +++++++++++++++++++ app/core/EngineService/EngineService.ts | 2 +- app/selectors/snaps/permissionController.ts | 30 +++++++ app/selectors/snaps/snapController.ts | 37 +++++++++ locales/languages/en.json | 2 +- 18 files changed, 402 insertions(+), 73 deletions(-) create mode 100644 app/components/UI/Snaps/SnapAvatar/SnapAvatar.styles.ts create mode 100644 app/components/UI/Snaps/SnapAvatar/SnapAvatar.test.tsx create mode 100644 app/components/UI/Snaps/SnapAvatar/SnapAvatar.tsx create mode 100644 app/selectors/snaps/permissionController.ts create mode 100644 app/selectors/snaps/snapController.ts diff --git a/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.constants.ts b/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.constants.ts index 7cca5eabcff..f5f3353dd06 100644 --- a/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.constants.ts +++ b/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.constants.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(external-snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) export const SNAP_INSTALL_FLOW = 'snap-install-flow'; export const SNAP_INSTALL_OK = 'snap-install-ok'; ///: END:ONLY_INCLUDE_IF diff --git a/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.styles.ts b/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.styles.ts index 0788c1ba427..54af3331d92 100644 --- a/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.styles.ts +++ b/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.styles.ts @@ -1,4 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(external-snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import { StyleSheet } from 'react-native'; import { Theme } from '../../../util/theme/models'; import Device from '../../../util/device'; @@ -37,6 +37,10 @@ const styleSheet = (params: { theme: Theme }) => { snapCell: { marginVertical: 16, }, + snapAvatar: { + alignSelf: 'center', + marginTop: 16, + }, snapPermissionContainer: { maxHeight: 300, borderWidth: 1, diff --git a/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.tsx b/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.tsx index f30f44189e5..4438da9e273 100644 --- a/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.tsx +++ b/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.tsx @@ -1,32 +1,50 @@ -///: BEGIN:ONLY_INCLUDE_IF(external-snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import React, { useEffect, useState } from 'react'; import ApprovalModal from '../ApprovalModal'; import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; -import Logger from '../../../util/Logger'; import { SnapInstallState } from './InstallSnapApproval.types'; import { InstallSnapConnectionRequest, + ///: END:ONLY_INCLUDE_IF + ///: BEGIN:ONLY_INCLUDE_IF(external-snaps) InstallSnapError, InstallSnapPermissionsRequest, InstallSnapSuccess, + ///: END:ONLY_INCLUDE_IF + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) } from './components'; -import { SNAP_INSTALL_FLOW } from './InstallSnapApproval.constants'; import { ApprovalRequest } from '@metamask/approval-controller'; +import { useSelector } from 'react-redux'; +import { selectSnapsMetadata } from '../../../selectors/snaps/snapController'; +import { + WALLET_SNAP_PERMISSION_KEY, + stripSnapPrefix, +} from '@metamask/snaps-utils'; const InstallSnapApproval = () => { + const snapsMetadata = useSelector(selectSnapsMetadata); + const [installState, setInstallState] = useState< SnapInstallState | undefined >(undefined); - const [isFinished, setIsFinished] = useState(false); + ///: END:ONLY_INCLUDE_IF + ///: BEGIN:ONLY_INCLUDE_IF(external-snaps) const [installError, setInstallError] = useState( undefined, ); + ///: END:ONLY_INCLUDE_IF + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) const { approvalRequest, onConfirm, onReject } = useApprovalRequest(); useEffect(() => { if (approvalRequest) { - if (approvalRequest.type === ApprovalTypes.REQUEST_PERMISSIONS) { + if ( + approvalRequest.type === ApprovalTypes.REQUEST_PERMISSIONS && + Object.keys(approvalRequest?.requestData?.permissions).includes( + WALLET_SNAP_PERMISSION_KEY, + ) + ) { setInstallState(SnapInstallState.Confirm); } else if ( approvalRequest.type === ApprovalTypes.INSTALL_SNAP && @@ -41,14 +59,11 @@ const InstallSnapApproval = () => { // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any - const getSnapName = (request: ApprovalRequest): string => { + const getSnapId = (request: ApprovalRequest): string => { // We first look for the name inside the snapId approvalRequest data const snapId = request?.requestData?.snapId; if (typeof snapId === 'string') { - const colonIndex = snapId.indexOf(':'); - if (colonIndex !== -1) { - return snapId.substring(colonIndex + 1); - } + return snapId; } // If there is no snapId present in the approvalRequest data, we look for the name inside the snapIds caveat const snapIdsCaveat = @@ -57,15 +72,16 @@ const InstallSnapApproval = () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any (c: any) => c.type === 'snapIds', ); - // return an empty string if we can't find the snap name in the approvalRequest data - return snapIdsCaveat?.value ? Object.keys(snapIdsCaveat.value)[0] : ''; + return Object.keys(snapIdsCaveat.value)[0]; }; + const getSnapMetadata = (snapId: string) => + snapsMetadata[snapId] ?? { name: stripSnapPrefix(snapId) }; + if (!approvalRequest) return null; - const onInstallSnapFinished = () => { - setIsFinished(true); - }; + ///: END:ONLY_INCLUDE_IF + ///: BEGIN:ONLY_INCLUDE_IF(external-snaps) const onPermissionsConfirm = async () => { try { @@ -75,54 +91,55 @@ const InstallSnapApproval = () => { }); setInstallState(SnapInstallState.SnapInstalled); } catch (error) { - Logger.error( - error as Error, - `${SNAP_INSTALL_FLOW} Failed to install snap`, - ); setInstallError(error as Error); setInstallState(SnapInstallState.SnapInstallError); } }; + ///: END:ONLY_INCLUDE_IF + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) - if (!approvalRequest) return null; + if (!approvalRequest || installState === undefined) return null; - const snapName = getSnapName(approvalRequest); + const snapId = getSnapId(approvalRequest); + const snapName = getSnapMetadata(snapId).name; + // TODO: This component should support connecting to multiple Snaps at once. const renderModalContent = () => { switch (installState) { case SnapInstallState.Confirm: return ( ); + ///: END:ONLY_INCLUDE_IF + ///: BEGIN:ONLY_INCLUDE_IF(external-snaps) case SnapInstallState.AcceptPermissions: return ( ); case SnapInstallState.SnapInstalled: - return ( - - ); + return ; case SnapInstallState.SnapInstallError: return ( ); + ///: END:ONLY_INCLUDE_IF + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) default: return null; } @@ -131,10 +148,7 @@ const InstallSnapApproval = () => { const content = renderModalContent(); return content ? ( - + {content} ) : null; diff --git a/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.types.ts b/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.types.ts index 00315095b33..60b1b30372d 100644 --- a/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.types.ts +++ b/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.types.ts @@ -1,8 +1,9 @@ -///: BEGIN:ONLY_INCLUDE_IF(external-snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) interface InstallSnapFlowProps { // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any approvalRequest: any; + snapId: string; snapName: string; onConfirm: () => void; onCancel: () => void; diff --git a/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/InstallSnapConnectionRequest.tsx b/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/InstallSnapConnectionRequest.tsx index ad646e4b063..d7675b83a25 100644 --- a/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/InstallSnapConnectionRequest.tsx +++ b/app/components/Approvals/InstallSnapApproval/components/InstallSnapConnectionRequest/InstallSnapConnectionRequest.tsx @@ -1,6 +1,6 @@ -///: BEGIN:ONLY_INCLUDE_IF(external-snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import React, { useMemo } from 'react'; -import { ImageSourcePropType, View } from 'react-native'; +import { View } from 'react-native'; import { InstallSnapFlowProps } from '../../InstallSnapApproval.types'; import styleSheet from '../../InstallSnapApproval.styles'; import { strings } from '../../../../../../locales/i18n'; @@ -11,10 +11,6 @@ import Text, { import TagUrl from '../../../../../component-library/components/Tags/TagUrl'; import { getUrlObj, prefixUrlWithProtocol } from '../../../../../util/browser'; import { IconName } from '../../../../../component-library/components/Icons/Icon'; -import Cell, { - CellVariant, -} from '../../../../../component-library/components/Cells/Cell'; -import { AvatarVariant } from '../../../../../component-library/components/Avatars/Avatar'; import { ButtonSize, ButtonVariants, @@ -29,15 +25,18 @@ import { SNAP_INSTALL_CONNECT, SNAP_INSTALL_CONNECTION_REQUEST, } from './InstallSnapConnectionRequest.constants'; +import { useFavicon } from '../../../../hooks/useFavicon'; +import { SnapAvatar } from '../../../../UI/Snaps/SnapAvatar/SnapAvatar'; const InstallSnapConnectionRequest = ({ approvalRequest, + snapId, snapName, onConfirm, onCancel, }: Pick< InstallSnapFlowProps, - 'approvalRequest' | 'onConfirm' | 'onCancel' | 'snapName' + 'approvalRequest' | 'onConfirm' | 'onCancel' | 'snapId' | 'snapName' >) => { const { styles } = useStyles(styleSheet, {}); @@ -46,10 +45,7 @@ const InstallSnapConnectionRequest = ({ [approvalRequest.origin], ); - const favicon: ImageSourcePropType = useMemo(() => { - const iconUrl = `https://api.faviconkit.com/${origin}/50`; - return { uri: iconUrl }; - }, [origin]); + const favicon = useFavicon(origin); const urlWithProtocol = prefixUrlWithProtocol(origin); @@ -85,6 +81,11 @@ const InstallSnapConnectionRequest = ({ label={urlWithProtocol} iconName={secureIcon} /> + {strings('install_snap.description', { @@ -92,15 +93,6 @@ const InstallSnapConnectionRequest = ({ snap: snapName, })} - { expectsResult: false, }; + const mockStore = configureMockStore(); + const mockInitialState = { + settings: {}, + engine: { + backgroundState: { + SubjectMetadataController: { + subjectMetadata: {}, + }, + SnapController: { + snaps: {}, + }, + }, + }, + }; + const store = mockStore(mockInitialState); + + // TODO: Replace "any" with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const Wrapper = ({ children }: any) => ( + {children} + ); + const onConfirm = jest.fn(); const onCancel = jest.fn(); @@ -51,8 +74,10 @@ describe('InstallSnapConnectionRequest', () => { approvalRequest={requestPermissionsData} onConfirm={onConfirm} onCancel={onCancel} + snapId="npm:@metamask/bip32-example-snap" snapName="@metamask/bip32-example-snap" />, + { wrapper: Wrapper }, ); expect(getByTestId(SNAP_INSTALL_CONNECTION_REQUEST)).toBeDefined(); }); @@ -63,8 +88,10 @@ describe('InstallSnapConnectionRequest', () => { approvalRequest={requestPermissionsData} onConfirm={onConfirm} onCancel={onCancel} + snapId="npm:@metamask/bip32-example-snap" snapName="@metamask/bip32-example-snap" />, + { wrapper: Wrapper }, ); fireEvent.press(getByTestId(SNAP_INSTALL_CONNECT)); @@ -77,8 +104,10 @@ describe('InstallSnapConnectionRequest', () => { approvalRequest={requestPermissionsData} onConfirm={onConfirm} onCancel={onCancel} + snapId="npm:@metamask/bip32-example-snap" snapName="@metamask/bip32-example-snap" />, + { wrapper: Wrapper }, ); fireEvent.press(getByTestId(SNAP_INSTALL_CANCEL)); @@ -91,12 +120,13 @@ describe('InstallSnapConnectionRequest', () => { approvalRequest={requestPermissionsData} onConfirm={onConfirm} onCancel={onCancel} + snapId="npm:@metamask/bip32-example-snap" snapName="@metamask/bip32-example-snap" />, + { wrapper: Wrapper }, ); const expectedUrl = 'https://metamask.github.io'; expect(getByText(expectedUrl)).toBeTruthy(); }); }); -///: END:ONLY_INCLUDE_IF diff --git a/app/components/Approvals/InstallSnapApproval/components/index.ts b/app/components/Approvals/InstallSnapApproval/components/index.ts index bf7c5e92a05..94543d785e1 100644 --- a/app/components/Approvals/InstallSnapApproval/components/index.ts +++ b/app/components/Approvals/InstallSnapApproval/components/index.ts @@ -1,11 +1,18 @@ -///: BEGIN:ONLY_INCLUDE_IF(external-snaps) +/* eslint-disable import/prefer-default-export */ +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps) import { InstallSnapConnectionRequest } from './InstallSnapConnectionRequest'; +///: END:ONLY_INCLUDE_IF +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import { InstallSnapSuccess } from './InstallSnapSuccess'; import { InstallSnapError } from './InstallSnapError'; import { InstallSnapPermissionsRequest } from './InstallSnapPermissionsRequest'; +///: END:ONLY_INCLUDE_IF +///: BEGIN:ONLY_INCLUDE_IF(external-snaps) export { InstallSnapPermissionsRequest }; export { InstallSnapError }; export { InstallSnapSuccess }; +///: END:ONLY_INCLUDE_IF +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps) export { InstallSnapConnectionRequest }; ///: END:ONLY_INCLUDE_IF diff --git a/app/components/Approvals/InstallSnapApproval/index.ts b/app/components/Approvals/InstallSnapApproval/index.ts index fab24aeafb5..14ab6d8ca0f 100644 --- a/app/components/Approvals/InstallSnapApproval/index.ts +++ b/app/components/Approvals/InstallSnapApproval/index.ts @@ -1,3 +1,3 @@ -///: BEGIN:ONLY_INCLUDE_IF(external-snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) export { default } from './InstallSnapApproval'; ///: END:ONLY_INCLUDE_IF diff --git a/app/components/Approvals/InstallSnapApproval/test/InstallSnapApproval.test.tsx b/app/components/Approvals/InstallSnapApproval/test/InstallSnapApproval.test.tsx index 941d1f51b7e..ea9775037b0 100644 --- a/app/components/Approvals/InstallSnapApproval/test/InstallSnapApproval.test.tsx +++ b/app/components/Approvals/InstallSnapApproval/test/InstallSnapApproval.test.tsx @@ -1,8 +1,9 @@ -///: BEGIN:ONLY_INCLUDE_IF(external-snaps) import React from 'react'; import { render, fireEvent, waitFor } from '@testing-library/react-native'; -import InstallSnapApproval from '../InstallSnapApproval'; import { ApprovalRequest } from '@metamask/approval-controller'; +import configureMockStore from 'redux-mock-store'; +import { Provider } from 'react-redux'; +import InstallSnapApproval from '../InstallSnapApproval'; import useApprovalRequest from '../../../Views/confirmations/hooks/useApprovalRequest'; import { SNAP_INSTALL_CANCEL, @@ -127,20 +128,46 @@ describe('InstallSnapApprovalFlow', () => { expectsResult: false, }; + const mockStore = configureMockStore(); + const mockInitialState = { + settings: {}, + engine: { + backgroundState: { + SubjectMetadataController: { + subjectMetadata: {}, + }, + SnapController: { + snaps: {}, + }, + }, + }, + }; + const store = mockStore(mockInitialState); + + // TODO: Replace "any" with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const Wrapper = ({ children }: any) => ( + {children} + ); + afterEach(() => { jest.clearAllMocks(); }); it('renders InstallSnapConnectionRequest component initially when approval type is wallet_requestPermissions', () => { mockApprovalRequest(requestPermissionsData); - const { getByTestId } = render(); + const { getByTestId } = render(, { + wrapper: Wrapper, + }); const connectionRequest = getByTestId(SNAP_INSTALL_CONNECTION_REQUEST); expect(connectionRequest).toBeDefined(); }); it('renders InstallSnapPermissionsRequest when approval type is wallet_installSnap', async () => { mockApprovalRequest(installSnapData); - const { findByTestId } = render(); + const { findByTestId } = render(, { + wrapper: Wrapper, + }); const permissionsRequest = await findByTestId( SNAP_INSTALL_PERMISSIONS_REQUEST, ); @@ -149,7 +176,9 @@ describe('InstallSnapApprovalFlow', () => { it('calls onConfirm when Approve button is pressed in InstallSnapPermissionsRequest', async () => { mockApprovalRequest(installSnapData); - const { getByTestId } = render(); + const { getByTestId } = render(, { + wrapper: Wrapper, + }); const permissionsApproveButton = getByTestId( SNAP_INSTALL_PERMISSIONS_REQUEST_APPROVE, ); @@ -159,7 +188,9 @@ describe('InstallSnapApprovalFlow', () => { it('renders InstallSnapSuccess on successful installation', async () => { mockApprovalRequest(installSnapData); - const { getByTestId, findByTestId } = render(); + const { getByTestId, findByTestId } = render(, { + wrapper: Wrapper, + }); const permissionsRequest = await findByTestId( SNAP_INSTALL_PERMISSIONS_REQUEST, ); @@ -178,7 +209,9 @@ describe('InstallSnapApprovalFlow', () => { throw new Error('Installation error'); }); - const { getByTestId, findByTestId } = render(); + const { getByTestId, findByTestId } = render(, { + wrapper: Wrapper, + }); const permissionsRequest = getByTestId(SNAP_INSTALL_PERMISSIONS_REQUEST); expect(permissionsRequest).toBeDefined(); const permissionsConfirmButton = getByTestId( @@ -192,10 +225,11 @@ describe('InstallSnapApprovalFlow', () => { it('calls onCancel on cancel button click', () => { mockApprovalRequest(installSnapData); - const { getByTestId } = render(); + const { getByTestId } = render(, { + wrapper: Wrapper, + }); const cancelButton = getByTestId(SNAP_INSTALL_CANCEL); fireEvent.press(cancelButton); expect(onReject).toHaveBeenCalledTimes(1); }); }); -///: END:ONLY_INCLUDE_IF diff --git a/app/components/Nav/Main/RootRPCMethodsUI.js b/app/components/Nav/Main/RootRPCMethodsUI.js index 5a59acd5385..2b331b13333 100644 --- a/app/components/Nav/Main/RootRPCMethodsUI.js +++ b/app/components/Nav/Main/RootRPCMethodsUI.js @@ -67,7 +67,7 @@ import { selectShouldUseSmartTransaction } from '../../../selectors/smartTransac import { STX_NO_HASH_ERROR } from '../../../util/smart-transactions/smart-publish-hook'; import { getSmartTransactionMetricsProperties } from '../../../util/smart-transactions'; -///: BEGIN:ONLY_INCLUDE_IF(external-snaps) +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import InstallSnapApproval from '../../Approvals/InstallSnapApproval'; ///: END:ONLY_INCLUDE_IF @@ -487,7 +487,7 @@ const RootRPCMethodsUI = (props) => { { - ///: BEGIN:ONLY_INCLUDE_IF(external-snaps) + ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) } { diff --git a/app/components/UI/Snaps/SnapAvatar/SnapAvatar.styles.ts b/app/components/UI/Snaps/SnapAvatar/SnapAvatar.styles.ts new file mode 100644 index 00000000000..04571d4ea74 --- /dev/null +++ b/app/components/UI/Snaps/SnapAvatar/SnapAvatar.styles.ts @@ -0,0 +1,39 @@ +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) +import { StyleSheet } from 'react-native'; +import { Theme } from '../../../../util/theme/models'; + +/** + * + * @param params Style sheet params. + * @param params.theme App theme from ThemeContext. + * @param params.vars Inputs that the style sheet depends on. + * @returns StyleSheet object. + */ +const styleSheet = (params: { theme: Theme }) => { + const { theme } = params; + const { colors } = theme; + return StyleSheet.create({ + avatar: { + backgroundColor: colors.background.alternativeHover, + }, + fallbackAvatar: { + backgroundColor: colors.background.alternativeHover, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + color: colors.text.alternative, + }, + fallbackAvatarText: { + textTransform: 'uppercase', + }, + badge: { + backgroundColor: colors.info.default, + color: colors.info.inverse, + borderColor: colors.background.alternative, + borderWidth: 2, + }, + }); +}; + +export default styleSheet; +///: END:ONLY_INCLUDE_IF diff --git a/app/components/UI/Snaps/SnapAvatar/SnapAvatar.test.tsx b/app/components/UI/Snaps/SnapAvatar/SnapAvatar.test.tsx new file mode 100644 index 00000000000..ffabfea630f --- /dev/null +++ b/app/components/UI/Snaps/SnapAvatar/SnapAvatar.test.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import configureMockStore from 'redux-mock-store'; +import { Provider } from 'react-redux'; +import { render } from '@testing-library/react-native'; +import { SnapAvatar } from './SnapAvatar'; + +describe('SnapAvatar', () => { + const mockStore = configureMockStore(); + const mockInitialState = { + settings: {}, + engine: { + backgroundState: { + SubjectMetadataController: { + subjectMetadata: { + 'npm:@metamask/bip32-example-snap': { + extensionId: null, + iconUrl: null, + name: 'BIP-32 Example Snap', + origin: 'npm:@metamask/bip32-example-snap', + subjectType: 'snap', + svgIcon: '', + version: '1.0.0', + }, + }, + }, + }, + }, + }; + const store = mockStore(mockInitialState); + + // TODO: Replace "any" with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const Wrapper = ({ children }: any) => ( + {children} + ); + + it('renders an icon using subject metadata', () => { + const { getByTestId } = render( + , + { + wrapper: Wrapper, + }, + ); + expect(getByTestId('snap-avatar-icon')).toBeDefined(); + }); + + it('falls back to the first non symbol in the snap name', () => { + const { getByTestId, getByText } = render( + , + { + wrapper: Wrapper, + }, + ); + expect(getByText('B')).toBeDefined(); + expect(getByTestId('snap-avatar-fallback')).toBeDefined(); + }); +}); diff --git a/app/components/UI/Snaps/SnapAvatar/SnapAvatar.tsx b/app/components/UI/Snaps/SnapAvatar/SnapAvatar.tsx new file mode 100644 index 00000000000..b8602c81881 --- /dev/null +++ b/app/components/UI/Snaps/SnapAvatar/SnapAvatar.tsx @@ -0,0 +1,78 @@ +/* eslint-disable react/prop-types */ +///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) +import React from 'react'; +import { useSelector } from 'react-redux'; +import AvatarFavicon from '../../../../component-library/components/Avatars/Avatar/variants/AvatarFavicon'; +import { selectTargetSubjectMetadata } from '../../../../selectors/snaps/permissionController'; +import BadgeWrapper from '../../../../component-library/components/Badges/BadgeWrapper'; +import { BadgePosition } from '../../../../component-library/components/Badges/BadgeWrapper/BadgeWrapper.types'; +import AvatarIcon from '../../../../component-library/components/Avatars/Avatar/variants/AvatarIcon'; +import { + IconColor, + IconName, +} from '../../../..//component-library/components/Icons/Icon'; +import AvatarBase from '../../../../component-library/components/Avatars/Avatar/foundation/AvatarBase'; +import Text from '../../../../component-library/components/Texts/Text'; +import { useStyles } from '../../../../component-library/hooks'; +import styleSheet from './SnapAvatar.styles'; +import { RootState } from '../../../../reducers'; +import { AvatarSize } from '../../../../component-library/components/Avatars/Avatar'; +import { ViewStyle } from 'react-native'; + +const getAvatarFallbackLetter = (subjectName: string) => + subjectName?.match(/[a-z0-9]/iu)?.[0]; + +export interface SnapAvatarProps { + snapId: string; + snapName: string; // TODO: Don't pass this in, derive it in the component instead. + style?: ViewStyle; +} + +export const SnapAvatar: React.FunctionComponent = ({ + snapId, + snapName, + style, +}) => { + const { styles } = useStyles(styleSheet, {}); + + const subjectMetadata = useSelector((state: RootState) => + selectTargetSubjectMetadata(state, snapId), + ); + + const iconUrl = subjectMetadata?.iconUrl; + + const fallbackAvatar = getAvatarFallbackLetter(snapName); + + return ( + + } + badgePosition={BadgePosition.BottomRight} + > + {iconUrl ? ( + + ) : ( + + {fallbackAvatar} + + )} + + ); +}; +///: END:ONLY_INCLUDE_IF diff --git a/app/core/EngineService/EngineService.ts b/app/core/EngineService/EngineService.ts index 5698e99ab44..ecec1d552cf 100644 --- a/app/core/EngineService/EngineService.ts +++ b/app/core/EngineService/EngineService.ts @@ -99,7 +99,7 @@ class EngineService { key: `${engine.context.SnapController.name}:stateChange`, }, { - name: 'subjectMetadataController', + name: 'SubjectMetadataController', key: `${engine.context.SubjectMetadataController.name}:stateChange`, }, ///: END:ONLY_INCLUDE_IF diff --git a/app/selectors/snaps/permissionController.ts b/app/selectors/snaps/permissionController.ts new file mode 100644 index 00000000000..08ae3291e28 --- /dev/null +++ b/app/selectors/snaps/permissionController.ts @@ -0,0 +1,30 @@ +import { RootState } from '../../reducers'; +import { memoize } from 'lodash'; +import { SubjectType } from '@metamask/permission-controller'; + +export const selectPermissionControllerState = (state: RootState) => + state.engine.backgroundState.PermissionController; + +export const selectSubjectMetadataControllerState = (state: RootState) => + state.engine.backgroundState.SubjectMetadataController; + +const getEmbeddableSvg = memoize( + (svgString) => `data:image/svg+xml;utf8,${encodeURIComponent(svgString)}`, +); + +function selectSubjectMetadata(state: RootState) { + return selectSubjectMetadataControllerState(state).subjectMetadata; +} + +export function selectTargetSubjectMetadata(state: RootState, origin: string) { + const metadata = selectSubjectMetadata(state)[origin]; + + if (metadata?.subjectType === SubjectType.Snap) { + return { + ...metadata, + iconUrl: metadata.svgIcon ? getEmbeddableSvg(metadata.svgIcon) : null, + }; + } + + return metadata; +} diff --git a/app/selectors/snaps/snapController.ts b/app/selectors/snaps/snapController.ts new file mode 100644 index 00000000000..25e8053ada7 --- /dev/null +++ b/app/selectors/snaps/snapController.ts @@ -0,0 +1,37 @@ +import { createSelector } from 'reselect'; +import { RootState } from '../../reducers'; +import { createDeepEqualSelector } from '../util'; +import { getLocalizedSnapManifest } from '@metamask/snaps-utils'; + +// TODO: Filter out huge values +export const selectSnapControllerState = (state: RootState) => + state.engine.backgroundState.SnapController; + +export const selectSnaps = createSelector( + selectSnapControllerState, + (controller) => controller.snaps, +); + +export const selectSnapsMetadata = createDeepEqualSelector( + selectSnaps, + (snaps) => + Object.values(snaps).reduce< + Record + >((snapsMetadata, snap) => { + const snapId = snap.id; + const manifest = snap.localizationFiles + ? getLocalizedSnapManifest( + snap.manifest, + // TODO: Use actual locale here. + 'en', + snap.localizationFiles, + ) + : snap.manifest; + + snapsMetadata[snapId] = { + name: manifest.proposedName, + description: manifest.description, + }; + return snapsMetadata; + }, {}), +); diff --git a/locales/languages/en.json b/locales/languages/en.json index 4a6875a0bc8..c78ad3a84ca 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -3075,7 +3075,7 @@ }, "install_snap": { "title": "Connection request", - "description": "{{origin}} wants to download and connect with {{snap}}. Make sure you trust the authors before you proceed.", + "description": "{{origin}} wants to use {{snap}}.", "permissions_request_title": "Permissions request", "permissions_request_description": "{{origin}} wants to install {{snap}}, which is requesting the following permissions.", "approve_permissions": "Approve",