From 3c622cd395f06d5a2e987160002338963badb479 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 16 Mar 2023 20:03:12 -0600 Subject: [PATCH 1/8] [TS dashboard] Draw dependencies between files (#17338) If you're thinking about converting a file to TypeScript, you might want to know what files import that file and what files it imports. This commit makes it so that if you click on a box in the visualization, it will draw lines between that box and the other boxes which represent its dependents and dependencies. Co-authored-by: legobeat <109787230+legobeat@users.noreply.github.com> --- .../app/components/App.tsx | 164 ++++++++++---- .../app/components/Box.tsx | 69 ++++++ .../app/components/Connections.tsx | 143 ++++++++++++ .../app/components/types.ts | 15 ++ .../app/styles/custom-elements.scss | 60 ++++- .../common/build-module-partitions.ts | 207 +++++++++++------- 6 files changed, 537 insertions(+), 121 deletions(-) create mode 100644 development/ts-migration-dashboard/app/components/Box.tsx create mode 100644 development/ts-migration-dashboard/app/components/Connections.tsx create mode 100644 development/ts-migration-dashboard/app/components/types.ts diff --git a/development/ts-migration-dashboard/app/components/App.tsx b/development/ts-migration-dashboard/app/components/App.tsx index 317856a1f070..e5ee1f3d0e89 100644 --- a/development/ts-migration-dashboard/app/components/App.tsx +++ b/development/ts-migration-dashboard/app/components/App.tsx @@ -1,7 +1,9 @@ -import React from 'react'; -import classnames from 'classnames'; -import { Tooltip as ReactTippy } from 'react-tippy'; +import React, { useState } from 'react'; import { readPartitionsFile } from '../../common/partitions-file'; +import type { ModulePartitionChild } from '../../common/build-module-partitions'; +import Box from './Box'; +import Connections from './Connections'; +import type { BoxRect, BoxModel } from './types'; type Summary = { numConvertedModules: number; @@ -12,16 +14,85 @@ function calculatePercentageComplete(summary: Summary) { return ((summary.numConvertedModules / summary.numModules) * 100).toFixed(1); } +const partitions = readPartitionsFile(); +const allModules = partitions.flatMap((partition) => { + return partition.children; +}); +const modulesById = allModules.reduce>( + (obj, module) => { + return { ...obj, [module.id]: module }; + }, + {}, +); +const overallTotal = { + numConvertedModules: allModules.filter((module) => module.hasBeenConverted) + .length, + numModules: allModules.length, +}; + export default function App() { - const partitions = readPartitionsFile(); + const [boxRectsByModuleId, setBoxRectsById] = useState | null>(null); + const boxesByModuleId = + boxRectsByModuleId === null + ? {} + : Object.values(boxRectsByModuleId).reduce>( + (obj, boxRect) => { + const module = modulesById[boxRect.moduleId]; + + const dependencyBoxRects = module.dependencyIds.reduce( + (dependencyBoxes, dependencyId) => { + if (boxRectsByModuleId[dependencyId] === undefined) { + return dependencyBoxes; + } + return [...dependencyBoxes, boxRectsByModuleId[dependencyId]]; + }, + [], + ); + + const dependentBoxRects = module.dependentIds.reduce( + (dependentBoxes, dependentId) => { + if (boxRectsByModuleId[dependentId] === undefined) { + return dependentBoxes; + } + return [...dependentBoxes, boxRectsByModuleId[dependentId]]; + }, + [], + ); - const allModules = partitions.flatMap((partition) => { - return partition.children; - }); - const overallTotal = { - numConvertedModules: allModules.filter((module) => module.hasBeenConverted) - .length, - numModules: allModules.length, + return { + ...obj, + [boxRect.moduleId]: { + rect: boxRect, + dependencyBoxRects, + dependentBoxRects, + }, + }; + }, + {}, + ); + const [activeBoxRectId, setActiveBoxRectId] = useState(null); + const activeBoxRect = + boxRectsByModuleId === null || activeBoxRectId === null + ? null + : boxRectsByModuleId[activeBoxRectId]; + + const registerBox = (id: string, boxRect: BoxRect) => { + setBoxRectsById((existingBoxRectsById) => { + if (existingBoxRectsById === undefined) { + return { [id]: boxRect }; + } + return { ...existingBoxRectsById, [id]: boxRect }; + }); + }; + const toggleConnectionsFor = (id: string) => { + if (activeBoxRectId !== undefined && activeBoxRectId === id) { + setActiveBoxRectId(null); + } else { + setActiveBoxRectId(id); + } }; return ( @@ -50,17 +121,35 @@ export default function App() {

- Each box -

+ Each box on this page represents a file in the codebase. Gray boxes +   -
- on this page represents a file that either we want to convert or - we've already converted to TypeScript (hover over a box to see the - filename). Boxes that are -
+ + represent files that need to be converted to TypeScript. Green boxes +   -
- greyed out are test or Storybook files. + + are files that have already been converted. Faded boxes + +   + + +   + + are test or Storybook files. +

+ +

+ You can hover over a box to see the name of the file that it + represents. You can also click on a box to see connections between + other files;{' '} + red lines + lead to dependencies (other files that import the file);{' '} + blue lines + lead to dependents (other files that are imported by the file).

@@ -103,36 +192,25 @@ export default function App() {

level {partition.level}
- {partition.children.map(({ name, hasBeenConverted }) => { - const isTest = /\.test\.(?:js|tsx?)/u.test(name); - const isStorybookModule = /\.stories\.(?:js|tsx?)/u.test( - name, - ); + {partition.children.map((module) => { + const areConnectionsVisible = activeBoxRectId === module.id; return ( - -
- + ); })}
); })} + {activeBoxRect === null ? null : ( + + )}
); diff --git a/development/ts-migration-dashboard/app/components/Box.tsx b/development/ts-migration-dashboard/app/components/Box.tsx new file mode 100644 index 000000000000..675cb63db1ee --- /dev/null +++ b/development/ts-migration-dashboard/app/components/Box.tsx @@ -0,0 +1,69 @@ +import React, { useEffect, useRef } from 'react'; +import classnames from 'classnames'; +import { Tooltip as ReactTippy } from 'react-tippy'; +import type { ModulePartitionChild } from '../../common/build-module-partitions'; +import type { BoxRect } from './types'; + +export default function Box({ + module, + register, + toggleConnectionsFor, + areConnectionsVisible, +}: { + module: ModulePartitionChild; + register: (id: string, boxRect: BoxRect) => void; + toggleConnectionsFor: (id: string) => void; + areConnectionsVisible: boolean; +}) { + const isTest = /\.test\.(?:js|tsx?)/u.test(module.id); + const isStorybookModule = /\.stories\.(?:js|tsx?)/u.test(module.id); + const ref = useRef(null); + + useEffect(() => { + if (ref.current?.offsetParent) { + const rect = ref.current.getBoundingClientRect(); + const offsetParentRect = ref.current.offsetParent.getBoundingClientRect(); + const top = rect.top - offsetParentRect.top; + const left = rect.left - offsetParentRect.left; + const centerX = left + rect.width / 2; + const centerY = top + rect.height / 2; + register(module.id, { + moduleId: module.id, + top, + left, + width: rect.width, + height: rect.height, + centerX, + centerY, + }); + } + }, [ref]); + + const onClick = (event: React.MouseEvent) => { + event.preventDefault(); + toggleConnectionsFor(module.id); + }; + + return ( + +
+ + ); +} diff --git a/development/ts-migration-dashboard/app/components/Connections.tsx b/development/ts-migration-dashboard/app/components/Connections.tsx new file mode 100644 index 000000000000..329bfb3e5d8f --- /dev/null +++ b/development/ts-migration-dashboard/app/components/Connections.tsx @@ -0,0 +1,143 @@ +import React from 'react'; +import type { BoxModel } from './types'; + +function buildShapePoints(coordinates: [number, number][]): string { + return coordinates.map(([x, y]) => `${x},${y}`).join(' '); +} + +function buildPathD(coordinates: [number, number][]): string { + return coordinates + .map(([x, y], index) => { + if (index === 0) { + return `M ${x},${y}`; + } + return `L ${x},${y}`; + }) + .join(' '); +} + +function Arrowhead({ + type, + x, + y, +}: { + type: 'dependency' | 'dependent'; + x: number; + y: number; +}) { + return ( + + ); +} + +function Line({ + type, + originX, + originY, + originYOffset = 0, + destinationX, + destinationY, + destinationYOffset = 0, +}: { + type: 'dependency' | 'dependent'; + originX: number; + originY: number; + originYOffset?: number; + destinationX: number; + destinationY: number; + destinationYOffset?: number; +}) { + const coordinates: [number, number][] = + type === 'dependency' + ? [ + [originX, originY], + [originX, originY + originYOffset], + [destinationX, originY + originYOffset], + [destinationX, destinationY], + ] + : [ + [originX, originY], + [originX, destinationY - destinationYOffset], + [destinationX, destinationY - destinationYOffset], + [destinationX, destinationY], + ]; + return ( + + ); +} + +function LineStart({ + type, + x, + y, +}: { + type: 'dependency' | 'dependent'; + x: number; + y: number; +}) { + return ( + + ); +} + +export default function Connections({ activeBox }: { activeBox: BoxModel }) { + return ( + + {activeBox.dependencyBoxRects.length === 0 ? null : ( + + )} + {activeBox.dependencyBoxRects.map((dependencyBoxRect) => { + return ( + + + + + ); + })} + {activeBox.dependentBoxRects.map((dependentBoxRect) => { + return ( + + + + + ); + })} + + ); +} diff --git a/development/ts-migration-dashboard/app/components/types.ts b/development/ts-migration-dashboard/app/components/types.ts new file mode 100644 index 000000000000..65b2349da045 --- /dev/null +++ b/development/ts-migration-dashboard/app/components/types.ts @@ -0,0 +1,15 @@ +export type BoxRect = { + moduleId: string; + top: number; + left: number; + width: number; + height: number; + centerX: number; + centerY: number; +}; + +export type BoxModel = { + rect: BoxRect; + dependencyBoxRects: BoxRect[]; + dependentBoxRects: BoxRect[]; +}; diff --git a/development/ts-migration-dashboard/app/styles/custom-elements.scss b/development/ts-migration-dashboard/app/styles/custom-elements.scss index 216c1ed6d79a..e53562024291 100644 --- a/development/ts-migration-dashboard/app/styles/custom-elements.scss +++ b/development/ts-migration-dashboard/app/styles/custom-elements.scss @@ -2,6 +2,10 @@ --blue-gray-350: hsl(209deg 13.7% 62.4%); --blue-gray-100: hsl(209.8deg 16.5% 89%); --green: hsl(113deg 100% 38%); + --red: hsl(13deg 98% 61%); + --blue: hsl(246deg 97% 55%); + --light-cyan: hsl(178deg 100% 85%); + --cyan: hsl(178deg 100% 42%); } .page-header { @@ -69,6 +73,10 @@ border: 1px solid rgb(0 0 0 / 50%); } +.partitions { + position: relative; +} + .partition { display: flex; gap: 0.5rem; @@ -94,8 +102,9 @@ .module { width: 1.5rem; height: 1.5rem; - border: 1px solid rgba(0 0 0 / 25%); + border: 1px solid hsla(0deg 0% 0% / 25%); border-radius: 0.25rem; + cursor: pointer; &--inline { display: inline-block; @@ -120,6 +129,55 @@ &--storybook { opacity: 0.3; } + + &--active { + border-color: var(--cyan); + background-color: var(--light-cyan); + border-width: 2px; + } +} + +.module-connections { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + pointer-events: none; +} + +.module-connection { + &__dependency-arrowhead { + fill: var(--red); + } + + &__dependency { + fill: none; + stroke: var(--red); + color: var(--red); + stroke-width: 2px; + } + + &__dependency-point { + fill: var(--red); + r: 3px; + } + + &__dependent-arrowhead { + fill: var(--blue); + } + + &__dependent { + fill: none; + stroke: var(--blue); + color: var(--blue); + stroke-width: 2px; + } + + &__dependent-point { + fill: var(--blue); + r: 3px; + } } /* Package overrides */ diff --git a/development/ts-migration-dashboard/common/build-module-partitions.ts b/development/ts-migration-dashboard/common/build-module-partitions.ts index 8ceaff18a3b4..aa58568b3d58 100644 --- a/development/ts-migration-dashboard/common/build-module-partitions.ts +++ b/development/ts-migration-dashboard/common/build-module-partitions.ts @@ -9,24 +9,42 @@ import { } from './constants'; /** - * Represents a module that has been imported somewhere in the codebase. + * Represents a module that has been imported somewhere in the codebase, whether + * it is local to the project or an NPM package. * - * @property id - The name of a file or NPM module. + * @property id - If an NPM package, then the name of the package; otherwise the + * path to a file within the project. * @property dependents - The modules which are imported by this module. * @property level - How many modules it takes to import this module (from the * root of the dependency tree). - * @property isExternal - Whether the module refers to a NPM module. + * @property isExternal - Whether the module refers to a NPM package. * @property hasBeenConverted - Whether the module was one of the files we * wanted to convert to TypeScript and has been converted. */ type Module = { id: string; dependents: Module[]; + dependencies: Module[]; level: number; isExternal: boolean; hasBeenConverted: boolean; }; +/** + * Represents a module that belongs to a certain level within the dependency + * graph, displayed as a box in the UI. + * + * @property id - The id of the module. + * @property hasBeenConverted - Whether or not the module has been converted to + * TypeScript. + */ +export type ModulePartitionChild = { + id: string; + hasBeenConverted: boolean; + dependentIds: string[]; + dependencyIds: string[]; +}; + /** * Represents a set of modules that sit at a certain level within the final * dependency tree. @@ -34,16 +52,10 @@ type Module = { * @property level - How many modules it takes to import this module (from the * root of the dependency tree). * @property children - The modules that share this same level. - * @property children[].name - The name of the item being imported. - * @property children[].hasBeenConverted - Whether or not the module (assuming - * that it is a file in our codebase) has been converted to TypeScript. */ export type ModulePartition = { level: number; - children: { - name: string; - hasBeenConverted: boolean; - }[]; + children: ModulePartitionChild[]; }; /** @@ -196,10 +208,11 @@ function buildModulesWithLevels( // includes `foo.js`, so we say `foo.js` is a circular dependency of `baz.js`, // and we don't need to follow it. - const modulesToFill: Module[] = entryModuleIds.map((moduleId) => { + let modulesToFill: Module[] = entryModuleIds.map((moduleId) => { return { id: moduleId, dependents: [], + dependencies: [], level: 0, isExternal: false, hasBeenConverted: /\.tsx?$/u.test(moduleId), @@ -208,76 +221,101 @@ function buildModulesWithLevels( const modulesById: Record = {}; while (modulesToFill.length > 0) { - const currentModule = modulesToFill.shift() as Module; - const childModulesToFill: Module[] = []; - (dependenciesByModuleId[currentModule.id] ?? []).forEach( - (givenChildModuleId) => { - const npmPackageMatch = givenChildModuleId.match( - /^node_modules\/(?:(@[^/]+)\/)?([^/]+)\/.+$/u, - ); + const currentModule = modulesToFill[0]; - let childModuleId; - if (npmPackageMatch) { - childModuleId = - npmPackageMatch[1] === undefined - ? `${npmPackageMatch[2]}` - : `${npmPackageMatch[1]}/${npmPackageMatch[2]}`; - } else { - childModuleId = givenChildModuleId; - } + if (currentModule.level > 100) { + throw new Error( + "Can't build module partitions, as the dependency graph is being traversed ad infinitum.", + ); + } - // Skip circular dependencies - if ( - findDirectAndIndirectDependentIdsOf(currentModule).has(childModuleId) - ) { - return; - } + const dependencyModulesToFill: Module[] = []; + for (const nonCanonicalDependencyModuleId of dependenciesByModuleId[ + currentModule.id + ]) { + // If the file path of the module is located in `node_modules`, use the + // name of the package as the ID + let dependencyModuleId; + const npmPackageMatch = nonCanonicalDependencyModuleId.match( + /^node_modules\/(?:(@[^/]+)\/)?([^/]+)\/.+$/u, + ); + if (npmPackageMatch) { + dependencyModuleId = + npmPackageMatch[1] === undefined + ? `${npmPackageMatch[2]}` + : `${npmPackageMatch[1]}/${npmPackageMatch[2]}`; + } else { + dependencyModuleId = nonCanonicalDependencyModuleId; + } - // Skip files that weren't on the original list of JavaScript files to - // convert, as we don't want them to show up on the status dashboard - if ( - !npmPackageMatch && - !allowedFilePaths.includes(childModuleId.replace(/\.tsx?$/u, '.js')) - ) { - return; - } + // Skip circular dependencies + if ( + findDirectAndIndirectDependentIdsOf(currentModule).has( + dependencyModuleId, + ) + ) { + continue; + } - const existingChildModule = modulesById[childModuleId]; + // Skip files that weren't on the original list of JavaScript files to + // convert, as we don't want them to show up on the status dashboard + if ( + !npmPackageMatch && + !allowedFilePaths.includes( + dependencyModuleId.replace(/\.tsx?$/u, '.js'), + ) + ) { + continue; + } - if (existingChildModule === undefined) { - const childModule: Module = { - id: childModuleId, - dependents: [currentModule], - level: currentModule.level + 1, - isExternal: Boolean(npmPackageMatch), - hasBeenConverted: /\.tsx?$/u.test(childModuleId), - }; - childModulesToFill.push(childModule); - } else { - if (currentModule.level + 1 > existingChildModule.level) { - existingChildModule.level = currentModule.level + 1; - // Update descendant modules' levels - childModulesToFill.push(existingChildModule); - } else { - // There is no point in descending into dependencies of this module - // if the new level of the module would be <= the existing level, - // because all of the dependencies from this point on are guaranteed - // to have a higher level and are therefore already at the right - // level. - } + let existingDependencyModule = modulesById[dependencyModuleId]; - if (!existingChildModule.dependents.includes(currentModule)) { - existingChildModule.dependents.push(currentModule); - } - } - }, - ); - if (childModulesToFill.length > 0) { - modulesToFill.push(...childModulesToFill); + if (existingDependencyModule === undefined) { + existingDependencyModule = { + id: dependencyModuleId, + dependents: [currentModule], + dependencies: [], + level: currentModule.level + 1, + isExternal: Boolean(npmPackageMatch), + hasBeenConverted: /\.tsx?$/u.test(dependencyModuleId), + }; + dependencyModulesToFill.push(existingDependencyModule); + } else if (currentModule.level + 1 > existingDependencyModule.level) { + existingDependencyModule.level = currentModule.level + 1; + // Update descendant modules' levels + dependencyModulesToFill.push(existingDependencyModule); + } else { + // There is no point in descending into dependencies of this module + // if the new level of the module would be <= the existing level, + // because all of the dependencies from this point on are guaranteed + // to have a higher level and are therefore already at the right + // level. + } + + if ( + !existingDependencyModule.dependents.some( + (m) => m.id === currentModule.id, + ) + ) { + existingDependencyModule.dependents.push(currentModule); + } + + if ( + !currentModule.dependencies.some( + (m) => m.id === existingDependencyModule.id, + ) + ) { + currentModule.dependencies.push(existingDependencyModule); + } + } + if (dependencyModulesToFill.length > 0) { + modulesToFill.push(...dependencyModulesToFill); } + if (!(currentModule.id in modulesById)) { modulesById[currentModule.id] = currentModule; } + modulesToFill = modulesToFill.slice(1); } return modulesById; @@ -288,20 +326,21 @@ function buildModulesWithLevels( * which import that file directly or through some other file. * * @param module - A module in the graph. - * @returns The ids of the modules which are incoming connections to + * @returns The ids of the modules which are direct and indirect dependents of * the module. */ function findDirectAndIndirectDependentIdsOf(module: Module): Set { - const modulesToProcess = [module]; + let modulesToProcess = [module]; const allDependentIds = new Set(); while (modulesToProcess.length > 0) { - const currentModule = modulesToProcess.shift() as Module; - currentModule.dependents.forEach((dependent) => { + const currentModule = modulesToProcess[0]; + for (const dependent of currentModule.dependents) { if (!allDependentIds.has(dependent.id)) { allDependentIds.add(dependent.id); modulesToProcess.push(dependent); } - }); + } + modulesToProcess = modulesToProcess.slice(1); } return allDependentIds; } @@ -328,8 +367,22 @@ function partitionModulesByLevel( } Object.values(modulesById).forEach((module) => { modulePartitions[module.level].children.push({ - name: module.id, + id: module.id, hasBeenConverted: module.hasBeenConverted, + dependentIds: module.dependents.map((dependent) => dependent.id).sort(), + dependencyIds: module.dependencies + .map((dependency) => dependency.id) + .sort(), + }); + }); + Object.values(modulePartitions).forEach((partition) => { + partition.children.sort((a, b) => { + if (a.id < b.id) { + return -1; + } else if (a.id > b.id) { + return 1; + } + return 0; }); }); return modulePartitions.reverse(); From e1ce248364f5e4cad6514a1b2ba78e8b325bdfe9 Mon Sep 17 00:00:00 2001 From: Peter <53189696+PeterYinusa@users.noreply.github.com> Date: Fri, 17 Mar 2023 09:55:52 +0000 Subject: [PATCH 2/8] e2e test erc20 fixtures (#18154) * add fixture * update test * update test * wait for ETH balance --- test/e2e/fixture-builder.js | 37 ++++++++++++ test/e2e/tests/send-hex-address.spec.js | 79 +++++++------------------ test/e2e/tests/send-to-contract.spec.js | 24 +------- 3 files changed, 58 insertions(+), 82 deletions(-) diff --git a/test/e2e/fixture-builder.js b/test/e2e/fixture-builder.js index 145d6bfc0431..a56197f4e694 100644 --- a/test/e2e/fixture-builder.js +++ b/test/e2e/fixture-builder.js @@ -701,6 +701,43 @@ class FixtureBuilder { return this; } + withTokensControllerERC20() { + merge(this.fixture.data.TokensController, { + tokens: [ + { + address: `__FIXTURE_SUBSTITUTION__CONTRACT${SMART_CONTRACTS.HST}`, + symbol: 'TST', + decimals: 4, + image: + 'https://static.metafi.codefi.network/api/v1/tokenIcons/1337/0x581c3c1a2a4ebde2a0df29b5cf4c116e42945947.png', + isERC721: false, + aggregators: [], + }, + ], + ignoredTokens: [], + detectedTokens: [], + allTokens: { + '0x539': { + '0x5cfe73b6021e818b776b421b1c4db2474086a7e1': [ + { + address: `__FIXTURE_SUBSTITUTION__CONTRACT${SMART_CONTRACTS.HST}`, + symbol: 'TST', + decimals: 4, + image: + 'https://static.metafi.codefi.network/api/v1/tokenIcons/1337/0x581c3c1a2a4ebde2a0df29b5cf4c116e42945947.png', + isERC721: false, + aggregators: [], + }, + ], + }, + }, + allIgnoredTokens: {}, + allDetectedTokens: {}, + suggestedAssets: [], + }); + return this; + } + withTransactionController(data) { merge( this.fixture.data.TransactionController diff --git a/test/e2e/tests/send-hex-address.spec.js b/test/e2e/tests/send-hex-address.spec.js index fcde578742e0..32301d325c6d 100644 --- a/test/e2e/tests/send-hex-address.spec.js +++ b/test/e2e/tests/send-hex-address.spec.js @@ -123,43 +123,20 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { await withFixtures( { dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), + fixtures: new FixtureBuilder().withTokensControllerERC20().build(), ganacheOptions, smartContract, title: this.test.title, failOnConsoleError: false, }, - async ({ driver, contractRegistry }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); + async ({ driver }) => { await driver.navigate(); await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - - await driver.openNewPage( - `http://127.0.0.1:8080/?contract=${contractAddress}`, - ); - let windowHandles = await driver.getAllWindowHandles(); - const extension = windowHandles[0]; - - // Using the line below to make wait time deterministic and avoid using a delay - // See more here https://github.com/MetaMask/metamask-extension/pull/15604/files#r949300551 - await driver.findClickableElement('#deployButton'); - - // Add token - await driver.clickElement('#watchAsset'); - await driver.waitUntilXWindowHandles(3); - windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); - await driver.clickElement({ text: 'Add token', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindow(extension); + await driver.waitForSelector({ + css: '[data-testid="eth-overview__primary-currency"]', + text: '24.9977 ETH', + }); // Send TST await driver.clickElement('[data-testid="home__asset-tab"]'); @@ -175,7 +152,10 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { css: '.ens-input__selected-input__title', text: hexPrefixedAddress, }); - await driver.delay(2000); + await driver.waitForSelector({ + css: '.transaction-detail-item', + text: '0.00008346 ETH', + }); await driver.clickElement({ text: 'Next', tag: 'button' }); // Confirm transaction @@ -208,42 +188,20 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { await withFixtures( { dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), + fixtures: new FixtureBuilder().withTokensControllerERC20().build(), ganacheOptions, smartContract, title: this.test.title, failOnConsoleError: false, }, - async ({ driver, contractRegistry }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); + async ({ driver }) => { await driver.navigate(); await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - - // Create TST - await driver.openNewPage( - `http://127.0.0.1:8080/?contract=${contractAddress}`, - ); - - let windowHandles = await driver.getAllWindowHandles(); - const extension = windowHandles[0]; - - // Add token - await driver.findClickableElement('#deployButton'); - await driver.clickElement('#watchAsset'); - await driver.waitUntilXWindowHandles(3); - windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); - await driver.clickElement({ text: 'Add token', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindow(extension); + await driver.waitForSelector({ + css: '[data-testid="eth-overview__primary-currency"]', + text: '24.9977 ETH', + }); // Send TST await driver.clickElement('[data-testid="home__asset-tab"]'); @@ -259,7 +217,10 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { css: '.ens-input__selected-input__title', text: hexPrefixedAddress, }); - await driver.delay(2000); + await driver.waitForSelector({ + css: '.transaction-detail-item', + text: '0.00008346 ETH', + }); await driver.clickElement({ text: 'Next', tag: 'button' }); // Confirm transaction diff --git a/test/e2e/tests/send-to-contract.spec.js b/test/e2e/tests/send-to-contract.spec.js index e7dffa5e7882..62a6a58e4275 100644 --- a/test/e2e/tests/send-to-contract.spec.js +++ b/test/e2e/tests/send-to-contract.spec.js @@ -18,9 +18,7 @@ describe('Send ERC20 token to contract address', function () { await withFixtures( { dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), + fixtures: new FixtureBuilder().withTokensControllerERC20().build(), ganacheOptions, smartContract, title: this.test.title, @@ -34,26 +32,6 @@ describe('Send ERC20 token to contract address', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - // Create TST - await driver.openNewPage( - `http://127.0.0.1:8080/?contract=${contractAddress}`, - ); - let windowHandles = await driver.getAllWindowHandles(); - const extension = windowHandles[0]; - - // Add token - await driver.findClickableElement('#deployButton'); - await driver.clickElement('#watchAsset'); - await driver.waitUntilXWindowHandles(3); - windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); - await driver.clickElement({ text: 'Add token', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindow(extension); - // Send TST await driver.clickElement('[data-testid="home__asset-tab"]'); await driver.clickElement('.token-cell'); From d6f58bceb0f9ca9087ff51fe9ce80d3e1993458b Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Fri, 17 Mar 2023 12:00:05 +0100 Subject: [PATCH 3/8] [FLASK] `snaps-monorepo@0.31.0` (#18142) * allow SnapController to call `ApprovalController:updateRequestState` action * combine popups * show only autorship pill on result * lint * update `snaps-monorepo@0.31.0` and regen policies * dedupe deps and fix fencing * fix update button text * fix fencing * Update a bunch of e2es * address requested changes * update policy * bump key-tree * fix lint * Update RPC E2E * fix locales * Remove wrong instance of window handle polling * design changes and address pr comments * remove unused imports * fix lint * fix fencing * remove unused locales * fence things * re-add redirection * bump test-snaps version * Fix update e2e * fix redirecting logic and address requested changes * force update metamask state on approved * move force update --------- Co-authored-by: Frederik Bolding --- app/_locales/de/messages.json | 7 - app/_locales/el/messages.json | 7 - app/_locales/en/messages.json | 43 ++++-- app/_locales/es/messages.json | 7 - app/_locales/fr/messages.json | 7 - app/_locales/hi/messages.json | 7 - app/_locales/id/messages.json | 7 - app/_locales/ja/messages.json | 7 - app/_locales/ko/messages.json | 7 - app/_locales/pt/messages.json | 7 - app/_locales/ru/messages.json | 7 - app/_locales/tl/messages.json | 7 - app/_locales/tr/messages.json | 7 - app/_locales/vi/messages.json | 7 - app/_locales/zh_CN/messages.json | 7 - app/scripts/metamask-controller.js | 2 +- lavamoat/browserify/beta/policy.json | 14 +- lavamoat/browserify/desktop/policy.json | 56 +------- lavamoat/browserify/flask/policy.json | 56 +------- lavamoat/browserify/main/policy.json | 14 +- package.json | 10 +- test/e2e/snaps/enums.js | 2 +- test/e2e/snaps/test-snap-bip-32.spec.js | 17 ++- test/e2e/snaps/test-snap-bip-44.spec.js | 17 ++- test/e2e/snaps/test-snap-cronjob.spec.js | 15 +- test/e2e/snaps/test-snap-dialog.spec.js | 15 +- test/e2e/snaps/test-snap-error.spec.js | 17 +-- test/e2e/snaps/test-snap-installed.spec.js | 31 +++-- test/e2e/snaps/test-snap-management.spec.js | 16 +-- test/e2e/snaps/test-snap-managestate.spec.js | 18 +-- test/e2e/snaps/test-snap-notification.spec.js | 18 +-- test/e2e/snaps/test-snap-rpc.spec.js | 84 +++++------ test/e2e/snaps/test-snap-txinsights.spec.js | 19 +-- test/e2e/snaps/test-snap-update.spec.js | 28 ++-- .../app/flask/install-error/index.js | 1 + .../app/flask/install-error/install-error.js | 39 ++++++ .../snap-install-warning.js | 29 ++-- .../snaps-authorship-pill.js | 17 ++- ui/helpers/constants/routes.ts | 2 + ui/helpers/utils/permission.js | 13 +- ui/helpers/utils/util.js | 14 +- .../flask/snap-install/index.scss | 21 ++- .../flask/snap-install/snap-install.js | 119 ++++++++++++---- .../flask/snap-result/index.js | 1 + .../flask/snap-result/index.scss | 16 +++ .../flask/snap-result/snap-result.js | 128 +++++++++++++++++ .../flask/snap-update/index.scss | 24 +++- .../flask/snap-update/snap-update.js | 130 +++++++++++++----- ui/pages/permissions-connect/flask/util.js | 50 +++++-- ui/pages/permissions-connect/index.scss | 1 + .../permissions-connect.component.js | 128 +++++++++++++---- .../permissions-connect.container.js | 19 ++- ui/selectors/permissions.js | 12 +- ui/store/actions.ts | 1 + yarn.lock | 102 +++++++------- 55 files changed, 883 insertions(+), 574 deletions(-) create mode 100644 ui/components/app/flask/install-error/index.js create mode 100644 ui/components/app/flask/install-error/install-error.js create mode 100644 ui/pages/permissions-connect/flask/snap-result/index.js create mode 100644 ui/pages/permissions-connect/flask/snap-result/index.scss create mode 100644 ui/pages/permissions-connect/flask/snap-result/snap-result.js diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 43af11d51369..f5e257ef6686 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -3376,16 +3376,9 @@ "message": "Sie gewähren dem Snap „$1“ wichtige $2-Zugriffsrechte. Dies kann nicht rückgängig gemacht werden und gibt „$1“ Kontrolle über Ihre $2-Konten und Vermögenswerte. Stellen Sie sicher, dass Sie „$1“ vertrauen, bevor Sie fortfahren.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, - "snapRequestsPermission": { - "message": "Für diesen Snap werden die folgenden Berechtigungen beantragt:" - }, "snapUpdate": { "message": "Snap aktualisieren" }, - "snapUpdateExplanation": { - "message": "$1 benötigt eine neuere Version Ihres Snaps.", - "description": "$1 is the dapp that is requesting an update to the snap." - }, "snaps": { "message": "Snaps" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index d52e449d23ef..f2ab8a5e08a7 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -3376,16 +3376,9 @@ "message": "Εκχωρείτε στο $2 βασική πρόσβαση στο snap \"$1\". Αυτό είναι αμετάκλητο και παρέχει στο \"$1\" τον έλεγχο των λογαριασμών και των περιουσιακών σας στοιχείων $2. Βεβαιωθείτε ότι εμπιστεύεστε το \"$1\" προτού συνεχίσετε.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, - "snapRequestsPermission": { - "message": "Αυτό το snap αιτείται τις παρακάτω άδειες:" - }, "snapUpdate": { "message": "Ενημέρωση Snap" }, - "snapUpdateExplanation": { - "message": "Το $1 χρειάζεται μια νεότερη έκδοση του snap σας.", - "description": "$1 is the dapp that is requesting an update to the snap." - }, "snaps": { "message": "Snaps" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 06f9ea6d720c..59eae8ed986d 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -3028,6 +3028,9 @@ "replace": { "message": "replace" }, + "requestFailed": { + "message": "Request failed" + }, "requestFlaggedAsMaliciousFallbackCopyReason": { "message": "The security provider has not shared additional details" }, @@ -3427,25 +3430,46 @@ "snapInstall": { "message": "Install snap" }, + "snapInstallRequest": { + "message": "$1 wants to install $2. Make sure you trust the authors before you proceed.", + "description": "$1 is the dApp origin requesting the snap and $2 is the snap name" + }, + "snapInstallRequestsPermission": { + "message": "$1 wants to install $2, which is requesting the following permissions. Make sure you trust the authors before you proceed.", + "description": "$1 is the dApp origin requesting the snap and $2 is the snap name" + }, "snapInstallWarningCheck": { - "message": "To confirm that you understand, check the box." + "message": "Ensure that the permission below align with your intended actions. Only proceed with authors you trust." }, "snapInstallWarningCheckPlural": { - "message": "To confirm that you understand, check all the boxes." + "message": "Ensure that the permissions below align with your intended actions. Only proceed with authors you trust." + }, + "snapInstallWarningHeading": { + "message": "Proceed with caution" }, "snapInstallWarningKeyAccess": { - "message": "You are granting $2 key access to the snap \"$1\". This is irrevocable and grants \"$1\" control of your $2 accounts and assets. Make sure you trust \"$1\" before proceeding.", + "message": "Grant $2 key access to $1", "description": "The first parameter is the name of the snap and the second one is the protocol" }, - "snapRequestsPermission": { - "message": "This snap is requesting the following permissions:" + "snapResultError": { + "message": "Error" + }, + "snapResultSuccess": { + "message": "Success" + }, + "snapResultSuccessDescription": { + "message": "$1 is now available to use." }, "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." + "snapUpdateRequest": { + "message": "$1 wants to update $2. Make sure you trust the authors before you proceed.", + "description": "$1 is the dApp origin requesting the snap and $2 is the snap name" + }, + "snapUpdateRequestsPermission": { + "message": "$1 wants to update $2, which is requesting the following permissions. Make sure you trust the authors before you proceed.", + "description": "$1 is the dApp origin requesting the snap and $2 is the snap name" }, "snaps": { "message": "Snaps" @@ -4386,6 +4410,9 @@ "upArrow": { "message": "up arrow" }, + "update": { + "message": "Update" + }, "updatedWithDate": { "message": "Updated $1" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 73c1a4e7ba6f..ad1319017ee3 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -3376,16 +3376,9 @@ "message": "Está otorgando acceso clave de $2 al complemento \"$1\". Esto es irrevocable y le otorga a \"$1\" el control de sus cuentas y activos de $2. Asegúrese de que confía en \"$1\" antes de continuar.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, - "snapRequestsPermission": { - "message": "Este complemento solicita los siguientes permisos:" - }, "snapUpdate": { "message": "Actualizar complemento" }, - "snapUpdateExplanation": { - "message": "$1 necesita una versión más reciente de su complemento.", - "description": "$1 is the dapp that is requesting an update to the snap." - }, "snaps": { "message": "Complementos" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 97e1254acb5b..78408e2dbd01 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -3376,16 +3376,9 @@ "message": "Vous autorisez $2 à accéder à la clé du snap « $1 ». Cette action est irréversible et accorde à « $1 » le contrôle de vos comptes et actifs $2. Assurez-vous que vous faites confiance à « $1 » avant de continuer.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, - "snapRequestsPermission": { - "message": "Ce snap demande les autorisations suivantes :" - }, "snapUpdate": { "message": "Mettre à jour Snap" }, - "snapUpdateExplanation": { - "message": "$1 a besoin d’une version plus récente de votre snap.", - "description": "$1 is the dapp that is requesting an update to the snap." - }, "snaps": { "message": "Snaps" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 65ebfc9947b9..61eafe59d5d8 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -3376,16 +3376,9 @@ "message": "आप स्नैप \"$1\" के लिए $2 कुंजी का एक्सेस प्रदान कर रहे हैं। यह अपरिवर्तनीय है और आपके $2 खातों और संपत्तियों पर \"$1\" नियंत्रण प्रदान करता है। आगे बढ़ने से पहले सुनिश्चित करें कि आप \"$1\" पर भरोसा करते हैं।", "description": "The first parameter is the name of the snap and the second one is the protocol" }, - "snapRequestsPermission": { - "message": "ये स्नैप निम्नलिखित अनुमतियों हेतु अनुरोध कर रहा है:" - }, "snapUpdate": { "message": "स्नैप अपडेट करें" }, - "snapUpdateExplanation": { - "message": "$1 को आपके स्नैप के नए वर्जन की जरूरत है।", - "description": "$1 is the dapp that is requesting an update to the snap." - }, "snaps": { "message": "स्नैप्स" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index d8edf57a250b..76dd89d9bd55 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -3376,16 +3376,9 @@ "message": "Anda memberikan $2 akses kunci ke snap \"$1\". Tindakan ini tidak dapat dibatalkan dan memberikan kendali \"$1\" atas akun dan aset $2 Anda. Sebelum melanjutkan, pastikan \"$1\" aman.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, - "snapRequestsPermission": { - "message": "Snap ini meminta izin berikut:" - }, "snapUpdate": { "message": "Perbarui Snap" }, - "snapUpdateExplanation": { - "message": "$1 memerlukan versi snap yang lebih baru.", - "description": "$1 is the dapp that is requesting an update to the snap." - }, "snaps": { "message": "Snap" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 16f6624fa6de..eb13dbc01285 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -3376,16 +3376,9 @@ "message": "スナップ「$1」に $2 へのキーアクセスを許可しようとしています。この操作は取り消し不能であり、$2 アカウントとアセットのコントロールを「$1」に許可することになります。続行する前に、必ず「$1」が信頼できることを確認してください。", "description": "The first parameter is the name of the snap and the second one is the protocol" }, - "snapRequestsPermission": { - "message": "このスナップが次のパーミッションをリクエストしています:" - }, "snapUpdate": { "message": "スナップを更新" }, - "snapUpdateExplanation": { - "message": "$1 に新しいバージョンのスナップが必要です。", - "description": "$1 is the dapp that is requesting an update to the snap." - }, "snaps": { "message": "スナップ" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index c328b268c65f..7b24d0458f3c 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -3376,16 +3376,9 @@ "message": "'$1' 스냅 이용에 필요한 $2 키 액세스 권한을 부여하고 있습니다. 이 작업은 사용자의 $2 계정과 자산에 '$1' 제어 권한을 부여하며 취소가 불가능합니다. '$1의 신뢰성을 확인한 후에 진행하세요.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, - "snapRequestsPermission": { - "message": "이 스냅이 다음 권한을 요청하고 있습니다." - }, "snapUpdate": { "message": "스냅 업데이트" }, - "snapUpdateExplanation": { - "message": "$1에는 스냅의 새 버전이 필요합니다", - "description": "$1 is the dapp that is requesting an update to the snap." - }, "snaps": { "message": "스냅" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 2c06c360f8e5..ae39ab7d96d3 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -3376,16 +3376,9 @@ "message": "Você está concedendo ao snap \"$1\" acesso à sua chave $2. Isso é irrevogável e concede a \"$1\" controle de suas contas e ativos $2. Certifique-se de que confia em \"$1\" antes de prosseguir.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, - "snapRequestsPermission": { - "message": "Esse snap está solicitando as seguintes permissões:" - }, "snapUpdate": { "message": "Atualizar snap" }, - "snapUpdateExplanation": { - "message": "$1 precisa de uma versão mais nova do seu snap.", - "description": "$1 is the dapp that is requesting an update to the snap." - }, "snaps": { "message": "Snaps" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 4e20ccf14423..f4edcbfc19e0 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -3376,16 +3376,9 @@ "message": "Вы предоставляете ключ доступа $2 к привязке \"$1\". Это действие нельзя отменить, и оно предоставляет \"$1\" управление всеми счетами и активами $2. Перед тем как продолжить, убедитесь, что доверяете \"$1\".", "description": "The first parameter is the name of the snap and the second one is the protocol" }, - "snapRequestsPermission": { - "message": "Этот снап запрашивает следующие разрешения:" - }, "snapUpdate": { "message": "Обновить снап" }, - "snapUpdateExplanation": { - "message": "$1 нужна более новая версия вашей привязки.", - "description": "$1 is the dapp that is requesting an update to the snap." - }, "snaps": { "message": "Снапы" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index dd8593209d37..461e9f2913ff 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -3376,16 +3376,9 @@ "message": "Binibigyan mo ang $2 ng key access sa snap na \"$1\". Hindi na ito mababawi at nagbibigay ito sa \"$1\" ng kontrol sa iyong mga $2 account at asset. Tiyaking pinagkakatiwalaan mo ang \"$1\" bago magpatuloy.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, - "snapRequestsPermission": { - "message": "Hinihiling ng snap na ito ang mga sumusunod na pahintulot:" - }, "snapUpdate": { "message": "I-update ang snap" }, - "snapUpdateExplanation": { - "message": "Kailangan ng $1 ng bagong bersyon ng iyong snap.", - "description": "$1 is the dapp that is requesting an update to the snap." - }, "snaps": { "message": "Mga Snap" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index f08af92c7976..8472fce54671 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -3376,16 +3376,9 @@ "message": "\"$1\" için $2 anahtar erişimi veriyorsunuz. Bu iptal edilemez ve $2 hesaplarınıza ve varlıklarınıza \"$1\" kontrolü verir. İlerlemeden önce \"$1\" alanına güvendiğinizden emin olun.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, - "snapRequestsPermission": { - "message": "Bu ek, aşağıdaki izinleri istiyor:" - }, "snapUpdate": { "message": "Snap'i güncelle" }, - "snapUpdateExplanation": { - "message": "$1 için daha yeni bir snap sürümü gerekli.", - "description": "$1 is the dapp that is requesting an update to the snap." - }, "snaps": { "message": "Snap'ler" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index dd7b0f7957be..892ad694205d 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -3376,16 +3376,9 @@ "message": "Bạn đang cấp quyền truy cập khóa $2 cho Snap \"$1\". Hành động này không thể hủy bỏ và sẽ cấp quyền kiểm soát tài khoản và tài sản $2 của bạn cho \"$1\". Đảm bảo bạn tin tưởng \"$1\" trước khi tiếp tục.", "description": "The first parameter is the name of the snap and the second one is the protocol" }, - "snapRequestsPermission": { - "message": "Snap này đang yêu cầu các quyền sau:" - }, "snapUpdate": { "message": "Cập nhật Snap" }, - "snapUpdateExplanation": { - "message": "$1 cần một phiên bản Snap mới hơn.", - "description": "$1 is the dapp that is requesting an update to the snap." - }, "snaps": { "message": "Snap" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 981759685f2a..9fec36276c0d 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -3376,16 +3376,9 @@ "message": "您正在向snap \"$1\"授予$2的密钥访问权限。此操作不可撤销,并会向\"$1\"授予对您的$2账户和资产的控制权。在继续之前,请确保您信任\"$1\"。", "description": "The first parameter is the name of the snap and the second one is the protocol" }, - "snapRequestsPermission": { - "message": "此Snap正在请求以下权限:" - }, "snapUpdate": { "message": "更新Snap" }, - "snapUpdateExplanation": { - "message": "$1需要更新版本的snap。", - "description": "$1 is the dapp that is requesting an update to the snap." - }, "snaps": { "message": "Snap" }, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 61f52a635363..c4277080418f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -787,6 +787,7 @@ export default class MetamaskController extends EventEmitter { `${this.permissionController.name}:getSubjectNames`, `${this.permissionController.name}:updateCaveat`, `${this.approvalController.name}:addRequest`, + `${this.approvalController.name}:updateRequestState`, `${this.permissionController.name}:grantPermissions`, `${this.subjectMetadataController.name}:getSubjectMetadata`, 'ExecutionService:executeSnap', @@ -3979,7 +3980,6 @@ export default class MetamaskController extends EventEmitter { sendMetrics: this.metaMetricsController.trackEvent.bind( this.metaMetricsController, ), - // Permission-related getAccounts: this.getPermittedAccounts.bind(this, origin), getPermissionsForOrigin: this.permissionController.getPermissions.bind( diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 98e2ef7d2313..116df79f1192 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -1392,7 +1392,7 @@ "packages": { "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, - "@metamask/rpc-methods>@metamask/utils": true, + "@metamask/utils": true, "@metamask/utils>superstruct": true } }, @@ -1410,18 +1410,6 @@ "browserify>buffer": true } }, - "@metamask/rpc-methods>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>superstruct": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, "@metamask/rpc-methods>nanoid": { "globals": { "crypto.getRandomValues": true diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index a84edb962cd8..ac03c763178f 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -1542,10 +1542,10 @@ "@metamask/key-tree>@noble/hashes": true, "@metamask/rpc-methods>@metamask/browser-passworder": true, "@metamask/rpc-methods>@metamask/permission-controller": true, - "@metamask/rpc-methods>@metamask/utils": true, "@metamask/rpc-methods>nanoid": true, "@metamask/snaps-ui": true, "@metamask/snaps-utils": true, + "@metamask/utils": true, "@metamask/utils>superstruct": true, "eth-rpc-errors": true } @@ -1594,18 +1594,6 @@ "ethjs>ethjs-unit": true } }, - "@metamask/rpc-methods>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>superstruct": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, "@metamask/rpc-methods>nanoid": { "globals": { "crypto.getRandomValues": true @@ -1669,7 +1657,6 @@ "@metamask/snaps-controllers>@metamask/base-controller": true, "@metamask/snaps-controllers>@metamask/permission-controller": true, "@metamask/snaps-controllers>@metamask/subject-metadata-controller": true, - "@metamask/snaps-controllers>@metamask/utils": true, "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>concat-stream": true, "@metamask/snaps-controllers>gunzip-maybe": true, @@ -1677,6 +1664,7 @@ "@metamask/snaps-controllers>readable-web-to-node-stream": true, "@metamask/snaps-controllers>tar-stream": true, "@metamask/snaps-utils": true, + "@metamask/utils": true, "eth-rpc-errors": true, "json-rpc-engine": true, "json-rpc-middleware-stream": true, @@ -1718,18 +1706,6 @@ "@metamask/snaps-controllers>@metamask/base-controller": true } }, - "@metamask/snaps-controllers>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>superstruct": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, "@metamask/snaps-controllers>concat-stream": { "packages": { "@metamask/snaps-controllers>concat-stream>readable-stream": true, @@ -1878,22 +1854,10 @@ }, "@metamask/snaps-ui": { "packages": { - "@metamask/snaps-ui>@metamask/utils": true, + "@metamask/utils": true, "@metamask/utils>superstruct": true } }, - "@metamask/snaps-ui>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>superstruct": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, "@metamask/snaps-utils": { "globals": { "TextDecoder": true, @@ -1907,27 +1871,15 @@ "packages": { "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, - "@metamask/snaps-utils>@metamask/utils": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true, + "@metamask/utils": true, "@metamask/utils>superstruct": true, "semver": true } }, - "@metamask/snaps-utils>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>superstruct": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, "@metamask/snaps-utils>cron-parser": { "packages": { "browserify>browser-resolve": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index a84edb962cd8..ac03c763178f 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -1542,10 +1542,10 @@ "@metamask/key-tree>@noble/hashes": true, "@metamask/rpc-methods>@metamask/browser-passworder": true, "@metamask/rpc-methods>@metamask/permission-controller": true, - "@metamask/rpc-methods>@metamask/utils": true, "@metamask/rpc-methods>nanoid": true, "@metamask/snaps-ui": true, "@metamask/snaps-utils": true, + "@metamask/utils": true, "@metamask/utils>superstruct": true, "eth-rpc-errors": true } @@ -1594,18 +1594,6 @@ "ethjs>ethjs-unit": true } }, - "@metamask/rpc-methods>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>superstruct": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, "@metamask/rpc-methods>nanoid": { "globals": { "crypto.getRandomValues": true @@ -1669,7 +1657,6 @@ "@metamask/snaps-controllers>@metamask/base-controller": true, "@metamask/snaps-controllers>@metamask/permission-controller": true, "@metamask/snaps-controllers>@metamask/subject-metadata-controller": true, - "@metamask/snaps-controllers>@metamask/utils": true, "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>concat-stream": true, "@metamask/snaps-controllers>gunzip-maybe": true, @@ -1677,6 +1664,7 @@ "@metamask/snaps-controllers>readable-web-to-node-stream": true, "@metamask/snaps-controllers>tar-stream": true, "@metamask/snaps-utils": true, + "@metamask/utils": true, "eth-rpc-errors": true, "json-rpc-engine": true, "json-rpc-middleware-stream": true, @@ -1718,18 +1706,6 @@ "@metamask/snaps-controllers>@metamask/base-controller": true } }, - "@metamask/snaps-controllers>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>superstruct": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, "@metamask/snaps-controllers>concat-stream": { "packages": { "@metamask/snaps-controllers>concat-stream>readable-stream": true, @@ -1878,22 +1854,10 @@ }, "@metamask/snaps-ui": { "packages": { - "@metamask/snaps-ui>@metamask/utils": true, + "@metamask/utils": true, "@metamask/utils>superstruct": true } }, - "@metamask/snaps-ui>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>superstruct": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, "@metamask/snaps-utils": { "globals": { "TextDecoder": true, @@ -1907,27 +1871,15 @@ "packages": { "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, - "@metamask/snaps-utils>@metamask/utils": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true, + "@metamask/utils": true, "@metamask/utils>superstruct": true, "semver": true } }, - "@metamask/snaps-utils>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>superstruct": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, "@metamask/snaps-utils>cron-parser": { "packages": { "browserify>browser-resolve": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 98e2ef7d2313..116df79f1192 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -1392,7 +1392,7 @@ "packages": { "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, - "@metamask/rpc-methods>@metamask/utils": true, + "@metamask/utils": true, "@metamask/utils>superstruct": true } }, @@ -1410,18 +1410,6 @@ "browserify>buffer": true } }, - "@metamask/rpc-methods>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>superstruct": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, "@metamask/rpc-methods>nanoid": { "globals": { "crypto.getRandomValues": true diff --git a/package.json b/package.json index f8957699db85..cffda13841c6 100644 --- a/package.json +++ b/package.json @@ -237,7 +237,7 @@ "@metamask/etherscan-link": "^2.2.0", "@metamask/gas-fee-controller": "^1.0.0", "@metamask/jazzicon": "^2.0.0", - "@metamask/key-tree": "^6.2.1", + "@metamask/key-tree": "^7.0.0", "@metamask/logo": "^3.1.1", "@metamask/metamask-eth-abis": "^3.0.0", "@metamask/notification-controller": "^1.0.0", @@ -247,13 +247,13 @@ "@metamask/post-message-stream": "^6.0.0", "@metamask/providers": "^10.2.1", "@metamask/rate-limit-controller": "^1.0.0", - "@metamask/rpc-methods": "^0.30.0", + "@metamask/rpc-methods": "^0.31.0", "@metamask/scure-bip39": "^2.0.3", "@metamask/slip44": "^2.1.0", "@metamask/smart-transactions-controller": "^3.1.0", - "@metamask/snaps-controllers": "^0.30.0", - "@metamask/snaps-ui": "^0.30.0", - "@metamask/snaps-utils": "^0.30.0", + "@metamask/snaps-controllers": "^0.31.0", + "@metamask/snaps-ui": "^0.31.0", + "@metamask/snaps-utils": "^0.31.0", "@metamask/subject-metadata-controller": "^1.0.0", "@metamask/utils": "^5.0.0", "@ngraveio/bc-ur": "^1.1.6", diff --git a/test/e2e/snaps/enums.js b/test/e2e/snaps/enums.js index c5992424c6d0..c9dccd19861e 100644 --- a/test/e2e/snaps/enums.js +++ b/test/e2e/snaps/enums.js @@ -1,3 +1,3 @@ module.exports = { - TEST_SNAPS_WEBSITE_URL: 'https://metamask.github.io/test-snaps/5.0.3/', + TEST_SNAPS_WEBSITE_URL: 'https://metamask.github.io/test-snaps/5.1.0/', }; diff --git a/test/e2e/snaps/test-snap-bip-32.spec.js b/test/e2e/snaps/test-snap-bip-32.spec.js index e69cdce346f8..2a5174498b1d 100644 --- a/test/e2e/snaps/test-snap-bip-32.spec.js +++ b/test/e2e/snaps/test-snap-bip-32.spec.js @@ -53,16 +53,8 @@ describe('Test Snap bip-32', function () { tag: 'button', }); - await driver.delay(2000); + await driver.waitForSelector({ text: 'Approve & install' }); - // switch to metamask extension - windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000); - - // approve install of snap - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); await driver.clickElement({ text: 'Approve & install', tag: 'button', @@ -77,6 +69,13 @@ describe('Test Snap bip-32', function () { tag: 'button', }); + await driver.waitForSelector({ text: 'Ok' }); + + await driver.clickElement({ + text: 'Ok', + tag: 'button', + }); + // switch back to test-snaps window await driver.switchToWindowWithTitle('Test Snaps', windowHandles); diff --git a/test/e2e/snaps/test-snap-bip-44.spec.js b/test/e2e/snaps/test-snap-bip-44.spec.js index ad12ac0acc90..126ad5e5b5b8 100644 --- a/test/e2e/snaps/test-snap-bip-44.spec.js +++ b/test/e2e/snaps/test-snap-bip-44.spec.js @@ -52,16 +52,8 @@ describe('Test Snap bip-44', function () { tag: 'button', }); - await driver.delay(2000); + await driver.waitForSelector({ text: 'Approve & install' }); - // switch to metamask extension - windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000); - - // approve install of snap - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); await driver.clickElement({ text: 'Approve & install', tag: 'button', @@ -76,6 +68,13 @@ describe('Test Snap bip-44', function () { tag: 'button', }); + await driver.waitForSelector({ text: 'Ok' }); + + await driver.clickElement({ + text: 'Ok', + tag: 'button', + }); + // click send inputs on test snap page await driver.switchToWindowWithTitle('Test Snaps', windowHandles); diff --git a/test/e2e/snaps/test-snap-cronjob.spec.js b/test/e2e/snaps/test-snap-cronjob.spec.js index d94d692baa27..78a15490a347 100644 --- a/test/e2e/snaps/test-snap-cronjob.spec.js +++ b/test/e2e/snaps/test-snap-cronjob.spec.js @@ -52,19 +52,20 @@ describe('Test Snap Cronjob', function () { tag: 'button', }); - await driver.delay(2000); + await driver.waitForSelector({ text: 'Approve & install' }); - // approve install of snap - windowHandles = await driver.waitUntilXWindowHandles(3, 1000, 10000); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); await driver.clickElement({ text: 'Approve & install', tag: 'button', }); + await driver.waitForSelector({ text: 'Ok' }); + + await driver.clickElement({ + text: 'Ok', + tag: 'button', + }); + // click send inputs on test snap page await driver.switchToWindowWithTitle('Test Snaps', windowHandles); diff --git a/test/e2e/snaps/test-snap-dialog.spec.js b/test/e2e/snaps/test-snap-dialog.spec.js index 4ca53ae223d0..031d14271e0b 100644 --- a/test/e2e/snaps/test-snap-dialog.spec.js +++ b/test/e2e/snaps/test-snap-dialog.spec.js @@ -52,19 +52,20 @@ describe('Test Snap Dialog', function () { tag: 'button', }); - await driver.delay(2000); + await driver.waitForSelector({ text: 'Approve & install' }); - // approve install of snap - windowHandles = await driver.waitUntilXWindowHandles(3, 1000, 10000); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); await driver.clickElement({ text: 'Approve & install', tag: 'button', }); + await driver.waitForSelector({ text: 'Ok' }); + + await driver.clickElement({ + text: 'Ok', + tag: 'button', + }); + // switch to test snaps tab await driver.switchToWindowWithTitle('Test Snaps', windowHandles); diff --git a/test/e2e/snaps/test-snap-error.spec.js b/test/e2e/snaps/test-snap-error.spec.js index 2e72a9b92efa..a57f2241eaee 100644 --- a/test/e2e/snaps/test-snap-error.spec.js +++ b/test/e2e/snaps/test-snap-error.spec.js @@ -38,7 +38,7 @@ describe('Test Snap Error', function () { await driver.delay(1000); // switch to metamask extension and click connect - let windowHandles = await driver.waitUntilXWindowHandles( + const windowHandles = await driver.waitUntilXWindowHandles( 3, 1000, 10000, @@ -53,19 +53,20 @@ describe('Test Snap Error', function () { tag: 'button', }); - await driver.delay(2000); + await driver.waitForSelector({ text: 'Approve & install' }); - // approve install of snap - windowHandles = await driver.waitUntilXWindowHandles(3, 1000, 10000); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); await driver.clickElement({ text: 'Approve & install', tag: 'button', }); + await driver.waitForSelector({ text: 'Ok' }); + + await driver.clickElement({ + text: 'Ok', + tag: 'button', + }); + // click send inputs on test snap page await driver.switchToWindowWithTitle('Test Snaps', windowHandles); diff --git a/test/e2e/snaps/test-snap-installed.spec.js b/test/e2e/snaps/test-snap-installed.spec.js index 2645c9b00415..c5adf1637888 100644 --- a/test/e2e/snaps/test-snap-installed.spec.js +++ b/test/e2e/snaps/test-snap-installed.spec.js @@ -52,19 +52,20 @@ describe('Test Snap Installed', function () { tag: 'button', }); - await driver.delay(2000); + await driver.waitForSelector({ text: 'Approve & install' }); - // approve install of snap - windowHandles = await driver.waitUntilXWindowHandles(3, 1000, 10000); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); await driver.clickElement({ text: 'Approve & install', tag: 'button', }); + await driver.waitForSelector({ text: 'Ok' }); + + await driver.clickElement({ + text: 'Ok', + tag: 'button', + }); + // click send inputs on test snap page await driver.switchToWindowWithTitle('Test Snaps', windowHandles); @@ -90,20 +91,20 @@ describe('Test Snap Installed', function () { tag: 'button', }); - await driver.delay(2000); + await driver.waitForSelector({ text: 'Approve & install' }); - // approve install of snap - windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); await driver.clickElement({ text: 'Approve & install', tag: 'button', }); - windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000); + await driver.waitForSelector({ text: 'Ok' }); + + await driver.clickElement({ + text: 'Ok', + tag: 'button', + }); + await driver.switchToWindowWithTitle('Test Snaps', windowHandles); // wait for npm installation success diff --git a/test/e2e/snaps/test-snap-management.spec.js b/test/e2e/snaps/test-snap-management.spec.js index f66068b531d2..b8eab6b0b390 100644 --- a/test/e2e/snaps/test-snap-management.spec.js +++ b/test/e2e/snaps/test-snap-management.spec.js @@ -54,21 +54,19 @@ describe('Test Snap Management', function () { tag: 'button', }); - await driver.delay(1000); + await driver.waitForSelector({ text: 'Approve & install' }); - // approve install of snap - windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); await driver.clickElement({ text: 'Approve & install', tag: 'button', }); - // delay for npm installation - await driver.delay(2000); + await driver.waitForSelector({ text: 'Ok' }); + + await driver.clickElement({ + text: 'Ok', + tag: 'button', + }); // switch to the original MM tab const extensionPage = windowHandles[0]; diff --git a/test/e2e/snaps/test-snap-managestate.spec.js b/test/e2e/snaps/test-snap-managestate.spec.js index bbf2d5bcf323..7e209d01a06f 100644 --- a/test/e2e/snaps/test-snap-managestate.spec.js +++ b/test/e2e/snaps/test-snap-managestate.spec.js @@ -41,7 +41,7 @@ describe('Test Snap manageState', function () { await driver.delay(1000); // switch to metamask extension and click connect - let windowHandles = await driver.waitUntilXWindowHandles( + const windowHandles = await driver.waitUntilXWindowHandles( 2, 1000, 10000, @@ -54,19 +54,21 @@ describe('Test Snap manageState', function () { text: 'Connect', tag: 'button', }); - await driver.delay(2000); - // approve install of snap - windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await driver.waitForSelector({ text: 'Approve & install' }); + await driver.clickElement({ text: 'Approve & install', tag: 'button', }); + await driver.waitForSelector({ text: 'Ok' }); + + await driver.clickElement({ + text: 'Ok', + tag: 'button', + }); + // fill and click send inputs on test snap page await driver.switchToWindowWithTitle('Test Snaps', windowHandles); diff --git a/test/e2e/snaps/test-snap-notification.spec.js b/test/e2e/snaps/test-snap-notification.spec.js index bf4ad409a63f..26809c18d8fb 100644 --- a/test/e2e/snaps/test-snap-notification.spec.js +++ b/test/e2e/snaps/test-snap-notification.spec.js @@ -40,7 +40,7 @@ describe('Test Snap Notification', function () { await driver.delay(1000); // switch to metamask extension and click connect - let windowHandles = await driver.waitUntilXWindowHandles( + const windowHandles = await driver.waitUntilXWindowHandles( 3, 1000, 10000, @@ -54,19 +54,21 @@ describe('Test Snap Notification', function () { text: 'Connect', tag: 'button', }); - await driver.delay(2000); - // approve install of snap - windowHandles = await driver.waitUntilXWindowHandles(3, 1000, 10000); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await driver.waitForSelector({ text: 'Approve & install' }); + await driver.clickElement({ text: 'Approve & install', tag: 'button', }); + await driver.waitForSelector({ text: 'Ok' }); + + await driver.clickElement({ + text: 'Ok', + tag: 'button', + }); + // click send inputs on test snap page await driver.switchToWindowWithTitle('Test Snaps', windowHandles); diff --git a/test/e2e/snaps/test-snap-rpc.spec.js b/test/e2e/snaps/test-snap-rpc.spec.js index 3f1716c5be20..0f512c6fedc1 100644 --- a/test/e2e/snaps/test-snap-rpc.spec.js +++ b/test/e2e/snaps/test-snap-rpc.spec.js @@ -28,13 +28,15 @@ describe('Test Snap RPC', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - // navigate to test snaps page and connect + // navigate to test snaps page await driver.driver.get(TEST_SNAPS_WEBSITE_URL); await driver.delay(1000); - const snapButton1 = await driver.findElement('#connectRpcSnap'); + + // find and scroll to the bip32 test and connect + const snapButton1 = await driver.findElement('#connectBip32'); await driver.scrollToElement(snapButton1); await driver.delay(1000); - await driver.clickElement('#connectRpcSnap'); + await driver.clickElement('#connectBip32'); await driver.delay(1000); // switch to metamask extension and click connect @@ -52,79 +54,79 @@ describe('Test Snap RPC', function () { tag: 'button', }); - await driver.delay(2000); + await driver.waitForSelector({ text: 'Approve & install' }); - // approve install of snap - windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); await driver.clickElement({ text: 'Approve & install', tag: 'button', }); - // switch back to test snaps page - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + // wait for permissions popover, click checkboxes and confirm + await driver.delay(1000); + await driver.clickElement('#key-access-bip32-m-44h-0h-secp256k1-0'); + await driver.clickElement('#key-access-bip32-m-44h-0h-ed25519-0'); + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); - // wait for npm installation success - await driver.waitForSelector({ - css: '#connectRpcSnap', - text: 'Reconnect to RPC Snap', + await driver.waitForSelector({ text: 'Ok' }); + + await driver.clickElement({ + text: 'Ok', + tag: 'button', }); - // click send inputs on test snap page - const snapButton2 = await driver.findElement('#sendRpc'); + // switch back to test-snaps window + await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + + const snapButton2 = await driver.findElement('#connectRpcSnap'); await driver.scrollToElement(snapButton2); await driver.delay(1000); - await driver.clickElement('#sendRpc'); + await driver.clickElement('#connectRpcSnap'); + await driver.delay(1000); - // approve and install part one windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000); await driver.switchToWindowWithTitle( 'MetaMask Notification', windowHandles, ); await driver.clickElement({ - text: 'Approve & install', + text: 'Connect', tag: 'button', }); - // wait for window to close - await driver.delay(2000); + await driver.waitForSelector({ text: 'Approve & install' }); - // approve and install part two - windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); await driver.clickElement({ text: 'Approve & install', tag: 'button', }); - // wait for permissions popover - await driver.waitForSelector({ - text: 'Confirm', - tag: 'button', - }); + await driver.waitForSelector({ text: 'Ok' }); - // click checkboxes and confirm - await driver.clickElement('#key-access-bip32-m-44h-0h-secp256k1-0'); - await driver.clickElement('#key-access-bip32-m-44h-0h-ed25519-0'); await driver.clickElement({ - text: 'Confirm', + text: 'Ok', tag: 'button', }); + await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + + // wait for npm installation success + await driver.waitForSelector({ + css: '#connectRpcSnap', + text: 'Reconnect to RPC Snap', + }); + + // click send inputs on test snap page + const snapButton3 = await driver.findElement('#sendRpc'); + await driver.scrollToElement(snapButton3); + await driver.delay(1000); + await driver.clickElement('#sendRpc'); + // delay for result creation await driver.delay(2500); - // check the results of the custom confirm - windowHandles = await driver.waitUntilXWindowHandles(1, 1000, 10000); - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); const confirmResult = await driver.findElement('#rpcResult'); assert.equal( await confirmResult.getText(), diff --git a/test/e2e/snaps/test-snap-txinsights.spec.js b/test/e2e/snaps/test-snap-txinsights.spec.js index 4003501a638d..b8fbe0063fcf 100644 --- a/test/e2e/snaps/test-snap-txinsights.spec.js +++ b/test/e2e/snaps/test-snap-txinsights.spec.js @@ -53,26 +53,21 @@ describe('Test Snap TxInsights', function () { tag: 'button', }); - // delay for npm installation - await driver.delay(2000); - - // switch to metamask extension - windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000); + await driver.waitForSelector({ text: 'Approve & install' }); - // approve install of snap - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); await driver.clickElement({ text: 'Approve & install', tag: 'button', }); - await driver.delay(1000); + await driver.waitForSelector({ text: 'Ok' }); + + await driver.clickElement({ + text: 'Ok', + tag: 'button', + }); // switch to test-snaps page and get accounts - windowHandles = await driver.waitUntilXWindowHandles(1, 1000, 10000); await driver.switchToWindowWithTitle('Test Snaps', windowHandles); await driver.clickElement('#getAccounts'); await driver.delay(1000); diff --git a/test/e2e/snaps/test-snap-update.spec.js b/test/e2e/snaps/test-snap-update.spec.js index 472b5a1def39..523356bac7a5 100644 --- a/test/e2e/snaps/test-snap-update.spec.js +++ b/test/e2e/snaps/test-snap-update.spec.js @@ -53,14 +53,9 @@ describe('Test Snap update', function () { text: 'Connect', tag: 'button', }); - await driver.delay(2000); - // approve install of snap - windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await driver.waitForSelector({ text: 'Approve & install' }); + await driver.clickElement({ text: 'Approve & install', tag: 'button', @@ -75,6 +70,13 @@ describe('Test Snap update', function () { tag: 'button', }); + await driver.waitForSelector({ text: 'Ok' }); + + await driver.clickElement({ + text: 'Ok', + tag: 'button', + }); + // navigate to test snap page await driver.switchToWindowWithTitle('Test Snaps', windowHandles); @@ -101,16 +103,22 @@ describe('Test Snap update', function () { 'MetaMask Notification', windowHandles, ); + + await driver.waitForSelector({ text: 'Approve & update' }); + await driver.clickElement({ text: 'Approve & update', tag: 'button', }); - // delay for npm installation - await driver.delay(2000); + await driver.waitForSelector({ text: 'Ok' }); + + await driver.clickElement({ + text: 'Ok', + tag: 'button', + }); // navigate to test snap page - windowHandles = await driver.waitUntilXWindowHandles(1, 1000, 10000); await driver.switchToWindowWithTitle('Test Snaps', windowHandles); // look for the correct version text diff --git a/ui/components/app/flask/install-error/index.js b/ui/components/app/flask/install-error/index.js new file mode 100644 index 000000000000..b6c937ba20ff --- /dev/null +++ b/ui/components/app/flask/install-error/index.js @@ -0,0 +1 @@ +export { default } from './install-error'; diff --git a/ui/components/app/flask/install-error/install-error.js b/ui/components/app/flask/install-error/install-error.js new file mode 100644 index 000000000000..39ecddf65442 --- /dev/null +++ b/ui/components/app/flask/install-error/install-error.js @@ -0,0 +1,39 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Box from '../../../ui/box/box'; +import { + AlignItems, + BLOCK_SIZES, + FLEX_DIRECTION, + FONT_WEIGHT, + JustifyContent, + TextVariant, +} from '../../../../helpers/constants/design-system'; +import ActionableMessage from '../../../ui/actionable-message/actionable-message'; +import { Text } from '../../../component-library'; + +const InstallError = ({ title, error }) => { + return ( + + + {title} + + + + + + ); +}; + +InstallError.propTypes = { + title: PropTypes.node.isRequired, + error: PropTypes.string.isRequired, +}; + +export default InstallError; diff --git a/ui/components/app/flask/snap-install-warning/snap-install-warning.js b/ui/components/app/flask/snap-install-warning/snap-install-warning.js index a5a7935c795b..70cedf0d2245 100644 --- a/ui/components/app/flask/snap-install-warning/snap-install-warning.js +++ b/ui/components/app/flask/snap-install-warning/snap-install-warning.js @@ -4,10 +4,14 @@ import { produce } from 'immer'; import classnames from 'classnames'; import { useI18nContext } from '../../../../hooks/useI18nContext'; import CheckBox from '../../../ui/check-box/check-box.component'; -import Typography from '../../../ui/typography/typography'; -import { TypographyVariant } from '../../../../helpers/constants/design-system'; + +import { + TextVariant, + TEXT_ALIGN, +} from '../../../../helpers/constants/design-system'; import Popover from '../../../ui/popover'; import Button from '../../../ui/button'; +import { Text } from '../../../component-library'; /** * a very simple reducer using produce from Immer to keep checkboxes state manipulation @@ -63,20 +67,23 @@ export default function SnapInstallWarning({ onCancel, onSubmit, warnings }) { return ( } headerProps={{ padding: [6, 6, 0] }} - contentProps={{ padding: [6, 4] }} + contentProps={{ + paddingLeft: [6, 4], + paddingRight: [6, 4], + paddingTop: 0, + paddingBottom: [6, 4], + }} footerProps={{ padding: [4, 6] }} > - + {warnings.length > 1 ? t('snapInstallWarningCheckPlural') : t('snapInstallWarningCheck')} - + {warnings.map((warning, i) => (
onCheckboxClicked(warning.id)} /> - +
))}
diff --git a/ui/components/app/flask/snaps-authorship-pill/snaps-authorship-pill.js b/ui/components/app/flask/snaps-authorship-pill/snaps-authorship-pill.js index 6a845f314e36..826f9c3ff879 100644 --- a/ui/components/app/flask/snaps-authorship-pill/snaps-authorship-pill.js +++ b/ui/components/app/flask/snaps-authorship-pill/snaps-authorship-pill.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; +import { getSnapPrefix } from '@metamask/snaps-utils'; import Chip from '../../../ui/chip'; import Box from '../../../ui/box'; import Typography from '../../../ui/typography'; @@ -11,20 +12,18 @@ import { TextColor, } from '../../../../helpers/constants/design-system'; import { useI18nContext } from '../../../../hooks/useI18nContext'; -import { SNAPS_METADATA } from '../../../../../shared/constants/snaps'; - -const snapIdPrefixes = ['npm:', 'local:']; +import { + getSnapName, + removeSnapIdPrefix, +} from '../../../../helpers/utils/util'; const SnapsAuthorshipPill = ({ snapId, version, className }) => { - // @todo Use getSnapPrefix from snaps-monorepo when possible // 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 snapPrefix = snapId && getSnapPrefix(snapId); + const packageName = snapId && removeSnapIdPrefix(snapId); const isNPM = snapPrefix === 'npm:'; const url = isNPM ? `https://www.npmjs.com/package/${packageName}` @@ -32,7 +31,7 @@ const SnapsAuthorshipPill = ({ snapId, version, className }) => { const icon = isNPM ? 'fab fa-npm fa-lg' : 'fas fa-code'; const t = useI18nContext(); - const friendlyName = SNAPS_METADATA[snapId]?.name ?? packageName; + const friendlyName = getSnapName(snapId); return ( { - const friendlyName = SNAPS_METADATA[snapId]?.name; + const friendlyName = getSnapName(snapId); if (friendlyName) { return { ...baseDescription, diff --git a/ui/helpers/utils/util.js b/ui/helpers/utils/util.js index 27a38a649516..52e306da5d5c 100644 --- a/ui/helpers/utils/util.js +++ b/ui/helpers/utils/util.js @@ -7,6 +7,9 @@ import { getFormattedIpfsUrl } from '@metamask/assets-controllers'; import slip44 from '@metamask/slip44'; import * as lodash from 'lodash'; import bowser from 'bowser'; +///: BEGIN:ONLY_INCLUDE_IN(flask) +import { getSnapPrefix } from '@metamask/snaps-utils'; +///: END:ONLY_INCLUDE_IN import { CHAIN_IDS } from '../../../shared/constants/network'; import { toChecksumHexAddress, @@ -20,7 +23,10 @@ import { import { Numeric } from '../../../shared/modules/Numeric'; import { OUTDATED_BROWSER_VERSIONS } from '../constants/common'; ///: BEGIN:ONLY_INCLUDE_IN(flask) -import { SNAPS_DERIVATION_PATHS } from '../../../shared/constants/snaps'; +import { + SNAPS_DERIVATION_PATHS, + SNAPS_METADATA, +} from '../../../shared/constants/snaps'; ///: END:ONLY_INCLUDE_IN // formatData :: ( date: ) -> String @@ -547,6 +553,12 @@ export function getSnapDerivationPathName(path, curve) { return pathMetadata?.name ?? null; } + +export const removeSnapIdPrefix = (snapId) => + snapId.replace(getSnapPrefix(snapId), ''); + +export const getSnapName = (snapId) => + SNAPS_METADATA[snapId]?.name ?? removeSnapIdPrefix(snapId); ///: END:ONLY_INCLUDE_IN /** diff --git a/ui/pages/permissions-connect/flask/snap-install/index.scss b/ui/pages/permissions-connect/flask/snap-install/index.scss index 51f1342a7592..dc725ce10afc 100644 --- a/ui/pages/permissions-connect/flask/snap-install/index.scss +++ b/ui/pages/permissions-connect/flask/snap-install/index.scss @@ -1,14 +1,27 @@ .snap-install { box-shadow: none; - .permissions-connect-permission-list { - padding: 0 24px; + .headers { + flex: 1; - .permission { - padding: 8px 0; + .permissions-connect-permission-list { + padding: 0 24px; + + .permission { + padding: 8px 0; + } + } + + .loader-container { + height: 100%; + } + + &__permission-description { + border-bottom: 1px solid var(--color-border-default); } } + .page-container__footer { width: 100%; margin-top: 12px; diff --git a/ui/pages/permissions-connect/flask/snap-install/snap-install.js b/ui/pages/permissions-connect/flask/snap-install/snap-install.js index 0cd060ae86c4..f8ee519496ef 100644 --- a/ui/pages/permissions-connect/flask/snap-install/snap-install.js +++ b/ui/pages/permissions-connect/flask/snap-install/snap-install.js @@ -3,7 +3,6 @@ import React, { useCallback, useState } from 'react'; import { PageContainerFooter } from '../../../../components/ui/page-container'; import PermissionsConnectPermissionList from '../../../../components/app/permissions-connect-permission-list'; 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'; @@ -13,13 +12,20 @@ import { BorderStyle, FLEX_DIRECTION, JustifyContent, - TypographyVariant, + TextVariant, + TEXT_ALIGN, } from '../../../../helpers/constants/design-system'; -import Typography from '../../../../components/ui/typography'; import { getSnapInstallWarnings } from '../util'; +import PulseLoader from '../../../../components/ui/pulse-loader/pulse-loader'; +import InstallError from '../../../../components/app/flask/install-error/install-error'; +import SnapsAuthorshipPill from '../../../../components/app/flask/snaps-authorship-pill/snaps-authorship-pill'; +import { Text } from '../../../../components/component-library'; +import { useOriginMetadata } from '../../../../hooks/useOriginMetadata'; +import { getSnapName } from '../../../../helpers/utils/util'; export default function SnapInstall({ request, + requestState, approveSnapInstall, rejectSnapInstall, targetSubjectMetadata, @@ -27,6 +33,7 @@ export default function SnapInstall({ const t = useI18nContext(); const [isShowingWarning, setIsShowingWarning] = useState(false); + const originMetadata = useOriginMetadata(request.metadata?.dappOrigin) || {}; const onCancel = useCallback( () => rejectSnapInstall(request.metadata.id), @@ -38,17 +45,37 @@ export default function SnapInstall({ [request, approveSnapInstall], ); + const hasError = !requestState.loading && requestState.error; + + const isLoading = requestState.loading; + const hasPermissions = - request?.permissions && Object.keys(request.permissions).length > 0; + !hasError && + requestState?.permissions && + Object.keys(requestState.permissions).length > 0; + + const isEmpty = !isLoading && !hasError && !hasPermissions; const warnings = getSnapInstallWarnings( - request?.permissions ?? {}, + requestState?.permissions ?? {}, targetSubjectMetadata, t, ); const shouldShowWarning = warnings.length > 0; + const snapName = getSnapName(targetSubjectMetadata.origin); + + const handleSubmit = () => { + if (!hasError && shouldShowWarning) { + setIsShowingWarning(true); + } else if (hasError) { + onCancel(); + } else { + onSubmit(); + } + }; + return ( - + {!hasError && ( + + {t('snapInstall')} + + )} + {isLoading && ( + + + + )} + {hasError && ( + + )} {hasPermissions && ( <> - - {t('snapRequestsPermission')} - + {t('snapInstallRequestsPermission', [ + {originMetadata?.hostname}, + {snapName}, + ])} + )} + {isEmpty && ( + + + {t('snapInstallRequest', [ + {originMetadata?.hostname}, + {snapName}, + ])} + + + )} setIsShowingWarning(true) : onSubmit - } - submitText={t(hasPermissions ? 'approveAndInstall' : 'install')} + onSubmit={handleSubmit} + submitText={t( + // eslint-disable-next-line no-nested-ternary + hasError ? 'ok' : hasPermissions ? 'approveAndInstall' : 'install', + )} /> {isShowingWarning && ( @@ -120,6 +184,7 @@ export default function SnapInstall({ SnapInstall.propTypes = { request: PropTypes.object.isRequired, + requestState: PropTypes.object.isRequired, approveSnapInstall: PropTypes.func.isRequired, rejectSnapInstall: PropTypes.func.isRequired, targetSubjectMetadata: PropTypes.shape({ diff --git a/ui/pages/permissions-connect/flask/snap-result/index.js b/ui/pages/permissions-connect/flask/snap-result/index.js new file mode 100644 index 000000000000..4da011e0b12c --- /dev/null +++ b/ui/pages/permissions-connect/flask/snap-result/index.js @@ -0,0 +1 @@ +export { default } from './snap-result'; diff --git a/ui/pages/permissions-connect/flask/snap-result/index.scss b/ui/pages/permissions-connect/flask/snap-result/index.scss new file mode 100644 index 000000000000..2d130d4fe0d6 --- /dev/null +++ b/ui/pages/permissions-connect/flask/snap-result/index.scss @@ -0,0 +1,16 @@ +.snap-result { + box-shadow: none; + + .headers { + flex: 1; + + .loader-container { + height: 100%; + } + } + + .page-container__footer { + width: 100%; + margin-top: 12px; + } +} diff --git a/ui/pages/permissions-connect/flask/snap-result/snap-result.js b/ui/pages/permissions-connect/flask/snap-result/snap-result.js new file mode 100644 index 000000000000..68adb7993cd7 --- /dev/null +++ b/ui/pages/permissions-connect/flask/snap-result/snap-result.js @@ -0,0 +1,128 @@ +import PropTypes from 'prop-types'; +import React, { useCallback } from 'react'; +import { PageContainerFooter } from '../../../../components/ui/page-container'; + +import PermissionsConnectFooter from '../../../../components/app/permissions-connect-footer'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; + +import Box from '../../../../components/ui/box/box'; +import { + AlignItems, + BLOCK_SIZES, + BorderStyle, + FLEX_DIRECTION, + FONT_WEIGHT, + JustifyContent, + TextVariant, + TEXT_ALIGN, +} from '../../../../helpers/constants/design-system'; +import { Text } from '../../../../components/component-library'; +import PulseLoader from '../../../../components/ui/pulse-loader/pulse-loader'; +import InstallError from '../../../../components/app/flask/install-error/install-error'; +import SnapsAuthorshipPill from '../../../../components/app/flask/snaps-authorship-pill/snaps-authorship-pill'; +import { getSnapName } from '../../../../helpers/utils/util'; + +export default function SnapResult({ + request, + requestState, + approveSnapResult, + targetSubjectMetadata, +}) { + const t = useI18nContext(); + + const onSubmit = useCallback( + () => approveSnapResult(request.metadata.id), + [request, approveSnapResult], + ); + + const hasError = !requestState.loading && requestState.error; + + const isLoading = requestState.loading; + + const snapName = getSnapName(targetSubjectMetadata.origin); + + return ( + + + + {isLoading && ( + + + + )} + {!isLoading && !hasError && ( + + + {t('snapResultSuccess')} + + + {t('snapResultSuccessDescription', [{snapName}])} + + + )} + {hasError && ( + + )} + + + + + + + + + ); +} + +SnapResult.propTypes = { + request: PropTypes.object.isRequired, + requestState: PropTypes.object.isRequired, + approveSnapResult: PropTypes.func.isRequired, + targetSubjectMetadata: PropTypes.shape({ + iconUrl: PropTypes.string, + name: PropTypes.string, + origin: PropTypes.string.isRequired, + sourceCode: PropTypes.string, + version: PropTypes.string, + }).isRequired, +}; diff --git a/ui/pages/permissions-connect/flask/snap-update/index.scss b/ui/pages/permissions-connect/flask/snap-update/index.scss index bc6f8c51a6e0..31e81420cb4b 100644 --- a/ui/pages/permissions-connect/flask/snap-update/index.scss +++ b/ui/pages/permissions-connect/flask/snap-update/index.scss @@ -1,13 +1,25 @@ .snap-update { box-shadow: none; - .update-snap-permission-list { - padding: 0 24px; + .headers { + flex: 1; - .new-permission, - .approved-permission, - .revoked-permission { - padding: 8px 0; + .loader-container { + height: 100%; + } + + .update-snap-permission-list { + padding: 0 24px; + + .new-permission, + .approved-permission, + .revoked-permission { + padding: 8px 0; + } + } + + &__permission-description { + border-bottom: 1px solid var(--color-border-default); } } diff --git a/ui/pages/permissions-connect/flask/snap-update/snap-update.js b/ui/pages/permissions-connect/flask/snap-update/snap-update.js index 0a030987c983..87b56f262184 100644 --- a/ui/pages/permissions-connect/flask/snap-update/snap-update.js +++ b/ui/pages/permissions-connect/flask/snap-update/snap-update.js @@ -2,7 +2,6 @@ import PropTypes from 'prop-types'; import React, { useCallback, 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'; @@ -12,14 +11,22 @@ import { BorderStyle, FLEX_DIRECTION, JustifyContent, - TypographyVariant, + TextVariant, + TEXT_ALIGN, } from '../../../../helpers/constants/design-system'; -import Typography from '../../../../components/ui/typography'; + import UpdateSnapPermissionList from '../../../../components/app/flask/update-snap-permission-list'; import { getSnapInstallWarnings } from '../util'; +import PulseLoader from '../../../../components/ui/pulse-loader/pulse-loader'; +import InstallError from '../../../../components/app/flask/install-error/install-error'; +import SnapsAuthorshipPill from '../../../../components/app/flask/snaps-authorship-pill/snaps-authorship-pill'; +import { Text } from '../../../../components/component-library'; +import { useOriginMetadata } from '../../../../hooks/useOriginMetadata'; +import { getSnapName } from '../../../../helpers/utils/util'; export default function SnapUpdate({ request, + requestState, approveSnapUpdate, rejectSnapUpdate, targetSubjectMetadata, @@ -27,6 +34,7 @@ export default function SnapUpdate({ const t = useI18nContext(); const [isShowingWarning, setIsShowingWarning] = useState(false); + const originMetadata = useOriginMetadata(request.metadata?.dappOrigin) || {}; const onCancel = useCallback( () => rejectSnapUpdate(request.metadata.id), @@ -38,14 +46,21 @@ export default function SnapUpdate({ [request, approveSnapUpdate], ); - const approvedPermissions = request.approvedPermissions ?? {}; - const revokedPermissions = request.unusedPermissions ?? {}; - const newPermissions = request.newPermissions ?? {}; + const approvedPermissions = requestState.approvedPermissions ?? {}; + const revokedPermissions = requestState.unusedPermissions ?? {}; + const newPermissions = requestState.newPermissions ?? {}; + + const isLoading = requestState.loading; + const hasError = !isLoading && requestState.error; + const hasPermissions = + !hasError && Object.keys(approvedPermissions).length + Object.keys(revokedPermissions).length + Object.keys(newPermissions).length > - 0; + 0; + + const isEmpty = !isLoading && !hasError && !hasPermissions; const warnings = getSnapInstallWarnings( newPermissions, @@ -55,6 +70,18 @@ export default function SnapUpdate({ const shouldShowWarning = warnings.length > 0; + const snapName = getSnapName(targetSubjectMetadata.origin); + + const handleSubmit = () => { + if (!hasError && shouldShowWarning) { + setIsShowingWarning(true); + } else if (hasError) { + onCancel(); + } else { + onSubmit(); + } + }; + return ( - - - {t('snapUpdateExplanation', [`${request.metadata.dappOrigin}`])} - + {!hasError && ( + + {t('snapUpdate')} + + )} + {isLoading && ( + + + + )} + {hasError && ( + + )} {hasPermissions && ( <> - - {t('snapRequestsPermission')} - + {t('snapUpdateRequestsPermission', [ + {originMetadata?.hostname}, + {snapName}, + ])} + )} + {isEmpty && ( + + + {t('snapUpdateRequest', [ + {originMetadata?.hostname}, + {snapName}, + ])} + + + )} setIsShowingWarning(true) : onSubmit - } - submitText={t('approveAndUpdate')} + onSubmit={handleSubmit} + submitText={t( + // eslint-disable-next-line no-nested-ternary + hasError ? 'ok' : hasPermissions ? 'approveAndUpdate' : 'update', + )} /> {isShowingWarning && ( @@ -138,6 +193,7 @@ export default function SnapUpdate({ SnapUpdate.propTypes = { request: PropTypes.object.isRequired, + requestState: PropTypes.object.isRequired, approveSnapUpdate: PropTypes.func.isRequired, rejectSnapUpdate: PropTypes.func.isRequired, targetSubjectMetadata: PropTypes.shape({ diff --git a/ui/pages/permissions-connect/flask/util.js b/ui/pages/permissions-connect/flask/util.js index ddea93342fc0..8c3ad93644ba 100644 --- a/ui/pages/permissions-connect/flask/util.js +++ b/ui/pages/permissions-connect/flask/util.js @@ -1,5 +1,16 @@ -import { flatMap } from '@metamask/snaps-utils'; -import { coinTypeToProtocolName } from '../../../helpers/utils/util'; +import React from 'react'; +import { Text } from '../../../components/component-library'; +import { + Color, + FONT_WEIGHT, + TextVariant, +} from '../../../helpers/constants/design-system'; + +import { + coinTypeToProtocolName, + getSnapName, + getSnapDerivationPathName, +} from '../../../helpers/utils/util'; export function getSnapInstallWarnings(permissions, targetSubjectMetadata, t) { const bip32EntropyPermissions = @@ -15,24 +26,45 @@ export function getSnapInstallWarnings(permissions, targetSubjectMetadata, t) { .map(([, value]) => value); return [ - ...flatMap(bip32EntropyPermissions, (permission, i) => + ...bip32EntropyPermissions.flatMap((permission, i) => permission.caveats[0].value.map(({ path, curve }) => ({ id: `key-access-bip32-${path .join('-') .replace(/'/gu, 'h')}-${curve}-${i}`, message: t('snapInstallWarningKeyAccess', [ - targetSubjectMetadata.name, - `${path.join('/')} (${curve})`, + + {getSnapName(targetSubjectMetadata.origin)} + , + + {getSnapDerivationPathName(path, curve) ?? + `${path.join('/')} (${curve})`} + , ]), })), ), - ...flatMap(bip44EntropyPermissions, (permission, i) => + ...bip44EntropyPermissions.flatMap((permission, i) => permission.caveats[0].value.map(({ coinType }) => ({ id: `key-access-bip44-${coinType}-${i}`, message: t('snapInstallWarningKeyAccess', [ - targetSubjectMetadata.name, - coinTypeToProtocolName(coinType) || - t('unrecognizedProtocol', [coinType]), + + {getSnapName(targetSubjectMetadata.origin)} + , + + {coinTypeToProtocolName(coinType) || + t('unrecognizedProtocol', [coinType])} + , ]), })), ), diff --git a/ui/pages/permissions-connect/index.scss b/ui/pages/permissions-connect/index.scss index 1ca3c116a1de..358f93a2e0a3 100644 --- a/ui/pages/permissions-connect/index.scss +++ b/ui/pages/permissions-connect/index.scss @@ -1,6 +1,7 @@ @import 'choose-account/index'; @import 'flask/snap-install/index'; @import 'flask/snap-update/index'; +@import 'flask/snap-result/index'; @import 'redirect/index'; .permissions-connect { diff --git a/ui/pages/permissions-connect/permissions-connect.component.js b/ui/pages/permissions-connect/permissions-connect.component.js index 28c7b373ee78..b5435d489539 100644 --- a/ui/pages/permissions-connect/permissions-connect.component.js +++ b/ui/pages/permissions-connect/permissions-connect.component.js @@ -19,6 +19,7 @@ import PermissionsRedirect from './redirect'; ///: BEGIN:ONLY_INCLUDE_IN(flask) import SnapInstall from './flask/snap-install'; import SnapUpdate from './flask/snap-update'; +import SnapResult from './flask/snap-result'; ///: END:ONLY_INCLUDE_IN const APPROVE_TIMEOUT = MILLISECOND * 1200; @@ -44,7 +45,9 @@ export default class PermissionConnect extends Component { ///: BEGIN:ONLY_INCLUDE_IN(flask) snapInstallPath: PropTypes.string.isRequired, snapUpdatePath: PropTypes.string.isRequired, - isSnap: PropTypes.bool.isRequired, + snapResultPath: PropTypes.string.isRequired, + requestType: PropTypes.string.isRequired, + requestState: PropTypes.object.isRequired, approvePendingApproval: PropTypes.func.isRequired, rejectPendingApproval: PropTypes.func.isRequired, ///: END:ONLY_INCLUDE_IN @@ -102,7 +105,8 @@ export default class PermissionConnect extends Component { ///: BEGIN:ONLY_INCLUDE_IN(flask) snapInstallPath, snapUpdatePath, - isSnap, + snapResultPath, + requestType, ///: END:ONLY_INCLUDE_IN getRequestAccountTabIds, permissionsRequest, @@ -123,13 +127,20 @@ export default class PermissionConnect extends Component { if (history.location.pathname === connectPath && !isRequestingAccounts) { ///: BEGIN:ONLY_INCLUDE_IN(flask) - if (isSnap) { - history.push( - permissionsRequest.newPermissions ? snapUpdatePath : snapInstallPath, - ); - } else { - ///: END:ONLY_INCLUDE_IN - history.push(confirmPermissionPath); + + switch (requestType) { + case 'wallet_installSnap': + history.push(snapInstallPath); + break; + case 'wallet_updateSnap': + history.push(snapUpdatePath); + break; + case 'wallet_installSnapResult': + history.push(snapResultPath); + break; + default: + ///: END:ONLY_INCLUDE_IN + history.push(confirmPermissionPath); ///: BEGIN:ONLY_INCLUDE_IN(flask) } ///: END:ONLY_INCLUDE_IN @@ -171,8 +182,8 @@ export default class PermissionConnect extends Component { ///: BEGIN:ONLY_INCLUDE_IN(flask) snapInstallPath, snapUpdatePath, - isSnap, - permissionsRequest, + snapResultPath, + requestType, ///: END:ONLY_INCLUDE_IN } = this.props; this.setState( @@ -183,30 +194,65 @@ export default class PermissionConnect extends Component { () => this.props.history.push(confirmPermissionPath), ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(flask) - () => - this.props.history.push( - // eslint-disable-next-line no-nested-ternary - isSnap - ? permissionsRequest.newPermissions - ? snapUpdatePath - : snapInstallPath - : confirmPermissionPath, - ), + () => { + switch (requestType) { + case 'wallet_installSnap': + this.props.history.push(snapInstallPath); + break; + case 'wallet_updateSnap': + this.props.history.push(snapUpdatePath); + break; + case 'wallet_installSnapResult': + this.props.history.push(snapResultPath); + break; + default: + this.props.history.push(confirmPermissionPath); + } + }, ///: END:ONLY_INCLUDE_IN ); }; redirect(approved) { - const { history } = this.props; + const { + history, + ///: BEGIN:ONLY_INCLUDE_IN(flask) + permissionsRequest, + ///: END:ONLY_INCLUDE_IN + } = this.props; + + ///: BEGIN:ONLY_INCLUDE_IN(flask) + const isRequestingSnap = + permissionsRequest?.permissions && + Object.keys(permissionsRequest.permissions).includes('wallet_snap'); + + const shouldRedirect = !isRequestingSnap; + + this.setState({ + redirecting: shouldRedirect, + permissionsApproved: approved, + }); + ///: END:ONLY_INCLUDE_IN + + ///: BEGIN:ONLY_INCLUDE_IN(main,beta) this.setState({ redirecting: true, permissionsApproved: approved, }); + ///: END:ONLY_INCLUDE_IN this.removeBeforeUnload(); + ///: BEGIN:ONLY_INCLUDE_IN(flask) + if (shouldRedirect && approved) { + setTimeout(() => history.push(DEFAULT_ROUTE), APPROVE_TIMEOUT); + } + ///: END:ONLY_INCLUDE_IN + ///: BEGIN:ONLY_INCLUDE_IN(main,beta) if (approved) { setTimeout(() => history.push(DEFAULT_ROUTE), APPROVE_TIMEOUT); - } else { + } + ///: END:ONLY_INCLUDE_IN + else { history.push(DEFAULT_ROUTE); } } @@ -268,6 +314,8 @@ export default class PermissionConnect extends Component { ///: BEGIN:ONLY_INCLUDE_IN(flask) snapInstallPath, snapUpdatePath, + snapResultPath, + requestState, approvePendingApproval, rejectPendingApproval, ///: END:ONLY_INCLUDE_IN @@ -340,19 +388,22 @@ export default class PermissionConnect extends Component { render={() => ( { approvePendingApproval(requestId, { ...permissionsRequest, + permissions: requestState.permissions, approvedAccounts: [...selectedAccountAddresses], }); - this.redirect(true); + this.setState({ permissionsApproved: true }); }} rejectSnapInstall={(requestId) => { rejectPendingApproval( requestId, serializeError(ethErrors.provider.userRejectedRequest()), ); - this.redirect(false); + this.setState({ permissionsApproved: true }); + this.removeBeforeUnload(); }} targetSubjectMetadata={targetSubjectMetadata} /> @@ -370,19 +421,44 @@ export default class PermissionConnect extends Component { render={() => ( { approvePendingApproval(requestId, { ...permissionsRequest, + permissions: requestState.permissions, approvedAccounts: [...selectedAccountAddresses], }); - this.redirect(true); + this.setState({ permissionsApproved: true }); }} rejectSnapUpdate={(requestId) => { rejectPendingApproval( requestId, serializeError(ethErrors.provider.userRejectedRequest()), ); - this.redirect(false); + this.setState({ permissionsApproved: false }); + this.removeBeforeUnload(); + }} + targetSubjectMetadata={targetSubjectMetadata} + /> + )} + /> + { + ///: END:ONLY_INCLUDE_IN + } + { + ///: BEGIN:ONLY_INCLUDE_IN(flask) + } + ( + { + approvePendingApproval(requestId); + this.setState({ permissionsApproved: true }); + this.removeBeforeUnload(); }} targetSubjectMetadata={targetSubjectMetadata} /> diff --git a/ui/pages/permissions-connect/permissions-connect.container.js b/ui/pages/permissions-connect/permissions-connect.container.js index 98e6e95113ff..c72db67e0f00 100644 --- a/ui/pages/permissions-connect/permissions-connect.container.js +++ b/ui/pages/permissions-connect/permissions-connect.container.js @@ -8,6 +8,8 @@ import { getSelectedAddress, ///: BEGIN:ONLY_INCLUDE_IN(flask) getSnapInstallOrUpdateRequests, + getRequestState, + getRequestType, ///: END:ONLY_INCLUDE_IN getTargetSubjectMetadata, } from '../../selectors'; @@ -30,6 +32,7 @@ import { ///: BEGIN:ONLY_INCLUDE_IN(flask) CONNECT_SNAP_INSTALL_ROUTE, CONNECT_SNAP_UPDATE_ROUTE, + CONNECT_SNAP_RESULT_ROUTE, ///: END:ONLY_INCLUDE_IN } from '../../helpers/constants/routes'; import PermissionApproval from './permissions-connect.component'; @@ -72,6 +75,10 @@ const mapStateToProps = (state, ownProps) => { ///: BEGIN:ONLY_INCLUDE_IN(flask) const isSnap = targetSubjectMetadata.subjectType === SubjectType.Snap; + + const requestType = getRequestType(state, permissionsRequestId); + + const requestState = getRequestState(state, permissionsRequestId); ///: END:ONLY_INCLUDE_IN const accountsWithLabels = getAccountsWithLabels(state); @@ -91,6 +98,7 @@ const mapStateToProps = (state, ownProps) => { ///: BEGIN:ONLY_INCLUDE_IN(flask) const snapInstallPath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_SNAP_INSTALL_ROUTE}`; const snapUpdatePath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_SNAP_UPDATE_ROUTE}`; + const snapResultPath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_SNAP_RESULT_ROUTE}`; ///: END:ONLY_INCLUDE_IN let totalPages = 1 + isRequestingAccounts; @@ -105,7 +113,11 @@ const mapStateToProps = (state, ownProps) => { } else if (pathname === confirmPermissionPath) { page = isRequestingAccounts ? '2' : '1'; ///: BEGIN:ONLY_INCLUDE_IN(flask) - } else if (pathname === snapInstallPath || pathname === snapUpdatePath) { + } else if ( + pathname === snapInstallPath || + pathname === snapUpdatePath || + pathname === snapResultPath + ) { page = isRequestingAccounts ? '3' : '2'; ///: END:ONLY_INCLUDE_IN } else { @@ -115,9 +127,12 @@ const mapStateToProps = (state, ownProps) => { return { isRequestingAccounts, ///: BEGIN:ONLY_INCLUDE_IN(flask) - isSnap, + requestType, snapInstallPath, snapUpdatePath, + snapResultPath, + requestState, + isSnap, ///: END:ONLY_INCLUDE_IN permissionsRequest, permissionsRequestId, diff --git a/ui/selectors/permissions.js b/ui/selectors/permissions.js index b9113a6e622e..27f5c4c88f6a 100644 --- a/ui/selectors/permissions.js +++ b/ui/selectors/permissions.js @@ -310,7 +310,9 @@ export function getSnapInstallOrUpdateRequests(state) { return Object.values(state.metamask.pendingApprovals) .filter( ({ type }) => - type === 'wallet_installSnap' || type === 'wallet_updateSnap', + type === 'wallet_installSnap' || + type === 'wallet_updateSnap' || + type === 'wallet_installSnapResult', ) .map(({ requestData }) => requestData); } @@ -334,3 +336,11 @@ export function getFirstPermissionRequest(state) { export function getPermissions(state, origin) { return getPermissionSubjects(state)[origin]?.permissions; } + +export function getRequestState(state, id) { + return state.metamask.pendingApprovals[id]?.requestState; +} + +export function getRequestType(state, id) { + return state.metamask.pendingApprovals[id]?.type; +} diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 6edae5f14bbb..a71cdb62e63c 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -3696,6 +3696,7 @@ export function approvePermissionsRequest( if (err) { dispatch(displayWarning(err)); } + forceUpdateMetamaskState(dispatch); }); }; } diff --git a/yarn.lock b/yarn.lock index e05d1c415b25..a23a32235c3e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4002,9 +4002,9 @@ __metadata: languageName: node linkType: hard -"@metamask/key-tree@npm:^6.2.1": - version: 6.2.1 - resolution: "@metamask/key-tree@npm:6.2.1" +"@metamask/key-tree@npm:^7.0.0": + version: 7.0.0 + resolution: "@metamask/key-tree@npm:7.0.0" dependencies: "@metamask/scure-bip39": ^2.1.0 "@metamask/utils": ^3.3.0 @@ -4012,7 +4012,7 @@ __metadata: "@noble/hashes": ^1.0.0 "@noble/secp256k1": ^1.5.5 "@scure/base": ^1.0.0 - checksum: 78931e20a2c933535c17d21e092dbc66e3e96f3c171a6814af51830b7774dd3b4059326180a3b1f52e48a258254a7ea70a31d0c335bf24a8c4250f8d196bc7ba + checksum: e3b8371ba41766e1936ff0b0b34c46b7a1297e51fe177246ae6446c4f15a6601a6ed4fc5d01d0594e8b1e9b1682096900f7ab4e7e15df94041ec025116a635b9 languageName: node linkType: hard @@ -4267,22 +4267,22 @@ __metadata: languageName: node linkType: hard -"@metamask/rpc-methods@npm:^0.30.0": - version: 0.30.0 - resolution: "@metamask/rpc-methods@npm:0.30.0" +"@metamask/rpc-methods@npm:^0.31.0": + version: 0.31.0 + resolution: "@metamask/rpc-methods@npm:0.31.0" dependencies: "@metamask/browser-passworder": ^4.0.2 - "@metamask/key-tree": ^6.2.1 + "@metamask/key-tree": ^7.0.0 "@metamask/permission-controller": ^3.0.0 - "@metamask/snaps-ui": ^0.30.0 - "@metamask/snaps-utils": ^0.30.0 + "@metamask/snaps-ui": ^0.31.0 + "@metamask/snaps-utils": ^0.31.0 "@metamask/types": ^1.1.0 - "@metamask/utils": ^3.4.1 + "@metamask/utils": ^5.0.0 "@noble/hashes": ^1.1.3 eth-rpc-errors: ^4.0.2 nanoid: ^3.1.31 superstruct: ^1.0.3 - checksum: ae584a2ea403653199c17e4fe43bdf26d25f2dbab8609e48f85d6a1def762526370e145cf69dc5649ee501f6e170ea27161b3d908c62ff1e9617af5e23564793 + checksum: 73e7271279a3eff43f5bca46343d582a3f90f2eda3c8e4dcfc46e01f097be2222decf8a4d66aee3a8fc4fdc1b15de176460ea0f2a87ec99889a2c5456800879c languageName: node linkType: hard @@ -4329,21 +4329,21 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-controllers@npm:^0.30.0": - version: 0.30.0 - resolution: "@metamask/snaps-controllers@npm:0.30.0" +"@metamask/snaps-controllers@npm:^0.31.0": + version: 0.31.0 + resolution: "@metamask/snaps-controllers@npm:0.31.0" dependencies: "@metamask/approval-controller": ^2.0.0 "@metamask/base-controller": ^2.0.0 "@metamask/object-multiplex": ^1.1.0 "@metamask/permission-controller": ^3.0.0 "@metamask/post-message-stream": ^6.1.0 - "@metamask/rpc-methods": ^0.30.0 - "@metamask/snaps-execution-environments": ^0.30.0 - "@metamask/snaps-registry": ^1.1.0 - "@metamask/snaps-utils": ^0.30.0 + "@metamask/rpc-methods": ^0.31.0 + "@metamask/snaps-execution-environments": ^0.31.0 + "@metamask/snaps-registry": ^1.1.1 + "@metamask/snaps-utils": ^0.31.0 "@metamask/subject-metadata-controller": ^2.0.0 - "@metamask/utils": ^3.4.1 + "@metamask/utils": ^5.0.0 "@xstate/fsm": ^2.0.0 concat-stream: ^2.0.0 cron-parser: ^4.5.0 @@ -4356,61 +4356,61 @@ __metadata: pump: ^3.0.0 readable-web-to-node-stream: ^3.0.2 tar-stream: ^2.2.0 - checksum: 022f875037fd058c237b8cba80e47a5ef222d379a073af4a35242e15c8c83ac902050ab8ae51a80536254b4ccb7e73b854c49b3ff5fac51154071da46d879e20 + checksum: 6e3a69216a3dbcdef7702caf60795bd7bc25169e491d695a14638c0ce67dcfc67e509f8f29604d974d23cc132edf933e16d2859d4502455e805a2ffd9a366d12 languageName: node linkType: hard -"@metamask/snaps-execution-environments@npm:^0.30.0": - version: 0.30.0 - resolution: "@metamask/snaps-execution-environments@npm:0.30.0" +"@metamask/snaps-execution-environments@npm:^0.31.0": + version: 0.31.0 + resolution: "@metamask/snaps-execution-environments@npm:0.31.0" dependencies: "@metamask/object-multiplex": ^1.2.0 "@metamask/post-message-stream": ^6.1.0 "@metamask/providers": ^10.2.0 - "@metamask/rpc-methods": ^0.30.0 - "@metamask/snaps-utils": ^0.30.0 - "@metamask/utils": ^3.4.1 + "@metamask/rpc-methods": ^0.31.0 + "@metamask/snaps-utils": ^0.31.0 + "@metamask/utils": ^5.0.0 eth-rpc-errors: ^4.0.3 json-rpc-engine: ^6.1.0 pump: ^3.0.0 ses: ^0.18.1 stream-browserify: ^3.0.0 superstruct: ^1.0.3 - checksum: ee81caec7bf77703510983f62f3f2cf94753b624ba14691ff80ca30efab9a07ca329b278cd08323c72bcb3aeef7e78d089af4d35d14a469afc4f7bb8bc7e5fa7 + checksum: 114b8370980868e3a8db650a9cac35a31f063d82621bbc7e052157a54ee96b60c26db260bcac904813b751fec103da5fe74af061d0c7dd3fb6c50dd975258350 languageName: node linkType: hard -"@metamask/snaps-registry@npm:^1.0.0, @metamask/snaps-registry@npm:^1.1.0": - version: 1.1.0 - resolution: "@metamask/snaps-registry@npm:1.1.0" +"@metamask/snaps-registry@npm:^1.1.1": + version: 1.1.1 + resolution: "@metamask/snaps-registry@npm:1.1.1" dependencies: - "@metamask/utils": ^3.4.0 + "@metamask/utils": ^5.0.0 superstruct: ^1.0.3 - checksum: a67a9e7a30a2f7bc55d130b278573aa2bf043ab5c5935704ab8166898dadf37557284412f9b653f9ed52059a557a51b8472aabbe6887a06922d5526ead12199b + checksum: 1e77c30af2955baf559160c1098f79a042196b23cd691448668672b4c8c3d92a0602061ab4c4f05b2f60310cfd81147c5e87ce80da6d4a48e45765d569132a77 languageName: node linkType: hard -"@metamask/snaps-ui@npm:^0.30.0": - version: 0.30.0 - resolution: "@metamask/snaps-ui@npm:0.30.0" +"@metamask/snaps-ui@npm:^0.31.0": + version: 0.31.0 + resolution: "@metamask/snaps-ui@npm:0.31.0" dependencies: - "@metamask/utils": ^3.4.1 + "@metamask/utils": ^5.0.0 superstruct: ^1.0.3 - checksum: a4612e08e830542b094bf995023221a24dfa45861d3ca919c1c11ae1bd3d6933789d40c667780145bcd3ff3053d0593e654ae1812ea9f3c7cc973d500cfb0a96 + checksum: b4d7df0e3f3bd28c8cb958c72143d442c44f0ca4f02e226725b804e9f24c1d9a0f7d1d46818adb2969651d4a4626b57a80e669c9867f0dc3335fd85cf458c9a3 languageName: node linkType: hard -"@metamask/snaps-utils@npm:^0.30.0": - version: 0.30.0 - resolution: "@metamask/snaps-utils@npm:0.30.0" +"@metamask/snaps-utils@npm:^0.31.0": + version: 0.31.0 + resolution: "@metamask/snaps-utils@npm:0.31.0" dependencies: "@babel/core": ^7.18.6 "@babel/types": ^7.18.7 "@metamask/permission-controller": ^3.0.0 "@metamask/providers": ^10.2.1 - "@metamask/snaps-registry": ^1.0.0 - "@metamask/snaps-ui": ^0.30.0 - "@metamask/utils": ^3.4.1 + "@metamask/snaps-registry": ^1.1.1 + "@metamask/snaps-ui": ^0.31.0 + "@metamask/utils": ^5.0.0 "@noble/hashes": ^1.1.3 "@scure/base": ^1.1.1 cron-parser: ^4.5.0 @@ -4422,7 +4422,7 @@ __metadata: ses: ^0.18.1 superstruct: ^1.0.3 validate-npm-package-name: ^5.0.0 - checksum: 56334795dee7deeddd63badd35870ebb51ba5336071146bd6f48a9f7c1d703515241ac9371d7a4e054d77d7c6d06f34530e7b9439074339a46fea89920d32bce + checksum: 007dc6e08989b0a197cc84d95b29a194c3408a2f9e822a42c2060a1c842ff10c737cf8f5f0e8f8178a7ea7f98565cfc8e16eaa9181331b50b5dc13c3a80655cd languageName: node linkType: hard @@ -4464,7 +4464,7 @@ __metadata: languageName: node linkType: hard -"@metamask/utils@npm:^3.0.1, @metamask/utils@npm:^3.0.3, @metamask/utils@npm:^3.3.0, @metamask/utils@npm:^3.3.1, @metamask/utils@npm:^3.4.0, @metamask/utils@npm:^3.4.1": +"@metamask/utils@npm:^3.0.1, @metamask/utils@npm:^3.0.3, @metamask/utils@npm:^3.3.0, @metamask/utils@npm:^3.3.1, @metamask/utils@npm:^3.4.1": version: 3.6.0 resolution: "@metamask/utils@npm:3.6.0" dependencies: @@ -24280,7 +24280,7 @@ __metadata: "@metamask/forwarder": ^1.1.0 "@metamask/gas-fee-controller": ^1.0.0 "@metamask/jazzicon": ^2.0.0 - "@metamask/key-tree": ^6.2.1 + "@metamask/key-tree": ^7.0.0 "@metamask/logo": ^3.1.1 "@metamask/metamask-eth-abis": ^3.0.0 "@metamask/notification-controller": ^1.0.0 @@ -24291,13 +24291,13 @@ __metadata: "@metamask/post-message-stream": ^6.0.0 "@metamask/providers": ^10.2.1 "@metamask/rate-limit-controller": ^1.0.0 - "@metamask/rpc-methods": ^0.30.0 + "@metamask/rpc-methods": ^0.31.0 "@metamask/scure-bip39": ^2.0.3 "@metamask/slip44": ^2.1.0 "@metamask/smart-transactions-controller": ^3.1.0 - "@metamask/snaps-controllers": ^0.30.0 - "@metamask/snaps-ui": ^0.30.0 - "@metamask/snaps-utils": ^0.30.0 + "@metamask/snaps-controllers": ^0.31.0 + "@metamask/snaps-ui": ^0.31.0 + "@metamask/snaps-utils": ^0.31.0 "@metamask/subject-metadata-controller": ^1.0.0 "@metamask/test-dapp": ^5.6.0 "@metamask/utils": ^5.0.0 From c21c2bdcf0c757b71a179a37bbcd7b87cf19b5dd Mon Sep 17 00:00:00 2001 From: legobeat <109787230+legobeat@users.noreply.github.com> Date: Fri, 17 Mar 2023 23:29:39 +0900 Subject: [PATCH 4/8] security: patch request for CVE-2023-28155 (#18208) * security: patch request for CVE-2023-28155 GHSA-p8p7-x288-28g6 Ported from https://github.com/request/request/pull/3444 * add iyarc exclusion --- .iyarc | 4 +++ .../request-npm-2.88.2-f4a57c72c4.patch | 31 +++++++++++++++++++ package.json | 5 ++- yarn.lock | 30 +++++++++++++++++- 4 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 .yarn/patches/request-npm-2.88.2-f4a57c72c4.patch diff --git a/.iyarc b/.iyarc index 3fa8de8b354d..79536d3837f1 100644 --- a/.iyarc +++ b/.iyarc @@ -15,3 +15,7 @@ GHSA-6fc8-4gx4-v693 # patched version of 3.3.1. We can remove this once the # smart-transaction-controller updates its dependency. GHSA-8gh8-hqwg-xf34 + +# request library is subject to SSRF. +# addressed by temporary patch in .yarn/patches/request-npm-2.88.2-f4a57c72c4.patch +GHSA-p8p7-x288-28g6 diff --git a/.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch b/.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch new file mode 100644 index 000000000000..c879c340c938 --- /dev/null +++ b/.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch @@ -0,0 +1,31 @@ +diff --git a/lib/redirect.js b/lib/redirect.js +index b9150e77c73d63367845c0aec15b5684d900943f..2864f9f2abc481ecf2b2dd96b1293f5b93393efd 100644 +--- a/lib/redirect.js ++++ b/lib/redirect.js +@@ -14,6 +14,7 @@ function Redirect (request) { + this.redirects = [] + this.redirectsFollowed = 0 + this.removeRefererHeader = false ++ this.allowInsecureRedirect = false + } + + Redirect.prototype.onRequest = function (options) { +@@ -40,6 +41,9 @@ Redirect.prototype.onRequest = function (options) { + if (options.followOriginalHttpMethod !== undefined) { + self.followOriginalHttpMethod = options.followOriginalHttpMethod + } ++ if (options.allowInsecureRedirect !== undefined) { ++ self.allowInsecureRedirect = options.allowInsecureRedirect ++ } + } + + Redirect.prototype.redirectTo = function (response) { +@@ -108,7 +112,7 @@ Redirect.prototype.onResponse = function (response) { + request.uri = url.parse(redirectTo) + + // handle the case where we change protocol from https to http or vice versa +- if (request.uri.protocol !== uriPrev.protocol) { ++ if (request.uri.protocol !== uriPrev.protocol && self.allowInsecureRedirect) { + delete request.agent + } + diff --git a/package.json b/package.json index cffda13841c6..d57328cb565a 100644 --- a/package.json +++ b/package.json @@ -201,7 +201,10 @@ "async-done@~1.3.2": "patch:async-done@npm%3A1.3.2#./.yarn/patches/async-done-npm-1.3.2-1f0a4a8997.patch", "async-done@^1.2.0": "patch:async-done@npm%3A1.3.2#./.yarn/patches/async-done-npm-1.3.2-1f0a4a8997.patch", "async-done@^1.2.2": "patch:async-done@npm%3A1.3.2#./.yarn/patches/async-done-npm-1.3.2-1f0a4a8997.patch", - "fast-json-patch@^3.1.1": "patch:fast-json-patch@npm%3A3.1.1#./.yarn/patches/fast-json-patch-npm-3.1.1-7e8bb70a45.patch" + "fast-json-patch@^3.1.1": "patch:fast-json-patch@npm%3A3.1.1#./.yarn/patches/fast-json-patch-npm-3.1.1-7e8bb70a45.patch", + "request@^2.83.0": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch", + "request@^2.88.2": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch", + "request@^2.85.0": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch" }, "dependencies": { "@babel/runtime": "^7.5.5", diff --git a/yarn.lock b/yarn.lock index a23a32235c3e..11acd8dea5d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29800,7 +29800,7 @@ __metadata: languageName: node linkType: hard -"request@npm:^2.83.0, request@npm:^2.85.0, request@npm:^2.88.2": +"request@npm:2.88.2": version: 2.88.2 resolution: "request@npm:2.88.2" dependencies: @@ -29828,6 +29828,34 @@ __metadata: languageName: node linkType: hard +"request@patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch::locator=metamask-crx%40workspace%3A.": + version: 2.88.2 + resolution: "request@patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch::version=2.88.2&hash=2aadd7&locator=metamask-crx%40workspace%3A." + dependencies: + aws-sign2: ~0.7.0 + aws4: ^1.8.0 + caseless: ~0.12.0 + combined-stream: ~1.0.6 + extend: ~3.0.2 + forever-agent: ~0.6.1 + form-data: ~2.3.2 + har-validator: ~5.1.3 + http-signature: ~1.2.0 + is-typedarray: ~1.0.0 + isstream: ~0.1.2 + json-stringify-safe: ~5.0.1 + mime-types: ~2.1.19 + oauth-sign: ~0.9.0 + performance-now: ^2.1.0 + qs: ~6.5.2 + safe-buffer: ^5.1.2 + tough-cookie: ~2.5.0 + tunnel-agent: ^0.6.0 + uuid: ^3.3.2 + checksum: 1a64d706b36b2bdd5803c3a0fd3fee5e76e8c17d01c34f84972460fbfa5914302c300821a1fafce804d236e637f3745f3bdfbbb4219c139e112076790fc279af + languageName: node + linkType: hard + "require-directory@npm:^2.1.1": version: 2.1.1 resolution: "require-directory@npm:2.1.1" From 3117890b305ffae45251264b30a8d77c32d888be Mon Sep 17 00:00:00 2001 From: George Marshall Date: Fri, 17 Mar 2023 10:06:59 -0700 Subject: [PATCH 5/8] AvatarBase font-size logic (#18203) * Updating AvatarBase to use Text component instead of Box and adding font size logic based on avatar size * Updating snaps --- .../__snapshots__/avatar-account.test.js.snap | 2 +- .../component-library/avatar-base/README.mdx | 2 + .../__snapshots__/avatar-base.test.js.snap | 2 +- .../avatar-base/avatar-base.js | 56 +++-- .../avatar-base/avatar-base.scss | 5 - .../avatar-base/avatar-base.stories.js | 3 +- .../avatar-base/avatar-base.test.js | 32 +-- .../__snapshots__/avatar-favicon.test.js.snap | 2 +- .../__snapshots__/avatar-icon.test.js.snap | 2 +- .../avatar-icon/avatar-icon.test.js | 4 +- .../avatar-network/README.mdx | 4 + .../__snapshots__/avatar-network.test.js.snap | 2 +- .../avatar-network/avatar-network.stories.js | 30 ++- .../avatar-network/avatar-network.test.js | 4 +- .../component-library/avatar-token/README.mdx | 4 + .../__snapshots__/avatar-token.test.js.snap | 2 +- .../avatar-token/avatar-token.stories.js | 228 +++++++++++++++++- .../avatar-token/avatar-token.test.js | 4 +- .../__snapshots__/picker-network.test.js.snap | 2 +- .../__snapshots__/tag-url.test.js.snap | 2 +- 20 files changed, 326 insertions(+), 66 deletions(-) diff --git a/ui/components/component-library/avatar-account/__snapshots__/avatar-account.test.js.snap b/ui/components/component-library/avatar-account/__snapshots__/avatar-account.test.js.snap index 4fb7ecbbe6be..0dfe9536f963 100644 --- a/ui/components/component-library/avatar-account/__snapshots__/avatar-account.test.js.snap +++ b/ui/components/component-library/avatar-account/__snapshots__/avatar-account.test.js.snap @@ -3,7 +3,7 @@ exports[`AvatarAccount should render correctly 1`] = `