diff --git a/.storybook/index.css b/.storybook/index.css new file mode 100644 index 000000000000..48ebae78fe46 --- /dev/null +++ b/.storybook/index.css @@ -0,0 +1,18 @@ +/* Fixes for any styles that are not compatible with Storybook */ + +.create-snap-account-page, .remove-snap-account-page, .snap-ui-renderer { + width: 100% !important; +} + +.snap-ui-renderer__footer-centered { + position: initial !important; + margin-top: auto !important; +} + +.snap-ui-renderer__container { + padding-bottom: 0 !important; +} + +.snap-ui-renderer__panel { + overflow-y: auto !important; +} diff --git a/.storybook/preview.js b/.storybook/preview.js index 525c364f2072..273afa2468b0 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -21,6 +21,7 @@ import { metamaskStorybookTheme } from './metamask-storybook-theme'; import { DocsContainer } from '@storybook/addon-docs'; import { themes } from '@storybook/theming'; import { AlertMetricsProvider } from '../ui/components/app/alert-system/contexts/alertMetricsContext'; +import './index.css'; // eslint-disable-next-line /* @ts-expect-error: Avoids error from window property not existing */ diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 19f6494e3b33..adfa6b2b2d15 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -6601,10 +6601,6 @@ "wrongNetworkName": { "message": "Laut unseren Aufzeichnungen stimmt dieser Netzwerkname nicht mit dieser Chain-ID überein." }, - "xOfYPending": { - "message": "$1 von $2 ausstehend", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Ja" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index cb3b257c3177..dec760694851 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -6601,10 +6601,6 @@ "wrongNetworkName": { "message": "Σύμφωνα με τα αρχεία μας, το όνομα του δικτύου ενδέχεται να μην αντιστοιχεί με αυτό το αναγνωριστικό αλυσίδας." }, - "xOfYPending": { - "message": "$1 από $2 σε εκκρεμότητα", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Ναι" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index d1f05af0b283..a62c7b891320 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -6782,10 +6782,6 @@ "wrongNetworkName": { "message": "According to our records, the network name may not correctly match this chain ID." }, - "xOfYPending": { - "message": "$1 of $2 pending", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Yes" }, diff --git a/app/_locales/en_GB/messages.json b/app/_locales/en_GB/messages.json index 92ad7c646a36..2addf34f3c26 100644 --- a/app/_locales/en_GB/messages.json +++ b/app/_locales/en_GB/messages.json @@ -6310,10 +6310,6 @@ "wrongNetworkName": { "message": "According to our records, the network name may not correctly match this chain ID." }, - "xOfYPending": { - "message": "$1 of $2 pending", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Yes" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 92cbd316b9fb..830f36f14e6b 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -6601,10 +6601,6 @@ "wrongNetworkName": { "message": "Según nuestros registros, es posible que el nombre de la red no coincida correctamente con este ID de cadena." }, - "xOfYPending": { - "message": "$1 de $2 están pendientes", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Sí" }, diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index 511ee6cbef71..8006ca4405be 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -2375,10 +2375,6 @@ "whatsThis": { "message": "¿Qué es esto?" }, - "xOfYPending": { - "message": "$1 de $2 están pendientes", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "youNeedToAllowCameraAccess": { "message": "Necesita permitir el acceso a la cámara para usar esta función." }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 6391c83412a2..f10de856daa9 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -6601,10 +6601,6 @@ "wrongNetworkName": { "message": "Selon nos informations, il se peut que le nom du réseau ne corresponde pas exactement à l’ID de chaîne." }, - "xOfYPending": { - "message": "$1 sur $2 en attente", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Oui" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 63438fb22a4f..2c022bf59e2c 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -6601,10 +6601,6 @@ "wrongNetworkName": { "message": "हमारे रिकॉर्ड के अनुसार, नेटवर्क का नाम इस चेन ID से ठीक से मेल नहीं खा सकता है।" }, - "xOfYPending": { - "message": "$2 में से $1 लंबित", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "हां" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index e1fbf1398d39..c405dd97dd0e 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -6601,10 +6601,6 @@ "wrongNetworkName": { "message": "Menurut catatan kami, nama jaringan mungkin tidak cocok dengan ID chain ini." }, - "xOfYPending": { - "message": "$1 dari $2 berstatus menunggu", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Ya" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 7d247adcc07a..f2530634aa7a 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -6601,10 +6601,6 @@ "wrongNetworkName": { "message": "弊社の記録によると、ネットワーク名がこのチェーンIDと正しく一致していない可能性があります。" }, - "xOfYPending": { - "message": "$2件中$1件が保留中", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "はい" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 1d6c0b9199d8..d27c468e1f7e 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -6601,10 +6601,6 @@ "wrongNetworkName": { "message": "기록에 따르면 네트워크 이름이 이 체인 ID와 일치하지 않습니다." }, - "xOfYPending": { - "message": "$1/$2개 보류 중", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "예" }, diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json index 3c08d76cb186..3d40dbfefd77 100644 --- a/app/_locales/ph/messages.json +++ b/app/_locales/ph/messages.json @@ -1591,10 +1591,6 @@ "whatsThis": { "message": "Ano ito?" }, - "xOfYPending": { - "message": "$1 sa $2 ang nakabinbin", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "youNeedToAllowCameraAccess": { "message": "Kailangan mong payagan ang pag-access sa camera para magamit ang feature na ito." }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index c7afcceb33e8..a43af8d072b6 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -6601,10 +6601,6 @@ "wrongNetworkName": { "message": "De acordo com os nossos registros, o nome da rede pode não corresponder a esta ID de cadeia." }, - "xOfYPending": { - "message": "$1 de $2 pendente(s)", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Sim" }, diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index ebf2af88c186..0bc1005ad674 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -2375,10 +2375,6 @@ "whatsThis": { "message": "O que é isso?" }, - "xOfYPending": { - "message": "$1 de $2 pendente", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "youNeedToAllowCameraAccess": { "message": "Você precisa permitir o acesso à câmera para usar esse recurso." }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index c9236e4396bd..a340ff947168 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -6601,10 +6601,6 @@ "wrongNetworkName": { "message": "Согласно нашим записям, имя сети может не соответствовать этому ID блокчейна." }, - "xOfYPending": { - "message": "$1 из $2 ожидающих", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Да" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 30ee4b2096eb..e76a63ae783e 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -6601,10 +6601,6 @@ "wrongNetworkName": { "message": "Ayon sa aming mga talaan, ang pangalan ng network ay maaaring hindi tumugma nang tama sa ID ng chain na ito." }, - "xOfYPending": { - "message": "$1 ng $2 ang nakabinbin", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Oo" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 16f7c1eb8a85..5dfa36030be2 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -6601,10 +6601,6 @@ "wrongNetworkName": { "message": "Kayıtlarımıza göre ağ adı bu zincir kimliği ile doğru şekilde eşleşmiyor olabilir." }, - "xOfYPending": { - "message": "$1 / $2 bekliyor", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Evet" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 39c62a701e4f..b001654f5553 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -6601,10 +6601,6 @@ "wrongNetworkName": { "message": "Theo hồ sơ của chúng tôi, tên mạng có thể không khớp chính xác với ID chuỗi này." }, - "xOfYPending": { - "message": "$1/$2 đang chờ xử lý", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Có" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 9c5da6003052..aa90b06379b4 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -6601,10 +6601,6 @@ "wrongNetworkName": { "message": "根据我们的记录,该网络名称可能与此链 ID 不匹配。" }, - "xOfYPending": { - "message": "$1 / $2 待处理", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "是" }, diff --git a/app/_locales/zh_TW/messages.json b/app/_locales/zh_TW/messages.json index 29953f67c941..ed1793427a90 100644 --- a/app/_locales/zh_TW/messages.json +++ b/app/_locales/zh_TW/messages.json @@ -1375,10 +1375,6 @@ "whatsThis": { "message": "這是什麼?" }, - "xOfYPending": { - "message": "$1 之 $2 等待中", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "youNeedToAllowCameraAccess": { "message": "需要准許存取攝影鏡頭才能啟用此功能" }, diff --git a/app/scripts/background.js b/app/scripts/background.js index 1b037f09328b..e9aaf2cab20b 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -15,11 +15,7 @@ import log from 'loglevel'; import browser from 'webextension-polyfill'; import { storeAsStream } from '@metamask/obs-store'; import { isObject } from '@metamask/utils'; -import { ApprovalType } from '@metamask/controller-utils'; import PortStream from 'extension-port-stream'; - -import { providerErrors } from '@metamask/rpc-errors'; -import { DIALOG_APPROVAL_TYPES } from '@metamask/snaps-rpc-methods'; import { NotificationServicesController } from '@metamask/notification-services-controller'; import { @@ -29,9 +25,6 @@ import { EXTENSION_MESSAGES, PLATFORM_FIREFOX, MESSAGE_TYPE, - ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) - SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES, - ///: END:ONLY_INCLUDE_IF } from '../../shared/constants/app'; import { REJECT_NOTIFICATION_CLOSE, @@ -1201,34 +1194,7 @@ export function setupController( REJECT_NOTIFICATION_CLOSE, ); - // Finally, resolve snap dialog approvals on Flask and reject all the others managed by the ApprovalController. - Object.values(controller.approvalController.state.pendingApprovals).forEach( - ({ id, type }) => { - switch (type) { - case ApprovalType.SnapDialogAlert: - case ApprovalType.SnapDialogPrompt: - case DIALOG_APPROVAL_TYPES.default: - controller.approvalController.accept(id, null); - break; - case ApprovalType.SnapDialogConfirmation: - controller.approvalController.accept(id, false); - break; - ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) - case SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountCreation: - case SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountRemoval: - case SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showSnapAccountRedirect: - controller.approvalController.accept(id, false); - break; - ///: END:ONLY_INCLUDE_IF - default: - controller.approvalController.reject( - id, - providerErrors.userRejectedRequest(), - ); - break; - } - }, - ); + controller.rejectAllPendingApprovals(); } // Updates the snaps registry and check for newly blocked snaps to block if the user has at least one snap installed that isn't preinstalled. diff --git a/app/scripts/lib/approval/utils.test.ts b/app/scripts/lib/approval/utils.test.ts new file mode 100644 index 000000000000..14c472df89ba --- /dev/null +++ b/app/scripts/lib/approval/utils.test.ts @@ -0,0 +1,103 @@ +import { + ApprovalController, + ApprovalRequest, +} from '@metamask/approval-controller'; +import { Json } from '@metamask/utils'; +import { ApprovalType } from '@metamask/controller-utils'; +import { providerErrors } from '@metamask/rpc-errors'; +import { DIALOG_APPROVAL_TYPES } from '@metamask/snaps-rpc-methods'; +import { SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES } from '../../../../shared/constants/app'; +import { rejectAllApprovals } from './utils'; + +const ID_MOCK = '123'; +const ID_MOCK_2 = '456'; +const INTERFACE_ID_MOCK = '789'; + +function createApprovalControllerMock( + pendingApprovals: Partial>>[], +) { + return { + state: { + pendingApprovals, + }, + accept: jest.fn(), + reject: jest.fn(), + } as unknown as jest.Mocked; +} + +describe('Approval Utils', () => { + describe('rejectAllApprovals', () => { + it('rejects approval requests with rejected error', () => { + const approvalController = createApprovalControllerMock([ + { id: ID_MOCK, type: ApprovalType.Transaction }, + { id: ID_MOCK_2, type: ApprovalType.EthSignTypedData }, + ]); + + rejectAllApprovals({ + approvalController, + }); + + expect(approvalController.reject).toHaveBeenCalledTimes(2); + expect(approvalController.reject).toHaveBeenCalledWith( + ID_MOCK, + providerErrors.userRejectedRequest(), + ); + expect(approvalController.reject).toHaveBeenCalledWith( + ID_MOCK_2, + providerErrors.userRejectedRequest(), + ); + }); + + // @ts-expect-error This function is missing from the Mocha type definitions + it.each([ + ApprovalType.SnapDialogAlert, + ApprovalType.SnapDialogPrompt, + DIALOG_APPROVAL_TYPES.default, + ])('accepts pending approval if type is %s', (type: string) => { + const approvalController = createApprovalControllerMock([ + { id: ID_MOCK, type }, + ]); + + rejectAllApprovals({ approvalController }); + + expect(approvalController.accept).toHaveBeenCalledTimes(1); + expect(approvalController.accept).toHaveBeenCalledWith(ID_MOCK, null); + }); + + // @ts-expect-error This function is missing from the Mocha type definitions + it.each([ + ApprovalType.SnapDialogConfirmation, + SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountCreation, + SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountRemoval, + SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showSnapAccountRedirect, + ])('accepts pending approval if type is %s', (type: string) => { + const approvalController = createApprovalControllerMock([ + { id: ID_MOCK, type }, + ]); + + rejectAllApprovals({ approvalController }); + + expect(approvalController.accept).toHaveBeenCalledTimes(1); + expect(approvalController.accept).toHaveBeenCalledWith(ID_MOCK, false); + }); + + // @ts-expect-error This function is missing from the Mocha type definitions + it.each([ + ApprovalType.SnapDialogAlert, + ApprovalType.SnapDialogPrompt, + DIALOG_APPROVAL_TYPES.default, + ApprovalType.SnapDialogConfirmation, + ])('deletes interface if type is %s', (type: string) => { + const approvalController = createApprovalControllerMock([ + { id: ID_MOCK, type, requestData: { id: INTERFACE_ID_MOCK } }, + ]); + + const deleteInterface = jest.fn(); + + rejectAllApprovals({ approvalController, deleteInterface }); + + expect(deleteInterface).toHaveBeenCalledTimes(1); + expect(deleteInterface).toHaveBeenCalledWith(INTERFACE_ID_MOCK); + }); + }); +}); diff --git a/app/scripts/lib/approval/utils.ts b/app/scripts/lib/approval/utils.ts new file mode 100644 index 000000000000..5bd44b5db079 --- /dev/null +++ b/app/scripts/lib/approval/utils.ts @@ -0,0 +1,56 @@ +import { ApprovalController } from '@metamask/approval-controller'; +import { ApprovalType } from '@metamask/controller-utils'; +import { DIALOG_APPROVAL_TYPES } from '@metamask/snaps-rpc-methods'; +import { providerErrors } from '@metamask/rpc-errors'; +import { createProjectLogger } from '@metamask/utils'; +///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) +import { SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES } from '../../../../shared/constants/app'; +///: END:ONLY_INCLUDE_IF + +const log = createProjectLogger('approval-utils'); + +export function rejectAllApprovals({ + approvalController, + deleteInterface, +}: { + approvalController: ApprovalController; + deleteInterface?: (id: string) => void; +}) { + const approvalRequestsById = approvalController.state.pendingApprovals; + const approvalRequests = Object.values(approvalRequestsById); + + for (const approvalRequest of approvalRequests) { + const { id, type } = approvalRequest; + const interfaceId = approvalRequest.requestData?.id as string; + + switch (type) { + case ApprovalType.SnapDialogAlert: + case ApprovalType.SnapDialogPrompt: + case DIALOG_APPROVAL_TYPES.default: + log('Rejecting snap dialog', { id, interfaceId }); + approvalController.accept(id, null); + deleteInterface?.(interfaceId); + break; + + case ApprovalType.SnapDialogConfirmation: + log('Rejecting snap confirmation', { id, interfaceId }); + approvalController.accept(id, false); + deleteInterface?.(interfaceId); + break; + + ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) + case SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountCreation: + case SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountRemoval: + case SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showSnapAccountRedirect: + log('Rejecting snap account confirmation', { id }); + approvalController.accept(id, false); + break; + ///: END:ONLY_INCLUDE_IF + + default: + log('Rejecting pending approval', { id }); + approvalController.reject(id, providerErrors.userRejectedRequest()); + break; + } + } +} diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 0e46a598faf8..9ba46c4c64e4 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -380,6 +380,7 @@ import { PatchStore } from './lib/PatchStore'; import { sanitizeUIState } from './lib/state-utils'; import BridgeStatusController from './controllers/bridge-status/bridge-status-controller'; import { BRIDGE_STATUS_CONTROLLER_NAME } from './controllers/bridge-status/constants'; +import { rejectAllApprovals } from './lib/approval/utils'; const { TRIGGER_TYPES } = NotificationServicesController.Constants; export const METAMASK_CONTROLLER_EVENTS = { @@ -3553,10 +3554,6 @@ export default class MetamaskController extends EventEmitter { markNotificationPopupAsAutomaticallyClosed: () => this.notificationManager.markAsAutomaticallyClosed(), - // approval - requestUserApproval: - approvalController.addAndShowApprovalRequest.bind(approvalController), - // primary keyring management addNewAccount: this.addNewAccount.bind(this), getSeedPhrase: this.getSeedPhrase.bind(this), @@ -4180,9 +4177,12 @@ export default class MetamaskController extends EventEmitter { ), trackInsightSnapView: this.trackInsightSnapView.bind(this), - // approval controller - resolvePendingApproval: this.resolvePendingApproval, + // ApprovalController + rejectAllPendingApprovals: this.rejectAllPendingApprovals.bind(this), rejectPendingApproval: this.rejectPendingApproval, + requestUserApproval: + approvalController.addAndShowApprovalRequest.bind(approvalController), + resolvePendingApproval: this.resolvePendingApproval, // Notifications resetViewedNotifications: announcementController.resetViewed.bind( @@ -7075,6 +7075,19 @@ export default class MetamaskController extends EventEmitter { } }; + rejectAllPendingApprovals() { + const deleteInterface = (id) => + this.controllerMessenger.call( + 'SnapInterfaceController:deleteInterface', + id, + ); + + rejectAllApprovals({ + approvalController: this.approvalController, + deleteInterface, + }); + } + async _onAccountChange(newAddress) { const permittedAccountsMap = getPermittedAccountsByOrigin( this.permissionController.state, diff --git a/test/e2e/page-objects/pages/test-snaps.ts b/test/e2e/page-objects/pages/test-snaps.ts new file mode 100644 index 000000000000..327947d121bc --- /dev/null +++ b/test/e2e/page-objects/pages/test-snaps.ts @@ -0,0 +1,50 @@ +import { Driver } from '../../webdriver/driver'; +import { TEST_SNAPS_WEBSITE_URL } from '../../snaps/enums'; +import { WINDOW_TITLES } from '../../helpers'; + +export class TestSnaps { + driver: Driver; + + private readonly connectDialogsSnapButton = + '[data-testid="dialogs"] [data-testid="connect-button"]'; + + private readonly dialogsSnapConfirmationButton = '#sendConfirmationButton'; + + constructor(driver: Driver) { + this.driver = driver; + } + + async openPage() { + await this.driver.openNewPage(TEST_SNAPS_WEBSITE_URL); + await this.driver.delay(1000); + } + + async clickConnectDialogsSnapButton() { + await this.driver.clickElement(this.connectDialogsSnapButton); + } + + async clickDialogsSnapConfirmationButton() { + await this.driver.clickElement(this.dialogsSnapConfirmationButton); + } + + async completeSnapInstallConfirmation() { + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await this.driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + await this.driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + + await this.driver.clickElement({ + text: 'OK', + tag: 'button', + }); + + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + } +} diff --git a/test/e2e/tests/confirmations/navigation.spec.ts b/test/e2e/tests/confirmations/navigation.spec.ts index 97985381b08b..9f26fa697068 100644 --- a/test/e2e/tests/confirmations/navigation.spec.ts +++ b/test/e2e/tests/confirmations/navigation.spec.ts @@ -8,9 +8,14 @@ import { WINDOW_TITLES, } from '../../helpers'; import { Driver } from '../../webdriver/driver'; +import { loginWithoutBalanceValidation } from '../../page-objects/flows/login.flow'; +import TestDapp from '../../page-objects/pages/test-dapp'; +import { createDappTransaction } from '../../page-objects/flows/transaction'; +import { TestSnaps } from '../../page-objects/pages/test-snaps'; +import Confirmation from '../../page-objects/pages/confirmations/redesign/confirmation'; import { withTransactionEnvelopeTypeFixtures } from './helpers'; -describe('Navigation Signature - Different signature types', function (this: Suite) { +describe('Confirmation Navigation', function (this: Suite) { it('initiates and queues multiple signatures and confirms', async function () { await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), @@ -120,6 +125,49 @@ describe('Navigation Signature - Different signature types', function (this: Sui }, ); }); + + it('navigates between transactions, signatures, and snap dialogs', async function () { + await withTransactionEnvelopeTypeFixtures( + this.test?.fullTitle(), + TransactionEnvelopeType.feeMarket, + async ({ driver }: { driver: Driver }) => { + await loginWithoutBalanceValidation(driver); + + const testSnaps = new TestSnaps(driver); + await testSnaps.openPage(); + await testSnaps.clickConnectDialogsSnapButton(); + await testSnaps.completeSnapInstallConfirmation(); + await testSnaps.clickDialogsSnapConfirmationButton(); + + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + await testDapp.clickSignTypedDatav4(); + + await createDappTransaction(driver); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + const confirmation = new Confirmation(driver); + await confirmation.check_pageNumbers(1, 3); + await driver.waitForSelector({ text: 'Confirmation Dialog' }); + + await confirmation.clickNextPage(); + await confirmation.check_pageNumbers(2, 3); + await driver.waitForSelector({ text: 'Signature request' }); + + await confirmation.clickNextPage(); + await confirmation.check_pageNumbers(3, 3); + await driver.waitForSelector({ text: 'Transfer request' }); + + await confirmation.clickPreviousPage(); + await confirmation.check_pageNumbers(2, 3); + await driver.waitForSelector({ text: 'Signature request' }); + + await confirmation.clickPreviousPage(); + await confirmation.check_pageNumbers(1, 3); + await driver.waitForSelector({ text: 'Confirmation Dialog' }); + }, + ); + }); }); async function verifySignTypedData(driver: Driver) { diff --git a/ui/components/app/permission-connect-header/permission-connect-header.js b/ui/components/app/permission-connect-header/permission-connect-header.js index c3f1c248b034..74ab6b06c824 100644 --- a/ui/components/app/permission-connect-header/permission-connect-header.js +++ b/ui/components/app/permission-connect-header/permission-connect-header.js @@ -19,8 +19,9 @@ import { AvatarBase, } from '../../component-library'; import { getAvatarFallbackLetter } from '../../../helpers/utils/util'; +import { Nav } from '../../../pages/confirmations/components/confirm/nav'; -const PermissionConnectHeader = ({ origin, iconUrl }) => { +const PermissionConnectHeader = ({ requestId, origin, iconUrl }) => { const transformOriginToTitle = (rawOrigin) => { try { const url = new URL(rawOrigin); @@ -33,61 +34,65 @@ const PermissionConnectHeader = ({ origin, iconUrl }) => { const title = transformOriginToTitle(origin); return ( - - - {iconUrl ? ( - - ) : ( - - {getAvatarFallbackLetter(title)} - - )} - + <> +