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

[FLASK] Add update snap UI (#15143)

* added snap-update folder

* addded update route, snap update component, updated permissions connect components

* added actions and selectors

* updated permissions selectors and updated permissions connect container to have update snap logic

* updated translations, added selector, updated request object

* updated translations, added update snap permission list component

* more fixes

* added CSS, redid some HTML

* lint fixes

* Add missing grantPermissions action

* updated button padding

* fixes

* removed prop type

* fix Update & Install wrapping

* made changes for forthcoming snap controller PR

* removed ununsed imports

* updated css

* re-added padding rule and removed unused translation messages

* addressed comments

* add subtext for new permissions

* lint fix

* removed unused translations

* some more changes

* fix e2e tests

* lint fix

* added in delay for e2e tests

* Revert "added in delay for e2e tests"

This reverts commit 095962a2c0c9de0b0b343d3134bb0787044dd8ce.

* fixed routing logic

Co-authored-by: Frederik Bolding <frederik.bolding@gmail.com>
Co-authored-by: Guillaume Roux <guillaumeroux123@gmail.com>
This commit is contained in:
Hassan Malik 2022-08-03 12:02:44 -04:00 committed by GitHub
parent 97b10f96e0
commit a7179a6b88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 449 additions and 143 deletions

View File

@ -285,9 +285,6 @@
"approvedAsset": {
"message": "Genehmigtes Asset"
},
"areYouDeveloper": {
"message": "Sind Sie ein Entwickler?"
},
"areYouSure": {
"message": "Sind Sie sicher?"
},
@ -2360,9 +2357,6 @@
"message": "Öffnen Sie MetaMask im Vollbildmodus, um Ihren Ledger über WebHID zu verbinden.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
"openSourceCode": {
"message": "Prüfen Sie den Quellcode"
},
"optional": {
"message": "Optional"
},

View File

@ -293,9 +293,6 @@
"approvedAsset": {
"message": "Εγκεκριμένο περιουσιακό στοιχείο"
},
"areYouDeveloper": {
"message": "Είστε προγραμματιστής;"
},
"areYouSure": {
"message": "Είστε βέβαιος/η;"
},
@ -2397,9 +2394,6 @@
"message": "Ανοίξτε το MetaMask σε πλήρη οθόνη για να συνδέσετε το ledger σας μέσω WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
"openSourceCode": {
"message": "Ελέγξτε τον πηγαίο κώδικα"
},
"optional": {
"message": "Προαιρετικό"
},

View File

@ -332,7 +332,10 @@
"description": "$1 is the symbol of the token for which the user is granting approval"
},
"approveAndInstall": {
"message": "Approve & Install"
"message": "Approve & install"
},
"approveAndUpdate": {
"message": "Approve & update"
},
"approveButtonText": {
"message": "Approve"
@ -350,8 +353,9 @@
"approvedAsset": {
"message": "Approved asset"
},
"areYouDeveloper": {
"message": "Are you a developer?"
"approvedOn": {
"message": "Approved on $1",
"description": "$1 is the approval date for a permission"
},
"areYouSure": {
"message": "Are you sure?"
@ -2454,9 +2458,6 @@
"message": "Open MetaMask in full screen to connect your ledger via WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
"openSourceCode": {
"message": "Check the source code"
},
"optional": {
"message": "Optional"
},
@ -2529,6 +2530,12 @@
"permissionRequestCapitalized": {
"message": "Permission Request"
},
"permissionRequested": {
"message": "Requested now"
},
"permissionRevoked": {
"message": "Revoked in this update"
},
"permission_accessNetwork": {
"message": "Access the Internet.",
"description": "The description of the `endowment:network-access` permission."
@ -3115,6 +3122,13 @@
"snapRequestsPermission": {
"message": "This snap is requesting the following permissions:"
},
"snapUpdate": {
"message": "Update Snap"
},
"snapUpdateExplanation": {
"message": "$1 needs a newer version of your snap.",
"description": "$1 is the dapp that is requesting an update to the snap."
},
"snaps": {
"message": "Snaps"
},

View File

@ -293,9 +293,6 @@
"approvedAsset": {
"message": "Activo aprobado"
},
"areYouDeveloper": {
"message": "¿Usted es desarrollador?"
},
"areYouSure": {
"message": "¿Está seguro?"
},
@ -2397,9 +2394,6 @@
"message": "Abra MetaMask en pantalla completa para conectar su Ledger a través de WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
"openSourceCode": {
"message": "Compruebe el código fuente"
},
"optional": {
"message": "Opcional"
},

View File

@ -293,9 +293,6 @@
"approvedAsset": {
"message": "Actif approuvé"
},
"areYouDeveloper": {
"message": "Êtes-vous un(e) développeur(-se) ?"
},
"areYouSure": {
"message": "En êtes-vous sûr(e) ?"
},
@ -2397,9 +2394,6 @@
"message": "Ouvrez MetaMask en mode plein écran pour connecter votre Ledger via WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
"openSourceCode": {
"message": "Vérifier le code source"
},
"optional": {
"message": "Facultatif"
},

View File

@ -293,9 +293,6 @@
"approvedAsset": {
"message": "स्वीकृत एसेट"
},
"areYouDeveloper": {
"message": "क्या आप एक डेवलपर हैं?"
},
"areYouSure": {
"message": "क्या आप सुनिश्चित हैं?"
},
@ -2397,9 +2394,6 @@
"message": "अपने लेजर को WebHID के माध्यम से कनेक्ट करने के लिए MetaMask को पूर्ण स्क्रीन में खोलें।",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
"openSourceCode": {
"message": "सोर्स कोड जांचें"
},
"optional": {
"message": "वैकल्पिक"
},

View File

@ -293,9 +293,6 @@
"approvedAsset": {
"message": "Aset yang disetujui"
},
"areYouDeveloper": {
"message": "Anda seorang pengembang?"
},
"areYouSure": {
"message": "Anda yakin?"
},
@ -2397,9 +2394,6 @@
"message": "Buka MetaMask dalam layar penuh untuk menghubungkan ledger Anda melalui WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
"openSourceCode": {
"message": "Periksa kode sumbernya"
},
"optional": {
"message": "Opsional"
},

View File

@ -293,9 +293,6 @@
"approvedAsset": {
"message": "承認済みのアセット"
},
"areYouDeveloper": {
"message": "開発者の方ですか?"
},
"areYouSure": {
"message": "よろしいですか?"
},
@ -2397,9 +2394,6 @@
"message": "WebHIDでLedgerを接続するには、MetaMaskを全画面モードで開いてください。",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
"openSourceCode": {
"message": "ソースコードを確認"
},
"optional": {
"message": "任意"
},

View File

@ -293,9 +293,6 @@
"approvedAsset": {
"message": "승인된 자산"
},
"areYouDeveloper": {
"message": "개발자이신가요?"
},
"areYouSure": {
"message": "확실한가요?"
},
@ -2397,9 +2394,6 @@
"message": "전체 화면에서 MetaMask를 열어 WebHID를 통해 Ledger를 연결합니다.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
"openSourceCode": {
"message": "소스 코드를 확인하세요"
},
"optional": {
"message": "옵션"
},

View File

@ -293,9 +293,6 @@
"approvedAsset": {
"message": "Ativo aprovado"
},
"areYouDeveloper": {
"message": "Você é desenvolvedor?"
},
"areYouSure": {
"message": "Tem certeza?"
},
@ -2397,9 +2394,6 @@
"message": "Abra a MetaMask em tela cheia para conectar sua ledger por meio do WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
"openSourceCode": {
"message": "Verifique o código-fonte"
},
"optional": {
"message": "Opcional"
},

View File

@ -293,9 +293,6 @@
"approvedAsset": {
"message": "Одобренный актив"
},
"areYouDeveloper": {
"message": "Вы разработчик?"
},
"areYouSure": {
"message": "Вы уверены?"
},
@ -2397,9 +2394,6 @@
"message": "Откройте MetaMask в полноэкранном режиме, чтобы подключить свой леджер через WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
"openSourceCode": {
"message": "Проверьте исходный код"
},
"optional": {
"message": "Необязательно"
},

View File

@ -293,9 +293,6 @@
"approvedAsset": {
"message": "Aprubadong asset"
},
"areYouDeveloper": {
"message": "Isa ka bang developer?"
},
"areYouSure": {
"message": "Sigurado ka ba?"
},
@ -2397,9 +2394,6 @@
"message": "Buksan ang MetaMask sa buong screen para ikonekta ang ledger mo sa pamamagitan ng WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
"openSourceCode": {
"message": "Suriin ang code ng pinagmulan"
},
"optional": {
"message": "Opsyonal"
},

View File

@ -293,9 +293,6 @@
"approvedAsset": {
"message": "Onaylanan varlık"
},
"areYouDeveloper": {
"message": "Geliştirici misin?"
},
"areYouSure": {
"message": "Emin misin?"
},
@ -2397,9 +2394,6 @@
"message": "Kayıt defterinizi WebHID üzerinden bağlamak için MetaMask'i tam ekran açın.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
"openSourceCode": {
"message": "Kaynak kodunu kontrol et"
},
"optional": {
"message": "İsteğe bağlı"
},

View File

@ -293,9 +293,6 @@
"approvedAsset": {
"message": "Tài sản được chấp nhận"
},
"areYouDeveloper": {
"message": "Bạn có phải là lập trình viên không?"
},
"areYouSure": {
"message": "Bạn có chắc chắn không?"
},
@ -2397,9 +2394,6 @@
"message": "Mở MetaMask ở chế độ toàn màn hình để kết nối thiết bị Ledger của bạn qua WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
"openSourceCode": {
"message": "Kiểm tra mã nguồn"
},
"optional": {
"message": "Không bắt buộc"
},

View File

@ -661,7 +661,10 @@ export default class MetamaskController extends EventEmitter {
`${this.permissionController.name}:hasPermissions`,
`${this.permissionController.name}:requestPermissions`,
`${this.permissionController.name}:revokeAllPermissions`,
`${this.permissionController.name}:revokePermissions`,
`${this.permissionController.name}:revokePermissionForAllSubjects`,
`${this.approvalController.name}:addRequest`,
`${this.permissionController.name}:grantPermissions`,
'ExecutionService:executeSnap',
'ExecutionService:getRpcRequestHandler',
'ExecutionService:terminateSnap',
@ -709,6 +712,7 @@ export default class MetamaskController extends EventEmitter {
},
state: initState.SnapController,
messenger: snapControllerMessenger,
featureFlags: { dappsCanUpdateSnaps: true },
});
this.notificationController = new NotificationController({

View File

@ -64,7 +64,7 @@ describe('Test Snap bip-44', function () {
windowHandles,
);
await driver.clickElement({
text: 'Approve & Install',
text: 'Approve & install',
tag: 'button',
});
// deal with permissions popover

View File

@ -56,7 +56,7 @@ describe('Test Snap Confirm', function () {
windowHandles,
);
await driver.clickElement({
text: 'Approve & Install',
text: 'Approve & install',
tag: 'button',
});

View File

@ -56,7 +56,7 @@ describe('Test Snap Error', function () {
windowHandles,
);
await driver.clickElement({
text: 'Approve & Install',
text: 'Approve & install',
tag: 'button',
});

View File

@ -64,7 +64,7 @@ describe('Test Snap manageState', function () {
windowHandles,
);
await driver.clickElement({
text: 'Approve & Install',
text: 'Approve & install',
tag: 'button',
});

View File

@ -64,7 +64,7 @@ describe('Test Snap Notification', function () {
windowHandles,
);
await driver.clickElement({
text: 'Approve & Install',
text: 'Approve & install',
tag: 'button',
});

View File

@ -31,10 +31,11 @@
@import 'edit-gas-fee-popover/network-statistics/status-slider/index';
@import 'edit-gas-fee-popover/edit-gas-tooltip/index';
@import 'flask/experimental-area/index';
@import 'flask/snaps-authorship-pill/index';
@import 'flask/snap-install-warning/index';
@import 'flask/snap-remove-warning/index';
@import 'flask/snap-settings-card/index';
@import 'flask/snaps-authorship-pill/index';
@import 'flask/update-snap-permission-list/index';
@import 'gas-customization/gas-modal-page-container/index';
@import 'gas-customization/gas-price-button-group/index';
@import 'gas-customization/index';

View File

@ -15,8 +15,14 @@ const snapIdPrefixes = ['npm:', 'local:'];
const SnapsAuthorshipPill = ({ snapId, version, className }) => {
// @todo Use getSnapPrefix from snaps-skunkworks when possible
const snapPrefix = snapIdPrefixes.find((prefix) => snapId.startsWith(prefix));
const packageName = snapId.replace(snapPrefix, '');
// We're using optional chaining with snapId, because with the current implementation
// of snap update in the snap controller, we do not have reference to snapId when an
// update request is rejected because the reference comes from the request itself and not subject metadata
// like it is done with snap install
const snapPrefix = snapIdPrefixes.find((prefix) =>
snapId?.startsWith(prefix),
);
const packageName = snapId?.replace(snapPrefix, '');
const isNPM = snapPrefix === 'npm:';
const url = isNPM
? `https://www.npmjs.com/package/${packageName}`

View File

@ -0,0 +1 @@
export { default } from './update-snap-permission-list';

View File

@ -0,0 +1,47 @@
.update-snap-permission-list {
width: 100%;
.approved-permission,
.new-permission,
.revoked-permission {
@include H6;
width: 100%;
padding-bottom: 16px;
border-bottom: 1px solid var(--color-border-default);
display: flex;
flex-direction: row;
align-items: center;
color: var(--color-text-default);
i {
display: block;
padding: 16px;
min-width: 16px;
min-height: 16px;
font-size: 1rem;
}
}
.approved-permission {
color: var(--color-success-default);
}
.revoked-permission {
color: var(--color-error-alternative);
}
.new-permission {
color: var(--color-info-default);
}
.permission-description {
width: 100%;
display: flex;
flex-direction: column;
}
.permission-description-subtext {
@include H7;
}
}

View File

@ -0,0 +1,112 @@
import React from 'react';
import PropTypes from 'prop-types';
import { getPermissionDescription } from '../../../../helpers/utils/permission';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import { formatDate } from '../../../../helpers/utils/util';
import Typography from '../../../ui/typography/typography';
import { COLORS } from '../../../../helpers/constants/design-system';
export default function UpdateSnapPermissionList({
approvedPermissions,
revokedPermissions,
newPermissions,
}) {
const t = useI18nContext();
const ApprovedPermissions = () => {
return Object.keys(approvedPermissions).map((approvedPermission) => {
const { label, rightIcon } = getPermissionDescription(
t,
approvedPermission,
);
const { date } = approvedPermissions[approvedPermission];
const formattedDate = formatDate(date, 'yyyy-MM-dd');
return (
<div className="approved-permission" key={approvedPermission}>
<i className="fas fa-check" />
<div className="permission-description">
{label}
<Typography
color={COLORS.TEXT_ALTERNATIVE}
className="permission-description-subtext"
boxProps={{ paddingTop: 1 }}
>
{t('approvedOn', [formattedDate])}
</Typography>
</div>
{rightIcon && <i className={rightIcon} />}
</div>
);
});
};
const RevokedPermissions = () => {
return Object.keys(revokedPermissions).map((revokedPermission) => {
const { label, rightIcon } = getPermissionDescription(
t,
revokedPermission,
);
return (
<div className="revoked-permission" key={revokedPermission}>
<i className="fas fa-x" />
<div className="permission-description">
{label}
<Typography
color={COLORS.TEXT_ALTERNATIVE}
boxProps={{ paddingTop: 1 }}
className="permission-description-subtext"
>
{t('permissionRevoked')}
</Typography>
</div>
{rightIcon && <i className={rightIcon} />}
</div>
);
});
};
const NewPermissions = () => {
return Object.keys(newPermissions).map((newPermission) => {
const { label, rightIcon } = getPermissionDescription(t, newPermission);
return (
<div className="new-permission" key={newPermission}>
<i className="fas fa-arrow-right" />
<div className="permission-description">
{label}
<Typography
color={COLORS.TEXT_ALTERNATIVE}
boxProps={{ paddingTop: 1 }}
className="permission-description-subtext"
>
{t('permissionRequested')}
</Typography>
</div>
{rightIcon && <i className={rightIcon} />}
</div>
);
});
};
return (
<div className="update-snap-permission-list">
<NewPermissions />
<ApprovedPermissions />
<RevokedPermissions />
</div>
);
}
UpdateSnapPermissionList.propTypes = {
/**
* Permissions that have already been approved
*/
approvedPermissions: PropTypes.object.isRequired,
/**
* Previously used permissions that are now revoked
*/
revokedPermissions: PropTypes.object.isRequired,
/**
* New permissions that are being requested
*/
newPermissions: PropTypes.object.isRequired,
};

View File

@ -29,7 +29,7 @@ export default class PermissionsConnectHeader extends Component {
rightIcon: PropTypes.node,
///: BEGIN:ONLY_INCLUDE_IN(flask)
snapVersion: PropTypes.string,
isSnapInstall: PropTypes.bool,
isSnapInstallOrUpdate: PropTypes.bool,
///: END:ONLY_INCLUDE_IN
};
@ -47,12 +47,12 @@ export default class PermissionsConnectHeader extends Component {
siteOrigin,
rightIcon,
///: BEGIN:ONLY_INCLUDE_IN(flask)
isSnapInstall,
isSnapInstallOrUpdate,
///: END:ONLY_INCLUDE_IN
} = this.props;
///: BEGIN:ONLY_INCLUDE_IN(flask)
if (isSnapInstall) {
if (isSnapInstallOrUpdate) {
return null;
}
///: END:ONLY_INCLUDE_IN
@ -79,7 +79,7 @@ export default class PermissionsConnectHeader extends Component {
///: BEGIN:ONLY_INCLUDE_IN(flask)
siteOrigin,
snapVersion,
isSnapInstall,
isSnapInstallOrUpdate,
///: END:ONLY_INCLUDE_IN
} = this.props;
return (
@ -93,7 +93,7 @@ export default class PermissionsConnectHeader extends Component {
<div className="permissions-connect-header__title">{headerTitle}</div>
{
///: BEGIN:ONLY_INCLUDE_IN(flask)
isSnapInstall && (
isSnapInstallOrUpdate && (
<SnapsAuthorshipPill snapId={siteOrigin} version={snapVersion} />
)
///: END:ONLY_INCLUDE_IN

View File

@ -35,6 +35,7 @@ const CONNECT_ROUTE = '/connect';
const CONNECT_CONFIRM_PERMISSIONS_ROUTE = '/confirm-permissions';
///: BEGIN:ONLY_INCLUDE_IN(flask)
const CONNECT_SNAP_INSTALL_ROUTE = '/snap-install';
const CONNECT_SNAP_UPDATE_ROUTE = '/snap-update';
const NOTIFICATIONS_ROUTE = '/notifications';
///: END:ONLY_INCLUDE_IN
const CONNECTED_ROUTE = '/connected';
@ -250,6 +251,7 @@ export {
CONNECT_CONFIRM_PERMISSIONS_ROUTE,
///: BEGIN:ONLY_INCLUDE_IN(flask)
CONNECT_SNAP_INSTALL_ROUTE,
CONNECT_SNAP_UPDATE_ROUTE,
NOTIFICATIONS_ROUTE,
///: END:ONLY_INCLUDE_IN
CONNECTED_ROUTE,

View File

@ -5,6 +5,9 @@ import {
activeTabHasPermissions,
getCurrentEthBalance,
getFirstPermissionRequest,
///: BEGIN:ONLY_INCLUDE_IN(flask)
getFirstSnapUpdateRequest,
///: END:ONLY_INCLUDE_IN
getIsMainnet,
getOriginOfCurrentTab,
getTotalUnapprovedCount,
@ -86,9 +89,18 @@ const mapStateToProps = (state) => {
const isPopup = envType === ENVIRONMENT_TYPE_POPUP;
const isNotification = envType === ENVIRONMENT_TYPE_NOTIFICATION;
const firstPermissionsRequest = getFirstPermissionRequest(state);
const firstPermissionsRequestId =
firstPermissionsRequest?.metadata.id || null;
let firstPermissionsRequest, firstPermissionsRequestId;
firstPermissionsRequest = getFirstPermissionRequest(state);
firstPermissionsRequestId = firstPermissionsRequest?.metadata.id || null;
// getFirstPermissionRequest should be updated with snap update logic once we hit main extension release
///: BEGIN:ONLY_INCLUDE_IN(flask)
if (!firstPermissionsRequest) {
firstPermissionsRequest = getFirstSnapUpdateRequest(state);
firstPermissionsRequestId = firstPermissionsRequest?.metadata.id || null;
}
///: END:ONLY_INCLUDE_IN
const originOfCurrentTab = getOriginOfCurrentTab(state);
const shouldShowWeb3ShimUsageNotification =

View File

@ -9,19 +9,6 @@
}
}
.source-code {
@include H7;
display: flex;
color: var(--color-text-alternative);
.link {
color: var(--color-primary-default);
margin-inline-start: 4px;
cursor: pointer;
}
}
.page-container__footer {
width: 100%;
margin-top: 12px;

View File

@ -68,7 +68,7 @@ export default function SnapInstall({
headerTitle={t('snapInstall')}
headerText={null} // TODO(ritave): Add header text when snaps support description
siteOrigin={targetSubjectMetadata.origin}
isSnapInstall
isSnapInstallOrUpdate
snapVersion={targetSubjectMetadata.version}
boxProps={{ alignItems: ALIGN_ITEMS.CENTER }}
/>
@ -90,31 +90,9 @@ export default function SnapInstall({
alignItems={ALIGN_ITEMS.CENTER}
flexDirection={FLEX_DIRECTION.COLUMN}
>
{targetSubjectMetadata.sourceCode ? (
<>
<div className="source-code">
<div className="text">{t('areYouDeveloper')}</div>
<div
className="link"
onClick={() =>
global.platform.openTab({
url: targetSubjectMetadata.sourceCode,
})
}
>
{t('openSourceCode')}
</div>
</div>
<Box paddingBottom={4}>
<PermissionsConnectFooter />
</Box>
</>
) : (
<Box className="snap-install__footer--no-source-code" paddingTop={4}>
<PermissionsConnectFooter />
</Box>
)}
<Box className="snap-install__footer--no-source-code" paddingTop={4}>
<PermissionsConnectFooter />
</Box>
<PageContainerFooter
cancelButtonType="default"
onCancel={onCancel}

View File

@ -0,0 +1 @@
export { default } from './snap-update';

View File

@ -0,0 +1,22 @@
.snap-update {
box-shadow: none;
.update-snap-permission-list {
padding: 0 24px;
.new-permission,
.approved-permission,
.revoked-permission {
padding: 8px 0;
}
}
.page-container__footer {
width: 100%;
margin-top: 12px;
button {
padding: 0.75rem;
}
}
}

View File

@ -0,0 +1,138 @@
import PropTypes from 'prop-types';
import React, { useCallback, useMemo, useState } from 'react';
import { PageContainerFooter } from '../../../../components/ui/page-container';
import PermissionsConnectFooter from '../../../../components/app/permissions-connect-footer';
import PermissionConnectHeader from '../../../../components/app/permissions-connect-header';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import SnapInstallWarning from '../../../../components/app/flask/snap-install-warning';
import Box from '../../../../components/ui/box/box';
import {
ALIGN_ITEMS,
BLOCK_SIZES,
BORDER_STYLE,
FLEX_DIRECTION,
JUSTIFY_CONTENT,
TYPOGRAPHY,
} from '../../../../helpers/constants/design-system';
import Typography from '../../../../components/ui/typography';
import UpdateSnapPermissionList from '../../../../components/app/flask/update-snap-permission-list';
export default function SnapUpdate({
request,
approveSnapUpdate,
rejectSnapUpdate,
targetSubjectMetadata,
}) {
const t = useI18nContext();
const [isShowingWarning, setIsShowingWarning] = useState(false);
const onCancel = useCallback(
() => rejectSnapUpdate(request.metadata.id),
[request, rejectSnapUpdate],
);
const onSubmit = useCallback(
() => approveSnapUpdate(request),
[request, approveSnapUpdate],
);
const shouldShowWarning = useMemo(
() =>
Boolean(
request.permissions &&
Object.keys(request.permissions).find((v) =>
v.startsWith('snap_getBip44Entropy_'),
),
),
[request.permissions],
);
return (
<Box
className="page-container snap-update"
justifyContent={JUSTIFY_CONTENT.SPACE_BETWEEN}
height={BLOCK_SIZES.FULL}
borderStyle={BORDER_STYLE.NONE}
flexDirection={FLEX_DIRECTION.COLUMN}
>
<Box
className="headers"
alignItems={ALIGN_ITEMS.CENTER}
flexDirection={FLEX_DIRECTION.COLUMN}
>
<PermissionConnectHeader
icon={targetSubjectMetadata.iconUrl}
iconName={targetSubjectMetadata.name}
headerTitle={t('snapUpdate')}
headerText={null} // TODO(ritave): Add header text when snaps support description
siteOrigin={request.snapId}
isSnapInstallOrUpdate
snapVersion={request.newVersion}
boxProps={{ alignItems: ALIGN_ITEMS.CENTER }}
/>
<Typography
boxProps={{
padding: [4, 4, 0, 4],
}}
variant={TYPOGRAPHY.H7}
tag="span"
>
{t('snapUpdateExplanation', [`${request.metadata.dappOrigin}`])}
</Typography>
<Typography
boxProps={{
padding: [2, 4, 0, 4],
}}
variant={TYPOGRAPHY.H7}
tag="span"
>
{t('snapRequestsPermission')}
</Typography>
<UpdateSnapPermissionList
approvedPermissions={request.approvedPermissions || {}}
revokedPermissions={request.unusedPermissions || {}}
newPermissions={request.newPermissions || {}}
/>
</Box>
<Box
className="footers"
alignItems={ALIGN_ITEMS.CENTER}
flexDirection={FLEX_DIRECTION.COLUMN}
>
<Box className="snap-update__footer--no-source-code" paddingTop={4}>
<PermissionsConnectFooter />
</Box>
<PageContainerFooter
cancelButtonType="default"
onCancel={onCancel}
cancelText={t('cancel')}
onSubmit={
shouldShowWarning ? () => setIsShowingWarning(true) : onSubmit
}
submitText={t('approveAndUpdate')}
/>
</Box>
{isShowingWarning && (
<SnapInstallWarning
onCancel={() => setIsShowingWarning(false)}
onSubmit={onSubmit}
snapName={targetSubjectMetadata.name}
/>
)}
</Box>
);
}
SnapUpdate.propTypes = {
request: PropTypes.object.isRequired,
approveSnapUpdate: PropTypes.func.isRequired,
rejectSnapUpdate: PropTypes.func.isRequired,
targetSubjectMetadata: PropTypes.shape({
iconUrl: PropTypes.string,
name: PropTypes.string,
origin: PropTypes.string.isRequired,
sourceCode: PropTypes.string,
version: PropTypes.string,
}).isRequired,
};

View File

@ -1,5 +1,6 @@
@import 'choose-account/index';
@import 'flask/snap-install/index';
@import 'flask/snap-update/index';
@import 'redirect/index';
.permissions-connect {

View File

@ -10,6 +10,7 @@ import ChooseAccount from './choose-account';
import PermissionsRedirect from './redirect';
///: BEGIN:ONLY_INCLUDE_IN(flask)
import SnapInstall from './flask/snap-install';
import SnapUpdate from './flask/snap-update';
///: END:ONLY_INCLUDE_IN
const APPROVE_TIMEOUT = MILLISECOND * 1200;
@ -35,6 +36,7 @@ export default class PermissionConnect extends Component {
confirmPermissionPath: PropTypes.string.isRequired,
///: BEGIN:ONLY_INCLUDE_IN(flask)
snapInstallPath: PropTypes.string.isRequired,
snapUpdatePath: PropTypes.string.isRequired,
isSnap: PropTypes.bool.isRequired,
///: END:ONLY_INCLUDE_IN
totalPages: PropTypes.string.isRequired,
@ -90,6 +92,7 @@ export default class PermissionConnect extends Component {
confirmPermissionPath,
///: BEGIN:ONLY_INCLUDE_IN(flask)
snapInstallPath,
snapUpdatePath,
isSnap,
///: END:ONLY_INCLUDE_IN
getCurrentWindowTab,
@ -114,7 +117,9 @@ export default class PermissionConnect extends Component {
if (history.location.pathname === connectPath && !isRequestingAccounts) {
///: BEGIN:ONLY_INCLUDE_IN(flask)
if (isSnap) {
history.push(snapInstallPath);
history.push(
permissionsRequest.newPermissions ? snapUpdatePath : snapInstallPath,
);
} else {
///: END:ONLY_INCLUDE_IN
history.push(confirmPermissionPath);
@ -229,6 +234,7 @@ export default class PermissionConnect extends Component {
confirmPermissionPath,
///: BEGIN:ONLY_INCLUDE_IN(flask)
snapInstallPath,
snapUpdatePath,
///: END:ONLY_INCLUDE_IN
} = this.props;
const {
@ -313,6 +319,29 @@ export default class PermissionConnect extends Component {
{
///: END:ONLY_INCLUDE_IN
}
{
///: BEGIN:ONLY_INCLUDE_IN(flask)
}
<Route
path={snapUpdatePath}
exact
render={() => (
<SnapUpdate
request={permissionsRequest || {}}
approveSnapUpdate={(...args) => {
approvePermissionsRequest(...args);
this.redirect(true);
}}
rejectSnapUpdate={(requestId) =>
this.cancelPermissionsRequest(requestId)
}
targetSubjectMetadata={targetSubjectMetadata}
/>
)}
/>
{
///: END:ONLY_INCLUDE_IN
}
</Switch>
)}
</div>

View File

@ -5,6 +5,9 @@ import {
getLastConnectedInfo,
getPermissionsRequests,
getSelectedAddress,
///: BEGIN:ONLY_INCLUDE_IN(flask)
getSnapUpdateRequests,
///: END:ONLY_INCLUDE_IN
getTargetSubjectMetadata,
} from '../../selectors';
import { getNativeCurrency } from '../../ducks/metamask/metamask';
@ -22,6 +25,7 @@ import {
CONNECT_CONFIRM_PERMISSIONS_ROUTE,
///: BEGIN:ONLY_INCLUDE_IN(flask)
CONNECT_SNAP_INSTALL_ROUTE,
CONNECT_SNAP_UPDATE_ROUTE,
///: END:ONLY_INCLUDE_IN
} from '../../helpers/constants/routes';
import { SUBJECT_TYPES } from '../../../shared/constants/app';
@ -34,7 +38,13 @@ const mapStateToProps = (state, ownProps) => {
},
location: { pathname },
} = ownProps;
const permissionsRequests = getPermissionsRequests(state);
let permissionsRequests = getPermissionsRequests(state);
///: BEGIN:ONLY_INCLUDE_IN(flask)
permissionsRequests = [
...permissionsRequests,
...getSnapUpdateRequests(state),
];
///: END:ONLY_INCLUDE_IN
const currentAddress = getSelectedAddress(state);
const permissionsRequest = permissionsRequests.find(
@ -42,7 +52,7 @@ const mapStateToProps = (state, ownProps) => {
);
const isRequestingAccounts = Boolean(
permissionsRequest?.permissions.eth_accounts,
permissionsRequest?.permissions?.eth_accounts,
);
const { metadata = {} } = permissionsRequest || {};
@ -77,6 +87,7 @@ const mapStateToProps = (state, ownProps) => {
const confirmPermissionPath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_CONFIRM_PERMISSIONS_ROUTE}`;
///: BEGIN:ONLY_INCLUDE_IN(flask)
const snapInstallPath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_SNAP_INSTALL_ROUTE}`;
const snapUpdatePath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_SNAP_UPDATE_ROUTE}`;
///: END:ONLY_INCLUDE_IN
let totalPages = 1 + isRequestingAccounts;
@ -91,7 +102,7 @@ const mapStateToProps = (state, ownProps) => {
} else if (pathname === confirmPermissionPath) {
page = isRequestingAccounts ? '2' : '1';
///: BEGIN:ONLY_INCLUDE_IN(flask)
} else if (pathname === snapInstallPath) {
} else if (pathname === snapInstallPath || pathname === snapUpdatePath) {
page = isRequestingAccounts ? '3' : '2';
///: END:ONLY_INCLUDE_IN
} else {
@ -103,6 +114,7 @@ const mapStateToProps = (state, ownProps) => {
///: BEGIN:ONLY_INCLUDE_IN(flask)
isSnap,
snapInstallPath,
snapUpdatePath,
///: END:ONLY_INCLUDE_IN
permissionsRequest,
permissionsRequestId,

View File

@ -280,6 +280,19 @@ export function getLastConnectedInfo(state) {
}, {});
}
///: BEGIN:ONLY_INCLUDE_IN(flask)
export function getSnapUpdateRequests(state) {
return Object.values(state.metamask.pendingApprovals)
.filter(({ type }) => type === 'wallet_updateSnap')
.map(({ requestData }) => requestData);
}
export function getFirstSnapUpdateRequest(state) {
const requests = getSnapUpdateRequests(state);
return requests && requests[0] ? requests[0] : null;
}
///: END:ONLY_INCLUDE_IN
export function getPermissionsRequests(state) {
return Object.values(state.metamask.pendingApprovals)
.filter(({ type }) => type === 'wallet_requestPermissions')
@ -290,3 +303,7 @@ export function getFirstPermissionRequest(state) {
const requests = getPermissionsRequests(state);
return requests && requests[0] ? requests[0] : null;
}
export function getPermissions(state, origin) {
return getPermissionSubjects(state)[origin].permissions;
}