diff --git a/app/scripts/lib/snap-keyring/snap-keyring.ts b/app/scripts/lib/snap-keyring/snap-keyring.ts index 25fa4456d6a0..17683dee0a52 100644 --- a/app/scripts/lib/snap-keyring/snap-keyring.ts +++ b/app/scripts/lib/snap-keyring/snap-keyring.ts @@ -11,7 +11,6 @@ import { SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES } from '../../../../shared/cons import { t } from '../../translate'; import MetamaskController from '../../metamask-controller'; import { IconName } from '../../../../ui/components/component-library/icon'; -import { getSnapName } from './utils/getSnapName'; import { isBlockedUrl } from './utils/isBlockedUrl'; import { showSuccess, showError } from './utils/showResult'; import { SnapKeyringBuilderMessenger } from './types'; @@ -40,6 +39,8 @@ export const getAccountsBySnapId = async ( * @param setSelectedAccountHelper - A function to update current selected account. * @param removeAccountHelper - A function to help remove an account based on its address. * @param trackEvent - A function to track MetaMetrics events. + * @param getSnapName - A function to get a snap's localized + * (or non-localized if there are no localization files) name from its manifest. * @returns The constructed SnapKeyring builder instance with the following methods: * - `saveState`: Persists all keyrings in the keyring controller. * - `addAccount`: Initiates the process of adding an account with user confirmation and handling the user input. @@ -55,6 +56,7 @@ export const snapKeyringBuilder = ( payload: Record, options?: Record, ) => void, + getSnapName: (snapId: string) => string, ) => { const builder = (() => { return new SnapKeyring(getSnapController() as any, { @@ -111,7 +113,7 @@ export const snapKeyringBuilder = ( snapId: string, handleUserInput: (accepted: boolean) => Promise, ) => { - const snapName = getSnapName(controllerMessenger, snapId); + const snapName = getSnapName(snapId); const { id: addAccountApprovalId } = controllerMessenger.call( 'ApprovalController:startFlow', ); @@ -237,7 +239,7 @@ export const snapKeyringBuilder = ( snapId: string, handleUserInput: (accepted: boolean) => Promise, ) => { - const snapName = getSnapName(controllerMessenger, snapId); + const snapName = getSnapName(snapId); const { id: removeAccountApprovalId } = controllerMessenger.call( 'ApprovalController:startFlow', ); diff --git a/app/scripts/lib/snap-keyring/utils/getSnapName.test.ts b/app/scripts/lib/snap-keyring/utils/getSnapName.test.ts deleted file mode 100644 index c010f61c5756..000000000000 --- a/app/scripts/lib/snap-keyring/utils/getSnapName.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { SubjectMetadataController } from '@metamask/permission-controller'; -import { ControllerMessenger } from '@metamask/base-controller'; -import { getSnapName } from './getSnapName'; - -const MOCK_SNAP_NAME = 'snap.test'; -const MOCK_SNAP_PREFIX = 'local'; -const MOCK_SNAP_ORIGIN = `${MOCK_SNAP_PREFIX}:${MOCK_SNAP_NAME}`; - -function setupControllerMessenger() { - const controllerMessenger = new ControllerMessenger(); - - // The SubjectMetadataController also requires some methods from PermissionController - controllerMessenger.registerActionHandler( - 'PermissionController:hasPermissions', - // Not important for our tests - (_) => true as never, - ); - - const subjectMetadata = [ - { - origin: MOCK_SNAP_ORIGIN, - name: MOCK_SNAP_NAME, - }, - ]; - const subjectMetadataController = new SubjectMetadataController({ - messenger: controllerMessenger.getRestricted({ - name: 'SubjectMetadataController', - allowedActions: [ - 'PermissionController:hasPermissions' as unknown as never, - ], - }), - subjectCacheLimit: subjectMetadata.length, - }); - subjectMetadata.forEach((x) => - subjectMetadataController.addSubjectMetadata(x), - ); - - return controllerMessenger; -} - -function getSnapKeyringBuilderMessenger( - controllerMessenger: any, - allowedActions: string[], -) { - return controllerMessenger.getRestricted({ - name: 'SnapKeyringBuilder', - allowedActions, - }); -} - -describe('getSnapName', () => { - const controllerMessenger = setupControllerMessenger(); - const messenger = getSnapKeyringBuilderMessenger(controllerMessenger, [ - 'SubjectMetadataController:getSubjectMetadata', - ]); - - it('should return the snap name', () => { - expect(getSnapName(messenger, MOCK_SNAP_ORIGIN)).toBe(MOCK_SNAP_NAME); - }); - - it('should return the snap name even if the SubjectMetadata is not known', () => { - const snapName = 'unknown-snap.test'; - const snapId = `${MOCK_SNAP_PREFIX}:${snapName}`; - - expect(getSnapName(messenger, snapId)).toBe(snapName); - }); - - it('should return null if snapId is null', () => { - // In our case we should probably never get a `null` origin (== snapId), but for the sake - // of completeness we make sure this is a well defined behavior - expect(getSnapName(messenger, null as unknown as string)).toBe(null); - }); - - it('should return null if snapId is undefined', () => { - // Same here - expect(getSnapName(messenger, undefined as unknown as string)).toBe(null); - }); - - it('should raise if the SubjectMetadata cannot be retrieved', () => { - // Disallow calls to `:getSubjectMetadata` - const unallowedMessenger = getSnapKeyringBuilderMessenger( - controllerMessenger, - [], - ); - - expect(() => { - return getSnapName(unallowedMessenger, MOCK_SNAP_ORIGIN); - }).toThrowError(); - }); -}); diff --git a/app/scripts/lib/snap-keyring/utils/getSnapName.ts b/app/scripts/lib/snap-keyring/utils/getSnapName.ts deleted file mode 100644 index 6ca98b205c6c..000000000000 --- a/app/scripts/lib/snap-keyring/utils/getSnapName.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { getSnapName as getSnapNameFromSubjectMetadata } from '../../../../../ui/helpers/utils/util'; -import { SnapKeyringBuilderMessenger } from '../types'; - -export const getSnapName = ( - controllerMessenger: SnapKeyringBuilderMessenger, - snapId: string, -): string => { - const subjectMetadata = controllerMessenger.call( - 'SubjectMetadataController:getSubjectMetadata', - snapId, - ); - - return getSnapNameFromSubjectMetadata(snapId, subjectMetadata); -}; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 89905caaeffa..14c79751ad0b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -144,6 +144,13 @@ import { TransactionType, } from '@metamask/transaction-controller'; +///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) +import { + getLocalizedSnapManifest, + stripSnapPrefix, +} from '@metamask/snaps-utils'; +///: END:ONLY_INCLUDE_IF + ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils'; ///: END:ONLY_INCLUDE_IF @@ -989,7 +996,6 @@ export default class MetamaskController extends EventEmitter { 'PhishingController:test', 'PhishingController:maybeUpdateState', 'KeyringController:getAccounts', - 'SubjectMetadataController:getSubjectMetadata', 'AccountsController:setSelectedAccount', 'AccountsController:getAccountByAddress', ], @@ -1003,6 +1009,31 @@ export default class MetamaskController extends EventEmitter { await this.accountsController.updateAccounts(); }; + const getSnapName = (id) => { + if (!id) { + return null; + } + + const currentLocale = this.getLocale(); + const { snaps } = this.snapController.state; + const snap = snaps[id]; + + if (!snap) { + return stripSnapPrefix(id); + } + + if (snap.localizationFiles) { + const localizedManifest = getLocalizedSnapManifest( + snap.manifest, + currentLocale, + snap.localizationFiles, + ); + return localizedManifest.proposedName; + } + + return snap.manifest.proposedName; + }; + additionalKeyrings.push( snapKeyringBuilder( snapKeyringBuildMessenger, @@ -1011,6 +1042,7 @@ export default class MetamaskController extends EventEmitter { (address) => this.preferencesController.setSelectedAddress(address), (address) => this.removeAccount(address), this.metaMetricsController.trackEvent.bind(this.metaMetricsController), + getSnapName, ), ); diff --git a/shared/constants/snaps.ts b/shared/constants/snaps.ts deleted file mode 100644 index b94ff7c7ab31..000000000000 --- a/shared/constants/snaps.ts +++ /dev/null @@ -1,43 +0,0 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) -type SnapsMetadata = { - [snapId: string]: { - name: string; - }; -}; - -// If a Snap ID is present in this object, its metadata is used before the info -// of the snap is fetched. Ideally this information would be fetched from the -// snap registry, but this is a temporary solution. -export const SNAPS_METADATA: SnapsMetadata = { - 'npm:@metamask/test-snap-error': { - name: 'Error Test Snap', - }, - 'npm:@metamask/test-snap-confirm': { - name: 'Confirm Test Snap', - }, - 'npm:@metamask/test-snap-dialog': { - name: 'Dialog Test Snap', - }, - 'npm:@metamask/test-snap-bip44': { - name: 'BIP-44 Test Snap', - }, - 'npm:@metamask/test-snap-managestate': { - name: 'Manage State Test Snap', - }, - 'npm:@metamask/test-snap-notification': { - name: 'Notification Test Snap', - }, - 'npm:@metamask/test-snap-bip32': { - name: 'BIP-32 Test Snap', - }, - 'npm:@metamask/test-snap-insights': { - name: 'Insights Test Snap', - }, - 'npm:@metamask/test-snap-rpc': { - name: 'RPC Test Snap', - }, - 'npm:@metamask/test-snap-cronjob': { - name: 'Cronjob Test Snap', - }, -}; -///: END:ONLY_INCLUDE_IF diff --git a/test/data/mock-state.json b/test/data/mock-state.json index 6e8b637c3778..e016195e3a23 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -148,6 +148,216 @@ "version": "5.1.2" } ] + }, + "npm:@metamask/test-snap-bip32": { + "id": "npm:@metamask/test-snap-bip32", + "origin": "npm:@metamask/test-snap-bip32", + "version": "5.1.2", + "iconUrl": null, + "initialPermissions": { + "endowment:ethereum-provider": {} + }, + "manifest": { + "description": "An example Snap that signs messages using BLS.", + "proposedName": "BIP-32 Test Snap", + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/test-snaps.git" + }, + "source": { + "location": { + "npm": { + "filePath": "dist/bundle.js", + "packageName": "@metamask/test-snap-bip32", + "registry": "https://registry.npmjs.org" + } + }, + "shasum": "L1k+dT9Q+y3KfIqzaH09MpDZVPS9ZowEh9w01ZMTWMU=" + }, + "version": "5.1.2" + }, + "versionHistory": [ + { + "date": 1680686075921, + "origin": "https://metamask.github.io", + "version": "5.1.2" + } + ] + }, + "npm:@metamask/test-snap-getEntropy": { + "id": "npm:@metamask/test-snap-getEntropy", + "origin": "npm:@metamask/test-snap-getEntropy", + "version": "5.1.2", + "iconUrl": null, + "initialPermissions": { + "endowment:ethereum-provider": {} + }, + "manifest": { + "description": "An example Snap that can derive snap specific entropy.", + "proposedName": "Get Entropy Test Snap", + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/test-snaps.git" + }, + "source": { + "location": { + "npm": { + "filePath": "dist/bundle.js", + "packageName": "@metamask/test-snap-getEntropy", + "registry": "https://registry.npmjs.org" + } + }, + "shasum": "L1k+dT9Q+y3KfIqzaH09MpDZVPS9ZowEh9w01ZMTWMU=" + }, + "version": "5.1.2" + }, + "versionHistory": [ + { + "date": 1680686075921, + "origin": "https://metamask.github.io", + "version": "5.1.2" + } + ] + }, + "npm:@metamask/test-snap-networkAccess": { + "id": "npm:@metamask/test-snap-networkAccess", + "origin": "npm:@metamask/test-snap-networkAccess", + "version": "5.1.2", + "iconUrl": null, + "initialPermissions": { + "endowment:ethereum-provider": {} + }, + "manifest": { + "description": "An example Snap that has network access.", + "proposedName": "Network Access Test Snap", + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/test-snaps.git" + }, + "source": { + "location": { + "npm": { + "filePath": "dist/bundle.js", + "packageName": "@metamask/test-snap-networkAccess", + "registry": "https://registry.npmjs.org" + } + }, + "shasum": "L1k+dT9Q+y3KfIqzaH09MpDZVPS9ZowEh9w01ZMTWMU=" + }, + "version": "5.1.2" + }, + "versionHistory": [ + { + "date": 1680686075921, + "origin": "https://metamask.github.io", + "version": "5.1.2" + } + ] + }, + "npm:@metamask/test-snap-wasm": { + "id": "npm:@metamask/test-snap-wasm", + "origin": "npm:@metamask/test-snap-wasm", + "version": "5.1.2", + "iconUrl": null, + "initialPermissions": { + "endowment:ethereum-provider": {} + }, + "manifest": { + "description": "An example Snap that has WASM access.", + "proposedName": "WASM Test Snap", + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/test-snaps.git" + }, + "source": { + "location": { + "npm": { + "filePath": "dist/bundle.js", + "packageName": "@metamask/test-snap-wasm", + "registry": "https://registry.npmjs.org" + } + }, + "shasum": "L1k+dT9Q+y3KfIqzaH09MpDZVPS9ZowEh9w01ZMTWMU=" + }, + "version": "5.1.2" + }, + "versionHistory": [ + { + "date": 1680686075921, + "origin": "https://metamask.github.io", + "version": "5.1.2" + } + ] + }, + "npm:@metamask/test-snap-notify": { + "id": "npm:@metamask/test-snap-notify", + "origin": "npm:@metamask/test-snap-notify", + "version": "5.1.2", + "iconUrl": null, + "initialPermissions": { + "endowment:ethereum-provider": {} + }, + "manifest": { + "description": "An example Snap that can send notifications.", + "proposedName": "Notification Test Snap", + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/test-snaps.git" + }, + "source": { + "location": { + "npm": { + "filePath": "dist/bundle.js", + "packageName": "@metamask/test-snap-notify", + "registry": "https://registry.npmjs.org" + } + }, + "shasum": "L1k+dT9Q+y3KfIqzaH09MpDZVPS9ZowEh9w01ZMTWMU=" + }, + "version": "5.1.2" + }, + "versionHistory": [ + { + "date": 1680686075921, + "origin": "https://metamask.github.io", + "version": "5.1.2" + } + ] + }, + "npm:@metamask/test-snap-dialog": { + "id": "npm:@metamask/test-snap-dialog", + "origin": "npm:@metamask/test-snap-dialog", + "version": "5.1.2", + "iconUrl": null, + "initialPermissions": { + "endowment:ethereum-provider": {} + }, + "manifest": { + "description": "An example Snap that can send dialog prompts.", + "proposedName": "Dialog Test Snap", + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/test-snaps.git" + }, + "source": { + "location": { + "npm": { + "filePath": "dist/bundle.js", + "packageName": "@metamask/test-snap-dialog", + "registry": "https://registry.npmjs.org" + } + }, + "shasum": "L1k+dT9Q+y3KfIqzaH09MpDZVPS9ZowEh9w01ZMTWMU=" + }, + "version": "5.1.2" + }, + "versionHistory": [ + { + "date": 1680686075921, + "origin": "https://metamask.github.io", + "version": "5.1.2" + } + ] } }, "preferences": { diff --git a/ui/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js b/ui/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js index 2f21022e6fb8..719fc96f06ad 100644 --- a/ui/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js +++ b/ui/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js @@ -37,7 +37,7 @@ export default class PermissionPageContainerContent extends PureComponent {
); diff --git a/ui/components/app/permissions-connect-permission-list/permissions-connect-permission-list.js b/ui/components/app/permissions-connect-permission-list/permissions-connect-permission-list.js index 6d0ccfcb77d9..a8d07ce4ce77 100644 --- a/ui/components/app/permissions-connect-permission-list/permissions-connect-permission-list.js +++ b/ui/components/app/permissions-connect-permission-list/permissions-connect-permission-list.js @@ -1,10 +1,13 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; import { getRightIcon, getWeightedPermissions, } from '../../../helpers/utils/permission'; import { useI18nContext } from '../../../hooks/useI18nContext'; +import { getSnapsMetadata } from '../../../selectors'; +import { getSnapName } from '../../../helpers/utils/util'; /** * Get one or more permission descriptions for a permission name. @@ -27,20 +30,24 @@ function getDescriptionNode(permission, index) { export default function PermissionsConnectPermissionList({ permissions, - targetSubjectMetadata, + subjectName, }) { const t = useI18nContext(); + const snapsMetadata = useSelector(getSnapsMetadata); return (
- {getWeightedPermissions(t, permissions, targetSubjectMetadata).map( - getDescriptionNode, - )} + {getWeightedPermissions({ + t, + permissions, + getSubjectName: getSnapName(snapsMetadata), + subjectName, + }).map(getDescriptionNode)}
); } PermissionsConnectPermissionList.propTypes = { permissions: PropTypes.object.isRequired, - targetSubjectMetadata: PropTypes.object.isRequired, + subjectName: PropTypes.string.isRequired, }; diff --git a/ui/components/app/snaps/snap-authorship-expanded/snap-authorship-expanded.js b/ui/components/app/snaps/snap-authorship-expanded/snap-authorship-expanded.js index 39ac1e0a0038..8ab9c3a299ca 100644 --- a/ui/components/app/snaps/snap-authorship-expanded/snap-authorship-expanded.js +++ b/ui/components/app/snaps/snap-authorship-expanded/snap-authorship-expanded.js @@ -19,13 +19,10 @@ import { TextColor, TextVariant, } from '../../../../helpers/constants/design-system'; -import { formatDate, getSnapName } from '../../../../helpers/utils/util'; +import { formatDate } from '../../../../helpers/utils/util'; import { useI18nContext } from '../../../../hooks/useI18nContext'; import { useOriginMetadata } from '../../../../hooks/useOriginMetadata'; -import { - getSnapRegistryData, - getTargetSubjectMetadata, -} from '../../../../selectors'; +import { getSnapRegistryData, getSnapMetadata } from '../../../../selectors'; import { disableSnap, enableSnap } from '../../../../store/actions'; import { Box, ButtonLink, Text } from '../../../component-library'; import ToggleButton from '../../../ui/toggle-button'; @@ -51,17 +48,17 @@ const SnapAuthorshipExpanded = ({ snapId, className, snap }) => { ? `https://www.npmjs.com/package/${packageName}${versionPath}` : packageName; - const subjectMetadata = useSelector((state) => - getTargetSubjectMetadata(state, snapId), - ); const snapRegistryData = useSelector((state) => getSnapRegistryData(state, snapId), ); + + const { name: snapName } = useSelector((state) => + getSnapMetadata(state, snapId), + ); + const { website = undefined } = snapRegistryData?.metadata ?? {}; const safeWebsite = useSafeWebsite(website); - const friendlyName = snapId && getSnapName(snapId, subjectMetadata); - const versionHistory = snap?.versionHistory ?? []; const installInfo = versionHistory.length ? versionHistory[versionHistory.length - 1] @@ -105,7 +102,7 @@ const SnapAuthorshipExpanded = ({ snapId, className, snap }) => { style={{ overflow: 'hidden' }} > - {friendlyName} + {snapName} - getTargetSubjectMetadata(state, snapId), + const { name: snapName } = useSelector((state) => + getSnapMetadata(state, snapId), ); - const friendlyName = snapId && getSnapName(snapId, subjectMetadata); - const openModal = () => setIsModalOpen(true); const closeModal = () => setIsModalOpen(false); @@ -73,7 +70,7 @@ const SnapAuthorshipHeader = ({ style={{ overflow: 'hidden' }} > - {friendlyName} + {snapName} + getSnapMetadata(state, snapId), + ); const iconUrl = subjectMetadata?.iconUrl; // We choose the first non-symbol char as the fallback icon. - const fallbackIcon = friendlyName?.match(/[a-z0-9]/iu)?.[0] ?? '?'; + const fallbackIcon = snapName?.match(/[a-z0-9]/iu)?.[0] ?? '?'; return ( ) : ( - getTargetSubjectMetadata(state, snapId), + const { name: snapName } = useSelector((state) => + getSnapManifest(state, snapId), ); - const friendlyName = getSnapName(snapId, snapMetadata); return ( {t('connectSnap', [ - {friendlyName} + {snapName} , ])} @@ -51,7 +49,7 @@ export default function SnapConnectCell({ origin, snapId }) {
{t('snapConnectionWarning', [ {origin}, - {friendlyName}, + {snapName}, ])}
} diff --git a/ui/components/app/snaps/snap-connect-cell/snap-connect-cell.stories.js b/ui/components/app/snaps/snap-connect-cell/snap-connect-cell.stories.js index b1007f91f1b1..6373a7f642d1 100644 --- a/ui/components/app/snaps/snap-connect-cell/snap-connect-cell.stories.js +++ b/ui/components/app/snaps/snap-connect-cell/snap-connect-cell.stories.js @@ -1,9 +1,15 @@ import React from 'react'; +import { Provider } from 'react-redux'; +import configureStore from '../../../../store/store'; +import mockState from '../../../../../test/data/mock-state.json'; import SnapConnectCell from '.'; +const store = configureStore(mockState); + export default { title: 'Components/App/Snaps/SnapConnectCell', component: SnapConnectCell, + decorators: [(story) => {story()}], }; export const DefaultStory = (args) => ; @@ -12,5 +18,5 @@ DefaultStory.storyName = 'Default'; DefaultStory.args = { origin: 'aave.com', - snapId: 'npm:@metamask/example-snap', + snapId: 'npm:@metamask/test-snap-bip44', }; diff --git a/ui/components/app/snaps/snap-home-page/snap-home-renderer.js b/ui/components/app/snaps/snap-home-page/snap-home-renderer.js index 01f5039f5959..8624827b5fc3 100644 --- a/ui/components/app/snaps/snap-home-page/snap-home-renderer.js +++ b/ui/components/app/snaps/snap-home-page/snap-home-renderer.js @@ -3,8 +3,7 @@ import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; import { Box, Text } from '../../../component-library'; import { SnapUIRenderer } from '../snap-ui-renderer'; -import { getTargetSubjectMetadata } from '../../../../selectors'; -import { getSnapName } from '../../../../helpers/utils/util'; +import { getSnapMetadata } from '../../../../selectors'; import { SnapDelineator } from '../snap-delineator'; import { DelineatorType } from '../../../../helpers/constants/snaps'; import { TextVariant } from '../../../../helpers/constants/design-system'; @@ -16,11 +15,10 @@ import { useSnapHome } from './useSnapHome'; export const SnapHomeRenderer = ({ snapId }) => { const dispatch = useDispatch(); const t = useI18nContext(); - const targetSubjectMetadata = useSelector((state) => - getTargetSubjectMetadata(state, snapId), + const { name: snapName } = useSelector((state) => + getSnapMetadata(state, snapId), ); - const snapName = getSnapName(snapId, targetSubjectMetadata); const { data, error, loading } = useSnapHome({ snapId }); const interfaceId = !loading && !error ? data?.id : undefined; diff --git a/ui/components/app/snaps/snap-insight/snap-insight.js b/ui/components/app/snaps/snap-insight/snap-insight.js index 1a754994af09..2f65756d648f 100644 --- a/ui/components/app/snaps/snap-insight/snap-insight.js +++ b/ui/components/app/snaps/snap-insight/snap-insight.js @@ -17,9 +17,8 @@ import Box from '../../../ui/box/box'; import { SnapUIRenderer } from '../snap-ui-renderer'; import { SnapDelineator } from '../snap-delineator'; import { DelineatorType } from '../../../../helpers/constants/snaps'; -import { getSnapName } from '../../../../helpers/utils/util'; import { Copyable } from '../copyable'; -import { getTargetSubjectMetadata } from '../../../../selectors'; +import { getSnapMetadata } from '../../../../selectors'; import { ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-mmi,build-beta) deleteInterface, @@ -74,12 +73,10 @@ export const SnapInsight = ({ }, [interfaceId]); ///: END:ONLY_INCLUDE_IF - const targetSubjectMetadata = useSelector((state) => - getTargetSubjectMetadata(state, snapId), + const { name: snapName } = useSelector((state) => + getSnapMetadata(state, snapId), ); - const snapName = getSnapName(snapId, targetSubjectMetadata); - const hasNoData = !error && !isLoading && !interfaceId; return ( diff --git a/ui/components/app/snaps/snap-legacy-authorship-header/snap-legacy-authorship-header.js b/ui/components/app/snaps/snap-legacy-authorship-header/snap-legacy-authorship-header.js index 2c5c49a591b4..346bcd3dcf2b 100644 --- a/ui/components/app/snaps/snap-legacy-authorship-header/snap-legacy-authorship-header.js +++ b/ui/components/app/snaps/snap-legacy-authorship-header/snap-legacy-authorship-header.js @@ -15,10 +15,9 @@ import { BorderColor, BorderRadius, } from '../../../../helpers/constants/design-system'; -import { getSnapName } from '../../../../helpers/utils/util'; import { Box, Text } from '../../../component-library'; -import { getTargetSubjectMetadata } from '../../../../selectors'; +import { getSnapMetadata } from '../../../../selectors'; import SnapAvatar from '../snap-avatar'; const SnapLegacyAuthorshipHeader = ({ @@ -28,13 +27,10 @@ const SnapLegacyAuthorshipHeader = ({ marginRight, }) => { const packageName = snapId && stripSnapPrefix(snapId); - - const subjectMetadata = useSelector((state) => - getTargetSubjectMetadata(state, snapId), + const { name: snapName } = useSelector((state) => + getSnapMetadata(state, snapId), ); - const friendlyName = snapId && getSnapName(snapId, subjectMetadata); - return ( - {friendlyName} + {snapName} { getTargetSubjectMetadata(state, snapId), ); + const { name: snapName, description } = useSelector((state) => + getSnapMetadata(state, snapId), + ); + const snap = useSelector((state) => getSnap(state, snapId)); const versionHistory = snap?.versionHistory ?? []; @@ -58,7 +63,6 @@ export const SnapMetadataModal = ({ snapId, isOpen, onClose }) => { const installOrigin = useOriginMetadata(installInfo?.origin); - const snapName = getSnapName(snapId, subjectMetadata); const snapPrefix = getSnapPrefix(snapId); const packageName = stripSnapPrefix(snapId); const isNPM = snapPrefix === 'npm:'; @@ -224,7 +228,7 @@ export const SnapMetadataModal = ({ snapId, isOpen, onClose }) => { boxProps={{ marginTop: 4 }} > - {snap?.manifest.description} + {description} diff --git a/ui/components/app/snaps/snap-permissions-list/snap-permissions-list.js b/ui/components/app/snaps/snap-permissions-list/snap-permissions-list.js index 3377f36d1497..8465cb0cd8c1 100644 --- a/ui/components/app/snaps/snap-permissions-list/snap-permissions-list.js +++ b/ui/components/app/snaps/snap-permissions-list/snap-permissions-list.js @@ -1,44 +1,51 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; import { getWeightedPermissions } from '../../../../helpers/utils/permission'; import { useI18nContext } from '../../../../hooks/useI18nContext'; import PermissionCell from '../../permission-cell'; import { Box } from '../../../component-library'; +import { getSnapsMetadata } from '../../../../selectors'; +import { getSnapName } from '../../../../helpers/utils/util'; export default function SnapPermissionsList({ snapId, + snapName, permissions, - targetSubjectMetadata, showOptions, }) { const t = useI18nContext(); + const snapsMetadata = useSelector(getSnapsMetadata); return ( - {getWeightedPermissions(t, permissions, targetSubjectMetadata).map( - (permission, index) => { - return ( - - ); - }, - )} + {getWeightedPermissions({ + t, + permissions, + subjectName: snapName, + getSubjectName: getSnapName(snapsMetadata), + }).map((permission, index) => { + return ( + + ); + })} ); } SnapPermissionsList.propTypes = { snapId: PropTypes.string.isRequired, + snapName: PropTypes.string.isRequired, permissions: PropTypes.object.isRequired, - targetSubjectMetadata: PropTypes.object.isRequired, showOptions: PropTypes.bool, }; diff --git a/ui/components/app/snaps/snap-permissions-list/snap-permissions-list.test.js b/ui/components/app/snaps/snap-permissions-list/snap-permissions-list.test.js index 4ba174224d41..f5f750f62461 100644 --- a/ui/components/app/snaps/snap-permissions-list/snap-permissions-list.test.js +++ b/ui/components/app/snaps/snap-permissions-list/snap-permissions-list.test.js @@ -1,6 +1,7 @@ import React from 'react'; import { screen } from '@testing-library/react'; import { renderWithProvider } from '../../../../../test/jest'; +import configureStore from '../../../../store/store'; import SnapPermissionsList from './snap-permissions-list'; describe('Snap Permission List', () => { @@ -21,6 +22,29 @@ describe('Snap Permission List', () => { subjectType: 'snap', version: '0.2.2', }; + const mockState = { + metamask: { + subjectMetadata: { + 'npm:@metamask/notifications-example-snap': { + name: 'Notifications Example Snap', + version: '1.2.3', + subjectType: 'snap', + }, + }, + snaps: { + 'npm:@metamask/notifications-example-snap': { + id: 'npm:@metamask/notifications-example-snap', + version: '1.2.3', + manifest: { + proposedName: 'Notifications Example Snap', + description: 'A snap', + }, + }, + }, + }, + }; + + const store = configureStore(mockState); it('renders permissions list for snaps', () => { renderWithProvider( @@ -28,6 +52,7 @@ describe('Snap Permission List', () => { permissions={mockPermissionData} targetSubjectMetadata={mockTargetSubjectMetadata} />, + store, ); expect( screen.getByText('Display dialog windows in MetaMask.'), diff --git a/ui/components/app/snaps/snap-ui-renderer/snap-ui-renderer.js b/ui/components/app/snaps/snap-ui-renderer/snap-ui-renderer.js index 6f3d0654aa26..977e25165d80 100644 --- a/ui/components/app/snaps/snap-ui-renderer/snap-ui-renderer.js +++ b/ui/components/app/snaps/snap-ui-renderer/snap-ui-renderer.js @@ -8,11 +8,9 @@ import MetaMaskTemplateRenderer from '../../metamask-template-renderer/metamask- import { TextVariant } from '../../../../helpers/constants/design-system'; import { SnapDelineator } from '../snap-delineator'; import { useI18nContext } from '../../../../hooks/useI18nContext'; - -import { getSnapName } from '../../../../helpers/utils/util'; import { + getSnapMetadata, getMemoizedInterfaceContent, - getMemoizedTargetSubjectMetadata, } from '../../../../selectors'; import { Box, FormTextField, Text } from '../../../component-library'; import { Copyable } from '../copyable'; @@ -38,12 +36,10 @@ const SnapUIRendererComponent = ({ interfaceId, }) => { const t = useI18nContext(); - const targetSubjectMetadata = useSelector((state) => - getMemoizedTargetSubjectMetadata(state, snapId), + const { name: snapName } = useSelector((state) => + getSnapMetadata(state, snapId), ); - const snapName = getSnapName(snapId, targetSubjectMetadata); - const content = useSelector((state) => getMemoizedInterfaceContent(state, interfaceId), ); diff --git a/ui/components/app/snaps/update-snap-permission-list/update-snap-permission-list.js b/ui/components/app/snaps/update-snap-permission-list/update-snap-permission-list.js index a3b33f85722f..9c68c81f73eb 100644 --- a/ui/components/app/snaps/update-snap-permission-list/update-snap-permission-list.js +++ b/ui/components/app/snaps/update-snap-permission-list/update-snap-permission-list.js @@ -1,9 +1,12 @@ import React from 'react'; +import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import { getWeightedPermissions } from '../../../../helpers/utils/permission'; import { useI18nContext } from '../../../../hooks/useI18nContext'; import PermissionCell from '../../permission-cell'; import { Box } from '../../../component-library'; +import { getSnapMetadata, getSnapsMetadata } from '../../../../selectors'; +import { getSnapName } from '../../../../helpers/utils/util'; export default function UpdateSnapPermissionList({ approvedPermissions, @@ -13,40 +16,54 @@ export default function UpdateSnapPermissionList({ }) { const t = useI18nContext(); + const { name: snapName } = useSelector((state) => + getSnapMetadata(state, targetSubjectMetadata.origin), + ); + + const snapsMetadata = useSelector(getSnapsMetadata); + const snapsNameGetter = getSnapName(snapsMetadata); + return ( - {getWeightedPermissions(t, newPermissions, targetSubjectMetadata).map( - (permission, index) => ( - - ), - )} - {getWeightedPermissions(t, revokedPermissions, targetSubjectMetadata).map( - (permission, index) => ( - - ), - )} - {getWeightedPermissions( + {getWeightedPermissions({ + t, + permissions: newPermissions, + subjectName: snapName, + getSubjectName: snapsNameGetter, + }).map((permission, index) => ( + + ))} + {getWeightedPermissions({ + t, + permissions: revokedPermissions, + subjectName: snapName, + getSubjectName: snapsNameGetter, + }).map((permission, index) => ( + + ))} + {getWeightedPermissions({ t, - approvedPermissions, - targetSubjectMetadata, - ).map((permission, index) => ( + permissions: approvedPermissions, + subjectName: snapName, + getSubjectName: snapsNameGetter, + }).map((permission, index) => ( - T + m
- T + m
- T + m
- {getSnapName(targetSubjectMetadata?.origin, targetSubjectMetadata)} + {snapName} ); } @@ -78,18 +77,18 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ weight: 3, }), ///: BEGIN:ONLY_INCLUDE_IF(snaps) - [RestrictedMethods.snap_dialog]: ({ t, targetSubjectMetadata }) => ({ + [RestrictedMethods.snap_dialog]: ({ t, subjectName }) => ({ label: t('permission_dialog'), description: t('permission_dialogDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), leftIcon: IconName.Messages, weight: 4, }), - [RestrictedMethods.snap_notify]: ({ t, targetSubjectMetadata }) => ({ + [RestrictedMethods.snap_notify]: ({ t, subjectName }) => ({ label: t('permission_notifications'), description: t('permission_notificationsDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), leftIcon: IconName.Notification, weight: 4, @@ -97,7 +96,7 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ [RestrictedMethods.snap_getBip32PublicKey]: ({ t, permissionValue, - targetSubjectMetadata, + subjectName, }) => permissionValue.caveats[0].value.map(({ path, curve }, i) => { const baseDescription = { @@ -134,7 +133,7 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ > {friendlyName} , - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), }; } @@ -161,14 +160,14 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ > {path.join('/')} , - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), }; }), [RestrictedMethods.snap_getBip32Entropy]: ({ t, permissionValue, - targetSubjectMetadata, + subjectName, }) => permissionValue.caveats[0].value.map(({ path, curve }, i) => { const baseDescription = { @@ -197,7 +196,7 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ , ]), description: t('permission_manageBip44AndBip32KeysDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), }; } @@ -215,14 +214,14 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ , ]), description: t('permission_manageBip44AndBip32KeysDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), }; }), [RestrictedMethods.snap_getBip44Entropy]: ({ t, permissionValue, - targetSubjectMetadata, + subjectName, }) => permissionValue.caveats[0].value.map(({ coinType }, i) => ({ label: t('permission_manageBip44Keys', [ @@ -237,7 +236,7 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ , ]), description: t('permission_manageBip44AndBip32KeysDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), leftIcon: IconName.Key, weight: 1, @@ -246,38 +245,32 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ getSlip44ProtocolName(coinType) ?? `${t('unknownNetworkForKeyEntropy')} m/44'/${coinType}'`, })), - [RestrictedMethods.snap_getEntropy]: ({ t, targetSubjectMetadata }) => ({ - label: t('permission_getEntropy', [ - getSnapNameComponent(targetSubjectMetadata), - ]), + [RestrictedMethods.snap_getEntropy]: ({ t, subjectName }) => ({ + label: t('permission_getEntropy', [getSnapNameComponent(subjectName)]), description: t('permission_getEntropyDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), leftIcon: IconName.SecurityKey, weight: 4, }), - [RestrictedMethods.snap_manageState]: ({ t, targetSubjectMetadata }) => ({ + [RestrictedMethods.snap_manageState]: ({ t, subjectName }) => ({ label: t('permission_manageState'), description: t('permission_manageStateDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), leftIcon: IconName.AddSquare, weight: 4, }), - [RestrictedMethods.snap_getLocale]: ({ t, targetSubjectMetadata }) => ({ + [RestrictedMethods.snap_getLocale]: ({ t, subjectName }) => ({ label: t('permission_getLocale'), description: t('permission_getLocaleDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), leftIcon: IconName.Global, weight: 4, }), - [RestrictedMethods.wallet_snap]: ({ - t, - permissionValue, - targetSubjectMetadata, - }) => { + [RestrictedMethods.wallet_snap]: ({ t, permissionValue, getSubjectName }) => { const snaps = permissionValue.caveats[0].value; const baseDescription = { leftIcon: getLeftIcon(IconName.Flash), @@ -285,8 +278,8 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ }; return Object.keys(snaps).map((snapId) => { - const friendlyName = getSnapName(snapId, targetSubjectMetadata); - if (friendlyName) { + const snapName = getSubjectName(snapId); + if (snapName) { return { ...baseDescription, label: t('permission_accessNamedSnap', [ @@ -296,10 +289,10 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ fontWeight={FontWeight.Medium} key={snapId} > - {friendlyName} + {snapName} , ]), - description: t('permission_accessSnapDescription', [friendlyName]), + description: t('permission_accessSnapDescription', [snapName]), }; } @@ -310,24 +303,18 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ }; }); }, - [EndowmentPermissions['endowment:network-access']]: ({ - t, - targetSubjectMetadata, - }) => ({ + [EndowmentPermissions['endowment:network-access']]: ({ t, subjectName }) => ({ label: t('permission_accessNetwork'), description: t('permission_accessNetworkDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), leftIcon: IconName.Wifi, weight: 3, }), - [EndowmentPermissions['endowment:webassembly']]: ({ - t, - targetSubjectMetadata, - }) => ({ + [EndowmentPermissions['endowment:webassembly']]: ({ t, subjectName }) => ({ label: t('permission_webAssembly'), description: t('permission_webAssemblyDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), leftIcon: IconName.DocumentCode, rightIcon: null, @@ -336,7 +323,7 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ [EndowmentPermissions['endowment:transaction-insight']]: ({ t, permissionValue, - targetSubjectMetadata, + subjectName, }) => { const baseDescription = { leftIcon: IconName.Speedometer, @@ -348,7 +335,7 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ ...baseDescription, label: t('permission_transactionInsight'), description: t('permission_transactionInsightDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), }, ]; @@ -362,7 +349,7 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ ...baseDescription, label: t('permission_transactionInsightOrigin'), description: t('permission_transactionInsightOriginDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), leftIcon: IconName.Explore, }); @@ -370,36 +357,31 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ return result; }, - [EndowmentPermissions['endowment:cronjob']]: ({ - t, - targetSubjectMetadata, - }) => ({ + [EndowmentPermissions['endowment:cronjob']]: ({ t, subjectName }) => ({ label: t('permission_cronjob'), description: t('permission_cronjobDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), leftIcon: IconName.Clock, weight: 3, }), [EndowmentPermissions['endowment:ethereum-provider']]: ({ t, - targetSubjectMetadata, + subjectName, }) => ({ label: t('permission_ethereumProvider'), description: t('permission_ethereumProviderDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), leftIcon: IconName.Ethereum, weight: 3, id: 'ethereum-provider-access', - message: t('ethereumProviderAccess', [ - getSnapNameComponent(targetSubjectMetadata), - ]), + message: t('ethereumProviderAccess', [getSnapNameComponent(subjectName)]), }), [EndowmentPermissions['endowment:rpc']]: ({ t, permissionValue, - targetSubjectMetadata, + subjectName, }) => { const baseDescription = { leftIcon: IconName.Hierarchy, @@ -414,11 +396,11 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ ...baseDescription, label: t('permission_rpc', [ t('otherSnaps'), - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), description: t('permission_rpcDescription', [ t('otherSnaps'), - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), }); } @@ -428,11 +410,11 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ ...baseDescription, label: t('permission_rpc', [ t('websites'), - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), description: t('permission_rpcDescription', [ t('websites'), - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), }); } @@ -482,11 +464,11 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ ...baseDescription, label: t('permission_rpc', [ originsMessage, - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), description: t('permission_rpcDescription', [ originsMessage, - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), }); } @@ -495,44 +477,38 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ }, [EndowmentPermissions['endowment:lifecycle-hooks']]: ({ t, - targetSubjectMetadata, + subjectName, }) => ({ label: t('permission_lifecycleHooks'), description: t('permission_lifecycleHooksDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), leftIcon: IconName.Hierarchy, weight: 4, }), - [EndowmentPermissions['endowment:page-home']]: ({ - t, - targetSubjectMetadata, - }) => ({ + [EndowmentPermissions['endowment:page-home']]: ({ t, subjectName }) => ({ label: t('permission_homePage'), description: t('permission_homePageDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), leftIcon: IconName.Home, weight: 4, }), ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) - [RestrictedMethods.snap_manageAccounts]: ({ t, targetSubjectMetadata }) => ({ + [RestrictedMethods.snap_manageAccounts]: ({ t, subjectName }) => ({ label: t('permission_manageAccounts'), description: t('permission_manageAccountsDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), leftIcon: getLeftIcon(IconName.UserCircleAdd), rightIcon: null, weight: 3, }), - [EndowmentPermissions['endowment:keyring']]: ({ - t, - targetSubjectMetadata, - }) => ({ + [EndowmentPermissions['endowment:keyring']]: ({ t, subjectName }) => ({ label: t('permission_keyring'), description: t('permission_keyringDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), leftIcon: getLeftIcon(IconName.UserCircleAdd), rightIcon: null, @@ -549,7 +525,7 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ [EndowmentPermissions['endowment:signature-insight']]: ({ t, permissionValue, - targetSubjectMetadata, + subjectName, }) => { const baseDescription = { leftIcon: IconName.Warning, @@ -561,7 +537,7 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ ...baseDescription, label: t('permission_signatureInsight'), description: t('permission_signatureInsightDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), }, ]; @@ -576,7 +552,7 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ ...baseDescription, label: t('permission_signatureInsightOrigin'), description: t('permission_signatureInsightOriginDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(subjectName), ]), leftIcon: IconName.Explore, }); @@ -610,21 +586,20 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ * @property {Function} t - The translation function. * @property {string} permissionName - The name of the permission. * @property {object} permissionValue - The permission object. - * @property {object} targetSubjectMetadata - Subject metadata. + * @property {string} subjectName - The name of the subject. + * @property {Function} getSubjectName - The function used to get the subject name. */ /** * @param {PermissionDescriptionParamsObject} params - The permission description params object. - * @param {Function} params.t - The translation function. - * @param {string} params.permissionName - The name of the permission to request - * @param {object} params.permissionValue - The value of the permission to request * @returns {PermissionLabelObject[]} */ export const getPermissionDescription = ({ t, permissionName, permissionValue, - targetSubjectMetadata, + subjectName, + getSubjectName, }) => { let value = PERMISSION_DESCRIPTIONS[UNKNOWN_PERMISSION]; @@ -636,7 +611,8 @@ export const getPermissionDescription = ({ t, permissionName, permissionValue, - targetSubjectMetadata, + subjectName, + getSubjectName, }); if (!Array.isArray(result)) { return [{ ...result, permissionName, permissionValue }]; @@ -649,16 +625,27 @@ export const getPermissionDescription = ({ })); }; +/** + * @typedef {object} WeightedPermissionDescriptionParamsObject + * @property {Function} t - The translation function. + * @property {string} permissions - The permissions object. + * @property {Function} [getSubjectName] - The function to get a subject name. + * @property {string} [subjectName] - The name of the subject. + */ + /** * Get the weighted permissions from a permissions object. The weight is used to * sort the permissions in the UI. * - * @param {Function} t - The translation function - * @param {object} permissions - The permissions object. - * @param {object} targetSubjectMetadata - The subject metadata. + * @param {WeightedPermissionDescriptionParamsObject} parms - The weighted permissions params object. * @returns {PermissionLabelObject[]} */ -export function getWeightedPermissions(t, permissions, targetSubjectMetadata) { +export function getWeightedPermissions({ + t, + permissions, + getSubjectName, + subjectName, +}) { return Object.entries(permissions) .reduce( (target, [permissionName, permissionValue]) => @@ -667,7 +654,8 @@ export function getWeightedPermissions(t, permissions, targetSubjectMetadata) { t, permissionName, permissionValue, - targetSubjectMetadata, + subjectName, + getSubjectName, }), ), [], diff --git a/ui/helpers/utils/util.js b/ui/helpers/utils/util.js index f9fc4d993590..c556b3ba5b3c 100644 --- a/ui/helpers/utils/util.js +++ b/ui/helpers/utils/util.js @@ -11,8 +11,8 @@ import { import * as lodash from 'lodash'; import bowser from 'bowser'; ///: BEGIN:ONLY_INCLUDE_IF(snaps) -import { stripSnapPrefix } from '@metamask/snaps-utils'; import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/snaps-rpc-methods'; +import { stripSnapPrefix } from '@metamask/snaps-utils'; // eslint-disable-next-line import/no-duplicates import { isObject } from '@metamask/utils'; ///: END:ONLY_INCLUDE_IF @@ -31,9 +31,6 @@ import { } from '../../../shared/constants/labels'; import { Numeric } from '../../../shared/modules/Numeric'; import { OUTDATED_BROWSER_VERSIONS } from '../constants/common'; -///: BEGIN:ONLY_INCLUDE_IF(snaps) -import { SNAPS_METADATA } from '../../../shared/constants/snaps'; -///: END:ONLY_INCLUDE_IF // formatData :: ( date: ) -> String import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; import { hexToDecimal } from '../../../shared/modules/conversion.utils'; @@ -565,21 +562,10 @@ export function isNullish(value) { } ///: BEGIN:ONLY_INCLUDE_IF(snaps) -export const getSnapName = (snapId, subjectMetadata) => { - if (SNAPS_METADATA[snapId]?.name) { - return SNAPS_METADATA[snapId].name; - } - - if (subjectMetadata) { - return subjectMetadata.name; - } - - // Mirrors a legacy behaviour of stripSnapPrefix - if (!snapId) { - return null; - } - - return stripSnapPrefix(snapId); +export const getSnapName = (snapsMetadata) => { + return (snapId) => { + return snapsMetadata[snapId]?.name ?? stripSnapPrefix(snapId); + }; }; export const getSnapRoute = (snapId) => { diff --git a/ui/hooks/useTransactionInsights.js b/ui/hooks/useTransactionInsights.js index 5e94b37c8e6d..77653a6d15af 100644 --- a/ui/hooks/useTransactionInsights.js +++ b/ui/hooks/useTransactionInsights.js @@ -15,13 +15,12 @@ import { SnapInsight } from '../components/app/snaps/snap-insight/snap-insight'; import { getInsightSnapIds, getInsightSnaps, - getSubjectMetadataDeepEqual, + getSnapsMetadata, } from '../selectors'; -import { getSnapName } from '../helpers/utils/util'; - ///: BEGIN:ONLY_INCLUDE_IF(build-flask) import { deleteInterface } from '../store/actions'; ///: END:ONLY_INCLUDE_IF +import { getSnapName } from '../helpers/utils/util'; import { useTransactionInsightSnaps } from './snaps/useTransactionInsightSnaps'; const isAllowedTransactionTypes = (transactionType) => @@ -41,8 +40,10 @@ const useTransactionInsights = ({ txData }) => { const { txParams, chainId, origin } = txData; const caip2ChainId = `eip155:${stripHexPrefix(chainId)}`; const insightSnaps = useSelector(getInsightSnaps); - const subjectMetadata = useSelector(getSubjectMetadataDeepEqual); const insightSnapIds = useSelector(getInsightSnapIds); + const snapsMetadata = useSelector(getSnapsMetadata); + + const snapsNameGetter = getSnapName(snapsMetadata); const [selectedInsightSnapId, setSelectedInsightSnapId] = useState( insightSnaps[0]?.id, @@ -98,7 +99,7 @@ const useTransactionInsights = ({ txData }) => { insightComponent = ( { ); } else if (insightSnaps.length > 1) { const dropdownOptions = insightSnaps?.map(({ id }) => { - const name = getSnapName(id, subjectMetadata[id]); + const name = snapsNameGetter(id); return { value: id, name, diff --git a/ui/pages/confirmations/confirmation/confirmation.js b/ui/pages/confirmations/confirmation/confirmation.js index 94fee5565009..0e4783a93f45 100644 --- a/ui/pages/confirmations/confirmation/confirmation.js +++ b/ui/pages/confirmations/confirmation/confirmation.js @@ -24,14 +24,14 @@ import { Size, TextColor } from '../../../helpers/constants/design-system'; import { useI18nContext } from '../../../hooks/useI18nContext'; import { MetaMetricsContext } from '../../../contexts/metametrics'; import { - ///: BEGIN:ONLY_INCLUDE_IF(snaps) - getTargetSubjectMetadata, - ///: END:ONLY_INCLUDE_IF getUnapprovedTemplatedConfirmations, getUnapprovedTxCount, getApprovalFlows, getTotalUnapprovedCount, useSafeChainsListValidationSelector, + ///: BEGIN:ONLY_INCLUDE_IF(snaps) + getSnapsMetadata, + ///: END:ONLY_INCLUDE_IF } from '../../../selectors'; import NetworkDisplay from '../../../components/app/network-display/network-display'; import Callout from '../../../components/ui/callout'; @@ -39,7 +39,6 @@ import { Icon, IconName } from '../../../components/component-library'; import Loading from '../../../components/ui/loading-screen'; ///: BEGIN:ONLY_INCLUDE_IF(snaps) import SnapAuthorshipHeader from '../../../components/app/snaps/snap-authorship-header'; -import { getSnapName } from '../../../helpers/utils/util'; import { SnapUIRenderer } from '../../../components/app/snaps/snap-ui-renderer'; ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) @@ -241,9 +240,9 @@ export default function ConfirmationPage({ const [submitAlerts, setSubmitAlerts] = useState([]); ///: BEGIN:ONLY_INCLUDE_IF(snaps) - const targetSubjectMetadata = useSelector((state) => - getTargetSubjectMetadata(state, pendingConfirmation?.origin), - ); + const snapsMetadata = useSelector(getSnapsMetadata); + + const name = snapsMetadata[pendingConfirmation?.origin]?.name; const SNAP_DIALOG_TYPE = [ ApprovalType.SnapDialogAlert, @@ -274,10 +273,7 @@ export default function ConfirmationPage({ let useSnapHeader = isSnapDialog; // When pendingConfirmation is undefined, this will also be undefined - const snapName = - isSnapDialog && - targetSubjectMetadata && - getSnapName(pendingConfirmation?.origin, targetSubjectMetadata); + const snapName = isSnapDialog && name; ///: END:ONLY_INCLUDE_IF const INPUT_STATE_CONFIRMATIONS = [ diff --git a/ui/pages/confirmations/confirmation/templates/add-ethereum-chain.test.js b/ui/pages/confirmations/confirmation/templates/add-ethereum-chain.test.js index 790252bb89b0..bacd44459de9 100644 --- a/ui/pages/confirmations/confirmation/templates/add-ethereum-chain.test.js +++ b/ui/pages/confirmations/confirmation/templates/add-ethereum-chain.test.js @@ -49,6 +49,7 @@ const mockBaseStore = { status: NetworkStatus.Available, }, }, + snaps: {}, }, }; diff --git a/ui/pages/confirmations/confirmation/templates/error.test.js b/ui/pages/confirmations/confirmation/templates/error.test.js index 1d6f25ac1681..642d2ef2ad29 100644 --- a/ui/pages/confirmations/confirmation/templates/error.test.js +++ b/ui/pages/confirmations/confirmation/templates/error.test.js @@ -37,6 +37,7 @@ const mockBaseStore = { }, approvalFlows: [], subjectMetadata: {}, + snaps: {}, }, }; diff --git a/ui/pages/confirmations/confirmation/templates/snap-account-redirect.test.js b/ui/pages/confirmations/confirmation/templates/snap-account-redirect.test.js index 055a822c4a2e..626ebee606e9 100644 --- a/ui/pages/confirmations/confirmation/templates/snap-account-redirect.test.js +++ b/ui/pages/confirmations/confirmation/templates/snap-account-redirect.test.js @@ -34,6 +34,7 @@ const mockBaseStore = { approvalFlows: [], subjectMetadata: {}, providerConfig, + snaps: {}, }, }; diff --git a/ui/pages/confirmations/confirmation/templates/success.test.js b/ui/pages/confirmations/confirmation/templates/success.test.js index b566c177a6b9..9267de5efaea 100644 --- a/ui/pages/confirmations/confirmation/templates/success.test.js +++ b/ui/pages/confirmations/confirmation/templates/success.test.js @@ -37,6 +37,7 @@ const mockBaseStore = { }, approvalFlows: [], subjectMetadata: {}, + snaps: {}, }, }; diff --git a/ui/pages/confirmations/confirmation/templates/switch-ethereum-chain.test.js b/ui/pages/confirmations/confirmation/templates/switch-ethereum-chain.test.js index 5f0e52e1df57..aeb0f5a2c1ed 100644 --- a/ui/pages/confirmations/confirmation/templates/switch-ethereum-chain.test.js +++ b/ui/pages/confirmations/confirmation/templates/switch-ethereum-chain.test.js @@ -54,6 +54,7 @@ const mockBaseStore = { status: NetworkStatus.Available, }, }, + snaps: {}, }, }; diff --git a/ui/pages/notifications/notification.test.js b/ui/pages/notifications/notification.test.js index 2dd860c81dec..90c44e18bcf7 100644 --- a/ui/pages/notifications/notification.test.js +++ b/ui/pages/notifications/notification.test.js @@ -39,6 +39,16 @@ describe('Notifications', () => { subjectType: 'snap', }, }, + snaps: { + 'npm:@metamask/notifications-example-snap': { + id: 'npm:@metamask/notifications-example-snap', + version: '1.2.3', + manifest: { + proposedName: 'Notifications Example Snap', + description: 'A snap', + }, + }, + }, }, }; @@ -91,6 +101,16 @@ describe('NotificationItem', () => { subjectType: 'snap', }, }, + snaps: { + 'npm:@metamask/notifications-example-snap': { + id: 'npm:@metamask/notifications-example-snap', + version: '1.2.3', + manifest: { + proposedName: 'Notifications Example Snap', + description: 'A snap', + }, + }, + }, }, }; const props = { diff --git a/ui/pages/notifications/notifications.js b/ui/pages/notifications/notifications.js index 5e9b26093a3f..a6197f163588 100644 --- a/ui/pages/notifications/notifications.js +++ b/ui/pages/notifications/notifications.js @@ -4,14 +4,10 @@ import classnames from 'classnames'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { SnapUIMarkdown } from '../../components/app/snaps/snap-ui-markdown'; -import { - formatDate, - getSnapName, - getSnapRoute, -} from '../../helpers/utils/util'; +import { formatDate, getSnapRoute } from '../../helpers/utils/util'; import { getNotifications, - getTargetSubjectMetadata, + getSnapMetadata, getUnreadNotifications, } from '../../selectors'; import { DEFAULT_ROUTE } from '../../helpers/constants/routes'; @@ -32,12 +28,10 @@ export function NotificationItem({ notification, onItemClick }) { const { message, origin, createdDate, readDate } = notification; const history = useHistory(); const t = useI18nContext(); - const targetSubjectMetadata = useSelector((state) => - getTargetSubjectMetadata(state, origin), + const { name: snapName } = useSelector((state) => + getSnapMetadata(state, origin), ); - const snapName = getSnapName(origin, targetSubjectMetadata); - const handleNameClick = (e) => { e.stopPropagation(); history.push(getSnapRoute(origin)); diff --git a/ui/pages/permissions-connect/snaps/snap-install/snap-install.js b/ui/pages/permissions-connect/snaps/snap-install/snap-install.js index db786527a7c7..ccfeabe699fd 100644 --- a/ui/pages/permissions-connect/snaps/snap-install/snap-install.js +++ b/ui/pages/permissions-connect/snaps/snap-install/snap-install.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import React, { useCallback, useState } from 'react'; +import { useSelector } from 'react-redux'; import { PageContainerFooter } from '../../../../components/ui/page-container'; import { useI18nContext } from '../../../../hooks/useI18nContext'; import SnapInstallWarning from '../../../../components/app/snaps/snap-install-warning'; @@ -25,12 +26,13 @@ import { Text, Box, } from '../../../../components/component-library'; -import { getSnapName } from '../../../../helpers/utils/util'; import SnapPermissionsList from '../../../../components/app/snaps/snap-permissions-list'; import { useScrollRequired } from '../../../../hooks/useScrollRequired'; import SiteOrigin from '../../../../components/ui/site-origin/site-origin'; import InstallError from '../../../../components/app/snaps/install-error/install-error'; import { useOriginMetadata } from '../../../../hooks/useOriginMetadata'; +import { getSnapMetadata, getSnapsMetadata } from '../../../../selectors'; +import { getSnapName } from '../../../../helpers/utils/util'; export default function SnapInstall({ request, @@ -43,6 +45,7 @@ export default function SnapInstall({ const siteMetadata = useOriginMetadata(request?.metadata?.dappOrigin) || {}; const { origin, iconUrl, name } = siteMetadata; const [isShowingWarning, setIsShowingWarning] = useState(false); + const snapsMetadata = useSelector(getSnapsMetadata); const { isScrollable, isScrolledToBottom, scrollToBottom, ref, onScroll } = useScrollRequired([requestState]); @@ -57,23 +60,22 @@ export default function SnapInstall({ [request, approveSnapInstall], ); - const hasError = !requestState.loading && requestState.error; + const { name: snapName } = useSelector((state) => + getSnapMetadata(state, targetSubjectMetadata.origin), + ); + const hasError = !requestState.loading && requestState.error; const isLoading = requestState.loading; const warnings = getSnapInstallWarnings( requestState?.permissions ?? {}, - targetSubjectMetadata, t, + snapName, + getSnapName(snapsMetadata), ); const shouldShowWarning = warnings.length > 0; - const snapName = getSnapName( - targetSubjectMetadata.origin, - targetSubjectMetadata, - ); - const handleSubmit = () => { if (!hasError && shouldShowWarning) { setIsShowingWarning(true); @@ -185,7 +187,6 @@ export default function SnapInstall({ {isScrollable && !isScrolledToBottom ? ( diff --git a/ui/pages/permissions-connect/snaps/snap-result/snap-result.js b/ui/pages/permissions-connect/snaps/snap-result/snap-result.js index dbdbe5304389..1c22c34068be 100644 --- a/ui/pages/permissions-connect/snaps/snap-result/snap-result.js +++ b/ui/pages/permissions-connect/snaps/snap-result/snap-result.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import React, { useCallback } from 'react'; +import { useSelector } from 'react-redux'; import { PageContainerFooter } from '../../../../components/ui/page-container'; import { useI18nContext } from '../../../../hooks/useI18nContext'; @@ -25,7 +26,7 @@ import { import PulseLoader from '../../../../components/ui/pulse-loader/pulse-loader'; import InstallError from '../../../../components/app/snaps/install-error/install-error'; import SnapAuthorshipHeader from '../../../../components/app/snaps/snap-authorship-header'; -import { getSnapName } from '../../../../helpers/utils/util'; +import { getSnapMetadata } from '../../../../selectors'; export default function SnapResult({ request, @@ -42,9 +43,8 @@ export default function SnapResult({ const hasError = !requestState.loading && requestState.error; const isLoading = requestState.loading; - const snapName = getSnapName( - targetSubjectMetadata.origin, - targetSubjectMetadata, + const { name: snapName } = useSelector((state) => + getSnapMetadata(state, targetSubjectMetadata.origin), ); function getSuccessScreen(requestType, snapNameToRender) { diff --git a/ui/pages/permissions-connect/snaps/snap-update/snap-update.js b/ui/pages/permissions-connect/snaps/snap-update/snap-update.js index 2077565b8626..fc1ac9fa21f8 100644 --- a/ui/pages/permissions-connect/snaps/snap-update/snap-update.js +++ b/ui/pages/permissions-connect/snaps/snap-update/snap-update.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import React, { useCallback, useState } from 'react'; +import { useSelector } from 'react-redux'; import { PageContainerFooter } from '../../../../components/ui/page-container'; import { useI18nContext } from '../../../../hooks/useI18nContext'; import SnapInstallWarning from '../../../../components/app/snaps/snap-install-warning'; @@ -28,8 +29,9 @@ import { ValidTag, Text, } from '../../../../components/component-library'; -import { getSnapName } from '../../../../helpers/utils/util'; import { useScrollRequired } from '../../../../hooks/useScrollRequired'; +import { getSnapMetadata, getSnapsMetadata } from '../../../../selectors'; +import { getSnapName } from '../../../../helpers/utils/util'; export default function SnapUpdate({ request, @@ -44,6 +46,7 @@ export default function SnapUpdate({ const { isScrollable, isScrolledToBottom, scrollToBottom, ref, onScroll } = useScrollRequired([requestState]); + const snapsMetadata = useSelector(getSnapsMetadata); const onCancel = useCallback( () => rejectSnapUpdate(request.metadata.id), @@ -55,6 +58,10 @@ export default function SnapUpdate({ [request, approveSnapUpdate], ); + const { name: snapName } = useSelector((state) => + getSnapMetadata(state, targetSubjectMetadata.origin), + ); + const approvedPermissions = requestState.approvedPermissions ?? {}; const revokedPermissions = requestState.unusedPermissions ?? {}; const newPermissions = requestState.newPermissions ?? {}; @@ -65,17 +72,13 @@ export default function SnapUpdate({ const warnings = getSnapInstallWarnings( newPermissions, - targetSubjectMetadata, t, + snapName, + getSnapName(snapsMetadata), ); const shouldShowWarning = warnings.length > 0; - const snapName = getSnapName( - targetSubjectMetadata.origin, - targetSubjectMetadata, - ); - const handleSubmit = () => { if (!hasError && shouldShowWarning) { setIsShowingWarning(true); diff --git a/ui/pages/permissions-connect/snaps/snaps-connect/snaps-connect.js b/ui/pages/permissions-connect/snaps/snaps-connect/snaps-connect.js index 5462699eafee..6232a20d435b 100644 --- a/ui/pages/permissions-connect/snaps/snaps-connect/snaps-connect.js +++ b/ui/pages/permissions-connect/snaps/snaps-connect/snaps-connect.js @@ -22,13 +22,10 @@ import { } from '../../../../helpers/constants/design-system'; import { PageContainerFooter } from '../../../../components/ui/page-container'; import SnapConnectCell from '../../../../components/app/snaps/snap-connect-cell/snap-connect-cell'; -import { getDedupedSnaps, getSnapName } from '../../../../helpers/utils/util'; +import { getDedupedSnaps } from '../../../../helpers/utils/util'; import PulseLoader from '../../../../components/ui/pulse-loader/pulse-loader'; import SnapPrivacyWarning from '../../../../components/app/snaps/snap-privacy-warning/snap-privacy-warning'; -import { - getPermissions, - getTargetSubjectMetadata, -} from '../../../../selectors'; +import { getPermissions, getSnapMetadata } from '../../../../selectors'; import SnapAvatar from '../../../../components/app/snaps/snap-avatar/snap-avatar'; import { useOriginMetadata } from '../../../../hooks/useOriginMetadata'; @@ -64,12 +61,14 @@ export default function SnapsConnect({ const snaps = getDedupedSnaps(request, currentPermissions); - const singularConnectSnapMetadata = useSelector((state) => - getTargetSubjectMetadata(state, snaps?.[0]), - ); - const SnapsConnectContent = () => { const { hostname: trimmedOrigin } = useOriginMetadata(origin) || {}; + + const snapId = snaps[0]; + const { name: snapName } = useSelector((state) => + getSnapMetadata(state, snapId), + ); + if (isLoading) { return ( ); } + if (snaps?.length > 1) { return ( ); } else if (snaps?.length === 1) { - const snapId = snaps[0]; - const snapName = getSnapName(snapId, singularConnectSnapMetadata); return ( { const permissionDescription = getPermissionDescription({ t, permissionName, permissionValue, - targetSubjectMetadata, + subjectName: snapName, + getSubjectName: getSnapName, }); return filteredWarnings.concat( diff --git a/ui/pages/snaps/snap-view/snap-settings.js b/ui/pages/snaps/snap-view/snap-settings.js index a8da8ad6eeb3..420ea61afe96 100644 --- a/ui/pages/snaps/snap-view/snap-settings.js +++ b/ui/pages/snaps/snap-view/snap-settings.js @@ -37,13 +37,12 @@ import { getSnaps, getSubjectsWithSnapPermission, getPermissions, - getTargetSubjectMetadata, getSnapLatestVersion, + getSnapMetadata, ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) getMemoizedMetaMaskIdentities, ///: END:ONLY_INCLUDE_IF } from '../../../selectors'; -import { getSnapName } from '../../../helpers/utils/util'; import { Box, Button, @@ -87,8 +86,9 @@ function SnapSettings({ snapId }) { const permissions = useSelector( (state) => snap && getPermissions(state, snap.id), ); - const targetSubjectMetadata = useSelector((state) => - getTargetSubjectMetadata(state, snap?.id), + + const { name: snapName, description } = useSelector((state) => + getSnapMetadata(state, snapId), ); let isKeyringSnap = false; @@ -112,8 +112,6 @@ function SnapSettings({ snapId }) { dispatch(disconnectOriginFromSnap(connectedOrigin, snap.id)); }; - const snapName = getSnapName(snap.id, targetSubjectMetadata); - const latestRegistryVersion = useSelector((state) => snap ? getSnapLatestVersion(state, snap?.id) : null, ); @@ -148,7 +146,7 @@ function SnapSettings({ snapId }) { - {snap?.manifest.description} + {description} @@ -156,8 +154,8 @@ function SnapSettings({ snapId }) { {t('permissions')} diff --git a/ui/pages/snaps/snap-view/snap-view.js b/ui/pages/snaps/snap-view/snap-view.js index 38ca2de828fd..5c3062fca28f 100644 --- a/ui/pages/snaps/snap-view/snap-view.js +++ b/ui/pages/snaps/snap-view/snap-view.js @@ -4,12 +4,7 @@ import { useSelector } from 'react-redux'; import { hasProperty } from '@metamask/utils'; import { BackgroundColor } from '../../../helpers/constants/design-system'; import { SNAPS_ROUTE } from '../../../helpers/constants/routes'; -import { - getSnaps, - getPermissions, - getTargetSubjectMetadata, -} from '../../../selectors'; -import { getSnapName } from '../../../helpers/utils/util'; +import { getSnaps, getPermissions, getSnapMetadata } from '../../../selectors'; import { ButtonIcon } from '../../../components/component-library'; import { Content, @@ -30,6 +25,10 @@ function SnapView() { .map(([_, snapState]) => snapState) .find((snapState) => snapState.id === snapId); + const { name: snapName } = useSelector((state) => + getSnapMetadata(state, snapId), + ); + useEffect(() => { if (!snap) { history.push(SNAPS_ROUTE); @@ -40,10 +39,6 @@ function SnapView() { (state) => snap && getPermissions(state, snap.id), ); - const targetSubjectMetadata = useSelector((state) => - getTargetSubjectMetadata(state, snap?.id), - ); - const hasHomePage = permissions && hasProperty(permissions, 'endowment:page-home'); const [showSettings, setShowSettings] = useState(!hasHomePage); @@ -52,8 +47,6 @@ function SnapView() { return null; } - const snapName = getSnapName(snap.id, targetSubjectMetadata); - const handleSettingsClick = () => { setShowSettings(true); }; diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 3b85a34ee50e..ed3e115ecc10 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -3,7 +3,10 @@ import { SubjectType } from '@metamask/permission-controller'; ///: END:ONLY_INCLUDE_IF import { ApprovalType } from '@metamask/controller-utils'; ///: BEGIN:ONLY_INCLUDE_IF(snaps) -import { stripSnapPrefix } from '@metamask/snaps-utils'; +import { + stripSnapPrefix, + getLocalizedSnapManifest, +} from '@metamask/snaps-utils'; import { memoize } from 'lodash'; import semver from 'semver'; ///: END:ONLY_INCLUDE_IF @@ -59,9 +62,6 @@ import { shortenAddress, getAccountByAddress, getURLHostName, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) - getSnapName, - ///: END:ONLY_INCLUDE_IF } from '../helpers/utils/util'; import { TEMPLATED_CONFIRMATION_APPROVAL_TYPES } from '../pages/confirmations/confirmation/templates'; @@ -1307,6 +1307,70 @@ export const getSnap = createDeepEqualSelector( }, ); +/** + * Get a selector that returns the snap manifest for a given `snapId`. + * + * @param {object} state - The Redux state object. + * @param {string} snapId - The snap ID to get the manifest for. + * @returns {object | undefined} The snap manifest. + */ +export const getSnapManifest = createDeepEqualSelector( + (state) => state.metamask.currentLocale, + (state, snapId) => getSnap(state, snapId), + (locale, snap) => { + if (!snap?.localizationFiles) { + return snap?.manifest; + } + + return getLocalizedSnapManifest( + snap.manifest, + locale, + snap.localizationFiles, + ); + }, +); + +/** + * Get a selector that returns the snap metadata (name and description) for a + * given `snapId`. + * + * @param {object} state - The Redux state object. + * @param {string} snapId - The snap ID to get the metadata for. + * @returns {object} An object containing the snap name and description. + */ +export const getSnapMetadata = createDeepEqualSelector( + (state, snapId) => getSnapManifest(state, snapId), + (_, snapId) => snapId, + (manifest, snapId) => { + return { + // The snap manifest may not be available if the Snap is not installed, so + // we use the snap ID as the name in that case. + name: manifest?.proposedName ?? (snapId ? stripSnapPrefix(snapId) : null), + description: manifest?.description, + }; + }, +); + +/** + * Get a selector that returns all snaps metadata (name and description) for a + * given `snapId`. + * + * @param {object} state - The Redux state object. + * @returns {object} An object mapping all installed snaps to their metadata, which contains the snap name and description. + */ +export const getSnapsMetadata = createDeepEqualSelector( + (state) => state, + (state) => { + return Object.keys(getSnaps(state)); + }, + (state, snapIds) => { + return snapIds.reduce((snapsMetadata, snapId) => { + snapsMetadata[snapId] = getSnapMetadata(state, snapId); + return snapsMetadata; + }, {}); + }, +); + export const getEnabledSnaps = createDeepEqualSelector(getSnaps, (snaps) => { return Object.values(snaps).reduce((acc, cur) => { if (cur.enabled) { @@ -2195,7 +2259,7 @@ export function getSnapsList(state) { iconUrl: targetSubjectMetadata?.iconUrl, subjectType: targetSubjectMetadata?.subjectType, packageName: stripSnapPrefix(snap.id), - name: getSnapName(snap.id, targetSubjectMetadata), + name: getSnapMetadata(state, snap.id).name, }; }); } diff --git a/ui/selectors/selectors.test.js b/ui/selectors/selectors.test.js index 6d16fe55340e..036bb1d5301c 100644 --- a/ui/selectors/selectors.test.js +++ b/ui/selectors/selectors.test.js @@ -960,7 +960,13 @@ describe('Selectors', () => { it('#getAllSnapAvailableUpdates', () => { const snapMap = selectors.getAllSnapAvailableUpdates(mockState); expect(Object.fromEntries(snapMap)).toStrictEqual({ + 'npm:@metamask/test-snap-bip32': false, 'npm:@metamask/test-snap-bip44': true, + 'npm:@metamask/test-snap-dialog': false, + 'npm:@metamask/test-snap-getEntropy': false, + 'npm:@metamask/test-snap-networkAccess': false, + 'npm:@metamask/test-snap-notify': false, + 'npm:@metamask/test-snap-wasm': false, }); });