From dbdfb88585bbe8b55d58c99110320e5e8e137b8e Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Tue, 21 Nov 2023 12:47:06 +0100 Subject: [PATCH 01/52] Add support for localised Snap manifests --- app/scripts/metamask-controller.js | 15 +- lavamoat/build-system/policy.json | 107 +++++++++++- .../snaps/snap-insight.js | 9 +- .../snap-authorship-expanded.js | 22 +-- .../snap-authorship-header.js | 17 +- .../app/snaps/snap-avatar/snap-avatar.js | 10 +- .../snap-connect-cell/snap-connect-cell.js | 12 +- .../snap-home-page/snap-home-renderer.js | 8 +- .../snap-legacy-authorship-header.js | 14 +- .../snap-permissions-list.js | 58 +++++-- .../snap-ui-renderer/snap-ui-renderer.js | 9 +- ui/helpers/utils/permission.js | 154 +++++++++--------- ui/helpers/utils/util.js | 13 +- ui/hooks/useTransactionInsights.js | 24 ++- ui/pages/confirmation/confirmation.js | 14 +- ui/pages/notifications/notifications.js | 14 +- .../snaps/snap-install/snap-install.js | 14 +- .../snaps/snap-result/snap-result.js | 8 +- .../snaps/snap-update/snap-update.js | 12 +- .../snaps/snaps-connect/snaps-connect.js | 20 +-- ui/pages/snaps/snap-view/snap-settings.js | 10 +- ui/pages/snaps/snap-view/snap-view.js | 17 +- ui/selectors/selectors.js | 48 +++++- 23 files changed, 380 insertions(+), 249 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 2cbd5f4ca002..5896fab48805 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -77,6 +77,7 @@ import { SnapController, IframeExecutionService, } from '@metamask/snaps-controllers'; +import { getLocalizedSnapManifest } from '@metamask/snaps-utils'; ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) @@ -2230,10 +2231,16 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger.subscribe( `${this.snapController.name}:snapAdded`, (snap, svgIcon = null) => { - const { - manifest: { proposedName }, - version, - } = snap; + const { manifest, localizationFiles, version } = snap; + + // In case the Snap uses a localized manifest, we need to get the + // proposed name from the localized manifest. + const { proposedName } = getLocalizedSnapManifest( + manifest, + 'en', + localizationFiles, + ); + this.subjectMetadataController.addSubjectMetadata({ subjectType: SubjectType.Snap, name: proposedName, diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index 5601c8594ced..2ace927ef7df 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -1080,6 +1080,16 @@ "react>object-assign": true } }, + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>has-unicode": { + "builtin": { + "os.type": true + }, + "globals": { + "process.env.LANG": true, + "process.env.LC_ALL": true, + "process.env.LC_CTYPE": true + } + }, "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>string-width": { "packages": { "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>string-width>is-fullwidth-code-point": true, @@ -1097,6 +1107,11 @@ "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>strip-ansi>ansi-regex": true } }, + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>wide-align": { + "packages": { + "yargs>string-width": true + } + }, "@lavamoat/lavapack": { "builtin": { "assert": true, @@ -4922,6 +4937,7 @@ "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog": true, "gulp-watch>chokidar>fsevents>node-pre-gyp>detect-libc": true, "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog": true, "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf": true, "gulp-watch>chokidar>fsevents>node-pre-gyp>semver": true } @@ -4979,9 +4995,20 @@ }, "packages": { "@storybook/core>@storybook/core-server>x-default-browser>default-browser-id>untildify>os-homedir": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-homedir": true, "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-tmpdir": true } }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-homedir": { + "builtin": { + "os.homedir": true + }, + "globals": { + "process.env": true, + "process.getuid": true, + "process.platform": true + } + }, "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-tmpdir": { "globals": { "process.env.SystemRoot": true, @@ -4992,6 +5019,70 @@ "process.platform": true } }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog": { + "builtin": { + "events.EventEmitter": true, + "util": true + }, + "globals": { + "process.nextTick": true, + "process.stderr": true + }, + "packages": { + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>console-control-strings": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge": true, + "nyc>yargs>set-blocking": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet": { + "builtin": { + "events.EventEmitter": true, + "util.inherits": true + }, + "packages": { + "koa>delegates": true, + "readable-stream": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge": { + "builtin": { + "util.format": true + }, + "globals": { + "clearInterval": true, + "process": true, + "setImmediate": true, + "setInterval": true + }, + "packages": { + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>console-control-strings": true, + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>has-unicode": true, + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>wide-align": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>aproba": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>string-width": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>strip-ansi": true, + "nyc>signal-exit": true, + "react>object-assign": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>string-width": { + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>string-width>is-fullwidth-code-point": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>strip-ansi": true, + "gulp>gulp-cli>yargs>string-width>code-point-at": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>string-width>is-fullwidth-code-point": { + "packages": { + "gulp>gulp-cli>yargs>string-width>is-fullwidth-code-point>number-is-nan": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>strip-ansi": { + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>strip-ansi>ansi-regex": true + } + }, "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf": { "builtin": { "assert": true, @@ -6398,6 +6489,13 @@ "chalk>supports-color>has-flag": true } }, + "mockttp>portfinder>mkdirp": { + "builtin": { + "fs": true, + "path.dirname": true, + "path.resolve": true + } + }, "nock>debug": { "builtin": { "tty.isatty": true, @@ -8005,14 +8103,7 @@ "path.dirname": true }, "packages": { - "stylelint>file-entry-cache>flat-cache>write>mkdirp": true - } - }, - "stylelint>file-entry-cache>flat-cache>write>mkdirp": { - "builtin": { - "fs": true, - "path.dirname": true, - "path.resolve": true + "mockttp>portfinder>mkdirp": true } }, "stylelint>global-modules": { diff --git a/ui/components/app/confirm-page-container/snaps/snap-insight.js b/ui/components/app/confirm-page-container/snaps/snap-insight.js index 9e1c9b97c75b..901c25e44c5a 100644 --- a/ui/components/app/confirm-page-container/snaps/snap-insight.js +++ b/ui/components/app/confirm-page-container/snaps/snap-insight.js @@ -26,9 +26,8 @@ import Box from '../../../ui/box/box'; import { SnapUIRenderer } from '../../snaps/snap-ui-renderer'; import { SnapDelineator } from '../../snaps/snap-delineator'; import { DelineatorType } from '../../../../helpers/constants/snaps'; -import { getSnapName } from '../../../../helpers/utils/util'; import { Copyable } from '../../snaps/copyable'; -import { getTargetSubjectMetadata } from '../../../../selectors'; +import { getSnapMetadata } from '../../../../selectors'; ///: BEGIN:ONLY_INCLUDE_IN(build-flask) import { trackInsightSnapUsage } from '../../../../store/actions'; ///: END:ONLY_INCLUDE_IN @@ -72,12 +71,10 @@ export const SnapInsight = ({ isLoading = insights.loading; ///: END:ONLY_INCLUDE_IN - const targetSubjectMetadata = useSelector((state) => - getTargetSubjectMetadata(state, snapId), + const { name: snapName } = useSelector((state) => + getSnapMetadata(state, snapId), ); - const snapName = getSnapName(snapId, targetSubjectMetadata); - const hasNoData = !error && !isLoading && !content; return ( { ? `https://www.npmjs.com/package/${packageName}${versionPath}` : packageName; - const subjectMetadata = useSelector((state) => - getTargetSubjectMetadata(state, snapId), - ); const snapRegistryData = useSelector((state) => getSnapRegistryData(state, snapId), ); + + const { name: friendlyName } = useSelector((state) => + getSnapMetadata(state, snapId), + ); + const { website = undefined } = snapRegistryData?.metadata ?? {}; const [safeWebsite, setSafeWebsite] = useState(null); @@ -75,13 +70,12 @@ const SnapAuthorshipExpanded = ({ snapId, className, snap }) => { setSafeWebsite(website); } }; + if (website) { performPhishingCheck(); } }, [website]); - const friendlyName = snapId && getSnapName(snapId, subjectMetadata); - const versionHistory = snap?.versionHistory ?? []; const installInfo = versionHistory.length ? versionHistory[versionHistory.length - 1] diff --git a/ui/components/app/snaps/snap-authorship-header/snap-authorship-header.js b/ui/components/app/snaps/snap-authorship-header/snap-authorship-header.js index 139bb8360942..50760db222ca 100644 --- a/ui/components/app/snaps/snap-authorship-header/snap-authorship-header.js +++ b/ui/components/app/snaps/snap-authorship-header/snap-authorship-header.js @@ -13,13 +13,13 @@ import { BlockSize, FontWeight, } from '../../../../helpers/constants/design-system'; +import { removeSnapIdPrefix } from '../../../../helpers/utils/util'; import { - getSnapName, - removeSnapIdPrefix, -} from '../../../../helpers/utils/util'; + getSnapMetadata, + getTargetSubjectMetadata, +} from '../../../../selectors'; import { Text, Box } from '../../../component-library'; -import { getTargetSubjectMetadata } from '../../../../selectors'; import SnapAvatar from '../snap-avatar'; import SnapVersion from '../snap-version/snap-version'; @@ -40,15 +40,18 @@ const SnapAuthorshipHeader = ({ getTargetSubjectMetadata(state, snapId), ); + const { name: snapName } = useSelector((state) => + getSnapMetadata(state, snapId), + ); + const versionPath = subjectMetadata?.version ? `/v/${subjectMetadata?.version}` : ''; + const url = isNPM ? `https://www.npmjs.com/package/${packageName}${versionPath}` : packageName; - const friendlyName = snapId && getSnapName(snapId, subjectMetadata); - return ( - {friendlyName} + {snapName} + getSnapMetadata(state, snapId), + ); const iconUrl = subjectMetadata?.iconUrl; diff --git a/ui/components/app/snaps/snap-connect-cell/snap-connect-cell.js b/ui/components/app/snaps/snap-connect-cell/snap-connect-cell.js index f168b7920d6e..32bcfe7e9ee9 100644 --- a/ui/components/app/snaps/snap-connect-cell/snap-connect-cell.js +++ b/ui/components/app/snaps/snap-connect-cell/snap-connect-cell.js @@ -8,7 +8,6 @@ import { Display, FontWeight, } from '../../../../helpers/constants/design-system'; -import { getSnapName } from '../../../../helpers/utils/util'; import { Icon, IconName, @@ -19,14 +18,13 @@ import { import Tooltip from '../../../ui/tooltip/tooltip'; import { useI18nContext } from '../../../../hooks/useI18nContext'; import SnapAvatar from '../snap-avatar/snap-avatar'; -import { getTargetSubjectMetadata } from '../../../../selectors'; +import { getSnapManifest } from '../../../../selectors'; export default function SnapConnectCell({ origin, snapId }) { const t = useI18nContext(); - const snapMetadata = useSelector((state) => - 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-home-page/snap-home-renderer.js b/ui/components/app/snaps/snap-home-page/snap-home-renderer.js index 97e2ea411974..b4a11dc641b4 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 { 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'; @@ -14,11 +13,10 @@ import { useSnapHome } from './useSnapHome'; export const SnapHomeRenderer = ({ snapId }) => { 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 content = !loading && !error && data?.content; 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 0d2f3e66b807..7ce5b6442ee9 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 @@ -14,13 +14,10 @@ import { BorderColor, BorderRadius, } from '../../../../helpers/constants/design-system'; -import { - getSnapName, - removeSnapIdPrefix, -} from '../../../../helpers/utils/util'; +import { removeSnapIdPrefix } 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 = ({ @@ -30,13 +27,10 @@ const SnapLegacyAuthorshipHeader = ({ marginRight, }) => { const packageName = snapId && removeSnapIdPrefix(snapId); - - const subjectMetadata = useSelector((state) => - getTargetSubjectMetadata(state, snapId), + const { name: friendlyName } = useSelector((state) => + getSnapMetadata(state, snapId), ); - const friendlyName = snapId && getSnapName(snapId, subjectMetadata); - return ( value); + + /** + * Get the snap name from the snap ID. + * + * This is used to get the names for permissions which include snap IDs as + * caveat. + * + * @param id - The snap ID. + * @returns {string | undefined} The snap name if it exists, or `undefined`. + */ + const getSnapName = (id) => { + const snap = getSnapMetadata(state, id); + return snap?.name; + }; return ( - {getWeightedPermissions(t, permissions, targetSubjectMetadata).map( - (permission, index) => { - return ( - - ); - }, - )} + {getWeightedPermissions( + t, + permissions, + targetSubjectMetadata, + snapName, + getSnapName, + ).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-ui-renderer/snap-ui-renderer.js b/ui/components/app/snaps/snap-ui-renderer/snap-ui-renderer.js index b1149d7cf16a..b904577d9da6 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 @@ -15,8 +15,7 @@ import { import { SnapDelineator } from '../snap-delineator'; import { useI18nContext } from '../../../../hooks/useI18nContext'; import Box from '../../../ui/box'; -import { getSnapName } from '../../../../helpers/utils/util'; -import { getTargetSubjectMetadata } from '../../../../selectors'; +import { getSnapMetadata } from '../../../../selectors'; import { Text } from '../../../component-library'; import { Copyable } from '../copyable'; import { DelineatorType } from '../../../../helpers/constants/snaps'; @@ -100,12 +99,10 @@ export const SnapUIRenderer = ({ boxProps, }) => { const t = useI18nContext(); - const targetSubjectMetadata = useSelector((state) => - getTargetSubjectMetadata(state, snapId), + const { name: snapName } = useSelector((state) => + getSnapMetadata(state, snapId), ); - const snapName = getSnapName(snapId, targetSubjectMetadata); - if (isLoading) { return ( - {getSnapName(targetSubjectMetadata?.origin, targetSubjectMetadata)} + {snapName} ); } @@ -78,18 +80,18 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ weight: 2, }), ///: BEGIN:ONLY_INCLUDE_IN(snaps) - [RestrictedMethods.snap_dialog]: ({ t, targetSubjectMetadata }) => ({ + [RestrictedMethods.snap_dialog]: ({ t, snapName }) => ({ label: t('permission_dialog'), description: t('permission_dialogDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), leftIcon: IconName.Messages, weight: 3, }), - [RestrictedMethods.snap_notify]: ({ t, targetSubjectMetadata }) => ({ + [RestrictedMethods.snap_notify]: ({ t, snapName }) => ({ label: t('permission_notifications'), description: t('permission_notificationsDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), leftIcon: IconName.Notification, weight: 3, @@ -97,7 +99,7 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ [RestrictedMethods.snap_getBip32PublicKey]: ({ t, permissionValue, - targetSubjectMetadata, + snapName, }) => permissionValue.caveats[0].value.map(({ path, curve }, i) => { const baseDescription = { @@ -160,14 +162,14 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ > {path.join('/')} , - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), }; }), [RestrictedMethods.snap_getBip32Entropy]: ({ t, permissionValue, - targetSubjectMetadata, + snapName, }) => permissionValue.caveats[0].value.map(({ path, curve }, i) => { const baseDescription = { @@ -196,7 +198,7 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ , ]), description: t('permission_manageBip44AndBip32KeysDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), }; } @@ -214,14 +216,14 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ , ]), description: t('permission_manageBip44AndBip32KeysDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), }; }), [RestrictedMethods.snap_getBip44Entropy]: ({ t, permissionValue, - targetSubjectMetadata, + snapName, }) => permissionValue.caveats[0].value.map(({ coinType }, i) => ({ label: t('permission_manageBip44Keys', [ @@ -236,7 +238,7 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ , ]), description: t('permission_manageBip44AndBip32KeysDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), leftIcon: IconName.Key, weight: 1, @@ -245,38 +247,32 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ coinTypeToProtocolName(coinType) || `${t('unknownNetworkForKeyEntropy')} m/44'/${coinType}'`, })), - [RestrictedMethods.snap_getEntropy]: ({ t, targetSubjectMetadata }) => ({ - label: t('permission_getEntropy', [ - getSnapNameComponent(targetSubjectMetadata), - ]), + [RestrictedMethods.snap_getEntropy]: ({ t, snapName }) => ({ + label: t('permission_getEntropy', [getSnapNameComponent(snapName)]), description: t('permission_getEntropyDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), leftIcon: IconName.SecurityKey, weight: 3, }), - [RestrictedMethods.snap_manageState]: ({ t, targetSubjectMetadata }) => ({ + [RestrictedMethods.snap_manageState]: ({ t, snapName }) => ({ label: t('permission_manageState'), description: t('permission_manageStateDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), leftIcon: IconName.AddSquare, weight: 3, }), - [RestrictedMethods.snap_getLocale]: ({ t, targetSubjectMetadata }) => ({ + [RestrictedMethods.snap_getLocale]: ({ t, snapName }) => ({ label: t('permission_getLocale'), description: t('permission_getLocaleDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), leftIcon: IconName.Global, weight: 3, }), - [RestrictedMethods.wallet_snap]: ({ - t, - permissionValue, - targetSubjectMetadata, - }) => { + [RestrictedMethods.wallet_snap]: ({ t, permissionValue, getSnapName }) => { const snaps = permissionValue.caveats[0].value; const baseDescription = { leftIcon: getLeftIcon(IconName.Flash), @@ -284,7 +280,7 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ }; return Object.keys(snaps).map((snapId) => { - const friendlyName = getSnapName(snapId, targetSubjectMetadata); + const friendlyName = getSnapName(snapId); if (friendlyName) { return { ...baseDescription, @@ -309,24 +305,18 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ }; }); }, - [EndowmentPermissions['endowment:network-access']]: ({ - t, - targetSubjectMetadata, - }) => ({ + [EndowmentPermissions['endowment:network-access']]: ({ t, snapName }) => ({ label: t('permission_accessNetwork'), description: t('permission_accessNetworkDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), leftIcon: IconName.Wifi, weight: 2, }), - [EndowmentPermissions['endowment:webassembly']]: ({ - t, - targetSubjectMetadata, - }) => ({ + [EndowmentPermissions['endowment:webassembly']]: ({ t, snapName }) => ({ label: t('permission_webAssembly'), description: t('permission_webAssemblyDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), leftIcon: IconName.DocumentCode, rightIcon: null, @@ -335,7 +325,7 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ [EndowmentPermissions['endowment:transaction-insight']]: ({ t, permissionValue, - targetSubjectMetadata, + snapName, }) => { const baseDescription = { leftIcon: IconName.Speedometer, @@ -347,7 +337,7 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ ...baseDescription, label: t('permission_transactionInsight'), description: t('permission_transactionInsightDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), }, ]; @@ -361,7 +351,7 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ ...baseDescription, label: t('permission_transactionInsightOrigin'), description: t('permission_transactionInsightOriginDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), leftIcon: IconName.Explore, }); @@ -369,36 +359,28 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ return result; }, - [EndowmentPermissions['endowment:cronjob']]: ({ - t, - targetSubjectMetadata, - }) => ({ + [EndowmentPermissions['endowment:cronjob']]: ({ t, snapName }) => ({ label: t('permission_cronjob'), description: t('permission_cronjobDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), leftIcon: IconName.Clock, weight: 2, }), - [EndowmentPermissions['endowment:ethereum-provider']]: ({ - t, - targetSubjectMetadata, - }) => ({ + [EndowmentPermissions['endowment:ethereum-provider']]: ({ t, snapName }) => ({ label: t('permission_ethereumProvider'), description: t('permission_ethereumProviderDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), leftIcon: IconName.Ethereum, weight: 2, id: 'ethereum-provider-access', - message: t('ethereumProviderAccess', [ - getSnapNameComponent(targetSubjectMetadata), - ]), + message: t('ethereumProviderAccess', [getSnapNameComponent(snapName)]), }), [EndowmentPermissions['endowment:rpc']]: ({ t, permissionValue, - targetSubjectMetadata, + snapName, }) => { const baseDescription = { leftIcon: IconName.Hierarchy, @@ -413,11 +395,11 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ ...baseDescription, label: t('permission_rpc', [ t('otherSnaps'), - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), description: t('permission_rpcDescription', [ t('otherSnaps'), - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), }); } @@ -427,11 +409,11 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ ...baseDescription, label: t('permission_rpc', [ t('websites'), - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), description: t('permission_rpcDescription', [ t('websites'), - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), }); } @@ -481,24 +463,21 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ ...baseDescription, label: t('permission_rpc', [ originsMessage, - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), description: t('permission_rpcDescription', [ originsMessage, - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), }); } return results; }, - [EndowmentPermissions['endowment:lifecycle-hooks']]: ({ - t, - targetSubjectMetadata, - }) => ({ + [EndowmentPermissions['endowment:lifecycle-hooks']]: ({ t, snapName }) => ({ label: t('permission_lifecycleHooks'), description: t('permission_lifecycleHooksDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), leftIcon: IconName.Hierarchy, weight: 3, @@ -516,22 +495,19 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ }), ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) - [RestrictedMethods.snap_manageAccounts]: ({ t, targetSubjectMetadata }) => ({ + [RestrictedMethods.snap_manageAccounts]: ({ t, snapName }) => ({ label: t('permission_manageAccounts'), description: t('permission_manageAccountsDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), leftIcon: getLeftIcon(IconName.UserCircleAdd), rightIcon: null, weight: 2, }), - [EndowmentPermissions['endowment:keyring']]: ({ - t, - targetSubjectMetadata, - }) => ({ + [EndowmentPermissions['endowment:keyring']]: ({ t, snapName }) => ({ label: t('permission_keyring'), description: t('permission_keyringDescription', [ - getSnapNameComponent(targetSubjectMetadata), + getSnapNameComponent(snapName), ]), leftIcon: getLeftIcon(IconName.UserCircleAdd), rightIcon: null, @@ -571,6 +547,10 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({ * @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 + * @param {object} params.targetSubjectMetadata - The subject metadata. + * @param {string} params.snapName - The snap name. + * @param {Function} [params.getSnapName] - The function to get the snap name + * from a snap ID. * @returns {PermissionLabelObject[]} */ export const getPermissionDescription = ({ @@ -578,6 +558,8 @@ export const getPermissionDescription = ({ permissionName, permissionValue, targetSubjectMetadata, + snapName, + getSnapName, }) => { let value = PERMISSION_DESCRIPTIONS[UNKNOWN_PERMISSION]; @@ -590,7 +572,10 @@ export const getPermissionDescription = ({ permissionName, permissionValue, targetSubjectMetadata, + snapName, + getSnapName, }); + if (!Array.isArray(result)) { return [{ ...result, permissionName, permissionValue }]; } @@ -609,9 +594,18 @@ export const getPermissionDescription = ({ * @param {Function} t - The translation function * @param {object} permissions - The permissions object. * @param {object} targetSubjectMetadata - The subject metadata. + * @param {string} [snapName] - The snap name. + * @param {Function} [getSnapName] - The function to get the snap name from a + * snap ID. * @returns {PermissionLabelObject[]} */ -export function getWeightedPermissions(t, permissions, targetSubjectMetadata) { +export function getWeightedPermissions( + t, + permissions, + targetSubjectMetadata, + snapName, + getSnapName, +) { return Object.entries(permissions) .reduce( (target, [permissionName, permissionValue]) => @@ -621,6 +615,8 @@ export function getWeightedPermissions(t, permissions, targetSubjectMetadata) { permissionName, permissionValue, targetSubjectMetadata, + snapName, + getSnapName, }), ), [], diff --git a/ui/helpers/utils/util.js b/ui/helpers/utils/util.js index 8b3c05e56cfe..22fb0a045ade 100644 --- a/ui/helpers/utils/util.js +++ b/ui/helpers/utils/util.js @@ -29,10 +29,7 @@ import { import { Numeric } from '../../../shared/modules/Numeric'; import { OUTDATED_BROWSER_VERSIONS } from '../constants/common'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) -import { - SNAPS_DERIVATION_PATHS, - SNAPS_METADATA, -} from '../../../shared/constants/snaps'; +import { SNAPS_DERIVATION_PATHS } from '../../../shared/constants/snaps'; ///: END:ONLY_INCLUDE_IN // formatData :: ( date: ) -> String import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; @@ -613,14 +610,6 @@ export function getSnapDerivationPathName(path, curve) { export const removeSnapIdPrefix = (snapId) => snapId?.replace(getSnapPrefix(snapId), ''); -export const getSnapName = (snapId, subjectMetadata) => { - if (SNAPS_METADATA[snapId]?.name) { - return SNAPS_METADATA[snapId].name; - } - - return subjectMetadata?.name ?? removeSnapIdPrefix(snapId); -}; - export const getSnapRoute = (snapId) => { return `${SNAPS_VIEW_ROUTE}/${encodeURIComponent(snapId)}`; }; diff --git a/ui/hooks/useTransactionInsights.js b/ui/hooks/useTransactionInsights.js index 0bacb52599ef..73a0fa426f3c 100644 --- a/ui/hooks/useTransactionInsights.js +++ b/ui/hooks/useTransactionInsights.js @@ -6,13 +6,13 @@ import { stripHexPrefix } from '../../shared/modules/hexstring-utils'; import { TransactionType } from '../../shared/constants/transaction'; import { Tab } from '../components/ui/tabs'; import DropdownTab from '../components/ui/tabs/snaps/dropdown-tab'; -import { SnapInsight } from '../components/app/confirm-page-container/snaps/snap-insight'; +import { SnapInsight } from '../components/app/confirm-page-container'; import { getInsightSnapIds, getInsightSnaps, + getSnapMetadata, getSubjectMetadataDeepEqual, } from '../selectors'; -import { getSnapName } from '../helpers/utils/util'; import { useTransactionInsightSnaps } from './snaps/useTransactionInsightSnaps'; const isAllowedTransactionTypes = (transactionType) => @@ -29,8 +29,22 @@ 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 state = useSelector((value) => value); + + /** + * Get the snap name from the snap ID. + * + * This is used to get the names for permissions which include snap IDs as + * caveat. + * + * @param id - The snap ID. + * @returns {string | undefined} The snap name if it exists, or `undefined`. + */ + const getSnapName = (id) => { + const snap = getSnapMetadata(state, id); + return snap?.name; + }; const [selectedInsightSnapId, setSelectedInsightSnapId] = useState( insightSnaps[0]?.id, @@ -75,7 +89,7 @@ const useTransactionInsights = ({ txData }) => { insightComponent = ( { ); } else if (insightSnaps.length > 1) { const dropdownOptions = insightSnaps?.map(({ id }) => { - const name = getSnapName(id, subjectMetadata[id]); + const name = getSnapName(id); return { value: id, name, diff --git a/ui/pages/confirmation/confirmation.js b/ui/pages/confirmation/confirmation.js index 2f5c4eb1dae6..1fbc32007777 100644 --- a/ui/pages/confirmation/confirmation.js +++ b/ui/pages/confirmation/confirmation.js @@ -21,14 +21,12 @@ import { DEFAULT_ROUTE } from '../../helpers/constants/routes'; import { Size, TextColor } from '../../helpers/constants/design-system'; import { useI18nContext } from '../../hooks/useI18nContext'; import { - ///: BEGIN:ONLY_INCLUDE_IN(snaps) - getTargetSubjectMetadata, - ///: END:ONLY_INCLUDE_IN getUnapprovedTemplatedConfirmations, getUnapprovedTxCount, getApprovalFlows, getTotalUnapprovedCount, useSafeChainsListValidationSelector, + getSnapMetadata, } from '../../selectors'; import NetworkDisplay from '../../components/app/network-display/network-display'; import Callout from '../../components/ui/callout'; @@ -36,7 +34,6 @@ import { Icon, IconName } from '../../components/component-library'; import Loading from '../../components/ui/loading-screen'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) import SnapAuthorshipHeader from '../../components/app/snaps/snap-authorship-header'; -import { getSnapName } from '../../helpers/utils/util'; ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) import { SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES } from '../../../shared/constants/app'; @@ -207,8 +204,8 @@ export default function ConfirmationPage({ const [submitAlerts, setSubmitAlerts] = useState([]); ///: BEGIN:ONLY_INCLUDE_IN(snaps) - const targetSubjectMetadata = useSelector((state) => - getTargetSubjectMetadata(state, pendingConfirmation?.origin), + const { name } = useSelector((state) => + getSnapMetadata(state, pendingConfirmation?.origin), ); const SNAP_DIALOG_TYPE = [ @@ -228,10 +225,7 @@ export default function ConfirmationPage({ const isSnapDialog = SNAP_DIALOG_TYPE.includes(pendingConfirmation?.type); // When pendingConfirmation is undefined, this will also be undefined - const snapName = - isSnapDialog && - targetSubjectMetadata && - getSnapName(pendingConfirmation?.origin, targetSubjectMetadata); + const snapName = isSnapDialog && name; ///: END:ONLY_INCLUDE_IN const INPUT_STATE_CONFIRMATIONS = [ 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 8846c7f4f427..78cce9e05c8b 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,12 @@ import { ValidTag, Text, } 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 } from '../../../../selectors'; export default function SnapInstall({ request, @@ -57,8 +58,11 @@ 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( @@ -69,11 +73,6 @@ export default function SnapInstall({ const shouldShowWarning = warnings.length > 0; - const snapName = getSnapName( - targetSubjectMetadata.origin, - targetSubjectMetadata, - ); - const handleSubmit = () => { if (!hasError && shouldShowWarning) { setIsShowingWarning(true); @@ -180,6 +179,7 @@ export default function SnapInstall({ 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 7fc94432d9cd..1a85af466cf1 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'; @@ -29,8 +30,8 @@ import { Text, } from '../../../../components/component-library'; import { useOriginMetadata } from '../../../../hooks/useOriginMetadata'; -import { getSnapName } from '../../../../helpers/utils/util'; import { useScrollRequired } from '../../../../hooks/useScrollRequired'; +import { getSnapMetadata } from '../../../../selectors'; export default function SnapUpdate({ request, @@ -57,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 ?? {}; @@ -73,11 +78,6 @@ export default function SnapUpdate({ 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 ( + getSnapMetadata(state, snapId), + ); + let isKeyringSnap = false; ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) isKeyringSnap = Boolean(subjects[snap?.id]?.permissions?.snap_manageAccounts); @@ -139,8 +143,6 @@ function SnapSettings({ snapId }) { } }; - const snapName = getSnapName(snap.id, targetSubjectMetadata); - const shouldDisplayMoreButton = isOverflowing && !isDescriptionOpen; const handleMoreClick = () => { setIsDescriptionOpen(true); @@ -157,7 +159,7 @@ function SnapSettings({ snapId }) { })} ref={descriptionRef} > - {snap?.manifest.description} + {description} {shouldDisplayMoreButton && (