diff --git a/.circleci/config.yml b/.circleci/config.yml index f75e475f65b9..f4b824285a19 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -118,6 +118,9 @@ workflows: - prep-build: requires: - prep-deps + - prep-build-mv2: + requires: + - prep-deps - prep-build-desktop: filters: branches: @@ -127,18 +130,24 @@ workflows: - prep-build-flask: requires: - prep-deps + - prep-build-flask-mv2: + requires: + - prep-deps - prep-build-test: requires: - prep-deps - - prep-build-multichain-test: + - prep-build-test-mv2: requires: - prep-deps - - prep-build-test-mv3: + - prep-build-multichain-test: requires: - prep-deps - prep-build-test-flask: requires: - prep-deps + - prep-build-test-flask-mv2: + requires: + - prep-deps - prep-build-test-mmi: requires: - prep-deps @@ -170,7 +179,7 @@ workflows: - prep-build-multichain-test - test-e2e-firefox: requires: - - prep-build-test + - prep-build-test-mv2 - test-e2e-chrome-rpc: requires: - prep-build-test @@ -182,7 +191,7 @@ workflows: - prep-build-test-flask - test-e2e-firefox-flask: requires: - - prep-build-test-flask + - prep-build-test-flask-mv2 - test-e2e-chrome-mmi: requires: - prep-build-test-mmi @@ -192,9 +201,6 @@ workflows: - test-e2e-chrome-rpc-mmi: requires: - prep-build-test-mmi - - test-e2e-chrome-mv3: - requires: - - prep-build-test-mv3 - test-e2e-chrome-vault-decryption: filters: branches: @@ -227,6 +233,9 @@ workflows: - validate-source-maps: requires: - prep-build + - validate-source-maps-mv2: + requires: + - prep-build-mv2 - validate-source-maps-beta: requires: - trigger-beta-build @@ -242,10 +251,13 @@ workflows: - validate-source-maps-flask: requires: - prep-build-flask - - test-mozilla-lint: + - validate-source-maps-flask-mv2: + requires: + - prep-build-flask-mv2 + - test-mozilla-lint-mv2: requires: - prep-deps - - prep-build + - prep-build-mv2 - test-mozilla-lint-desktop: filters: branches: @@ -253,10 +265,10 @@ workflows: requires: - prep-deps - prep-build-desktop - - test-mozilla-lint-flask: + - test-mozilla-lint-flask-mv2: requires: - prep-deps - - prep-build-flask + - prep-build-flask-mv2 - all-tests-pass: requires: - test-deps-depcheck @@ -277,9 +289,9 @@ workflows: - validate-source-maps-desktop - validate-source-maps-flask - validate-source-maps-mmi - - test-mozilla-lint + - test-mozilla-lint-mv2 - test-mozilla-lint-desktop - - test-mozilla-lint-flask + - test-mozilla-lint-flask-mv2 - test-e2e-chrome - test-e2e-chrome-multichain - test-e2e-chrome-multiple-providers @@ -298,18 +310,19 @@ workflows: - prep-build-test - stats-module-load-init: requires: - - prep-build-test-mv3 + - prep-build-test - job-publish-prerelease: requires: - prep-deps - prep-build + - prep-build-mv2 - trigger-beta-build - prep-build-desktop - prep-build-mmi - prep-build-flask + - prep-build-flask-mv2 - prep-build-storybook - prep-build-ts-migration-dashboard - - prep-build-test-mv3 - benchmark - user-actions-benchmark - stats-module-load-init @@ -321,9 +334,11 @@ workflows: requires: - prep-deps - prep-build + - prep-build-mv2 - prep-build-desktop - prep-build-mmi - prep-build-flask + - prep-build-flask-mv2 - all-tests-pass - job-publish-storybook: filters: @@ -561,6 +576,48 @@ jobs: - dist - builds + prep-build-mv2: + executor: node-browsers-medium-plus + steps: + - run: *shallow-git-clone + - attach_workspace: + at: . + - when: + condition: + not: + matches: + pattern: /^master$/ + value: << pipeline.git.branch >> + steps: + - run: + name: build:dist + command: ENABLE_MV3=false yarn build dist + - when: + condition: + matches: + pattern: /^master$/ + value: << pipeline.git.branch >> + steps: + - run: + name: build:prod + command: ENABLE_MV3=false yarn build prod + - run: + name: build:debug + command: find dist/ -type f -exec md5sum {} \; | sort -k 2 + - run: + name: Move mmi build to 'dist-mv2' to avoid conflict with production build + command: mv ./dist ./dist-mv2 + - run: + name: Move mmi zips to 'builds-mv2' to avoid conflict with production build + command: mv ./builds ./builds-mv2 + - store_artifacts: + path: builds-mv2 + - persist_to_workspace: + root: . + paths: + - dist-mv2 + - builds-mv2 + prep-build-desktop: executor: node-browsers-medium-plus steps: @@ -668,6 +725,46 @@ jobs: - dist-flask - builds-flask + prep-build-flask-mv2: + executor: node-browsers-medium-plus + steps: + - run: *shallow-git-clone + - attach_workspace: + at: . + - when: + condition: + not: + matches: + pattern: /^master$/ + value: << pipeline.git.branch >> + steps: + - run: + name: build:dist + command: ENABLE_MV3=false yarn build --build-type flask dist + - when: + condition: + matches: + pattern: /^master$/ + value: << pipeline.git.branch >> + steps: + - run: + name: build:prod + command: yarn build --build-type flask prod + - run: + name: build:debug + command: find dist/ -type f -exec md5sum {} \; | sort -k 2 + - run: + name: Move flask build to 'dist-flask' to avoid conflict with production build + command: mv ./dist ./dist-flask-mv2 + - run: + name: Move flask zips to 'builds-flask' to avoid conflict with production build + command: mv ./builds ./builds-flask-mv2 + - persist_to_workspace: + root: . + paths: + - dist-flask-mv2 + - builds-flask-mv2 + prep-build-test-flask: executor: node-browsers-medium-plus steps: @@ -689,6 +786,27 @@ jobs: - dist-test-flask - builds-test-flask + prep-build-test-flask-mv2: + executor: node-browsers-medium-plus + steps: + - run: *shallow-git-clone + - attach_workspace: + at: . + - run: + name: Build extension for testing + command: yarn build:test:flask:mv2 + - run: + name: Move test build to 'dist-test-flask' to avoid conflict with production build + command: mv ./dist ./dist-test-flask-mv2 + - run: + name: Move test zips to 'builds-test-flask' to avoid conflict with production build + command: mv ./builds ./builds-test-flask-mv2 + - persist_to_workspace: + root: . + paths: + - dist-test-flask-mv2 + - builds-test-flask-mv2 + prep-build-test-mmi: executor: node-browsers-medium-plus steps: @@ -738,28 +856,30 @@ jobs: path: builds-test-mmi-playwright destination: builds-test-mmi-playwright - prep-build-test-mv3: + prep-build-test: executor: node-browsers-medium-plus steps: - run: *shallow-git-clone - attach_workspace: at: . - run: - name: Build extension in mv3 for testing - command: yarn build:test:mv3 + name: Build extension for testing + command: yarn build:test - run: name: Move test build to 'dist-test' to avoid conflict with production build - command: mv ./dist ./dist-test-mv3 + command: mv ./dist ./dist-test - run: - name: Move test zips to 'builds-test-mv3' to avoid conflict with production build - command: mv ./builds ./builds-test-mv3 + name: Move test zips to 'builds-test' to avoid conflict with production build + command: mv ./builds ./builds-test + - store_artifacts: + path: builds-test - persist_to_workspace: root: . paths: - - dist-test-mv3 - - builds-test-mv3 + - dist-test + - builds-test - prep-build-test: + prep-build-test-mv2: executor: node-browsers-medium-plus steps: - run: *shallow-git-clone @@ -767,20 +887,20 @@ jobs: at: . - run: name: Build extension for testing - command: yarn build:test + command: yarn build:test:mv2 - run: - name: Move test build to 'dist-test' to avoid conflict with production build - command: mv ./dist ./dist-test + name: Move test build to 'dist-test-mv2' to avoid conflict with production build + command: mv ./dist ./dist-test-mv2 - run: - name: Move test zips to 'builds-test' to avoid conflict with production build - command: mv ./builds ./builds-test + name: Move test zips to 'builds-test-mv2' to avoid conflict with production build + command: mv ./builds ./builds-test-mv2 - store_artifacts: - path: builds-test + path: builds-test-mv2 - persist_to_workspace: root: . paths: - - dist-test - - builds-test + - dist-test-mv2 + - builds-test-mv2 prep-build-multichain-test: executor: node-browsers-medium-plus @@ -991,31 +1111,6 @@ jobs: - store_test_results: path: test/test-results/e2e - test-e2e-chrome-mv3: - executor: node-browsers-medium-plus - parallelism: 16 - steps: - - run: *shallow-git-clone - - attach_workspace: - at: . - - run: - name: Move test build to dist - command: mv ./dist-test-mv3 ./dist - - run: - name: Move test zips to builds - command: mv ./builds-test-mv3 ./builds - - run: - name: test:e2e:chrome - command: | - if .circleci/scripts/test-run-e2e.sh - then - timeout 20m yarn test:e2e:chrome --retries 2 || echo "Temporarily suppressing MV3 e2e test failures" - fi - no_output_timeout: 5m - - store_artifacts: - path: test-artifacts - destination: test-artifacts - test-e2e-chrome-rpc: executor: node-browsers-medium parallelism: 1 @@ -1120,13 +1215,14 @@ jobs: at: . - run: name: Move test build to dist - command: mv ./dist-test-flask ./dist + command: mv ./dist-test-flask-mv2 ./dist - run: name: Move test zips to builds - command: mv ./builds-test-flask ./builds + command: mv ./builds-test-flask-mv2 ./builds - run: name: test:e2e:firefox:flask command: | + export ENABLE_MV3=false if .circleci/scripts/test-run-e2e.sh then timeout 20m yarn test:e2e:firefox:flask --retries 2 @@ -1242,13 +1338,14 @@ jobs: at: . - run: name: Move test build to dist - command: mv ./dist-test ./dist + command: mv ./dist-test-mv2 ./dist - run: name: Move test zips to builds - command: mv ./builds-test ./builds + command: mv ./builds-test-mv2 ./builds - run: name: test:e2e:firefox command: | + export ENABLE_MV3=false if .circleci/scripts/test-run-e2e.sh then timeout 20m yarn test:e2e:firefox --retries 2 @@ -1314,21 +1411,21 @@ jobs: at: . - run: name: Move test build to dist - command: mv ./dist-test-mv3 ./dist + command: mv ./dist-test ./dist - run: name: Move test zips to builds - command: mv ./builds-test-mv3 ./builds + command: mv ./builds-test ./builds - run: name: Run page load benchmark command: | - mkdir -p test-artifacts/chrome/mv3 - cp -R development/charts/flamegraph test-artifacts/chrome/mv3/initialisation - cp -R development/charts/flamegraph/chart test-artifacts/chrome/mv3/initialisation/background - cp -R development/charts/flamegraph/chart test-artifacts/chrome/mv3/initialisation/ui - cp -R development/charts/table test-artifacts/chrome/mv3/load_time + mkdir -p test-artifacts/chrome/ + cp -R development/charts/flamegraph test-artifacts/chrome/initialisation + cp -R development/charts/flamegraph/chart test-artifacts/chrome/initialisation/background + cp -R development/charts/flamegraph/chart test-artifacts/chrome/initialisation/ui + cp -R development/charts/table test-artifacts/chrome/load_time - run: name: Run page load benchmark - command: yarn mv3:stats:chrome --out test-artifacts/chrome/mv3 + command: yarn mv3:stats:chrome --out test-artifacts/chrome - run: name: Install jq command: sudo apt install jq -y @@ -1370,9 +1467,15 @@ jobs: - store_artifacts: path: builds-flask destination: builds-flask + - store_artifacts: + path: builds-flask-mv2 + destination: builds-flask-mv2 - store_artifacts: path: builds-mmi destination: builds-mmi + - store_artifacts: + path: builds-mv2 + destination: builds-mv2 - store_artifacts: path: builds-test - store_artifacts: @@ -1599,12 +1702,50 @@ jobs: name: Validate source maps command: yarn validate-source-maps - test-mozilla-lint: + validate-source-maps-flask-mv2: + executor: node-browsers-small + steps: + - run: *shallow-git-clone + - attach_workspace: + at: . + - run: + name: Move flask build to dist + command: mv ./dist-flask-mv2 ./dist + - run: + name: Move flask zips to builds + command: mv ./builds-flask-mv2 ./builds + - run: + name: Validate source maps + command: yarn validate-source-maps + + validate-source-maps-mv2: + executor: node-browsers-small + steps: + - run: *shallow-git-clone + - attach_workspace: + at: . + - run: + name: Move flask build to dist + command: mv ./dist-mv2 ./dist + - run: + name: Move flask zips to builds + command: mv ./builds-mv2 ./builds + - run: + name: Validate source maps + command: yarn validate-source-maps + + test-mozilla-lint-mv2: executor: node-browsers-medium steps: - run: *shallow-git-clone - attach_workspace: at: . + - run: + name: Move flask build to dist + command: mv ./dist-mv2 ./dist + - run: + name: Move flask zips to builds + command: mv ./builds-mv2 ./builds - run: name: test:mozilla-lint command: yarn mozilla-lint @@ -1625,7 +1766,7 @@ jobs: name: test:mozilla-lint command: yarn mozilla-lint - test-mozilla-lint-flask: + test-mozilla-lint-flask-mv2: executor: node-browsers-medium steps: - run: *shallow-git-clone @@ -1633,10 +1774,10 @@ jobs: at: . - run: name: Move flask build to dist - command: mv ./dist-flask ./dist + command: mv ./dist-flask-mv2 ./dist - run: name: Move flask zips to builds - command: mv ./builds-flask ./builds + command: mv ./builds-flask-mv2 ./builds - run: name: test:mozilla-lint command: yarn mozilla-lint diff --git a/.circleci/scripts/bundle-stats-commit.sh b/.circleci/scripts/bundle-stats-commit.sh index b7cb44ac756a..14b3604d82ec 100755 --- a/.circleci/scripts/bundle-stats-commit.sh +++ b/.circleci/scripts/bundle-stats-commit.sh @@ -42,7 +42,7 @@ git clone git@github.com:MetaMask/extension_bundlesize_stats.git temp { echo " '${CIRCLE_SHA1}': "; - cat test-artifacts/chrome/mv3/bundle_size_stats.json; + cat test-artifacts/chrome/bundle_size_stats.json; echo ", "; } >> temp/stats/bundle_size_data.temp.js @@ -57,14 +57,14 @@ if [ -f temp/stats/bundle_size_data.json ]; then { echo "},"; echo "\"$CIRCLE_SHA1\":"; - cat test-artifacts/chrome/mv3/bundle_size_stats.json; + cat test-artifacts/chrome/bundle_size_stats.json; echo "}"; } >> bundle_size_stats.temp.json else { echo "{"; echo "\"$CIRCLE_SHA1\":"; - cat test-artifacts/chrome/mv3/bundle_size_stats.json; + cat test-artifacts/chrome/bundle_size_stats.json; echo "}"; } > bundle_size_stats.temp.json fi diff --git a/.depcheckrc.yml b/.depcheckrc.yml index b4d28d081ec0..f2f9b37fd563 100644 --- a/.depcheckrc.yml +++ b/.depcheckrc.yml @@ -62,6 +62,8 @@ ignores: - 'jest-environment-jsdom' # babel - '@babel/plugin-transform-logical-assignment-operators' + # trezor + - 'ts-mixer' # files depcheck should not parse ignorePatterns: diff --git a/.metamaskrc.dist b/.metamaskrc.dist index e45a7740afc3..c7431cc0719b 100644 --- a/.metamaskrc.dist +++ b/.metamaskrc.dist @@ -21,7 +21,8 @@ BLOCKAID_PUBLIC_KEY= ; SELENIUM_HEADLESS= ; Set this to 1 to make chrome e2e tests disable DoH/DoT and use system DNS ; SELENIUM_USE_SYSTEM_DNS= - +; Set this to true to enable the snap path for the Firefox WebDriver (Linux) +; FIREFOX_SNAP= ENABLE_CONFIRMATION_REDESIGN= diff --git a/.vscode/package.json-schema.json b/.vscode/package.json-schema.json index 5f40b5f2f745..c377e274b3ce 100644 --- a/.vscode/package.json-schema.json +++ b/.vscode/package.json-schema.json @@ -32,6 +32,16 @@ "description": "Defines which dependencies' scripts are allowed to run upon install. If this setting is true, the scripts are allowed. If false, the scripts are not allowed. Run `yarn allow-scripts auto` to add a dependency to this list (defaults to false)." } } + }, + "resolutions": { + "type": "object", + "required": ["ts-mixer@npm:^6.0.3"], + "properties": { + "ts-mixer@npm:^6.0.3": { + "type": "string", + "description": "ts-mixer exports a `browser` field that points to the ESM version, but our build system can't process it. This resolution and patch file forces the CommonJS version to be used instead." + } + } } } } diff --git a/.yarn/patches/@metamask-snaps-controllers-npm-8.0.0-7e59688855.patch b/.yarn/patches/@metamask-snaps-controllers-npm-8.0.0-7e59688855.patch new file mode 100644 index 000000000000..4d05672ddaf8 --- /dev/null +++ b/.yarn/patches/@metamask-snaps-controllers-npm-8.0.0-7e59688855.patch @@ -0,0 +1,206 @@ +diff --git a/dist/chunk-IRVUYBSV.mjs b/dist/chunk-IRVUYBSV.mjs +index 499574e7ffa925674ce2685f00742e15007df102..7926d9f03d5e6711944d0620dae9fc625c9fc2ab 100644 +--- a/dist/chunk-IRVUYBSV.mjs ++++ b/dist/chunk-IRVUYBSV.mjs +@@ -31,14 +31,15 @@ import { + import { nanoid } from "nanoid"; + import { pipeline } from "readable-stream"; + var controllerName = "ExecutionService"; +-var _snapRpcHooks, _snapToJobMap, _jobToSnapMap, _messenger, _initTimeout, _pingTimeout, _terminationTimeout, _removeSnapHooks, removeSnapHooks_fn, _createSnapHooks, createSnapHooks_fn, _mapSnapAndJob, mapSnapAndJob_fn, _removeSnapAndJobMapping, removeSnapAndJobMapping_fn; ++var _snapRpcHooks, _snapToJobMap, _jobToSnapMap, _messenger, _initTimeout, _pingTimeout, _terminationTimeout, _usePing, _removeSnapHooks, removeSnapHooks_fn, _createSnapHooks, createSnapHooks_fn, _mapSnapAndJob, mapSnapAndJob_fn, _removeSnapAndJobMapping, removeSnapAndJobMapping_fn; + var AbstractExecutionService = class { + constructor({ + setupSnapProvider, + messenger, + initTimeout = inMilliseconds(60, Duration.Second), + pingTimeout = inMilliseconds(2, Duration.Second), +- terminationTimeout = inMilliseconds(1, Duration.Second) ++ terminationTimeout = inMilliseconds(1, Duration.Second), ++ usePing = true + }) { + __privateAdd(this, _removeSnapHooks); + __privateAdd(this, _createSnapHooks); +@@ -51,6 +52,7 @@ var AbstractExecutionService = class { + __privateAdd(this, _initTimeout, void 0); + __privateAdd(this, _pingTimeout, void 0); + __privateAdd(this, _terminationTimeout, void 0); ++ __privateAdd(this, _usePing, void 0); + __privateSet(this, _snapRpcHooks, /* @__PURE__ */ new Map()); + this.jobs = /* @__PURE__ */ new Map(); + this.setupSnapProvider = setupSnapProvider; +@@ -60,6 +62,7 @@ var AbstractExecutionService = class { + __privateSet(this, _initTimeout, initTimeout); + __privateSet(this, _pingTimeout, pingTimeout); + __privateSet(this, _terminationTimeout, terminationTimeout); ++ __privateSet(this, _usePing, usePing); + this.registerMessageHandlers(); + } + /** +@@ -268,16 +271,18 @@ var AbstractExecutionService = class { + const timer = new Timer(__privateGet(this, _initTimeout)); + const job = await this.initJob(jobId, timer); + __privateMethod(this, _mapSnapAndJob, mapSnapAndJob_fn).call(this, snapId, job.id); +- const pingResult = await withTimeout( +- this.command(job.id, { +- jsonrpc: "2.0", +- method: "ping", +- id: nanoid() +- }), +- __privateGet(this, _pingTimeout) +- ); +- if (pingResult === hasTimedOut) { +- throw new Error("The Snaps execution environment failed to start."); ++ if (__privateGet(this, _usePing)) { ++ const pingResult = await withTimeout( ++ this.command(job.id, { ++ jsonrpc: "2.0", ++ method: "ping", ++ id: nanoid() ++ }), ++ __privateGet(this, _pingTimeout) ++ ); ++ if (pingResult === hasTimedOut) { ++ throw new Error("The Snaps execution environment failed to start."); ++ } + } + const rpcStream = job.streams.rpc; + this.setupSnapProvider(snapId, rpcStream); +@@ -341,6 +346,7 @@ _messenger = new WeakMap(); + _initTimeout = new WeakMap(); + _pingTimeout = new WeakMap(); + _terminationTimeout = new WeakMap(); ++_usePing = new WeakMap(); + _removeSnapHooks = new WeakSet(); + removeSnapHooks_fn = function(snapId) { + __privateGet(this, _snapRpcHooks).delete(snapId); +diff --git a/dist/chunk-JZCXZEFV.js b/dist/chunk-JZCXZEFV.js +index 1ace9d2de3101797650c7d1be19c184b782b4163..9142d804a6acbe2bb0d961c5a5bb1e633f377a92 100644 +--- a/dist/chunk-JZCXZEFV.js ++++ b/dist/chunk-JZCXZEFV.js +@@ -31,14 +31,15 @@ var _utils = require('@metamask/utils'); + var _nanoid = require('nanoid'); + var _readablestream = require('readable-stream'); + var controllerName = "ExecutionService"; +-var _snapRpcHooks, _snapToJobMap, _jobToSnapMap, _messenger, _initTimeout, _pingTimeout, _terminationTimeout, _removeSnapHooks, removeSnapHooks_fn, _createSnapHooks, createSnapHooks_fn, _mapSnapAndJob, mapSnapAndJob_fn, _removeSnapAndJobMapping, removeSnapAndJobMapping_fn; ++var _snapRpcHooks, _snapToJobMap, _jobToSnapMap, _messenger, _initTimeout, _pingTimeout, _terminationTimeout, _usePing, _removeSnapHooks, removeSnapHooks_fn, _createSnapHooks, createSnapHooks_fn, _mapSnapAndJob, mapSnapAndJob_fn, _removeSnapAndJobMapping, removeSnapAndJobMapping_fn; + var AbstractExecutionService = class { + constructor({ + setupSnapProvider, + messenger, + initTimeout = _utils.inMilliseconds.call(void 0, 60, _utils.Duration.Second), + pingTimeout = _utils.inMilliseconds.call(void 0, 2, _utils.Duration.Second), +- terminationTimeout = _utils.inMilliseconds.call(void 0, 1, _utils.Duration.Second) ++ terminationTimeout = _utils.inMilliseconds.call(void 0, 1, _utils.Duration.Second), ++ usePing = true + }) { + _chunkEXN2TFDJjs.__privateAdd.call(void 0, this, _removeSnapHooks); + _chunkEXN2TFDJjs.__privateAdd.call(void 0, this, _createSnapHooks); +@@ -51,6 +52,7 @@ var AbstractExecutionService = class { + _chunkEXN2TFDJjs.__privateAdd.call(void 0, this, _initTimeout, void 0); + _chunkEXN2TFDJjs.__privateAdd.call(void 0, this, _pingTimeout, void 0); + _chunkEXN2TFDJjs.__privateAdd.call(void 0, this, _terminationTimeout, void 0); ++ _chunkEXN2TFDJjs.__privateAdd.call(void 0, this, _usePing, void 0); + _chunkEXN2TFDJjs.__privateSet.call(void 0, this, _snapRpcHooks, /* @__PURE__ */ new Map()); + this.jobs = /* @__PURE__ */ new Map(); + this.setupSnapProvider = setupSnapProvider; +@@ -60,6 +62,7 @@ var AbstractExecutionService = class { + _chunkEXN2TFDJjs.__privateSet.call(void 0, this, _initTimeout, initTimeout); + _chunkEXN2TFDJjs.__privateSet.call(void 0, this, _pingTimeout, pingTimeout); + _chunkEXN2TFDJjs.__privateSet.call(void 0, this, _terminationTimeout, terminationTimeout); ++ _chunkEXN2TFDJjs.__privateSet.call(void 0, this, _usePing, usePing); + this.registerMessageHandlers(); + } + /** +@@ -268,16 +271,18 @@ var AbstractExecutionService = class { + const timer = new (0, _chunkBOFPNIRXjs.Timer)(_chunkEXN2TFDJjs.__privateGet.call(void 0, this, _initTimeout)); + const job = await this.initJob(jobId, timer); + _chunkEXN2TFDJjs.__privateMethod.call(void 0, this, _mapSnapAndJob, mapSnapAndJob_fn).call(this, snapId, job.id); +- const pingResult = await _chunk4DPX4O3Tjs.withTimeout.call(void 0, +- this.command(job.id, { +- jsonrpc: "2.0", +- method: "ping", +- id: _nanoid.nanoid.call(void 0, ) +- }), +- _chunkEXN2TFDJjs.__privateGet.call(void 0, this, _pingTimeout) +- ); +- if (pingResult === _chunk4DPX4O3Tjs.hasTimedOut) { +- throw new Error("The Snaps execution environment failed to start."); ++ if (_chunkEXN2TFDJjs.__privateGet.call(void 0, this, _usePing)) { ++ const pingResult = await _chunk4DPX4O3Tjs.withTimeout.call(void 0, ++ this.command(job.id, { ++ jsonrpc: "2.0", ++ method: "ping", ++ id: _nanoid.nanoid.call(void 0, ) ++ }), ++ _chunkEXN2TFDJjs.__privateGet.call(void 0, this, _pingTimeout) ++ ); ++ if (pingResult === _chunk4DPX4O3Tjs.hasTimedOut) { ++ throw new Error("The Snaps execution environment failed to start."); ++ } + } + const rpcStream = job.streams.rpc; + this.setupSnapProvider(snapId, rpcStream); +@@ -341,6 +346,7 @@ _messenger = new WeakMap(); + _initTimeout = new WeakMap(); + _pingTimeout = new WeakMap(); + _terminationTimeout = new WeakMap(); ++_usePing = new WeakMap(); + _removeSnapHooks = new WeakSet(); + removeSnapHooks_fn = function(snapId) { + _chunkEXN2TFDJjs.__privateGet.call(void 0, this, _snapRpcHooks).delete(snapId); +diff --git a/dist/chunk-M2NMZ4ER.js b/dist/chunk-M2NMZ4ER.js +index 5e56452ad179871bb8e9580437f53016c0094273..a634cc4a0b2012cf3af314bd3d72131e971046cd 100644 +--- a/dist/chunk-M2NMZ4ER.js ++++ b/dist/chunk-M2NMZ4ER.js +@@ -32,7 +32,8 @@ var ProxyExecutionService = class extends _chunkJZCXZEFVjs.AbstractExecutionServ + }) { + super({ + messenger, +- setupSnapProvider ++ setupSnapProvider, ++ usePing: false + }); + _chunkEXN2TFDJjs.__privateAdd.call(void 0, this, _stream, void 0); + _chunkEXN2TFDJjs.__privateSet.call(void 0, this, _stream, stream); +@@ -63,6 +64,13 @@ var ProxyExecutionService = class extends _chunkJZCXZEFVjs.AbstractExecutionServ + stream: _chunkEXN2TFDJjs.__privateGet.call(void 0, this, _stream), + jobId + }); ++ await new Promise((resolve) => { ++ stream.once("data", resolve); ++ stream.write({ ++ name: "command", ++ data: { jsonrpc: "2.0", method: "ping", id: _nanoid.nanoid.call(void 0, ) } ++ }); ++ }); + return { worker: jobId, stream }; + } + }; +diff --git a/dist/chunk-MB23XAVD.mjs b/dist/chunk-MB23XAVD.mjs +index 3a31a241bd689e480f2cf573d894e6aefc4dc672..6467cdf0893ff4b0f0a71b28f6b4d7603c5257eb 100644 +--- a/dist/chunk-MB23XAVD.mjs ++++ b/dist/chunk-MB23XAVD.mjs +@@ -32,7 +32,8 @@ var ProxyExecutionService = class extends AbstractExecutionService { + }) { + super({ + messenger, +- setupSnapProvider ++ setupSnapProvider, ++ usePing: false + }); + __privateAdd(this, _stream, void 0); + __privateSet(this, _stream, stream); +@@ -63,6 +64,13 @@ var ProxyExecutionService = class extends AbstractExecutionService { + stream: __privateGet(this, _stream), + jobId + }); ++ await new Promise((resolve) => { ++ stream.once("data", resolve); ++ stream.write({ ++ name: "command", ++ data: { jsonrpc: "2.0", method: "ping", id: nanoid() } ++ }); ++ }); + return { worker: jobId, stream }; + } + }; diff --git a/.yarn/patches/@trezor-connect-web-npm-9.2.2-a4de8e45fc.patch b/.yarn/patches/@trezor-connect-web-npm-9.2.2-a4de8e45fc.patch new file mode 100644 index 000000000000..3701a590648b --- /dev/null +++ b/.yarn/patches/@trezor-connect-web-npm-9.2.2-a4de8e45fc.patch @@ -0,0 +1,43 @@ +diff --git a/lib/index.js b/lib/index.js +index 82a21b70dc18597fdbb1c5bfdbebb10f615c723d..04edf4b3a8a5a2bebda8bf907225b502ab5c30ad 100644 +--- a/lib/index.js ++++ b/lib/index.js +@@ -109,7 +109,9 @@ const init = async (settings = {}) => { + _log.enabled = !!_settings.debug; + window.addEventListener('message', handleMessage); + window.addEventListener('unload', dispose); +- await iframe.init(_settings); ++ var modifiedSettings = Object.assign({}, _settings); ++ modifiedSettings.env = 'webextension'; ++ await iframe.init(modifiedSettings); + if (_settings.sharedLogger !== false) { + iframe.initIframeLogger(); + } +@@ -125,7 +127,9 @@ const call = async (params) => { + } + _popupManager.request(); + try { +- await init(_settings); ++ var modifiedSettings = Object.assign({}, _settings); ++ modifiedSettings.env = 'webextension'; ++ await init(modifiedSettings); + } + catch (error) { + if (_popupManager) { +diff --git a/lib/popup/index.js b/lib/popup/index.js +index 6948bdb73b381bb72fb8b87fe2006d0046b65acb..d8f80f77728a18a851da3f03a5ed956cb46fe8e6 100644 +--- a/lib/popup/index.js ++++ b/lib/popup/index.js +@@ -272,9 +272,11 @@ class PopupManager extends events_1.default { + this.popupPromise.resolve(); + } + (_b = this.iframeHandshakePromise) === null || _b === void 0 ? void 0 : _b.promise.then(payload => { ++ var modifiedSettings = Object.assign({}, this.settings); ++ modifiedSettings.env = 'webextension'; + this.channel.postMessage({ + type: events_2.POPUP.INIT, +- payload: Object.assign(Object.assign({}, payload), { settings: this.settings }), ++ payload: Object.assign(Object.assign({}, payload), { settings: modifiedSettings }), + }); + }); + } diff --git a/.yarn/patches/@trezor-schema-utils-npm-1.0.1-70c2a68232.patch b/.yarn/patches/@trezor-schema-utils-npm-1.0.2-7dd48689b2.patch similarity index 86% rename from .yarn/patches/@trezor-schema-utils-npm-1.0.1-70c2a68232.patch rename to .yarn/patches/@trezor-schema-utils-npm-1.0.2-7dd48689b2.patch index 10f0290df76f..cef9bab97809 100644 --- a/.yarn/patches/@trezor-schema-utils-npm-1.0.1-70c2a68232.patch +++ b/.yarn/patches/@trezor-schema-utils-npm-1.0.2-7dd48689b2.patch @@ -1,5 +1,5 @@ diff --git a/lib/index.js b/lib/index.js -index b7a810396d6c0dad839fc08f1e192f5df134879f..93cb54be0f33c77e9f801f1c23330eb0fc65bb38 100644 +index b7a810396d6c0dad839fc08f1e192f5df134879f..14c6b3afaf1ca1d2805bea6c136cf9e48881db33 100644 --- a/lib/index.js +++ b/lib/index.js @@ -4,7 +4,7 @@ exports.Optional = exports.Type = exports.AssertWeak = exports.Assert = exports. diff --git a/.yarn/patches/ts-mixer-npm-6.0.4-5d9747bdf5.patch b/.yarn/patches/ts-mixer-npm-6.0.4-5d9747bdf5.patch new file mode 100644 index 000000000000..5c6f1730c141 --- /dev/null +++ b/.yarn/patches/ts-mixer-npm-6.0.4-5d9747bdf5.patch @@ -0,0 +1,12 @@ +diff --git a/package.json b/package.json +index 0f83178c00d4168f35241589d2e77c8a874ed4bd..f0ff8911f5366f9cb05c64e73fe38c4b3e9a74a7 100644 +--- a/package.json ++++ b/package.json +@@ -4,7 +4,6 @@ + "description": "A very small TypeScript library that provides tolerable Mixin functionality.", + "main": "dist/cjs/index.js", + "module": "dist/esm/index.js", +- "browser": "dist/esm/index.js", + "unpkg": "dist/esm/index.min.js", + "types": "dist/types/index.d.ts", + "files": [ diff --git a/CHANGELOG.md b/CHANGELOG.md index 6452f6a4c4cd..342cc2db03a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [11.16.6] +### Added +- Add a Basic Functionality Toggle to settings, enabling users to opt-out of some features that send network requests to external services ([#23456](https://github.com/MetaMask/metamask-extension/pull/23456)) + +### Changed +- Update MetaMask Chrome builds to Manifest V3 ([#24746](https://github.com/MetaMask/metamask-extension/pull/24746)) + +### Fixed +- Ensure network requests are not made during onboarding + - ([#24890](https://github.com/MetaMask/metamask-extension/pull/24890)) + - ([#24891](https://github.com/MetaMask/metamask-extension/pull/24891)) + - ([#24887](https://github.com/MetaMask/metamask-extension/pull/24887)) + ## [11.16.5] ### Changed - Re-enable the opt-in modal (2df1eb566b) @@ -4768,7 +4781,8 @@ Update styles and spacing on the critical error page ([#20350](https://github.c - Added the ability to restore accounts from seed words. -[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v11.16.5...HEAD +[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v11.16.6...HEAD +[11.16.6]: https://github.com/MetaMask/metamask-extension/compare/v11.16.5...v11.16.6 [11.16.5]: https://github.com/MetaMask/metamask-extension/compare/v11.16.4...v11.16.5 [11.16.4]: https://github.com/MetaMask/metamask-extension/compare/v11.16.3...v11.16.4 [11.16.3]: https://github.com/MetaMask/metamask-extension/compare/v11.16.2...v11.16.3 diff --git a/README.md b/README.md index 41f53ba99d53..c6b4cec0fb51 100644 --- a/README.md +++ b/README.md @@ -120,11 +120,11 @@ Before running e2e tests, ensure you've run `yarn install` to download dependenc - `yarn build:test` for main build - `yarn build:test:flask` for flask build - `yarn build:test:mmi` for mmi build - - `yarn build:test:mv3` for mv3 build + - `yarn build:test:mv2` for mv2 build 3. Start a test build with live changes: `yarn start:test` is particularly useful for development. It starts a test build that automatically recompiles application code upon changes. This option is ideal for iterative testing and development. This command also allows you to generate test builds for various types, including: - `yarn start:test` for main build - `yarn start:test:flask` for flask build - - `yarn start:test:mv3` for mv3 build + - `yarn start:test:mv2` for mv2 build Note: The `yarn start:test` command (which initiates the testDev build type) has LavaMoat disabled for both the build system and the application, offering a streamlined testing experience during development. On the other hand, `yarn build:test` enables LavaMoat for enhanced security in both the build system and application, mirroring production environments more closely. @@ -133,6 +133,7 @@ Note: The `yarn start:test` command (which initiates the testDev build type) has Once you have your test build ready, choose the browser for your e2e tests: - For Firefox, run `yarn test:e2e:firefox`. + - Note: If you are running Firefox as a snap package on Linux, ensure you enable the appropriate environment variable: `FIREFOX_SNAP=true yarn test:e2e:firefox` - For Chrome, run `yarn test:e2e:chrome`. These scripts support additional options for debugging. Use `--help`to see all available options. @@ -179,7 +180,7 @@ Different build types have different e2e tests sets. In order to run them look i ```console "test:e2e:chrome:mmi": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --mmi", "test:e2e:chrome:snaps": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --snaps", - "test:e2e:chrome:mv3": "ENABLE_MV3=true SELENIUM_BROWSER=chrome node test/e2e/run-all.js", + "test:e2e:firefox": "ENABLE_MV3=false SELENIUM_BROWSER=firefox node test/e2e/run-all.js", ``` #### Note: Running MMI e2e tests diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 66df11979872..350195769da2 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -556,6 +556,34 @@ "basic": { "message": "Basic" }, + "basicConfigurationBannerCTA": { + "message": "Turn on basic functionality" + }, + "basicConfigurationBannerTitle": { + "message": "Basic functionality is off" + }, + "basicConfigurationDescription": { + "message": "MetaMask offers basic features like token details and gas settings through internet services. When you use internet services, your IP address is shared, in this case with MetaMask. This is just like when you visit any website. MetaMask uses this data temporarily and never sells your data. You can use a VPN or turn off these services, but it may affect your MetaMask experience. To learn more read our $1.", + "description": "$1 is to be replaced by the message for privacyMsg, and will link to https://consensys.io/privacy-policy" + }, + "basicConfigurationLabel": { + "message": "Basic functionality" + }, + "basicConfigurationModalCheckbox": { + "message": "I understand and want to continue" + }, + "basicConfigurationModalDisclaimerOff": { + "message": "This means you won't fully optimize your time on MetaMask. Basic features (like token details, optimal gas settings, and others) won't be available to you." + }, + "basicConfigurationModalDisclaimerOn": { + "message": "To optimize your time on MetaMask, you’ll need to turn on this feature. Basic functions (like token details, optimal gas settings, and others) are important to the web3 experience." + }, + "basicConfigurationModalHeadingOff": { + "message": "Turn off basic functionality" + }, + "basicConfigurationModalHeadingOn": { + "message": "Turn on basic functionality" + }, "beCareful": { "message": "Be careful" }, @@ -1376,6 +1404,9 @@ "developerOptionsResetStatesOnboarding": { "message": "Resets various states related to onboarding and redirects to the \"Secure Your Wallet\" onboarding page." }, + "developerOptionsServiceWorkerKeepAlive": { + "message": "Results in a timestamp being continuously saved to session.storage" + }, "disabledGasOptionToolTipMessage": { "message": "“$1” is disabled because it does not meet the minimum of a 10% increase from the original gas fee.", "description": "$1 is gas estimate type which can be market or aggressive" @@ -4288,6 +4319,9 @@ "sepolia": { "message": "Sepolia test network" }, + "serviceWorkerKeepAlive": { + "message": "Service Worker Keep Alive" + }, "setAdvancedPrivacySettingsDetails": { "message": "MetaMask uses these trusted third-party services to enhance product usability and safety." }, @@ -5700,6 +5734,12 @@ "tryAgain": { "message": "Try again" }, + "turnOff": { + "message": "Turn off" + }, + "turnOn": { + "message": "Turn on" + }, "turnOnTokenDetection": { "message": "Turn on enhanced token detection" }, diff --git a/app/scripts/app-init.js b/app/scripts/app-init.js index 9e77a509b9c5..754346ecf2a0 100644 --- a/app/scripts/app-init.js +++ b/app/scripts/app-init.js @@ -132,6 +132,21 @@ chrome.runtime.onMessage.addListener(() => { return false; }); +/* + * If the service worker is stopped and restarted, then the 'install' event will not occur + * and the chrome.runtime.onMessage will only occur if it was a message that restarted the + * the service worker. To ensure that importAllScripts is called, we need to call it in module + * scope as below. To avoid having `importAllScripts()` called before installation, we only + * call it if the serviceWorker state is 'activated'. More on service worker states here: + * https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/state. Testing also shows + * that whenever the already installed service worker is stopped and then restarted, the state + * is 'activated'. + */ +// eslint-disable-next-line no-undef +if (self.serviceWorker.state === 'activated') { + importAllScripts(); +} + /* * This content script is injected programmatically because * MAIN world injection does not work properly via manifest @@ -146,6 +161,7 @@ const registerInPageContentScript = async () => { js: ['scripts/inpage.js'], runAt: 'document_start', world: 'MAIN', + allFrames: true, }, ]); } catch (err) { diff --git a/app/scripts/background.js b/app/scripts/background.js index 5e3f56ee001d..eaf304ba964d 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -287,13 +287,16 @@ async function initialize() { ///: END:ONLY_INCLUDE_IF let isFirstMetaMaskControllerSetup; + if (isManifestV3) { // Save the timestamp immediately and then every `SAVE_TIMESTAMP_INTERVAL` // miliseconds. This keeps the service worker alive. - const SAVE_TIMESTAMP_INTERVAL_MS = 2 * 1000; + if (initState.PreferencesController?.enableMV3TimestampSave !== false) { + const SAVE_TIMESTAMP_INTERVAL_MS = 2 * 1000; - saveTimestamp(); - setInterval(saveTimestamp, SAVE_TIMESTAMP_INTERVAL_MS); + saveTimestamp(); + setInterval(saveTimestamp, SAVE_TIMESTAMP_INTERVAL_MS); + } const sessionData = await browser.storage.session.get([ 'isFirstMetaMaskControllerSetup', @@ -974,7 +977,7 @@ const addAppInstalledEvent = () => { setTimeout(() => { // If the controller is not set yet, we wait and try to add the "App Installed" event again. addAppInstalledEvent(); - }, 1000); + }, 500); }; // On first install, open a new tab with MetaMask diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 8264d8105dde..c255851389c0 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -114,6 +114,11 @@ export default class PreferencesController { ///: END:ONLY_INCLUDE_IF useExternalNameSources: true, useTransactionSimulations: true, + enableMV3TimestampSave: true, + // Turning OFF basic functionality toggle means turning OFF this useExternalServices flag. + // Whenever useExternalServices is false, certain features will be disabled. + // The flag is true by Default, meaning the toggle is ON by default. + useExternalServices: true, ...opts.initState, }; @@ -212,6 +217,16 @@ export default class PreferencesController { this.store.updateState({ useSafeChainsListValidation: val }); } + toggleExternalServices(useExternalServices) { + this.store.updateState({ useExternalServices }); + this.setUseTokenDetection(useExternalServices); + this.setUseCurrencyRateCheck(useExternalServices); + this.setUsePhishDetect(useExternalServices); + this.setUseAddressBarEnsResolution(useExternalServices); + this.setOpenSeaEnabled(useExternalServices); + this.setUseNftDetection(useExternalServices); + } + /** * Setter for the `useTokenDetection` property * @@ -675,6 +690,10 @@ export default class PreferencesController { this.store.updateState({ incomingTransactionsPreferences: updatedValue }); } + setServiceWorkerKeepAlivePreference(value) { + this.store.updateState({ enableMV3TimestampSave: value }); + } + getRpcMethodPreferences() { return this.store.getState().disabledRpcMethodPreferences; } diff --git a/app/scripts/controllers/preferences.test.js b/app/scripts/controllers/preferences.test.js index 80ee7a3cf86e..14912b9294ff 100644 --- a/app/scripts/controllers/preferences.test.js +++ b/app/scripts/controllers/preferences.test.js @@ -452,4 +452,19 @@ describe('preferences controller', () => { ).toStrictEqual(false); }); }); + + describe('setServiceWorkerKeepAlivePreference', () => { + it('should default to true', () => { + expect( + preferencesController.store.getState().enableMV3TimestampSave, + ).toStrictEqual(true); + }); + + it('should set the setServiceWorkerKeepAlivePreference property in state', () => { + preferencesController.setServiceWorkerKeepAlivePreference(false); + expect( + preferencesController.store.getState().enableMV3TimestampSave, + ).toStrictEqual(false); + }); + }); }); diff --git a/app/scripts/lib/createDupeReqFilterMiddleware.js b/app/scripts/lib/createDupeReqFilterMiddleware.js deleted file mode 100644 index 556e71c18517..000000000000 --- a/app/scripts/lib/createDupeReqFilterMiddleware.js +++ /dev/null @@ -1,23 +0,0 @@ -import log from 'loglevel'; - -/** - * Returns a middleware that filters out requests already seen - * - * @returns {Function} - */ -export default function createDupeReqFilterMiddleware() { - const processedRequestId = []; - return function filterDuplicateRequestMiddleware( - /** @type {any} */ req, - /** @type {any} */ _res, - /** @type {Function} */ next, - /** @type {Function} */ end, - ) { - if (processedRequestId.indexOf(req.id) >= 0) { - log.info(`RPC request with id ${req.id} already seen.`); - return end(); - } - processedRequestId.push(req.id); - return next(); - }; -} diff --git a/app/scripts/lib/createDupeReqFilterMiddleware.test.js b/app/scripts/lib/createDupeReqFilterMiddleware.test.js deleted file mode 100644 index 503f383d9ed1..000000000000 --- a/app/scripts/lib/createDupeReqFilterMiddleware.test.js +++ /dev/null @@ -1,30 +0,0 @@ -import createDupeReqFilterMiddleware from './createDupeReqFilterMiddleware'; - -describe('createDupeReqFilterMiddleware', () => { - it('call function next if request is seen first time', () => { - const filterFn = createDupeReqFilterMiddleware(); - const request = { id: 1 }; - const nextMock = jest.fn(); - const endMock = jest.fn(); - - filterFn(request, undefined, nextMock, endMock); - - expect(nextMock).toHaveBeenCalledTimes(1); - expect(endMock).not.toHaveBeenCalled(); - }); - - it('call function end if request is seen second time', () => { - const filterFn = createDupeReqFilterMiddleware(); - const request = { id: 1 }; - const nextMock = jest.fn(); - const endMock = jest.fn(); - - filterFn(request, undefined, nextMock, endMock); - expect(nextMock).toHaveBeenCalledTimes(1); - expect(endMock).not.toHaveBeenCalled(); - - filterFn(request, undefined, nextMock, endMock); - expect(nextMock).toHaveBeenCalledTimes(1); - expect(endMock).toHaveBeenCalledTimes(1); - }); -}); diff --git a/app/scripts/lib/createDupeReqFilterStream.test.ts b/app/scripts/lib/createDupeReqFilterStream.test.ts new file mode 100644 index 000000000000..1486ac7d1325 --- /dev/null +++ b/app/scripts/lib/createDupeReqFilterStream.test.ts @@ -0,0 +1,388 @@ +import NodeStream from 'node:stream'; +import OurReadableStream from 'readable-stream'; + +import type { JsonRpcRequest } from '@metamask/utils'; +import createDupeReqFilterStream, { + THREE_MINUTES, +} from './createDupeReqFilterStream'; + +const { Transform } = OurReadableStream; + +function createTestStream(output: JsonRpcRequest[] = [], S = Transform) { + const transformStream = createDupeReqFilterStream(); + const testOutStream = new S({ + transform: (chunk: JsonRpcRequest, _, cb) => { + output.push(chunk); + cb(); + }, + objectMode: true, + }); + + transformStream.pipe(testOutStream); + + return transformStream; +} + +function runStreamTest( + requests: JsonRpcRequest[] = [], + advanceTimersTime = 10, + S = Transform, +) { + return new Promise((resolve, reject) => { + const output: JsonRpcRequest[] = []; + const testStream = createTestStream(output, S); + + testStream + .on('finish', () => resolve(output)) + .on('error', (err) => reject(err)); + + requests.forEach((request) => testStream.write(request)); + testStream.end(); + + jest.advanceTimersByTime(advanceTimersTime); + }); +} + +describe('createDupeReqFilterStream', () => { + beforeEach(() => { + jest.useFakeTimers({ now: 10 }); + }); + + it('lets through requests with ids being seen for the first time', async () => { + const requests = [ + { id: 1, method: 'foo' }, + { id: 2, method: 'bar' }, + ]; + + const expectedOutput = [ + { id: 1, method: 'foo' }, + { id: 2, method: 'bar' }, + ]; + + const output = await runStreamTest(requests); + expect(output).toEqual(expectedOutput); + }); + + it('does not let through the request if the id has been seen before', async () => { + const requests = [ + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, // duplicate + ]; + + const expectedOutput = [{ id: 1, method: 'foo' }]; + + const output = await runStreamTest(requests); + expect(output).toEqual(expectedOutput); + }); + + it("lets through requests if they don't have an id", async () => { + const requests = [{ method: 'notify1' }, { method: 'notify2' }]; + + const expectedOutput = [{ method: 'notify1' }, { method: 'notify2' }]; + + const output = await runStreamTest(requests); + expect(output).toEqual(expectedOutput); + }); + + it('handles a mix of request types', async () => { + const requests = [ + { id: 1, method: 'foo' }, + { method: 'notify1' }, + { id: 1, method: 'foo' }, + { id: 2, method: 'bar' }, + { method: 'notify2' }, + { id: 2, method: 'bar' }, + { id: 3, method: 'baz' }, + ]; + + const expectedOutput = [ + { id: 1, method: 'foo' }, + { method: 'notify1' }, + { id: 2, method: 'bar' }, + { method: 'notify2' }, + { id: 3, method: 'baz' }, + ]; + + const output = await runStreamTest(requests); + expect(output).toEqual(expectedOutput); + }); + + it('expires single id after three minutes', () => { + const output: JsonRpcRequest[] = []; + const testStream = createTestStream(output); + + const requests1 = [ + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + ]; + const expectedOutputBeforeExpiryTime = [{ id: 1, method: 'foo' }]; + + requests1.forEach((request) => testStream.write(request)); + expect(output).toEqual(expectedOutputBeforeExpiryTime); + + const requests2 = [ + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + ]; + const expectedOutputAfterExpiryTime = [ + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + ]; + + jest.advanceTimersByTime(THREE_MINUTES); + + requests2.forEach((request) => testStream.write(request)); + expect(output).toEqual(expectedOutputAfterExpiryTime); + }); + + it('does not expire single id after less than three', () => { + const output: JsonRpcRequest[] = []; + const testStream = createTestStream(output); + + const requests1 = [ + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + ]; + const expectedOutputBeforeTimeElapses = [{ id: 1, method: 'foo' }]; + + requests1.forEach((request) => testStream.write(request)); + expect(output).toEqual(expectedOutputBeforeTimeElapses); + + const requests2 = [ + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + ]; + const expectedOutputAfterTimeElapses = expectedOutputBeforeTimeElapses; + + jest.advanceTimersByTime(THREE_MINUTES - 1); + + requests2.forEach((request) => testStream.write(request)); + expect(output).toEqual(expectedOutputAfterTimeElapses); + }); + + it('expires multiple ids after three minutes', () => { + const output: JsonRpcRequest[] = []; + const testStream = createTestStream(output); + + const requests1 = [ + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + { id: 2, method: 'bar' }, + { id: 2, method: 'bar' }, + { id: 3, method: 'baz' }, + { id: 3, method: 'baz' }, + ]; + const expectedOutputBeforeExpiryTime = [ + { id: 1, method: 'foo' }, + { id: 2, method: 'bar' }, + { id: 3, method: 'baz' }, + ]; + + requests1.forEach((request) => testStream.write(request)); + expect(output).toEqual(expectedOutputBeforeExpiryTime); + + const requests2 = [ + { id: 3, method: 'baz' }, + { id: 3, method: 'baz' }, + { id: 2, method: 'bar' }, + { id: 2, method: 'bar' }, + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + ]; + const expectedOutputAfterExpiryTime = [ + { id: 1, method: 'foo' }, + { id: 2, method: 'bar' }, + { id: 3, method: 'baz' }, + { id: 3, method: 'baz' }, + { id: 2, method: 'bar' }, + { id: 1, method: 'foo' }, + ]; + + jest.advanceTimersByTime(THREE_MINUTES); + + requests2.forEach((request) => testStream.write(request)); + expect(output).toEqual(expectedOutputAfterExpiryTime); + }); + + it('expires single id in three minute intervals', () => { + const output: JsonRpcRequest[] = []; + const testStream = createTestStream(output); + + const requests1 = [ + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + ]; + const expectedOutputBeforeExpiryTime = [{ id: 1, method: 'foo' }]; + + requests1.forEach((request) => testStream.write(request)); + expect(output).toEqual(expectedOutputBeforeExpiryTime); + + const requests2 = [ + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + ]; + const expectedOutputAfterFirstExpiryTime = [ + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + ]; + + jest.advanceTimersByTime(THREE_MINUTES); + + requests2.forEach((request) => testStream.write(request)); + expect(output).toEqual(expectedOutputAfterFirstExpiryTime); + + const requests3 = [ + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + ]; + const expectedOutputAfterSecondExpiryTime = [ + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + { id: 1, method: 'foo' }, + ]; + + jest.advanceTimersByTime(THREE_MINUTES); + + requests3.forEach((request) => testStream.write(request)); + expect(output).toEqual(expectedOutputAfterSecondExpiryTime); + }); + + it('expires somes ids at intervals while not expiring others', () => { + const output: JsonRpcRequest[] = []; + const testStream = createTestStream(output); + + const requests1 = [ + { id: 1, method: 'foo' }, + { id: 2, method: 'bar' }, + ]; + const expectedOutputBeforeExpiryTime = [ + { id: 1, method: 'foo' }, + { id: 2, method: 'bar' }, + ]; + + requests1.forEach((request) => testStream.write(request)); + expect(output).toEqual(expectedOutputBeforeExpiryTime); + + const requests2 = [{ id: 3, method: 'baz' }]; + const expectedOutputAfterFirstExpiryTime = [ + { id: 1, method: 'foo' }, + { id: 2, method: 'bar' }, + { id: 3, method: 'baz' }, + ]; + + jest.advanceTimersByTime(THREE_MINUTES - 1); + + requests2.forEach((request) => testStream.write(request)); + expect(output).toEqual(expectedOutputAfterFirstExpiryTime); + + const requests3 = [ + { id: 1, method: 'foo' }, + { id: 2, method: 'bar' }, + { id: 3, method: 'baz' }, + { id: 4, method: 'buzz' }, + ]; + const expectedOutputAfterSecondExpiryTime = [ + { id: 1, method: 'foo' }, + { id: 2, method: 'bar' }, + { id: 3, method: 'baz' }, + { id: 1, method: 'foo' }, + { id: 2, method: 'bar' }, + { id: 4, method: 'buzz' }, + ]; + + jest.advanceTimersByTime(THREE_MINUTES - 1); + + requests3.forEach((request) => testStream.write(request)); + expect(output).toEqual(expectedOutputAfterSecondExpiryTime); + }); + + it('handles running expiry job without seeing any ids', () => { + const output: JsonRpcRequest[] = []; + const testStream = createTestStream(output); + + const requests1 = [{ id: 1, method: 'foo' }]; + const expectedOutputBeforeExpiryTime = [{ id: 1, method: 'foo' }]; + + requests1.forEach((request) => testStream.write(request)); + expect(output).toEqual(expectedOutputBeforeExpiryTime); + + jest.advanceTimersByTime(THREE_MINUTES + 1); + + expect(output).toEqual(expectedOutputBeforeExpiryTime); + }); + + [ + ['node:stream', NodeStream] as [string, typeof NodeStream], + // Redundantly include used version twice for regression-detection purposes + ['readable-stream', OurReadableStream] as [ + string, + typeof OurReadableStream, + ], + ].forEach(([name, streamsImpl]) => { + describe(`Using Streams implementation: ${name}`, () => { + [ + ['Duplex', streamsImpl.Duplex] as [string, typeof streamsImpl.Duplex], + ['Transform', streamsImpl.Transform] as [ + string, + typeof streamsImpl.Transform, + ], + ['Writable', streamsImpl.Writable] as [ + string, + typeof streamsImpl.Writable, + ], + ].forEach(([className, S]) => { + it(`handles a mix of request types coming through a ${className} stream`, async () => { + const requests = [ + { id: 1, method: 'foo' }, + { method: 'notify1' }, + { id: 1, method: 'foo' }, + { id: 2, method: 'bar' }, + { method: 'notify2' }, + { id: 2, method: 'bar' }, + { id: 3, method: 'baz' }, + ]; + + const expectedOutput = [ + { id: 1, method: 'foo' }, + { method: 'notify1' }, + { id: 2, method: 'bar' }, + { method: 'notify2' }, + { id: 3, method: 'baz' }, + ]; + + const output: JsonRpcRequest[] = []; + const testStream = createDupeReqFilterStream(); + const testOutStream = new S({ + transform: (chunk: JsonRpcRequest, _, cb) => { + output.push(chunk); + cb(); + }, + objectMode: true, + }); + + testOutStream._write = ( + chunk: JsonRpcRequest, + _: BufferEncoding, + callback: (error?: Error | null) => void, + ) => { + output.push(chunk); + callback(); + }; + + testStream.pipe(testOutStream); + + requests.forEach((request) => testStream.write(request)); + + expect(output).toEqual(expectedOutput); + }); + }); + }); + }); +}); diff --git a/app/scripts/lib/createDupeReqFilterStream.ts b/app/scripts/lib/createDupeReqFilterStream.ts new file mode 100644 index 000000000000..63d801e7f1e4 --- /dev/null +++ b/app/scripts/lib/createDupeReqFilterStream.ts @@ -0,0 +1,68 @@ +import { Transform } from 'readable-stream'; +import log from 'loglevel'; +import type { JsonRpcRequest } from '@metamask/utils'; +import { MINUTE } from '../../../shared/constants/time'; + +export const THREE_MINUTES = MINUTE * 3; + +/** + * Creates a set abstraction whose values expire after three minutes. + * + * @returns The expiry set. + */ +const makeExpirySet = () => { + const map: Map = new Map(); + + setInterval(() => { + const cutoffTime = Date.now() - THREE_MINUTES; + + for (const [id, timestamp] of map.entries()) { + if (timestamp <= cutoffTime) { + map.delete(id); + } else { + break; + } + } + }, THREE_MINUTES); + + return { + /** + * Attempts to add a value to the set. + * + * @param value - The value to add. + * @returns `true` if the value was added, and `false` if it already existed. + */ + add(value: string | number) { + if (!map.has(value)) { + map.set(value, Date.now()); + return true; + } + return false; + }, + }; +}; + +/** + * Returns a transform stream that filters out requests whose ids we've already seen. + * Ignores JSON-RPC notifications, i.e. requests with an `undefined` id. + * + * @returns The stream object. + */ +export default function createDupeReqFilterStream() { + const seenRequestIds = makeExpirySet(); + return new Transform({ + transform(chunk: JsonRpcRequest, _, cb) { + // JSON-RPC notifications have no ids; our only recourse is to let them through. + const hasNoId = chunk.id === undefined; + const requestNotYetSeen = seenRequestIds.add(chunk.id); + + if (hasNoId || requestNotYetSeen) { + cb(null, chunk); + } else { + log.debug(`RPC request with id "${chunk.id}" already seen.`); + cb(); + } + }, + objectMode: true, + }); +} diff --git a/app/scripts/lib/createMetaRPCHandler.js b/app/scripts/lib/createMetaRPCHandler.js index f55319783fea..d89156f8680c 100644 --- a/app/scripts/lib/createMetaRPCHandler.js +++ b/app/scripts/lib/createMetaRPCHandler.js @@ -1,7 +1,6 @@ import { ethErrors, serializeError } from 'eth-rpc-errors'; -import { isManifestV3 } from '../../../shared/modules/mv3.utils'; -const createMetaRPCHandler = (api, outStream, store, localStoreApiWrapper) => { +const createMetaRPCHandler = (api, outStream) => { return async (data) => { if (outStream._writableState.ended) { return; @@ -23,10 +22,6 @@ const createMetaRPCHandler = (api, outStream, store, localStoreApiWrapper) => { result = await api[data.method](...data.params); } catch (err) { error = err; - } finally { - if (isManifestV3 && store && data.method !== 'getState') { - localStoreApiWrapper.set(store.getState()); - } } if (outStream._writableState.ended) { diff --git a/app/scripts/lib/offscreen-bridge/ledger-offscreen-bridge.ts b/app/scripts/lib/offscreen-bridge/ledger-offscreen-bridge.ts index fd622fac3b8a..e17178a76b25 100644 --- a/app/scripts/lib/offscreen-bridge/ledger-offscreen-bridge.ts +++ b/app/scripts/lib/offscreen-bridge/ledger-offscreen-bridge.ts @@ -131,7 +131,7 @@ export class LedgerOffscreenBridge implements LedgerBridge { chrome.runtime.sendMessage( { target: OffscreenCommunicationTarget.ledgerOffscreen, - action: LedgerAction.signMessage, + action: LedgerAction.signPersonalMessage, params, }, (response) => { diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index b5d6accaeb06..c950019bf7cf 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -226,6 +226,7 @@ export const SENTRY_BACKGROUND_STATE = { useNativeCurrencyAsPrimaryCurrency: true, petnamesEnabled: true, }, + useExternalServices: false, selectedAddress: false, snapRegistryList: false, theme: true, @@ -241,6 +242,7 @@ export const SENTRY_BACKGROUND_STATE = { useTokenDetection: true, useRequestQueue: true, useTransactionSimulations: true, + enableMV3TimestampSave: true, hasDismissedOpenSeaToBlockaidBanner: true, }, SelectedNetworkController: { domains: false }, diff --git a/app/scripts/metamask-controller.actions.test.js b/app/scripts/metamask-controller.actions.test.js index 31dcd6d5bf36..b260d23dbbce 100644 --- a/app/scripts/metamask-controller.actions.test.js +++ b/app/scripts/metamask-controller.actions.test.js @@ -14,7 +14,7 @@ import { PermissionsRequestNotFoundError } from '@metamask/permission-controller import nock from 'nock'; import mockEncryptor from '../../test/lib/mock-encryptor'; -const { Ganache } = require('../../test/e2e/ganache'); +const { Ganache } = require('../../test/e2e/seeder/ganache'); const ganacheServer = new Ganache(); diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 8604758c519b..b421cb6a4844 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -266,7 +266,7 @@ import { mmiKeyringBuilderFactory } from './mmi-keyring-builder-factory'; ///: END:ONLY_INCLUDE_IF import ComposableObservableStore from './lib/ComposableObservableStore'; import AccountTracker from './lib/account-tracker'; -import createDupeReqFilterMiddleware from './lib/createDupeReqFilterMiddleware'; +import createDupeReqFilterStream from './lib/createDupeReqFilterStream'; import createLoggerMiddleware from './lib/createLoggerMiddleware'; import { createMethodMiddleware } from './lib/rpc-method-middleware'; import createOriginMiddleware from './lib/createOriginMiddleware'; @@ -800,6 +800,19 @@ export default class MetamaskController extends EventEmitter { messenger: currencyRateMessenger, state: initState.CurrencyController, }); + const initialFetchExchangeRate = + this.currencyRateController.fetchExchangeRate.bind( + this.currencyRateController, + ); + this.currencyRateController.fetchExchangeRate = (...args) => { + if (this.preferencesController.store.getState().useCurrencyRateCheck) { + return initialFetchExchangeRate(...args); + } + return { + conversionRate: null, + usdConversionRate: null, + }; + }; const phishingControllerMessenger = this.controllerMessenger.getRestricted({ name: 'PhishingController', @@ -1478,6 +1491,8 @@ export default class MetamaskController extends EventEmitter { if (!prevCompletedOnboarding && currCompletedOnboarding) { const { address } = this.accountsController.getSelectedAccount(); + this._addAccountsWithBalance(); + this.postOnboardingInitialization(); this.triggerNetworkrequests(); // execute once the token detection on the post-onboarding @@ -2967,6 +2982,10 @@ export default class MetamaskController extends EventEmitter { preferencesController.setIncomingTransactionsPreferences.bind( preferencesController, ), + setServiceWorkerKeepAlivePreference: + preferencesController.setServiceWorkerKeepAlivePreference.bind( + preferencesController, + ), markPasswordForgotten: this.markPasswordForgotten.bind(this), unMarkPasswordForgotten: this.unMarkPasswordForgotten.bind(this), getRequestAccountTabIds: this.getRequestAccountTabIds, @@ -3051,6 +3070,7 @@ export default class MetamaskController extends EventEmitter { throw new Error(`No account found for address: ${address}`); } }, + toggleExternalServices: this.toggleExternalServices.bind(this), addToken: tokensController.addToken.bind(tokensController), updateTokenType: tokensController.updateTokenType.bind(tokensController), setFeatureFlag: preferencesController.setFeatureFlag.bind( @@ -3753,6 +3773,9 @@ export default class MetamaskController extends EventEmitter { async createNewVaultAndRestore(password, encodedSeedPhrase) { const releaseLock = await this.createVaultMutex.acquire(); try { + const { completedOnboarding } = + this.onboardingController.store.getState(); + const seedPhraseAsBuffer = Buffer.from(encodedSeedPhrase); // clear known identities @@ -3773,7 +3796,9 @@ export default class MetamaskController extends EventEmitter { this.txController.clearUnapprovedTransactions(); - this.tokenDetectionController.enable(); + if (completedOnboarding) { + this.tokenDetectionController.enable(); + } // create new vault const vault = await this.keyringController.createNewVaultAndRestore( @@ -3781,49 +3806,16 @@ export default class MetamaskController extends EventEmitter { this._convertMnemonicToWordlistIndices(seedPhraseAsBuffer), ); - // Scan accounts until we find an empty one - const { chainId } = this.networkController.state.providerConfig; - const ethQuery = new EthQuery(this.provider); - const accounts = await this.keyringController.getAccounts(); - let address = accounts[accounts.length - 1]; - - for (let count = accounts.length; ; count++) { - const balance = await this.getBalance(address, ethQuery); + if (completedOnboarding) { + await this._addAccountsWithBalance(); - if (balance === '0x0') { - // This account has no balance, so check for tokens - await this.tokenDetectionController.detectTokens({ - selectedAddress: address, - }); - - const tokens = - this.tokensController.state.allTokens?.[chainId]?.[address]; - const detectedTokens = - this.tokensController.state.allDetectedTokens?.[chainId]?.[address]; - - if ( - (tokens?.length ?? 0) === 0 && - (detectedTokens?.length ?? 0) === 0 - ) { - // This account has no balance or tokens - if (count !== 1) { - await this.removeAccount(address); - } - break; - } - } - - // This account has assets, so check the next one - ({ addedAccountAddress: address } = - await this.keyringController.addNewAccount(count)); + // This must be set as soon as possible to communicate to the + // keyring's iframe and have the setting initialized properly + // Optimistically called to not block MetaMask login due to + // Ledger Keyring GitHub downtime + this.setLedgerTransportPreference(); } - // This must be set as soon as possible to communicate to the - // keyring's iframe and have the setting initialized properly - // Optimistically called to not block MetaMask login due to - // Ledger Keyring GitHub downtime - this.setLedgerTransportPreference(); - this.selectFirstAccount(); return vault; @@ -3832,6 +3824,45 @@ export default class MetamaskController extends EventEmitter { } } + async _addAccountsWithBalance() { + // Scan accounts until we find an empty one + const { chainId } = this.networkController.state.providerConfig; + const ethQuery = new EthQuery(this.provider); + const accounts = await this.keyringController.getAccounts(); + let address = accounts[accounts.length - 1]; + + for (let count = accounts.length; ; count++) { + const balance = await this.getBalance(address, ethQuery); + + if (balance === '0x0') { + // This account has no balance, so check for tokens + await this.tokenDetectionController.detectTokens({ + selectedAddress: address, + }); + + const tokens = + this.tokensController.state.allTokens?.[chainId]?.[address]; + const detectedTokens = + this.tokensController.state.allDetectedTokens?.[chainId]?.[address]; + + if ( + (tokens?.length ?? 0) === 0 && + (detectedTokens?.length ?? 0) === 0 + ) { + // This account has no balance or tokens + if (count !== 1) { + await this.removeAccount(address); + } + break; + } + } + + // This account has assets, so check the next one + ({ addedAccountAddress: address } = + await this.keyringController.addNewAccount(count)); + } + } + /** * Encodes a BIP-39 mnemonic as the indices of words in the English BIP-39 wordlist. * @@ -3893,6 +3924,7 @@ export default class MetamaskController extends EventEmitter { * @param {string} password - The user's password */ async submitPassword(password) { + const { completedOnboarding } = this.onboardingController.store.getState(); await this.keyringController.submitPassword(password); ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) @@ -3911,7 +3943,9 @@ export default class MetamaskController extends EventEmitter { // keyring's iframe and have the setting initialized properly // Optimistically called to not block MetaMask login due to // Ledger Keyring GitHub downtime - this.setLedgerTransportPreference(); + if (completedOnboarding) { + this.setLedgerTransportPreference(); + } } async _loginUser(password) { @@ -4083,6 +4117,10 @@ export default class MetamaskController extends EventEmitter { async connectHardware(deviceName, page, hdPath) { const keyring = await this.getKeyringForDevice(deviceName, hdPath); + if (deviceName === HardwareDeviceNames.ledger) { + await this.setLedgerTransportPreference(keyring); + } + let accounts = []; switch (page) { case -1: @@ -4610,6 +4648,7 @@ export default class MetamaskController extends EventEmitter { * @param {string} [options.subjectType] - The type of the sender, i.e. subject. */ setupUntrustedCommunication({ connectionStream, sender, subjectType }) { + const { completedOnboarding } = this.onboardingController.store.getState(); const { usePhishDetect } = this.preferencesController.store.getState(); let _subjectType; @@ -4621,12 +4660,12 @@ export default class MetamaskController extends EventEmitter { _subjectType = SubjectType.Website; } - if (sender.url) { + if (usePhishDetect && completedOnboarding && sender.url) { const { hostname } = new URL(sender.url); this.phishingController.maybeUpdateState(); // Check if new connection is blocked if phishing detection is on const phishingTestResponse = this.phishingController.test(hostname); - if (usePhishDetect && phishingTestResponse?.result) { + if (phishingTestResponse?.result) { this.sendPhishingWarning(connectionStream, hostname); this.metaMetricsController.trackEvent({ event: MetaMetricsEventName.PhishingPageDisplayed, @@ -4736,15 +4775,7 @@ export default class MetamaskController extends EventEmitter { this.emit('controllerConnectionChanged', this.activeControllerConnections); // set up postStream transport - outStream.on( - 'data', - createMetaRPCHandler( - api, - outStream, - this.store, - this.localStoreApiWrapper, - ), - ); + outStream.on('data', createMetaRPCHandler(api, outStream)); const handleUpdate = (update) => { if (outStream._writableState.ended) { return; @@ -4825,12 +4856,14 @@ export default class MetamaskController extends EventEmitter { tabId, }); + const dupeReqFilterStream = createDupeReqFilterStream(); + // setup connection const providerStream = createEngineStream({ engine }); const connectionId = this.addConnection(origin, { engine }); - pump(outStream, providerStream, outStream, (err) => { + pump(outStream, dupeReqFilterStream, providerStream, outStream, (err) => { // handle any middleware cleanup engine._middleware.forEach((mid) => { if (mid.destroy && typeof mid.destroy === 'function') { @@ -4908,10 +4941,6 @@ export default class MetamaskController extends EventEmitter { engine.emit('notification', message), ); - if (isManifestV3) { - engine.push(createDupeReqFilterMiddleware()); - } - // append tabId to each request if it exists if (tabId) { engine.push(createTabIdMiddleware({ tabId })); @@ -5632,6 +5661,18 @@ export default class MetamaskController extends EventEmitter { }; } + toggleExternalServices(useExternal) { + this.preferencesController.toggleExternalServices(useExternal); + this.tokenListController.updatePreventPollingOnNetworkRestart(!useExternal); + if (useExternal) { + this.tokenDetectionController.enable(); + this.gasFeeController.enableNonRPCGasFeeApis(); + } else { + this.tokenDetectionController.disable(); + this.gasFeeController.disableNonRPCGasFeeApis(); + } + } + //============================================================================= // CONFIG //============================================================================= @@ -5659,14 +5700,16 @@ export default class MetamaskController extends EventEmitter { /** * Sets the Ledger Live preference to use for Ledger hardware wallet support * + * @param _keyring * @deprecated This method is deprecated and will be removed in the future. * Only webhid connections are supported in chrome and u2f in firefox. */ - async setLedgerTransportPreference() { + async setLedgerTransportPreference(_keyring) { const transportType = window.navigator.hid ? LedgerTransportTypes.webhid : LedgerTransportTypes.u2f; - const keyring = await this.getKeyringForDevice(HardwareDeviceNames.ledger); + const keyring = + _keyring || (await this.getKeyringForDevice(HardwareDeviceNames.ledger)); if (keyring?.updateTransportMethod) { return keyring.updateTransportMethod(transportType).catch((e) => { throw e; diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index d9c5983ff29e..fde932f58659 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -31,7 +31,7 @@ import { flushPromises } from '../../test/lib/timer-helpers'; import { deferredPromise } from './lib/util'; import MetaMaskController from './metamask-controller'; -const { Ganache } = require('../../test/e2e/ganache'); +const { Ganache } = require('../../test/e2e/seeder/ganache'); const ganacheServer = new Ganache(); @@ -624,6 +624,10 @@ describe('MetaMaskController', () => { } }); + jest + .spyOn(metamaskController.onboardingController.store, 'getState') + .mockReturnValue({ completedOnboarding: true }); + // Give account 2 a token jest .spyOn(metamaskController.tokensController, 'state', 'get') @@ -1113,6 +1117,10 @@ describe('MetaMaskController', () => { metamaskController.preferencesController.setSecurityAlertsEnabled( false, ); + jest + .spyOn(metamaskController.onboardingController.store, 'getState') + .mockReturnValue({ completedOnboarding: true }); + metamaskController.preferencesController.setUsePhishDetect(true); }); afterAll(() => { @@ -1146,6 +1154,49 @@ describe('MetaMaskController', () => { streamTest.end(); }); + it('checks the sender hostname with the phishing controller', async () => { + jest + .spyOn(metamaskController.phishingController, 'maybeUpdateState') + .mockReturnValue(); + + jest + .spyOn(metamaskController.phishingController, 'test') + .mockReturnValue({ result: 'mock' }); + + jest.spyOn(metamaskController, 'sendPhishingWarning').mockReturnValue(); + const phishingMessageSender = { + url: 'http://test.metamask-phishing.io', + tab: {}, + }; + + const { resolve } = deferredPromise(); + const streamTest = createThoughStream((chunk, _, cb) => { + if (chunk.name !== 'phishing') { + cb(); + return; + } + expect(chunk.data.hostname).toStrictEqual( + new URL(phishingMessageSender.url).hostname, + ); + resolve(); + cb(); + }); + + metamaskController.setupUntrustedCommunication({ + connectionStream: streamTest, + sender: phishingMessageSender, + }); + + expect( + metamaskController.phishingController.maybeUpdateState, + ).toHaveBeenCalled(); + expect(metamaskController.phishingController.test).toHaveBeenCalled(); + expect(metamaskController.sendPhishingWarning).toHaveBeenCalledWith( + expect.anything(), + 'test.metamask-phishing.io', + ); + }); + it('adds a tabId, origin and networkClient to requests', async () => { const messageSender = { url: 'http://mycrypto.com', diff --git a/builds.yml b/builds.yml index a38eeaca6527..78caad611346 100644 --- a/builds.yml +++ b/builds.yml @@ -189,14 +189,17 @@ env: - SUPPORT_REQUEST_LINK: https://metamask.zendesk.com/hc/en-us - SKIP_BACKGROUND_INITIALIZATION: false - # TODO(ritave): Move ManifestV3 into a feature? - - ENABLE_MV3: false + - ENABLE_MV3: true # These are exclusively used for MV3 - APPLY_LAVAMOAT - FILE_NAMES # This variable is read by Trezor's source and breaks build if not included - ASSET_PREFIX: null + - SUITE_TYPE: null + - COMMITHASH: null + - VERSION: null + - IS_CODESIGN_BUILD: null - SENTRY_MMI_DSN: '' diff --git a/development/build/index.js b/development/build/index.js index cc89cb6ad1e6..8cbb3e6a471f 100755 --- a/development/build/index.js +++ b/development/build/index.js @@ -109,6 +109,8 @@ async function defineAndRunBuildTasks() { 'WeakSet', 'Event', 'Image', // Used by browser to generate notifications + 'fetch', // Used by browser to generate notifications + 'OffscreenCanvas', // Used by browser to generate notifications // globals chromedriver needs to function /cdc_[a-zA-Z0-9]+_[a-zA-Z]+/iu, 'performance', diff --git a/development/build/manifest.js b/development/build/manifest.js index a12f1c455558..c15107564c9a 100644 --- a/development/build/manifest.js +++ b/development/build/manifest.js @@ -3,7 +3,10 @@ const path = require('path'); const childProcess = require('child_process'); const { mergeWith, cloneDeep } = require('lodash'); -const baseManifest = process.env.ENABLE_MV3 +const IS_MV3_ENABLED = + process.env.ENABLE_MV3 === 'true' || process.env.ENABLE_MV3 === undefined; + +const baseManifest = IS_MV3_ENABLED ? require('../../app/manifest/v3/_base.json') : require('../../app/manifest/v2/_base.json'); const { loadBuildTypesConfig } = require('../lib/build-type'); @@ -32,7 +35,7 @@ function createManifestTasks({ '..', '..', 'app', - process.env.ENABLE_MV3 ? 'manifest/v3' : 'manifest/v2', + IS_MV3_ENABLED ? 'manifest/v3' : 'manifest/v2', `${platform}.json`, ), ); @@ -136,7 +139,7 @@ function createManifestTasks({ buildType, applyLavaMoat, shouldIncludeSnow, - shouldIncludeMV3: process.env.ENABLE_MV3, + shouldIncludeMV3: IS_MV3_ENABLED, }); manifest.description = `${environment} build from git id: ${gitRevisionStr}`; diff --git a/development/build/scripts.js b/development/build/scripts.js index 476e4974df71..b83a438063f7 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -1,5 +1,4 @@ // TODO(ritave): Remove switches on hardcoded build types - const { callbackify } = require('util'); const path = require('path'); const { writeFileSync, readFileSync, unlinkSync } = require('fs'); @@ -53,6 +52,9 @@ const { createRemoveFencedCodeTransform, } = require('./transforms/remove-fenced-code'); +const isEnableMV3 = + process.env.ENABLE_MV3 === 'true' || process.env.ENABLE_MV3 === undefined; + // map dist files to bag of needed native APIs against LM scuttling const scuttlingConfigBase = { 'scripts/sentry-install.js': { @@ -186,7 +188,7 @@ function createScriptTasks({ // In MV3 we will need to build our offscreen entry point bundle and any // entry points for iframes that we want to lockdown with LavaMoat. - if (process.env.ENABLE_MV3 === 'true') { + if (isEnableMV3) { standardEntryPoints.push('offscreen'); } @@ -345,7 +347,7 @@ function createScriptTasks({ () => { // MV3 injects inpage into the tab's main world, but in MV2 we need // to do it manually: - if (process.env.ENABLE_MV3) { + if (isEnableMV3) { return; } // stringify scripts/inpage.js into itself, and then make it inject itself into the page @@ -689,7 +691,7 @@ function createFactoredBuild({ applyLavaMoat, destinationFileName: 'load-background.js', }); - if (process.env.ENABLE_MV3) { + if (isEnableMV3) { const jsBundles = [ ...commonSet.values(), ...groupSet.values(), @@ -991,7 +993,9 @@ function setupMinification(buildConfiguration) { function setupScuttlingWrapping(buildConfiguration, applyLavaMoat, envVars) { const scuttlingConfig = - envVars.ENABLE_MV3 === 'true' + envVars.ENABLE_MV3 === 'true' || + envVars.ENABLE_MV3 === undefined || + envVars.ENABLE_MV3 === true ? mv3ScuttlingConfig : standardScuttlingConfig; const { events } = buildConfiguration; diff --git a/development/build/static.js b/development/build/static.js index f6abf7b488de..6469d9737995 100644 --- a/development/build/static.js +++ b/development/build/static.js @@ -208,7 +208,11 @@ function getCopyTargets( allCopyTargets.push({ src: getPathInsideNodeModules('@blockaid/ppom_release', '/'), pattern: '*.wasm', - dest: process.env.ENABLE_MV3 ? 'scripts/' : '', + dest: + process.env.ENABLE_MV3 === 'true' || + process.env.ENABLE_MV3 === undefined + ? 'scripts/' + : '', }); } diff --git a/development/metamaskbot-build-announce.js b/development/metamaskbot-build-announce.js index e49bd848ca55..f85d64faa887 100755 --- a/development/metamaskbot-build-announce.js +++ b/development/metamaskbot-build-announce.js @@ -68,14 +68,20 @@ async function start() { const platforms = ['chrome', 'firefox']; const buildLinks = platforms .map((platform) => { - const url = `${BUILD_LINK_BASE}/builds/metamask-${platform}-${VERSION}.zip`; + const url = + platform === 'firefox' + ? `${BUILD_LINK_BASE}/builds-mv2/metamask-${platform}-${VERSION}.zip` + : `${BUILD_LINK_BASE}/builds/metamask-${platform}-${VERSION}.zip`; return `${platform}`; }) .join(', '); const betaBuildLinks = `chrome`; const flaskBuildLinks = platforms .map((platform) => { - const url = `${BUILD_LINK_BASE}/builds-flask/metamask-flask-${platform}-${VERSION}-flask.0.zip`; + const url = + platform === 'firefox' + ? `${BUILD_LINK_BASE}/builds-flask-mv2/metamask-flask-${platform}-${VERSION}-flask.0.zip` + : `${BUILD_LINK_BASE}/builds-flask/metamask-flask-${platform}-${VERSION}-flask.0.zip`; return `${platform}`; }) .join(', '); @@ -87,13 +93,19 @@ async function start() { .join(', '); const testBuildLinks = platforms .map((platform) => { - const url = `${BUILD_LINK_BASE}/builds-test/metamask-${platform}-${VERSION}.zip`; + const url = + platform === 'firefox' + ? `${BUILD_LINK_BASE}/builds-test-mv2/metamask-${platform}-${VERSION}.zip` + : `${BUILD_LINK_BASE}/builds-test/metamask-${platform}-${VERSION}.zip`; return `${platform}`; }) .join(', '); const testFlaskBuildLinks = platforms .map((platform) => { - const url = `${BUILD_LINK_BASE}/builds-test-flask/metamask-flask-${platform}-${VERSION}-flask.0.zip`; + const url = + platform === 'firefox' + ? `${BUILD_LINK_BASE}/builds-test-flask-mv2/metamask-flask-${platform}-${VERSION}-flask.0.zip` + : `${BUILD_LINK_BASE}/builds-test-flask/metamask-flask-${platform}-${VERSION}-flask.0.zip`; return `${platform}`; }) .join(', '); @@ -144,13 +156,13 @@ async function start() { // links to bundle browser builds const depVizUrl = `${BUILD_LINK_BASE}/build-artifacts/build-viz/index.html`; const depVizLink = `Build System`; - const moduleInitStatsBackgroundUrl = `${BUILD_LINK_BASE}/test-artifacts/chrome/mv3/initialisation/background/index.html`; + const moduleInitStatsBackgroundUrl = `${BUILD_LINK_BASE}/test-artifacts/chrome/initialisation/background/index.html`; const moduleInitStatsBackgroundLink = `Background Module Init Stats`; - const moduleInitStatsUIUrl = `${BUILD_LINK_BASE}/test-artifacts/chrome/mv3/initialisation/ui/index.html`; + const moduleInitStatsUIUrl = `${BUILD_LINK_BASE}/test-artifacts/chrome/initialisation/ui/index.html`; const moduleInitStatsUILink = `UI Init Stats`; - const moduleLoadStatsUrl = `${BUILD_LINK_BASE}/test-artifacts/chrome/mv3/load_time/index.html`; + const moduleLoadStatsUrl = `${BUILD_LINK_BASE}/test-artifacts/chrome/load_time/index.html`; const moduleLoadStatsLink = `Module Load Stats`; - const bundleSizeStatsUrl = `${BUILD_LINK_BASE}/test-artifacts/chrome/mv3/bundle_size.json`; + const bundleSizeStatsUrl = `${BUILD_LINK_BASE}/test-artifacts/chrome/bundle_size.json`; const bundleSizeStatsLink = `Bundle Size Stats`; const userActionsStatsUrl = `${BUILD_LINK_BASE}/test-artifacts/chrome/benchmark/user_actions.json`; const userActionsStatsLink = `E2e Actions Stats`; @@ -297,7 +309,7 @@ async function start() { path.resolve( __dirname, '..', - path.join('test-artifacts', 'chrome', 'mv3', 'bundle_size.json'), + path.join('test-artifacts', 'chrome', 'bundle_size.json'), ), 'utf-8', ), diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 1ee47784aefd..73e9ebc27689 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -1253,8 +1253,8 @@ "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web": true, "@metamask/eth-trezor-keyring>hdkey": true, - "@trezor/connect-web": true, "browserify>buffer": true, "webpack>events": true } @@ -1265,6 +1265,37 @@ "@trezor/connect-web>tslib": true } }, + "@metamask/eth-trezor-keyring>@trezor/connect-web": { + "globals": { + "URLSearchParams": true, + "__TREZOR_CONNECT_SRC": true, + "addEventListener": true, + "btoa": true, + "chrome": true, + "clearInterval": true, + "clearTimeout": true, + "console.warn": true, + "document.body": true, + "document.createElement": true, + "document.createTextNode": true, + "document.getElementById": true, + "document.querySelectorAll": true, + "location": true, + "navigator": true, + "open": true, + "origin": true, + "removeEventListener": true, + "setInterval": true, + "setTimeout": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect": true, + "@trezor/connect-web>@trezor/connect-common": true, + "@trezor/connect-web>@trezor/utils": true, + "@trezor/connect-web>tslib": true, + "webpack>events": true + } + }, "@metamask/eth-trezor-keyring>hdkey": { "packages": { "browserify>assert": true, @@ -1775,6 +1806,33 @@ "eslint>optionator>fast-levenshtein": true } }, + "@metamask/post-message-stream": { + "globals": { + "MessageEvent.prototype": true, + "WorkerGlobalScope": true, + "addEventListener": true, + "browser": true, + "chrome": true, + "location.origin": true, + "postMessage": true, + "removeEventListener": true + }, + "packages": { + "@metamask/post-message-stream>readable-stream": true, + "@metamask/utils": true + } + }, + "@metamask/post-message-stream>readable-stream": { + "packages": { + "browserify>browser-resolve": true, + "browserify>buffer": true, + "browserify>process": true, + "browserify>string_decoder": true, + "pumpify>inherits": true, + "readable-stream>util-deprecate": true, + "webpack>events": true + } + }, "@metamask/ppom-validator>elliptic": { "packages": { "@metamask/ppom-validator>elliptic>brorand": true, @@ -2122,6 +2180,9 @@ } }, "@metamask/snaps-execution-environments": { + "globals": { + "document.getElementById": true + }, "packages": { "@metamask/post-message-stream": true, "@metamask/snaps-utils": true, @@ -2618,23 +2679,20 @@ "location": true, "navigator": true, "open": true, + "origin": true, "removeEventListener": true, "setInterval": true, "setTimeout": true }, "packages": { "@trezor/connect-web>@trezor/connect": true, + "@trezor/connect-web>@trezor/connect-common": true, "@trezor/connect-web>@trezor/utils": true, "@trezor/connect-web>tslib": true, "webpack>events": true } }, "@trezor/connect-web>@trezor/connect": { - "globals": { - "console.error": true, - "console.log": true, - "console.warn": true - }, "packages": { "@trezor/connect-web>@trezor/connect>@trezor/protobuf": true, "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, @@ -2643,9 +2701,82 @@ "@trezor/connect-web>tslib": true } }, + "@trezor/connect-web>@trezor/connect-common": { + "globals": { + "console.warn": true, + "localStorage.getItem": true, + "localStorage.setItem": true, + "navigator": true, + "setTimeout": true, + "window": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": true, + "@trezor/connect-web>@trezor/utils": true, + "@trezor/connect-web>tslib": true + } + }, + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": { + "globals": { + "innerHeight": true, + "innerWidth": true, + "location.hostname": true, + "location.origin": true, + "navigator.languages": true, + "navigator.platform": true, + "navigator.userAgent": true, + "screen.height": true, + "screen.width": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": true, + "@trezor/connect-web>tslib": true, + "browserify>process": true + } + }, + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": { + "globals": { + "define": true + } + }, "@trezor/connect-web>@trezor/connect>@trezor/protobuf": { "packages": { - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": true, + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, + "@trezor/connect-web>tslib": true, + "browserify>buffer": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": { + "globals": { + "process": true, + "setTimeout": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/base64": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/eventemitter": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/float": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/path": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/pool": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/utf8": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": { + "globals": { + "console.log": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": { + "globals": { + "XMLHttpRequest": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true } }, "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": { @@ -2654,14 +2785,25 @@ }, "packages": { "@trezor/connect-web>@trezor/connect>@trezor/schema-utils>@sinclair/typebox": true, - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils>ts-mixer": true, - "browserify>buffer": true + "browserify>buffer": true, + "ts-mixer": true } }, "@trezor/connect-web>@trezor/utils": { "globals": { + "AbortController": true, + "Intl.NumberFormat": true, "clearTimeout": true, + "console.error": true, + "console.info": true, + "console.log": true, + "console.warn": true, "setTimeout": true + }, + "packages": { + "@trezor/connect-web>tslib": true, + "browserify>buffer": true, + "webpack>events": true } }, "@trezor/connect-web>tslib": { diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index 97bb725500b4..2c7ff7a2a4fb 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -1338,8 +1338,8 @@ "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web": true, "@metamask/eth-trezor-keyring>hdkey": true, - "@trezor/connect-web": true, "browserify>buffer": true, "webpack>events": true } @@ -1350,6 +1350,37 @@ "@trezor/connect-web>tslib": true } }, + "@metamask/eth-trezor-keyring>@trezor/connect-web": { + "globals": { + "URLSearchParams": true, + "__TREZOR_CONNECT_SRC": true, + "addEventListener": true, + "btoa": true, + "chrome": true, + "clearInterval": true, + "clearTimeout": true, + "console.warn": true, + "document.body": true, + "document.createElement": true, + "document.createTextNode": true, + "document.getElementById": true, + "document.querySelectorAll": true, + "location": true, + "navigator": true, + "open": true, + "origin": true, + "removeEventListener": true, + "setInterval": true, + "setTimeout": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect": true, + "@trezor/connect-web>@trezor/connect-common": true, + "@trezor/connect-web>@trezor/utils": true, + "@trezor/connect-web>tslib": true, + "webpack>events": true + } + }, "@metamask/eth-trezor-keyring>hdkey": { "packages": { "browserify>assert": true, @@ -2439,6 +2470,9 @@ } }, "@metamask/snaps-execution-environments": { + "globals": { + "document.getElementById": true + }, "packages": { "@metamask/post-message-stream": true, "@metamask/snaps-utils": true, @@ -2948,23 +2982,20 @@ "location": true, "navigator": true, "open": true, + "origin": true, "removeEventListener": true, "setInterval": true, "setTimeout": true }, "packages": { "@trezor/connect-web>@trezor/connect": true, + "@trezor/connect-web>@trezor/connect-common": true, "@trezor/connect-web>@trezor/utils": true, "@trezor/connect-web>tslib": true, "webpack>events": true } }, "@trezor/connect-web>@trezor/connect": { - "globals": { - "console.error": true, - "console.log": true, - "console.warn": true - }, "packages": { "@trezor/connect-web>@trezor/connect>@trezor/protobuf": true, "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, @@ -2973,9 +3004,82 @@ "@trezor/connect-web>tslib": true } }, + "@trezor/connect-web>@trezor/connect-common": { + "globals": { + "console.warn": true, + "localStorage.getItem": true, + "localStorage.setItem": true, + "navigator": true, + "setTimeout": true, + "window": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": true, + "@trezor/connect-web>@trezor/utils": true, + "@trezor/connect-web>tslib": true + } + }, + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": { + "globals": { + "innerHeight": true, + "innerWidth": true, + "location.hostname": true, + "location.origin": true, + "navigator.languages": true, + "navigator.platform": true, + "navigator.userAgent": true, + "screen.height": true, + "screen.width": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": true, + "@trezor/connect-web>tslib": true, + "browserify>process": true + } + }, + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": { + "globals": { + "define": true + } + }, "@trezor/connect-web>@trezor/connect>@trezor/protobuf": { "packages": { - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": true, + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, + "@trezor/connect-web>tslib": true, + "browserify>buffer": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": { + "globals": { + "process": true, + "setTimeout": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/base64": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/eventemitter": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/float": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/path": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/pool": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/utf8": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": { + "globals": { + "console.log": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": { + "globals": { + "XMLHttpRequest": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true } }, "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": { @@ -2984,14 +3088,25 @@ }, "packages": { "@trezor/connect-web>@trezor/connect>@trezor/schema-utils>@sinclair/typebox": true, - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils>ts-mixer": true, - "browserify>buffer": true + "browserify>buffer": true, + "ts-mixer": true } }, "@trezor/connect-web>@trezor/utils": { "globals": { + "AbortController": true, + "Intl.NumberFormat": true, "clearTimeout": true, + "console.error": true, + "console.info": true, + "console.log": true, + "console.warn": true, "setTimeout": true + }, + "packages": { + "@trezor/connect-web>tslib": true, + "browserify>buffer": true, + "webpack>events": true } }, "@trezor/connect-web>tslib": { diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index e0d6ae837fba..591b07aec78c 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -1338,8 +1338,8 @@ "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web": true, "@metamask/eth-trezor-keyring>hdkey": true, - "@trezor/connect-web": true, "browserify>buffer": true, "webpack>events": true } @@ -1350,6 +1350,37 @@ "@trezor/connect-web>tslib": true } }, + "@metamask/eth-trezor-keyring>@trezor/connect-web": { + "globals": { + "URLSearchParams": true, + "__TREZOR_CONNECT_SRC": true, + "addEventListener": true, + "btoa": true, + "chrome": true, + "clearInterval": true, + "clearTimeout": true, + "console.warn": true, + "document.body": true, + "document.createElement": true, + "document.createTextNode": true, + "document.getElementById": true, + "document.querySelectorAll": true, + "location": true, + "navigator": true, + "open": true, + "origin": true, + "removeEventListener": true, + "setInterval": true, + "setTimeout": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect": true, + "@trezor/connect-web>@trezor/connect-common": true, + "@trezor/connect-web>@trezor/utils": true, + "@trezor/connect-web>tslib": true, + "webpack>events": true + } + }, "@metamask/eth-trezor-keyring>hdkey": { "packages": { "browserify>assert": true, @@ -2491,6 +2522,9 @@ } }, "@metamask/snaps-execution-environments": { + "globals": { + "document.getElementById": true + }, "packages": { "@metamask/post-message-stream": true, "@metamask/snaps-utils": true, @@ -3000,23 +3034,20 @@ "location": true, "navigator": true, "open": true, + "origin": true, "removeEventListener": true, "setInterval": true, "setTimeout": true }, "packages": { "@trezor/connect-web>@trezor/connect": true, + "@trezor/connect-web>@trezor/connect-common": true, "@trezor/connect-web>@trezor/utils": true, "@trezor/connect-web>tslib": true, "webpack>events": true } }, "@trezor/connect-web>@trezor/connect": { - "globals": { - "console.error": true, - "console.log": true, - "console.warn": true - }, "packages": { "@trezor/connect-web>@trezor/connect>@trezor/protobuf": true, "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, @@ -3025,9 +3056,82 @@ "@trezor/connect-web>tslib": true } }, + "@trezor/connect-web>@trezor/connect-common": { + "globals": { + "console.warn": true, + "localStorage.getItem": true, + "localStorage.setItem": true, + "navigator": true, + "setTimeout": true, + "window": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": true, + "@trezor/connect-web>@trezor/utils": true, + "@trezor/connect-web>tslib": true + } + }, + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": { + "globals": { + "innerHeight": true, + "innerWidth": true, + "location.hostname": true, + "location.origin": true, + "navigator.languages": true, + "navigator.platform": true, + "navigator.userAgent": true, + "screen.height": true, + "screen.width": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": true, + "@trezor/connect-web>tslib": true, + "browserify>process": true + } + }, + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": { + "globals": { + "define": true + } + }, "@trezor/connect-web>@trezor/connect>@trezor/protobuf": { "packages": { - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": true, + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, + "@trezor/connect-web>tslib": true, + "browserify>buffer": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": { + "globals": { + "process": true, + "setTimeout": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/base64": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/eventemitter": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/float": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/path": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/pool": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/utf8": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": { + "globals": { + "console.log": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": { + "globals": { + "XMLHttpRequest": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true } }, "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": { @@ -3036,14 +3140,25 @@ }, "packages": { "@trezor/connect-web>@trezor/connect>@trezor/schema-utils>@sinclair/typebox": true, - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils>ts-mixer": true, - "browserify>buffer": true + "browserify>buffer": true, + "ts-mixer": true } }, "@trezor/connect-web>@trezor/utils": { "globals": { + "AbortController": true, + "Intl.NumberFormat": true, "clearTimeout": true, + "console.error": true, + "console.info": true, + "console.log": true, + "console.warn": true, "setTimeout": true + }, + "packages": { + "@trezor/connect-web>tslib": true, + "browserify>buffer": true, + "webpack>events": true } }, "@trezor/connect-web>tslib": { diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index c3331315af7a..816de3d28ecc 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -1253,8 +1253,8 @@ "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web": true, "@metamask/eth-trezor-keyring>hdkey": true, - "@trezor/connect-web": true, "browserify>buffer": true, "webpack>events": true } @@ -1265,6 +1265,37 @@ "@trezor/connect-web>tslib": true } }, + "@metamask/eth-trezor-keyring>@trezor/connect-web": { + "globals": { + "URLSearchParams": true, + "__TREZOR_CONNECT_SRC": true, + "addEventListener": true, + "btoa": true, + "chrome": true, + "clearInterval": true, + "clearTimeout": true, + "console.warn": true, + "document.body": true, + "document.createElement": true, + "document.createTextNode": true, + "document.getElementById": true, + "document.querySelectorAll": true, + "location": true, + "navigator": true, + "open": true, + "origin": true, + "removeEventListener": true, + "setInterval": true, + "setTimeout": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect": true, + "@trezor/connect-web>@trezor/connect-common": true, + "@trezor/connect-web>@trezor/utils": true, + "@trezor/connect-web>tslib": true, + "webpack>events": true + } + }, "@metamask/eth-trezor-keyring>hdkey": { "packages": { "browserify>assert": true, @@ -2406,6 +2437,9 @@ } }, "@metamask/snaps-execution-environments": { + "globals": { + "document.getElementById": true + }, "packages": { "@metamask/post-message-stream": true, "@metamask/snaps-utils": true, @@ -2915,23 +2949,20 @@ "location": true, "navigator": true, "open": true, + "origin": true, "removeEventListener": true, "setInterval": true, "setTimeout": true }, "packages": { "@trezor/connect-web>@trezor/connect": true, + "@trezor/connect-web>@trezor/connect-common": true, "@trezor/connect-web>@trezor/utils": true, "@trezor/connect-web>tslib": true, "webpack>events": true } }, "@trezor/connect-web>@trezor/connect": { - "globals": { - "console.error": true, - "console.log": true, - "console.warn": true - }, "packages": { "@trezor/connect-web>@trezor/connect>@trezor/protobuf": true, "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, @@ -2940,9 +2971,82 @@ "@trezor/connect-web>tslib": true } }, + "@trezor/connect-web>@trezor/connect-common": { + "globals": { + "console.warn": true, + "localStorage.getItem": true, + "localStorage.setItem": true, + "navigator": true, + "setTimeout": true, + "window": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": true, + "@trezor/connect-web>@trezor/utils": true, + "@trezor/connect-web>tslib": true + } + }, + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": { + "globals": { + "innerHeight": true, + "innerWidth": true, + "location.hostname": true, + "location.origin": true, + "navigator.languages": true, + "navigator.platform": true, + "navigator.userAgent": true, + "screen.height": true, + "screen.width": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": true, + "@trezor/connect-web>tslib": true, + "browserify>process": true + } + }, + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": { + "globals": { + "define": true + } + }, "@trezor/connect-web>@trezor/connect>@trezor/protobuf": { "packages": { - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": true, + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, + "@trezor/connect-web>tslib": true, + "browserify>buffer": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": { + "globals": { + "process": true, + "setTimeout": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/base64": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/eventemitter": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/float": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/path": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/pool": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/utf8": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": { + "globals": { + "console.log": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": { + "globals": { + "XMLHttpRequest": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true } }, "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": { @@ -2951,14 +3055,25 @@ }, "packages": { "@trezor/connect-web>@trezor/connect>@trezor/schema-utils>@sinclair/typebox": true, - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils>ts-mixer": true, - "browserify>buffer": true + "browserify>buffer": true, + "ts-mixer": true } }, "@trezor/connect-web>@trezor/utils": { "globals": { + "AbortController": true, + "Intl.NumberFormat": true, "clearTimeout": true, + "console.error": true, + "console.info": true, + "console.log": true, + "console.warn": true, "setTimeout": true + }, + "packages": { + "@trezor/connect-web>tslib": true, + "browserify>buffer": true, + "webpack>events": true } }, "@trezor/connect-web>tslib": { diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index 95a06d230977..f18cd7e33cbe 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -1386,8 +1386,8 @@ "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web": true, "@metamask/eth-trezor-keyring>hdkey": true, - "@trezor/connect-web": true, "browserify>buffer": true, "webpack>events": true } @@ -1398,6 +1398,37 @@ "@trezor/connect-web>tslib": true } }, + "@metamask/eth-trezor-keyring>@trezor/connect-web": { + "globals": { + "URLSearchParams": true, + "__TREZOR_CONNECT_SRC": true, + "addEventListener": true, + "btoa": true, + "chrome": true, + "clearInterval": true, + "clearTimeout": true, + "console.warn": true, + "document.body": true, + "document.createElement": true, + "document.createTextNode": true, + "document.getElementById": true, + "document.querySelectorAll": true, + "location": true, + "navigator": true, + "open": true, + "origin": true, + "removeEventListener": true, + "setInterval": true, + "setTimeout": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect": true, + "@trezor/connect-web>@trezor/connect-common": true, + "@trezor/connect-web>@trezor/utils": true, + "@trezor/connect-web>tslib": true, + "webpack>events": true + } + }, "@metamask/eth-trezor-keyring>hdkey": { "packages": { "browserify>assert": true, @@ -2539,6 +2570,9 @@ } }, "@metamask/snaps-execution-environments": { + "globals": { + "document.getElementById": true + }, "packages": { "@metamask/post-message-stream": true, "@metamask/snaps-utils": true, @@ -3048,23 +3082,20 @@ "location": true, "navigator": true, "open": true, + "origin": true, "removeEventListener": true, "setInterval": true, "setTimeout": true }, "packages": { "@trezor/connect-web>@trezor/connect": true, + "@trezor/connect-web>@trezor/connect-common": true, "@trezor/connect-web>@trezor/utils": true, "@trezor/connect-web>tslib": true, "webpack>events": true } }, "@trezor/connect-web>@trezor/connect": { - "globals": { - "console.error": true, - "console.log": true, - "console.warn": true - }, "packages": { "@trezor/connect-web>@trezor/connect>@trezor/protobuf": true, "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, @@ -3073,9 +3104,82 @@ "@trezor/connect-web>tslib": true } }, + "@trezor/connect-web>@trezor/connect-common": { + "globals": { + "console.warn": true, + "localStorage.getItem": true, + "localStorage.setItem": true, + "navigator": true, + "setTimeout": true, + "window": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": true, + "@trezor/connect-web>@trezor/utils": true, + "@trezor/connect-web>tslib": true + } + }, + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": { + "globals": { + "innerHeight": true, + "innerWidth": true, + "location.hostname": true, + "location.origin": true, + "navigator.languages": true, + "navigator.platform": true, + "navigator.userAgent": true, + "screen.height": true, + "screen.width": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": true, + "@trezor/connect-web>tslib": true, + "browserify>process": true + } + }, + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": { + "globals": { + "define": true + } + }, "@trezor/connect-web>@trezor/connect>@trezor/protobuf": { "packages": { - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": true, + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, + "@trezor/connect-web>tslib": true, + "browserify>buffer": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": { + "globals": { + "process": true, + "setTimeout": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/base64": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/eventemitter": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/float": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/path": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/pool": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/utf8": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": { + "globals": { + "console.log": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": { + "globals": { + "XMLHttpRequest": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true } }, "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": { @@ -3084,14 +3188,25 @@ }, "packages": { "@trezor/connect-web>@trezor/connect>@trezor/schema-utils>@sinclair/typebox": true, - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils>ts-mixer": true, - "browserify>buffer": true + "browserify>buffer": true, + "ts-mixer": true } }, "@trezor/connect-web>@trezor/utils": { "globals": { + "AbortController": true, + "Intl.NumberFormat": true, "clearTimeout": true, + "console.error": true, + "console.info": true, + "console.log": true, + "console.warn": true, "setTimeout": true + }, + "packages": { + "@trezor/connect-web>tslib": true, + "browserify>buffer": true, + "webpack>events": true } }, "@trezor/connect-web>tslib": { diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index 5f35f7209448..dc9df69fea9f 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -3,18 +3,22 @@ "@babel/code-frame": { "globals": { "console.warn": true, - "process.emitWarning": true + "process": true }, "packages": { "@babel/code-frame>@babel/highlight": true, - "@babel/code-frame>chalk": true + "postcss>picocolors": true } }, "@babel/code-frame>@babel/highlight": { + "globals": { + "process": true + }, "packages": { "@babel/code-frame>@babel/highlight>chalk": true, "lavamoat>@babel/highlight>@babel/helper-validator-identifier": true, - "loose-envify>js-tokens": true + "loose-envify>js-tokens": true, + "postcss>picocolors": true } }, "@babel/code-frame>@babel/highlight>chalk": { @@ -53,42 +57,6 @@ "gulp-livereload>chalk>supports-color>has-flag": true } }, - "@babel/code-frame>chalk": { - "globals": { - "process.env.TERM": true, - "process.platform": true - }, - "packages": { - "@babel/code-frame>chalk>ansi-styles": true, - "@babel/code-frame>chalk>escape-string-regexp": true, - "@babel/code-frame>chalk>supports-color": true - } - }, - "@babel/code-frame>chalk>ansi-styles": { - "packages": { - "@babel/code-frame>chalk>ansi-styles>color-convert": true - } - }, - "@babel/code-frame>chalk>ansi-styles>color-convert": { - "packages": { - "@babel/code-frame>chalk>ansi-styles>color-convert>color-name": true - } - }, - "@babel/code-frame>chalk>supports-color": { - "builtin": { - "os.release": true - }, - "globals": { - "process.env": true, - "process.platform": true, - "process.stderr": true, - "process.stdout": true, - "process.versions.node.split": true - }, - "packages": { - "gulp-livereload>chalk>supports-color>has-flag": true - } - }, "@babel/core": { "builtin": { "assert": true, diff --git a/package.json b/package.json index f2abbb4d9f46..875817fb43f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask-crx", - "version": "11.16.5", + "version": "11.16.6", "private": true, "repository": { "type": "git", @@ -8,28 +8,30 @@ }, "scripts": { "start": "yarn build:dev dev --apply-lavamoat=false --snow=false", - "start:mv3": "ENABLE_MV3=true yarn build:dev dev --apply-lavamoat=false", + "start:mv2": "ENABLE_MV3=false yarn build:dev dev --apply-lavamoat=false --snow=false", "start:flask": "yarn start --build-type flask", "start:mmi": "yarn start --build-type mmi", "start:lavamoat": "yarn build:dev dev --apply-lavamoat=true", "dist": "yarn build dist", - "dist:mv3": "ENABLE_MV3=true yarn build dist", + "dist:mv2": "ENABLE_MV3=false yarn build dist", "dist:mmi": "yarn dist --build-type mmi", "dist:mmi:debug": "yarn dist --build-type mmi --apply-lavamoat=false", "build": "yarn lavamoat:build", "build:dev": "node development/build/index.js", - "start:test": "BLOCKAID_FILE_CDN=static.cx.metamask.io/api/v1/confirmations/ppom SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev --apply-lavamoat=false --snow=false", + "start:test": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev", "start:test:flask": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev --build-type flask --apply-lavamoat=false --snow=false", - "start:test:mv3": "ENABLE_MV3=true SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev", + "start:test:mv2:flask": "ENABLE_MV3=false SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev --build-type flask --apply-lavamoat=false --snow=false", + "start:test:mv2": "ENABLE_MV3=false BLOCKAID_FILE_CDN=static.cx.metamask.io/api/v1/confirmations/ppom SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev --apply-lavamoat=false --snow=false", "benchmark:chrome": "SELENIUM_BROWSER=chrome ts-node test/e2e/benchmark.js", - "mv3:stats:chrome": "SELENIUM_BROWSER=chrome ENABLE_MV3=true ts-node test/e2e/mv3-perf-stats/index.js", + "mv3:stats:chrome": "SELENIUM_BROWSER=chrome ts-node test/e2e/mv3-perf-stats/index.js", "user-actions-benchmark:chrome": "SELENIUM_BROWSER=chrome ts-node test/e2e/user-actions-benchmark.js", "benchmark:firefox": "SELENIUM_BROWSER=firefox ts-node test/e2e/benchmark.js", - "build:test": "BLOCKAID_FILE_CDN=static.cx.metamask.io/api/v1/confirmations/ppom SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build test", + "build:test": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build test", + "build:test:dev": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev --apply-lavamoat=false", "build:test:flask": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' yarn build test --build-type flask", + "build:test:flask:mv2": "ENABLE_MV3=false SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' yarn build test --build-type flask", "build:test:mmi": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' yarn build test --build-type mmi", - "build:test:mv3": "ENABLE_MV3=true SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build test", - "build:test:dev:mv3": "ENABLE_MV3=true SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev --apply-lavamoat=false", + "build:test:mv2": "ENABLE_MV3=false BLOCKAID_FILE_CDN=static.cx.metamask.io/api/v1/confirmations/ppom SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build test", "test": "yarn lint && yarn test:unit && yarn test:unit:jest", "dapp": "node development/static-server.js node_modules/@metamask/test-dapp/dist --port 8080", "dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && yarn dapp'", @@ -49,7 +51,6 @@ "test:e2e:mmi:visual": "./test/e2e/mmi/scripts/run-visual-test.sh check", "test:e2e:mmi:visual:update": "./test/e2e/mmi/scripts/run-visual-test.sh update", "test:e2e:pw:report": "yarn playwright show-report public/playwright/playwright-reports/html", - "test:e2e:chrome:mv3": "ENABLE_MV3=true SELENIUM_BROWSER=chrome node test/e2e/run-all.js", "test:e2e:chrome:rpc": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --rpc", "test:e2e:chrome:multi-provider": "MULTIPROVIDER=true SELENIUM_BROWSER=chrome node test/e2e/run-all.js --multi-provider", "test:e2e:firefox": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js", @@ -97,8 +98,8 @@ "lavamoat:build:auto": "yarn lavamoat:build --writeAutoPolicy", "lavamoat:debug:build": "yarn lavamoat:build --writeAutoPolicyDebug --policydebug lavamoat/build-system/policy-debug.json", "lavamoat:debug:webapp": "WRITE_AUTO_POLICY_DEBUG=1 yarn lavamoat:webapp:auto", - "lavamoat:webapp:auto": "node ./development/generate-lavamoat-policies.js --devMode=true", - "lavamoat:webapp:auto:ci": "node ./development/generate-lavamoat-policies.js --parallel=false", + "lavamoat:webapp:auto": "ENABLE_MV3=true node ./development/generate-lavamoat-policies.js --devMode=true", + "lavamoat:webapp:auto:ci": "ENABLE_MV3=true node ./development/generate-lavamoat-policies.js --parallel=false", "lavamoat:auto": "yarn lavamoat:build:auto && yarn lavamoat:webapp:auto", "ts-migration:dashboard:build": "ts-node development/ts-migration-dashboard/scripts/build-app.ts", "ts-migration:dashboard:deploy": "gh-pages --dist development/ts-migration-dashboard/build/final --remote ts-migration-dashboard", @@ -211,7 +212,7 @@ "semver@7.3.7": "^7.5.4", "semver@7.3.8": "^7.5.4", "nonce-tracker@npm:^3.0.0": "patch:nonce-tracker@npm%3A3.0.0#~/.yarn/patches/nonce-tracker-npm-3.0.0-c5e9a93f9d.patch", - "@trezor/schema-utils@npm:1.0.1": "patch:@trezor/schema-utils@npm%3A1.0.1#~/.yarn/patches/@trezor-schema-utils-npm-1.0.1-70c2a68232.patch", + "@trezor/schema-utils@npm:1.0.2": "patch:@trezor/schema-utils@npm%3A1.0.2#~/.yarn/patches/@trezor-schema-utils-npm-1.0.2-7dd48689b2.patch", "lavamoat-core@npm:^15.1.1": "patch:lavamoat-core@npm%3A15.1.1#~/.yarn/patches/lavamoat-core-npm-15.1.1-51fbe39988.patch", "@metamask/snaps-sdk": "4.0.1", "@metamask/snaps-rpc-methods@^8.0.0": "8.0.0", @@ -245,7 +246,12 @@ "@metamask/network-controller@npm:^18.1.0": "patch:@metamask/network-controller@npm%3A18.1.0#~/.yarn/patches/@metamask-network-controller-npm-18.1.0-680908c29a.patch", "@metamask/keyring-controller@npm:^13.0.0": "patch:@metamask/keyring-controller@npm%3A13.0.0#~/.yarn/patches/@metamask-keyring-controller-npm-13.0.0-d94816a680.patch", "@spruceid/siwe-parser@npm:1.1.3": "patch:@spruceid/siwe-parser@npm%3A2.1.0#~/.yarn/patches/@spruceid-siwe-parser-npm-2.1.0-060b7ede7a.patch", - "@spruceid/siwe-parser@npm:2.1.0": "patch:@spruceid/siwe-parser@npm%3A2.1.0#~/.yarn/patches/@spruceid-siwe-parser-npm-2.1.0-060b7ede7a.patch" + "@spruceid/siwe-parser@npm:2.1.0": "patch:@spruceid/siwe-parser@npm%3A2.1.0#~/.yarn/patches/@spruceid-siwe-parser-npm-2.1.0-060b7ede7a.patch", + "@trezor/connect-web@npm:^9.2.2": "patch:@trezor/connect-web@npm%3A9.2.2#~/.yarn/patches/@trezor-connect-web-npm-9.2.2-a4de8e45fc.patch", + "ts-mixer@npm:^6.0.3": "patch:ts-mixer@npm%3A6.0.4#~/.yarn/patches/ts-mixer-npm-6.0.4-5d9747bdf5.patch", + "sucrase@npm:3.34.0": "^3.35.0", + "@expo/config/glob": "^10.3.10", + "@expo/config-plugins/glob": "^10.3.10" }, "dependencies": { "@babel/runtime": "patch:@babel/runtime@npm%3A7.24.0#~/.yarn/patches/@babel-runtime-npm-7.24.0-7eb1dd11a2.patch", @@ -324,7 +330,7 @@ "@metamask/selected-network-controller": "^12.0.1", "@metamask/signature-controller": "^12.0.0", "@metamask/smart-transactions-controller": "^10.0.1", - "@metamask/snaps-controllers": "8.0.0", + "@metamask/snaps-controllers": "patch:@metamask/snaps-controllers@npm%3A8.0.0#~/.yarn/patches/@metamask-snaps-controllers-npm-8.0.0-7e59688855.patch", "@metamask/snaps-execution-environments": "6.0.2", "@metamask/snaps-rpc-methods": "8.0.0", "@metamask/snaps-sdk": "4.0.1", @@ -342,7 +348,7 @@ "@sentry/integrations": "^7.53.0", "@sentry/types": "^7.53.0", "@sentry/utils": "^7.53.0", - "@trezor/connect-web": "9.1.12", + "@trezor/connect-web": "patch:@trezor/connect-web@npm%3A9.2.2#~/.yarn/patches/@trezor-connect-web-npm-9.2.2-a4de8e45fc.patch", "@zxing/browser": "^0.1.4", "@zxing/library": "0.20.0", "await-semaphore": "^0.1.1", @@ -411,6 +417,7 @@ "ses": "^1.1.0", "simple-git": "^3.20.0", "single-call-balance-checker-abi": "^1.0.0", + "ts-mixer": "patch:ts-mixer@npm%3A6.0.4#~/.yarn/patches/ts-mixer-npm-6.0.4-5d9747bdf5.patch", "unicode-confusables": "^0.1.1", "uuid": "^8.3.2", "web3-stream-provider": "^5.0.0", @@ -685,7 +692,8 @@ "@trezor/connect-web>@trezor/connect>@trezor/utxo-lib>tiny-secp256k1": false, "@storybook/test-runner>@swc/core": false, "@lavamoat/lavadome-react>@lavamoat/preinstall-always-fail": false, - "tsx>esbuild": false + "tsx>esbuild": false, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": false } }, "packageManager": "yarn@4.0.2" diff --git a/privacy-snapshot.json b/privacy-snapshot.json index 309c3fb88f55..2fdcc5f22a95 100644 --- a/privacy-snapshot.json +++ b/privacy-snapshot.json @@ -45,5 +45,6 @@ "tx-insights.metaswap.codefi.network", "tx-sentinel-ethereum-mainnet.api.cx.metamask.io", "unresponsive-rpc.url", - "www.4byte.directory" + "www.4byte.directory", + "lattice.gridplus.io" ] diff --git a/shared/constants/offscreen-communication.ts b/shared/constants/offscreen-communication.ts index 3be4d5da32de..cab4232c09dc 100644 --- a/shared/constants/offscreen-communication.ts +++ b/shared/constants/offscreen-communication.ts @@ -39,7 +39,7 @@ export enum LedgerAction { unlock = 'ledger-unlock', getPublicKey = 'ledger-unlock', signTransaction = 'ledger-sign-transaction', - signMessage = 'ledger-sign-message', + signPersonalMessage = 'ledger-sign-personal-message', signTypedData = 'ledger-sign-typed-data', } diff --git a/shared/modules/i18n.test.ts b/shared/modules/i18n.test.ts index 7a52137b7dc8..8452ef48238c 100644 --- a/shared/modules/i18n.test.ts +++ b/shared/modules/i18n.test.ts @@ -281,7 +281,7 @@ describe('I18N Module', () => { it('returns json from locale file', async () => { const result = await fetchLocale(localeCodeMock); expect(result).toStrictEqual({ - url: `./_locales/${localeCodeMock}/messages.json`, + url: `../_locales/${localeCodeMock}/messages.json`, }); }); diff --git a/shared/modules/i18n.ts b/shared/modules/i18n.ts index 790d9da25313..d19cfa4b3b11 100644 --- a/shared/modules/i18n.ts +++ b/shared/modules/i18n.ts @@ -91,7 +91,7 @@ export async function fetchLocale( ): Promise { try { const response = await fetchWithTimeout( - `./_locales/${localeCode}/messages.json`, + `../_locales/${localeCode}/messages.json`, ); return await response.json(); } catch (error) { diff --git a/test/e2e/tests/metrics/mock-data.js b/test/data/mock-data.js similarity index 100% rename from test/e2e/tests/metrics/mock-data.js rename to test/data/mock-data.js diff --git a/test/e2e/accounts/common.ts b/test/e2e/accounts/common.ts index fe231e652674..82f57a85586c 100644 --- a/test/e2e/accounts/common.ts +++ b/test/e2e/accounts/common.ts @@ -10,6 +10,7 @@ import { validateContractDetails, multipleGanacheOptions, regularDelayMs, + openDapp, } from '../helpers'; import { Driver } from '../webdriver/driver'; import { TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL } from '../constants'; @@ -182,7 +183,20 @@ async function switchToAccount2(driver: Driver) { } export async function connectAccountToTestDapp(driver: Driver) { - await switchToOrOpenDapp(driver); + try { + // Do an unusually fast switchToWindowWithTitle, just 1 second + await driver.switchToWindowWithTitle( + WINDOW_TITLES.TestDApp, + null, + 1000, + 1000, + ); + } catch { + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await openDapp(driver); + } await driver.clickElement('#connectButton'); await driver.delay(regularDelayMs); diff --git a/test/e2e/fixture-builder.js b/test/e2e/fixture-builder.js index 67889e1f8cab..6cec07fa3e81 100644 --- a/test/e2e/fixture-builder.js +++ b/test/e2e/fixture-builder.js @@ -156,6 +156,7 @@ function defaultFixture(inputChainId = CHAIN_IDS.LOCALHOST) { PreferencesController: { advancedGasFee: null, currentLocale: 'en', + useExternalServices: true, dismissSeedBackUpReminder: true, featureFlags: {}, forgottenPassword: false, @@ -307,6 +308,7 @@ function onboardingFixture() { useNativeCurrencyAsPrimaryCurrency: true, petnamesEnabled: true, }, + useExternalServices: true, theme: 'light', useBlockie: false, useNftDetection: false, diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index f2cf15aa6956..1b5e83c5ba20 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -8,7 +8,7 @@ const { difference } = require('lodash'); const createStaticServer = require('../../development/create-static-server'); const { tEn } = require('../lib/i18n-helpers'); const { setupMocking } = require('./mock-e2e'); -const { Ganache } = require('./ganache'); +const { Ganache } = require('./seeder/ganache'); const FixtureServer = require('./fixture-server'); const PhishingWarningPageServer = require('./phishing-warning-page-server'); const { buildWebDriver } = require('./webdriver'); @@ -507,9 +507,17 @@ const onboardingCompleteWalletCreationWithOptOut = async (driver) => { await driver.findElement({ text: 'Wallet creation successful', tag: 'h2' }); // opt-out from third party API await driver.clickElement({ text: 'Advanced configuration', tag: 'a' }); + await driver.clickElement( + '[data-testid="basic-functionality-toggle"] .toggle-button', + ); + await driver.clickElement('[id="basic-configuration-checkbox"]'); + await driver.clickElement({ text: 'Turn off', tag: 'button' }); + await Promise.all( ( - await driver.findClickableElements('.toggle-button.toggle-button--on') + await driver.findClickableElements( + '.toggle-button.toggle-button--on:not([data-testid="basic-functionality-toggle"] .toggle-button)', + ) ).map((toggle) => toggle.click()), ); // complete onboarding @@ -641,7 +649,7 @@ const DAPP_URL = 'http://127.0.0.1:8080'; const DAPP_ONE_URL = 'http://127.0.0.1:8081'; const openDapp = async (driver, contract = null, dappURL = DAPP_URL) => { - contract + return contract ? await driver.openNewPage(`${dappURL}/?contract=${contract}`) : await driver.openNewPage(dappURL); }; diff --git a/test/e2e/json-rpc/switchEthereumChain.spec.js b/test/e2e/json-rpc/switchEthereumChain.spec.js index 78b02c8634e5..9c326a0d0116 100644 --- a/test/e2e/json-rpc/switchEthereumChain.spec.js +++ b/test/e2e/json-rpc/switchEthereumChain.spec.js @@ -30,13 +30,8 @@ describe('Switch Ethereum Chain for two dapps', function () { await unlockWallet(driver); // open two dapps - await openDapp(driver, undefined, DAPP_URL); - await openDapp(driver, undefined, DAPP_ONE_URL); - - // Window Handling - const windowHandles = await driver.getAllWindowHandles(); - const dappOne = windowHandles[1]; - const dappTwo = windowHandles[2]; + const dappOne = await openDapp(driver, undefined, DAPP_URL); + const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL); // switchEthereumChain request const switchEthereumChainRequest = JSON.stringify({ @@ -100,13 +95,9 @@ describe('Switch Ethereum Chain for two dapps', function () { await unlockWallet(driver); // open two dapps - await openDapp(driver, undefined, DAPP_URL); + const dappOne = await openDapp(driver, undefined, DAPP_URL); await openDapp(driver, undefined, DAPP_ONE_URL); - // Window Handling - const windowHandles = await driver.getAllWindowHandles(); - const dappOne = windowHandles[1]; - // Initiate send transaction on Dapp two await driver.clickElement('#sendButton'); await driver.delay(2000); @@ -172,13 +163,9 @@ describe('Switch Ethereum Chain for two dapps', function () { await unlockWallet(driver); // open two dapps - await openDapp(driver, undefined, DAPP_URL); + const dappOne = await openDapp(driver, undefined, DAPP_URL); await openDapp(driver, undefined, DAPP_ONE_URL); - // Window Handling - let windowHandles = await driver.getAllWindowHandles(); - const dappOne = windowHandles[1]; - // switchEthereumChain request const switchEthereumChainRequest = JSON.stringify({ jsonrpc: '2.0', @@ -217,10 +204,16 @@ describe('Switch Ethereum Chain for two dapps', function () { // Confirm switchEthereumChain with queued pending tx await driver.clickElement({ text: 'Switch network', tag: 'button' }); - // Window handles should only be expanded mm, dapp one, dapp 2 (3 total) + // Window handles should only be expanded mm, dapp one, dapp 2, and the offscreen document + // if this is an MV3 build(3 or 4 total) await driver.wait(async () => { - windowHandles = await driver.getAllWindowHandles(); - return windowHandles.length === 3; + const windowHandles = await driver.getAllWindowHandles(); + const numberOfWindowHandlesToExpect = + process.env.ENABLE_MV3 === 'true' || + process.env.ENABLE_MV3 === undefined + ? 4 + : 3; + return windowHandles.length === numberOfWindowHandlesToExpect; }); }, ); @@ -245,13 +238,9 @@ describe('Switch Ethereum Chain for two dapps', function () { await unlockWallet(driver); // open two dapps - await openDapp(driver, undefined, DAPP_URL); + const dappOne = await openDapp(driver, undefined, DAPP_URL); await openDapp(driver, undefined, DAPP_ONE_URL); - // Window Handling - const windowHandles = await driver.getAllWindowHandles(); - const dappOne = windowHandles[1]; - // switchEthereumChain request const switchEthereumChainRequest = JSON.stringify({ jsonrpc: '2.0', diff --git a/test/e2e/ganache.ts b/test/e2e/seeder/ganache.ts similarity index 100% rename from test/e2e/ganache.ts rename to test/e2e/seeder/ganache.ts diff --git a/test/e2e/tests/account-menu/account-details.spec.js b/test/e2e/tests/account/account-details.spec.js similarity index 100% rename from test/e2e/tests/account-menu/account-details.spec.js rename to test/e2e/tests/account/account-details.spec.js diff --git a/test/e2e/tests/account-menu/account-hide-unhide.spec.js b/test/e2e/tests/account/account-hide-unhide.spec.js similarity index 100% rename from test/e2e/tests/account-menu/account-hide-unhide.spec.js rename to test/e2e/tests/account/account-hide-unhide.spec.js diff --git a/test/e2e/tests/account-menu/account-pin-unpin.spec.js b/test/e2e/tests/account/account-pin-unpin.spec.js similarity index 100% rename from test/e2e/tests/account-menu/account-pin-unpin.spec.js rename to test/e2e/tests/account/account-pin-unpin.spec.js diff --git a/test/e2e/tests/account-menu/add-account.spec.js b/test/e2e/tests/account/add-account.spec.js similarity index 100% rename from test/e2e/tests/account-menu/add-account.spec.js rename to test/e2e/tests/account/add-account.spec.js diff --git a/test/e2e/tests/account-menu/import-flow.spec.js b/test/e2e/tests/account/import-flow.spec.js similarity index 96% rename from test/e2e/tests/account-menu/import-flow.spec.js rename to test/e2e/tests/account/import-flow.spec.js index 4c0a7b0fd973..c4adce8e183a 100644 --- a/test/e2e/tests/account-menu/import-flow.spec.js +++ b/test/e2e/tests/account/import-flow.spec.js @@ -87,6 +87,7 @@ describe('Import flow @no-mmi', function () { // accepts the account password after lock await unlockWallet(driver, { + navigate: false, waitLoginSuccess: false, }); @@ -377,8 +378,11 @@ describe('Import flow @no-mmi', function () { ); }); - it('Connects to a Hardware wallet for trezor', async function () { - if (process.env.ENABLE_MV3) { + it('Connects to a Hardware wallet for lattice', async function () { + if ( + process.env.ENABLE_MV3 === 'true' || + process.env.ENABLE_MV3 === undefined + ) { // Hardware wallets not supported in MV3 build yet this.skip(); } @@ -401,11 +405,15 @@ describe('Import flow @no-mmi', function () { text: 'Add hardware wallet', tag: 'button', }); - await driver.delay(regularDelayMs); + await driver.findClickableElement( + '[data-testid="hardware-connect-close-btn"]', + ); + await driver.clickElement('[data-testid="connect-lattice-btn"]'); + await driver.findClickableElement({ + text: 'Continue', + tag: 'button', + }); - // should open the TREZOR Connect popup - await driver.clickElement('.hw-connect__btn:nth-of-type(2)'); - await driver.delay(largeDelayMs * 2); await driver.clickElement({ text: 'Continue', tag: 'button' }); const allWindows = await driver.waitUntilXWindowHandles(2); diff --git a/test/e2e/tests/incremental-security.spec.js b/test/e2e/tests/account/incremental-security.spec.js similarity index 98% rename from test/e2e/tests/incremental-security.spec.js rename to test/e2e/tests/account/incremental-security.spec.js index 857cd79e3e8d..bdbde3b26101 100644 --- a/test/e2e/tests/incremental-security.spec.js +++ b/test/e2e/tests/account/incremental-security.spec.js @@ -1,6 +1,6 @@ const { strict: assert } = require('assert'); -const { convertToHexValue, withFixtures, openDapp } = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); +const { convertToHexValue, withFixtures, openDapp } = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); const WALLET_PASSWORD = 'correct horse battery staple'; diff --git a/test/e2e/tests/lock-account.spec.js b/test/e2e/tests/account/lock-account.spec.js similarity index 92% rename from test/e2e/tests/lock-account.spec.js rename to test/e2e/tests/account/lock-account.spec.js index a4d23ab7838f..d384ff0020ae 100644 --- a/test/e2e/tests/lock-account.spec.js +++ b/test/e2e/tests/account/lock-account.spec.js @@ -3,8 +3,8 @@ const { defaultGanacheOptions, withFixtures, unlockWallet, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); +} = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); describe('Lock and unlock', function () { it('successfully unlocks after lock', async function () { diff --git a/test/e2e/tests/lockdown.spec.js b/test/e2e/tests/account/lockdown.spec.js similarity index 89% rename from test/e2e/tests/lockdown.spec.js rename to test/e2e/tests/account/lockdown.spec.js index 988463bc13ae..885c468d8fca 100644 --- a/test/e2e/tests/lockdown.spec.js +++ b/test/e2e/tests/account/lockdown.spec.js @@ -3,10 +3,10 @@ const { Browser } = require('selenium-webdriver'); const { getGlobalProperties, testIntrinsic, -} = require('../../helpers/protect-intrinsics-helpers'); -const { convertToHexValue, withFixtures } = require('../helpers'); -const { PAGES } = require('../webdriver/driver'); -const FixtureBuilder = require('../fixture-builder'); +} = require('../../../helpers/protect-intrinsics-helpers'); +const { convertToHexValue, withFixtures } = require('../../helpers'); +const { PAGES } = require('../../webdriver/driver'); +const FixtureBuilder = require('../../fixture-builder'); const isFirefox = process.env.SELENIUM_BROWSER === Browser.FIREFOX; @@ -79,6 +79,7 @@ describe('lockdown', function () { ); await driver.navigate(PAGES.BACKGROUND); + await driver.delay(1000); assert.equal( await driver.executeScript(lockdownTestScript), true, diff --git a/test/e2e/tests/metamask-responsive-ui.spec.js b/test/e2e/tests/account/metamask-responsive-ui.spec.js similarity index 98% rename from test/e2e/tests/metamask-responsive-ui.spec.js rename to test/e2e/tests/account/metamask-responsive-ui.spec.js index 7f80c8380284..fc51eac7c3d4 100644 --- a/test/e2e/tests/metamask-responsive-ui.spec.js +++ b/test/e2e/tests/account/metamask-responsive-ui.spec.js @@ -6,8 +6,8 @@ const { locateAccountBalanceDOM, openActionMenuAndStartSendFlow, unlockWallet, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); +} = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); describe('MetaMask Responsive UI', function () { it('Creating a new wallet @no-mmi', async function () { diff --git a/test/e2e/tests/migrate-old-vault.spec.js b/test/e2e/tests/account/migrate-old-vault.spec.js similarity index 89% rename from test/e2e/tests/migrate-old-vault.spec.js rename to test/e2e/tests/account/migrate-old-vault.spec.js index 93bd07010bbe..610a2afebec3 100644 --- a/test/e2e/tests/migrate-old-vault.spec.js +++ b/test/e2e/tests/account/migrate-old-vault.spec.js @@ -1,6 +1,6 @@ const { strict: assert } = require('assert'); -const { defaultGanacheOptions, withFixtures } = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); +const { defaultGanacheOptions, withFixtures } = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); const lock = async (driver) => { await driver.clickElement('[data-testid="account-options-menu-button"]'); diff --git a/test/e2e/tests/contract-interactions.spec.js b/test/e2e/tests/dapp-interactions/contract-interactions.spec.js similarity index 95% rename from test/e2e/tests/contract-interactions.spec.js rename to test/e2e/tests/dapp-interactions/contract-interactions.spec.js index d59ece2d97ed..a6ebe01575c8 100644 --- a/test/e2e/tests/contract-interactions.spec.js +++ b/test/e2e/tests/dapp-interactions/contract-interactions.spec.js @@ -6,10 +6,10 @@ const { largeDelayMs, WINDOW_TITLES, locateAccountBalanceDOM, -} = require('../helpers'); +} = require('../../helpers'); -const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); -const FixtureBuilder = require('../fixture-builder'); +const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); +const FixtureBuilder = require('../../fixture-builder'); describe('Deploy contract and call contract methods', function () { const smartContract = SMART_CONTRACTS.PIGGYBANK; diff --git a/test/e2e/tests/encrypt-decrypt.spec.js b/test/e2e/tests/dapp-interactions/encrypt-decrypt.spec.js similarity index 98% rename from test/e2e/tests/encrypt-decrypt.spec.js rename to test/e2e/tests/dapp-interactions/encrypt-decrypt.spec.js index e9cac127315b..2f595138d0cf 100644 --- a/test/e2e/tests/encrypt-decrypt.spec.js +++ b/test/e2e/tests/dapp-interactions/encrypt-decrypt.spec.js @@ -5,8 +5,8 @@ const { openDapp, unlockWallet, WINDOW_TITLES, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); +} = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); async function validateEncryptionKey(driver, encryptionKey) { await driver.clickElement('#getEncryptionKeyButton'); diff --git a/test/e2e/tests/eth-subscribe.spec.js b/test/e2e/tests/dapp-interactions/eth-subscribe.spec.js similarity index 96% rename from test/e2e/tests/eth-subscribe.spec.js rename to test/e2e/tests/dapp-interactions/eth-subscribe.spec.js index bbcb4db6a6dd..8ca8ec0cc74c 100644 --- a/test/e2e/tests/eth-subscribe.spec.js +++ b/test/e2e/tests/dapp-interactions/eth-subscribe.spec.js @@ -4,8 +4,8 @@ const { openDapp, DAPP_ONE_URL, unlockWallet, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); +} = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); describe('eth_subscribe', function () { it('only broadcasts subscription notifications on the page that registered the subscription', async function () { diff --git a/test/e2e/tests/failing-contract.spec.js b/test/e2e/tests/dapp-interactions/failing-contract.spec.js similarity index 97% rename from test/e2e/tests/failing-contract.spec.js rename to test/e2e/tests/dapp-interactions/failing-contract.spec.js index ca96f6b971f7..11972d7256c1 100644 --- a/test/e2e/tests/failing-contract.spec.js +++ b/test/e2e/tests/dapp-interactions/failing-contract.spec.js @@ -5,9 +5,9 @@ const { unlockWallet, WINDOW_TITLES, generateGanacheOptions, -} = require('../helpers'); -const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); -const FixtureBuilder = require('../fixture-builder'); +} = require('../../helpers'); +const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); +const FixtureBuilder = require('../../fixture-builder'); describe('Failing contract interaction ', function () { const smartContract = SMART_CONTRACTS.FAILING; diff --git a/test/e2e/tests/provider-api.spec.js b/test/e2e/tests/dapp-interactions/provider-api.spec.js similarity index 97% rename from test/e2e/tests/provider-api.spec.js rename to test/e2e/tests/dapp-interactions/provider-api.spec.js index b2cd456d5f41..ed0bbdf674a4 100644 --- a/test/e2e/tests/provider-api.spec.js +++ b/test/e2e/tests/dapp-interactions/provider-api.spec.js @@ -5,8 +5,8 @@ const { withFixtures, openDapp, unlockWallet, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); +} = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); describe('MetaMask', function () { it('provider should inform dapp when switching networks', async function () { diff --git a/test/e2e/tests/signin-with-ethereum.spec.js b/test/e2e/tests/dapp-interactions/signin-with-ethereum.spec.js similarity index 97% rename from test/e2e/tests/signin-with-ethereum.spec.js rename to test/e2e/tests/dapp-interactions/signin-with-ethereum.spec.js index 251e45023147..5c6e3bb4803c 100644 --- a/test/e2e/tests/signin-with-ethereum.spec.js +++ b/test/e2e/tests/dapp-interactions/signin-with-ethereum.spec.js @@ -6,8 +6,8 @@ const { DAPP_URL, unlockWallet, WINDOW_TITLES, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); +} = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); describe('Sign in with ethereum', function () { it('user should be able to confirm sign in with ethereum', async function () { diff --git a/test/e2e/tests/metrics/app-installed.spec.js b/test/e2e/tests/metrics/app-installed.spec.js index 148bd09bc7d0..cb2ddde78198 100644 --- a/test/e2e/tests/metrics/app-installed.spec.js +++ b/test/e2e/tests/metrics/app-installed.spec.js @@ -5,6 +5,7 @@ const { onboardingBeginCreateNewWallet, onboardingChooseMetametricsOption, getEventPayloads, + tinyDelayMs, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -49,7 +50,7 @@ describe('App Installed Events @no-mmi', function () { }, async ({ driver, mockedEndpoint: mockedEndpoints }) => { await driver.navigate(); - + await driver.delay(tinyDelayMs); await onboardingBeginCreateNewWallet(driver); await onboardingChooseMetametricsOption(driver, true); diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/metrics/errors.spec.js similarity index 99% rename from test/e2e/tests/errors.spec.js rename to test/e2e/tests/metrics/errors.spec.js index 800c7e444536..0961b46c5aa5 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/metrics/errors.spec.js @@ -5,9 +5,9 @@ const { get, has, set, unset, cloneDeep } = require('lodash'); const { Browser } = require('selenium-webdriver'); const { format } = require('prettier'); const { isObject } = require('@metamask/utils'); -const { SENTRY_UI_STATE } = require('../../../app/scripts/lib/setupSentry'); -const FixtureBuilder = require('../fixture-builder'); -const { convertToHexValue, withFixtures } = require('../helpers'); +const { SENTRY_UI_STATE } = require('../../../../app/scripts/lib/setupSentry'); +const FixtureBuilder = require('../../fixture-builder'); +const { convertToHexValue, withFixtures } = require('../../helpers'); /** * Derive a UI state field from a background state field. diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json similarity index 99% rename from test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json rename to test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index d8d7b91b268c..f1f60ae568ed 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -176,6 +176,7 @@ "useNativeCurrencyAsPrimaryCurrency": true, "petnamesEnabled": true }, + "useExternalServices": "boolean", "ipfsGateway": "string", "isIpfsGatewayEnabled": "boolean", "useAddressBarEnsResolution": true, @@ -185,6 +186,7 @@ "snapsAddSnapAccountModalDismissed": "boolean", "useExternalNameSources": "boolean", "useTransactionSimulations": true, + "enableMV3TimestampSave": true, "selectedAddress": "string" }, "SelectedNetworkController": { "domains": "object" }, diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json similarity index 99% rename from test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json rename to test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index c60b137e8222..be4d78846a6b 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -101,6 +101,7 @@ "usePhishDetect": true, "dismissSeedBackUpReminder": true, "disabledRpcMethodPreferences": { "eth_sign": false }, + "useExternalServices": "boolean", "useMultiAccountBalanceChecker": true, "hasDismissedOpenSeaToBlockaidBanner": false, "useSafeChainsListValidation": true, @@ -125,6 +126,7 @@ "snapsAddSnapAccountModalDismissed": "boolean", "useExternalNameSources": "boolean", "useTransactionSimulations": true, + "enableMV3TimestampSave": true, "selectedAddress": "string", "metaMetricsId": "fake-metrics-id", "eventsBeforeMetricsOptIn": "object", diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json similarity index 97% rename from test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json rename to test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json index bd6629ad6d32..46abd96e24bf 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json @@ -85,6 +85,7 @@ "PreferencesController": { "advancedGasFee": null, "currentLocale": "en", + "useExternalServices": "boolean", "dismissSeedBackUpReminder": true, "featureFlags": {}, "forgottenPassword": false, @@ -114,9 +115,7 @@ "useMultiAccountBalanceChecker": true, "useRequestQueue": false }, - "SelectedNetworkController": { - "domains": "object" - }, + "SelectedNetworkController": { "domains": "object" }, "SmartTransactionsController": { "smartTransactionsState": { "fees": {}, diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json similarity index 97% rename from test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json rename to test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json index 8f7ccefba379..14d2c7ea9f74 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -85,6 +85,7 @@ "PreferencesController": { "advancedGasFee": null, "currentLocale": "en", + "useExternalServices": "boolean", "dismissSeedBackUpReminder": true, "featureFlags": {}, "forgottenPassword": false, @@ -114,9 +115,7 @@ "useMultiAccountBalanceChecker": true, "useRequestQueue": false }, - "SelectedNetworkController": { - "domains": "object" - }, + "SelectedNetworkController": { "domains": "object" }, "SmartTransactionsController": { "smartTransactionsState": { "fees": {}, diff --git a/test/e2e/tests/metrics/swaps.spec.js b/test/e2e/tests/metrics/swaps.spec.js index 4e5d3db29903..ac76eb1fbdd6 100644 --- a/test/e2e/tests/metrics/swaps.spec.js +++ b/test/e2e/tests/metrics/swaps.spec.js @@ -29,7 +29,7 @@ const { FEATURE_FLAGS_API_MOCK_RESULT, TRADES_API_MOCK_RESULT, NETWORKS_2_API_MOCK_RESULT, -} = require('./mock-data'); +} = require('../../../data/mock-data'); const numberOfSegmentRequests = 19; diff --git a/test/e2e/tests/custom-rpc-history.spec.js b/test/e2e/tests/network/custom-rpc-history.spec.js similarity index 99% rename from test/e2e/tests/custom-rpc-history.spec.js rename to test/e2e/tests/network/custom-rpc-history.spec.js index e16c90a764e6..7df8746a2e62 100644 --- a/test/e2e/tests/custom-rpc-history.spec.js +++ b/test/e2e/tests/network/custom-rpc-history.spec.js @@ -5,8 +5,8 @@ const { withFixtures, regularDelayMs, unlockWallet, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); +} = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); describe('Custom RPC history', function () { it(`creates first custom RPC entry`, async function () { diff --git a/test/e2e/tests/network/update-network.spec.ts b/test/e2e/tests/network/update-network.spec.ts index b21993683e2e..1c09b88a621d 100644 --- a/test/e2e/tests/network/update-network.spec.ts +++ b/test/e2e/tests/network/update-network.spec.ts @@ -38,7 +38,8 @@ const selectors = { const inputData = { networkName: 'Update Network', rpcUrl: 'test', - chainId: '0x539', + chainId_part1: '0x53', + chainId_part2: '9', }; async function navigateToEditNetwork(driver: Driver) { @@ -63,7 +64,14 @@ describe('Update Network:', function (this: Suite) { selectors.networkNameInputField, inputData.networkName, ); - await driver.fill(selectors.chainIdInputField, inputData.chainId); + + // We fill in the chain ID in two steps, allowing the error message time to disappear once the field is correctly completed. + await driver.fill(selectors.chainIdInputField, inputData.chainId_part1); + const chainIdInputField = await driver.findElement( + selectors.chainIdInputField, + ); + await chainIdInputField.sendKeys(inputData.chainId_part2); + await driver.clickElement(selectors.saveButton); // Validate the network name is updated diff --git a/test/e2e/tests/onboarding/onboarding.spec.js b/test/e2e/tests/onboarding/onboarding.spec.js index 68fd12e07a95..de0939ac2282 100644 --- a/test/e2e/tests/onboarding/onboarding.spec.js +++ b/test/e2e/tests/onboarding/onboarding.spec.js @@ -312,6 +312,37 @@ describe('MetaMask onboarding @no-mmi', function () { ); }); + it('User can turn off basic functionality in advanced configurations', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder({ onboarding: true }).build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await driver.navigate(); + await importSRPOnboardingFlow( + driver, + TEST_SEED_PHRASE, + WALLET_PASSWORD, + ); + + await driver.clickElement({ text: 'Advanced configuration', tag: 'a' }); + await driver.clickElement( + '[data-testid="basic-functionality-toggle"] .toggle-button', + ); + await driver.clickElement('[id="basic-configuration-checkbox"]'); + await driver.clickElement({ text: 'Turn off', tag: 'button' }); + await driver.clickElement({ text: 'Done', tag: 'button' }); + // Check that the 'basic functionality is off' banner is displayed on the home screen after onboarding completion + await driver.waitForSelector({ + text: 'Basic functionality is off', + css: '.mm-banner-alert', + }); + }, + ); + }); + it("doesn't make any network requests to infura before onboarding is completed", async function () { async function mockInfura(mockServer) { const infuraUrl = @@ -448,6 +479,137 @@ describe('MetaMask onboarding @no-mmi', function () { ); }); + it("doesn't make any network requests to infura before onboarding by import is completed", async function () { + async function mockInfura(mockServer) { + const infuraUrl = + 'https://mainnet.infura.io/v3/00000000000000000000000000000000'; + const sampleAddress = '1111111111111111111111111111111111111111'; + + return [ + await mockServer + .forPost(infuraUrl) + .withJsonBodyIncluding({ method: 'eth_blockNumber' }) + .thenCallback(() => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '1111111111111111', + result: '0x1', + }, + }; + }), + await mockServer + .forPost(infuraUrl) + .withJsonBodyIncluding({ method: 'eth_getBalance' }) + .thenCallback(() => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '1111111111111111', + result: '0x1', + }, + }; + }), + await mockServer + .forPost(infuraUrl) + .withJsonBodyIncluding({ method: 'eth_getBlockByNumber' }) + .thenCallback(() => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '1111111111111111', + result: {}, + }, + }; + }), + await mockServer + .forPost(infuraUrl) + .withJsonBodyIncluding({ method: 'eth_call' }) + .thenCallback(() => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '1111111111111111', + result: `0x000000000000000000000000${sampleAddress}`, + }, + }; + }), + await mockServer + .forPost(infuraUrl) + .withJsonBodyIncluding({ method: 'net_version' }) + .thenCallback(() => { + return { + statusCode: 200, + json: { id: 8262367391254633, jsonrpc: '2.0', result: '1337' }, + }; + }), + ]; + } + + await withFixtures( + { + fixtures: new FixtureBuilder({ onboarding: true }) + .withNetworkControllerOnMainnet() + .build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.fullTitle(), + testSpecificMock: mockInfura, + }, + async ({ driver, mockedEndpoint: mockedEndpoints }) => { + const password = 'password'; + + await driver.navigate(); + + await importSRPOnboardingFlow(driver, TEST_SEED_PHRASE, password); + + await driver.delay(regularDelayMs); + + for (let i = 0; i < mockedEndpoints.length; i += 1) { + const mockedEndpoint = await mockedEndpoints[i]; + const requests = await mockedEndpoint.getSeenRequests(); + + assert.equal( + requests.length, + 0, + `${mockedEndpoints[i]} should make no requests before onboarding`, + ); + } + + // complete + await driver.clickElement('[data-testid="onboarding-complete-done"]'); + + // pin extension + await driver.clickElement('[data-testid="pin-extension-next"]'); + await driver.clickElement('[data-testid="pin-extension-done"]'); + + // pin extension walkthrough screen + await driver.findElement('[data-testid="account-menu-icon"]'); + // requests happen here! + + for (let i = 0; i < mockedEndpoints.length; i += 1) { + const mockedEndpoint = await mockedEndpoints[i]; + + await driver.wait(async () => { + const isPending = await mockedEndpoint.isPending(); + return isPending === false; + }, driver.timeout); + + const requests = await mockedEndpoint.getSeenRequests(); + + assert.equal( + requests.length > 0, + true, + `${mockedEndpoints[i]} should make requests after onboarding`, + ); + } + }, + ); + }); + it('Provides an onboarding path for a user who has restored their account from state persistence failure', async function () { // We don't use onboarding:true here because we want there to be a vault, // simulating what will happen when a user eventually restores their vault diff --git a/test/e2e/mock-page-with-disallowed-iframe/index.html b/test/e2e/tests/phishing-controller/mock-page-with-disallowed-iframe/index.html similarity index 100% rename from test/e2e/mock-page-with-disallowed-iframe/index.html rename to test/e2e/tests/phishing-controller/mock-page-with-disallowed-iframe/index.html diff --git a/test/e2e/mock-page-with-iframe/index.html b/test/e2e/tests/phishing-controller/mock-page-with-iframe/index.html similarity index 100% rename from test/e2e/mock-page-with-iframe/index.html rename to test/e2e/tests/phishing-controller/mock-page-with-iframe/index.html diff --git a/test/e2e/tests/phishing-controller/phishing-detection.spec.js b/test/e2e/tests/phishing-controller/phishing-detection.spec.js index 491b4726f9ad..a749ef743882 100644 --- a/test/e2e/tests/phishing-controller/phishing-detection.spec.js +++ b/test/e2e/tests/phishing-controller/phishing-detection.spec.js @@ -76,7 +76,7 @@ describe('Phishing Detection', function () { }); }, dapp: true, - dappPaths: ['mock-page-with-iframe'], + dappPaths: ['./tests/phishing-controller/mock-page-with-iframe'], dappOptions: { numberOfDapps: 2, }, @@ -114,7 +114,9 @@ describe('Phishing Detection', function () { }); }, dapp: true, - dappPaths: ['mock-page-with-disallowed-iframe'], + dappPaths: [ + './tests/phishing-controller/mock-page-with-disallowed-iframe', + ], dappOptions: { numberOfDapps: 2, }, @@ -263,7 +265,9 @@ describe('Phishing Detection', function () { }); }, dapp: true, - dappPaths: ['mock-page-with-disallowed-iframe'], + dappPaths: [ + './tests/phishing-controller/mock-page-with-disallowed-iframe', + ], dappOptions: { numberOfDapps: 2, }, diff --git a/test/e2e/tests/ppom/migrate-opensea-to-blockaid-banner.spec.js b/test/e2e/tests/ppom/migrate-opensea-to-blockaid-banner.spec.js index 5639e607c46d..77430a250f1e 100644 --- a/test/e2e/tests/ppom/migrate-opensea-to-blockaid-banner.spec.js +++ b/test/e2e/tests/ppom/migrate-opensea-to-blockaid-banner.spec.js @@ -1,12 +1,12 @@ -const { connectAccountToTestDapp } = require('../../accounts/common'); const FixtureBuilder = require('../../fixture-builder'); const { defaultGanacheOptions, - unlockWallet, + logInWithBalanceValidation, withFixtures, openDapp, WINDOW_TITLES, } = require('../../helpers'); +const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); describe('Migrate Opensea to Blockaid Banner @no-mmi', function () { const ONE_CLICK_CONFIRMATIONS_USING_BLOCKAID = [ @@ -31,17 +31,15 @@ describe('Migrate Opensea to Blockaid Banner @no-mmi', function () { .withPreferencesController({ hasMigratedFromOpenSeaToBlockaid: true, }) + .withPermissionControllerConnectedToTestDapp() .build(), ganacheOptions: defaultGanacheOptions, title: this.test.fullTitle(), }, - async ({ driver }) => { - await unlockWallet(driver); + async ({ driver, ganacheServer }) => { + await logInWithBalanceValidation(driver, ganacheServer); await openDapp(driver); - await connectAccountToTestDapp(driver); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); await driver.clickElement(`#${confirmation.testDAppBtnId}`); await driver.delay(2000); @@ -53,37 +51,25 @@ describe('Migrate Opensea to Blockaid Banner @no-mmi', function () { }); it('Shows up on Token Approval transaction confirmations', async function () { + const smartContract = SMART_CONTRACTS.HST; await withFixtures( { dapp: true, fixtures: new FixtureBuilder() .withPreferencesController({ hasMigratedFromOpenSeaToBlockaid: true }) + .withPermissionControllerConnectedToTestDapp() .build(), ganacheOptions: defaultGanacheOptions, + smartContract, title: this.test.fullTitle(), }, - async ({ driver }) => { - await unlockWallet(driver); - await openDapp(driver); - - await connectAccountToTestDapp(driver); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.findClickableElement('#createToken'); - await driver.clickElement('#createToken'); - await driver.delay(2000); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.findClickableElements({ - text: 'Confirm', - tag: 'button', - }); - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - }); + async ({ driver, contractRegistry, ganacheServer }) => { + const contractAddress = await contractRegistry.getContractAddress( + smartContract, + ); + await logInWithBalanceValidation(driver, ganacheServer); + await openDapp(driver, contractAddress); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); await driver.clickElement({ text: 'Approve Tokens', tag: 'button' }); await driver.delay(2000); diff --git a/test/mocks/json-rpc-result.ts b/test/e2e/tests/ppom/mocks/json-rpc-result.ts similarity index 100% rename from test/mocks/json-rpc-result.ts rename to test/e2e/tests/ppom/mocks/json-rpc-result.ts diff --git a/test/e2e/mock-server-json-rpc.ts b/test/e2e/tests/ppom/mocks/mock-server-json-rpc.ts similarity index 96% rename from test/e2e/mock-server-json-rpc.ts rename to test/e2e/tests/ppom/mocks/mock-server-json-rpc.ts index d49b8d523b04..f539df033b02 100644 --- a/test/e2e/mock-server-json-rpc.ts +++ b/test/e2e/tests/ppom/mocks/mock-server-json-rpc.ts @@ -1,5 +1,5 @@ import { MockttpServer } from 'mockttp'; -import { mockJsonRpcResult } from '../mocks/json-rpc-result'; +import { mockJsonRpcResult } from './json-rpc-result'; type RequestConfig = [ method: string, diff --git a/test/e2e/tests/ppom/ppom-blockaid-alert-erc20-approval.spec.js b/test/e2e/tests/ppom/ppom-blockaid-alert-erc20-approval.spec.js index 23a62746a299..3fad82ef28a2 100644 --- a/test/e2e/tests/ppom/ppom-blockaid-alert-erc20-approval.spec.js +++ b/test/e2e/tests/ppom/ppom-blockaid-alert-erc20-approval.spec.js @@ -1,6 +1,5 @@ const { strict: assert } = require('assert'); const FixtureBuilder = require('../../fixture-builder'); -const { mockServerJsonRpc } = require('../../mock-server-json-rpc'); const { WINDOW_TITLES, @@ -9,6 +8,7 @@ const { unlockWallet, withFixtures, } = require('../../helpers'); +const { mockServerJsonRpc } = require('./mocks/mock-server-json-rpc'); const bannerAlertSelector = '[data-testid="security-provider-banner-alert"]'; diff --git a/test/e2e/tests/ppom/ppom-blockaid-alert-erc20-transfer.spec.js b/test/e2e/tests/ppom/ppom-blockaid-alert-erc20-transfer.spec.js index 22d343e70478..4122695dfb50 100644 --- a/test/e2e/tests/ppom/ppom-blockaid-alert-erc20-transfer.spec.js +++ b/test/e2e/tests/ppom/ppom-blockaid-alert-erc20-transfer.spec.js @@ -1,6 +1,5 @@ const { strict: assert } = require('assert'); const FixtureBuilder = require('../../fixture-builder'); -const { mockServerJsonRpc } = require('../../mock-server-json-rpc'); const { WINDOW_TITLES, @@ -9,6 +8,7 @@ const { unlockWallet, withFixtures, } = require('../../helpers'); +const { mockServerJsonRpc } = require('./mocks/mock-server-json-rpc'); const bannerAlertSelector = '[data-testid="security-provider-banner-alert"]'; diff --git a/test/e2e/tests/ppom/ppom-blockaid-alert-metrics.spec.js b/test/e2e/tests/ppom/ppom-blockaid-alert-metrics.spec.js index 26b9d63793f9..39fb16cc4135 100644 --- a/test/e2e/tests/ppom/ppom-blockaid-alert-metrics.spec.js +++ b/test/e2e/tests/ppom/ppom-blockaid-alert-metrics.spec.js @@ -1,6 +1,5 @@ const { strict: assert } = require('assert'); const FixtureBuilder = require('../../fixture-builder'); -const { mockServerJsonRpc } = require('../../mock-server-json-rpc'); const { WINDOW_TITLES, defaultGanacheOptions, @@ -10,6 +9,7 @@ const { getEventPayloads, switchToNotificationWindow, } = require('../../helpers'); +const { mockServerJsonRpc } = require('./mocks/mock-server-json-rpc'); const selectedAddress = '0x5cfe73b6021e818b776b421b1c4db2474086a7e1'; const selectedAddressWithoutPrefix = '5cfe73b6021e818b776b421b1c4db2474086a7e1'; diff --git a/test/e2e/tests/ppom/ppom-blockaid-alert-networks-support.spec.js b/test/e2e/tests/ppom/ppom-blockaid-alert-networks-support.spec.js index 0d6f66292342..719f8cbdc16b 100644 --- a/test/e2e/tests/ppom/ppom-blockaid-alert-networks-support.spec.js +++ b/test/e2e/tests/ppom/ppom-blockaid-alert-networks-support.spec.js @@ -1,6 +1,5 @@ const { strict: assert } = require('assert'); const FixtureBuilder = require('../../fixture-builder'); -const { mockServerJsonRpc } = require('../../mock-server-json-rpc'); const { WINDOW_TITLES, defaultGanacheOptions, @@ -8,6 +7,7 @@ const { unlockWallet, withFixtures, } = require('../../helpers'); +const { mockServerJsonRpc } = require('./mocks/mock-server-json-rpc'); async function mockInfura(mockServer) { await mockServerJsonRpc(mockServer, [ diff --git a/test/e2e/tests/ppom/ppom-blockaid-alert-simple-send.spec.js b/test/e2e/tests/ppom/ppom-blockaid-alert-simple-send.spec.js index c354ac738324..7525fe423a69 100644 --- a/test/e2e/tests/ppom/ppom-blockaid-alert-simple-send.spec.js +++ b/test/e2e/tests/ppom/ppom-blockaid-alert-simple-send.spec.js @@ -1,6 +1,5 @@ const { strict: assert } = require('assert'); const FixtureBuilder = require('../../fixture-builder'); -const { mockServerJsonRpc } = require('../../mock-server-json-rpc'); const { defaultGanacheOptions, @@ -8,6 +7,7 @@ const { sendScreenToConfirmScreen, unlockWallet, } = require('../../helpers'); +const { mockServerJsonRpc } = require('./mocks/mock-server-json-rpc'); const bannerAlertSelector = '[data-testid="security-provider-banner-alert"]'; const mockMaliciousAddress = '0x5fbdb2315678afecb367f032d93f642f64180aa3'; diff --git a/test/e2e/tests/ppom/ppom-blockaid-alert-trade-order-farming.spec.js b/test/e2e/tests/ppom/ppom-blockaid-alert-trade-order-farming.spec.js index ce59742f6306..8f2debc7b4f2 100644 --- a/test/e2e/tests/ppom/ppom-blockaid-alert-trade-order-farming.spec.js +++ b/test/e2e/tests/ppom/ppom-blockaid-alert-trade-order-farming.spec.js @@ -1,6 +1,5 @@ const { strict: assert } = require('assert'); const FixtureBuilder = require('../../fixture-builder'); -const { mockServerJsonRpc } = require('../../mock-server-json-rpc'); const { WINDOW_TITLES, @@ -9,6 +8,7 @@ const { unlockWallet, withFixtures, } = require('../../helpers'); +const { mockServerJsonRpc } = require('./mocks/mock-server-json-rpc'); const bannerAlertSelector = '[data-testid="security-provider-banner-alert"]'; diff --git a/test/e2e/tests/ppom/ppom-blockaid-alert.spec.js b/test/e2e/tests/ppom/ppom-blockaid-alert.spec.js index 413946473c9f..34ede993a476 100644 --- a/test/e2e/tests/ppom/ppom-blockaid-alert.spec.js +++ b/test/e2e/tests/ppom/ppom-blockaid-alert.spec.js @@ -1,6 +1,5 @@ const { strict: assert } = require('assert'); const FixtureBuilder = require('../../fixture-builder'); -const { mockServerJsonRpc } = require('../../mock-server-json-rpc'); const { WINDOW_TITLES, @@ -10,6 +9,7 @@ const { withFixtures, switchToNotificationWindow, } = require('../../helpers'); +const { mockServerJsonRpc } = require('./mocks/mock-server-json-rpc'); const bannerAlertSelector = '[data-testid="security-provider-banner-alert"]'; const selectedAddress = '0x5cfe73b6021e818b776b421b1c4db2474086a7e1'; diff --git a/test/e2e/tests/ppom/ppom-blockaid-setApprovalForAll-farming.spec.js b/test/e2e/tests/ppom/ppom-blockaid-setApprovalForAll-farming.spec.js index 0c918eb26391..8939ad0b180e 100644 --- a/test/e2e/tests/ppom/ppom-blockaid-setApprovalForAll-farming.spec.js +++ b/test/e2e/tests/ppom/ppom-blockaid-setApprovalForAll-farming.spec.js @@ -1,6 +1,5 @@ const { strict: assert } = require('assert'); const FixtureBuilder = require('../../fixture-builder'); -const { mockServerJsonRpc } = require('../../mock-server-json-rpc'); const { WINDOW_TITLES, @@ -9,6 +8,7 @@ const { unlockWallet, withFixtures, } = require('../../helpers'); +const { mockServerJsonRpc } = require('./mocks/mock-server-json-rpc'); const bannerAlertSelector = '[data-testid="security-provider-banner-alert"]'; diff --git a/test/e2e/tests/privacy/basic-functionality.spec.js b/test/e2e/tests/privacy/basic-functionality.spec.js new file mode 100644 index 000000000000..eea5e02229a4 --- /dev/null +++ b/test/e2e/tests/privacy/basic-functionality.spec.js @@ -0,0 +1,119 @@ +const { strict: assert } = require('assert'); +const { + TEST_SEED_PHRASE, + withFixtures, + importSRPOnboardingFlow, + WALLET_PASSWORD, + tinyDelayMs, + defaultGanacheOptions, +} = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); + +async function mockApis(mockServer) { + return [ + await mockServer + .forGet('https://token-api.metaswap.codefi.network/tokens/1') + .thenCallback(() => { + return { + statusCode: 200, + body: [{ fakedata: true }], + }; + }), + await mockServer + .forGet('https://min-api.cryptocompare.com/data/price') + .withQuery({ fsym: 'ETH', tsyms: 'USD' }) + .thenCallback(() => { + return { + statusCode: 200, + json: { + fakedata: 0, + }, + }; + }), + ]; +} + +describe('MetaMask onboarding @no-mmi', function () { + it('should prevent network requests to basic functionality endpoints when the basica functionality toggle is off', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder({ onboarding: true }).build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.fullTitle(), + testSpecificMock: mockApis, + }, + async ({ driver, mockedEndpoint: mockedEndpoints }) => { + await driver.navigate(); + await importSRPOnboardingFlow( + driver, + TEST_SEED_PHRASE, + WALLET_PASSWORD, + ); + + await driver.clickElement({ text: 'Advanced configuration', tag: 'a' }); + await driver.clickElement( + '[data-testid="basic-functionality-toggle"] .toggle-button', + ); + await driver.clickElement('[id="basic-configuration-checkbox"]'); + await driver.clickElement({ text: 'Turn off', tag: 'button' }); + await driver.clickElement( + '[data-testid="currency-rate-check-toggle"] .toggle-button', + ); + await driver.clickElement({ text: 'Done', tag: 'button' }); + + await driver.clickElement('[data-testid="network-display"]'); + + await driver.clickElement({ text: 'Ethereum Mainnet', tag: 'p' }); + await driver.delay(tinyDelayMs); + + for (let i = 0; i < mockedEndpoints.length; i += 1) { + const requests = await mockedEndpoints[i].getSeenRequests(); + + assert.equal( + requests.length, + 0, + `${mockedEndpoints[i]} should make requests after onboarding`, + ); + } + }, + ); + }); + + it('should not prevent network requests to basic functionality endpoints when the basica functionality toggle is on', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder({ onboarding: true }).build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.fullTitle(), + testSpecificMock: mockApis, + }, + async ({ driver, mockedEndpoint: mockedEndpoints }) => { + await driver.navigate(); + await importSRPOnboardingFlow( + driver, + TEST_SEED_PHRASE, + WALLET_PASSWORD, + ); + + await driver.clickElement({ text: 'Advanced configuration', tag: 'a' }); + + await driver.clickElement({ text: 'Done', tag: 'button' }); + + await driver.clickElement('[data-testid="network-display"]'); + + await driver.clickElement({ text: 'Ethereum Mainnet', tag: 'p' }); + await driver.delay(tinyDelayMs); + + for (let i = 0; i < mockedEndpoints.length; i += 1) { + const requests = await mockedEndpoints[i].getSeenRequests(); + + assert.equal( + requests.length, + 1, + `${mockedEndpoints[i]} should make requests after onboarding`, + ); + } + }, + ); + }); +}); diff --git a/test/e2e/tests/request-queuing/multiple-networks-dapps-txs.spec.js b/test/e2e/tests/request-queuing/multiple-networks-dapps-txs.spec.js index c61a785f8e3a..1bd2a32030ff 100644 --- a/test/e2e/tests/request-queuing/multiple-networks-dapps-txs.spec.js +++ b/test/e2e/tests/request-queuing/multiple-networks-dapps-txs.spec.js @@ -146,14 +146,7 @@ describe('Request Queuing for Multiple Dapps and Txs on different networks.', fu await driver.switchToWindowWithTitle( WINDOW_TITLES.ExtensionInFullScreenView, ); - - // TODO: Reload fix to have the confirmations show - await driver.executeScript(`window.location.reload()`); - - // Second Switch Network - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); + await driver.clickElement('[data-testid="home__activity-tab"]'); // Check for unconfirmed transaction in tx list await driver.wait(async () => { diff --git a/test/e2e/tests/request-queuing/ui.spec.js b/test/e2e/tests/request-queuing/ui.spec.js index 8e7ab0fadb0d..e85d87226e84 100644 --- a/test/e2e/tests/request-queuing/ui.spec.js +++ b/test/e2e/tests/request-queuing/ui.spec.js @@ -64,8 +64,25 @@ async function openDappAndSwitchChain(driver, dappUrl, chainId) { async function selectDappClickSendGetNetwork(driver, dappUrl) { await driver.switchToWindowWithUrl(dappUrl); + // Windows: MetaMask, TestDapp1, TestDapp2 + const expectedWindowHandles = 3; + await driver.waitUntilXWindowHandles(expectedWindowHandles); + const currentWindowHandles = await driver.getAllWindowHandles(); await driver.clickElement('#sendButton'); - await switchToNotificationWindow(driver, 4); + + // Under mv3, we don't need to add to the current number of window handles + // because the offscreen document returned by getAllWindowHandles provides + // an extra window handle + const newWindowHandles = await driver.waitUntilXWindowHandles( + process.env.ENABLE_MV3 === 'true' || process.env.ENABLE_MV3 === undefined + ? currentWindowHandles.length + : currentWindowHandles.length + 1, + ); + const [newNotificationWindowHandle] = newWindowHandles.filter( + (h) => !currentWindowHandles.includes(h), + ); + await driver.switchToWindow(newNotificationWindowHandle); + const networkPill = await driver.findElement( '[data-testid="network-display"]', ); diff --git a/test/e2e/tests/settings/full-size-view-settings.spec.js b/test/e2e/tests/settings/full-size-view-settings.spec.js index 6e93f96babce..bef81caf6183 100644 --- a/test/e2e/tests/settings/full-size-view-settings.spec.js +++ b/test/e2e/tests/settings/full-size-view-settings.spec.js @@ -33,28 +33,25 @@ describe('Full-size View Setting @no-mmi', function () { await unlockWallet(driver); await toggleFullSizeViewSetting(driver); await openDapp(driver); + const windowHandlesPreClick = await driver.waitUntilXWindowHandles( + 2, + 1000, + 10000, + ); await driver.clickElement('#maliciousPermit'); // Opens the extension in popup view. - const windowHandles = await driver.waitUntilXWindowHandles( + const windowHandlesPostClick = await driver.waitUntilXWindowHandles( 3, 1000, 10000, ); - const fullScreenWindowTitle = await driver.getWindowTitleByHandlerId( - windowHandles[0], + const [newWindowHandle] = windowHandlesPostClick.filter( + (handleId) => !windowHandlesPreClick.includes(handleId), ); - const dappWindowTitle = await driver.getWindowTitleByHandlerId( - windowHandles[1], - ); - const popUpWindowTitle = await driver.getWindowTitleByHandlerId( - windowHandles[2], + const newWindowTitle = await driver.getWindowTitleByHandlerId( + newWindowHandle, ); - assert.equal( - fullScreenWindowTitle, - WINDOW_TITLES.ExtensionInFullScreenView, - ); - assert.equal(dappWindowTitle, WINDOW_TITLES.TestDApp); - assert.equal(popUpWindowTitle, WINDOW_TITLES.Dialog); + assert.equal(newWindowTitle, WINDOW_TITLES.Dialog); }, ); }); diff --git a/test/e2e/tests/localization.spec.js b/test/e2e/tests/settings/localization.spec.js similarity index 92% rename from test/e2e/tests/localization.spec.js rename to test/e2e/tests/settings/localization.spec.js index 7c8c35d3d169..707cc120e578 100644 --- a/test/e2e/tests/localization.spec.js +++ b/test/e2e/tests/settings/localization.spec.js @@ -3,8 +3,8 @@ const { defaultGanacheOptions, withFixtures, unlockWallet, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); +} = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); describe('Localization', function () { it('can correctly display Philippine peso symbol and code', async function () { diff --git a/test/e2e/tests/settings-security-reveal-srp.spec.js b/test/e2e/tests/settings/settings-security-reveal-srp.spec.js similarity index 96% rename from test/e2e/tests/settings-security-reveal-srp.spec.js rename to test/e2e/tests/settings/settings-security-reveal-srp.spec.js index 59affd6f4bf7..f99e606fb815 100644 --- a/test/e2e/tests/settings-security-reveal-srp.spec.js +++ b/test/e2e/tests/settings/settings-security-reveal-srp.spec.js @@ -5,9 +5,9 @@ const { completeSRPRevealQuiz, tapAndHoldToRevealSRP, closeSRPReveal, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); -const { tEn } = require('../../lib/i18n-helpers'); +} = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); +const { tEn } = require('../../../lib/i18n-helpers'); describe('Reveal SRP through settings', function () { const testPassword = 'correct horse battery staple'; diff --git a/test/e2e/tests/terms-of-use.spec.js b/test/e2e/tests/settings/terms-of-use.spec.js similarity index 93% rename from test/e2e/tests/terms-of-use.spec.js rename to test/e2e/tests/settings/terms-of-use.spec.js index 842f7135b179..87c2c2d0018d 100644 --- a/test/e2e/tests/terms-of-use.spec.js +++ b/test/e2e/tests/settings/terms-of-use.spec.js @@ -3,8 +3,8 @@ const { defaultGanacheOptions, withFixtures, unlockWallet, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); +} = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); describe('Terms of use', function () { it('accepts the updated terms of use @no-mmi', async function () { diff --git a/test/e2e/synchronous-injection/index.html b/test/e2e/tests/synchronous-injection/index.html similarity index 100% rename from test/e2e/synchronous-injection/index.html rename to test/e2e/tests/synchronous-injection/index.html diff --git a/test/e2e/synchronous-injection/synchronous-injection.spec.js b/test/e2e/tests/synchronous-injection/synchronous-injection.spec.js similarity index 85% rename from test/e2e/synchronous-injection/synchronous-injection.spec.js rename to test/e2e/tests/synchronous-injection/synchronous-injection.spec.js index a23332b472d5..917e289f320f 100644 --- a/test/e2e/synchronous-injection/synchronous-injection.spec.js +++ b/test/e2e/tests/synchronous-injection/synchronous-injection.spec.js @@ -1,7 +1,7 @@ const { strict: assert } = require('assert'); -const { withFixtures, unlockWallet, openDapp } = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); -const createStaticServer = require('../../../development/create-static-server'); +const { withFixtures, unlockWallet, openDapp } = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); +const createStaticServer = require('../../../../development/create-static-server'); const dappPort = 8080; diff --git a/test/e2e/tests/tokens/import-tokens.spec.js b/test/e2e/tests/tokens/import-tokens.spec.js index ffd1d474cea8..a04c67a410ad 100644 --- a/test/e2e/tests/tokens/import-tokens.spec.js +++ b/test/e2e/tests/tokens/import-tokens.spec.js @@ -85,8 +85,9 @@ describe('Import flow', function () { '[data-testid="token-list-loading-message"]', ); - const items = await driver.findElements('.multichain-token-list-item'); - assert.equal(items.length, 4); + const expectedTokenListElementsAreFound = + await driver.elementCountBecomesN('.multichain-token-list-item', 4); + assert.equal(expectedTokenListElementsAreFound, true); }, ); }); diff --git a/test/e2e/tests/transaction/edit-gas-fee.spec.js b/test/e2e/tests/transaction/edit-gas-fee.spec.js index fcd15e964de1..42fd2c4c2111 100644 --- a/test/e2e/tests/transaction/edit-gas-fee.spec.js +++ b/test/e2e/tests/transaction/edit-gas-fee.spec.js @@ -1,10 +1,10 @@ const { strict: assert } = require('assert'); const { - getWindowHandles, withFixtures, openDapp, unlockWallet, generateGanacheOptions, + WINDOW_TITLES, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -176,17 +176,16 @@ describe('Editing Confirm Transaction', function () { }); // check transaction in extension popup - const windowHandles = await getWindowHandles(driver, 3); - await driver.switchToWindow(windowHandles.popup); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); await driver.waitForSelector({ text: 'Site suggested', }); await driver.clickElement('[data-testid="edit-gas-fee-icon"]'); - await driver.waitForSelector({ - text: 'sec', - tag: 'span', - }); + // -- should render the popover with no error + // this is to test in MV3 a racing issue when request for suggestedGasFees is not fetched properly + // some data would not be defined yet + await driver.waitForSelector('.edit-gas-fee-popover'); await driver.clickElement( '[data-testid="edit-gas-fee-item-dappSuggested"]', ); @@ -207,7 +206,9 @@ describe('Editing Confirm Transaction', function () { await driver.clickElement({ text: 'Confirm', tag: 'button' }); // transaction should correct values in activity tab - await driver.switchToWindow(windowHandles.extension); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); await driver.clickElement('[data-testid="home__activity-tab"]'); await driver.wait(async () => { const confirmedTxes = await driver.findElements( diff --git a/test/e2e/tests/transaction/multiple-transactions.spec.js b/test/e2e/tests/transaction/multiple-transactions.spec.js index 3976c4877987..2477ae3f4843 100644 --- a/test/e2e/tests/transaction/multiple-transactions.spec.js +++ b/test/e2e/tests/transaction/multiple-transactions.spec.js @@ -5,6 +5,7 @@ const { regularDelayMs, unlockWallet, generateGanacheOptions, + WINDOW_TITLES, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -30,19 +31,15 @@ describe('Multiple transactions', function () { tag: 'button', }); await driver.waitUntilXWindowHandles(3); - const windowHandles = await driver.getAllWindowHandles(); - const extensionTab = windowHandles[0]; - const dApp = windowHandles[1]; - const confirmation = windowHandles[2]; - await driver.switchToWindow(dApp); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); // creates second transaction await driver.clickElement({ text: 'Send EIP 1559 Transaction', tag: 'button', }); - await driver.switchToWindow(confirmation); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); // confirms second transaction await driver.waitForSelector({ @@ -50,7 +47,7 @@ describe('Multiple transactions', function () { tag: 'a', }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.switchToWindow(confirmation); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); // wait for the "Reject 2 transactions" to disappear await driver.assertElementNotPresent( @@ -61,7 +58,9 @@ describe('Multiple transactions', function () { await driver.clickElement({ text: 'Confirm', tag: 'button' }); await driver.waitUntilXWindowHandles(2); - await driver.switchToWindow(extensionTab); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); await driver.delay(regularDelayMs); await driver.clickElement('[data-testid="home__activity-tab"]'); await driver.waitForSelector( @@ -98,17 +97,15 @@ describe('Multiple transactions', function () { tag: 'button', }); await driver.waitUntilXWindowHandles(3); - const windowHandles = await driver.getAllWindowHandles(); - const extension = windowHandles[0]; - const confirmation = windowHandles[2]; - await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); // creates second transaction await driver.clickElement({ text: 'Send EIP 1559 Transaction', tag: 'button', }); - await driver.switchToWindow(confirmation); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); // rejects second transaction await driver.waitForSelector({ @@ -121,7 +118,9 @@ describe('Multiple transactions', function () { await driver.clickElement({ text: 'Reject', tag: 'button' }); await driver.waitUntilXWindowHandles(2); - await driver.switchToWindow(extension); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); await driver.delay(regularDelayMs); await driver.clickElement('[data-testid="home__activity-tab"]'); diff --git a/test/e2e/tests/transaction/send-eth.spec.js b/test/e2e/tests/transaction/send-eth.spec.js index b56f5847778b..a92de7b42c1a 100644 --- a/test/e2e/tests/transaction/send-eth.spec.js +++ b/test/e2e/tests/transaction/send-eth.spec.js @@ -161,11 +161,11 @@ describe('Send ETH', function () { smartContract, title: this.test.fullTitle(), }, - async ({ driver, contractRegistry }) => { + async ({ driver, contractRegistry, ganacheServer }) => { const contractAddress = await contractRegistry.getContractAddress( smartContract, ); - await unlockWallet(driver); + await logInWithBalanceValidation(driver, ganacheServer); await driver.clickElement('[data-testid="eth-overview-send"]'); await driver.fill( diff --git a/test/e2e/webdriver/chrome.js b/test/e2e/webdriver/chrome.js index 29bd0f90014b..0050d50e8430 100644 --- a/test/e2e/webdriver/chrome.js +++ b/test/e2e/webdriver/chrome.js @@ -35,13 +35,8 @@ class ChromeDriver { args.push('--auto-open-devtools-for-tabs'); } - if (process.env.ENABLE_MV3) { - args.push('--log-level=0'); - args.push('--enable-logging'); - args.push(`--user-data-dir=${process.cwd()}/test-artifacts/chrome`); - } else { - args.push('--log-level=3'); - } + args.push('--log-level=3'); + args.push('--enable-logging'); if (process.env.CI || process.env.CODESPACES) { args.push('--disable-gpu'); diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index 5fa8b7b97c73..f5f81f038bdf 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -109,6 +109,14 @@ until.elementIsNotPresent = function elementIsNotPresent(locator) { }); }; +until.foundElementCountIs = function foundElementCountIs(locator, n) { + return new Condition(`Element count is ${n}`, function (driver) { + return driver.findElements(locator).then(function (elements) { + return elements.length === n; + }); + }); +}; + /** * This is MetaMask's custom E2E test driver, wrapping the Selenium WebDriver. * For Selenium WebDriver API documentation, see: @@ -265,6 +273,20 @@ class Driver { }, this.timeout); } + async elementCountBecomesN(rawLocator, n, timeout = this.timeout) { + const locator = this.buildLocator(rawLocator); + try { + await this.driver.wait(until.foundElementCountIs(locator, n), timeout); + return true; + } catch (e) { + const elements = await this.findElements(locator); + console.error( + `Waiting for count of ${locator} elements to be ${n}, but it is ${elements.length}`, + ); + return false; + } + } + /** * Wait until an element is absent. * @@ -374,9 +396,23 @@ class Driver { return elements.map((element) => wrapElementWithAPI(element, this)); } - async clickElement(rawLocator) { - const element = await this.findClickableElement(rawLocator); - await element.click(); + async clickElement(rawLocator, retries = 3) { + for (let attempt = 0; attempt < retries; attempt++) { + try { + const element = await this.findClickableElement(rawLocator); + await element.click(); + return; + } catch (error) { + if ( + error.name === 'StaleElementReferenceError' && + attempt < retries - 1 + ) { + await this.delay(1000); + } else { + throw error; + } + } + } } /** @@ -511,8 +547,9 @@ class Driver { } async openNewPage(url) { - const newHandle = await this.driver.switchTo().newWindow(); + await this.driver.switchTo().newWindow(); await this.openNewURL(url); + const newHandle = await this.driver.getWindowHandle(); return newHandle; } @@ -536,12 +573,15 @@ class Driver { return await this.driver.getAllWindowHandles(); } - async waitUntilXWindowHandles(x, delayStep = 1000, timeout = this.timeout) { + async waitUntilXWindowHandles(_x, delayStep = 1000, timeout = this.timeout) { + const x = + process.env.ENABLE_MV3 === 'true' || process.env.ENABLE_MV3 === undefined + ? _x + 1 + : _x; let timeElapsed = 0; let windowHandles = []; while (timeElapsed <= timeout) { - windowHandles = await this.driver.getAllWindowHandles(); - + windowHandles = await this.getAllWindowHandles(); if (windowHandles.length === x) { return windowHandles; } diff --git a/test/e2e/webdriver/firefox.js b/test/e2e/webdriver/firefox.js index 78194496e478..f92c27d61cf4 100644 --- a/test/e2e/webdriver/firefox.js +++ b/test/e2e/webdriver/firefox.js @@ -68,10 +68,18 @@ class FirefoxDriver { const builder = new Builder() .forBrowser('firefox') .setFirefoxOptions(options); + + // For cases where Firefox is installed as snap (Linux) + const FF_SNAP_GECKO_PATH = '/snap/bin/geckodriver'; + const service = process.env.FIREFOX_SNAP + ? new firefox.ServiceBuilder(FF_SNAP_GECKO_PATH) + : new firefox.ServiceBuilder(); + if (port) { - const service = new firefox.ServiceBuilder().setPort(port); - builder.setFirefoxService(service); + service.setPort(port); } + + builder.setFirefoxService(service); const driver = builder.build(); const fxDriver = new FirefoxDriver(driver); diff --git a/test/scenarios/10. address-book/add a contact to the address book.csv b/test/manual-scenarios/address-book/add a contact to the address book.csv similarity index 100% rename from test/scenarios/10. address-book/add a contact to the address book.csv rename to test/manual-scenarios/address-book/add a contact to the address book.csv diff --git a/test/scenarios/10. address-book/remove a contact from the address book.csv b/test/manual-scenarios/address-book/remove a contact from the address book.csv similarity index 100% rename from test/scenarios/10. address-book/remove a contact from the address book.csv rename to test/manual-scenarios/address-book/remove a contact from the address book.csv diff --git a/test/scenarios/10. address-book/update a contact in the address book.csv b/test/manual-scenarios/address-book/update a contact in the address book.csv similarity index 100% rename from test/scenarios/10. address-book/update a contact in the address book.csv rename to test/manual-scenarios/address-book/update a contact in the address book.csv diff --git a/test/scenarios/8. backup/backup user data.csv b/test/manual-scenarios/backup/backup user data.csv similarity index 100% rename from test/scenarios/8. backup/backup user data.csv rename to test/manual-scenarios/backup/backup user data.csv diff --git a/test/scenarios/8. backup/restore user data.csv b/test/manual-scenarios/backup/restore user data.csv similarity index 100% rename from test/scenarios/8. backup/restore user data.csv rename to test/manual-scenarios/backup/restore user data.csv diff --git a/test/scenarios/18. contract/deploy contract, mint and import NFT with hardware wallet.csv b/test/manual-scenarios/contract/deploy contract, mint and import NFT with hardware wallet.csv similarity index 100% rename from test/scenarios/18. contract/deploy contract, mint and import NFT with hardware wallet.csv rename to test/manual-scenarios/contract/deploy contract, mint and import NFT with hardware wallet.csv diff --git a/test/scenarios/18. contract/hardware wallet qr based connect.csv b/test/manual-scenarios/contract/hardware wallet qr based connect.csv similarity index 100% rename from test/scenarios/18. contract/hardware wallet qr based connect.csv rename to test/manual-scenarios/contract/hardware wallet qr based connect.csv diff --git a/test/scenarios/18. contract/send token with hardware wallet.csv b/test/manual-scenarios/contract/send token with hardware wallet.csv similarity index 100% rename from test/scenarios/18. contract/send token with hardware wallet.csv rename to test/manual-scenarios/contract/send token with hardware wallet.csv diff --git a/test/scenarios/12. encrypt and decrypt/encrypt and decrypt.csv b/test/manual-scenarios/encrypt and decrypt/encrypt and decrypt.csv similarity index 100% rename from test/scenarios/12. encrypt and decrypt/encrypt and decrypt.csv rename to test/manual-scenarios/encrypt and decrypt/encrypt and decrypt.csv diff --git a/test/scenarios/5. ens/name resolution.csv b/test/manual-scenarios/ens/name resolution.csv similarity index 100% rename from test/scenarios/5. ens/name resolution.csv rename to test/manual-scenarios/ens/name resolution.csv diff --git a/test/scenarios/11. gas fee/EIP-1559 gas.csv b/test/manual-scenarios/gas fee/EIP-1559 gas.csv similarity index 100% rename from test/scenarios/11. gas fee/EIP-1559 gas.csv rename to test/manual-scenarios/gas fee/EIP-1559 gas.csv diff --git a/test/scenarios/11. gas fee/legacy gas.csv b/test/manual-scenarios/gas fee/legacy gas.csv similarity index 100% rename from test/scenarios/11. gas fee/legacy gas.csv rename to test/manual-scenarios/gas fee/legacy gas.csv diff --git a/test/scenarios/16. incoming-transactions/receive native token.csv b/test/manual-scenarios/incoming-transactions/receive native token.csv similarity index 100% rename from test/scenarios/16. incoming-transactions/receive native token.csv rename to test/manual-scenarios/incoming-transactions/receive native token.csv diff --git a/test/scenarios/2. keyring/connect hardware wallet.csv b/test/manual-scenarios/keyring/connect hardware wallet.csv similarity index 100% rename from test/scenarios/2. keyring/connect hardware wallet.csv rename to test/manual-scenarios/keyring/connect hardware wallet.csv diff --git a/test/scenarios/2. keyring/reset a wallet.csv b/test/manual-scenarios/keyring/reset a wallet.csv similarity index 100% rename from test/scenarios/2. keyring/reset a wallet.csv rename to test/manual-scenarios/keyring/reset a wallet.csv diff --git a/test/scenarios/9. metrics/send event metrics.csv b/test/manual-scenarios/metrics/send event metrics.csv similarity index 100% rename from test/scenarios/9. metrics/send event metrics.csv rename to test/manual-scenarios/metrics/send event metrics.csv diff --git a/test/scenarios/7. network/add custom network from dApp.csv b/test/manual-scenarios/network/add custom network from dApp.csv similarity index 100% rename from test/scenarios/7. network/add custom network from dApp.csv rename to test/manual-scenarios/network/add custom network from dApp.csv diff --git a/test/scenarios/7. network/add custom network manually.csv b/test/manual-scenarios/network/add custom network manually.csv similarity index 100% rename from test/scenarios/7. network/add custom network manually.csv rename to test/manual-scenarios/network/add custom network manually.csv diff --git a/test/scenarios/7. network/add network from the list of popular networks.csv b/test/manual-scenarios/network/add network from the list of popular networks.csv similarity index 100% rename from test/scenarios/7. network/add network from the list of popular networks.csv rename to test/manual-scenarios/network/add network from the list of popular networks.csv diff --git a/test/scenarios/7. network/delete networks from the dropdown list.csv b/test/manual-scenarios/network/delete networks from the dropdown list.csv similarity index 100% rename from test/scenarios/7. network/delete networks from the dropdown list.csv rename to test/manual-scenarios/network/delete networks from the dropdown list.csv diff --git a/test/scenarios/7. network/delete networks in Settings.csv b/test/manual-scenarios/network/delete networks in Settings.csv similarity index 100% rename from test/scenarios/7. network/delete networks in Settings.csv rename to test/manual-scenarios/network/delete networks in Settings.csv diff --git a/test/scenarios/7. network/switching networks.csv b/test/manual-scenarios/network/switching networks.csv similarity index 100% rename from test/scenarios/7. network/switching networks.csv rename to test/manual-scenarios/network/switching networks.csv diff --git a/test/scenarios/7. network/update networks.csv b/test/manual-scenarios/network/update networks.csv similarity index 100% rename from test/scenarios/7. network/update networks.csv rename to test/manual-scenarios/network/update networks.csv diff --git a/test/scenarios/1. onboarding/create a wallet.csv b/test/manual-scenarios/onboarding/create a wallet.csv similarity index 100% rename from test/scenarios/1. onboarding/create a wallet.csv rename to test/manual-scenarios/onboarding/create a wallet.csv diff --git a/test/scenarios/1. onboarding/import a wallet.csv b/test/manual-scenarios/onboarding/import a wallet.csv similarity index 100% rename from test/scenarios/1. onboarding/import a wallet.csv rename to test/manual-scenarios/onboarding/import a wallet.csv diff --git a/test/scenarios/14. permissions/connecting and disconnecting from a dapp.csv b/test/manual-scenarios/permissions/connecting and disconnecting from a dapp.csv similarity index 100% rename from test/scenarios/14. permissions/connecting and disconnecting from a dapp.csv rename to test/manual-scenarios/permissions/connecting and disconnecting from a dapp.csv diff --git a/test/scenarios/6. phishing/warning page.csv b/test/manual-scenarios/phishing/warning page.csv similarity index 100% rename from test/scenarios/6. phishing/warning page.csv rename to test/manual-scenarios/phishing/warning page.csv diff --git a/test/scenarios/17. settings/about-metamask/ui-validation.csv b/test/manual-scenarios/settings/about-metamask/ui-validation.csv similarity index 100% rename from test/scenarios/17. settings/about-metamask/ui-validation.csv rename to test/manual-scenarios/settings/about-metamask/ui-validation.csv diff --git a/test/scenarios/17. settings/advanced/check custom nonce toggle.csv b/test/manual-scenarios/settings/advanced/check custom nonce toggle.csv similarity index 100% rename from test/scenarios/17. settings/advanced/check custom nonce toggle.csv rename to test/manual-scenarios/settings/advanced/check custom nonce toggle.csv diff --git a/test/scenarios/17. settings/advanced/show-hex-data.csv b/test/manual-scenarios/settings/advanced/show-hex-data.csv similarity index 100% rename from test/scenarios/17. settings/advanced/show-hex-data.csv rename to test/manual-scenarios/settings/advanced/show-hex-data.csv diff --git a/test/scenarios/17. settings/general/change-language.csv b/test/manual-scenarios/settings/general/change-language.csv similarity index 100% rename from test/scenarios/17. settings/general/change-language.csv rename to test/manual-scenarios/settings/general/change-language.csv diff --git a/test/scenarios/13. sign/eth sign with hardware wallet.csv b/test/manual-scenarios/sign/eth sign with hardware wallet.csv similarity index 100% rename from test/scenarios/13. sign/eth sign with hardware wallet.csv rename to test/manual-scenarios/sign/eth sign with hardware wallet.csv diff --git a/test/scenarios/13. sign/eth sign.csv b/test/manual-scenarios/sign/eth sign.csv similarity index 100% rename from test/scenarios/13. sign/eth sign.csv rename to test/manual-scenarios/sign/eth sign.csv diff --git a/test/scenarios/13. sign/personal sign with hardware wallet.csv b/test/manual-scenarios/sign/personal sign with hardware wallet.csv similarity index 100% rename from test/scenarios/13. sign/personal sign with hardware wallet.csv rename to test/manual-scenarios/sign/personal sign with hardware wallet.csv diff --git a/test/scenarios/13. sign/personal sign.csv b/test/manual-scenarios/sign/personal sign.csv similarity index 100% rename from test/scenarios/13. sign/personal sign.csv rename to test/manual-scenarios/sign/personal sign.csv diff --git a/test/scenarios/13. sign/sign in with ethereum.csv b/test/manual-scenarios/sign/sign in with ethereum.csv similarity index 100% rename from test/scenarios/13. sign/sign in with ethereum.csv rename to test/manual-scenarios/sign/sign in with ethereum.csv diff --git a/test/scenarios/13. sign/sign typed data v3 with hardware wallet.csv b/test/manual-scenarios/sign/sign typed data v3 with hardware wallet.csv similarity index 100% rename from test/scenarios/13. sign/sign typed data v3 with hardware wallet.csv rename to test/manual-scenarios/sign/sign typed data v3 with hardware wallet.csv diff --git a/test/scenarios/13. sign/sign typed data v4 with hardware wallet.csv b/test/manual-scenarios/sign/sign typed data v4 with hardware wallet.csv similarity index 100% rename from test/scenarios/13. sign/sign typed data v4 with hardware wallet.csv rename to test/manual-scenarios/sign/sign typed data v4 with hardware wallet.csv diff --git a/test/scenarios/13. sign/sign typed with data.csv b/test/manual-scenarios/sign/sign typed with data.csv similarity index 100% rename from test/scenarios/13. sign/sign typed with data.csv rename to test/manual-scenarios/sign/sign typed with data.csv diff --git a/test/scenarios/15. swap/smart swap.csv b/test/manual-scenarios/swap/smart swap.csv similarity index 100% rename from test/scenarios/15. swap/smart swap.csv rename to test/manual-scenarios/swap/smart swap.csv diff --git a/test/scenarios/15. swap/swap eth.csv b/test/manual-scenarios/swap/swap eth.csv similarity index 100% rename from test/scenarios/15. swap/swap eth.csv rename to test/manual-scenarios/swap/swap eth.csv diff --git a/test/scenarios/4. tokens/approve erc 1155 token.csv b/test/manual-scenarios/tokens/approve erc 1155 token.csv similarity index 100% rename from test/scenarios/4. tokens/approve erc 1155 token.csv rename to test/manual-scenarios/tokens/approve erc 1155 token.csv diff --git a/test/scenarios/4. tokens/approve erc20 custom amount.csv b/test/manual-scenarios/tokens/approve erc20 custom amount.csv similarity index 100% rename from test/scenarios/4. tokens/approve erc20 custom amount.csv rename to test/manual-scenarios/tokens/approve erc20 custom amount.csv diff --git a/test/scenarios/4. tokens/approve erc20 token.csv b/test/manual-scenarios/tokens/approve erc20 token.csv similarity index 100% rename from test/scenarios/4. tokens/approve erc20 token.csv rename to test/manual-scenarios/tokens/approve erc20 token.csv diff --git a/test/scenarios/4. tokens/approve erc721 token.csv b/test/manual-scenarios/tokens/approve erc721 token.csv similarity index 100% rename from test/scenarios/4. tokens/approve erc721 token.csv rename to test/manual-scenarios/tokens/approve erc721 token.csv diff --git a/test/scenarios/4. tokens/autodetect NFTs.csv b/test/manual-scenarios/tokens/autodetect NFTs.csv similarity index 100% rename from test/scenarios/4. tokens/autodetect NFTs.csv rename to test/manual-scenarios/tokens/autodetect NFTs.csv diff --git a/test/scenarios/4. tokens/autodetect tokens.csv b/test/manual-scenarios/tokens/autodetect tokens.csv similarity index 100% rename from test/scenarios/4. tokens/autodetect tokens.csv rename to test/manual-scenarios/tokens/autodetect tokens.csv diff --git a/test/scenarios/4. tokens/import erc1155 token origin MM.csv b/test/manual-scenarios/tokens/import erc1155 token origin MM.csv similarity index 100% rename from test/scenarios/4. tokens/import erc1155 token origin MM.csv rename to test/manual-scenarios/tokens/import erc1155 token origin MM.csv diff --git a/test/scenarios/4. tokens/import erc1155 token origin dapp.csv b/test/manual-scenarios/tokens/import erc1155 token origin dapp.csv similarity index 100% rename from test/scenarios/4. tokens/import erc1155 token origin dapp.csv rename to test/manual-scenarios/tokens/import erc1155 token origin dapp.csv diff --git a/test/scenarios/4. tokens/import erc20 token origin MM.csv b/test/manual-scenarios/tokens/import erc20 token origin MM.csv similarity index 100% rename from test/scenarios/4. tokens/import erc20 token origin MM.csv rename to test/manual-scenarios/tokens/import erc20 token origin MM.csv diff --git a/test/scenarios/4. tokens/import erc20 token origin dapp.csv b/test/manual-scenarios/tokens/import erc20 token origin dapp.csv similarity index 100% rename from test/scenarios/4. tokens/import erc20 token origin dapp.csv rename to test/manual-scenarios/tokens/import erc20 token origin dapp.csv diff --git a/test/scenarios/4. tokens/import erc721 token origin MM.csv b/test/manual-scenarios/tokens/import erc721 token origin MM.csv similarity index 100% rename from test/scenarios/4. tokens/import erc721 token origin MM.csv rename to test/manual-scenarios/tokens/import erc721 token origin MM.csv diff --git a/test/scenarios/4. tokens/import erc721 token origin dapp.csv b/test/manual-scenarios/tokens/import erc721 token origin dapp.csv similarity index 100% rename from test/scenarios/4. tokens/import erc721 token origin dapp.csv rename to test/manual-scenarios/tokens/import erc721 token origin dapp.csv diff --git a/test/scenarios/3. transactions/cancel transaction using same nonce.csv b/test/manual-scenarios/transactions/cancel transaction using same nonce.csv similarity index 100% rename from test/scenarios/3. transactions/cancel transaction using same nonce.csv rename to test/manual-scenarios/transactions/cancel transaction using same nonce.csv diff --git a/test/scenarios/3. transactions/cancel transaction.csv b/test/manual-scenarios/transactions/cancel transaction.csv similarity index 100% rename from test/scenarios/3. transactions/cancel transaction.csv rename to test/manual-scenarios/transactions/cancel transaction.csv diff --git a/test/scenarios/3. transactions/deploy failing transaction.csv b/test/manual-scenarios/transactions/deploy failing transaction.csv similarity index 100% rename from test/scenarios/3. transactions/deploy failing transaction.csv rename to test/manual-scenarios/transactions/deploy failing transaction.csv diff --git a/test/scenarios/3. transactions/deposit and withdraw.csv b/test/manual-scenarios/transactions/deposit and withdraw.csv similarity index 100% rename from test/scenarios/3. transactions/deposit and withdraw.csv rename to test/manual-scenarios/transactions/deposit and withdraw.csv diff --git a/test/scenarios/3. transactions/navigate multiple transactions.csv b/test/manual-scenarios/transactions/navigate multiple transactions.csv similarity index 100% rename from test/scenarios/3. transactions/navigate multiple transactions.csv rename to test/manual-scenarios/transactions/navigate multiple transactions.csv diff --git a/test/scenarios/3. transactions/send erc20 token origin MM.csv b/test/manual-scenarios/transactions/send erc20 token origin MM.csv similarity index 100% rename from test/scenarios/3. transactions/send erc20 token origin MM.csv rename to test/manual-scenarios/transactions/send erc20 token origin MM.csv diff --git a/test/scenarios/3. transactions/send erc20 token origin dapp.csv b/test/manual-scenarios/transactions/send erc20 token origin dapp.csv similarity index 100% rename from test/scenarios/3. transactions/send erc20 token origin dapp.csv rename to test/manual-scenarios/transactions/send erc20 token origin dapp.csv diff --git a/test/scenarios/3. transactions/send erc721 token origin MM.csv b/test/manual-scenarios/transactions/send erc721 token origin MM.csv similarity index 100% rename from test/scenarios/3. transactions/send erc721 token origin MM.csv rename to test/manual-scenarios/transactions/send erc721 token origin MM.csv diff --git a/test/scenarios/3. transactions/send erc721 token origin dapp.csv b/test/manual-scenarios/transactions/send erc721 token origin dapp.csv similarity index 100% rename from test/scenarios/3. transactions/send erc721 token origin dapp.csv rename to test/manual-scenarios/transactions/send erc721 token origin dapp.csv diff --git a/test/scenarios/3. transactions/send native token origin MM.csv b/test/manual-scenarios/transactions/send native token origin MM.csv similarity index 100% rename from test/scenarios/3. transactions/send native token origin MM.csv rename to test/manual-scenarios/transactions/send native token origin MM.csv diff --git a/test/scenarios/3. transactions/send native token origin dapp.csv b/test/manual-scenarios/transactions/send native token origin dapp.csv similarity index 100% rename from test/scenarios/3. transactions/send native token origin dapp.csv rename to test/manual-scenarios/transactions/send native token origin dapp.csv diff --git a/test/scenarios/3. transactions/send transactions with custom nonce.csv b/test/manual-scenarios/transactions/send transactions with custom nonce.csv similarity index 100% rename from test/scenarios/3. transactions/send transactions with custom nonce.csv rename to test/manual-scenarios/transactions/send transactions with custom nonce.csv diff --git a/test/scenarios/3. transactions/speed up transaction.csv b/test/manual-scenarios/transactions/speed up transaction.csv similarity index 100% rename from test/scenarios/3. transactions/speed up transaction.csv rename to test/manual-scenarios/transactions/speed up transaction.csv diff --git a/ui/__mocks__/webextension-polyfill.js b/ui/__mocks__/webextension-polyfill.js index 0984c767deb2..693368f15e0c 100644 --- a/ui/__mocks__/webextension-polyfill.js +++ b/ui/__mocks__/webextension-polyfill.js @@ -1,3 +1,3 @@ module.exports = { - runtime: { getManifest: () => ({ manifest_version: 2 }) }, + runtime: { getManifest: () => ({ manifest_version: 3 }) }, }; diff --git a/ui/components/app/basic-configuration-modal/basic-configuration-modal.stories.tsx b/ui/components/app/basic-configuration-modal/basic-configuration-modal.stories.tsx new file mode 100644 index 000000000000..bb5bd4418de4 --- /dev/null +++ b/ui/components/app/basic-configuration-modal/basic-configuration-modal.stories.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import configureStore from '../../../store/store'; +import testData from '../../../../.storybook/test-data'; +import { BasicConfigurationModal } from '.'; + +const store = configureStore(testData); + +export default { + title: 'Components/BasicConfigurationModal', + decorators: [(storyFn) => {storyFn()}], + component: BasicConfigurationModal, +}; + +export const DefaultStory = () => ; + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/basic-configuration-modal/basic-configuration-modal.tsx b/ui/components/app/basic-configuration-modal/basic-configuration-modal.tsx new file mode 100644 index 000000000000..3918e81e81a7 --- /dev/null +++ b/ui/components/app/basic-configuration-modal/basic-configuration-modal.tsx @@ -0,0 +1,153 @@ +import React, { useMemo, useState } from 'react'; +import { useLocation } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; +import { + Display, + FlexDirection, + AlignItems, + JustifyContent, + TextVariant, + BlockSize, + IconColor, + FontWeight, +} from '../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { toggleExternalServices } from '../../../store/actions'; +import { + ModalOverlay, + ModalContent, + ModalHeader, + Modal, + Box, + Text, + ModalFooter, + Button, + IconName, + ButtonVariant, + Icon, + IconSize, + Checkbox, + ButtonSize, + Label, +} from '../../component-library'; +import { getUseExternalServices } from '../../../selectors'; +import { + hideBasicFunctionalityModal, + onboardingToggleBasicFunctionalityOff, +} from '../../../ducks/app/app'; +import { ONBOARDING_PRIVACY_SETTINGS_ROUTE } from '../../../helpers/constants/routes'; + +export function BasicConfigurationModal() { + const t = useI18nContext(); + const [hasAgreed, setHasAgreed] = useState(false); + const dispatch = useDispatch(); + const isExternalServicesEnabled = useSelector(getUseExternalServices); + const { pathname } = useLocation(); + const onboardingFlow = useMemo(() => { + return pathname === ONBOARDING_PRIVACY_SETTINGS_ROUTE; + }, [pathname]); + + function closeModal() { + dispatch(hideBasicFunctionalityModal()); + } + + return ( + + + + + + + + {isExternalServicesEnabled + ? t('basicConfigurationModalHeadingOff') + : t('basicConfigurationModalHeadingOn')} + + + + + + + {isExternalServicesEnabled + ? t('basicConfigurationModalDisclaimerOff') + : t('basicConfigurationModalDisclaimerOn')} + + {isExternalServicesEnabled && ( + + setHasAgreed((prevValue) => !prevValue)} + /> + + + )} + + + + + + + + + + + ); +} diff --git a/ui/components/app/basic-configuration-modal/index.ts b/ui/components/app/basic-configuration-modal/index.ts new file mode 100644 index 000000000000..847e5e18197c --- /dev/null +++ b/ui/components/app/basic-configuration-modal/index.ts @@ -0,0 +1 @@ +export { BasicConfigurationModal } from './basic-configuration-modal'; diff --git a/ui/components/app/wallet-overview/eth-overview.js b/ui/components/app/wallet-overview/eth-overview.js index abd5351ed712..2b6702f54b04 100644 --- a/ui/components/app/wallet-overview/eth-overview.js +++ b/ui/components/app/wallet-overview/eth-overview.js @@ -34,6 +34,7 @@ import { getPreferences, getSelectedInternalAccount, getSelectedAccountCachedBalance, + getUseExternalServices, ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) getSwapsDefaultToken, getCurrentKeyring, @@ -96,6 +97,7 @@ const EthOverview = ({ className, showAddress }) => { ); const account = useSelector(getSelectedInternalAccount); + const isExternalServicesEnabled = useSelector(getUseExternalServices); const isSwapsChain = useSelector(getIsSwapsChain); const isSigningEnabled = account.methods.includes(EthMethod.SignTransaction) || @@ -323,7 +325,9 @@ const EthOverview = ({ className, showAddress }) => { /> { preferences: { useNativeCurrencyAsPrimaryCurrency: true, }, + useExternalServices: true, useCurrencyRateCheck: true, currentCurrency: 'usd', currencyRates: { diff --git a/ui/components/ui/new-network-info/new-network-info.js b/ui/components/ui/new-network-info/new-network-info.js index dd82fdcb5a3a..37520ff16711 100644 --- a/ui/components/ui/new-network-info/new-network-info.js +++ b/ui/components/ui/new-network-info/new-network-info.js @@ -20,6 +20,7 @@ import { getIsBridgeChain, getMetaMetricsId, getUseTokenDetection, + getUseExternalServices, } from '../../../selectors'; import { setFirstTimeUsedNetwork } from '../../../store/actions'; import { @@ -43,6 +44,7 @@ export default function NewNetworkInfo() { const [showPopup, setShowPopup] = useState(true); const [isLoading, setIsLoading] = useState(true); const autoDetectToken = useSelector(getUseTokenDetection); + const areExternalServicesEnabled = useSelector(getUseExternalServices); const providerConfig = useSelector(getProviderConfig); const currentNetwork = useSelector(getCurrentNetwork); const metaMetricsId = useSelector(getMetaMetricsId); @@ -65,6 +67,9 @@ export default function NewNetworkInfo() { }, [providerConfig.chainId]); useEffect(() => { + if (!areExternalServicesEnabled) { + return; + } checkTokenDetection(); // we want to only fetch once // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/ui/components/ui/new-network-info/new-network-info.test.js b/ui/components/ui/new-network-info/new-network-info.test.js index 1af1d1ef2337..d964218c0cda 100644 --- a/ui/components/ui/new-network-info/new-network-info.test.js +++ b/ui/components/ui/new-network-info/new-network-info.test.js @@ -90,6 +90,7 @@ describe('NewNetworkInfo', () => { chainId: '0x1', type: 'mainnet', }, + useExternalServices: true, useTokenDetection: false, currencyRates: {}, }, @@ -183,6 +184,7 @@ describe('NewNetworkInfo', () => { chainId: '0x1', type: 'mainnet', }, + useExternalServices: true, useTokenDetection: true, currencyRates: {}, }, diff --git a/ui/ducks/app/app.ts b/ui/ducks/app/app.ts index e2f15c1c25b6..eee7c06d31f2 100644 --- a/ui/ducks/app/app.ts +++ b/ui/ducks/app/app.ts @@ -75,6 +75,8 @@ type AppState = { smartTransactionsErrorMessageDismissed: boolean; ledgerWebHidConnectedStatus: WebHIDConnectedStatuses; ledgerTransportStatus: HardwareTransportStates; + showBasicFunctionalityModal: boolean; + externalServicesOnboardingToggleState: boolean; newNftAddedMessage: string; removeNftMessage: string; newNetworkAddedName: string; @@ -116,6 +118,8 @@ const initialState: AppState = { networkDropdownOpen: false, importNftsModal: { open: false }, showIpfsModalOpen: false, + showBasicFunctionalityModal: false, + externalServicesOnboardingToggleState: true, keyringRemovalSnapModal: { snapName: '', result: 'none', @@ -209,6 +213,29 @@ export default function reduceApp( }, }; + case actionConstants.SHOW_BASIC_FUNCTIONALITY_MODAL_OPEN: + return { + ...appState, + showBasicFunctionalityModal: true, + }; + + case actionConstants.SHOW_BASIC_FUNCTIONALITY_MODAL_CLOSE: + return { + ...appState, + showBasicFunctionalityModal: false, + }; + + case actionConstants.ONBOARDING_TOGGLE_BASIC_FUNCTIONALITY_ON: + return { + ...appState, + externalServicesOnboardingToggleState: true, + }; + case actionConstants.ONBOARDING_TOGGLE_BASIC_FUNCTIONALITY_OFF: + return { + ...appState, + externalServicesOnboardingToggleState: false, + }; + case actionConstants.SHOW_IPFS_MODAL_OPEN: return { ...appState, @@ -555,6 +582,30 @@ export function hideWhatsNewPopup(): Action { }; } +export function openBasicFunctionalityModal(): Action { + return { + type: actionConstants.SHOW_BASIC_FUNCTIONALITY_MODAL_OPEN, + }; +} + +export function hideBasicFunctionalityModal(): Action { + return { + type: actionConstants.SHOW_BASIC_FUNCTIONALITY_MODAL_CLOSE, + }; +} + +export function onboardingToggleBasicFunctionalityOn(): Action { + return { + type: actionConstants.ONBOARDING_TOGGLE_BASIC_FUNCTIONALITY_ON, + }; +} + +export function onboardingToggleBasicFunctionalityOff(): Action { + return { + type: actionConstants.ONBOARDING_TOGGLE_BASIC_FUNCTIONALITY_OFF, + }; +} + export function toggleGasLoadingAnimation( payload: boolean, ): PayloadAction { diff --git a/ui/helpers/constants/settings.js b/ui/helpers/constants/settings.js index 33cb39c0ac5c..e120f237918e 100644 --- a/ui/helpers/constants/settings.js +++ b/ui/helpers/constants/settings.js @@ -490,11 +490,20 @@ const SETTINGS_CONSTANTS = [ { featureFlag: 'ENABLE_SETTINGS_PAGE_DEV_OPTIONS', tabMessage: (t) => t('developerOptions'), - sectionMessage: (t) => t('onboarding'), + sectionMessage: (t) => t('serviceWorkerKeepAlive'), descriptionMessage: (t) => t('developerOptionsResetStatesOnboarding'), route: `${DEVELOPER_OPTIONS_ROUTE}#reset-states-onboarding`, icon: IconName.CodeCircle, }, + // developerOptions settingsRefs[3] + { + featureFlag: 'ENABLE_SETTINGS_PAGE_DEV_OPTIONS', + tabMessage: (t) => t('developerOptions'), + sectionMessage: (t) => t('serviceWorkerKeepAlive'), + descriptionMessage: (t) => t('developerOptionsServiceWorkerKeepAlive'), + route: `${DEVELOPER_OPTIONS_ROUTE}#service-worker-keep-alive`, + icon: IconName.CodeCircle, + }, ]; if (process.env.ENABLE_CONFIRMATION_REDESIGN) { diff --git a/ui/helpers/utils/settings-search.test.js b/ui/helpers/utils/settings-search.test.js index 25c6c57a23b1..418d395cf208 100644 --- a/ui/helpers/utils/settings-search.test.js +++ b/ui/helpers/utils/settings-search.test.js @@ -146,7 +146,7 @@ const t = (key) => { describe('Settings Search Utils', () => { describe('getSettingsRoutes', () => { it('should be an array of settings routes objects', () => { - const NUM_OF_ENV_FEATURE_FLAG_SETTINGS = 3; + const NUM_OF_ENV_FEATURE_FLAG_SETTINGS = 4; expect(getSettingsRoutes()).toHaveLength( SETTINGS_CONSTANTS.length - NUM_OF_ENV_FEATURE_FLAG_SETTINGS, diff --git a/ui/pages/confirmations/components/edit-gas-fee-popover/edit-gas-item/useCustomTimeEstimate.js b/ui/pages/confirmations/components/edit-gas-fee-popover/edit-gas-item/useCustomTimeEstimate.js index 662e2b5ca1a4..0a4ae5276b4e 100644 --- a/ui/pages/confirmations/components/edit-gas-fee-popover/edit-gas-item/useCustomTimeEstimate.js +++ b/ui/pages/confirmations/components/edit-gas-fee-popover/edit-gas-item/useCustomTimeEstimate.js @@ -62,7 +62,6 @@ export const useCustomTimeEstimate = ({ return {}; } - const { low = {}, medium = {}, high = {} } = gasFeeEstimates; let waitTimeEstimate = ''; if ( @@ -73,11 +72,12 @@ export const useCustomTimeEstimate = ({ ) { waitTimeEstimate = Number(customEstimatedTime?.upperTimeBound); } else if ( - Number(maxPriorityFeePerGas) >= Number(medium.suggestedMaxPriorityFeePerGas) + Number(maxPriorityFeePerGas) >= + Number(gasFeeEstimates?.medium?.suggestedMaxPriorityFeePerGas) ) { - waitTimeEstimate = high.minWaitTimeEstimate; + waitTimeEstimate = gasFeeEstimates?.high?.minWaitTimeEstimate; } else { - waitTimeEstimate = low.maxWaitTimeEstimate; + waitTimeEstimate = gasFeeEstimates?.low?.maxWaitTimeEstimate; } return { waitTimeEstimate }; diff --git a/ui/pages/create-account/connect-hardware/__snapshots__/index.test.tsx.snap b/ui/pages/create-account/connect-hardware/__snapshots__/index.test.tsx.snap index 2501b80424a1..274f9962e979 100644 --- a/ui/pages/create-account/connect-hardware/__snapshots__/index.test.tsx.snap +++ b/ui/pages/create-account/connect-hardware/__snapshots__/index.test.tsx.snap @@ -38,6 +38,7 @@ exports[`ConnectHardwareForm should match snapshot 1`] = ` > + +
+
+
+
+ +
+
+ + Onboarding + +
+ Resets various states related to onboarding and redirects to the "Secure Your Wallet" onboarding page. +
+
+
+ +
+
+
+
+
+
+
+
+
+ + Service Worker Keep Alive + +
+ Results in a timestamp being continuously saved to session.storage +
+
+
+
+
+
+`; diff --git a/ui/pages/settings/developer-options-tab/developer-options-tab.js b/ui/pages/settings/developer-options-tab/developer-options-tab.js deleted file mode 100644 index 7692891a4f88..000000000000 --- a/ui/pages/settings/developer-options-tab/developer-options-tab.js +++ /dev/null @@ -1,181 +0,0 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { useHistory } from 'react-router-dom'; - -import { - Box, - Button, - ButtonVariant, - Icon, - IconName, - IconSize, - Text, -} from '../../../components/component-library'; -import { - IconColor, - TextColor, - Display, - FlexDirection, - JustifyContent, - AlignItems, -} from '../../../helpers/constants/design-system'; -import { ONBOARDING_SECURE_YOUR_WALLET_ROUTE } from '../../../helpers/constants/routes'; -import { - getNumberOfSettingRoutesInTab, - handleSettingsRefs, -} from '../../../helpers/utils/settings-search'; - -import { useI18nContext } from '../../../hooks/useI18nContext'; -import { - resetOnboarding, - resetViewedNotifications, -} from '../../../store/actions'; -import { getEnvironmentType } from '../../../../app/scripts/lib/util'; -import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app'; - -const DeveloperOptionsTab = () => { - const t = useI18nContext(); - const dispatch = useDispatch(); - const history = useHistory(); - - const [hasResetAnnouncements, setHasResetAnnouncements] = useState(false); - const [hasResetOnboarding, setHasResetOnboarding] = useState(false); - - const settingsRefs = Array( - getNumberOfSettingRoutesInTab(t, t('developerOptions')), - ) - .fill(undefined) - .map(() => { - return React.createRef(); - }); - - const handleResetAnnouncementClick = useCallback(() => { - resetViewedNotifications(); - setHasResetAnnouncements(true); - }, []); - - const handleResetOnboardingClick = useCallback(async () => { - await dispatch(resetOnboarding()); - setHasResetOnboarding(true); - - const backUpSRPRoute = `${ONBOARDING_SECURE_YOUR_WALLET_ROUTE}/?isFromReminder=true`; - const isPopup = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP; - - if (isPopup) { - global.platform.openExtensionInBrowser(backUpSRPRoute); - } else { - history.push(backUpSRPRoute); - } - }, [dispatch, history]); - - useEffect(() => { - handleSettingsRefs(t, t('developerOptions'), settingsRefs); - }, [t, settingsRefs]); - - return ( -
- - {t('states')} - - - {t('resetStates')} - - -
- -
- {t('announcements')} -
- {t('developerOptionsResetStatesAnnouncementsDescription')} -
-
- -
- -
-
- - -
-
- - -
- {t('onboarding')} -
- {t('developerOptionsResetStatesOnboarding')} -
-
- -
- -
-
- - -
-
-
-
- ); -}; - -export default DeveloperOptionsTab; diff --git a/ui/pages/settings/developer-options-tab/developer-options-tab.stories.tsx b/ui/pages/settings/developer-options-tab/developer-options-tab.stories.tsx new file mode 100644 index 000000000000..ee0bfb0a96a7 --- /dev/null +++ b/ui/pages/settings/developer-options-tab/developer-options-tab.stories.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import DeveloperOptionsTab from './index'; + +const DeveloperOptionsTabStory = { + title: 'Pages/Settings/DeveloperOptionsTab', + + component: DeveloperOptionsTab, +}; + +export const DefaultStory = ({ variant, address }) => ( + +); + +DefaultStory.storyName = 'Default'; + +export default DeveloperOptionsTabStory; diff --git a/ui/pages/settings/developer-options-tab/developer-options-tab.test.tsx b/ui/pages/settings/developer-options-tab/developer-options-tab.test.tsx new file mode 100644 index 000000000000..d23be4777bda --- /dev/null +++ b/ui/pages/settings/developer-options-tab/developer-options-tab.test.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import configureMockStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import { fireEvent } from '@testing-library/react'; +import { renderWithProvider } from '../../../../test/jest/rendering'; +import mockState from '../../../../test/data/mock-state.json'; +import DeveloperOptionsTab from '.'; + +const mockSetServiceWorkerKeepAlivePreference = jest.fn(); + +jest.mock('../../../store/actions.ts', () => ({ + setServiceWorkerKeepAlivePreference: () => + mockSetServiceWorkerKeepAlivePreference, +})); + +describe('Develop options tab', () => { + const mockStore = configureMockStore([thunk])(mockState); + + it('should match snapshot', () => { + const { container } = renderWithProvider( + , + mockStore, + ); + + expect(container).toMatchSnapshot(); + }); + + it('should toggle Service Worker Keep Alive', async () => { + const { getByTestId } = renderWithProvider( + , + mockStore, + ); + const triggerButton = getByTestId( + 'developer-options-service-worker-alive-toggle', + ); + expect(triggerButton).toBeInTheDocument(); + fireEvent.click(triggerButton); + + expect(mockSetServiceWorkerKeepAlivePreference).toHaveBeenCalled(); + }); +}); diff --git a/ui/pages/settings/developer-options-tab/developer-options-tab.tsx b/ui/pages/settings/developer-options-tab/developer-options-tab.tsx new file mode 100644 index 000000000000..f145ec785924 --- /dev/null +++ b/ui/pages/settings/developer-options-tab/developer-options-tab.tsx @@ -0,0 +1,238 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { useHistory } from 'react-router-dom'; + +import { + Box, + Button, + ButtonVariant, + Icon, + IconName, + IconSize, + Text, +} from '../../../components/component-library'; +import { + IconColor, + TextColor, + Display, + FlexDirection, + JustifyContent, + AlignItems, +} from '../../../helpers/constants/design-system'; +import ToggleButton from '../../../components/ui/toggle-button'; +import { ONBOARDING_SECURE_YOUR_WALLET_ROUTE } from '../../../helpers/constants/routes'; +import { + getNumberOfSettingRoutesInTab, + handleSettingsRefs, +} from '../../../helpers/utils/settings-search'; + +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { + resetOnboarding, + resetViewedNotifications, + setServiceWorkerKeepAlivePreference, +} from '../../../store/actions'; +import { getEnvironmentType } from '../../../../app/scripts/lib/util'; +import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app'; + +const DeveloperOptionsTab = () => { + const t = useI18nContext(); + const dispatch = useDispatch(); + const history = useHistory(); + + const [hasResetAnnouncements, setHasResetAnnouncements] = useState(false); + const [hasResetOnboarding, setHasResetOnboarding] = useState(false); + const [isServiceWorkerKeptAlive, setIsServiceWorkerKeptAlive] = + useState(true); + + const settingsRefs = Array( + getNumberOfSettingRoutesInTab(t, t('developerOptions')), + ) + .fill(undefined) + .map(() => { + return React.createRef(); + }); + + useEffect(() => { + handleSettingsRefs(t, t('developerOptions'), settingsRefs); + }, [t, settingsRefs]); + + const handleResetAnnouncementClick = useCallback((): void => { + resetViewedNotifications(); + setHasResetAnnouncements(true); + }, []); + + const handleResetOnboardingClick = useCallback(async (): Promise => { + await dispatch(resetOnboarding()); + setHasResetOnboarding(true); + + const backUpSRPRoute = `${ONBOARDING_SECURE_YOUR_WALLET_ROUTE}/?isFromReminder=true`; + const isPopup = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP; + + if (isPopup) { + const { platform } = global; + if (platform?.openExtensionInBrowser) { + platform?.openExtensionInBrowser(backUpSRPRoute, null, true); + } + } else { + history.push(backUpSRPRoute); + } + }, [dispatch, history]); + + const handleToggleServiceWorkerAlive = async ( + value: boolean, + ): Promise => { + await dispatch(setServiceWorkerKeepAlivePreference(value)); + setIsServiceWorkerKeptAlive(value); + }; + + const renderAnnouncementReset = () => { + return ( + } + className="settings-page__content-row" + display={Display.Flex} + flexDirection={FlexDirection.Row} + justifyContent={JustifyContent.spaceBetween} + gap={4} + > +
+ {t('announcements')} +
+ {t('developerOptionsResetStatesAnnouncementsDescription')} +
+
+ +
+ +
+
+ + +
+
+ ); + }; + + const renderOnboardingReset = () => { + return ( + } + className="settings-page__content-row" + display={Display.Flex} + flexDirection={FlexDirection.Row} + justifyContent={JustifyContent.spaceBetween} + gap={4} + > +
+ {t('onboarding')} +
+ {t('developerOptionsResetStatesOnboarding')} +
+
+ +
+ +
+
+ + +
+
+ ); + }; + + const renderServiceWorkerKeepAliveToggle = () => { + return ( + } + className="settings-page__content-row" + display={Display.Flex} + flexDirection={FlexDirection.Row} + justifyContent={JustifyContent.spaceBetween} + gap={4} + > +
+
+ {t('serviceWorkerKeepAlive')} +
+ {t('developerOptionsServiceWorkerKeepAlive')} +
+
+
+ +
+ handleToggleServiceWorkerAlive(!value)} + offLabel={t('off')} + onLabel={t('on')} + dataTestId="developer-options-service-worker-alive-toggle" + /> +
+
+ ); + }; + return ( +
+ + {t('states')} + + } + > + {t('resetStates')} + + +
+ {renderAnnouncementReset()} + {renderOnboardingReset()} + {renderServiceWorkerKeepAliveToggle()} +
+
+ ); +}; + +export default DeveloperOptionsTab; diff --git a/ui/pages/settings/developer-options-tab/index.js b/ui/pages/settings/developer-options-tab/index.tsx similarity index 100% rename from ui/pages/settings/developer-options-tab/index.js rename to ui/pages/settings/developer-options-tab/index.tsx diff --git a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap index 7760799bfe4f..79726f584e1a 100644 --- a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap +++ b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap @@ -5,6 +5,84 @@ exports[`Security Tab should match snapshot 1`] = `
+
+ +
+