1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 01:47:00 +01:00

Sort permissions based on weight (#17660)

This commit is contained in:
Maarten Zuidhoorn 2023-02-08 15:28:48 +01:00 committed by GitHub
parent b5c1de900d
commit ff85b040cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 193 additions and 124 deletions

View File

@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { getPermissionDescription } from '../../../../helpers/utils/permission';
import { isFunction } from 'lodash';
import { getWeightedPermissions } from '../../../../helpers/utils/permission';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import { formatDate } from '../../../../helpers/utils/util';
import Typography from '../../../ui/typography/typography';
@ -13,97 +14,50 @@ export default function UpdateSnapPermissionList({
}) {
const t = useI18nContext();
const ApprovedPermissions = () => {
return Object.entries(approvedPermissions).map(
([permissionName, permissionValue]) => {
const permissions = getPermissionDescription(
t,
permissionName,
permissionValue,
);
const { date } = permissionValue;
const formattedDate = formatDate(date, 'yyyy-MM-dd');
return permissions.map(({ label, rightIcon }) => (
<div className="approved-permission" key={permissionName}>
<i className="fas fa-check" />
<div className="permission-description">
{label}
<Typography
color={TextColor.textAlternative}
className="permission-description-subtext"
boxProps={{ paddingTop: 1 }}
>
{t('approvedOn', [formattedDate])}
</Typography>
</div>
{rightIcon && <i className={rightIcon} />}
const Permissions = ({ className, permissions, subText }) => {
return getWeightedPermissions(t, permissions).map(
({ label, rightIcon, permissionName, permissionValue }) => (
<div className={className} key={permissionName}>
<i className="fas fa-x" />
<div className="permission-description">
{label}
<Typography
color={TextColor.textAlternative}
boxProps={{ paddingTop: 1 }}
className="permission-description-subtext"
>
{isFunction(subText)
? subText(permissionName, permissionValue)
: subText}
</Typography>
</div>
));
},
);
};
const RevokedPermissions = () => {
return Object.entries(revokedPermissions).map(
([permissionName, permissionValue]) => {
const permissions = getPermissionDescription(
t,
permissionName,
permissionValue,
);
return permissions.map(({ label, rightIcon }) => (
<div className="revoked-permission" key={permissionName}>
<i className="fas fa-x" />
<div className="permission-description">
{label}
<Typography
color={TextColor.textAlternative}
boxProps={{ paddingTop: 1 }}
className="permission-description-subtext"
>
{t('permissionRevoked')}
</Typography>
</div>
{rightIcon && <i className={rightIcon} />}
</div>
));
},
);
};
const NewPermissions = () => {
return Object.entries(newPermissions).map(
([permissionName, permissionValue]) => {
const permissions = getPermissionDescription(
t,
permissionName,
permissionValue,
);
return permissions.map(({ label, rightIcon }) => (
<div className="new-permission" key={permissionName}>
<i className="fas fa-arrow-right" />
<div className="permission-description">
{label}
<Typography
color={TextColor.textAlternative}
boxProps={{ paddingTop: 1 }}
className="permission-description-subtext"
>
{t('permissionRequested')}
</Typography>
</div>
{rightIcon && <i className={rightIcon} />}
</div>
));
},
{rightIcon && <i className={rightIcon} />}
</div>
),
);
};
return (
<div className="update-snap-permission-list">
<NewPermissions />
<ApprovedPermissions />
<RevokedPermissions />
<Permissions
className="new-permission"
permissions={newPermissions}
subText={t('permissionRequested')}
/>
<Permissions
className="approved-permission"
permissions={approvedPermissions}
subText={(_, permissionValue) => {
const { date } = permissionValue;
const formattedDate = formatDate(date, 'yyyy-MM-dd');
return t('approvedOn', [formattedDate]);
}}
/>
<Permissions
className="revoked-permission"
permissions={revokedPermissions}
subText={t('permissionRevoked')}
/>
</div>
);
}

View File

@ -0,0 +1,51 @@
import React from 'react';
import UpdateSnapPermissionList from './update-snap-permission-list';
export default {
title: 'Components/App/UpdateSnapPermissionList',
component: UpdateSnapPermissionList,
argTypes: {
permissions: {
control: 'object',
},
},
};
export const DefaultStory = (args) => <UpdateSnapPermissionList {...args} />;
DefaultStory.storyName = 'Default';
DefaultStory.args = {
approvedPermissions: {
'endowment:network-access': {
date: 1620710693178,
},
snap_getBip32PublicKey: {
date: 1620710693178,
caveats: [
{
value: [
{
path: ['m', `44'`, `0'`],
curve: 'secp256k1',
},
],
},
],
},
},
revokedPermissions: {
snap_notify: {
date: 1620710693178,
},
eth_accounts: {
date: 1620710693178,
},
},
newPermissions: {
snap_dialog: {
date: 1620710693178,
},
},
};

View File

@ -1,30 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import { getPermissionDescription } from '../../../helpers/utils/permission';
import { getWeightedPermissions } from '../../../helpers/utils/permission';
import { useI18nContext } from '../../../hooks/useI18nContext';
/**
* Get one or more permission descriptions for a permission name.
*
* @param t - The translation function.
* @param permissionName - The name of the permission to request.
* @param permissionValue - The value of the permission to request.
* @param permission - The permission to render.
* @param permission.label - The text label.
* @param permission.leftIcon - The left icon.
* @param permission.rightIcon - The right icon.
* @param permission.permissionName - The name of the permission.
* @param index - The index of the permission in the permissions array.
* @returns {JSX.Element[]} An array of permission description nodes.
*/
function getDescriptionNodes(t, permissionName, permissionValue) {
const permissions = getPermissionDescription(
t,
permissionName,
permissionValue,
);
return permissions.map(({ label, leftIcon, rightIcon }, index) => (
function getDescriptionNode(
{ label, leftIcon, rightIcon, permissionName },
index,
) {
return (
<div className="permission" key={`${permissionName}-${index}`}>
<i className={leftIcon} />
{label}
{rightIcon && <i className={rightIcon} />}
</div>
));
);
}
export default function PermissionsConnectPermissionList({ permissions }) {
@ -32,13 +32,7 @@ export default function PermissionsConnectPermissionList({ permissions }) {
return (
<div className="permissions-connect-permission-list">
{Object.entries(permissions).reduce(
(target, [permissionName, permissionValue]) =>
target.concat(
getDescriptionNodes(t, permissionName, permissionValue),
),
[],
)}
{getWeightedPermissions(t, permissions).map(getDescriptionNode)}
</div>
);
}

View File

@ -19,6 +19,19 @@ DefaultStory.storyName = 'Default';
DefaultStory.args = {
permissions: {
eth_accounts: true,
eth_accounts: {},
snap_dialog: {},
snap_getBip32PublicKey: {
caveats: [
{
value: [
{
path: ['m', `44'`, `0'`],
curve: 'secp256k1',
},
],
},
],
},
},
};

View File

@ -24,75 +24,87 @@ const PERMISSION_DESCRIPTIONS = deepFreeze({
label: t('permission_ethereumAccounts'),
leftIcon: 'fas fa-eye',
rightIcon: null,
weight: 2,
}),
///: BEGIN:ONLY_INCLUDE_IN(flask)
[RestrictedMethods.snap_confirm]: (t) => ({
label: t('permission_customConfirmation'),
leftIcon: 'fas fa-user-check',
rightIcon: null,
weight: 3,
}),
[RestrictedMethods.snap_dialog]: (t) => ({
label: t('permission_dialog'),
leftIcon: 'fas fa-user-check',
rightIcon: null,
weight: 3,
}),
[RestrictedMethods.snap_notify]: (t) => ({
leftIcon: 'fas fa-bell',
label: t('permission_notifications'),
rightIcon: null,
weight: 3,
}),
[RestrictedMethods.snap_getBip32PublicKey]: (t, _, permissionValue) =>
permissionValue.caveats[0].value.map(({ path, curve }) => {
const baseDescription = {
leftIcon: 'fas fa-eye',
rightIcon: null,
weight: 1,
};
const friendlyName = getSnapDerivationPathName(path, curve);
if (friendlyName) {
return {
...baseDescription,
label: t('permission_viewNamedBip32PublicKeys', [
<span className="permission-label-item" key={path.join('/')}>
{friendlyName}
</span>,
path.join('/'),
]),
leftIcon: 'fas fa-eye',
rightIcon: null,
};
}
return {
...baseDescription,
label: t('permission_viewBip32PublicKeys', [
<span className="permission-label-item" key={path.join('/')}>
{path.join('/')}
</span>,
curve,
]),
leftIcon: 'fas fa-eye',
rightIcon: null,
};
}),
[RestrictedMethods.snap_getBip32Entropy]: (t, _, permissionValue) =>
permissionValue.caveats[0].value.map(({ path, curve }) => {
const baseDescription = {
leftIcon: 'fas fa-door-open',
rightIcon: null,
weight: 1,
};
const friendlyName = getSnapDerivationPathName(path, curve);
if (friendlyName) {
return {
...baseDescription,
label: t('permission_manageNamedBip32Keys', [
<span className="permission-label-item" key={path.join('/')}>
{friendlyName}
</span>,
path.join('/'),
]),
leftIcon: 'fas fa-door-open',
rightIcon: null,
};
}
return {
...baseDescription,
label: t('permission_manageBip32Keys', [
<span className="permission-label-item" key={path.join('/')}>
{path.join('/')}
</span>,
curve,
]),
leftIcon: 'fas fa-door-open',
rightIcon: null,
};
}),
[RestrictedMethods.snap_getBip44Entropy]: (t, _, permissionValue) =>
@ -105,59 +117,71 @@ const PERMISSION_DESCRIPTIONS = deepFreeze({
]),
leftIcon: 'fas fa-door-open',
rightIcon: null,
weight: 1,
})),
[RestrictedMethods.snap_getEntropy]: (t) => ({
label: t('permission_getEntropy'),
leftIcon: 'fas fa-key',
rightIcon: null,
weight: 3,
}),
[RestrictedMethods.snap_manageState]: (t) => ({
label: t('permission_manageState'),
leftIcon: 'fas fa-download',
rightIcon: null,
weight: 3,
}),
[RestrictedMethods['wallet_snap_*']]: (t, permissionName) => {
const baseDescription = {
leftIcon: 'fas fa-bolt',
rightIcon: null,
};
const snapId = permissionName.split('_').slice(-1);
const friendlyName = SNAPS_METADATA[snapId]?.name;
if (friendlyName) {
return {
...baseDescription,
label: t('permission_accessNamedSnap', [
<span className="permission-label-item" key={snapId}>
{friendlyName}
</span>,
]),
leftIcon: 'fas fa-bolt',
rightIcon: null,
};
}
return {
...baseDescription,
label: t('permission_accessSnap', [snapId]),
leftIcon: 'fas fa-bolt',
rightIcon: null,
};
},
[EndowmentPermissions['endowment:network-access']]: (t) => ({
label: t('permission_accessNetwork'),
leftIcon: 'fas fa-wifi',
rightIcon: null,
weight: 2,
}),
[EndowmentPermissions['endowment:long-running']]: (t) => ({
label: t('permission_longRunning'),
leftIcon: 'fas fa-infinity',
rightIcon: null,
weight: 3,
}),
[EndowmentPermissions['endowment:transaction-insight']]: (
t,
_,
permissionValue,
) => {
const baseDescription = {
leftIcon: 'fas fa-info',
rightIcon: null,
};
const result = [
{
...baseDescription,
label: t('permission_transactionInsight'),
leftIcon: 'fas fa-info',
rightIcon: null,
},
];
@ -167,22 +191,25 @@ const PERMISSION_DESCRIPTIONS = deepFreeze({
permissionValue.caveats[0].value
) {
result.push({
...baseDescription,
label: t('permission_transactionInsightOrigin'),
leftIcon: 'fas fa-compass',
rightIcon: null,
});
}
return result;
},
[EndowmentPermissions['endowment:cronjob']]: (t) => ({
label: t('permission_cronjob'),
leftIcon: 'fas fa-clock',
rightIcon: null,
weight: 2,
}),
[EndowmentPermissions['endowment:ethereum-provider']]: (t) => ({
label: t('permission_ethereumProvider'),
leftIcon: 'fab fa-ethereum',
rightIcon: null,
weight: 1,
}),
[EndowmentPermissions['endowment:rpc']]: (t, _, permissionValue) => {
const { snaps, dapps } = getRpcCaveatOrigins(permissionValue);
@ -200,6 +227,7 @@ const PERMISSION_DESCRIPTIONS = deepFreeze({
label,
leftIcon: 'fas fa-plug',
rightIcon: null,
weight: 2,
}));
},
///: END:ONLY_INCLUDE_IN
@ -207,6 +235,7 @@ const PERMISSION_DESCRIPTIONS = deepFreeze({
label: t('permission_unknown', [permissionName ?? 'undefined']),
leftIcon: 'fas fa-times-circle',
rightIcon: null,
weight: 4,
}),
});
@ -215,6 +244,9 @@ const PERMISSION_DESCRIPTIONS = deepFreeze({
* @property {string} label - The text label.
* @property {string} leftIcon - The left icon.
* @property {string} rightIcon - The right icon.
* @property {number} weight - The weight of the permission.
* @property {string} permissionName - The name of the permission.
* @property {string} permissionValue - The raw value of the permission.
*/
/**
@ -243,7 +275,32 @@ export const getPermissionDescription = (
const result = value(t, permissionName, permissionValue);
if (!Array.isArray(result)) {
return [result];
return [{ ...result, permissionName, permissionValue }];
}
return result;
return result.map((item) => ({
...item,
permissionName,
permissionValue,
}));
};
/**
* 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.
* @returns {PermissionLabelObject[]}
*/
export function getWeightedPermissions(t, permissions) {
return Object.entries(permissions)
.reduce(
(target, [permissionName, permissionValue]) =>
target.concat(
getPermissionDescription(t, permissionName, permissionValue),
),
[],
)
.sort((left, right) => left.weight - right.weight);
}