diff --git a/.github/workflows/build-lint-test.yml b/.github/workflows/build-lint-test.yml index 88bdd3d76c..9d3700e3af 100644 --- a/.github/workflows/build-lint-test.yml +++ b/.github/workflows/build-lint-test.yml @@ -14,18 +14,18 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x, 20.x] + node-version: [18.x, 20.11.x] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: yarn - run: yarn --immutable - name: Cache "@metamask/snaps-execution-environments" build id: cache-snaps-execution-environments-build - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | packages/snaps-execution-environments/dist/browserify @@ -37,61 +37,34 @@ jobs: id: workspace-package-names run: | { - echo "test-workspace-package-names=$(yarn workspaces filter --include 'packages/*' --exclude 'packages/examples' --json)" - echo "e2e-workspace-package-names=$(yarn workspaces filter --include 'packages/examples/packages/**' --exclude 'packages/examples/packages/invoke-snap' --json)" - echo "all-workspace-package-names=$(yarn workspaces filter --include '{.,packages/**}' --exclude 'packages/snaps-cli/test/snap' --json)" + echo "test-workspace-package-names=$(yarn workspaces filter list --include 'packages/*' --exclude 'packages/examples' --json)" + echo "e2e-workspace-package-names=$(yarn workspaces filter list --include 'packages/examples/packages/**' --exclude 'packages/examples/packages/invoke-snap' --json)" + echo "all-workspace-package-names=$(yarn workspaces filter list --include '{.,packages/**}' --exclude 'packages/snaps-cli/test/snap' --json)" } >> "$GITHUB_OUTPUT" shell: bash - build-source: - name: Build source + build: + name: Build runs-on: ubuntu-latest needs: prepare steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v3 - with: - node-version-file: '.nvmrc' - cache: yarn - - run: yarn --immutable --immutable-cache - - name: Build source - run: yarn build:source - - name: Cache build files - uses: actions/cache@v3 - with: - path: | - packages/*/dist/esm - packages/*/dist/cjs - key: build-source-${{ runner.os }}-${{ github.sha }} - - name: Require clean working directory - shell: bash - run: | - if ! git diff --exit-code; then - echo "Working tree dirty at end of job" - exit 1 - fi - - build-types: - name: Build types - runs-on: ubuntu-latest - needs: prepare - steps: - - uses: actions/checkout@v3 - - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: yarn - run: yarn --immutable --immutable-cache + - name: Build + run: yarn build:ci - name: Build types run: yarn build:types - name: Cache build files - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | - packages/*/dist/types - key: build-types-${{ runner.os }}-${{ github.sha }} + packages/*/dist + key: build-source-${{ runner.os }}-${{ github.sha }} - name: Require clean working directory shell: bash run: | @@ -100,52 +73,20 @@ jobs: exit 1 fi - post-build: - name: Post-build - runs-on: ubuntu-latest - needs: - - build-source - - build-types - steps: - - uses: actions/checkout@v3 - - name: Setup Node - uses: actions/setup-node@v3 - with: - node-version-file: '.nvmrc' - cache: yarn - - run: yarn --immutable --immutable-cache - - name: Restore build files - uses: actions/cache@v3 - with: - path: | - packages/*/dist/esm - packages/*/dist/cjs - key: build-source-${{ runner.os }}-${{ github.sha }} - fail-on-cache-miss: true - - name: Restore types files - uses: actions/cache@v3 - with: - path: | - packages/*/dist/types - key: build-types-${{ runner.os }}-${{ github.sha }} - fail-on-cache-miss: true - - name: Post-build - run: yarn build:post-tsc:ci - build-simulator: name: Build "@metamask/snaps-simulator" runs-on: ubuntu-latest needs: prepare steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: yarn - run: yarn --immutable --immutable-cache - name: Restore "@metamask/snaps-execution-environments" build - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | packages/snaps-execution-environments/dist/browserify @@ -153,7 +94,7 @@ jobs: fail-on-cache-miss: true - name: Cache Webpack vendor id: cache-webpack-vendor - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | packages/snaps-simulator/vendor @@ -165,7 +106,7 @@ jobs: run: yarn workspace @metamask/snaps-simulator run build:webpack - name: Cache "@metamask/snaps-simulator" build id: cache-e2e-simulator-build - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | packages/snaps-simulator/dist/webpack @@ -183,9 +124,9 @@ jobs: runs-on: ubuntu-latest needs: prepare steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: yarn @@ -205,9 +146,9 @@ jobs: runs-on: ubuntu-latest needs: prepare steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: yarn @@ -231,9 +172,9 @@ jobs: matrix: package-name: ${{ fromJson(needs.prepare.outputs.all-workspace-package-names) }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: yarn @@ -254,17 +195,17 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x, 20.x] + node-version: [18.x, 20.11.x] package-name: ${{ fromJson(needs.prepare.outputs.test-workspace-package-names) }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: yarn - name: Restore "@metamask/snaps-execution-environments" build - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | packages/snaps-execution-environments/dist/browserify @@ -273,18 +214,21 @@ jobs: - run: yarn --immutable --immutable-cache - name: Install Google Chrome run: yarn install-chrome + - run: yarn workspace @metamask/snaps-sdk run build + if: ${{ matrix.package-name == '@metamask/snaps-cli' }} - run: yarn workspace ${{ matrix.package-name }} run test:ci - name: Get coverage folder id: get-coverage-folder run: | echo "stub" >> stub echo "coverage-folder=$(yarn workspaces list --json | grep ${{ matrix.package-name }} | jq -r '.location')/coverage" >> "$GITHUB_OUTPUT" + echo "artifact-name=$(echo ${{ matrix.package-name }} | sed 's:.*/::')" >> "$GITHUB_OUTPUT" shell: bash - name: Upload coverage artifact if: ${{ matrix.node-version == '18.x' }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: coverage + name: coverage-${{ steps.get-coverage-folder.outputs.artifact-name }} path: | stub ${{ steps.get-coverage-folder.outputs.coverage-folder }}/**/coverage-final.json @@ -303,11 +247,12 @@ jobs: runs-on: ubuntu-latest needs: test steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download coverage artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: - name: coverage + pattern: coverage-* + merge-multiple: true - name: Upload coverage results uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 with: @@ -318,42 +263,33 @@ jobs: runs-on: ubuntu-latest needs: - prepare - - build-source - - build-types + - build strategy: fail-fast: false matrix: - node-version: [18.x, 20.x] + node-version: [18.x, 20.11.x] package-name: ${{ fromJson(needs.prepare.outputs.e2e-workspace-package-names) }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: yarn - name: Restore "@metamask/snaps-execution-environments" build - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | packages/snaps-execution-environments/dist/browserify key: snaps-execution-environments-build-${{ runner.os }}-${{ matrix.node-version }}-${{ github.sha }} fail-on-cache-miss: true - name: Restore build files - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | - packages/*/dist/esm - packages/*/dist/cjs + packages/*/dist key: build-source-${{ runner.os }}-${{ github.sha }} fail-on-cache-miss: true - - name: Restore types files - uses: actions/cache@v3 - with: - path: | - packages/*/dist/types - key: build-types-${{ runner.os }}-${{ github.sha }} - fail-on-cache-miss: true - run: yarn --immutable --immutable-cache - name: Build snap run: yarn workspace ${{ matrix.package-name }} run build @@ -375,11 +311,12 @@ jobs: matrix: os: [macOS-latest, windows-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: yarn - run: yarn --immutable + - run: yarn workspace @metamask/snaps-sdk run build - run: yarn workspace @metamask/snaps-cli run test diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5db44d5472..0f7bcecc96 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,7 +11,7 @@ jobs: name: Check workflows runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download actionlint id: download-actionlint run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/7fdc9630cc360ea1a469eed64ac6d78caeda1234/scripts/download-actionlint.bash) 1.6.25 @@ -33,7 +33,7 @@ jobs: contents: write uses: ./.github/workflows/publish-github-pages.yml with: - build_script: yarn workspace @metamask/snaps-simulator build:post-tsc + build_script: yarn workspace @metamask/snaps-simulator build publish_dir: ./packages/snaps-simulator/dist/webpack/main destination_dir: snaps-simulator/staging secrets: diff --git a/.github/workflows/publish-environment.yml b/.github/workflows/publish-environment.yml index 44525a971b..76797d0c68 100644 --- a/.github/workflows/publish-environment.yml +++ b/.github/workflows/publish-environment.yml @@ -19,9 +19,9 @@ jobs: - name: Ensure `destination_dir` is not empty if: ${{ inputs.destination_dir == '' }} run: exit 1 - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - run: yarn --immutable diff --git a/.github/workflows/publish-github-pages.yml b/.github/workflows/publish-github-pages.yml index 85a187047b..55839a62ea 100644 --- a/.github/workflows/publish-github-pages.yml +++ b/.github/workflows/publish-github-pages.yml @@ -33,9 +33,9 @@ jobs: - name: Ensure `publish_dir` is not empty if: ${{ inputs.publish_dir == '' }} run: exit 1 - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - run: yarn --immutable diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml index df04fdf90f..162ab2599e 100644 --- a/.github/workflows/publish-preview.yml +++ b/.github/workflows/publish-preview.yml @@ -12,7 +12,7 @@ jobs: outputs: IS_FORK: ${{ steps.is-fork.outputs.IS_FORK }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Determine whether this PR is from a fork id: is-fork run: echo "IS_FORK=$(gh pr view --json isCrossRepository --jq '.isCrossRepository' "${PR_NUMBER}" )" >> "$GITHUB_OUTPUT" @@ -29,14 +29,14 @@ jobs: if: ${{ needs.is-fork-pull-request.outputs.IS_FORK == 'false' }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Checkout pull request run: gh pr checkout "${PR_NUMBER}" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.issue.number }} - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: yarn diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 4db6c4d408..cce43c2d35 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -33,11 +33,11 @@ jobs: outputs: tag: ${{ steps.get-release-tag.outputs.tag }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.sha }} - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - run: yarn install --immutable @@ -53,11 +53,11 @@ jobs: contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.sha }} - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - uses: MetaMask/action-publish-release@v3 @@ -69,7 +69,7 @@ jobs: - run: | yarn install --immutable yarn build - - uses: actions/cache@v3 + - uses: actions/cache@v4 id: restore-build with: path: | @@ -82,10 +82,10 @@ jobs: runs-on: ubuntu-latest needs: publish-release steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.sha }} - - uses: actions/cache@v3 + - uses: actions/cache@v4 id: restore-build with: path: | @@ -109,10 +109,10 @@ jobs: - npm-publish-dry-run - get-release-tag steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.sha }} - - uses: actions/cache@v3 + - uses: actions/cache@v4 id: restore-build with: path: | @@ -133,7 +133,7 @@ jobs: outputs: IS_ENVIRONMENT_RELEASE: ${{ steps.is-environment-release.outputs.IS_ENVIRONMENT_RELEASE }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.sha }} fetch-depth: 2 @@ -153,7 +153,7 @@ jobs: outputs: version: ${{ steps.version.outputs.VERSION }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.sha }} - id: version @@ -218,7 +218,7 @@ jobs: IS_TEST_SNAPS_RELEASE: ${{ steps.set-output.outputs.IS_TEST_SNAPS_RELEASE }} TEST_SNAPS_VERSION: ${{ steps.set-output.outputs.TEST_SNAPS_VERSION }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.sha }} fetch-depth: 2 @@ -269,7 +269,7 @@ jobs: IS_SIMULATOR_RELEASE: ${{ steps.set-output.outputs.IS_SIMULATOR_RELEASE }} SIMULATOR_VERSION: ${{ steps.set-output.outputs.SIMULATOR_VERSION }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.sha }} fetch-depth: 2 @@ -292,7 +292,7 @@ jobs: contents: write uses: ./.github/workflows/publish-github-pages.yml with: - build_script: yarn workspace @metamask/snaps-simulator build:post-tsc + build_script: yarn workspace @metamask/snaps-simulator build publish_dir: ./packages/snaps-simulator/dist/webpack/main destination_dir: snaps-simulator/${{ needs.is-simulator-release.outputs.SIMULATOR_VERSION }} secrets: @@ -306,7 +306,7 @@ jobs: contents: write uses: ./.github/workflows/publish-github-pages.yml with: - build_script: yarn workspace @metamask/snaps-simulator build:post-tsc + build_script: yarn workspace @metamask/snaps-simulator build publish_dir: ./packages/snaps-simulator/dist/webpack/main destination_dir: snaps-simulator/latest secrets: diff --git a/.github/workflows/security-code-scanner.yml b/.github/workflows/security-code-scanner.yml new file mode 100644 index 0000000000..6a729b418a --- /dev/null +++ b/.github/workflows/security-code-scanner.yml @@ -0,0 +1,35 @@ +name: 'MetaMask Security Code Scanner' + +on: + push: + branches: ['main'] + pull_request: + +jobs: + run-security-scan: + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + steps: + - name: MetaMask Security Code Scanner + uses: MetaMask/Security-Code-Scanner@main + with: + repo: ${{ github.repository }} + paths_ignored: | + tests/ + '**/test/' + '**/test-utils/' + '**/__mocks__/' + '**/__snapshots__/' + '**/__fixtures__/' + '**/*.test.js*' + '**/*.test.ts*' + '**/*.test.browser.ts*' + docs/ + '**/jest.environment.js' + '**/jest.config.js' + node_modules + project_metrics_token: ${{secrets.SECURITY_SCAN_METRICS_TOKEN}} + slack_webhook: ${{ secrets.APPSEC_BOT_SLACK_WEBHOOK }} diff --git a/.github/workflows/update-pull-request.yml b/.github/workflows/update-pull-request.yml index 309d2944eb..3b8ced51f6 100644 --- a/.github/workflows/update-pull-request.yml +++ b/.github/workflows/update-pull-request.yml @@ -13,7 +13,7 @@ jobs: outputs: IS_FORK: ${{ steps.is-fork.outputs.IS_FORK }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Determine whether this PR is from a fork id: is-fork run: echo "IS_FORK=$(gh pr view --json isCrossRepository --jq '.isCrossRepository' "${PR_NUMBER}" )" >> "$GITHUB_OUTPUT" @@ -29,7 +29,7 @@ jobs: if: ${{ needs.is-fork-pull-request.outputs.IS_FORK == 'false' }} steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: React to the comment run: | gh api \ @@ -53,14 +53,14 @@ jobs: COMMIT_SHA: ${{ steps.commit-sha.outputs.COMMIT_SHA }} steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Checkout pull request run: gh pr checkout "${PR_NUMBER}" env: GITHUB_TOKEN: ${{ secrets.PULL_REQUEST_UPDATE_TOKEN }} PR_NUMBER: ${{ github.event.issue.number }} - name: Use Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: 'yarn' @@ -76,14 +76,14 @@ jobs: needs: prepare steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Checkout pull request run: gh pr checkout "${PR_NUMBER}" env: GITHUB_TOKEN: ${{ secrets.PULL_REQUEST_UPDATE_TOKEN }} PR_NUMBER: ${{ github.event.issue.number }} - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: 'yarn' @@ -92,7 +92,7 @@ jobs: - name: Deduplicate yarn.lock run: yarn dedupe - name: Cache yarn.lock - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: yarn.lock key: cache-yarn-lock-${{ needs.prepare.outputs.COMMIT_SHA }} @@ -105,20 +105,20 @@ jobs: - dedupe-yarn-lock steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Checkout pull request run: gh pr checkout "${PR_NUMBER}" env: GITHUB_TOKEN: ${{ secrets.PULL_REQUEST_UPDATE_TOKEN }} PR_NUMBER: ${{ github.event.issue.number }} - name: Restore yarn.lock - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: yarn.lock key: cache-yarn-lock-${{ needs.prepare.outputs.COMMIT_SHA }} fail-on-cache-miss: true - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: 'yarn' @@ -127,7 +127,7 @@ jobs: - name: Regenerate LavaMoat policies run: yarn build:lavamoat:policy - name: Cache LavaMoat policies - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: packages/snaps-execution-environments/lavamoat key: cache-lavamoat-${{ needs.prepare.outputs.COMMIT_SHA }} @@ -140,20 +140,20 @@ jobs: - dedupe-yarn-lock steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Checkout pull request run: gh pr checkout "${PR_NUMBER}" env: GITHUB_TOKEN: ${{ secrets.PULL_REQUEST_UPDATE_TOKEN }} PR_NUMBER: ${{ github.event.issue.number }} - name: Restore yarn.lock - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: yarn.lock key: cache-yarn-lock-${{ needs.prepare.outputs.COMMIT_SHA }} fail-on-cache-miss: true - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: 'yarn' @@ -161,12 +161,11 @@ jobs: run: yarn --immutable - name: Build dependencies run: | - yarn build:source - yarn build:types + yarn build:ci - name: Update examples run: yarn build:examples - name: Cache examples - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: packages/examples/packages key: cache-examples-${{ needs.prepare.outputs.COMMIT_SHA }} @@ -181,7 +180,7 @@ jobs: - update-examples steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Use PAT to ensure that the commit later can trigger status check # workflows. @@ -199,7 +198,7 @@ jobs: id: commit-sha run: echo "COMMIT_SHA=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" - name: Restore yarn.lock - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: yarn.lock key: cache-yarn-lock-${{ needs.prepare.outputs.COMMIT_SHA }} @@ -209,7 +208,7 @@ jobs: git add yarn.lock git commit -m "Deduplicate yarn.lock" || true - name: Restore LavaMoat policies - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: packages/snaps-execution-environments/lavamoat key: cache-lavamoat-${{ needs.prepare.outputs.COMMIT_SHA }} @@ -219,7 +218,7 @@ jobs: git add packages/snaps-execution-environments/lavamoat git commit -m "Update LavaMoat policies" || true - name: Restore examples - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: packages/examples/packages key: cache-examples-${{ needs.prepare.outputs.COMMIT_SHA }} diff --git a/.nvmrc b/.nvmrc index 9a2a0e219c..07533ba8b3 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v20 +v20.11 diff --git a/.yarn/patches/@lavamoat-lavapack-npm-6.0.2-abebdc70c1.patch b/.yarn/patches/@lavamoat-lavapack-npm-6.1.1-b81af21193.patch similarity index 82% rename from .yarn/patches/@lavamoat-lavapack-npm-6.0.2-abebdc70c1.patch rename to .yarn/patches/@lavamoat-lavapack-npm-6.1.1-b81af21193.patch index 6c429fb0e6..168d01c6db 100644 --- a/.yarn/patches/@lavamoat-lavapack-npm-6.0.2-abebdc70c1.patch +++ b/.yarn/patches/@lavamoat-lavapack-npm-6.1.1-b81af21193.patch @@ -1,8 +1,8 @@ diff --git a/src/runtime.js b/src/runtime.js -index 545794c974c94f12f86abe510a33598b63cb3c01..2cd1210cfe6de51224e6b493770ad48c67c0c6d0 100644 +index b07f2d2cfba47561e6c43bf2b8b529b69129cae9..73c6c4a60ce2c571f42ac08fb79ae4bf0a7a27ef 100644 --- a/src/runtime.js +++ b/src/runtime.js -@@ -13196,6 +13196,17 @@ module.exports = { +@@ -13290,6 +13290,17 @@ module.exports = { function loadModuleData (moduleId) { diff --git a/.yarn/patches/@puppeteer-browsers-npm-1.7.0-203cb4f44b.patch b/.yarn/patches/@puppeteer-browsers-npm-1.7.0-203cb4f44b.patch new file mode 100644 index 0000000000..65cc6bbbc5 --- /dev/null +++ b/.yarn/patches/@puppeteer-browsers-npm-1.7.0-203cb4f44b.patch @@ -0,0 +1,104 @@ +diff --git a/lib/cjs/browser-data/chrome-headless-shell.js b/lib/cjs/browser-data/chrome-headless-shell.js +index af555e4fb3294f7a48660f8d4a97ddf52dada0e5..c3b6a9f5860d08817869016dcef13eab3c63499a 100644 +--- a/lib/cjs/browser-data/chrome-headless-shell.js ++++ b/lib/cjs/browser-data/chrome-headless-shell.js +@@ -35,7 +35,7 @@ function folder(platform) { + return 'win64'; + } + } +-function resolveDownloadUrl(platform, buildId, baseUrl = 'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing') { ++function resolveDownloadUrl(platform, buildId, baseUrl = 'https://storage.googleapis.com/chrome-for-testing-public') { + return `${baseUrl}/${resolveDownloadPath(platform, buildId).join('/')}`; + } + exports.resolveDownloadUrl = resolveDownloadUrl; +diff --git a/lib/cjs/browser-data/chrome.js b/lib/cjs/browser-data/chrome.js +index 6af4e6f63e55e83a00e5f5c557fc0dbb0bfc4865..9988779d2d307815776d6bcdd2a877af15f015cb 100644 +--- a/lib/cjs/browser-data/chrome.js ++++ b/lib/cjs/browser-data/chrome.js +@@ -36,7 +36,7 @@ function folder(platform) { + return 'win64'; + } + } +-function resolveDownloadUrl(platform, buildId, baseUrl = 'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing') { ++function resolveDownloadUrl(platform, buildId, baseUrl = 'https://storage.googleapis.com/chrome-for-testing-public') { + return `${baseUrl}/${resolveDownloadPath(platform, buildId).join('/')}`; + } + exports.resolveDownloadUrl = resolveDownloadUrl; +diff --git a/lib/cjs/browser-data/chromedriver.js b/lib/cjs/browser-data/chromedriver.js +index f9ced6c4724e3cb3a3188989f1bab25b3aa4941b..faea49e7c8b1f4fa85acfae0d78a9a1a2a3c0a30 100644 +--- a/lib/cjs/browser-data/chromedriver.js ++++ b/lib/cjs/browser-data/chromedriver.js +@@ -35,7 +35,7 @@ function folder(platform) { + return 'win64'; + } + } +-function resolveDownloadUrl(platform, buildId, baseUrl = 'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing') { ++function resolveDownloadUrl(platform, buildId, baseUrl = 'https://storage.googleapis.com/chrome-for-testing-public') { + return `${baseUrl}/${resolveDownloadPath(platform, buildId).join('/')}`; + } + exports.resolveDownloadUrl = resolveDownloadUrl; +diff --git a/lib/cjs/install.d.ts b/lib/cjs/install.d.ts +index 68fb99dff49e3c82b9dbd035343562ca8157a53d..5d9bd466c83458f6497a716ae6f6bbde444927c2 100644 +--- a/lib/cjs/install.d.ts ++++ b/lib/cjs/install.d.ts +@@ -47,7 +47,7 @@ export interface InstallOptions { + * + * @defaultValue Either + * +- * - https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing or ++ * - https://storage.googleapis.com/chrome-for-testing-public or + * - https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central + * + */ +diff --git a/lib/esm/browser-data/chrome-headless-shell.js b/lib/esm/browser-data/chrome-headless-shell.js +index cf45517bd6dbc1cdebd289b5e01db9a557c2822d..f8fd3d6362cd2e40f33df5fb4f30f7ec7f06d611 100644 +--- a/lib/esm/browser-data/chrome-headless-shell.js ++++ b/lib/esm/browser-data/chrome-headless-shell.js +@@ -29,7 +29,7 @@ function folder(platform) { + return 'win64'; + } + } +-export function resolveDownloadUrl(platform, buildId, baseUrl = 'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing') { ++export function resolveDownloadUrl(platform, buildId, baseUrl = 'https://storage.googleapis.com/chrome-for-testing-public') { + return `${baseUrl}/${resolveDownloadPath(platform, buildId).join('/')}`; + } + export function resolveDownloadPath(platform, buildId) { +diff --git a/lib/esm/browser-data/chrome.js b/lib/esm/browser-data/chrome.js +index e550644bfc0893169feb46d884e1de01d10ccb12..87f8daec7b3e5d30790946149c51cae01278ecbc 100644 +--- a/lib/esm/browser-data/chrome.js ++++ b/lib/esm/browser-data/chrome.js +@@ -30,7 +30,7 @@ function folder(platform) { + return 'win64'; + } + } +-export function resolveDownloadUrl(platform, buildId, baseUrl = 'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing') { ++export function resolveDownloadUrl(platform, buildId, baseUrl = 'https://storage.googleapis.com/chrome-for-testing-public') { + return `${baseUrl}/${resolveDownloadPath(platform, buildId).join('/')}`; + } + export function resolveDownloadPath(platform, buildId) { +diff --git a/lib/esm/browser-data/chromedriver.js b/lib/esm/browser-data/chromedriver.js +index a4504e4f2d9b361b393c013d3cb6ca325a346770..aa48aca65487921e04f735283a99218621682bdc 100644 +--- a/lib/esm/browser-data/chromedriver.js ++++ b/lib/esm/browser-data/chromedriver.js +@@ -29,7 +29,7 @@ function folder(platform) { + return 'win64'; + } + } +-export function resolveDownloadUrl(platform, buildId, baseUrl = 'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing') { ++export function resolveDownloadUrl(platform, buildId, baseUrl = 'https://storage.googleapis.com/chrome-for-testing-public') { + return `${baseUrl}/${resolveDownloadPath(platform, buildId).join('/')}`; + } + export function resolveDownloadPath(platform, buildId) { +diff --git a/lib/esm/install.d.ts b/lib/esm/install.d.ts +index 68fb99dff49e3c82b9dbd035343562ca8157a53d..5d9bd466c83458f6497a716ae6f6bbde444927c2 100644 +--- a/lib/esm/install.d.ts ++++ b/lib/esm/install.d.ts +@@ -47,7 +47,7 @@ export interface InstallOptions { + * + * @defaultValue Either + * +- * - https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing or ++ * - https://storage.googleapis.com/chrome-for-testing-public or + * - https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central + * + */ diff --git a/.yarn/patches/lavamoat-browserify-npm-17.0.2-7b95761e43.patch b/.yarn/patches/lavamoat-browserify-npm-17.0.5-16c00e9ef9.patch similarity index 100% rename from .yarn/patches/lavamoat-browserify-npm-17.0.2-7b95761e43.patch rename to .yarn/patches/lavamoat-browserify-npm-17.0.5-16c00e9ef9.patch diff --git a/.yarn/patches/tsconfig-paths-npm-3.14.2-90ce75420d.patch b/.yarn/patches/tsconfig-paths-npm-3.14.2-90ce75420d.patch new file mode 100644 index 0000000000..21e7e5abd9 --- /dev/null +++ b/.yarn/patches/tsconfig-paths-npm-3.14.2-90ce75420d.patch @@ -0,0 +1,13 @@ +diff --git a/lib/try-path.js b/lib/try-path.js +index de11ccbf9090658eb0ee61bdba1984ee21bcb3b7..2e75f72483aa7b05bf0a2cde06b3c009eb2103e8 100644 +--- a/lib/try-path.js ++++ b/lib/try-path.js +@@ -85,6 +85,6 @@ function matchStar(pattern, search) { + if (search.substr(search.length - part2.length) !== part2) { + return undefined; + } +- return search.substr(star, search.length - part2.length); ++ return search.substr(star, search.length - part2.length - part1.length); + } + //# sourceMappingURL=try-path.js.map +\ No newline at end of file diff --git a/.yarn/patches/tsup-npm-8.0.2-86e40f68a7.patch b/.yarn/patches/tsup-npm-8.0.2-86e40f68a7.patch new file mode 100644 index 0000000000..2ac7512ab5 --- /dev/null +++ b/.yarn/patches/tsup-npm-8.0.2-86e40f68a7.patch @@ -0,0 +1,13 @@ +diff --git a/dist/index.js b/dist/index.js +index 4500c4e43c3bbd24aa60b7d4cf95aa3fee8eb185..9c442bc216f99b7cfadb5ac62cb98d3ae9ce2f56 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -1813,6 +1813,8 @@ var cjsSplitting = () => { + } + const { transform: transform3 } = await Promise.resolve().then(() => require("sucrase")); + const result = transform3(code, { ++ // https://github.com/egoist/tsup/issues/1087 ++ disableESTransforms: true, + filePath: info.path, + transforms: ["imports"], + sourceMapOptions: this.options.sourcemap ? { diff --git a/.yarn/plugins/local/plugin-workspaces-filter.js b/.yarn/plugins/local/plugin-workspaces-filter.js index 378b09130f..38808392b8 100644 --- a/.yarn/plugins/local/plugin-workspaces-filter.js +++ b/.yarn/plugins/local/plugin-workspaces-filter.js @@ -6,8 +6,8 @@ module.exports = { const { Command, Option, UsageError } = require('clipanion'); const { isString, isBoolean } = require('typanion'); - class FilterCommand extends BaseCommand { - static paths = [['workspaces', 'filter']]; + class FilterListCommand extends BaseCommand { + static paths = [['workspaces', 'filter', 'list']]; static usage = Command.Usage({ description: 'Filter workspaces', @@ -18,11 +18,11 @@ module.exports = { examples: [ [ `List workspaces based on a glob pattern`, - `yarn workspaces filter --include "packages/*"`, + `yarn workspaces filter list --include "packages/*"`, ], [ 'Exclude workspaces based on a glob pattern', - `yarn workspaces filter --exclude "packages/*/foo"`, + `yarn workspaces filter list --exclude "packages/*/foo"`, ], ], }); @@ -42,6 +42,34 @@ module.exports = { validator: isBoolean, }); + /** + * List the names of the workspaces. If `--json` is set, the names will be + * printed as a JSON array. + * + * @param {Workspace[]} workspaces + * @param {Configuration} configuration + * @returns {Promise} + */ + async list(workspaces, configuration) { + const report = await StreamReport.start( + { + configuration, + json: this.json, + stdout: this.context.stdout, + }, + async (report) => { + for (const workspace of workspaces) { + report.reportInfo(null, workspace.relativeCwd); + } + + const result = workspaces.map((workspace) => workspace.manifest.raw.name); + report.reportJson(result); + }, + ); + + return report.exitCode(); + } + async execute() { // Note: We have to import `minimatch` here, because Yarn will always // load the plugin, even if the command is not used, and `minimatch` @@ -61,37 +89,137 @@ module.exports = { ); } - const report = await StreamReport.start( - { - configuration, - json: this.json, - stdout: this.context.stdout, - }, - async (report) => { - const filteredWorkspaces = workspaces.filter((workspace) => { - return ( - (!this.include || - minimatch(workspace.relativeCwd, this.include)) && - (!this.exclude || - !minimatch(workspace.relativeCwd, this.exclude)) - ); - }); - - for (const workspace of filteredWorkspaces) { - report.reportInfo(null, workspace.relativeCwd); - } + const filteredWorkspaces = workspaces.filter((workspace) => { + return ( + (!this.include || + minimatch(workspace.relativeCwd, this.include)) && + (!this.exclude || + !minimatch(workspace.relativeCwd, this.exclude)) + ); + }); - const result = filteredWorkspaces.map((workspace) => workspace.manifest.raw.name); - report.reportJson(result); - }, + return await this.list(filteredWorkspaces, configuration); + } + } + + class FilterRunCommand extends BaseCommand { + static paths = [['workspaces', 'filter']]; + + static usage = Command.Usage({ + description: 'Filter workspaces', + details: ` + This command will run a command in workspaces based on the given + criteria. It's like \`yarn workspaces foreach\` but on steroids. + `, + examples: [ + [ + `List workspaces based on a glob pattern`, + `yarn workspaces filter --include "packages/*" run build`, + ], + [ + 'Exclude workspaces based on a glob pattern', + `yarn workspaces filter --exclude "packages/*/foo" run build`, + ], + ], + }); + + commandName = Option.String({ + required: true, + description: `The name of the command to run`, + validator: isString, + }); + + args = Option.Proxy({ + required: false, + }); + + parallel = Option.Boolean(`--parallel`, { + default: false, + description: `Run the commands in parallel`, + validator: isBoolean, + }); + + topological = Option.Boolean(`--topological`, false, { + description: `Run the commands in topological order`, + validator: isBoolean, + }); + + include = Option.String('--include', { + description: `List workspaces based on a glob pattern`, + validator: isString, + }); + + exclude = Option.String('--exclude', { + description: `Exclude workspaces based on a glob pattern`, + validator: isString, + }); + + noPrivate = Option.Boolean(`--no-private`, false, { + description: `Exclude private workspaces`, + validator: isBoolean, + }); + + /** + * Run the given command on the workspaces. + * + * @param workspaces - The workspaces to run the command on. + * @param commandName - The name of the command to run. + * @param args - The arguments to pass to the command. + * @return {Promise} + */ + async run(workspaces, commandName, args) { + let extraArgs = []; + if (this.parallel) { + extraArgs.push('--parallel'); + } + + if (this.topological) { + extraArgs.push('--topological'); + extraArgs.push('--topological-dev'); + } + + const includes = workspaces.map((workspace) => workspace.manifest.name) + .flatMap(({ scope, name }) => ['--include', `@${scope}/${name}`]); + + await this.cli.run(['workspaces', 'foreach', '--verbose', ...includes, ...extraArgs, commandName, ...args], this.context); + } + + async execute() { + // Note: We have to import `minimatch` here, because Yarn will always + // load the plugin, even if the command is not used, and `minimatch` + // may not be installed. + const { minimatch } = await import('minimatch'); + + const configuration = await Configuration.find( + this.context.cwd, + this.context.plugins, ); + const { project } = await Project.find(configuration, this.context.cwd); + const { workspaces } = project; - return report.exitCode(); + if (!this.include && !this.exclude) { + throw new UsageError( + `This command requires at least one of --include or --exclude to be specified.`, + ); + } + + const filteredWorkspaces = workspaces.filter((workspace) => { + return ( + (!this.include || + minimatch(workspace.relativeCwd, this.include)) && + (!this.exclude || + !minimatch(workspace.relativeCwd, this.exclude)) + ); + }).filter((workspace) => { + return !this.noPrivate || !workspace.manifest.private; + }); + + return await this.run(filteredWorkspaces, this.commandName, this.args); } } return { - commands: [FilterCommand], + commands: [FilterListCommand, FilterRunCommand], }; }, }; diff --git a/constraints.pro b/constraints.pro index b5997c9bed..258537ce64 100644 --- a/constraints.pro +++ b/constraints.pro @@ -157,24 +157,72 @@ gen_enforced_field(WorkspaceCwd, 'types', './dist/types/index.d.ts') :- \+ is_example(WorkspaceCwd), \+ workspace_field(WorkspaceCwd, 'private', true), WorkspaceCwd \= '.'. +gen_enforced_field(WorkspaceCwd, 'exports["."].types', './dist/types/index.d.ts') :- + \+ is_example(WorkspaceCwd), + \+ workspace_field(WorkspaceCwd, 'private', true), + WorkspaceCwd \= '.'. % The entrypoint for the dependency must be `./dist/cjs/index.js`. -gen_enforced_field(WorkspaceCwd, 'main', './dist/cjs/index.js') :- +gen_enforced_field(WorkspaceCwd, 'main', './dist/index.js') :- + \+ is_example(WorkspaceCwd), + \+ workspace_field(WorkspaceCwd, 'private', true), + WorkspaceCwd \= '.'. +gen_enforced_field(WorkspaceCwd, 'exports["."].require', './dist/index.js') :- \+ is_example(WorkspaceCwd), \+ workspace_field(WorkspaceCwd, 'private', true), WorkspaceCwd \= '.'. % The module entrypoint for the dependency must be `./dist/esm/index.js`. -gen_enforced_field(WorkspaceCwd, 'module', './dist/esm/index.js') :- +gen_enforced_field(WorkspaceCwd, 'module', './dist/index.mjs') :- + \+ is_example(WorkspaceCwd), + \+ workspace_field(WorkspaceCwd, 'private', true), + WorkspaceCwd \= '.'. +gen_enforced_field(WorkspaceCwd, 'exports["."].import', './dist/index.mjs') :- + \+ is_example(WorkspaceCwd), + \+ workspace_field(WorkspaceCwd, 'private', true), + WorkspaceCwd \= '.'. + +% `package.json` must be exported. +gen_enforced_field(WorkspaceCwd, 'exports["./package.json"]', './package.json') :- + \+ is_example(WorkspaceCwd), + \+ workspace_field(WorkspaceCwd, 'private', true), + WorkspaceCwd \= '.'. + +% The list of files included in the package must only include files generated +% during the build step. +gen_enforced_field(WorkspaceCwd, 'files', ['dist']) :- + \+ is_example(WorkspaceCwd), + \+ workspace_field(WorkspaceCwd, 'private', true), + WorkspaceCwd \= '.', + WorkspaceCwd \= 'packages/snaps-jest', + WorkspaceCwd \= 'packages/snaps-cli'. +gen_enforced_field(WorkspaceCwd, 'files', ['dist', 'jest-preset.js']) :- + WorkspaceCwd = 'packages/snaps-jest'. + +% Dependencies must have a build script. +gen_enforced_field(WorkspaceCwd, 'scripts.build', 'tsup --clean && yarn build:types') :- + \+ is_example(WorkspaceCwd), + \+ workspace_field(WorkspaceCwd, 'private', true), + WorkspaceCwd \= '.', + WorkspaceCwd \= 'packages/snaps-simulator', + WorkspaceCwd \= 'packages/snaps-cli', + WorkspaceCwd \= 'packages/snaps-execution-environments'. +gen_enforced_field(WorkspaceCwd, 'scripts.build:types', 'tsc --project tsconfig.build.json') :- \+ is_example(WorkspaceCwd), \+ workspace_field(WorkspaceCwd, 'private', true), WorkspaceCwd \= '.'. +gen_enforced_field(WorkspaceCwd, 'scripts.build:ci', 'tsup --clean') :- + \+ is_example(WorkspaceCwd), + \+ workspace_field(WorkspaceCwd, 'private', true), + WorkspaceCwd \= '.', + WorkspaceCwd \= 'packages/snaps-simulator'. % Dependencies must have preview scripts. gen_enforced_field(WorkspaceCwd, 'scripts.publish:preview', 'yarn npm publish --tag preview') :- \+ workspace_field(WorkspaceCwd, 'private', true), WorkspaceCwd \= '.'. +% Dependencies must have a "publishConfig" field. gen_enforced_field(WorkspaceCwd, 'publishConfig.access', 'public') :- \+ workspace_field(WorkspaceCwd, 'private', true), WorkspaceCwd \= '.'. diff --git a/jest.config.base.js b/jest.config.base.js index cfd44e2485..6c82f47e22 100644 --- a/jest.config.base.js +++ b/jest.config.base.js @@ -95,6 +95,7 @@ module.exports = { // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module moduleNameMapper: { '^@metamask/(.+)/test-utils$': ['/../$1/src/test-utils'], + '^@metamask/(.+)/node$': ['/../$1/src/node'], '^@metamask/(.+)$': [ '/../$1/src', '/../../node_modules/@metamask/$1', diff --git a/package.json b/package.json index 7f4a3784e9..d119368f3e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "31.0.0", + "version": "44.0.0", "private": true, "repository": { "type": "git", @@ -10,23 +10,21 @@ "packages/*" ], "scripts": { - "setup": "yarn install", "postinstall": "simple-git-hooks", "lint:eslint": "eslint . --cache --ext js,jsx,ts,tsx", "lint:misc": "prettier --no-error-on-unmatched-pattern --loglevel warn '**/*.json' '**/*.md' '!**/CHANGELOG.md' '**/*.yml' '**/*.html'", "lint:changelogs": "yarn workspaces foreach --parallel --verbose run lint:changelog", - "lint:dependencies": "yarn workspaces foreach --parallel --verbose run lint:dependencies", + "lint:dependencies": "yarn workspaces foreach --parallel --verbose run lint:dependencies && yarn dedupe --check", "lint:tsconfig": "node scripts/verify-tsconfig.mjs", "lint": "yarn workspaces foreach --parallel run lint:eslint && yarn lint:misc --check && yarn lint:tsconfig && yarn constraints && yarn lint:dependencies", - "lint:fix": "yarn workspaces foreach --parallel run lint:eslint --fix && yarn lint:misc --write && yarn lint:tsconfig && yarn constraints --fix", + "lint:fix": "yarn workspaces foreach --parallel run lint:eslint --fix && yarn lint:misc --write && yarn lint:tsconfig && yarn constraints --fix && yarn dedupe", "lint:ci": "yarn lint:eslint && yarn lint:misc --check && yarn lint:tsconfig && yarn constraints && yarn lint:dependencies", - "build": "yarn build:source && yarn build:types && yarn build:post-tsc", + "build": "yarn build:source && yarn build:types", + "build:ci": "yarn workspaces filter --include \"packages/*\" --parallel --topological --no-private run build:ci", "build:clean": "yarn clean && yarn build", - "build:source": "yarn workspaces foreach --parallel --verbose run build:source", + "build:source": "yarn workspaces filter --parallel --topological --exclude \"{packages/examples,packages/examples/packages/invoke-snap}\" run build", "build:types": "tsc --build tsconfig.build.json", "build:examples": "yarn workspace @metamask/example-snaps build", - "build:post-tsc": "yarn workspaces foreach --parallel --topological --topological-dev --verbose run build:post-tsc", - "build:post-tsc:ci": "yarn workspaces foreach --parallel --topological --topological-dev --verbose --exclude root --exclude \"@metamask/snaps-simulator\" --exclude \"@metamask/snaps-execution-environments\" --exclude \"@metamask/snaps-jest\" --exclude \"@metamask/example-snaps\" --exclude \"@metamask/test-snaps\" run build:post-tsc", "clean": "yarn workspaces foreach --parallel --verbose run clean", "test": "yarn workspaces foreach --parallel --verbose run test", "test:browser": "yarn workspaces foreach --verbose run test:browser", @@ -51,7 +49,9 @@ "resolutions": { "@babel/core": "patch:@babel/core@npm%3A7.23.2#./.yarn/patches/@babel-core-npm-7.23.2-b93f586907.patch", "@esbuild-plugins/node-modules-polyfill@^0.2.2": "patch:@esbuild-plugins/node-modules-polyfill@npm%3A0.2.2#./.yarn/patches/@esbuild-plugins-node-modules-polyfill-npm-0.2.2-f612681798.patch", - "@lavamoat/lavapack@^6.0.2": "patch:@lavamoat/lavapack@npm%3A6.0.2#./.yarn/patches/@lavamoat-lavapack-npm-6.0.2-abebdc70c1.patch", + "@lavamoat/lavapack@^6.1.1": "patch:@lavamoat/lavapack@npm%3A6.1.1#./.yarn/patches/@lavamoat-lavapack-npm-6.1.1-b81af21193.patch", + "@puppeteer/browsers@1.4.6": "patch:@puppeteer/browsers@npm%3A1.7.0#./.yarn/patches/@puppeteer-browsers-npm-1.7.0-203cb4f44b.patch", + "@puppeteer/browsers@^1.6.0": "patch:@puppeteer/browsers@npm%3A1.7.0#./.yarn/patches/@puppeteer-browsers-npm-1.7.0-203cb4f44b.patch", "@types/glob@*": "patch:@types/glob@npm%3A7.1.4#./.yarn/patches/@types-glob-npm-7.1.4-d45247eaa2.patch", "@types/glob@^7.1.1": "patch:@types/glob@npm%3A7.1.4#./.yarn/patches/@types-glob-npm-7.1.4-d45247eaa2.patch", "@types/mocha@^10.0.1": "patch:@types/mocha@npm:10.0.1#.yarn/patches/@types-mocha-npm-10.0.1-7c94e9e170.patch", @@ -61,11 +61,15 @@ "jest-fetch-mock@^3.0.3": "patch:jest-fetch-mock@npm:3.0.3#.yarn/patches/jest-fetch-mock-npm-3.0.3-ac072ca8af.patch", "jest-util@^29.5.0": "patch:jest-util@npm%3A29.6.3#./.yarn/patches/jest-util-npm-29.6.3-6ffdea2c1c.patch", "jest-util@^29.6.3": "patch:jest-util@npm%3A29.6.3#./.yarn/patches/jest-util-npm-29.6.3-6ffdea2c1c.patch", - "lavamoat-browserify@^17.0.2": "patch:lavamoat-browserify@npm%3A17.0.2#./.yarn/patches/lavamoat-browserify-npm-17.0.2-7b95761e43.patch", - "luxon@^3.2.1": "patch:luxon@npm%3A3.3.0#./.yarn/patches/luxon-npm-3.3.0-bdbae9bfd5.patch" + "lavamoat-browserify@^17.0.5": "patch:lavamoat-browserify@npm%3A17.0.5#./.yarn/patches/lavamoat-browserify-npm-17.0.5-16c00e9ef9.patch", + "luxon@^3.2.1": "patch:luxon@npm%3A3.3.0#./.yarn/patches/luxon-npm-3.3.0-bdbae9bfd5.patch", + "tsconfig-paths@^3.11.0": "patch:tsconfig-paths@npm%3A3.14.2#./.yarn/patches/tsconfig-paths-npm-3.14.2-90ce75420d.patch", + "tsconfig-paths@^3.14.1": "patch:tsconfig-paths@npm%3A3.14.2#./.yarn/patches/tsconfig-paths-npm-3.14.2-90ce75420d.patch", + "tsconfig-paths@^4.1.2": "patch:tsconfig-paths@npm%3A3.14.2#./.yarn/patches/tsconfig-paths-npm-3.14.2-90ce75420d.patch", + "tsup@^8.0.1": "patch:tsup@npm%3A8.0.2#./.yarn/patches/tsup-npm-8.0.2-86e40f68a7.patch" }, "devDependencies": { - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/create-release-branch": "^3.0.0", "@metamask/eslint-config": "^12.1.0", @@ -73,13 +77,12 @@ "@metamask/eslint-config-nodejs": "^12.1.0", "@metamask/eslint-config-typescript": "^12.1.0", "@metamask/utils": "^8.3.0", - "@swc/cli": "^0.1.62", "@swc/core": "1.3.78", "@types/jest": "^27.5.1", "@types/node": "18.14.2", "@typescript-eslint/eslint-plugin": "^5.42.1", "@typescript-eslint/parser": "^5.42.1", - "chromedriver": "^121.0.0", + "chromedriver": "^123.0.1", "depcheck": "^1.4.7", "eslint": "^8.27.0", "eslint-config-prettier": "^8.5.0", @@ -101,6 +104,7 @@ "semver": "^7.5.4", "simple-git-hooks": "^2.7.0", "ts-node": "^10.9.1", + "tsup": "^8.0.1", "typescript": "~4.8.4" }, "engines": { @@ -116,7 +120,8 @@ "geckodriver": true, "ts-node>@swc/core": true, "@swc/core": true, - "favicons>sharp": true + "favicons>sharp": true, + "tsup>esbuild": true } }, "packageManager": "yarn@3.6.0" diff --git a/packages/create-snap/.depcheckrc.json b/packages/create-snap/.depcheckrc.json index 15d64e734b..c075cbea8e 100644 --- a/packages/create-snap/.depcheckrc.json +++ b/packages/create-snap/.depcheckrc.json @@ -5,6 +5,7 @@ "@lavamoat/preinstall-always-fail", "@metamask/auto-changelog", "@metamask/eslint-*", + "@metamask/create-snap", "@types/*", "@typescript-eslint/*", "eslint-config-*", diff --git a/packages/create-snap/CHANGELOG.md b/packages/create-snap/CHANGELOG.md index 650355d971..73882d81a1 100644 --- a/packages/create-snap/CHANGELOG.md +++ b/packages/create-snap/CHANGELOG.md @@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [4.0.2] +### Fixed +- Fix detection of minimum Node.js version ([#2292](https://github.com/MetaMask/snaps/pull/2292)) + +## [4.0.1] +### Fixed +- Fix minor build configuration problems ([#2220](https://github.com/MetaMask/snaps/pull/2220)) + +## [4.0.0] +### Changed +- **BREAKING:** Update ESM build to be fully compliant with the ESM standard ([#2210](https://github.com/MetaMask/snaps/pull/2210)) + ## [3.1.1] ### Changed - Bump several MetaMask dependencies ([#1964](https://github.com/MetaMask/snaps/pull/1964)) @@ -51,7 +63,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/create-snap@3.1.1...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/create-snap@4.0.2...HEAD +[4.0.2]: https://github.com/MetaMask/snaps/compare/@metamask/create-snap@4.0.1...@metamask/create-snap@4.0.2 +[4.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/create-snap@4.0.0...@metamask/create-snap@4.0.1 +[4.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/create-snap@3.1.1...@metamask/create-snap@4.0.0 [3.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/create-snap@3.1.0...@metamask/create-snap@3.1.1 [3.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/create-snap@3.0.1...@metamask/create-snap@3.1.0 [3.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/create-snap@3.0.0...@metamask/create-snap@3.0.1 diff --git a/packages/create-snap/package.json b/packages/create-snap/package.json index 1925065c86..45723564d5 100644 --- a/packages/create-snap/package.json +++ b/packages/create-snap/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/create-snap", - "version": "3.1.1", + "version": "4.0.2", "description": "A CLI for creating MetaMask Snaps.", "repository": { "type": "git", @@ -8,23 +8,25 @@ }, "license": "ISC", "sideEffects": false, - "main": "./dist/cjs/index.js", - "module": "./dist/esm/index.js", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/types/index.d.ts" + }, + "./package.json": "./package.json" + }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", "types": "./dist/types/index.d.ts", - "bin": "./dist/cjs/main.js", + "bin": "./dist/main.js", "files": [ - "dist/cjs/**", - "dist/esm/**", - "dist/types/**" + "dist" ], "scripts": { - "build": "yarn build:source && yarn build:types", - "build:source": "yarn build:esm && yarn build:cjs", + "build": "tsup --clean && yarn build:types", "build:types": "tsc --project tsconfig.build.json", - "build:esm": "swc src --out-dir dist/esm --config-file ../../.swcrc.build.json --config module.type=es6", - "build:cjs": "swc src --out-dir dist/cjs --config-file ../../.swcrc.build.json --config module.type=commonjs", - "build:post-tsc": "yarn build:chmod", - "build:chmod": "chmod +x ./dist/esm/main.js && chmod +x ./dist/cjs/main.js", + "build:chmod": "chmod +x ./dist/main.mjs && chmod +x ./dist/main.js", "build:clean": "yarn clean && yarn build", "build:watch": "tsc-watch --onSuccess 'yarn build:chmod'", "clean": "rimraf '*.tsbuildinfo' 'dist'", @@ -40,21 +42,21 @@ "publish:package": "../../scripts/publish-package.sh", "lint:ci": "yarn lint", "publish:preview": "yarn npm publish --tag preview", - "lint:dependencies": "depcheck" + "lint:dependencies": "depcheck", + "build:ci": "tsup --clean" }, "dependencies": { "@metamask/snaps-utils": "workspace:^", - "@metamask/utils": "^8.3.0", + "semver": "^7.5.4", "yargs": "^17.7.1" }, "devDependencies": { - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", "@metamask/eslint-config-nodejs": "^12.1.0", "@metamask/eslint-config-typescript": "^12.1.0", - "@swc/cli": "^0.1.62", "@swc/core": "1.3.78", "@swc/jest": "^0.2.26", "@types/jest": "^27.5.1", @@ -81,6 +83,7 @@ "rimraf": "^4.1.2", "ts-node": "^10.9.1", "tsc-watch": "^4.5.0", + "tsup": "^8.0.1", "typescript": "~4.8.4" }, "engines": { diff --git a/packages/create-snap/src/cmds/init/initHandler.test.ts b/packages/create-snap/src/cmds/init/initHandler.test.ts index 9b6c8f3b47..27262933f0 100644 --- a/packages/create-snap/src/cmds/init/initHandler.test.ts +++ b/packages/create-snap/src/cmds/init/initHandler.test.ts @@ -4,9 +4,9 @@ import { getMockSnapFiles, getMockSnapFilesWithUpdatedChecksum, } from '@metamask/snaps-utils/test-utils'; -import * as utils from '@metamask/utils'; import { promises as fs } from 'fs'; import pathUtils from 'path'; +import semver from 'semver'; import { resetFileSystem } from '../../test-utils'; import type { YargsArgs } from '../../types/yargs'; @@ -15,9 +15,9 @@ import * as initUtils from './initUtils'; jest.mock('fs'); -jest.mock('@metamask/utils', () => ({ - ...jest.requireActual('@metamask/utils'), - satisfiesVersionRange: jest.fn(), +jest.mock('semver', () => ({ + ...jest.requireActual('semver'), + satisfies: jest.fn(), })); jest.mock('@metamask/snaps-utils', () => ({ @@ -47,7 +47,7 @@ describe('initialize', () => { }); it('successfully initializes a Snap project in the current working directory', async () => { - jest.spyOn(utils, 'satisfiesVersionRange').mockImplementation(() => true); + jest.spyOn(semver, 'satisfies').mockImplementation(() => true); jest.spyOn(initUtils, 'isGitInstalled').mockImplementation(() => true); jest.spyOn(initUtils, 'cloneTemplate').mockImplementation(); @@ -82,7 +82,7 @@ describe('initialize', () => { }); it('successfully initializes a Snap project in a given directory', async () => { - jest.spyOn(utils, 'satisfiesVersionRange').mockImplementation(() => true); + jest.spyOn(semver, 'satisfies').mockImplementation(() => true); jest.spyOn(initUtils, 'isGitInstalled').mockImplementation(() => true); @@ -123,7 +123,7 @@ describe('initialize', () => { }); it("defaults to 'src/index.js' if there is no main entry in package.json", async () => { - jest.spyOn(utils, 'satisfiesVersionRange').mockImplementation(() => true); + jest.spyOn(semver, 'satisfies').mockImplementation(() => true); jest.spyOn(initUtils, 'isGitInstalled').mockImplementation(() => true); jest.spyOn(initUtils, 'cloneTemplate').mockImplementation(); @@ -163,7 +163,7 @@ describe('initialize', () => { }); it("doesn't init if it's already in a git repo", async () => { - jest.spyOn(utils, 'satisfiesVersionRange').mockImplementation(() => true); + jest.spyOn(semver, 'satisfies').mockImplementation(() => true); jest.spyOn(initUtils, 'isGitInstalled').mockImplementation(() => true); jest.spyOn(initUtils, 'cloneTemplate').mockImplementation(); @@ -212,12 +212,12 @@ describe('initialize', () => { }; await expect(initHandler({ ...getMockArgv() })).rejects.toThrow( - `Init Error: You are using an outdated version of Node (${process.version}). Please update to Node >=18.6.0.`, + `Init Error: You are using an outdated version of Node (${process.version}). Please update to Node 18.16.0 or later.`, ); }); it('fails if git is not installed', async () => { - jest.spyOn(utils, 'satisfiesVersionRange').mockImplementation(() => true); + jest.spyOn(semver, 'satisfies').mockImplementation(() => true); const isGitInstalledMock = jest .spyOn(initUtils, 'isGitInstalled') @@ -231,7 +231,7 @@ describe('initialize', () => { }); it('fails if it can\t clone template and clean files', async () => { - jest.spyOn(utils, 'satisfiesVersionRange').mockImplementation(() => true); + jest.spyOn(semver, 'satisfies').mockImplementation(() => true); jest.spyOn(initUtils, 'isGitInstalled').mockImplementation(() => true); @@ -249,7 +249,7 @@ describe('initialize', () => { }); it('fails if an error is thrown', async () => { - jest.spyOn(utils, 'satisfiesVersionRange').mockImplementation(() => true); + jest.spyOn(semver, 'satisfies').mockImplementation(() => true); jest.spyOn(initUtils, 'isGitInstalled').mockImplementation(() => true); diff --git a/packages/create-snap/src/cmds/init/initHandler.ts b/packages/create-snap/src/cmds/init/initHandler.ts index 978ff7f256..ed55ffee78 100644 --- a/packages/create-snap/src/cmds/init/initHandler.ts +++ b/packages/create-snap/src/cmds/init/initHandler.ts @@ -1,14 +1,16 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import cliPackageJson from '@metamask/create-snap/package.json'; import type { NpmSnapPackageJson } from '@metamask/snaps-utils'; import { NpmSnapFileNames, readJsonFile, createSnapManifest, logInfo, -} from '@metamask/snaps-utils'; -import type { SemVerRange, SemVerVersion } from '@metamask/utils'; -import { satisfiesVersionRange } from '@metamask/utils'; +} from '@metamask/snaps-utils/node'; import { promises as fs } from 'fs'; import pathUtils from 'path'; +import type { SemVer } from 'semver'; +import semver from 'semver'; import type { YargsArgs } from '../../types/yargs'; import { @@ -22,8 +24,6 @@ import { yarnInstall, } from './initUtils'; -const SATISFIED_VERSION = '>=18.6.0' as SemVerRange; - /** * Creates a new snap package, based on one of the provided templates. This * creates all the necessary files, like `package.json`, `snap.config.js`, etc. @@ -37,14 +37,14 @@ const SATISFIED_VERSION = '>=18.6.0' as SemVerRange; export async function initHandler(argv: YargsArgs) { const { directory } = argv; - const isVersionSupported = satisfiesVersionRange( - process.version as SemVerVersion, - SATISFIED_VERSION, - ); + const versionRange = cliPackageJson.engines.node; + const minimumVersion = (semver.minVersion(versionRange) as SemVer).format(); + + const isVersionSupported = semver.satisfies(process.version, versionRange); if (!isVersionSupported) { throw new Error( - `Init Error: You are using an outdated version of Node (${process.version}). Please update to Node ${SATISFIED_VERSION}.`, + `Init Error: You are using an outdated version of Node (${process.version}). Please update to Node ${minimumVersion} or later.`, ); } diff --git a/packages/create-snap/src/cmds/init/initUtils.ts b/packages/create-snap/src/cmds/init/initUtils.ts index 830e8fdaab..ad744389e2 100644 --- a/packages/create-snap/src/cmds/init/initUtils.ts +++ b/packages/create-snap/src/cmds/init/initUtils.ts @@ -10,7 +10,7 @@ export const SNAP_LOCATION = 'packages/snap/'; /** * Checks if the destination folder exists and if it's empty. Otherwise create it. * - * @param directory - The desination folder. + * @param directory - The destination folder. */ export async function prepareWorkingDirectory( directory: string, diff --git a/packages/create-snap/tsconfig.json b/packages/create-snap/tsconfig.json index 28cf1b8272..85adfc60aa 100644 --- a/packages/create-snap/tsconfig.json +++ b/packages/create-snap/tsconfig.json @@ -3,6 +3,6 @@ "compilerOptions": { "baseUrl": "./" }, - "include": ["./src", "./src/**/*.json"], + "include": ["./src", "./src/**/*.json", "package.json", "tsup.config.ts"], "references": [{ "path": "../snaps-cli" }, { "path": "../snaps-utils" }] } diff --git a/packages/create-snap/tsup.config.ts b/packages/create-snap/tsup.config.ts new file mode 100644 index 0000000000..d62dc9943a --- /dev/null +++ b/packages/create-snap/tsup.config.ts @@ -0,0 +1,16 @@ +import deepmerge from 'deepmerge'; +import type { Options } from 'tsup'; + +import packageJson from './package.json'; + +// `tsup.config.ts` is not under `rootDir`, so we need to use `require` instead. +// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires +const { default: baseConfig } = require('../../tsup.config'); + +const config: Options = { + name: packageJson.name, + external: ['@metamask/create-snap'], + platform: 'node', +}; + +export default deepmerge(baseConfig, config); diff --git a/packages/examples/README.md b/packages/examples/README.md index 143744db16..198594b54d 100644 --- a/packages/examples/README.md +++ b/packages/examples/README.md @@ -42,6 +42,8 @@ The following is a list of the snaps in this directory. - [**`packages/images`**](./packages/images): This snap demonstrates how to render images in a snap. It can generate a QR code from a string, and render it in a dialog, as well as render an image from a URL. + [**`packages/interactive-ui`**](./packages/interactive-ui): This snap demonstrates how to + use interactive UI in a snap. It can display interactive UIs in various APIs available in the Snap. - [**`packages/invoke-snap`**](./packages/invoke-snap): These snaps demonstrate how to use the `snap_invokeSnap` method to invoke another snap. - [**`packages/json-rpc`**](./packages/json-rpc): This snap demonstrates how to diff --git a/packages/examples/package.json b/packages/examples/package.json index 4ba5471a73..6bf35384f0 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/example-snaps", - "version": "3.0.0", + "version": "3.2.2", "private": true, "repository": { "type": "git", @@ -14,7 +14,6 @@ "scripts": { "build": "yarn workspaces foreach --parallel --verbose --no-private run build", "build:clean": "yarn clean && yarn build", - "build:post-tsc": "yarn build", "clean": "yarn workspaces foreach --parallel --verbose --no-private run clean", "start": "yarn workspaces foreach --parallel --verbose --interlaced --no-private --jobs unlimited run start", "start:test": "yarn start", @@ -28,7 +27,7 @@ "lint:dependencies": "depcheck" }, "devDependencies": { - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/bip32/CHANGELOG.md b/packages/examples/packages/bip32/CHANGELOG.md index 23734a652b..dd0954fd69 100644 --- a/packages/examples/packages/bip32/CHANGELOG.md +++ b/packages/examples/packages/bip32/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.1.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1930](https://github.com/MetaMask/snaps/pull/1930), @@ -39,7 +47,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/bip32-example-snap@2.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/bip32-example-snap@2.1.2...HEAD +[2.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/bip32-example-snap@2.1.1...@metamask/bip32-example-snap@2.1.2 +[2.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/bip32-example-snap@2.1.0...@metamask/bip32-example-snap@2.1.1 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/bip32-example-snap@2.0.1...@metamask/bip32-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/bip32-example-snap@2.0.0...@metamask/bip32-example-snap@2.0.1 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/bip32-example-snap@1.0.0...@metamask/bip32-example-snap@2.0.0 diff --git a/packages/examples/packages/bip32/images/icon.svg b/packages/examples/packages/bip32/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/bip32/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/bip32/jest.config.js b/packages/examples/packages/bip32/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/bip32/jest.config.js +++ b/packages/examples/packages/bip32/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/bip32/package.json b/packages/examples/packages/bip32/package.json index 5768150901..24c0240c60 100644 --- a/packages/examples/packages/bip32/package.json +++ b/packages/examples/packages/bip32/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/bip32-example-snap", - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating the use of `snap_getBip32Entropy`.", "repository": { "type": "git", @@ -11,7 +11,6 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "images/icon.svg", "snap.manifest.json" ], "scripts": { @@ -32,7 +31,6 @@ }, "dependencies": { "@metamask/key-tree": "^9.0.0", - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^", "@metamask/utils": "^8.3.0", "@noble/ed25519": "^1.6.0", @@ -40,7 +38,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/bip32/snap.config.ts b/packages/examples/packages/bip32/snap.config.ts index a2da5d2fb8..acef41d515 100644 --- a/packages/examples/packages/bip32/snap.config.ts +++ b/packages/examples/packages/bip32/snap.config.ts @@ -2,17 +2,15 @@ import type { SnapConfig } from '@metamask/snaps-cli'; import { resolve } from 'path'; const config: SnapConfig = { - bundler: 'webpack', input: resolve(__dirname, 'src/index.ts'), server: { port: 8001, }, - polyfills: { - stream: true, - }, stats: { - builtIns: false, buffer: false, + builtIns: { + ignore: ['crypto'], + }, }, }; diff --git a/packages/examples/packages/bip32/snap.manifest.json b/packages/examples/packages/bip32/snap.manifest.json index 4edb1abf15..cf71f8235f 100644 --- a/packages/examples/packages/bip32/snap.manifest.json +++ b/packages/examples/packages/bip32/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating the use of `snap_getBip32Entropy`.", "proposedName": "BIP-32 Example Snap", "repository": { @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "QSFl2gMVYB8vpB/cFlnXCNNi/d9FrYNP4rLOqNY6dMA=", + "shasum": "jkpdIbOorF+RwjMTHoNaiJxQt4E44b/jSVE+gAvWe0k=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/bip32/src/index.ts b/packages/examples/packages/bip32/src/index.ts index 0a13c0a730..5d22328f64 100644 --- a/packages/examples/packages/bip32/src/index.ts +++ b/packages/examples/packages/bip32/src/index.ts @@ -1,4 +1,3 @@ -import { providerErrors, rpcErrors } from '@metamask/rpc-errors'; import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; import { DialogType, @@ -6,6 +5,9 @@ import { text, heading, copyable, + InvalidParamsError, + UserRejectedRequestError, + MethodNotFoundError, } from '@metamask/snaps-sdk'; import { add0x, @@ -46,9 +48,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { const { message, curve, ...params } = request.params as SignMessageParams; if (!message || typeof message !== 'string') { - throw rpcErrors.invalidParams({ - message: `Invalid signature data: "${message}".`, - }); + throw new InvalidParamsError(`Invalid signature data: "${message}".`); } const node = await getPrivateNode({ ...params, curve }); @@ -71,7 +71,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { }); if (!approved) { - throw providerErrors.userRejectedRequest(); + throw new UserRejectedRequestError(); } if (curve === 'ed25519') { @@ -95,10 +95,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { throw new Error(`Unsupported curve: ${String(curve)}.`); } - default: { - throw rpcErrors.methodNotFound({ - data: { method: request.method }, - }); - } + default: + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/bip44/CHANGELOG.md b/packages/examples/packages/bip44/CHANGELOG.md index b8fef572a6..3b77680764 100644 --- a/packages/examples/packages/bip44/CHANGELOG.md +++ b/packages/examples/packages/bip44/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.1.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1930](https://github.com/MetaMask/snaps/pull/1930), @@ -44,7 +52,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/bip44-example-snap@2.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/bip44-example-snap@2.1.2...HEAD +[2.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/bip44-example-snap@2.1.1...@metamask/bip44-example-snap@2.1.2 +[2.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/bip44-example-snap@2.1.0...@metamask/bip44-example-snap@2.1.1 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/bip44-example-snap@2.0.1...@metamask/bip44-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/bip44-example-snap@2.0.0...@metamask/bip44-example-snap@2.0.1 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/bip44-example-snap@1.0.0...@metamask/bip44-example-snap@2.0.0 diff --git a/packages/examples/packages/bip44/images/icon.svg b/packages/examples/packages/bip44/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/bip44/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/bip44/jest.config.js b/packages/examples/packages/bip44/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/bip44/jest.config.js +++ b/packages/examples/packages/bip44/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/bip44/package.json b/packages/examples/packages/bip44/package.json index ca51f816d7..85f8ec8779 100644 --- a/packages/examples/packages/bip44/package.json +++ b/packages/examples/packages/bip44/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/bip44-example-snap", - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating the use of `snap_getBip44Entropy`.", "repository": { "type": "git", @@ -11,7 +11,6 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "images/icon.svg", "snap.manifest.json" ], "scripts": { @@ -32,14 +31,13 @@ }, "dependencies": { "@metamask/key-tree": "^9.0.0", - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^", "@metamask/utils": "^8.3.0", "@noble/bls12-381": "^1.2.0" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/bip44/snap.config.ts b/packages/examples/packages/bip44/snap.config.ts index db5d1978d1..147b9274a5 100644 --- a/packages/examples/packages/bip44/snap.config.ts +++ b/packages/examples/packages/bip44/snap.config.ts @@ -2,14 +2,15 @@ import type { SnapConfig } from '@metamask/snaps-cli'; import { resolve } from 'path'; const config: SnapConfig = { - bundler: 'webpack', input: resolve(__dirname, 'src/index.ts'), server: { port: 8002, }, - polyfills: { - buffer: true, - stream: true, + stats: { + buffer: false, + builtIns: { + ignore: ['crypto'], + }, }, }; diff --git a/packages/examples/packages/bip44/snap.manifest.json b/packages/examples/packages/bip44/snap.manifest.json index 9570bc6015..4a1c0569e0 100644 --- a/packages/examples/packages/bip44/snap.manifest.json +++ b/packages/examples/packages/bip44/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating the use of `snap_getBip44Entropy`.", "proposedName": "BIP-44 Example Snap", "repository": { @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "YJ3L2U/8vVAxhVma0Up4vCHRHCFWmJbVtZTgLrwUsEQ=", + "shasum": "juam47X5pZt5pn8CZFDT6Ij6ZKn0Tm82R0km7fS4+KU=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/bip44/src/index.ts b/packages/examples/packages/bip44/src/index.ts index bac330dcac..4025ea5155 100644 --- a/packages/examples/packages/bip44/src/index.ts +++ b/packages/examples/packages/bip44/src/index.ts @@ -1,4 +1,3 @@ -import { rpcErrors, providerErrors } from '@metamask/rpc-errors'; import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; import { DialogType, @@ -6,6 +5,8 @@ import { text, heading, copyable, + MethodNotFoundError, + UserRejectedRequestError, } from '@metamask/snaps-sdk'; import { bytesToHex, stringToBytes } from '@metamask/utils'; import { getPublicKey, sign } from '@noble/bls12-381'; @@ -58,7 +59,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { }); if (!approved) { - throw providerErrors.userRejectedRequest(); + throw new UserRejectedRequestError(); } const newLocal = await sign(stringToBytes(message), privateKey); @@ -66,8 +67,6 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { } default: - throw rpcErrors.methodNotFound({ - data: { method: request.method }, - }); + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/browserify-plugin/CHANGELOG.md b/packages/examples/packages/browserify-plugin/CHANGELOG.md index 6d9e7ce460..f932696cf4 100644 --- a/packages/examples/packages/browserify-plugin/CHANGELOG.md +++ b/packages/examples/packages/browserify-plugin/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.1.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1946](https://github.com/MetaMask/snaps/pull/1946)) @@ -34,7 +42,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/browserify-plugin-example-snap@2.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/browserify-plugin-example-snap@2.1.2...HEAD +[2.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/browserify-plugin-example-snap@2.1.1...@metamask/browserify-plugin-example-snap@2.1.2 +[2.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/browserify-plugin-example-snap@2.1.0...@metamask/browserify-plugin-example-snap@2.1.1 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/browserify-plugin-example-snap@2.0.1...@metamask/browserify-plugin-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/browserify-plugin-example-snap@2.0.0...@metamask/browserify-plugin-example-snap@2.0.1 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/browserify-plugin-example-snap@1.0.0...@metamask/browserify-plugin-example-snap@2.0.0 diff --git a/packages/examples/packages/browserify-plugin/images/icon.svg b/packages/examples/packages/browserify-plugin/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/browserify-plugin/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/browserify-plugin/jest.config.js b/packages/examples/packages/browserify-plugin/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/browserify-plugin/jest.config.js +++ b/packages/examples/packages/browserify-plugin/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/browserify-plugin/package.json b/packages/examples/packages/browserify-plugin/package.json index 911dc77b4b..b41bfd6062 100644 --- a/packages/examples/packages/browserify-plugin/package.json +++ b/packages/examples/packages/browserify-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/browserify-plugin-example-snap", - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating how to use the Browserify plugin to bundle a snap.", "repository": { "type": "git", @@ -11,7 +11,6 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "images/icon.svg", "snap.manifest.json" ], "scripts": { @@ -30,12 +29,11 @@ "lint:dependencies": "depcheck" }, "dependencies": { - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/browserify-plugin/snap.manifest.json b/packages/examples/packages/browserify-plugin/snap.manifest.json index ec2a46b280..8e2bd1ab87 100644 --- a/packages/examples/packages/browserify-plugin/snap.manifest.json +++ b/packages/examples/packages/browserify-plugin/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating how to use the Browserify plugin to bundle a snap.", "proposedName": "Browserify Plugin Example Snap", "repository": { @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "P+P4MtFvjt9ZBLtQlojhpFLBQx1KR78jJkWsdDft3eg=", + "shasum": "3WGWYaz2orItvBB3IaxTOKBdYUAxyXIj6rw0HWobxs4=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/browserify-plugin/src/index.ts b/packages/examples/packages/browserify-plugin/src/index.ts index 660d49a6d2..9df9df4067 100644 --- a/packages/examples/packages/browserify-plugin/src/index.ts +++ b/packages/examples/packages/browserify-plugin/src/index.ts @@ -1,5 +1,7 @@ -import { rpcErrors } from '@metamask/rpc-errors'; -import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; +import { + MethodNotFoundError, + type OnRpcRequestHandler, +} from '@metamask/snaps-sdk'; /** * Handle incoming JSON-RPC requests from the dapp, sent through the @@ -21,9 +23,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { return 'Hello from Browserify!'; default: { - throw rpcErrors.methodNotFound({ - data: { method: request.method }, - }); + throw new MethodNotFoundError({ method: request.method }); } } }; diff --git a/packages/examples/packages/browserify/CHANGELOG.md b/packages/examples/packages/browserify/CHANGELOG.md index 01f4e9dffc..bc723aceb6 100644 --- a/packages/examples/packages/browserify/CHANGELOG.md +++ b/packages/examples/packages/browserify/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.1.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1946](https://github.com/MetaMask/snaps/pull/1946)) @@ -33,7 +41,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add Browserify example snap ([#1632](https://github.com/MetaMask/snaps/pull/1632)) - This snap demonstrates how to use the deprecated Browserify configuration format. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/browserify-example-snap@2.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/browserify-example-snap@2.1.2...HEAD +[2.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/browserify-example-snap@2.1.1...@metamask/browserify-example-snap@2.1.2 +[2.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/browserify-example-snap@2.1.0...@metamask/browserify-example-snap@2.1.1 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/browserify-example-snap@2.0.1...@metamask/browserify-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/browserify-example-snap@2.0.0...@metamask/browserify-example-snap@2.0.1 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/browserify-example-snap@1.0.0...@metamask/browserify-example-snap@2.0.0 diff --git a/packages/examples/packages/browserify/images/icon.svg b/packages/examples/packages/browserify/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/browserify/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/browserify/jest.config.js b/packages/examples/packages/browserify/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/browserify/jest.config.js +++ b/packages/examples/packages/browserify/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/browserify/package.json b/packages/examples/packages/browserify/package.json index 616970d4b9..88db2a3df9 100644 --- a/packages/examples/packages/browserify/package.json +++ b/packages/examples/packages/browserify/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/browserify-example-snap", - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating how to use Browserify to bundle a snap.", "repository": { "type": "git", @@ -11,7 +11,6 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "images/icon.svg", "snap.manifest.json" ], "scripts": { @@ -31,12 +30,11 @@ "lint:dependencies": "depcheck" }, "dependencies": { - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/browserify/snap.config.ts b/packages/examples/packages/browserify/snap.config.ts index 7addbe4a95..b821ddbff0 100644 --- a/packages/examples/packages/browserify/snap.config.ts +++ b/packages/examples/packages/browserify/snap.config.ts @@ -2,6 +2,7 @@ import type { SnapConfig } from '@metamask/snaps-cli'; import { resolve } from 'path'; const config: SnapConfig = { + bundler: 'browserify', cliOptions: { src: resolve(__dirname, 'src/index.ts'), port: 8021, diff --git a/packages/examples/packages/browserify/snap.manifest.json b/packages/examples/packages/browserify/snap.manifest.json index c63386af0a..585852f222 100644 --- a/packages/examples/packages/browserify/snap.manifest.json +++ b/packages/examples/packages/browserify/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating how to use the Browserify plugin to bundle a snap.", "proposedName": "Browserify Plugin Example Snap", "repository": { @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "3lhWUOj8f2t2WRLwzzD3dJyDz0DNT6+UeSBM5vMIUI0=", + "shasum": "7ac+04dajWBtACaIvkhlyASHvoXXNauqOxp7R56FkYk=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/browserify/src/index.ts b/packages/examples/packages/browserify/src/index.ts index de8c3482d9..92f1bbee41 100644 --- a/packages/examples/packages/browserify/src/index.ts +++ b/packages/examples/packages/browserify/src/index.ts @@ -1,5 +1,7 @@ -import { rpcErrors } from '@metamask/rpc-errors'; -import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; +import { + MethodNotFoundError, + type OnRpcRequestHandler, +} from '@metamask/snaps-sdk'; /** * Handle incoming JSON-RPC requests from the dapp, sent through the @@ -23,9 +25,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { return 'Hello from the MetaMask Snaps CLI using Browserify!'; default: { - throw rpcErrors.methodNotFound({ - data: { method: request.method }, - }); + throw new MethodNotFoundError({ method: request.method }); } } }; diff --git a/packages/examples/packages/browserify/tsconfig.json b/packages/examples/packages/browserify/tsconfig.json index b3efd0ff58..4a5db591e3 100644 --- a/packages/examples/packages/browserify/tsconfig.json +++ b/packages/examples/packages/browserify/tsconfig.json @@ -4,6 +4,7 @@ "resolveJsonModule": true, "baseUrl": "./", "paths": { + "@metamask/*/node": ["../../../*/src/node"], "@metamask/*": ["../../../*/src"] } }, diff --git a/packages/examples/packages/client-status/CHANGELOG.md b/packages/examples/packages/client-status/CHANGELOG.md index 72dc89c06c..419b51103f 100644 --- a/packages/examples/packages/client-status/CHANGELOG.md +++ b/packages/examples/packages/client-status/CHANGELOG.md @@ -6,9 +6,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.0.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [1.0.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [1.0.0] ### Added - Add `snap_getClientStatus` example snap ([#2159](https://github.com/MetaMask/snaps/pull/2159)) -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/client-status-example-snap@1.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/client-status-example-snap@1.0.2...HEAD +[1.0.2]: https://github.com/MetaMask/snaps/compare/@metamask/client-status-example-snap@1.0.1...@metamask/client-status-example-snap@1.0.2 +[1.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/client-status-example-snap@1.0.0...@metamask/client-status-example-snap@1.0.1 [1.0.0]: https://github.com/MetaMask/snaps/releases/tag/@metamask/client-status-example-snap@1.0.0 diff --git a/packages/examples/packages/client-status/images/icon.svg b/packages/examples/packages/client-status/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/client-status/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/client-status/jest.config.js b/packages/examples/packages/client-status/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/client-status/jest.config.js +++ b/packages/examples/packages/client-status/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/client-status/package.json b/packages/examples/packages/client-status/package.json index 26af5e3cd9..ac6517a3b5 100644 --- a/packages/examples/packages/client-status/package.json +++ b/packages/examples/packages/client-status/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/client-status-example-snap", - "version": "1.0.0", + "version": "1.0.2", "description": "MetaMask example snap demonstrating the use of `snap_getClientStatus`.", "repository": { "type": "git", @@ -11,8 +11,7 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "snap.manifest.json", - "images/icon.svg" + "snap.manifest.json" ], "scripts": { "build": "mm-snap build", @@ -31,12 +30,11 @@ "lint:dependencies": "depcheck" }, "dependencies": { - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/client-status/snap.config.ts b/packages/examples/packages/client-status/snap.config.ts index 23b5ee31cb..f9be041679 100644 --- a/packages/examples/packages/client-status/snap.config.ts +++ b/packages/examples/packages/client-status/snap.config.ts @@ -2,7 +2,6 @@ import type { SnapConfig } from '@metamask/snaps-cli'; import { resolve } from 'path'; const config: SnapConfig = { - bundler: 'webpack', input: resolve(__dirname, 'src/index.ts'), server: { port: 8027, diff --git a/packages/examples/packages/client-status/snap.manifest.json b/packages/examples/packages/client-status/snap.manifest.json index fd6ded6bb1..76301ef1c9 100644 --- a/packages/examples/packages/client-status/snap.manifest.json +++ b/packages/examples/packages/client-status/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "1.0.0", + "version": "1.0.2", "description": "MetaMask example snap demonstrating the use of `snap_getClientStatus`", "proposedName": "Client Status Example Snap", "repository": { @@ -7,11 +7,10 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "CbetiYXt1neGeDoKP1mkh02XqGA5wT9IRR2+Zlu0JC8=", + "shasum": "FDGAlp5hKbt1dkKFjgSV0RPl3vpHY6hz0s+hNHgGK/w=", "location": { "npm": { "filePath": "dist/bundle.js", - "iconPath": "images/icon.svg", "packageName": "@metamask/client-status-example-snap", "registry": "https://registry.npmjs.org/" } diff --git a/packages/examples/packages/client-status/src/index.ts b/packages/examples/packages/client-status/src/index.ts index ba26986cc3..7cfead5220 100644 --- a/packages/examples/packages/client-status/src/index.ts +++ b/packages/examples/packages/client-status/src/index.ts @@ -1,5 +1,7 @@ -import { rpcErrors } from '@metamask/rpc-errors'; -import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; +import { + MethodNotFoundError, + type OnRpcRequestHandler, +} from '@metamask/snaps-sdk'; /** * Handle incoming JSON-RPC requests from the dapp, sent through the @@ -21,10 +23,6 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { } default: - throw rpcErrors.methodNotFound({ - data: { - method: request.method, - }, - }); + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/cronjobs/CHANGELOG.md b/packages/examples/packages/cronjobs/CHANGELOG.md index c78fde45c8..ca52c8c0f5 100644 --- a/packages/examples/packages/cronjobs/CHANGELOG.md +++ b/packages/examples/packages/cronjobs/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.3] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.1.2] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.1] ### Changed - Change cronjob interval to run more often ([#2164](https://github.com/MetaMask/snaps/pull/2164)) @@ -43,7 +51,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/cronjob-example-snap@2.1.1...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/cronjob-example-snap@2.1.3...HEAD +[2.1.3]: https://github.com/MetaMask/snaps/compare/@metamask/cronjob-example-snap@2.1.2...@metamask/cronjob-example-snap@2.1.3 +[2.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/cronjob-example-snap@2.1.1...@metamask/cronjob-example-snap@2.1.2 [2.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/cronjob-example-snap@2.1.0...@metamask/cronjob-example-snap@2.1.1 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/cronjob-example-snap@2.0.1...@metamask/cronjob-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/cronjob-example-snap@2.0.0...@metamask/cronjob-example-snap@2.0.1 diff --git a/packages/examples/packages/cronjobs/images/icon.svg b/packages/examples/packages/cronjobs/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/cronjobs/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/cronjobs/jest.config.js b/packages/examples/packages/cronjobs/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/cronjobs/jest.config.js +++ b/packages/examples/packages/cronjobs/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/cronjobs/package.json b/packages/examples/packages/cronjobs/package.json index 9604d24dd0..9f5b9621c3 100644 --- a/packages/examples/packages/cronjobs/package.json +++ b/packages/examples/packages/cronjobs/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/cronjob-example-snap", - "version": "2.1.1", + "version": "2.1.3", "description": "MetaMask example snap demonstrating the use of cronjobs in snaps.", "repository": { "type": "git", @@ -11,8 +11,7 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "snap.manifest.json", - "images/icon.svg" + "snap.manifest.json" ], "scripts": { "build": "mm-snap build", @@ -31,12 +30,11 @@ "lint:dependencies": "depcheck" }, "dependencies": { - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/cronjobs/snap.config.ts b/packages/examples/packages/cronjobs/snap.config.ts index 851d7c8a67..a6dd13cf54 100644 --- a/packages/examples/packages/cronjobs/snap.config.ts +++ b/packages/examples/packages/cronjobs/snap.config.ts @@ -2,7 +2,6 @@ import type { SnapConfig } from '@metamask/snaps-cli'; import { resolve } from 'path'; const config: SnapConfig = { - bundler: 'webpack', input: resolve(__dirname, 'src/index.ts'), server: { port: 8004, diff --git a/packages/examples/packages/cronjobs/snap.manifest.json b/packages/examples/packages/cronjobs/snap.manifest.json index 7890e3213e..9054a3bd5e 100644 --- a/packages/examples/packages/cronjobs/snap.manifest.json +++ b/packages/examples/packages/cronjobs/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.1", + "version": "2.1.3", "description": "MetaMask example snap demonstrating the use of cronjobs in snaps.", "proposedName": "Cronjob Example Snap", "repository": { @@ -7,11 +7,10 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "Lkw2STqcJn6zw3u7uB7QTBjVc539gv/RFdNL6DqAE0Q=", + "shasum": "hO0YsQrTcKU6YAuxwBOEPNIssWs3f6GvecSCPWOu0ds=", "location": { "npm": { "filePath": "dist/bundle.js", - "iconPath": "images/icon.svg", "packageName": "@metamask/cronjob-example-snap", "registry": "https://registry.npmjs.org/" } diff --git a/packages/examples/packages/cronjobs/src/index.ts b/packages/examples/packages/cronjobs/src/index.ts index eacfe3ca63..6bb7d7b738 100644 --- a/packages/examples/packages/cronjobs/src/index.ts +++ b/packages/examples/packages/cronjobs/src/index.ts @@ -1,6 +1,5 @@ -import { rpcErrors } from '@metamask/rpc-errors'; import type { OnCronjobHandler } from '@metamask/snaps-sdk'; -import { panel, text, heading } from '@metamask/snaps-sdk'; +import { panel, text, heading, MethodNotFoundError } from '@metamask/snaps-sdk'; /** * Handle cronjob execution requests from MetaMask. This handler handles one @@ -31,10 +30,6 @@ export const onCronjob: OnCronjobHandler = async ({ request }) => { }, }); default: - throw rpcErrors.methodNotFound({ - data: { - method: request.method, - }, - }); + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/dialogs/CHANGELOG.md b/packages/examples/packages/dialogs/CHANGELOG.md index 8d16380fe1..8d53df319d 100644 --- a/packages/examples/packages/dialogs/CHANGELOG.md +++ b/packages/examples/packages/dialogs/CHANGELOG.md @@ -6,6 +6,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.2.1] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.2.0] +### Added +- Add a link to confirmation dialog ([#2112](https://github.com/MetaMask/snaps/pull/2112)) + +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1930](https://github.com/MetaMask/snaps/pull/1930), @@ -44,7 +55,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/dialog-example-snap@2.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/dialog-example-snap@2.2.1...HEAD +[2.2.1]: https://github.com/MetaMask/snaps/compare/@metamask/dialog-example-snap@2.2.0...@metamask/dialog-example-snap@2.2.1 +[2.2.0]: https://github.com/MetaMask/snaps/compare/@metamask/dialog-example-snap@2.1.0...@metamask/dialog-example-snap@2.2.0 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/dialog-example-snap@2.0.1...@metamask/dialog-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/dialog-example-snap@2.0.0...@metamask/dialog-example-snap@2.0.1 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/dialog-example-snap@1.0.0...@metamask/dialog-example-snap@2.0.0 diff --git a/packages/examples/packages/dialogs/images/icon.svg b/packages/examples/packages/dialogs/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/dialogs/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/dialogs/jest.config.js b/packages/examples/packages/dialogs/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/dialogs/jest.config.js +++ b/packages/examples/packages/dialogs/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/dialogs/package.json b/packages/examples/packages/dialogs/package.json index 754cc2504e..1b5de7d0f7 100644 --- a/packages/examples/packages/dialogs/package.json +++ b/packages/examples/packages/dialogs/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/dialog-example-snap", - "version": "2.1.0", + "version": "2.2.1", "description": "MetaMask example snap demonstrating the use of `snap_dialog`.", "repository": { "type": "git", @@ -11,8 +11,7 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "snap.manifest.json", - "images/icon.svg" + "snap.manifest.json" ], "scripts": { "build": "mm-snap build", @@ -31,12 +30,11 @@ "lint:dependencies": "depcheck" }, "dependencies": { - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/dialogs/snap.config.ts b/packages/examples/packages/dialogs/snap.config.ts index 565d931476..666bf6eb00 100644 --- a/packages/examples/packages/dialogs/snap.config.ts +++ b/packages/examples/packages/dialogs/snap.config.ts @@ -2,13 +2,12 @@ import type { SnapConfig } from '@metamask/snaps-cli'; import { resolve } from 'path'; const config: SnapConfig = { - bundler: 'webpack', input: resolve(__dirname, 'src/index.ts'), server: { port: 8005, }, - polyfills: { - stream: true, + stats: { + buffer: false, }, }; diff --git a/packages/examples/packages/dialogs/snap.manifest.json b/packages/examples/packages/dialogs/snap.manifest.json index f7967e913a..abb36d20d7 100644 --- a/packages/examples/packages/dialogs/snap.manifest.json +++ b/packages/examples/packages/dialogs/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.0", + "version": "2.2.1", "description": "MetaMask example snap demonstrating the use of `snap_dialog`.", "proposedName": "Dialog Example Snap", "repository": { @@ -7,11 +7,10 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "6C/WHL3tog2KCybrsAn8kKs6ff/jpGs98YqiSRmyIq0=", + "shasum": "miP/5TlkFeW4Slz/t2jN4XMC1+VxI1uupHg86IEHUmY=", "location": { "npm": { "filePath": "dist/bundle.js", - "iconPath": "images/icon.svg", "packageName": "@metamask/dialog-example-snap", "registry": "https://registry.npmjs.org/" } diff --git a/packages/examples/packages/dialogs/src/index.ts b/packages/examples/packages/dialogs/src/index.ts index 7e7428a8c1..b367d39a5e 100644 --- a/packages/examples/packages/dialogs/src/index.ts +++ b/packages/examples/packages/dialogs/src/index.ts @@ -1,6 +1,11 @@ -import { rpcErrors } from '@metamask/rpc-errors'; import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; -import { DialogType, panel, text, heading } from '@metamask/snaps-sdk'; +import { + DialogType, + panel, + text, + heading, + MethodNotFoundError, +} from '@metamask/snaps-sdk'; /** * Handle incoming JSON-RPC requests from the dapp, sent through the @@ -71,10 +76,6 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { }); default: - throw rpcErrors.methodNotFound({ - data: { - method: request.method, - }, - }); + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/errors/CHANGELOG.md b/packages/examples/packages/errors/CHANGELOG.md index 6d1dd2de84..6fd06c78ef 100644 --- a/packages/examples/packages/errors/CHANGELOG.md +++ b/packages/examples/packages/errors/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.1.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1946](https://github.com/MetaMask/snaps/pull/1946)) @@ -42,7 +50,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/error-example-snap@2.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/error-example-snap@2.1.2...HEAD +[2.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/error-example-snap@2.1.1...@metamask/error-example-snap@2.1.2 +[2.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/error-example-snap@2.1.0...@metamask/error-example-snap@2.1.1 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/error-example-snap@2.0.1...@metamask/error-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/error-example-snap@2.0.0...@metamask/error-example-snap@2.0.1 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/error-example-snap@1.0.0...@metamask/error-example-snap@2.0.0 diff --git a/packages/examples/packages/errors/images/icon.svg b/packages/examples/packages/errors/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/errors/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/errors/jest.config.js b/packages/examples/packages/errors/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/errors/jest.config.js +++ b/packages/examples/packages/errors/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/errors/package.json b/packages/examples/packages/errors/package.json index 9719e84562..6216a516be 100644 --- a/packages/examples/packages/errors/package.json +++ b/packages/examples/packages/errors/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/error-example-snap", - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating the use of error handling in snaps.", "repository": { "type": "git", @@ -11,8 +11,7 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "snap.manifest.json", - "images/icon.svg" + "snap.manifest.json" ], "scripts": { "build": "mm-snap build", @@ -35,7 +34,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/errors/snap.config.ts b/packages/examples/packages/errors/snap.config.ts index 2f1fc839b4..4eebaa1d0e 100644 --- a/packages/examples/packages/errors/snap.config.ts +++ b/packages/examples/packages/errors/snap.config.ts @@ -2,7 +2,6 @@ import type { SnapConfig } from '@metamask/snaps-cli'; import { resolve } from 'path'; const config: SnapConfig = { - bundler: 'webpack', input: resolve(__dirname, 'src/index.ts'), server: { port: 8006, diff --git a/packages/examples/packages/errors/snap.manifest.json b/packages/examples/packages/errors/snap.manifest.json index aadfa42262..2054eacb13 100644 --- a/packages/examples/packages/errors/snap.manifest.json +++ b/packages/examples/packages/errors/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating the use of error handling in snaps.", "proposedName": "Error Example Snap", "repository": { @@ -7,11 +7,10 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "yHYVhtnHm6SonjnHmOYISrwKrySimS7p+jOISJsIi90=", + "shasum": "6sJsnsPeGr45rdXrBtKhdNhbqBJhatSFzMWHP8gcWQ0=", "location": { "npm": { "filePath": "dist/bundle.js", - "iconPath": "images/icon.svg", "packageName": "@metamask/error-example-snap", "registry": "https://registry.npmjs.org/" } diff --git a/packages/examples/packages/ethereum-provider/CHANGELOG.md b/packages/examples/packages/ethereum-provider/CHANGELOG.md index 7654413751..72c1664020 100644 --- a/packages/examples/packages/ethereum-provider/CHANGELOG.md +++ b/packages/examples/packages/ethereum-provider/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.1.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1946](https://github.com/MetaMask/snaps/pull/1946)) @@ -42,7 +50,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/ethereum-provider-example-snap@2.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/ethereum-provider-example-snap@2.1.2...HEAD +[2.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/ethereum-provider-example-snap@2.1.1...@metamask/ethereum-provider-example-snap@2.1.2 +[2.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/ethereum-provider-example-snap@2.1.0...@metamask/ethereum-provider-example-snap@2.1.1 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/ethereum-provider-example-snap@2.0.1...@metamask/ethereum-provider-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/ethereum-provider-example-snap@2.0.0...@metamask/ethereum-provider-example-snap@2.0.1 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/ethereum-provider-example-snap@1.0.0...@metamask/ethereum-provider-example-snap@2.0.0 diff --git a/packages/examples/packages/ethereum-provider/images/icon.svg b/packages/examples/packages/ethereum-provider/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/ethereum-provider/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/ethereum-provider/jest.config.js b/packages/examples/packages/ethereum-provider/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/ethereum-provider/jest.config.js +++ b/packages/examples/packages/ethereum-provider/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/ethereum-provider/package.json b/packages/examples/packages/ethereum-provider/package.json index 0586d99cd3..25dc01b524 100644 --- a/packages/examples/packages/ethereum-provider/package.json +++ b/packages/examples/packages/ethereum-provider/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/ethereum-provider-example-snap", - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating the use of the Ethereum Provider API and `endowment:ethereum-provider` permission.", "repository": { "type": "git", @@ -11,7 +11,6 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "images/icon.svg", "snap.manifest.json" ], "scripts": { @@ -31,13 +30,12 @@ "lint:dependencies": "depcheck" }, "dependencies": { - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^", "@metamask/utils": "^8.3.0" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/ethereum-provider/snap.config.ts b/packages/examples/packages/ethereum-provider/snap.config.ts index 0101431cb2..9115c8d924 100644 --- a/packages/examples/packages/ethereum-provider/snap.config.ts +++ b/packages/examples/packages/ethereum-provider/snap.config.ts @@ -2,11 +2,13 @@ import type { SnapConfig } from '@metamask/snaps-cli'; import { resolve } from 'path'; const config: SnapConfig = { - bundler: 'webpack', input: resolve(__dirname, 'src/index.ts'), server: { port: 8007, }, + stats: { + buffer: false, + }, }; export default config; diff --git a/packages/examples/packages/ethereum-provider/snap.manifest.json b/packages/examples/packages/ethereum-provider/snap.manifest.json index 5a305b94d2..5f00fedc2d 100644 --- a/packages/examples/packages/ethereum-provider/snap.manifest.json +++ b/packages/examples/packages/ethereum-provider/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating the use of the Ethereum Provider API and `endowment:ethereum-provider` permission.", "proposedName": "Ethereum Provider Example Snap", "repository": { @@ -7,11 +7,10 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "iPSKLAHha7slKXycULWDEFukdnsUN44svh54UwN+PfI=", + "shasum": "O6/rl2sgxCPD1IiFbM+VL10bXthiFp3nJuMyAD3aXwQ=", "location": { "npm": { "filePath": "dist/bundle.js", - "iconPath": "images/icon.svg", "packageName": "@metamask/ethereum-provider-example-snap", "registry": "https://registry.npmjs.org/" } diff --git a/packages/examples/packages/ethereum-provider/src/index.ts b/packages/examples/packages/ethereum-provider/src/index.ts index ef28a7aefa..50437699d7 100644 --- a/packages/examples/packages/ethereum-provider/src/index.ts +++ b/packages/examples/packages/ethereum-provider/src/index.ts @@ -1,5 +1,7 @@ -import { rpcErrors } from '@metamask/rpc-errors'; -import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; +import { + MethodNotFoundError, + type OnRpcRequestHandler, +} from '@metamask/snaps-sdk'; import type { Hex } from '@metamask/utils'; import { assert, stringToBytes, bytesToHex } from '@metamask/utils'; @@ -121,10 +123,6 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { } default: - throw rpcErrors.methodNotFound({ - data: { - method: request.method, - }, - }); + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/ethers-js/CHANGELOG.md b/packages/examples/packages/ethers-js/CHANGELOG.md index a2aec2ea01..057b484d41 100644 --- a/packages/examples/packages/ethers-js/CHANGELOG.md +++ b/packages/examples/packages/ethers-js/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.1.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1946](https://github.com/MetaMask/snaps/pull/1946), [#1954](https://github.com/MetaMask/snaps/pull/1954)) @@ -42,7 +50,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/ethers-js-example-snap@2.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/ethers-js-example-snap@2.1.2...HEAD +[2.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/ethers-js-example-snap@2.1.1...@metamask/ethers-js-example-snap@2.1.2 +[2.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/ethers-js-example-snap@2.1.0...@metamask/ethers-js-example-snap@2.1.1 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/ethers-js-example-snap@2.0.1...@metamask/ethers-js-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/ethers-js-example-snap@2.0.0...@metamask/ethers-js-example-snap@2.0.1 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/ethers-js-example-snap@1.0.0...@metamask/ethers-js-example-snap@2.0.0 diff --git a/packages/examples/packages/ethers-js/images/icon.svg b/packages/examples/packages/ethers-js/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/ethers-js/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/ethers-js/jest.config.js b/packages/examples/packages/ethers-js/jest.config.js index 90f355afcf..63fa626851 100644 --- a/packages/examples/packages/ethers-js/jest.config.js +++ b/packages/examples/packages/ethers-js/jest.config.js @@ -22,6 +22,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/ethers-js/package.json b/packages/examples/packages/ethers-js/package.json index 6372fdd99c..fe00484806 100644 --- a/packages/examples/packages/ethers-js/package.json +++ b/packages/examples/packages/ethers-js/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/ethers-js-example-snap", - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating how to use Ethers.js inside a snap.", "repository": { "type": "git", @@ -11,7 +11,6 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "images/icon.svg", "snap.manifest.json" ], "scripts": { @@ -31,13 +30,12 @@ "lint:dependencies": "depcheck" }, "dependencies": { - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^", "ethers": "^6.3.0" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/ethers-js/snap.config.ts b/packages/examples/packages/ethers-js/snap.config.ts index 3f8a69666f..81b6c1d121 100644 --- a/packages/examples/packages/ethers-js/snap.config.ts +++ b/packages/examples/packages/ethers-js/snap.config.ts @@ -4,15 +4,15 @@ import { resolve } from 'path'; import { DefinePlugin } from 'webpack'; const config: SnapConfig = { - bundler: 'webpack', input: resolve(__dirname, 'src/index.ts'), server: { port: 8008, }, - polyfills: { - crypto: true, - stream: true, - buffer: true, + stats: { + buffer: false, + builtIns: { + ignore: ['crypto'], + }, }, customizeWebpackConfig: (defaultConfig) => merge(defaultConfig, { diff --git a/packages/examples/packages/ethers-js/snap.manifest.json b/packages/examples/packages/ethers-js/snap.manifest.json index 9c70e8559c..3e6345311d 100644 --- a/packages/examples/packages/ethers-js/snap.manifest.json +++ b/packages/examples/packages/ethers-js/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating how to use Ethers.js inside a snap.", "proposedName": "Ethers.js Example Snap", "repository": { @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "WzJz80aWrAaRVDxBO3QuijwxlNfSBXaCsuSX2NzYw3U=", + "shasum": "UND77v81lE7TMxWXZvzD6wsD7jCRsFFhYyjePnBwBbs=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/ethers-js/src/index.ts b/packages/examples/packages/ethers-js/src/index.ts index 7ca645bb5f..30cab5bce8 100644 --- a/packages/examples/packages/ethers-js/src/index.ts +++ b/packages/examples/packages/ethers-js/src/index.ts @@ -1,6 +1,12 @@ -import { providerErrors, rpcErrors } from '@metamask/rpc-errors'; import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; -import { panel, heading, copyable, text } from '@metamask/snaps-sdk'; +import { + panel, + heading, + copyable, + text, + UserRejectedRequestError, + MethodNotFoundError, +} from '@metamask/snaps-sdk'; import { Wallet } from 'ethers'; import type { SignMessageParams } from './types'; @@ -50,15 +56,13 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { }); if (!result) { - throw providerErrors.userRejectedRequest(); + throw new UserRejectedRequestError(); } return wallet.signMessage(params.message); } default: - throw rpcErrors.methodNotFound({ - data: { method: request.method }, - }); + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/get-entropy/CHANGELOG.md b/packages/examples/packages/get-entropy/CHANGELOG.md index c4d641f4bf..386aaf6d89 100644 --- a/packages/examples/packages/get-entropy/CHANGELOG.md +++ b/packages/examples/packages/get-entropy/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.1.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1930](https://github.com/MetaMask/snaps/pull/1930), @@ -44,7 +52,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/get-entropy-example-snap@2.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/get-entropy-example-snap@2.1.2...HEAD +[2.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/get-entropy-example-snap@2.1.1...@metamask/get-entropy-example-snap@2.1.2 +[2.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/get-entropy-example-snap@2.1.0...@metamask/get-entropy-example-snap@2.1.1 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/get-entropy-example-snap@2.0.1...@metamask/get-entropy-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/get-entropy-example-snap@2.0.0...@metamask/get-entropy-example-snap@2.0.1 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/get-entropy-example-snap@1.0.0...@metamask/get-entropy-example-snap@2.0.0 diff --git a/packages/examples/packages/get-entropy/images/icon.svg b/packages/examples/packages/get-entropy/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/get-entropy/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/get-entropy/jest.config.js b/packages/examples/packages/get-entropy/jest.config.js index 92dfdcd867..60c521cefb 100644 --- a/packages/examples/packages/get-entropy/jest.config.js +++ b/packages/examples/packages/get-entropy/jest.config.js @@ -22,6 +22,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/get-entropy/package.json b/packages/examples/packages/get-entropy/package.json index a7401f7783..10e12edc3e 100644 --- a/packages/examples/packages/get-entropy/package.json +++ b/packages/examples/packages/get-entropy/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/get-entropy-example-snap", - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating the use of `snap_getEntropy`.", "repository": { "type": "git", @@ -11,7 +11,6 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "images/icon.svg", "snap.manifest.json" ], "scripts": { @@ -31,14 +30,13 @@ "lint:dependencies": "depcheck" }, "dependencies": { - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^", "@metamask/utils": "^8.3.0", "@noble/bls12-381": "^1.2.0" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/get-entropy/snap.config.ts b/packages/examples/packages/get-entropy/snap.config.ts index 49a83436d5..ab1bf1efb7 100644 --- a/packages/examples/packages/get-entropy/snap.config.ts +++ b/packages/examples/packages/get-entropy/snap.config.ts @@ -2,13 +2,15 @@ import type { SnapConfig } from '@metamask/snaps-cli'; import { resolve } from 'path'; const config: SnapConfig = { - bundler: 'webpack', input: resolve(__dirname, 'src/index.ts'), server: { port: 8009, }, - polyfills: { - stream: true, + stats: { + buffer: false, + builtIns: { + ignore: ['crypto'], + }, }, }; diff --git a/packages/examples/packages/get-entropy/snap.manifest.json b/packages/examples/packages/get-entropy/snap.manifest.json index d09ac08445..637f25280e 100644 --- a/packages/examples/packages/get-entropy/snap.manifest.json +++ b/packages/examples/packages/get-entropy/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating the use of `snap_getEntropy`.", "proposedName": "Get Entropy Example Snap", "repository": { @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "IwH0dVg5Nj35EMSblUIZk+5JTyEVS+18LozwqMFd3vk=", + "shasum": "pXhAXvfd96IJp5935S8XjyHdIJ7G3fG0rIfykvoNAHU=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/get-entropy/src/index.ts b/packages/examples/packages/get-entropy/src/index.ts index 62d01a92a3..4a680d2e07 100644 --- a/packages/examples/packages/get-entropy/src/index.ts +++ b/packages/examples/packages/get-entropy/src/index.ts @@ -1,4 +1,3 @@ -import { rpcErrors, providerErrors } from '@metamask/rpc-errors'; import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; import { DialogType, @@ -6,6 +5,8 @@ import { text, heading, copyable, + UserRejectedRequestError, + MethodNotFoundError, } from '@metamask/snaps-sdk'; import { bytesToHex, stringToBytes } from '@metamask/utils'; import { sign } from '@noble/bls12-381'; @@ -47,7 +48,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { }); if (!approved) { - throw providerErrors.userRejectedRequest(); + throw new UserRejectedRequestError(); } const privateKey = await getEntropy(salt); @@ -56,8 +57,6 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { } default: - throw rpcErrors.methodNotFound({ - data: { method: request.method }, - }); + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/get-file/CHANGELOG.md b/packages/examples/packages/get-file/CHANGELOG.md index a53a77e0ca..463738d5e4 100644 --- a/packages/examples/packages/get-file/CHANGELOG.md +++ b/packages/examples/packages/get-file/CHANGELOG.md @@ -6,11 +6,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.1.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [1.1.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [1.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1946](https://github.com/MetaMask/snaps/pull/1946)) - - This package replaces the `@metamask/snaps-types` and - - `@metamask/snaps-ui` packages, and is much more lightweight. + - This package replaces the `@metamask/snaps-types` and + - `@metamask/snaps-ui` packages, and is much more lightweight. ## [1.0.1] ### Fixed @@ -20,7 +28,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add `snap_getFile` example Snap ([#1836](https://github.com/MetaMask/snaps/pull/1836), [#1858](https://github.com/MetaMask/snaps/pull/1858)) -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/get-file-example-snap@1.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/get-file-example-snap@1.1.2...HEAD +[1.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/get-file-example-snap@1.1.1...@metamask/get-file-example-snap@1.1.2 +[1.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/get-file-example-snap@1.1.0...@metamask/get-file-example-snap@1.1.1 [1.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/get-file-example-snap@1.0.1...@metamask/get-file-example-snap@1.1.0 [1.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/get-file-example-snap@1.0.0...@metamask/get-file-example-snap@1.0.1 [1.0.0]: https://github.com/MetaMask/snaps/releases/tag/@metamask/get-file-example-snap@1.0.0 diff --git a/packages/examples/packages/get-file/images/icon.svg b/packages/examples/packages/get-file/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/get-file/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/get-file/jest.config.js b/packages/examples/packages/get-file/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/get-file/jest.config.js +++ b/packages/examples/packages/get-file/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/get-file/package.json b/packages/examples/packages/get-file/package.json index b6f335b40c..f8bdbb79a0 100644 --- a/packages/examples/packages/get-file/package.json +++ b/packages/examples/packages/get-file/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/get-file-example-snap", - "version": "1.1.0", + "version": "1.1.2", "description": "MetaMask example snap demonstrating the use of `snap_getFile`.", "repository": { "type": "git", @@ -11,7 +11,6 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "images/icon.svg", "snap.manifest.json", "files/" ], @@ -32,12 +31,11 @@ "lint:dependencies": "depcheck" }, "dependencies": { - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/get-file/snap.config.ts b/packages/examples/packages/get-file/snap.config.ts index 1f618481d4..8388695427 100644 --- a/packages/examples/packages/get-file/snap.config.ts +++ b/packages/examples/packages/get-file/snap.config.ts @@ -2,13 +2,12 @@ import type { SnapConfig } from '@metamask/snaps-cli'; import { resolve } from 'path'; const config: SnapConfig = { - bundler: 'webpack', input: resolve(__dirname, 'src/index.ts'), server: { port: 8024, }, - polyfills: { - stream: true, + stats: { + buffer: false, }, }; diff --git a/packages/examples/packages/get-file/snap.manifest.json b/packages/examples/packages/get-file/snap.manifest.json index fd2f5dad3d..0da8e0bb79 100644 --- a/packages/examples/packages/get-file/snap.manifest.json +++ b/packages/examples/packages/get-file/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "1.1.0", + "version": "1.1.2", "description": "MetaMask example snap demonstrating the use of `snap_getFile`.", "proposedName": "Get File Example Snap", "repository": { @@ -7,11 +7,10 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "O7/2QbXLnB1Y6Mwhd41qldPQWu8LugZHZQfiDg2YDk8=", + "shasum": "8xZw7bkVD+WIkH1wQIGHi2Vv/kNTRsk+RT/2nYAshmc=", "location": { "npm": { "filePath": "dist/bundle.js", - "iconPath": "images/icon.svg", "packageName": "@metamask/get-file-example-snap", "registry": "https://registry.npmjs.org" } diff --git a/packages/examples/packages/get-file/src/index.ts b/packages/examples/packages/get-file/src/index.ts index 4164c35834..c405fc871d 100644 --- a/packages/examples/packages/get-file/src/index.ts +++ b/packages/examples/packages/get-file/src/index.ts @@ -1,5 +1,7 @@ -import { rpcErrors } from '@metamask/rpc-errors'; -import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; +import { + MethodNotFoundError, + type OnRpcRequestHandler, +} from '@metamask/snaps-sdk'; /** * Handle incoming JSON-RPC requests from the dapp, sent through the @@ -45,8 +47,6 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { } default: - throw rpcErrors.methodNotFound({ - data: { method: request.method }, - }); + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/home-page/CHANGELOG.md b/packages/examples/packages/home-page/CHANGELOG.md index 0f9b50a707..319fdfc27c 100644 --- a/packages/examples/packages/home-page/CHANGELOG.md +++ b/packages/examples/packages/home-page/CHANGELOG.md @@ -6,16 +6,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.1.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [1.1.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [1.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1946](https://github.com/MetaMask/snaps/pull/1946), [#1954](https://github.com/MetaMask/snaps/pull/1954)) - - This package replaces the `@metamask/snaps-types` and - - `@metamask/snaps-ui` packages, and is much more lightweight. + - This package replaces the `@metamask/snaps-types` and + - `@metamask/snaps-ui` packages, and is much more lightweight. ## [1.0.0] ### Added - Initial release ([#1918](https://github.com/MetaMask/snaps/pull/1918)) -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/home-page-example-snap@1.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/home-page-example-snap@1.1.2...HEAD +[1.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/home-page-example-snap@1.1.1...@metamask/home-page-example-snap@1.1.2 +[1.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/home-page-example-snap@1.1.0...@metamask/home-page-example-snap@1.1.1 [1.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/home-page-example-snap@1.0.0...@metamask/home-page-example-snap@1.1.0 [1.0.0]: https://github.com/MetaMask/snaps/releases/tag/@metamask/home-page-example-snap@1.0.0 diff --git a/packages/examples/packages/home-page/images/icon.svg b/packages/examples/packages/home-page/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/home-page/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/home-page/jest.config.js b/packages/examples/packages/home-page/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/home-page/jest.config.js +++ b/packages/examples/packages/home-page/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/home-page/package.json b/packages/examples/packages/home-page/package.json index 115f3ffffc..aa99a8a625 100644 --- a/packages/examples/packages/home-page/package.json +++ b/packages/examples/packages/home-page/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/home-page-example-snap", - "version": "1.1.0", + "version": "1.1.2", "description": "MetaMask example snap demonstrating the use of home pages.", "repository": { "type": "git", @@ -11,7 +11,6 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "images/icon.svg", "snap.manifest.json" ], "scripts": { @@ -35,7 +34,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/home-page/snap.config.ts b/packages/examples/packages/home-page/snap.config.ts index f9573a0652..f5633a2f55 100644 --- a/packages/examples/packages/home-page/snap.config.ts +++ b/packages/examples/packages/home-page/snap.config.ts @@ -2,13 +2,12 @@ import type { SnapConfig } from '@metamask/snaps-cli'; import { resolve } from 'path'; const config: SnapConfig = { - bundler: 'webpack', input: resolve(__dirname, 'src/index.ts'), server: { port: 8025, }, - polyfills: { - stream: true, + stats: { + buffer: false, }, }; diff --git a/packages/examples/packages/home-page/snap.manifest.json b/packages/examples/packages/home-page/snap.manifest.json index e7562485cc..28e504def3 100644 --- a/packages/examples/packages/home-page/snap.manifest.json +++ b/packages/examples/packages/home-page/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "1.1.0", + "version": "1.1.2", "description": "MetaMask example snap demonstrating the use of home pages.", "proposedName": "Home Page Example Snap", "repository": { @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "RABZLQsVw++ceVWu6WziPyyZWfLA/dkskOLGSG0Wrbk=", + "shasum": "ASDoD6KZ+AAXAm6XLnT2GLnqYGNWTXWWTWkv2vXY2is=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/home-page/src/index.test.ts b/packages/examples/packages/home-page/src/index.test.ts index 4c494a84d5..10872266fb 100644 --- a/packages/examples/packages/home-page/src/index.test.ts +++ b/packages/examples/packages/home-page/src/index.test.ts @@ -8,7 +8,9 @@ describe('onHomePage', () => { const response = await onHomePage(); - expect(response).toRender( + const screen = response.getInterface(); + + expect(screen).toRender( panel([heading('Hello world!'), text('Welcome to my Snap home page!')]), ); }); diff --git a/packages/examples/packages/images/CHANGELOG.md b/packages/examples/packages/images/CHANGELOG.md index aa399df1be..144e6dd66b 100644 --- a/packages/examples/packages/images/CHANGELOG.md +++ b/packages/examples/packages/images/CHANGELOG.md @@ -6,4 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -[Unreleased]: https://github.com/MetaMask/snaps/ +## [1.1.0] +### Changed +- Add example showing how to import and use images ([#2284](https://github.com/MetaMask/snaps/pull/2284)) +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [1.0.0] +### Added +- Add images example Snap ([#2002](https://github.com/MetaMask/snaps/pull/2002)) + +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/images-example-snap@1.1.0...HEAD +[1.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/images-example-snap@1.0.0...@metamask/images-example-snap@1.1.0 +[1.0.0]: https://github.com/MetaMask/snaps/releases/tag/@metamask/images-example-snap@1.0.0 diff --git a/packages/examples/packages/images/images/icon.svg b/packages/examples/packages/images/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/images/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/images/jest.config.js b/packages/examples/packages/images/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/images/jest.config.js +++ b/packages/examples/packages/images/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/images/package.json b/packages/examples/packages/images/package.json index 9643223440..5b530d5f87 100644 --- a/packages/examples/packages/images/package.json +++ b/packages/examples/packages/images/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/images-example-snap", - "version": "0.0.0", + "version": "1.1.0", "description": "MetaMask example Snap demonstrating how to render images in Snaps UI.", "repository": { "type": "git", @@ -11,8 +11,7 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "snap.manifest.json", - "images/icon.svg" + "snap.manifest.json" ], "scripts": { "build": "mm-snap build", @@ -31,13 +30,12 @@ "lint:dependencies": "depcheck" }, "dependencies": { - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^", "uqr": "^0.1.2" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/images/snap.config.ts b/packages/examples/packages/images/snap.config.ts index ad52859420..680c09621c 100644 --- a/packages/examples/packages/images/snap.config.ts +++ b/packages/examples/packages/images/snap.config.ts @@ -1,7 +1,6 @@ import type { SnapConfig } from '@metamask/snaps-cli'; const config: SnapConfig = { - bundler: 'webpack', input: './src/index.ts', server: { port: 8026, diff --git a/packages/examples/packages/images/snap.manifest.json b/packages/examples/packages/images/snap.manifest.json index f604ec9ad7..a364da5989 100644 --- a/packages/examples/packages/images/snap.manifest.json +++ b/packages/examples/packages/images/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "0.0.0", + "version": "1.1.0", "description": "MetaMask example Snap demonstrating how to render images in Snaps UI.", "proposedName": "Images Example Snap", "repository": { @@ -7,11 +7,10 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "9I+SLSNIzCFxIKAGMAQbQuUeSJNkPJc4mGmc25rLqo8=", + "shasum": "NnBTEkmPwyO5U2Nj+7o1P1IIeONNGFWibQnRUel34DA=", "location": { "npm": { "filePath": "dist/bundle.js", - "iconPath": "images/icon.svg", "packageName": "@metamask/images-example-snap", "registry": "https://registry.npmjs.org/" } diff --git a/packages/examples/packages/images/src/images/icon.png b/packages/examples/packages/images/src/images/icon.png new file mode 100644 index 0000000000..080a3b8477 Binary files /dev/null and b/packages/examples/packages/images/src/images/icon.png differ diff --git a/packages/examples/packages/images/src/images/icon.svg b/packages/examples/packages/images/src/images/icon.svg new file mode 100644 index 0000000000..5dc14b4dc1 --- /dev/null +++ b/packages/examples/packages/images/src/images/icon.svg @@ -0,0 +1,5 @@ + + + diff --git a/packages/examples/packages/images/src/index.test.ts b/packages/examples/packages/images/src/index.test.ts index eae8ddbd78..f398bcf39a 100644 --- a/packages/examples/packages/images/src/index.test.ts +++ b/packages/examples/packages/images/src/index.test.ts @@ -26,7 +26,7 @@ describe('onRpcRequest', () => { describe('getQrCode', () => { it('generates a QR code for the given data', async () => { - const { request, close } = await installSnap(); + const { request } = await installSnap(); const response = request({ method: 'getQrCode', @@ -46,16 +46,14 @@ describe('onRpcRequest', () => { await ui.ok(); expect(await response).toRespondWith(null); - - await close(); }); }); describe('getCat', () => { - // This test is flaky so we disable it for now + // This test is flaky, so we disable it for now. // eslint-disable-next-line jest/no-disabled-tests it.skip('shows a cat', async () => { - const { request, close } = await installSnap(); + const { request } = await installSnap(); const response = request({ method: 'getCat', @@ -84,8 +82,66 @@ describe('onRpcRequest', () => { await ui.ok(); expect(await response).toRespondWith(null); + }); + }); + + describe('getSvgIcon', () => { + it('shows an SVG icon', async () => { + const { request } = await installSnap(); + + const response = request({ + method: 'getSvgIcon', + }); + + const ui = await response.getInterface(); + // eslint-disable-next-line jest/prefer-strict-equal + expect(ui.content).toEqual({ + type: 'panel', + children: [ + { + type: 'text', + value: 'Here is an SVG icon:', + }, + { + type: 'image', + value: expect.stringContaining(' { + it('shows a PNG icon', async () => { + const { request } = await installSnap(); - await close(); + const response = request({ + method: 'getPngIcon', + }); + + const ui = await response.getInterface(); + // eslint-disable-next-line jest/prefer-strict-equal + expect(ui.content).toEqual({ + type: 'panel', + children: [ + { + type: 'text', + value: 'Here is a PNG icon:', + }, + { + type: 'image', + value: expect.stringContaining('data:image/png;base64'), + }, + ], + }); + + await ui.ok(); + + expect(await response).toRespondWith(null); }); }); }); diff --git a/packages/examples/packages/images/src/index.ts b/packages/examples/packages/images/src/index.ts index d0b3faec9c..42e71a7f17 100644 --- a/packages/examples/packages/images/src/index.ts +++ b/packages/examples/packages/images/src/index.ts @@ -1,4 +1,3 @@ -import { rpcErrors } from '@metamask/rpc-errors'; import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; import { DialogType, @@ -6,9 +5,13 @@ import { image, panel, text, + MethodNotFoundError, } from '@metamask/snaps-sdk'; import { renderSVG } from 'uqr'; +import pngIcon from './images/icon.png'; +import svgIcon from './images/icon.svg'; + /** * The parameters for the `getQrCode` method. * @@ -74,9 +77,33 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { }); } - default: - throw rpcErrors.methodNotFound({ - data: { method: request.method }, + case 'getSvgIcon': { + return await snap.request({ + method: 'snap_dialog', + params: { + type: DialogType.Alert, + + // `.svg` files are imported as strings, so they can be used directly + // with the `image` component. + content: panel([text('Here is an SVG icon:'), image(svgIcon)]), + }, + }); + } + + case 'getPngIcon': { + return await snap.request({ + method: 'snap_dialog', + params: { + type: DialogType.Alert, + + // `.png` files are imported as SVGs containing an `` tag, + // so they can be used directly with the `image` component. + content: panel([text('Here is a PNG icon:'), image(pngIcon)]), + }, }); + } + + default: + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/interactive-ui/.depcheckrc.json b/packages/examples/packages/interactive-ui/.depcheckrc.json new file mode 100644 index 0000000000..15d64e734b --- /dev/null +++ b/packages/examples/packages/interactive-ui/.depcheckrc.json @@ -0,0 +1,17 @@ +{ + "ignore-patterns": ["dist", "coverage"], + "ignores": [ + "@lavamoat/allow-scripts", + "@lavamoat/preinstall-always-fail", + "@metamask/auto-changelog", + "@metamask/eslint-*", + "@types/*", + "@typescript-eslint/*", + "eslint-config-*", + "eslint-plugin-*", + "prettier-plugin-packagejson", + "ts-node", + "typedoc", + "typescript" + ] +} diff --git a/packages/examples/packages/interactive-ui/.eslintrc.js b/packages/examples/packages/interactive-ui/.eslintrc.js new file mode 100644 index 0000000000..a47fd0b65d --- /dev/null +++ b/packages/examples/packages/interactive-ui/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: ['../../.eslintrc.js'], + + parserOptions: { + tsconfigRootDir: __dirname, + }, +}; diff --git a/packages/examples/packages/interactive-ui/CHANGELOG.md b/packages/examples/packages/interactive-ui/CHANGELOG.md new file mode 100644 index 0000000000..996e47cc6e --- /dev/null +++ b/packages/examples/packages/interactive-ui/CHANGELOG.md @@ -0,0 +1,24 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.0.2] +### Fixed +- Fix a crash when submitting an empty string ([#2333](https://github.com/MetaMask/snaps/pull/2333)) + +## [1.0.1] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [1.0.0] +### Added +- Add interactive UI example Snap ([#2171](https://github.com/MetaMask/snaps/pull/2171)) + +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/interactive-ui-example-snap@1.0.2...HEAD +[1.0.2]: https://github.com/MetaMask/snaps/compare/@metamask/interactive-ui-example-snap@1.0.1...@metamask/interactive-ui-example-snap@1.0.2 +[1.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/interactive-ui-example-snap@1.0.0...@metamask/interactive-ui-example-snap@1.0.1 +[1.0.0]: https://github.com/MetaMask/snaps/releases/tag/@metamask/interactive-ui-example-snap@1.0.0 diff --git a/packages/examples/packages/interactive-ui/LICENSE.APACHE2 b/packages/examples/packages/interactive-ui/LICENSE.APACHE2 new file mode 100644 index 0000000000..5fb887469b --- /dev/null +++ b/packages/examples/packages/interactive-ui/LICENSE.APACHE2 @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 ConsenSys Software Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/examples/packages/interactive-ui/LICENSE.MIT0 b/packages/examples/packages/interactive-ui/LICENSE.MIT0 new file mode 100644 index 0000000000..1a8536859a --- /dev/null +++ b/packages/examples/packages/interactive-ui/LICENSE.MIT0 @@ -0,0 +1,16 @@ +MIT No Attribution + +Copyright 2024 ConsenSys Software Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/examples/packages/interactive-ui/README.md b/packages/examples/packages/interactive-ui/README.md new file mode 100644 index 0000000000..9d56587692 --- /dev/null +++ b/packages/examples/packages/interactive-ui/README.md @@ -0,0 +1,26 @@ +# `@metamask/interactive-ui-example-snap` + +This snap demonstrates how to use interactive UI to build reactive custom UI interfaces across all the available APIs. + +## Snap usage + +### onRpcRequest + +This snap exposes an `onRpcRequest` handler, which supports the following +JSON-RPC methods: + +- `dialog`: Create a `snap_dialog` with an interactive interface. This demonstrates that a snap can show an interactive `snap_dialog` that the user can interact with. + +- `getState`: Get the state of a given interface. This demonstrates that a snap can retrieve an interface state. + +### onTransaction + +This snap exposes an `onTransaction` handler, which is called when a transaction +is sent by the user. It shows a user interface with details about the transaction. + +### onHomePage + +The snap exposes an `onHomePage` handler, which shows a user interface. + +For more information, you can refer to +[the end-to-end tests](./src/index.test.ts). diff --git a/packages/examples/packages/interactive-ui/jest.config.js b/packages/examples/packages/interactive-ui/jest.config.js new file mode 100644 index 0000000000..071fd27bdc --- /dev/null +++ b/packages/examples/packages/interactive-ui/jest.config.js @@ -0,0 +1,26 @@ +const deepmerge = require('deepmerge'); + +const baseConfig = require('../../../../jest.config.base'); + +module.exports = deepmerge(baseConfig, { + preset: '@metamask/snaps-jest', + + // Since `@metamask/snaps-jest` runs in the browser, we can't collect + // coverage information. + collectCoverage: false, + + // This is required for the tests to run inside the `MetaMask/snaps` + // repository. You don't need this in your own project. + moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], + '^@metamask/(.+)$': [ + '/../../../$1/src', + '/../../../../node_modules/@metamask/$1', + '/node_modules/@metamask/$1', + ], + }, +}); diff --git a/packages/examples/packages/interactive-ui/package.json b/packages/examples/packages/interactive-ui/package.json new file mode 100644 index 0000000000..1f22a9d923 --- /dev/null +++ b/packages/examples/packages/interactive-ui/package.json @@ -0,0 +1,74 @@ +{ + "name": "@metamask/interactive-ui-example-snap", + "version": "1.0.2", + "description": "MetaMask example snap demonstrating the use of interactive UI.", + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/snaps.git" + }, + "license": "(MIT-0 OR Apache-2.0)", + "sideEffects": false, + "main": "./dist/bundle.js", + "files": [ + "dist/", + "snap.manifest.json" + ], + "scripts": { + "build": "mm-snap build", + "build:clean": "yarn clean && yarn build", + "clean": "rimraf \"dist\"", + "lint": "yarn lint:eslint && yarn lint:misc --check && yarn lint:changelog && yarn lint:dependencies", + "lint:changelog": "../../../../scripts/validate-changelog.sh @metamask/interactive-ui-example-snap", + "lint:ci": "yarn lint", + "lint:eslint": "eslint . --cache --ext js,ts,jsx,tsx", + "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write", + "lint:misc": "prettier --no-error-on-unmatched-pattern --loglevel warn \"**/*.json\" \"**/*.md\" \"**/*.html\" \"!CHANGELOG.md\" \"!snap.manifest.json\" --ignore-path ../../../../.gitignore", + "start": "mm-snap watch", + "test": "yarn test:e2e", + "test:e2e": "jest", + "publish:preview": "yarn npm publish --tag preview", + "lint:dependencies": "depcheck" + }, + "dependencies": { + "@metamask/snaps-sdk": "workspace:^", + "@metamask/utils": "^8.3.0" + }, + "devDependencies": { + "@jest/globals": "^29.5.0", + "@lavamoat/allow-scripts": "^3.0.3", + "@metamask/auto-changelog": "^3.4.4", + "@metamask/eslint-config": "^12.1.0", + "@metamask/eslint-config-jest": "^12.1.0", + "@metamask/eslint-config-nodejs": "^12.1.0", + "@metamask/eslint-config-typescript": "^12.1.0", + "@metamask/snaps-cli": "workspace:^", + "@metamask/snaps-jest": "workspace:^", + "@swc/core": "1.3.78", + "@swc/jest": "^0.2.26", + "@typescript-eslint/eslint-plugin": "^5.42.1", + "@typescript-eslint/parser": "^5.42.1", + "deepmerge": "^4.2.2", + "depcheck": "^1.4.7", + "eslint": "^8.27.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jest": "^27.1.5", + "eslint-plugin-jsdoc": "^39.6.2", + "eslint-plugin-n": "^15.7.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-promise": "^6.1.1", + "jest": "^29.0.2", + "prettier": "^2.7.1", + "prettier-plugin-packagejson": "^2.2.11", + "rimraf": "^4.1.2", + "ts-node": "^10.9.1", + "typescript": "~4.8.4" + }, + "engines": { + "node": "^18.16 || >=20" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } +} diff --git a/packages/examples/packages/interactive-ui/snap.config.ts b/packages/examples/packages/interactive-ui/snap.config.ts new file mode 100644 index 0000000000..00d56f12be --- /dev/null +++ b/packages/examples/packages/interactive-ui/snap.config.ts @@ -0,0 +1,14 @@ +import type { SnapConfig } from '@metamask/snaps-cli'; +import { resolve } from 'path'; + +const config: SnapConfig = { + input: resolve(__dirname, 'src/index.ts'), + server: { + port: 8028, + }, + stats: { + buffer: false, + }, +}; + +export default config; diff --git a/packages/examples/packages/interactive-ui/snap.manifest.json b/packages/examples/packages/interactive-ui/snap.manifest.json new file mode 100644 index 0000000000..308151e090 --- /dev/null +++ b/packages/examples/packages/interactive-ui/snap.manifest.json @@ -0,0 +1,30 @@ +{ + "version": "1.0.2", + "description": "MetaMask example snap demonstrating the use of interactive UI", + "proposedName": "Interactive UI Example Snap", + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/snaps.git" + }, + "source": { + "shasum": "hwxuorPU9EqinXS2GRl8aIDAonawqBxoeKW8xhUsG/U=", + "location": { + "npm": { + "filePath": "dist/bundle.js", + "packageName": "@metamask/interactive-ui-example-snap", + "registry": "https://registry.npmjs.org/" + } + } + }, + "initialPermissions": { + "endowment:rpc": { + "dapps": true, + "snaps": false + }, + "snap_dialog": {}, + "endowment:transaction-insight": {}, + "endowment:page-home": {}, + "snap_manageState": {} + }, + "manifestVersion": "0.1" +} diff --git a/packages/examples/packages/interactive-ui/src/index.test.ts b/packages/examples/packages/interactive-ui/src/index.test.ts new file mode 100644 index 0000000000..75d8f9b56b --- /dev/null +++ b/packages/examples/packages/interactive-ui/src/index.test.ts @@ -0,0 +1,253 @@ +import { expect } from '@jest/globals'; +import { installSnap } from '@metamask/snaps-jest'; +import { + ButtonType, + address, + button, + copyable, + form, + heading, + input, + panel, + row, + text, +} from '@metamask/snaps-sdk'; +import { assert } from '@metamask/utils'; + +describe('onRpcRequest', () => { + it('throws an error if the requested method does not exist', async () => { + const { request } = await installSnap(); + + const response = await request({ + method: 'foo', + }); + + expect(response).toRespondWithError({ + code: -32601, + message: 'The method does not exist / is not available.', + stack: expect.any(String), + data: { + method: 'foo', + cause: null, + }, + }); + }); + + describe('dialog', () => { + it('creates a new Snap interface and use it in a confirmation dialog', async () => { + const { request } = await installSnap(); + + const response = request({ + method: 'dialog', + }); + + const startScreen = await response.getInterface(); + assert(startScreen.type === 'confirmation'); + + expect(startScreen).toRender( + panel([ + heading('Interactive UI Example Snap'), + button({ value: 'Update UI', name: 'update' }), + ]), + ); + + await startScreen.clickElement('update'); + + const formScreen = await response.getInterface(); + + expect(formScreen).toRender( + panel([ + heading('Interactive UI Example Snap'), + form({ + name: 'example-form', + children: [ + input({ + name: 'example-input', + placeholder: 'Enter something...', + }), + button('Submit', ButtonType.Submit, 'submit'), + ], + }), + ]), + ); + + await formScreen.typeInField('example-input', 'foobar'); + + await formScreen.clickElement('submit'); + + const resultScreen = await response.getInterface(); + + expect(resultScreen).toRender( + panel([ + heading('Interactive UI Example Snap'), + text('The submitted value is:'), + copyable('foobar'), + ]), + ); + await resultScreen.ok(); + + expect(await response).toRespondWith(true); + }); + + it('lets users input nothing', async () => { + const { request } = await installSnap(); + + const response = request({ + method: 'dialog', + }); + + const startScreen = await response.getInterface(); + assert(startScreen.type === 'confirmation'); + + expect(startScreen).toRender( + panel([ + heading('Interactive UI Example Snap'), + button({ value: 'Update UI', name: 'update' }), + ]), + ); + + await startScreen.clickElement('update'); + + const formScreen = await response.getInterface(); + + expect(formScreen).toRender( + panel([ + heading('Interactive UI Example Snap'), + form({ + name: 'example-form', + children: [ + input({ + name: 'example-input', + placeholder: 'Enter something...', + }), + button('Submit', ButtonType.Submit, 'submit'), + ], + }), + ]), + ); + + await formScreen.clickElement('submit'); + + const resultScreen = await response.getInterface(); + + expect(resultScreen).toRender( + panel([ + heading('Interactive UI Example Snap'), + text('The submitted value is:'), + copyable(''), + ]), + ); + await resultScreen.ok(); + + expect(await response).toRespondWith(true); + }); + }); + + describe('getState', () => { + it('gets the interface state', async () => { + const { request } = await installSnap(); + + const response = request({ + method: 'dialog', + }); + + const ui = await response.getInterface(); + + const getStateResponse = await request({ + method: 'getState', + }); + + await ui.ok(); + + expect(getStateResponse).toRespondWith({}); + }); + }); +}); + +describe('onHomePage', () => { + it('returns custom UI', async () => { + const { onHomePage } = await installSnap(); + + const response = await onHomePage(); + + const startScreen = response.getInterface(); + + expect(startScreen).toRender( + panel([ + heading('Interactive UI Example Snap'), + button({ value: 'Update UI', name: 'update' }), + ]), + ); + + await startScreen.clickElement('update'); + + const formScreen = response.getInterface(); + + expect(formScreen).toRender( + panel([ + heading('Interactive UI Example Snap'), + form({ + name: 'example-form', + children: [ + input({ + name: 'example-input', + placeholder: 'Enter something...', + }), + button('Submit', ButtonType.Submit, 'submit'), + ], + }), + ]), + ); + + await formScreen.typeInField('example-input', 'foobar'); + + await formScreen.clickElement('submit'); + + const resultScreen = response.getInterface(); + + expect(resultScreen).toRender( + panel([ + heading('Interactive UI Example Snap'), + text('The submitted value is:'), + copyable('foobar'), + ]), + ); + }); +}); + +describe('onTransaction', () => { + const FROM_ADDRESS = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045'; + const TO_ADDRESS = '0x4bbeeb066ed09b7aed07bf39eee0460dfa261520'; + it('returns custom UI', async () => { + const { onTransaction } = await installSnap(); + + const response = await onTransaction({ + from: FROM_ADDRESS, + to: TO_ADDRESS, + // This is not a valid ERC-20 transfer as all the values are zero, but it + // is enough to test the `onTransaction` handler. + data: '0xa9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + }); + + const startScreen = response.getInterface(); + + expect(startScreen).toRender( + panel([ + row('From', address(FROM_ADDRESS)), + row('To', address(TO_ADDRESS)), + button({ value: 'See transaction type', name: 'transaction-type' }), + ]), + ); + + await startScreen.clickElement('transaction-type'); + + const txTypeScreen = response.getInterface(); + + expect(txTypeScreen).toRender( + panel([ + row('Transaction type', text('ERC-20')), + button({ value: 'Go back', name: 'go-back' }), + ]), + ); + }); +}); diff --git a/packages/examples/packages/interactive-ui/src/index.ts b/packages/examples/packages/interactive-ui/src/index.ts new file mode 100644 index 0000000000..cc92b710b0 --- /dev/null +++ b/packages/examples/packages/interactive-ui/src/index.ts @@ -0,0 +1,191 @@ +import type { + OnRpcRequestHandler, + OnHomePageHandler, + OnTransactionHandler, + OnUserInputHandler, +} from '@metamask/snaps-sdk'; +import { + UserInputEventType, + ManageStateOperation, + assert, + MethodNotFoundError, +} from '@metamask/snaps-sdk'; + +import { + createInterface, + displayTransactionType, + getInsightContent, + showForm, + showResult, +} from './ui'; + +/** + * Handle incoming JSON-RPC requests from the dapp, sent through the + * `wallet_invokeSnap` method. This handler handles two methods: + * + * - `dialog`: Create a `snap_dialog` with an interactive interface. This demonstrates + * that a snap can show an interactive `snap_dialog` that the user can interact with. + * + * - `getState`: Get the state of a given interface. This demonstrates + * that a snap can retrieve an interface state. + * + * @param params - The request parameters. + * @param params.request - The JSON-RPC request object. + * @returns The JSON-RPC response. + * @see https://docs.metamask.io/snaps/reference/exports/#onrpcrequest + * @see https://docs.metamask.io/snaps/reference/rpc-api/#wallet_invokesnap + */ +export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { + switch (request.method) { + case 'dialog': { + try { + const interfaceId = await createInterface(); + + await snap.request({ + method: 'snap_manageState', + params: { + operation: ManageStateOperation.UpdateState, + newState: { interfaceId }, + encrypted: false, + }, + }); + + return await snap.request({ + method: 'snap_dialog', + params: { + type: 'confirmation', + id: interfaceId, + }, + }); + } finally { + await snap.request({ + method: 'snap_manageState', + params: { + operation: ManageStateOperation.ClearState, + encrypted: false, + }, + }); + } + } + + case 'getState': { + const snapState = await snap.request({ + method: 'snap_manageState', + params: { + operation: ManageStateOperation.GetState, + encrypted: false, + }, + }); + + assert(snapState?.interfaceId, 'No interface ID found in state.'); + + const state = await snap.request({ + method: 'snap_getInterfaceState', + params: { + id: snapState.interfaceId as string, + }, + }); + + return state; + } + + default: + throw new MethodNotFoundError({ + method: request.method, + }); + } +}; + +/** + * Handle incoming home page requests from the MetaMask clients. + * Create a new Snap Interface and return it. + * + * @returns A static panel rendered with custom UI. + * @see https://docs.metamask.io/snaps/reference/exports/#onhomepage + */ +export const onHomePage: OnHomePageHandler = async () => { + const interfaceId = await createInterface(); + + return { id: interfaceId }; +}; + +/** + * Handle incoming transactions, sent through the `wallet_sendTransaction` + * method. This handler decodes the transaction data, and displays the type of + * transaction in the transaction insights panel. + * + * The `onTransaction` handler is different from the `onRpcRequest` handler in + * that it is called by MetaMask when a transaction is initiated, rather than + * when a dapp sends a JSON-RPC request. The handler is called before the + * transaction is signed, so it can be used to display information about the + * transaction to the user before they sign it. + * + * The `onTransaction` handler returns a Snaps interface ID, which is used to + * retrieve the associated interface components in the transaction insights panel. + * + * @param args - The request parameters. + * @param args.transaction - The transaction object. This contains the + * transaction parameters, such as the `from`, `to`, `value`, and `data` fields. + * @returns The transaction insights. + */ +export const onTransaction: OnTransactionHandler = async ({ transaction }) => { + await snap.request({ + method: 'snap_manageState', + params: { + operation: ManageStateOperation.UpdateState, + newState: { transaction }, + }, + }); + + const interfaceId = await snap.request({ + method: 'snap_createInterface', + params: { + ui: await getInsightContent(), + }, + }); + + return { id: interfaceId }; +}; + +/** + * Handle incoming user events coming from the MetaMask clients open interfaces. + * + * @param params - The event parameters. + * @param params.id - The Snap interface ID where the event was fired. + * @param params.event - The event object containing the event type, name and value. + * @see https://docs.metamask.io/snaps/reference/exports/#onuserinput + */ +export const onUserInput: OnUserInputHandler = async ({ id, event }) => { + if (event.type === UserInputEventType.ButtonClickEvent) { + switch (event.name) { + case 'update': + await showForm(id); + break; + + case 'transaction-type': + await displayTransactionType(id); + break; + + case 'go-back': + await snap.request({ + method: 'snap_updateInterface', + params: { + id, + ui: await getInsightContent(), + }, + }); + break; + + default: + break; + } + } + + if ( + event.type === UserInputEventType.FormSubmitEvent && + event.name === 'example-form' + ) { + const inputValue = event.value['example-input']; + await showResult(id, inputValue ?? ''); + } +}; diff --git a/packages/examples/packages/interactive-ui/src/ui.ts b/packages/examples/packages/interactive-ui/src/ui.ts new file mode 100644 index 0000000000..39af31a0ae --- /dev/null +++ b/packages/examples/packages/interactive-ui/src/ui.ts @@ -0,0 +1,137 @@ +import { + ButtonType, + ManageStateOperation, + address, + button, + copyable, + form, + heading, + input, + panel, + row, + text, + assert, +} from '@metamask/snaps-sdk'; +import type { Component, Transaction } from '@metamask/snaps-sdk'; + +import { decodeData } from './utils'; + +/** + * Initiate a new interface with the starting screen. + * + * @returns The Snap interface ID. + */ +export async function createInterface(): Promise { + return await snap.request({ + method: 'snap_createInterface', + params: { + ui: panel([ + heading('Interactive UI Example Snap'), + button({ value: 'Update UI', name: 'update' }), + ]), + }, + }); +} + +/** + * Create the transaction insights components to display. + * + * @returns The transaction insight content. + */ +export async function getInsightContent(): Promise { + const snapState = await snap.request({ + method: 'snap_manageState', + params: { + operation: ManageStateOperation.GetState, + }, + }); + + assert(snapState?.transaction, 'No transaction found in Snap state.'); + + const { from, to } = snapState.transaction as Transaction; + + return panel([ + row('From', address(from)), + row('To', to ? address(to) : text('None')), + button({ value: 'See transaction type', name: 'transaction-type' }), + ]); +} + +/** + * Update a Snap interface to display the transaction type after fetching + * the transaction from state. + * + * @param id - The interface ID to update. + */ +export async function displayTransactionType(id: string) { + const snapState = await snap.request({ + method: 'snap_manageState', + params: { + operation: ManageStateOperation.GetState, + }, + }); + + assert(snapState?.transaction, 'No transaction found in Snap state.'); + + const transaction = snapState.transaction as Transaction; + + const type = decodeData(transaction.data); + + await snap.request({ + method: 'snap_updateInterface', + params: { + id, + ui: panel([ + row('Transaction type', text(type)), + button({ value: 'Go back', name: 'go-back' }), + ]), + }, + }); +} + +/** + * Update the interface with a simple form containing an input and a submit button. + * + * @param id - The Snap interface ID to update. + */ +export async function showForm(id: string) { + await snap.request({ + method: 'snap_updateInterface', + params: { + id, + ui: panel([ + heading('Interactive UI Example Snap'), + form({ + name: 'example-form', + children: [ + input({ + name: 'example-input', + placeholder: 'Enter something...', + }), + button('Submit', ButtonType.Submit, 'submit'), + ], + }), + ]), + }, + }); +} + +/** + * Update a Snap interface to show a given value. + * + * @param id - The Snap interface ID to update. + * @param value - The value to display in the UI. + */ +export async function showResult(id: string, value: string) { + await snap.request({ + method: 'snap_updateInterface', + params: { + id, + ui: panel([ + heading('Interactive UI Example Snap'), + text('The submitted value is:'), + copyable(value), + ]), + }, + }); +} diff --git a/packages/examples/packages/interactive-ui/src/utils.ts b/packages/examples/packages/interactive-ui/src/utils.ts new file mode 100644 index 0000000000..85981f4cd5 --- /dev/null +++ b/packages/examples/packages/interactive-ui/src/utils.ts @@ -0,0 +1,44 @@ +import { remove0x } from '@metamask/utils'; + +/** + * The function signatures for the different types of transactions. This is used + * to determine the type of transaction. This list is not exhaustive, and only + * contains the most common types of transactions for demonstration purposes. + */ +const FUNCTION_SIGNATURES = [ + { + name: 'ERC-20', + signature: 'a9059cbb', + }, + { + name: 'ERC-721', + signature: '23b872dd', + }, + { + name: 'ERC-1155', + signature: 'f242432a', + }, +]; + +/** + * Decode the transaction data. This checks the signature of the function that + * is being called, and returns the type of transaction. + * + * @param data - The transaction data. + * @returns The type of transaction, or "Unknown," if the function signature + * does not match any known signatures. + */ +export function decodeData(data?: string) { + if (data && typeof data === 'string') { + const normalisedData = remove0x(data); + const signature = normalisedData.slice(0, 8); + + const functionSignature = FUNCTION_SIGNATURES.find( + (value) => value.signature === signature, + ); + + return functionSignature?.name ?? 'Unknown'; + } + + return 'Unknown'; +} diff --git a/packages/examples/packages/interactive-ui/tsconfig.json b/packages/examples/packages/interactive-ui/tsconfig.json new file mode 100644 index 0000000000..1cb4c3315f --- /dev/null +++ b/packages/examples/packages/interactive-ui/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": "./", + "paths": { + "@metamask/*": ["../../../*/src"] + } + }, + "include": ["src", "snap.config.ts"], + "references": [ + { + "path": "../../../snaps-sdk" + }, + { + "path": "../../../snaps-jest" + }, + { + "path": "../../../snaps-cli" + } + ] +} diff --git a/packages/examples/packages/invoke-snap/package.json b/packages/examples/packages/invoke-snap/package.json index a67a26facc..76385cb4b1 100644 --- a/packages/examples/packages/invoke-snap/package.json +++ b/packages/examples/packages/invoke-snap/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/invoke-snap-example-snap", - "version": "2.1.0", + "version": "2.1.2", "private": true, "description": "MetaMask example snaps demonstrating the use of `wallet_invokeSnap` to call a snap from another snap.", "repository": { @@ -14,7 +14,6 @@ "scripts": { "build": "yarn workspaces foreach --parallel --verbose run build", "build:clean": "yarn clean && yarn build", - "build:post-tsc": "yarn build", "clean": "yarn workspaces foreach --parallel --verbose run clean", "start": "yarn workspaces foreach --parallel --verbose --interlaced --jobs unlimited run start", "test": "yarn workspaces foreach --parallel --verbose --interlaced run test", @@ -26,7 +25,7 @@ "lint:dependencies": "depcheck" }, "devDependencies": { - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/invoke-snap/packages/consumer-signer/CHANGELOG.md b/packages/examples/packages/invoke-snap/packages/consumer-signer/CHANGELOG.md index 71f89cdeea..7bf1c33d39 100644 --- a/packages/examples/packages/invoke-snap/packages/consumer-signer/CHANGELOG.md +++ b/packages/examples/packages/invoke-snap/packages/consumer-signer/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.1.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1946](https://github.com/MetaMask/snaps/pull/1946)) @@ -42,7 +50,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/consumer-signer-example-snap@2.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/consumer-signer-example-snap@2.1.2...HEAD +[2.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/consumer-signer-example-snap@2.1.1...@metamask/consumer-signer-example-snap@2.1.2 +[2.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/consumer-signer-example-snap@2.1.0...@metamask/consumer-signer-example-snap@2.1.1 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/consumer-signer-example-snap@2.0.1...@metamask/consumer-signer-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/consumer-signer-example-snap@2.0.0...@metamask/consumer-signer-example-snap@2.0.1 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/consumer-signer-example-snap@1.0.0...@metamask/consumer-signer-example-snap@2.0.0 diff --git a/packages/examples/packages/invoke-snap/packages/consumer-signer/images/icon.svg b/packages/examples/packages/invoke-snap/packages/consumer-signer/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/invoke-snap/packages/consumer-signer/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/invoke-snap/packages/consumer-signer/jest.config.js b/packages/examples/packages/invoke-snap/packages/consumer-signer/jest.config.js index 3afbf727e3..d36d825410 100644 --- a/packages/examples/packages/invoke-snap/packages/consumer-signer/jest.config.js +++ b/packages/examples/packages/invoke-snap/packages/consumer-signer/jest.config.js @@ -22,6 +22,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../../../$1/src/node', + '/../../../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../../../$1/src', '/../../../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/invoke-snap/packages/consumer-signer/package.json b/packages/examples/packages/invoke-snap/packages/consumer-signer/package.json index 7cd5f5690c..3faa052ee8 100644 --- a/packages/examples/packages/invoke-snap/packages/consumer-signer/package.json +++ b/packages/examples/packages/invoke-snap/packages/consumer-signer/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/consumer-signer-example-snap", - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating how a snap can communicate with another snap to sign messages.", "repository": { "type": "git", @@ -11,7 +11,6 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "images/icon.svg", "snap.manifest.json" ], "scripts": { @@ -32,14 +31,13 @@ }, "dependencies": { "@metamask/key-tree": "^9.0.0", - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^", "@metamask/utils": "^8.3.0", "@noble/hashes": "^1.3.1" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/invoke-snap/packages/consumer-signer/snap.config.ts b/packages/examples/packages/invoke-snap/packages/consumer-signer/snap.config.ts index 7c1550a710..f22cdc2f9f 100644 --- a/packages/examples/packages/invoke-snap/packages/consumer-signer/snap.config.ts +++ b/packages/examples/packages/invoke-snap/packages/consumer-signer/snap.config.ts @@ -2,13 +2,12 @@ import type { SnapConfig } from '@metamask/snaps-cli'; import { resolve } from 'path'; const config: SnapConfig = { - bundler: 'webpack', input: resolve(__dirname, 'src/index.ts'), server: { port: 8012, }, - polyfills: { - stream: true, + stats: { + buffer: false, }, }; diff --git a/packages/examples/packages/invoke-snap/packages/consumer-signer/snap.manifest.json b/packages/examples/packages/invoke-snap/packages/consumer-signer/snap.manifest.json index c0a17f981d..06638654ca 100644 --- a/packages/examples/packages/invoke-snap/packages/consumer-signer/snap.manifest.json +++ b/packages/examples/packages/invoke-snap/packages/consumer-signer/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating how a snap can communicate with another snap to sign messages.", "proposedName": "Consumer Signer Example Snap", "repository": { @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "rOixy+uQEfnU0AfVmJi1dY2R7laPP/VKaYJFlo0txzE=", + "shasum": "2IXxEn6f4kNYV6wI5294Iqs6rRx/tfvq+g70+2WGxBA=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/invoke-snap/packages/consumer-signer/src/index.ts b/packages/examples/packages/invoke-snap/packages/consumer-signer/src/index.ts index 7cdf5ee47a..0e6a482ff5 100644 --- a/packages/examples/packages/invoke-snap/packages/consumer-signer/src/index.ts +++ b/packages/examples/packages/invoke-snap/packages/consumer-signer/src/index.ts @@ -1,5 +1,8 @@ -import { rpcErrors } from '@metamask/rpc-errors'; -import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; +import { + InvalidParamsError, + MethodNotFoundError, + type OnRpcRequestHandler, +} from '@metamask/snaps-sdk'; import { assert, stringToBytes } from '@metamask/utils'; import { keccak_256 as keccak256 } from '@noble/hashes/sha3'; @@ -37,10 +40,9 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { assert( path[1] === `bip32:60'`, - rpcErrors.invalidParams({ - message: - "This snap only supports the Ethereum mainnet. Please use the `bip32:60'` coin type.", - }), + new InvalidParamsError( + "This snap only supports the Ethereum mainnet. Please use the `bip32:60'` coin type.", + ), ); const account = await snap.request({ @@ -75,8 +77,6 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { } default: - throw rpcErrors.methodNotFound({ - data: { method: request.method }, - }); + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/invoke-snap/packages/core-signer/CHANGELOG.md b/packages/examples/packages/invoke-snap/packages/core-signer/CHANGELOG.md index ab48e471fb..733728d83d 100644 --- a/packages/examples/packages/invoke-snap/packages/core-signer/CHANGELOG.md +++ b/packages/examples/packages/invoke-snap/packages/core-signer/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.1.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1930](https://github.com/MetaMask/snaps/pull/1930), @@ -44,7 +52,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/core-signer-example-snap@2.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/core-signer-example-snap@2.1.2...HEAD +[2.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/core-signer-example-snap@2.1.1...@metamask/core-signer-example-snap@2.1.2 +[2.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/core-signer-example-snap@2.1.0...@metamask/core-signer-example-snap@2.1.1 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/core-signer-example-snap@2.0.1...@metamask/core-signer-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/core-signer-example-snap@2.0.0...@metamask/core-signer-example-snap@2.0.1 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/core-signer-example-snap@1.0.0...@metamask/core-signer-example-snap@2.0.0 diff --git a/packages/examples/packages/invoke-snap/packages/core-signer/images/icon.svg b/packages/examples/packages/invoke-snap/packages/core-signer/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/invoke-snap/packages/core-signer/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/invoke-snap/packages/core-signer/jest.config.js b/packages/examples/packages/invoke-snap/packages/core-signer/jest.config.js index 3afbf727e3..d36d825410 100644 --- a/packages/examples/packages/invoke-snap/packages/core-signer/jest.config.js +++ b/packages/examples/packages/invoke-snap/packages/core-signer/jest.config.js @@ -22,6 +22,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../../../$1/src/node', + '/../../../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../../../$1/src', '/../../../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/invoke-snap/packages/core-signer/package.json b/packages/examples/packages/invoke-snap/packages/core-signer/package.json index a7fca4cd8d..646155afd3 100644 --- a/packages/examples/packages/invoke-snap/packages/core-signer/package.json +++ b/packages/examples/packages/invoke-snap/packages/core-signer/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/core-signer-example-snap", - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating how a snap can communicate with another snap to sign messages.", "repository": { "type": "git", @@ -11,7 +11,6 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "images/icon.svg", "snap.manifest.json" ], "scripts": { @@ -32,7 +31,6 @@ }, "dependencies": { "@metamask/key-tree": "^9.0.0", - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^", "@metamask/utils": "^8.3.0", "@noble/curves": "^1.1.0", @@ -40,7 +38,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/invoke-snap/packages/core-signer/snap.config.ts b/packages/examples/packages/invoke-snap/packages/core-signer/snap.config.ts index 950e145993..b88cb5de58 100644 --- a/packages/examples/packages/invoke-snap/packages/core-signer/snap.config.ts +++ b/packages/examples/packages/invoke-snap/packages/core-signer/snap.config.ts @@ -2,13 +2,15 @@ import type { SnapConfig } from '@metamask/snaps-cli'; import { resolve } from 'path'; const config: SnapConfig = { - bundler: 'webpack', input: resolve(__dirname, 'src/index.ts'), server: { port: 8011, }, - polyfills: { - stream: true, + stats: { + buffer: false, + builtIns: { + ignore: ['crypto'], + }, }, }; diff --git a/packages/examples/packages/invoke-snap/packages/core-signer/snap.manifest.json b/packages/examples/packages/invoke-snap/packages/core-signer/snap.manifest.json index a569dff97d..d9d9994e07 100644 --- a/packages/examples/packages/invoke-snap/packages/core-signer/snap.manifest.json +++ b/packages/examples/packages/invoke-snap/packages/core-signer/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating how a snap can communicate with another snap to sign messages.", "proposedName": "Core Signer Example Snap", "repository": { @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "BgiCbLwBUImrUY2jRtApDdP/8uVw7dfOp9uvVwxNON0=", + "shasum": "QvLqQVoORJ/nB82vcc8SNmFqRpUZCJw/Gyj1vryeA/c=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/invoke-snap/packages/core-signer/src/index.ts b/packages/examples/packages/invoke-snap/packages/core-signer/src/index.ts index fe879bc9e4..a00cb87a7e 100644 --- a/packages/examples/packages/invoke-snap/packages/core-signer/src/index.ts +++ b/packages/examples/packages/invoke-snap/packages/core-signer/src/index.ts @@ -1,4 +1,3 @@ -import { rpcErrors, providerErrors } from '@metamask/rpc-errors'; import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; import { DialogType, @@ -6,6 +5,8 @@ import { text, heading, copyable, + MethodNotFoundError, + UserRejectedRequestError, } from '@metamask/snaps-sdk'; import { add0x, assert, hexToBytes } from '@metamask/utils'; import { secp256k1 } from '@noble/curves/secp256k1'; @@ -68,7 +69,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { }); if (!approved) { - throw providerErrors.userRejectedRequest(); + throw new UserRejectedRequestError(); } const signature = secp256k1.sign( @@ -80,8 +81,6 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { } default: - throw rpcErrors.methodNotFound({ - data: { method: request.method }, - }); + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/json-rpc/CHANGELOG.md b/packages/examples/packages/json-rpc/CHANGELOG.md index 95db52dbbf..0f224cc13b 100644 --- a/packages/examples/packages/json-rpc/CHANGELOG.md +++ b/packages/examples/packages/json-rpc/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.1.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1946](https://github.com/MetaMask/snaps/pull/1946)) @@ -34,7 +42,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/json-rpc-example-snap@2.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/json-rpc-example-snap@2.1.2...HEAD +[2.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/json-rpc-example-snap@2.1.1...@metamask/json-rpc-example-snap@2.1.2 +[2.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/json-rpc-example-snap@2.1.0...@metamask/json-rpc-example-snap@2.1.1 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/json-rpc-example-snap@2.0.1...@metamask/json-rpc-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/json-rpc-example-snap@2.0.0...@metamask/json-rpc-example-snap@2.0.1 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/json-rpc-example-snap@1.0.0...@metamask/json-rpc-example-snap@2.0.0 diff --git a/packages/examples/packages/json-rpc/images/icon.svg b/packages/examples/packages/json-rpc/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/json-rpc/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/json-rpc/jest.config.js b/packages/examples/packages/json-rpc/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/json-rpc/jest.config.js +++ b/packages/examples/packages/json-rpc/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/json-rpc/package.json b/packages/examples/packages/json-rpc/package.json index 1137a7a948..59180e73f5 100644 --- a/packages/examples/packages/json-rpc/package.json +++ b/packages/examples/packages/json-rpc/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/json-rpc-example-snap", - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating the use of the `endowment:rpc` permission.", "repository": { "type": "git", @@ -11,7 +11,6 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "images/icon.svg", "snap.manifest.json" ], "scripts": { @@ -31,12 +30,11 @@ "lint:dependencies": "depcheck" }, "dependencies": { - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/json-rpc/snap.config.ts b/packages/examples/packages/json-rpc/snap.config.ts index 414fdb5244..15df84202c 100644 --- a/packages/examples/packages/json-rpc/snap.config.ts +++ b/packages/examples/packages/json-rpc/snap.config.ts @@ -1,7 +1,6 @@ import type { SnapConfig } from '@metamask/snaps-cli'; const config: SnapConfig = { - bundler: 'webpack', input: './src/index.ts', server: { port: 8013, diff --git a/packages/examples/packages/json-rpc/snap.manifest.json b/packages/examples/packages/json-rpc/snap.manifest.json index 54736e4291..df2ea2d35c 100644 --- a/packages/examples/packages/json-rpc/snap.manifest.json +++ b/packages/examples/packages/json-rpc/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.0", + "version": "2.1.2", "description": "An example snap to test JSON-RPC permissions.", "proposedName": "JSON-RPC Example Snap", "repository": { @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "GSs2zxpSeeiUs0w3uuYS7b3K5eEuqyDN3mpXO7BNOsc=", + "shasum": "AsKG87SRdGV6xeJVjT+OranYxEM6YyJz4XLVAPFDcEQ=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/json-rpc/src/index.ts b/packages/examples/packages/json-rpc/src/index.ts index 573c7c1bbc..cae6a1033c 100644 --- a/packages/examples/packages/json-rpc/src/index.ts +++ b/packages/examples/packages/json-rpc/src/index.ts @@ -1,5 +1,7 @@ -import { rpcErrors } from '@metamask/rpc-errors'; -import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; +import { + MethodNotFoundError, + type OnRpcRequestHandler, +} from '@metamask/snaps-sdk'; /** * Handle incoming JSON-RPC requests from the dapp, sent through the @@ -59,10 +61,6 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { } default: - throw rpcErrors.methodNotFound({ - data: { - method: request.method, - }, - }); + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/json-rpc/tsconfig.json b/packages/examples/packages/json-rpc/tsconfig.json index 1cb4c3315f..42b2282b7b 100644 --- a/packages/examples/packages/json-rpc/tsconfig.json +++ b/packages/examples/packages/json-rpc/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "baseUrl": "./", "paths": { + "@metamask/*/node": ["../../../*/src/node"], "@metamask/*": ["../../../*/src"] } }, diff --git a/packages/examples/packages/lifecycle-hooks/CHANGELOG.md b/packages/examples/packages/lifecycle-hooks/CHANGELOG.md index f3cf93ba6b..11648d45ec 100644 --- a/packages/examples/packages/lifecycle-hooks/CHANGELOG.md +++ b/packages/examples/packages/lifecycle-hooks/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.1.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1946](https://github.com/MetaMask/snaps/pull/1946), [#1954](https://github.com/MetaMask/snaps/pull/1954)) @@ -33,7 +41,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add lifecycle hooks example snap ([#1645](https://github.com/MetaMask/snaps/pull/1645)) - This snap demonstrates how to use the `onInstall` and `onUpdate` lifecycle hooks. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/lifecycle-hooks-example-snap@2.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/lifecycle-hooks-example-snap@2.1.2...HEAD +[2.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/lifecycle-hooks-example-snap@2.1.1...@metamask/lifecycle-hooks-example-snap@2.1.2 +[2.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/lifecycle-hooks-example-snap@2.1.0...@metamask/lifecycle-hooks-example-snap@2.1.1 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/lifecycle-hooks-example-snap@2.0.1...@metamask/lifecycle-hooks-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/lifecycle-hooks-example-snap@2.0.0...@metamask/lifecycle-hooks-example-snap@2.0.1 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/lifecycle-hooks-example-snap@1.0.0...@metamask/lifecycle-hooks-example-snap@2.0.0 diff --git a/packages/examples/packages/lifecycle-hooks/images/icon.svg b/packages/examples/packages/lifecycle-hooks/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/lifecycle-hooks/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/lifecycle-hooks/jest.config.js b/packages/examples/packages/lifecycle-hooks/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/lifecycle-hooks/jest.config.js +++ b/packages/examples/packages/lifecycle-hooks/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/lifecycle-hooks/package.json b/packages/examples/packages/lifecycle-hooks/package.json index d196755ada..8983456860 100644 --- a/packages/examples/packages/lifecycle-hooks/package.json +++ b/packages/examples/packages/lifecycle-hooks/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/lifecycle-hooks-example-snap", - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating the use of the `onInstall` and `onUpdate` lifecycle hooks.", "repository": { "type": "git", @@ -11,7 +11,6 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "images/icon.svg", "snap.manifest.json" ], "scripts": { @@ -35,7 +34,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/lifecycle-hooks/snap.config.ts b/packages/examples/packages/lifecycle-hooks/snap.config.ts index 7edc1e2657..d604a86129 100644 --- a/packages/examples/packages/lifecycle-hooks/snap.config.ts +++ b/packages/examples/packages/lifecycle-hooks/snap.config.ts @@ -1,7 +1,6 @@ import type { SnapConfig } from '@metamask/snaps-cli'; const config: SnapConfig = { - bundler: 'webpack', input: './src/index.ts', server: { port: 8022, diff --git a/packages/examples/packages/lifecycle-hooks/snap.manifest.json b/packages/examples/packages/lifecycle-hooks/snap.manifest.json index d4d86cb4c4..f9ec38e838 100644 --- a/packages/examples/packages/lifecycle-hooks/snap.manifest.json +++ b/packages/examples/packages/lifecycle-hooks/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating the use of the `onInstall` and `onUpdate` lifecycle hooks.", "proposedName": "Lifecycle Hooks Example Snap", "repository": { @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "n5mn1SsXj1hVgOc0weMmwEMVEStLYYu/pfAhMGIbF7w=", + "shasum": "PUJ46lEhp4Cvr6fkIhlr7hCJvjZGgsmpEubMrdnNTeA=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/localization/CHANGELOG.md b/packages/examples/packages/localization/CHANGELOG.md index 324e5e42ef..c9eca57547 100644 --- a/packages/examples/packages/localization/CHANGELOG.md +++ b/packages/examples/packages/localization/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.1.3] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [1.1.2] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [1.1.1] ### Fixed - Publish locales folder to NPM ([#1962](https://github.com/MetaMask/snaps/pull/1962)) @@ -13,8 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1946](https://github.com/MetaMask/snaps/pull/1946)) - - This package replaces the `@metamask/snaps-types` and - - `@metamask/snaps-ui` packages, and is much more lightweight. + - This package replaces the `@metamask/snaps-types` and + - `@metamask/snaps-ui` packages, and is much more lightweight. ### Fixed - Include localization files in checksum calculations ([#1956](https://github.com/MetaMask/snaps/pull/1956)) @@ -23,7 +31,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Initial release ([#1889](https://github.com/MetaMask/snaps/pull/1889)) -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/localization-example-snap@1.1.1...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/localization-example-snap@1.1.3...HEAD +[1.1.3]: https://github.com/MetaMask/snaps/compare/@metamask/localization-example-snap@1.1.2...@metamask/localization-example-snap@1.1.3 +[1.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/localization-example-snap@1.1.1...@metamask/localization-example-snap@1.1.2 [1.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/localization-example-snap@1.1.0...@metamask/localization-example-snap@1.1.1 [1.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/localization-example-snap@1.0.0...@metamask/localization-example-snap@1.1.0 [1.0.0]: https://github.com/MetaMask/snaps/releases/tag/@metamask/localization-example-snap@1.0.0 diff --git a/packages/examples/packages/localization/images/icon.svg b/packages/examples/packages/localization/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/localization/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/localization/jest.config.js b/packages/examples/packages/localization/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/localization/jest.config.js +++ b/packages/examples/packages/localization/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/localization/package.json b/packages/examples/packages/localization/package.json index fad5de064e..014850f739 100644 --- a/packages/examples/packages/localization/package.json +++ b/packages/examples/packages/localization/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/localization-example-snap", - "version": "1.1.1", + "version": "1.1.3", "description": "MetaMask example snap demonstrating how to localize a snap.", "repository": { "type": "git", @@ -12,7 +12,6 @@ "files": [ "dist/", "snap.manifest.json", - "images/icon.svg", "locales/" ], "scripts": { @@ -32,12 +31,11 @@ "lint:dependencies": "depcheck" }, "dependencies": { - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/localization/snap.config.ts b/packages/examples/packages/localization/snap.config.ts index 2fa2dea79c..1a55062221 100644 --- a/packages/examples/packages/localization/snap.config.ts +++ b/packages/examples/packages/localization/snap.config.ts @@ -1,7 +1,6 @@ import type { SnapConfig } from '@metamask/snaps-cli'; const config: SnapConfig = { - bundler: 'webpack', input: './src/index.ts', server: { port: 8020, diff --git a/packages/examples/packages/localization/snap.manifest.json b/packages/examples/packages/localization/snap.manifest.json index fbff98cf40..6b0f986b20 100644 --- a/packages/examples/packages/localization/snap.manifest.json +++ b/packages/examples/packages/localization/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "1.1.1", + "version": "1.1.3", "description": "{{ description }}", "proposedName": "{{ name }}", "repository": { @@ -7,11 +7,10 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "2IJ+Uy5UALEDdYa57oi024k13H4Jn1YsR+fH5SUQATM=", + "shasum": "294hRqYtFbkho4eU2gc+amfn6OiUXXjhtMOQMGbgF/I=", "location": { "npm": { "filePath": "dist/bundle.js", - "iconPath": "images/icon.svg", "packageName": "@metamask/localization-example-snap", "registry": "https://registry.npmjs.org/" } diff --git a/packages/examples/packages/localization/src/index.ts b/packages/examples/packages/localization/src/index.ts index ff513c4ba3..77c9e6c919 100644 --- a/packages/examples/packages/localization/src/index.ts +++ b/packages/examples/packages/localization/src/index.ts @@ -1,5 +1,7 @@ -import { rpcErrors } from '@metamask/rpc-errors'; -import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; +import { + MethodNotFoundError, + type OnRpcRequestHandler, +} from '@metamask/snaps-sdk'; import { getMessage } from './locales'; @@ -20,8 +22,6 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { return await getMessage('hello'); default: - throw rpcErrors.methodNotFound({ - data: { method: request.method }, - }); + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/manage-state/CHANGELOG.md b/packages/examples/packages/manage-state/CHANGELOG.md index 31dc9cd07a..dd70cca2e8 100644 --- a/packages/examples/packages/manage-state/CHANGELOG.md +++ b/packages/examples/packages/manage-state/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.2.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.2.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.2.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1930](https://github.com/MetaMask/snaps/pull/1930), @@ -48,7 +56,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/manage-state-example-snap@2.2.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/manage-state-example-snap@2.2.2...HEAD +[2.2.2]: https://github.com/MetaMask/snaps/compare/@metamask/manage-state-example-snap@2.2.1...@metamask/manage-state-example-snap@2.2.2 +[2.2.1]: https://github.com/MetaMask/snaps/compare/@metamask/manage-state-example-snap@2.2.0...@metamask/manage-state-example-snap@2.2.1 [2.2.0]: https://github.com/MetaMask/snaps/compare/@metamask/manage-state-example-snap@2.1.0...@metamask/manage-state-example-snap@2.2.0 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/manage-state-example-snap@2.0.1...@metamask/manage-state-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/manage-state-example-snap@2.0.0...@metamask/manage-state-example-snap@2.0.1 diff --git a/packages/examples/packages/manage-state/images/icon.svg b/packages/examples/packages/manage-state/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/manage-state/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/manage-state/jest.config.js b/packages/examples/packages/manage-state/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/manage-state/jest.config.js +++ b/packages/examples/packages/manage-state/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/manage-state/package.json b/packages/examples/packages/manage-state/package.json index db80df2183..073abbb83a 100644 --- a/packages/examples/packages/manage-state/package.json +++ b/packages/examples/packages/manage-state/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/manage-state-example-snap", - "version": "2.2.0", + "version": "2.2.2", "description": "MetaMask example snap demonstrating the use of `snap_manageState`.", "repository": { "type": "git", @@ -11,8 +11,7 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "snap.manifest.json", - "images/icon.svg" + "snap.manifest.json" ], "scripts": { "build": "mm-snap build", @@ -31,12 +30,11 @@ "lint:dependencies": "depcheck" }, "dependencies": { - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/manage-state/snap.config.ts b/packages/examples/packages/manage-state/snap.config.ts index add0b3fcbc..53791af521 100644 --- a/packages/examples/packages/manage-state/snap.config.ts +++ b/packages/examples/packages/manage-state/snap.config.ts @@ -1,7 +1,6 @@ import type { SnapConfig } from '@metamask/snaps-cli'; const config: SnapConfig = { - bundler: 'webpack', input: './src/index.ts', server: { port: 8014, @@ -9,9 +8,6 @@ const config: SnapConfig = { stats: { buffer: false, }, - polyfills: { - stream: true, - }, }; export default config; diff --git a/packages/examples/packages/manage-state/snap.manifest.json b/packages/examples/packages/manage-state/snap.manifest.json index 343437e9d8..52005d49d6 100644 --- a/packages/examples/packages/manage-state/snap.manifest.json +++ b/packages/examples/packages/manage-state/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.2.0", + "version": "2.2.2", "description": "MetaMask example snap demonstrating the use of `snap_manageState`.", "proposedName": "Manage State Example Snap", "repository": { @@ -7,11 +7,10 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "UYM2R0zuW24ODQjTqnm+H+K6p3g/ae+Ml2EbyY5//Ok=", + "shasum": "744MKon7/xMQ6JJyV6K8PANnf7WXWkKLz06oQhszMqA=", "location": { "npm": { "filePath": "dist/bundle.js", - "iconPath": "images/icon.svg", "packageName": "@metamask/manage-state-example-snap", "registry": "https://registry.npmjs.org/" } diff --git a/packages/examples/packages/manage-state/src/index.ts b/packages/examples/packages/manage-state/src/index.ts index 1502445ecc..046f2cf278 100644 --- a/packages/examples/packages/manage-state/src/index.ts +++ b/packages/examples/packages/manage-state/src/index.ts @@ -1,5 +1,7 @@ -import { rpcErrors } from '@metamask/rpc-errors'; -import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; +import { + MethodNotFoundError, + type OnRpcRequestHandler, +} from '@metamask/snaps-sdk'; import type { BaseParams, SetStateParams } from './types'; import { clearState, getState, setState } from './utils'; @@ -51,10 +53,6 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { } default: - throw rpcErrors.methodNotFound({ - data: { - method: request.method, - }, - }); + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/name-lookup/CHANGELOG.md b/packages/examples/packages/name-lookup/CHANGELOG.md index 87f1ab92b2..366969b2ab 100644 --- a/packages/examples/packages/name-lookup/CHANGELOG.md +++ b/packages/examples/packages/name-lookup/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.0.2] +### Changed +- Re-release after multiple changes in the monorepo ([#2295](https://github.com/MetaMask/snaps/pull/2295)) + +## [3.0.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [3.0.0] ### Changed - **BREAKING:** Update snap to match new API ([#2113](https://github.com/MetaMask/snaps/pull/2113)) @@ -29,7 +37,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add name lookup example snap ([#1768](https://github.com/MetaMask/snaps/pull/1768), [#1754](https://github.com/MetaMask/snaps/pull/1754)) -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/name-lookup-example-snap@3.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/name-lookup-example-snap@3.0.2...HEAD +[3.0.2]: https://github.com/MetaMask/snaps/compare/@metamask/name-lookup-example-snap@3.0.1...@metamask/name-lookup-example-snap@3.0.2 +[3.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/name-lookup-example-snap@3.0.0...@metamask/name-lookup-example-snap@3.0.1 [3.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/name-lookup-example-snap@2.1.0...@metamask/name-lookup-example-snap@3.0.0 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/name-lookup-example-snap@2.0.1...@metamask/name-lookup-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/name-lookup-example-snap@2.0.0...@metamask/name-lookup-example-snap@2.0.1 diff --git a/packages/examples/packages/name-lookup/images/icon.svg b/packages/examples/packages/name-lookup/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/name-lookup/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/name-lookup/jest.config.js b/packages/examples/packages/name-lookup/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/name-lookup/jest.config.js +++ b/packages/examples/packages/name-lookup/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/name-lookup/package.json b/packages/examples/packages/name-lookup/package.json index bc31f9e7c0..e202aa6ce5 100644 --- a/packages/examples/packages/name-lookup/package.json +++ b/packages/examples/packages/name-lookup/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/name-lookup-example-snap", - "version": "3.0.0", + "version": "3.0.2", "description": "MetaMask example snap demonstrating the use of the `endowment:name-lookup` permission.", "repository": { "type": "git", @@ -11,7 +11,6 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "images/icon.svg", "snap.manifest.json" ], "scripts": { @@ -35,7 +34,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/name-lookup/snap.config.ts b/packages/examples/packages/name-lookup/snap.config.ts index c2f7f62178..5e18455663 100644 --- a/packages/examples/packages/name-lookup/snap.config.ts +++ b/packages/examples/packages/name-lookup/snap.config.ts @@ -1,7 +1,6 @@ import type { SnapConfig } from '@metamask/snaps-cli'; const config: SnapConfig = { - bundler: 'webpack', input: './src/index.ts', server: { port: 8023, diff --git a/packages/examples/packages/name-lookup/snap.manifest.json b/packages/examples/packages/name-lookup/snap.manifest.json index 3194204b0f..a876a59eb2 100644 --- a/packages/examples/packages/name-lookup/snap.manifest.json +++ b/packages/examples/packages/name-lookup/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "3.0.0", + "version": "3.0.2", "description": "MetaMask example snap demonstrating the use of the `endowment:name-lookup` permission.", "proposedName": "Name Lookup Example Snap", "repository": { @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "snZ/j7c61yFp0K0b77HAw0cLdQswUuqoJE+nVrrBK/A=", + "shasum": "WN15beK70xlxFC71Zv+AFJn0laKEPJxgF63Rpwbboc8=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/network-access/CHANGELOG.md b/packages/examples/packages/network-access/CHANGELOG.md index 6d66876e39..704745359e 100644 --- a/packages/examples/packages/network-access/CHANGELOG.md +++ b/packages/examples/packages/network-access/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.1.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1946](https://github.com/MetaMask/snaps/pull/1946)) @@ -46,7 +54,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/network-example-snap@2.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/network-example-snap@2.1.2...HEAD +[2.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/network-example-snap@2.1.1...@metamask/network-example-snap@2.1.2 +[2.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/network-example-snap@2.1.0...@metamask/network-example-snap@2.1.1 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/network-example-snap@2.0.1...@metamask/network-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/network-example-snap@2.0.0...@metamask/network-example-snap@2.0.1 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/network-example-snap@1.0.0...@metamask/network-example-snap@2.0.0 diff --git a/packages/examples/packages/network-access/images/icon.svg b/packages/examples/packages/network-access/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/network-access/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/network-access/jest.config.js b/packages/examples/packages/network-access/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/network-access/jest.config.js +++ b/packages/examples/packages/network-access/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/network-access/package.json b/packages/examples/packages/network-access/package.json index 1af2e7130d..17f0d96823 100644 --- a/packages/examples/packages/network-access/package.json +++ b/packages/examples/packages/network-access/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/network-example-snap", - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating the use of the `endowment:network-access` permission in snaps.", "repository": { "type": "git", @@ -11,7 +11,6 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "images/icon.svg", "snap.manifest.json" ], "scripts": { @@ -31,19 +30,19 @@ "lint:dependencies": "depcheck" }, "dependencies": { - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^", "@metamask/utils": "^8.3.0" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", "@metamask/eslint-config-nodejs": "^12.1.0", "@metamask/eslint-config-typescript": "^12.1.0", "@metamask/snaps-cli": "workspace:^", + "@metamask/snaps-controllers": "workspace:^", "@metamask/snaps-jest": "workspace:^", "@swc/core": "1.3.78", "@swc/jest": "^0.2.26", diff --git a/packages/examples/packages/network-access/snap.config.ts b/packages/examples/packages/network-access/snap.config.ts index c5fab26290..47cc20f1c4 100644 --- a/packages/examples/packages/network-access/snap.config.ts +++ b/packages/examples/packages/network-access/snap.config.ts @@ -1,7 +1,6 @@ import type { SnapConfig } from '@metamask/snaps-cli'; const config: SnapConfig = { - bundler: 'webpack', input: './src/index.ts', server: { port: 8015, @@ -9,9 +8,6 @@ const config: SnapConfig = { stats: { buffer: false, }, - polyfills: { - stream: true, - }, }; export default config; diff --git a/packages/examples/packages/network-access/snap.manifest.json b/packages/examples/packages/network-access/snap.manifest.json index dbf277c4a0..199a8a6460 100644 --- a/packages/examples/packages/network-access/snap.manifest.json +++ b/packages/examples/packages/network-access/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating the use of the `endowment:network-access` permission in snaps.", "proposedName": "Network Access Test Snap", "repository": { @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "tC8AzqPr6zKC9Zig+rOiKtN1foI1B9IWL7C/oPHMhAE=", + "shasum": "ncWlW87GtmR4X97s0HHLcutUShbjk1wB3Jx1hFFCq80=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/network-access/src/index.test.ts b/packages/examples/packages/network-access/src/index.test.ts index 36eacae5c3..30e0aa313e 100644 --- a/packages/examples/packages/network-access/src/index.test.ts +++ b/packages/examples/packages/network-access/src/index.test.ts @@ -1,9 +1,12 @@ import { expect } from '@jest/globals'; +import { NodeProcessExecutionService } from '@metamask/snaps-controllers'; import { installSnap } from '@metamask/snaps-jest'; describe('onRpcRequest', () => { it('throws an error if the requested method does not exist', async () => { - const { request } = await installSnap(); + const { request } = await installSnap({ + executionService: NodeProcessExecutionService, + }); const response = await request({ method: 'foo', @@ -21,8 +24,11 @@ describe('onRpcRequest', () => { }); describe('fetch', () => { - it('fetches a URL and returns the JSON response', async () => { - const { request } = await installSnap(); + // This test is disabled as it is flaky. + it.skip('fetches a URL and returns the JSON response', async () => { + const { request } = await installSnap({ + executionService: NodeProcessExecutionService, + }); const url = 'https://dummyjson.com/http/200'; const response = await request({ diff --git a/packages/examples/packages/network-access/src/index.ts b/packages/examples/packages/network-access/src/index.ts index 96761f8f59..44b5ea9a3e 100644 --- a/packages/examples/packages/network-access/src/index.ts +++ b/packages/examples/packages/network-access/src/index.ts @@ -1,5 +1,7 @@ -import { rpcErrors } from '@metamask/rpc-errors'; -import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; +import { + MethodNotFoundError, + type OnRpcRequestHandler, +} from '@metamask/snaps-sdk'; import { assert } from '@metamask/utils'; import type { FetchParams } from './types'; @@ -46,10 +48,6 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { } default: - throw rpcErrors.methodNotFound({ - data: { - method: request.method, - }, - }); + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/network-access/tsconfig.json b/packages/examples/packages/network-access/tsconfig.json index 026ee84ff7..a73dc9023f 100644 --- a/packages/examples/packages/network-access/tsconfig.json +++ b/packages/examples/packages/network-access/tsconfig.json @@ -16,6 +16,9 @@ }, { "path": "../../../snaps-cli" + }, + { + "path": "../../../snaps-controllers" } ] } diff --git a/packages/examples/packages/notifications/CHANGELOG.md b/packages/examples/packages/notifications/CHANGELOG.md index 311f014306..a5b942fe78 100644 --- a/packages/examples/packages/notifications/CHANGELOG.md +++ b/packages/examples/packages/notifications/CHANGELOG.md @@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.3] +### Fixed +- Fix native notifications not working reliably ([#2310](https://github.com/MetaMask/snaps/pull/2310)) + +## [2.1.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.1.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1930](https://github.com/MetaMask/snaps/pull/1930), @@ -44,7 +56,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/notification-example-snap@2.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/notification-example-snap@2.1.3...HEAD +[2.1.3]: https://github.com/MetaMask/snaps/compare/@metamask/notification-example-snap@2.1.2...@metamask/notification-example-snap@2.1.3 +[2.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/notification-example-snap@2.1.1...@metamask/notification-example-snap@2.1.2 +[2.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/notification-example-snap@2.1.0...@metamask/notification-example-snap@2.1.1 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/notification-example-snap@2.0.1...@metamask/notification-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/notification-example-snap@2.0.0...@metamask/notification-example-snap@2.0.1 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/notification-example-snap@1.0.0...@metamask/notification-example-snap@2.0.0 diff --git a/packages/examples/packages/notifications/images/icon.svg b/packages/examples/packages/notifications/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/notifications/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/notifications/jest.config.js b/packages/examples/packages/notifications/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/notifications/jest.config.js +++ b/packages/examples/packages/notifications/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/notifications/package.json b/packages/examples/packages/notifications/package.json index 83ee1ca456..c452063ef5 100644 --- a/packages/examples/packages/notifications/package.json +++ b/packages/examples/packages/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/notification-example-snap", - "version": "2.1.0", + "version": "2.1.3", "description": "MetaMask example snap demonstrating the use of `snap_notify`.", "repository": { "type": "git", @@ -11,8 +11,7 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "snap.manifest.json", - "images/icon.svg" + "snap.manifest.json" ], "scripts": { "build": "mm-snap build", @@ -31,12 +30,11 @@ "lint:dependencies": "depcheck" }, "dependencies": { - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/notifications/snap.config.ts b/packages/examples/packages/notifications/snap.config.ts index becda5172b..63795ac6c1 100644 --- a/packages/examples/packages/notifications/snap.config.ts +++ b/packages/examples/packages/notifications/snap.config.ts @@ -1,7 +1,6 @@ import type { SnapConfig } from '@metamask/snaps-cli'; const config: SnapConfig = { - bundler: 'webpack', input: './src/index.ts', server: { port: 8016, @@ -9,9 +8,6 @@ const config: SnapConfig = { stats: { buffer: false, }, - polyfills: { - stream: true, - }, }; export default config; diff --git a/packages/examples/packages/notifications/snap.manifest.json b/packages/examples/packages/notifications/snap.manifest.json index 90a8ac9177..381e669c3a 100644 --- a/packages/examples/packages/notifications/snap.manifest.json +++ b/packages/examples/packages/notifications/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.0", + "version": "2.1.3", "description": "MetaMask example snap demonstrating the use of `snap_notify`.", "proposedName": "Notifications Example Snap", "repository": { @@ -7,11 +7,10 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "cp3fETtiBBloowVov0avNIkEq2zxQnyGq1ddqKfgjf4=", + "shasum": "DDhpMR4WnEVF6S+bUOAhBZhrMTrY877EtPFJH5/61EQ=", "location": { "npm": { "filePath": "dist/bundle.js", - "iconPath": "images/icon.svg", "packageName": "@metamask/notification-example-snap", "registry": "https://registry.npmjs.org/" } diff --git a/packages/examples/packages/notifications/src/index.test.ts b/packages/examples/packages/notifications/src/index.test.ts index f5289cdc23..3d046637ef 100644 --- a/packages/examples/packages/notifications/src/index.test.ts +++ b/packages/examples/packages/notifications/src/index.test.ts @@ -49,7 +49,7 @@ describe('onRpcRequest', () => { expect(response).toRespondWith(null); expect(response).toSendNotification( - 'Hello, Jest, from the browser!', + 'Hello from the browser!', NotificationType.Native, ); }); diff --git a/packages/examples/packages/notifications/src/index.ts b/packages/examples/packages/notifications/src/index.ts index 4ec8fdbb5d..69f4d45857 100644 --- a/packages/examples/packages/notifications/src/index.ts +++ b/packages/examples/packages/notifications/src/index.ts @@ -1,5 +1,4 @@ -import { rpcErrors } from '@metamask/rpc-errors'; -import { NotificationType } from '@metamask/snaps-sdk'; +import { MethodNotFoundError, NotificationType } from '@metamask/snaps-sdk'; import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; /** @@ -11,16 +10,12 @@ import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; * * @param params - The request parameters. * @param params.request - The JSON-RPC request object. - * @param params.origin - The origin of the dapp that sent the request. * @returns The JSON-RPC response. * @see https://docs.metamask.io/snaps/reference/exports/#onrpcrequest * @see https://docs.metamask.io/snaps/reference/rpc-api/#wallet_invokesnap * @see https://docs.metamask.io/snaps/reference/rpc-api/#snap_notify */ -export const onRpcRequest: OnRpcRequestHandler = async ({ - origin, - request, -}) => { +export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { switch (request.method) { case 'inApp': return await snap.request({ @@ -40,13 +35,11 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ // We're using the `NotificationType` enum here, but you can also use // the string values directly, e.g. `type: 'native'`. type: NotificationType.Native, - message: `Hello, ${origin}, from the browser!`, + message: `Hello from the browser!`, }, }); default: - throw rpcErrors.methodNotFound({ - data: { method: request.method }, - }); + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/rollup-plugin/CHANGELOG.md b/packages/examples/packages/rollup-plugin/CHANGELOG.md index 2fd190c58d..058418416f 100644 --- a/packages/examples/packages/rollup-plugin/CHANGELOG.md +++ b/packages/examples/packages/rollup-plugin/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.1.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1946](https://github.com/MetaMask/snaps/pull/1946)) @@ -34,7 +42,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/rollup-plugin-example-snap@2.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/rollup-plugin-example-snap@2.1.2...HEAD +[2.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/rollup-plugin-example-snap@2.1.1...@metamask/rollup-plugin-example-snap@2.1.2 +[2.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/rollup-plugin-example-snap@2.1.0...@metamask/rollup-plugin-example-snap@2.1.1 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/rollup-plugin-example-snap@2.0.1...@metamask/rollup-plugin-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/rollup-plugin-example-snap@2.0.0...@metamask/rollup-plugin-example-snap@2.0.1 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/rollup-plugin-example-snap@1.0.0...@metamask/rollup-plugin-example-snap@2.0.0 diff --git a/packages/examples/packages/rollup-plugin/images/icon.svg b/packages/examples/packages/rollup-plugin/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/rollup-plugin/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/rollup-plugin/jest.config.js b/packages/examples/packages/rollup-plugin/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/rollup-plugin/jest.config.js +++ b/packages/examples/packages/rollup-plugin/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/rollup-plugin/package.json b/packages/examples/packages/rollup-plugin/package.json index 594c2f4284..7597809f6a 100644 --- a/packages/examples/packages/rollup-plugin/package.json +++ b/packages/examples/packages/rollup-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/rollup-plugin-example-snap", - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating how to use the Rollup plugin to bundle a snap.", "repository": { "type": "git", @@ -11,7 +11,6 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "images/icon.svg", "snap.manifest.json" ], "scripts": { @@ -31,7 +30,6 @@ "lint:dependencies": "depcheck" }, "dependencies": { - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^" }, "devDependencies": { @@ -39,7 +37,7 @@ "@babel/preset-env": "^7.23.2", "@babel/preset-typescript": "^7.23.2", "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/rollup-plugin/snap.manifest.json b/packages/examples/packages/rollup-plugin/snap.manifest.json index 57b7c0dfac..6b1135ee9f 100644 --- a/packages/examples/packages/rollup-plugin/snap.manifest.json +++ b/packages/examples/packages/rollup-plugin/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating how to use the Rollup plugin to bundle a snap.", "proposedName": "Rollup Plugin Example Snap", "repository": { @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "WAdPaw8Bhb7PoPru3P/wGmOBLAxWvKr8Dj3l9eqvIx4=", + "shasum": "NOD8o7g47PyStwKZc/2yvDC82WRARyLJVX22CmwmXto=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/rollup-plugin/src/index.ts b/packages/examples/packages/rollup-plugin/src/index.ts index 4c03a59b17..6fa78c93c7 100644 --- a/packages/examples/packages/rollup-plugin/src/index.ts +++ b/packages/examples/packages/rollup-plugin/src/index.ts @@ -1,5 +1,7 @@ -import { rpcErrors } from '@metamask/rpc-errors'; -import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; +import { + MethodNotFoundError, + type OnRpcRequestHandler, +} from '@metamask/snaps-sdk'; /** * Handle incoming JSON-RPC requests from the dapp, sent through the @@ -20,10 +22,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { case 'hello': return 'Hello from Rollup!'; - default: { - throw rpcErrors.methodNotFound({ - data: { method: request.method }, - }); - } + default: + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/signature-insights/CHANGELOG.md b/packages/examples/packages/signature-insights/CHANGELOG.md index 25a5efe9be..2c000b5b8a 100644 --- a/packages/examples/packages/signature-insights/CHANGELOG.md +++ b/packages/examples/packages/signature-insights/CHANGELOG.md @@ -6,9 +6,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.0.2] +### Changed +- Re-release after multiple changes in the monorepo ([#2295](https://github.com/MetaMask/snaps/pull/2295)) + +## [1.0.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [1.0.0] ### Added - Add signature insights example ([#2114](https://github.com/MetaMask/snaps/pull/2079)) -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/signature-insights-example-snap@1.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/signature-insights-example-snap@1.0.2...HEAD +[1.0.2]: https://github.com/MetaMask/snaps/compare/@metamask/signature-insights-example-snap@1.0.1...@metamask/signature-insights-example-snap@1.0.2 +[1.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/signature-insights-example-snap@1.0.0...@metamask/signature-insights-example-snap@1.0.1 [1.0.0]: https://github.com/MetaMask/snaps/releases/tag/@metamask/signature-insights-example-snap@1.0.0 diff --git a/packages/examples/packages/signature-insights/images/icon.svg b/packages/examples/packages/signature-insights/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/signature-insights/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/signature-insights/jest.config.js b/packages/examples/packages/signature-insights/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/signature-insights/jest.config.js +++ b/packages/examples/packages/signature-insights/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/signature-insights/package.json b/packages/examples/packages/signature-insights/package.json index f3ed782fcd..f708f59e9e 100644 --- a/packages/examples/packages/signature-insights/package.json +++ b/packages/examples/packages/signature-insights/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/signature-insights-example-snap", - "version": "1.0.0", + "version": "1.0.2", "description": "MetaMask example snap demonstrating the use of the Signature Insights API.", "repository": { "type": "git", @@ -11,7 +11,6 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "images/icon.svg", "snap.manifest.json" ], "scripts": { @@ -35,7 +34,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/signature-insights/snap.config.ts b/packages/examples/packages/signature-insights/snap.config.ts index 32b0959504..1f4db03528 100644 --- a/packages/examples/packages/signature-insights/snap.config.ts +++ b/packages/examples/packages/signature-insights/snap.config.ts @@ -2,14 +2,10 @@ import type { SnapConfig } from '@metamask/snaps-cli'; import { resolve } from 'path'; const config: SnapConfig = { - bundler: 'webpack', input: resolve(__dirname, 'src/index.ts'), server: { port: 8017, }, - polyfills: { - stream: true, - }, stats: { buffer: false, }, diff --git a/packages/examples/packages/signature-insights/snap.manifest.json b/packages/examples/packages/signature-insights/snap.manifest.json index 38d1067975..bc89d67ced 100644 --- a/packages/examples/packages/signature-insights/snap.manifest.json +++ b/packages/examples/packages/signature-insights/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "1.0.0", + "version": "1.0.2", "description": "MetaMask example snap demonstrating the use of the Signature Insights API.", "proposedName": "Signature Insights Example Snap", "repository": { @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "5lRRSiPylWsWVHQCSuphKjtdZawf6/NceKn+XY2VWE4=", + "shasum": "3ksEl1GxJVWbtkGeXfHIF5OCu1uqialcTetE/3nmLrg=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/signature-insights/src/index.test.ts b/packages/examples/packages/signature-insights/src/index.test.ts index a616456486..0feddc4433 100644 --- a/packages/examples/packages/signature-insights/src/index.test.ts +++ b/packages/examples/packages/signature-insights/src/index.test.ts @@ -13,7 +13,9 @@ describe('onSignature', () => { data: '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0', }); - expect(response).toRender( + const screen = response.getInterface(); + + expect(screen).toRender( panel([ row('From:', text('0xd8da6bf26964af9d7eed9e03e53415d37aa96045')), row( diff --git a/packages/examples/packages/transaction-insights/CHANGELOG.md b/packages/examples/packages/transaction-insights/CHANGELOG.md index da3ca59f1a..8cc70eb16f 100644 --- a/packages/examples/packages/transaction-insights/CHANGELOG.md +++ b/packages/examples/packages/transaction-insights/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.2.2] +### Fixed +- Fix address validation in row component ([#2257](https://github.com/MetaMask/snaps/pull/2257)) + +## [2.2.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.2.0] ### Added - Use new `row` and `address` component ([#1968](https://github.com/MetaMask/snaps/pull/1968)) @@ -52,7 +60,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/insights-example-snap@2.2.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/insights-example-snap@2.2.2...HEAD +[2.2.2]: https://github.com/MetaMask/snaps/compare/@metamask/insights-example-snap@2.2.1...@metamask/insights-example-snap@2.2.2 +[2.2.1]: https://github.com/MetaMask/snaps/compare/@metamask/insights-example-snap@2.2.0...@metamask/insights-example-snap@2.2.1 [2.2.0]: https://github.com/MetaMask/snaps/compare/@metamask/insights-example-snap@2.1.0...@metamask/insights-example-snap@2.2.0 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/insights-example-snap@2.0.2...@metamask/insights-example-snap@2.1.0 [2.0.2]: https://github.com/MetaMask/snaps/compare/@metamask/insights-example-snap@2.0.1...@metamask/insights-example-snap@2.0.2 diff --git a/packages/examples/packages/transaction-insights/images/icon.svg b/packages/examples/packages/transaction-insights/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/transaction-insights/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/transaction-insights/jest.config.js b/packages/examples/packages/transaction-insights/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/transaction-insights/jest.config.js +++ b/packages/examples/packages/transaction-insights/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/transaction-insights/package.json b/packages/examples/packages/transaction-insights/package.json index 80560998e4..69f308fadb 100644 --- a/packages/examples/packages/transaction-insights/package.json +++ b/packages/examples/packages/transaction-insights/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/insights-example-snap", - "version": "2.2.0", + "version": "2.2.2", "description": "MetaMask example snap demonstrating the use of the Transaction Insights API.", "repository": { "type": "git", @@ -11,8 +11,7 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "snap.manifest.json", - "images/icon.svg" + "snap.manifest.json" ], "scripts": { "build": "mm-snap build", @@ -36,7 +35,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/transaction-insights/snap.config.ts b/packages/examples/packages/transaction-insights/snap.config.ts index 380d314e92..6efab33f2b 100644 --- a/packages/examples/packages/transaction-insights/snap.config.ts +++ b/packages/examples/packages/transaction-insights/snap.config.ts @@ -1,7 +1,6 @@ import type { SnapConfig } from '@metamask/snaps-cli'; const config: SnapConfig = { - bundler: 'webpack', input: './src/index.ts', server: { port: 8018, @@ -9,9 +8,6 @@ const config: SnapConfig = { stats: { buffer: false, }, - polyfills: { - stream: true, - }, }; export default config; diff --git a/packages/examples/packages/transaction-insights/snap.manifest.json b/packages/examples/packages/transaction-insights/snap.manifest.json index fb5703179a..3d00fd9215 100644 --- a/packages/examples/packages/transaction-insights/snap.manifest.json +++ b/packages/examples/packages/transaction-insights/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.2.0", + "version": "2.2.2", "description": "MetaMask example snap demonstrating the use of the Transaction Insights API.", "proposedName": "Insights Example Snap", "repository": { @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "avAfBrcz61u72O627FXzvoEcUNvzm0BQuPSr01R0tBk=", + "shasum": "bjeONcTKE7AQKC1lRgIPC/TusPoq1n4DCRtGT42MLck=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/transaction-insights/src/index.test.ts b/packages/examples/packages/transaction-insights/src/index.test.ts index f931497091..cb26ae56d2 100644 --- a/packages/examples/packages/transaction-insights/src/index.test.ts +++ b/packages/examples/packages/transaction-insights/src/index.test.ts @@ -17,7 +17,9 @@ describe('onTransaction', () => { data: '0xa9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', }); - expect(response).toRender( + const screen = response.getInterface(); + + expect(screen).toRender( panel([ row('From', address(FROM_ADDRESS)), row('To', address(TO_ADDRESS)), @@ -37,7 +39,9 @@ describe('onTransaction', () => { data: '0x23b872dd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', }); - expect(response).toRender( + const screen = response.getInterface(); + + expect(screen).toRender( panel([ row('From', address(FROM_ADDRESS)), row('To', address(TO_ADDRESS)), @@ -57,7 +61,9 @@ describe('onTransaction', () => { data: '0xf242432a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', }); - expect(response).toRender( + const screen = response.getInterface(); + + expect(screen).toRender( panel([ row('From', address(FROM_ADDRESS)), row('To', address(TO_ADDRESS)), @@ -75,7 +81,9 @@ describe('onTransaction', () => { data: '0xabcdef1200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', }); - expect(response).toRender( + const screen = response.getInterface(); + + expect(screen).toRender( panel([ row('From', address(FROM_ADDRESS)), row('To', address(TO_ADDRESS)), @@ -93,7 +101,9 @@ describe('onTransaction', () => { data: '0x', }); - expect(response).toRender( + const screen = response.getInterface(); + + expect(screen).toRender( panel([ row('From', address(FROM_ADDRESS)), row('To', address(TO_ADDRESS)), diff --git a/packages/examples/packages/wasm/CHANGELOG.md b/packages/examples/packages/wasm/CHANGELOG.md index f779b5e20d..3d13d5bd03 100644 --- a/packages/examples/packages/wasm/CHANGELOG.md +++ b/packages/examples/packages/wasm/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.3] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.1.2] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.1] ### Changed - Use synchronously initialized WASM ([#2024](https://github.com/MetaMask/snaps/pull/2024)) @@ -38,7 +46,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/wasm-example-snap@2.1.1...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/wasm-example-snap@2.1.3...HEAD +[2.1.3]: https://github.com/MetaMask/snaps/compare/@metamask/wasm-example-snap@2.1.2...@metamask/wasm-example-snap@2.1.3 +[2.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/wasm-example-snap@2.1.1...@metamask/wasm-example-snap@2.1.2 [2.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/wasm-example-snap@2.1.0...@metamask/wasm-example-snap@2.1.1 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/wasm-example-snap@2.0.1...@metamask/wasm-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/wasm-example-snap@2.0.0...@metamask/wasm-example-snap@2.0.1 diff --git a/packages/examples/packages/wasm/images/icon.svg b/packages/examples/packages/wasm/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/wasm/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/wasm/jest.config.js b/packages/examples/packages/wasm/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/wasm/jest.config.js +++ b/packages/examples/packages/wasm/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/wasm/package.json b/packages/examples/packages/wasm/package.json index 81f9e3f210..8d65f4736a 100644 --- a/packages/examples/packages/wasm/package.json +++ b/packages/examples/packages/wasm/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/wasm-example-snap", - "version": "2.1.1", + "version": "2.1.3", "description": "MetaMask example snap demonstrating the use of WebAssembly and the `endowment:webassembly` permission.", "repository": { "type": "git", @@ -11,8 +11,7 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "snap.manifest.json", - "images/icon.svg" + "snap.manifest.json" ], "scripts": { "build": "yarn build:wasm && mm-snap build", @@ -32,12 +31,11 @@ "lint:dependencies": "depcheck" }, "dependencies": { - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/wasm/snap.config.ts b/packages/examples/packages/wasm/snap.config.ts index 0f9781d355..f36b79fcee 100644 --- a/packages/examples/packages/wasm/snap.config.ts +++ b/packages/examples/packages/wasm/snap.config.ts @@ -2,7 +2,6 @@ import type { SnapConfig } from '@metamask/snaps-cli'; import { resolve } from 'path'; const config: SnapConfig = { - bundler: 'webpack', input: resolve(__dirname, 'src/index.ts'), server: { port: 8019, diff --git a/packages/examples/packages/wasm/snap.manifest.json b/packages/examples/packages/wasm/snap.manifest.json index 9a8ed2e789..b04a99ceaf 100644 --- a/packages/examples/packages/wasm/snap.manifest.json +++ b/packages/examples/packages/wasm/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.1", + "version": "2.1.3", "description": "MetaMask example snap demonstrating the use of WebAssembly and the `endowment:webassembly` permission.", "proposedName": "WebAssembly Example Snap", "repository": { @@ -7,11 +7,10 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "wnkrJ/PwH+MZlRboaJaJ91TTr2/7tJ+X40HbErUMqYY=", + "shasum": "AfJMAwhmPMfxswVoRr+wtYe381fuZ5yOeuk+LKJilF8=", "location": { "npm": { "filePath": "dist/bundle.js", - "iconPath": "images/icon.svg", "packageName": "@metamask/wasm-example-snap", "registry": "https://registry.npmjs.org/" } diff --git a/packages/examples/packages/wasm/src/index.ts b/packages/examples/packages/wasm/src/index.ts index 6ad9e5e6f8..10e34d79d6 100644 --- a/packages/examples/packages/wasm/src/index.ts +++ b/packages/examples/packages/wasm/src/index.ts @@ -1,5 +1,7 @@ -import { rpcErrors } from '@metamask/rpc-errors'; -import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; +import { + MethodNotFoundError, + type OnRpcRequestHandler, +} from '@metamask/snaps-sdk'; // This is only imported for its type. It is not used at runtime. // eslint-disable-next-line import/order @@ -48,5 +50,5 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { return program[method](...params); } - throw rpcErrors.methodNotFound({ data: { method } }); + throw new MethodNotFoundError({ method: request.method }); }; diff --git a/packages/examples/packages/webpack-plugin/CHANGELOG.md b/packages/examples/packages/webpack-plugin/CHANGELOG.md index 88228b6cf3..b92ae4aeab 100644 --- a/packages/examples/packages/webpack-plugin/CHANGELOG.md +++ b/packages/examples/packages/webpack-plugin/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.2] +### Changed +- Use error wrappers ([#2178](https://github.com/MetaMask/snaps/pull/2178)) + +## [2.1.1] +### Changed +- Remove snap icon ([#2189](https://github.com/MetaMask/snaps/pull/2189)) + ## [2.1.0] ### Changed - Use `@metamask/snaps-sdk` package ([#1946](https://github.com/MetaMask/snaps/pull/1946)) @@ -34,7 +42,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/webpack-plugin-example-snap@2.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/webpack-plugin-example-snap@2.1.2...HEAD +[2.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/webpack-plugin-example-snap@2.1.1...@metamask/webpack-plugin-example-snap@2.1.2 +[2.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/webpack-plugin-example-snap@2.1.0...@metamask/webpack-plugin-example-snap@2.1.1 [2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/webpack-plugin-example-snap@2.0.1...@metamask/webpack-plugin-example-snap@2.1.0 [2.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/webpack-plugin-example-snap@2.0.0...@metamask/webpack-plugin-example-snap@2.0.1 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/webpack-plugin-example-snap@1.0.0...@metamask/webpack-plugin-example-snap@2.0.0 diff --git a/packages/examples/packages/webpack-plugin/images/icon.svg b/packages/examples/packages/webpack-plugin/images/icon.svg deleted file mode 100644 index f555e1ea73..0000000000 --- a/packages/examples/packages/webpack-plugin/images/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/examples/packages/webpack-plugin/jest.config.js b/packages/examples/packages/webpack-plugin/jest.config.js index 270869ba84..071fd27bdc 100644 --- a/packages/examples/packages/webpack-plugin/jest.config.js +++ b/packages/examples/packages/webpack-plugin/jest.config.js @@ -12,6 +12,11 @@ module.exports = deepmerge(baseConfig, { // This is required for the tests to run inside the `MetaMask/snaps` // repository. You don't need this in your own project. moduleNameMapper: { + '^@metamask/(.+)/node$': [ + '/../../../$1/src/node', + '/../../../../node_modules/@metamask/$1/node', + '/node_modules/@metamask/$1/node', + ], '^@metamask/(.+)$': [ '/../../../$1/src', '/../../../../node_modules/@metamask/$1', diff --git a/packages/examples/packages/webpack-plugin/package.json b/packages/examples/packages/webpack-plugin/package.json index d7b99b7433..b86061b8a0 100644 --- a/packages/examples/packages/webpack-plugin/package.json +++ b/packages/examples/packages/webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/webpack-plugin-example-snap", - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating how to use the Webpack plugin to bundle a snap.", "repository": { "type": "git", @@ -11,7 +11,6 @@ "main": "./dist/bundle.js", "files": [ "dist/", - "images/icon.svg", "snap.manifest.json" ], "scripts": { @@ -31,12 +30,11 @@ "lint:dependencies": "depcheck" }, "dependencies": { - "@metamask/rpc-errors": "^6.1.0", "@metamask/snaps-sdk": "workspace:^" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", diff --git a/packages/examples/packages/webpack-plugin/snap.manifest.json b/packages/examples/packages/webpack-plugin/snap.manifest.json index b849fba43c..37bd0b17dd 100644 --- a/packages/examples/packages/webpack-plugin/snap.manifest.json +++ b/packages/examples/packages/webpack-plugin/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.1.0", + "version": "2.1.2", "description": "MetaMask example snap demonstrating how to use the Webpack plugin to bundle a snap.", "proposedName": "Webpack Plugin Example Snap", "repository": { @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "EEZaVbtatO8bPPoFPohQkBXr8LLl88pR+h/HVdf8eV4=", + "shasum": "vQlLJfewN8oCDFFtJ660My9bphqC8Q8QXeWG9GNzxX8=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/webpack-plugin/src/index.ts b/packages/examples/packages/webpack-plugin/src/index.ts index 01148acbad..d8cf691baf 100644 --- a/packages/examples/packages/webpack-plugin/src/index.ts +++ b/packages/examples/packages/webpack-plugin/src/index.ts @@ -1,5 +1,7 @@ -import { rpcErrors } from '@metamask/rpc-errors'; -import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; +import { + MethodNotFoundError, + type OnRpcRequestHandler, +} from '@metamask/snaps-sdk'; /** * Handle incoming JSON-RPC requests from the dapp, sent through the @@ -20,10 +22,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { case 'hello': return 'Hello from Webpack!'; - default: { - throw rpcErrors.methodNotFound({ - data: { method: request.method }, - }); - } + default: + throw new MethodNotFoundError({ method: request.method }); } }; diff --git a/packages/examples/packages/webpack-plugin/webpack.config.ts b/packages/examples/packages/webpack-plugin/webpack.config.ts index 95413ae17f..d529f11605 100644 --- a/packages/examples/packages/webpack-plugin/webpack.config.ts +++ b/packages/examples/packages/webpack-plugin/webpack.config.ts @@ -35,7 +35,7 @@ const config: Configuration = { minimize: true, minimizer: [ new TerserPlugin({ - minify: TerserPlugin.swcMinify, + parallel: true, }), ], }, diff --git a/packages/snaps-browserify-plugin/CHANGELOG.md b/packages/snaps-browserify-plugin/CHANGELOG.md index 17f72dfa5b..89351faca6 100644 --- a/packages/snaps-browserify-plugin/CHANGELOG.md +++ b/packages/snaps-browserify-plugin/CHANGELOG.md @@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [4.0.1] +### Fixed +- Fix minor build configuration problems ([#2220](https://github.com/MetaMask/snaps/pull/2220)) + +## [4.0.0] +### Changed +- **BREAKING:** Update ESM build to be fully compliant with the ESM standard ([#2210](https://github.com/MetaMask/snaps/pull/2210)) + +## [3.0.3] +### Changed +- Bump several MetaMask dependencies ([#2054](https://github.com/MetaMask/snaps/pull/2054), [#2100](https://github.com/MetaMask/snaps/pull/2100), [#2105](https://github.com/MetaMask/snaps/pull/2105), [#2173](https://github.com/MetaMask/snaps/pull/2173)) + ## [3.0.2] ### Changed - Bump several MetaMask dependencies ([#1964](https://github.com/MetaMask/snaps/pull/1964)) @@ -32,7 +44,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-browserify-plugin@3.0.2...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-browserify-plugin@4.0.1...HEAD +[4.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-browserify-plugin@4.0.0...@metamask/snaps-browserify-plugin@4.0.1 +[4.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-browserify-plugin@3.0.3...@metamask/snaps-browserify-plugin@4.0.0 +[3.0.3]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-browserify-plugin@3.0.2...@metamask/snaps-browserify-plugin@3.0.3 [3.0.2]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-browserify-plugin@3.0.1...@metamask/snaps-browserify-plugin@3.0.2 [3.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-browserify-plugin@3.0.0...@metamask/snaps-browserify-plugin@3.0.1 [3.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-browserify-plugin@2.0.0...@metamask/snaps-browserify-plugin@3.0.0 diff --git a/packages/snaps-browserify-plugin/package.json b/packages/snaps-browserify-plugin/package.json index 0dfccdf9c6..1d04a207b8 100644 --- a/packages/snaps-browserify-plugin/package.json +++ b/packages/snaps-browserify-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/snaps-browserify-plugin", - "version": "3.0.2", + "version": "4.0.1", "keywords": [ "browserify-plugin" ], @@ -9,13 +9,19 @@ "url": "https://github.com/MetaMask/snaps.git" }, "sideEffects": false, - "main": "./dist/cjs/index.js", - "module": "./dist/esm/index.js", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/types/index.d.ts" + }, + "./package.json": "./package.json" + }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", "types": "./dist/types/index.d.ts", "files": [ - "dist/cjs/**", - "dist/esm/**", - "dist/types/**" + "dist" ], "scripts": { "test": "jest && yarn posttest", @@ -26,12 +32,9 @@ "lint": "yarn lint:eslint && yarn lint:misc --check && yarn lint:changelog && yarn lint:dependencies", "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write", "lint:changelog": "../../scripts/validate-changelog.sh @metamask/snaps-browserify-plugin", - "build": "yarn build:source && yarn build:types", - "build:source": "yarn build:esm && yarn build:cjs", + "build": "tsup --clean && yarn build:types", "build:types": "tsc --project tsconfig.build.json", - "build:esm": "swc src --out-dir dist/esm --config-file ../../.swcrc.build.json --config module.type=es6", - "build:cjs": "swc src --out-dir dist/cjs --config-file ../../.swcrc.build.json --config module.type=commonjs", - "build:clean": "yarn clean && yarn build", + "build:ci": "tsup --clean", "clean": "rimraf '*.tsbuildinfo' 'dist'", "publish:preview": "yarn npm publish --tag preview", "lint:ci": "yarn lint", @@ -43,13 +46,12 @@ "readable-stream": "^3.6.2" }, "devDependencies": { - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", "@metamask/eslint-config-nodejs": "^12.1.0", "@metamask/eslint-config-typescript": "^12.1.0", - "@swc/cli": "^0.1.62", "@swc/core": "1.3.78", "@swc/jest": "^0.2.26", "@types/browserify": "^12.0.37", @@ -75,6 +77,7 @@ "prettier": "^2.7.1", "prettier-plugin-packagejson": "^2.2.11", "rimraf": "^4.1.2", + "tsup": "^8.0.1", "typescript": "~4.8.4" }, "engines": { diff --git a/packages/snaps-browserify-plugin/src/plugin.test.ts b/packages/snaps-browserify-plugin/src/plugin.test.ts index fef1529b26..b0e5f4d676 100644 --- a/packages/snaps-browserify-plugin/src/plugin.test.ts +++ b/packages/snaps-browserify-plugin/src/plugin.test.ts @@ -6,7 +6,7 @@ import { evalBundle, logWarning, PostProcessWarning, -} from '@metamask/snaps-utils'; +} from '@metamask/snaps-utils/node'; import { DEFAULT_SNAP_BUNDLE, getSnapManifest, @@ -23,8 +23,8 @@ import plugin, { SnapsBrowserifyTransform } from './plugin'; jest.mock('fs'); -jest.mock('@metamask/snaps-utils', () => ({ - ...jest.requireActual('@metamask/snaps-utils'), +jest.mock('@metamask/snaps-utils/node', () => ({ + ...jest.requireActual('@metamask/snaps-utils/node'), evalBundle: jest.fn(), checkManifest: jest.fn(), logWarning: jest.fn(), diff --git a/packages/snaps-browserify-plugin/src/plugin.ts b/packages/snaps-browserify-plugin/src/plugin.ts index ea44aeb090..d04ee8fb15 100644 --- a/packages/snaps-browserify-plugin/src/plugin.ts +++ b/packages/snaps-browserify-plugin/src/plugin.ts @@ -1,11 +1,11 @@ -import type { PostProcessOptions } from '@metamask/snaps-utils'; +import type { PostProcessOptions } from '@metamask/snaps-utils/node'; import { checkManifest, evalBundle, logWarning, postProcessBundle, useTemporaryFile, -} from '@metamask/snaps-utils'; +} from '@metamask/snaps-utils/node'; import type { BrowserifyObject } from 'browserify'; import { fromSource } from 'convert-source-map'; import pathUtils from 'path'; diff --git a/packages/snaps-browserify-plugin/tsconfig.json b/packages/snaps-browserify-plugin/tsconfig.json index 4a3ba29e4e..29291d0908 100644 --- a/packages/snaps-browserify-plugin/tsconfig.json +++ b/packages/snaps-browserify-plugin/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "baseUrl": "./" }, - "include": ["./src"], + "include": ["./src", "package.json", "tsup.config.ts"], "references": [ { "path": "../snaps-utils" diff --git a/packages/snaps-browserify-plugin/tsup.config.ts b/packages/snaps-browserify-plugin/tsup.config.ts new file mode 100644 index 0000000000..3eaf645296 --- /dev/null +++ b/packages/snaps-browserify-plugin/tsup.config.ts @@ -0,0 +1,14 @@ +import deepmerge from 'deepmerge'; +import type { Options } from 'tsup'; + +import packageJson from './package.json'; + +// `tsup.config.ts` is not under `rootDir`, so we need to use `require` instead. +// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires +const { default: baseConfig } = require('../../tsup.config'); + +const config: Options = { + name: packageJson.name, +}; + +export default deepmerge(baseConfig, config); diff --git a/packages/snaps-cli/CHANGELOG.md b/packages/snaps-cli/CHANGELOG.md index 815b039a8c..2fd79fe9a4 100644 --- a/packages/snaps-cli/CHANGELOG.md +++ b/packages/snaps-cli/CHANGELOG.md @@ -6,6 +6,55 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [6.1.1] +### Fixed +- Disable `topLevelAwait` configuration option ([#2358](https://github.com/MetaMask/snaps/pull/2358)) + - Before this the CLI would produce invalid builds when using top-level await. + +## [6.1.0] +### Added +- Add support for importing SVG, PNG, and JPEG files directly ([#2284](https://github.com/MetaMask/snaps/pull/2284)) + - You can now import these files using a regular import declaration when using the Webpack-based config. + - To opt out of this feature (i.e., to use custom image loading logic), add the following to your config: + ```ts + { + features: { + images: false, + }, + } + ``` + +### Changed +- Update CLI docs link ([#2294](https://github.com/MetaMask/snaps/pull/2294)) + +### Fixed +- Fix detection of minimum Node.js version ([#2292](https://github.com/MetaMask/snaps/pull/2292)) + +## [6.0.2] +### Fixed +- Publish `.browserslistrc` ([#2227](https://github.com/MetaMask/snaps/pull/2227)) + +## [6.0.1] +### Fixed +- Fix minor build configuration problems ([#2220](https://github.com/MetaMask/snaps/pull/2220)) + +## [6.0.0] +### Changed +- **BREAKING:** Update ESM build to be fully compliant with the ESM standard ([#2210](https://github.com/MetaMask/snaps/pull/2210)) +- **BREAKING:** Change config to use Webpack by default ([#2214](https://github.com/MetaMask/snaps/pull/2214)) + - You can still use Browserify by specifying `bundler: 'browserify'`. + +## [5.1.1] +### Fixed +- Support new lines in CLI message formatting ([#2194](https://github.com/MetaMask/snaps/pull/2194)) + +## [5.1.0] +### Changed +- Optimize CLI Webpack configuration ([#2175](https://github.com/MetaMask/snaps/pull/2175)) + - This can reduce the size of Snaps in certain cases. +- Show Webpack compilation warnings in CLI ([#2186](https://github.com/MetaMask/snaps/pull/2186), [#2192](https://github.com/MetaMask/snaps/pull/2192)) +- Add a warning when no icon is found and when icon is not square ([#2185](https://github.com/MetaMask/snaps/pull/2185)) + ## [5.0.0] ### Changed - **BREAKING:** Disable source maps by default ([#2166](https://github.com/MetaMask/snaps/pull/2166)) @@ -102,7 +151,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@5.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@6.1.1...HEAD +[6.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@6.1.0...@metamask/snaps-cli@6.1.1 +[6.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@6.0.2...@metamask/snaps-cli@6.1.0 +[6.0.2]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@6.0.1...@metamask/snaps-cli@6.0.2 +[6.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@6.0.0...@metamask/snaps-cli@6.0.1 +[6.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@5.1.1...@metamask/snaps-cli@6.0.0 +[5.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@5.1.0...@metamask/snaps-cli@5.1.1 +[5.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@5.0.0...@metamask/snaps-cli@5.1.0 [5.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@4.0.1...@metamask/snaps-cli@5.0.0 [4.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@4.0.0...@metamask/snaps-cli@4.0.1 [4.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@3.0.5...@metamask/snaps-cli@4.0.0 diff --git a/packages/snaps-cli/package.json b/packages/snaps-cli/package.json index 7208332e4b..a99c7e8152 100644 --- a/packages/snaps-cli/package.json +++ b/packages/snaps-cli/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/snaps-cli", - "version": "5.0.0", + "version": "6.1.1", "description": "A CLI for developing MetaMask Snaps.", "repository": { "type": "git", @@ -8,28 +8,30 @@ }, "license": "ISC", "sideEffects": false, - "main": "./dist/cjs/index.js", - "module": "./dist/esm/index.js", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/types/index.d.ts" + }, + "./package.json": "./package.json", + "./browserslistrc": "./.browserslistrc" + }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", "types": "./dist/types/index.d.ts", "bin": { - "mm-snap": "./dist/cjs/main.js" + "mm-snap": "./dist/main.js" }, "files": [ - "dist/cjs/**", - "dist/esm/**", - "dist/types/**", + "dist", ".browserslistrc" ], "scripts": { - "build": "yarn build:source && yarn build:types", - "build:source": "yarn build:esm && yarn build:cjs", + "build": "tsup --clean && yarn build:types && yarn build:chmod && yarn build:readme", "build:types": "tsc --project tsconfig.build.json", - "build:esm": "swc src --out-dir dist/esm --config-file ../../.swcrc.build.json --config module.type=es6", - "build:cjs": "swc src --out-dir dist/cjs --config-file ../../.swcrc.build.json --config module.type=commonjs", - "build:post-tsc": "yarn build:chmod && yarn build:readme", - "build:chmod": "chmod +x ./dist/esm/main.js && chmod +x ./dist/cjs/main.js", + "build:chmod": "chmod +x ./dist/main.mjs && chmod +x ./dist/main.js", "build:readme": "node ./scripts/updateReadme.js", - "build:clean": "yarn clean && yarn build", "build:watch": "tsc-watch --onSuccess 'yarn build:chmod'", "clean": "rimraf '*.tsbuildinfo' 'dist'", "test": "jest --runInBand && yarn posttest", @@ -43,7 +45,8 @@ "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write", "publish:preview": "yarn npm publish --tag preview", "lint:ci": "yarn lint", - "lint:dependencies": "depcheck" + "lint:dependencies": "depcheck", + "build:ci": "tsup --clean" }, "dependencies": { "@babel/core": "^7.23.2", @@ -65,7 +68,6 @@ "browserify-zlib": "^0.2.0", "buffer": "^6.0.3", "chalk": "^4.1.2", - "chokidar": "^3.5.2", "console-browserify": "^1.2.0", "constants-browserify": "^1.0.0", "crypto-browserify": "^3.12.0", @@ -84,6 +86,7 @@ "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "string_decoder": "^1.3.0", + "strip-ansi": "^6.0.1", "superstruct": "^1.0.3", "swc-loader": "^0.2.3", "terser-webpack-plugin": "^5.3.9", @@ -97,13 +100,12 @@ "yargs": "^17.7.1" }, "devDependencies": { - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", "@metamask/eslint-config-nodejs": "^12.1.0", "@metamask/eslint-config-typescript": "^12.1.0", - "@swc/cli": "^0.1.62", "@swc/jest": "^0.2.26", "@types/browserify": "^12.0.37", "@types/jest": "^27.5.1", @@ -133,6 +135,7 @@ "rimraf": "^4.1.2", "ts-node": "^10.9.1", "tsc-watch": "^4.5.0", + "tsup": "^8.0.1", "typescript": "~4.8.4" }, "engines": { diff --git a/packages/snaps-cli/scripts/updateReadme.js b/packages/snaps-cli/scripts/updateReadme.js index 87fb19c1b8..69b289fa24 100644 --- a/packages/snaps-cli/scripts/updateReadme.js +++ b/packages/snaps-cli/scripts/updateReadme.js @@ -13,7 +13,7 @@ main(); * `mm-snap --help`. */ async function main() { - const binPath = path.join(__dirname, '../dist/cjs/main.js'); + const binPath = path.join(__dirname, '../dist/main.js'); const readmePath = path.join(__dirname, '../README.md'); const currentReadme = await fs.readFile(readmePath, 'utf8'); diff --git a/packages/snaps-cli/src/__fixtures__/configs/cjs.ts b/packages/snaps-cli/src/__fixtures__/configs/cjs.ts index ad33a5474f..038fbdcc5c 100644 --- a/packages/snaps-cli/src/__fixtures__/configs/cjs.ts +++ b/packages/snaps-cli/src/__fixtures__/configs/cjs.ts @@ -4,7 +4,6 @@ import type { SnapConfig } from '../../config'; const config: SnapConfig = { - bundler: 'webpack', input: 'src/index.ts', }; diff --git a/packages/snaps-cli/src/__fixtures__/configs/esm.ts b/packages/snaps-cli/src/__fixtures__/configs/esm.ts index e68687dbba..d576e3b8ec 100644 --- a/packages/snaps-cli/src/__fixtures__/configs/esm.ts +++ b/packages/snaps-cli/src/__fixtures__/configs/esm.ts @@ -1,7 +1,6 @@ import type { SnapConfig } from '../../config'; const config: SnapConfig = { - bundler: 'webpack', input: 'src/index.ts', }; diff --git a/packages/snaps-cli/src/__fixtures__/configs/invalid.ts b/packages/snaps-cli/src/__fixtures__/configs/invalid.ts index fdc7dd71b3..980b725e4e 100644 --- a/packages/snaps-cli/src/__fixtures__/configs/invalid.ts +++ b/packages/snaps-cli/src/__fixtures__/configs/invalid.ts @@ -1,7 +1,6 @@ import type { SnapConfig } from '../../config'; const config: SnapConfig = { - bundler: 'webpack', // @ts-expect-error - Invalid option. foo: 'bar', }; diff --git a/packages/snaps-cli/src/__fixtures__/configs/typescript/snap.config.ts b/packages/snaps-cli/src/__fixtures__/configs/typescript/snap.config.ts index 578b71446f..b463a6dcab 100644 --- a/packages/snaps-cli/src/__fixtures__/configs/typescript/snap.config.ts +++ b/packages/snaps-cli/src/__fixtures__/configs/typescript/snap.config.ts @@ -1,7 +1,6 @@ import type { SnapConfig } from '../../../config'; const config: SnapConfig = { - bundler: 'webpack', input: 'src/index.ts', }; diff --git a/packages/snaps-cli/src/cli.test.ts b/packages/snaps-cli/src/cli.test.ts index 01feb9602c..b564694f6b 100644 --- a/packages/snaps-cli/src/cli.test.ts +++ b/packages/snaps-cli/src/cli.test.ts @@ -28,7 +28,7 @@ const getMockArgv = (...args: string[]) => { const HELP_TEXT_REGEX = /^\s*Usage: .+ \[options\]/u; describe('checkNodeVersion', () => { - it.each(['16.17.0', '16.18.0', '18.6.0', '18.7.0', '20.0.0'])( + it.each(['18.16.0', '18.17.0', '20.0.0'])( 'does not exit if the Node version is %s', (version) => { const spy = jest.spyOn(process, 'exit').mockImplementation(); @@ -38,8 +38,8 @@ describe('checkNodeVersion', () => { }, ); - it.each(['14.0.0', '16.0.0', '16.16.1'])( - 'logs a message and exists if the Node version is %s', + it.each(['14.0.0', '16.0.0', '16.16.1', '18.0.0', '18.5.0'])( + 'logs a message and exits if the Node version is %s', (version) => { const spy = jest.spyOn(process, 'exit').mockImplementation(); const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); @@ -51,26 +51,7 @@ describe('checkNodeVersion', () => { expect(consoleErrorSpy).toHaveBeenCalledTimes(1); expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringContaining( - `Node version ${version} is not supported. Please use Node 16.17.0 or later.`, - ), - ); - }, - ); - - it.each(['18.0.0', '18.5.0'])( - 'logs a message and exists if the Node version is %s', - (version) => { - const spy = jest.spyOn(process, 'exit').mockImplementation(); - const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); - - checkNodeVersion(version); - - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledWith(1); - expect(consoleErrorSpy).toHaveBeenCalledTimes(1); - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining( - `Node version ${version} is not supported. Please use Node 18.6.0 or later.`, + `Node version ${version} is not supported. Please use Node 18.16.0 or later.`, ), ); }, diff --git a/packages/snaps-cli/src/cli.ts b/packages/snaps-cli/src/cli.ts old mode 100755 new mode 100644 index 0a8b6567f8..180c37ed20 --- a/packages/snaps-cli/src/cli.ts +++ b/packages/snaps-cli/src/cli.ts @@ -1,3 +1,6 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import packageJson from '@metamask/snaps-cli/package.json'; +import type { SemVer } from 'semver'; import semver from 'semver'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; @@ -6,9 +9,6 @@ import builders from './builders'; import { getConfigByArgv } from './config'; import { error, getYargsErrorMessage, sanitizeInputs } from './utils'; -const MINIMUM_NODE_16_VERSION = '16.17.0'; -const MINIMUM_NODE_18_VERSION = '18.6.0'; - /** * Check the Node version. If the Node version is less than the minimum required * version, this logs an error and exits the process. @@ -18,26 +18,12 @@ const MINIMUM_NODE_18_VERSION = '18.6.0'; export function checkNodeVersion( nodeVersion: string = process.version.slice(1), ) { - const majorVersion = semver.major(nodeVersion); - const message = `Node version ${nodeVersion} is not supported. Please use Node ${MINIMUM_NODE_16_VERSION} or later.`; - - if (majorVersion < 16) { - error(message); - // eslint-disable-next-line n/no-process-exit - process.exit(1); - } - - // Node 16 and 18 have a different minimum version requirement, so we need to - // check for both. - if (majorVersion === 16 && semver.lt(nodeVersion, MINIMUM_NODE_16_VERSION)) { - error(message); - // eslint-disable-next-line n/no-process-exit - process.exit(1); - } + const versionRange = packageJson.engines.node; + const minimumVersion = (semver.minVersion(versionRange) as SemVer).format(); - if (majorVersion === 18 && semver.lt(nodeVersion, MINIMUM_NODE_18_VERSION)) { + if (!semver.satisfies(nodeVersion, versionRange)) { error( - `Node version ${nodeVersion} is not supported. Please use Node ${MINIMUM_NODE_18_VERSION} or later.`, + `Node version ${nodeVersion} is not supported. Please use Node ${minimumVersion} or later.`, ); // eslint-disable-next-line n/no-process-exit process.exit(1); diff --git a/packages/snaps-cli/src/commands/build/build.e2e.test.ts b/packages/snaps-cli/src/commands/build/build.e2e.test.ts index 2e305fed6c..ebde010bcc 100644 --- a/packages/snaps-cli/src/commands/build/build.e2e.test.ts +++ b/packages/snaps-cli/src/commands/build/build.e2e.test.ts @@ -32,15 +32,21 @@ describe('mm-snap build', () => { runner = getCommandRunner(command, []); await runner.wait(); - expect(runner.stderr).toStrictEqual([]); expect(runner.stdout).toContainEqual( expect.stringMatching(/Checking the input file\./u), ); expect(runner.stdout).toContainEqual( expect.stringMatching(/Building the snap bundle\./u), ); - expect(runner.stdout).toContainEqual( - expect.stringMatching(/Compiled \d+ files? in \d+ms\./u), + expect(runner.stderr).toContainEqual( + expect.stringMatching( + /Compiled \d+ files? in \d+ms with \d+ warnings?\./u, + ), + ); + expect(runner.stderr).toContainEqual( + expect.stringContaining( + 'No icon found in the Snap manifest. It is recommended to include an icon for the Snap. See https://docs.metamask.io/snaps/how-to/design-a-snap/#guidelines-at-a-glance for more information.', + ), ); expect(runner.stdout).toContainEqual( expect.stringMatching(/Evaluating the snap bundle\./u), @@ -55,15 +61,21 @@ describe('mm-snap build', () => { runner = getCommandRunner(command, [], SNAP_BROWSERIFY_DIR); await runner.wait(); - expect(runner.stderr).toStrictEqual([]); expect(runner.stdout).toContainEqual( expect.stringMatching(/Checking the input file\./u), ); expect(runner.stdout).toContainEqual( expect.stringMatching(/Building the snap bundle\./u), ); - expect(runner.stdout).toContainEqual( - expect.stringMatching(/Compiled \d+ files? in \d+ms\./u), + expect(runner.stderr).toContainEqual( + expect.stringMatching( + /Compiled \d+ files? in \d+ms with \d+ warnings?\./u, + ), + ); + expect(runner.stderr).toContainEqual( + expect.stringContaining( + 'No icon found in the Snap manifest. It is recommended to include an icon for the Snap. See https://docs.metamask.io/snaps/how-to/design-a-snap/#guidelines-at-a-glance for more information.', + ), ); expect(runner.stdout).toContainEqual( expect.stringMatching(/Evaluating the snap bundle\./u), diff --git a/packages/snaps-cli/src/commands/build/build.ts b/packages/snaps-cli/src/commands/build/build.ts index 1c43d9e358..e4166f98c2 100644 --- a/packages/snaps-cli/src/commands/build/build.ts +++ b/packages/snaps-cli/src/commands/build/build.ts @@ -1,4 +1,4 @@ -import { isFile } from '@metamask/snaps-utils'; +import { isFile } from '@metamask/snaps-utils/node'; import { resolve as pathResolve } from 'path'; import type { ProcessedConfig, ProcessedWebpackConfig } from '../../config'; diff --git a/packages/snaps-cli/src/commands/eval/eval.ts b/packages/snaps-cli/src/commands/eval/eval.ts index 928761b450..b82f7906a0 100644 --- a/packages/snaps-cli/src/commands/eval/eval.ts +++ b/packages/snaps-cli/src/commands/eval/eval.ts @@ -1,4 +1,4 @@ -import { isFile } from '@metamask/snaps-utils'; +import { isFile } from '@metamask/snaps-utils/node'; import { resolve } from 'path'; import type { ProcessedConfig } from '../../config'; diff --git a/packages/snaps-cli/src/commands/eval/implementation.test.ts b/packages/snaps-cli/src/commands/eval/implementation.test.ts index 259ac1beea..c3a73150ef 100644 --- a/packages/snaps-cli/src/commands/eval/implementation.test.ts +++ b/packages/snaps-cli/src/commands/eval/implementation.test.ts @@ -1,5 +1,5 @@ -import * as utils from '@metamask/snaps-utils'; -import { SnapEvalError } from '@metamask/snaps-utils'; +import * as utils from '@metamask/snaps-utils/node'; +import { SnapEvalError } from '@metamask/snaps-utils/node'; import { DEFAULT_SNAP_BUNDLE } from '@metamask/snaps-utils/test-utils'; import normalFs from 'fs'; import { dirname, join } from 'path'; @@ -10,11 +10,13 @@ const { promises: fs } = normalFs; jest.mock('fs'); -jest.mock('@metamask/snaps-utils', () => ({ - ...jest.requireActual('@metamask/snaps-utils'), +jest.mock('@metamask/snaps-utils/node', () => ({ + ...jest.requireActual('@metamask/snaps-utils/node'), evalBundle: jest .fn() - .mockImplementation(jest.requireActual('@metamask/snaps-utils').evalBundle), + .mockImplementation( + jest.requireActual('@metamask/snaps-utils/node').evalBundle, + ), })); describe('evaluate', () => { diff --git a/packages/snaps-cli/src/commands/eval/implementation.ts b/packages/snaps-cli/src/commands/eval/implementation.ts index 901dc4c1aa..7db18e2d50 100644 --- a/packages/snaps-cli/src/commands/eval/implementation.ts +++ b/packages/snaps-cli/src/commands/eval/implementation.ts @@ -1,4 +1,4 @@ -import { evalBundle, SnapEvalError, indent } from '@metamask/snaps-utils'; +import { evalBundle, SnapEvalError, indent } from '@metamask/snaps-utils/node'; import { red } from 'chalk'; import { CommandError } from '../../errors'; diff --git a/packages/snaps-cli/src/commands/manifest/implementation.ts b/packages/snaps-cli/src/commands/manifest/implementation.ts index ce7a8d888d..8b98ca3af8 100644 --- a/packages/snaps-cli/src/commands/manifest/implementation.ts +++ b/packages/snaps-cli/src/commands/manifest/implementation.ts @@ -1,4 +1,4 @@ -import { checkManifest, indent } from '@metamask/snaps-utils'; +import { checkManifest, indent } from '@metamask/snaps-utils/node'; import { red, yellow } from 'chalk'; import type { Ora } from 'ora'; import { dirname } from 'path'; diff --git a/packages/snaps-cli/src/commands/manifest/manifest.e2e.test.ts b/packages/snaps-cli/src/commands/manifest/manifest.e2e.test.ts index 6adcecf0bb..9c650b1e25 100644 --- a/packages/snaps-cli/src/commands/manifest/manifest.e2e.test.ts +++ b/packages/snaps-cli/src/commands/manifest/manifest.e2e.test.ts @@ -14,15 +14,19 @@ describe('mm-snap manifest', () => { runner = getCommandRunner(command, []); await runner.wait(); - expect(runner.stderr).toStrictEqual([]); expect(runner.stdout).toContainEqual( expect.stringMatching(/Checking the input file\./u), ); expect(runner.stdout).toContainEqual( expect.stringMatching(/Validating the snap manifest\./u), ); - expect(runner.stdout).toContainEqual( - expect.stringMatching(/The snap manifest file is valid\./u), + expect(runner.stderr).toContainEqual( + expect.stringMatching(/The snap manifest file has warnings\./u), + ); + expect(runner.stderr).toContainEqual( + expect.stringContaining( + 'No icon found in the Snap manifest. It is recommended to include an icon for the Snap. See https://docs.metamask.io/snaps/how-to/design-a-snap/#guidelines-at-a-glance for more information.', + ), ); expect(runner.exitCode).toBe(0); }, diff --git a/packages/snaps-cli/src/commands/manifest/manifest.ts b/packages/snaps-cli/src/commands/manifest/manifest.ts index 926024b5a3..dd9bd7ff4c 100644 --- a/packages/snaps-cli/src/commands/manifest/manifest.ts +++ b/packages/snaps-cli/src/commands/manifest/manifest.ts @@ -1,4 +1,4 @@ -import { isFile } from '@metamask/snaps-utils'; +import { isFile } from '@metamask/snaps-utils/node'; import type { ProcessedConfig } from '../../config'; import { CommandError } from '../../errors'; diff --git a/packages/snaps-cli/src/commands/watch/watch.e2e.test.ts b/packages/snaps-cli/src/commands/watch/watch.e2e.test.ts index 9af1fcef43..06d75fc10d 100644 --- a/packages/snaps-cli/src/commands/watch/watch.e2e.test.ts +++ b/packages/snaps-cli/src/commands/watch/watch.e2e.test.ts @@ -25,13 +25,16 @@ describe('mm-snap watch', () => { 'builds and watches for changes using "mm-snap %s"', async (command) => { runner = getCommandRunner(command, ['--port', '0']); - await runner.waitForStdout(/Compiled \d+ files? in \d+ms\./u); + await runner.waitForStderr( + /Compiled \d+ files? in \d+ms with \d+ warnings?\./u, + ); await fs.writeFile(SNAP_FILE, originalFile); await runner.waitForStdout(/Changes detected in .+, recompiling\./u); - await runner.waitForStdout(/Compiled \d+ files? in \d+ms\./u); + await runner.waitForStderr( + /Compiled \d+ files? in \d+ms with \d+ warnings?\./u, + ); - expect(runner.stderr).toStrictEqual([]); expect(runner.stdout).toContainEqual( expect.stringMatching(/Checking the input file\./u), ); @@ -46,8 +49,15 @@ describe('mm-snap watch', () => { expect(runner.stdout).toContainEqual( expect.stringMatching(/Building the snap bundle\./u), ); - expect(runner.stdout).toContainEqual( - expect.stringMatching(/Compiled \d+ files? in \d+ms\./u), + expect(runner.stderr).toContainEqual( + expect.stringMatching( + /Compiled \d+ files? in \d+ms with \d+ warnings?\./u, + ), + ); + expect(runner.stderr).toContainEqual( + expect.stringContaining( + 'No icon found in the Snap manifest. It is recommended to include an icon for the Snap. See https://docs.metamask.io/snaps/how-to/design-a-snap/#guidelines-at-a-glance for more information.', + ), ); expect(runner.stdout).toContainEqual( expect.stringMatching(/Changes detected in .+, recompiling\./u), diff --git a/packages/snaps-cli/src/commands/watch/watch.ts b/packages/snaps-cli/src/commands/watch/watch.ts index fe109bcd18..81ef64cea0 100644 --- a/packages/snaps-cli/src/commands/watch/watch.ts +++ b/packages/snaps-cli/src/commands/watch/watch.ts @@ -1,4 +1,4 @@ -import { isFile } from '@metamask/snaps-utils'; +import { isFile } from '@metamask/snaps-utils/node'; import type { ProcessedConfig, ProcessedWebpackConfig } from '../../config'; import { CommandError } from '../../errors'; diff --git a/packages/snaps-cli/src/config.test.ts b/packages/snaps-cli/src/config.test.ts index 3e4e571c9d..6ca21c2a1e 100644 --- a/packages/snaps-cli/src/config.test.ts +++ b/packages/snaps-cli/src/config.test.ts @@ -51,6 +51,52 @@ describe('getConfig', () => { ).toThrow(`Unknown key: ${bold('cliOptions')}, received:`); }); + it.each([ + { + input: 'src/index.js', + sourceMap: false, + output: { + path: 'dist', + }, + server: { + port: 8081, + }, + }, + { + input: 'src/index.js', + output: { + path: 'dist', + }, + server: { + port: 8081, + }, + }, + { + input: 'src/index.js', + server: { + port: 8081, + }, + }, + { + input: 'src/index.js', + output: {}, + }, + { + input: 'src/index.js', + }, + {}, + ])('returns a valid config for `%o`', (value) => { + const config = getConfig(value, MOCK_ARGV); + + expect(config).toStrictEqual( + getMockConfig('webpack', { + input: resolve(process.cwd(), 'src', 'index.js'), + }), + ); + + expect(config.legacy).toBeUndefined(); + }); + describe('browserify', () => { it.each([ { @@ -73,20 +119,18 @@ describe('getConfig', () => { }, }, { + bundler: 'browserify', cliOptions: { - src: 'src/index.js', port: 8081, }, }, { - cliOptions: { - port: 8081, - }, + bundler: 'browserify', + cliOptions: {}, }, { - cliOptions: {}, + bundler: 'browserify', }, - {}, ])('returns a valid config for `%o`', (value) => { const config = getConfig(value, MOCK_ARGV); @@ -115,6 +159,7 @@ describe('getConfig', () => { expect(() => getConfig( { + bundler: 'browserify', cliOptions: { depsToTranspile: ['foo', 'bar'], transpilationMode: TranspilationModes.LocalOnly, diff --git a/packages/snaps-cli/src/config.ts b/packages/snaps-cli/src/config.ts index 220b8d97f6..4c5c8afe82 100644 --- a/packages/snaps-cli/src/config.ts +++ b/packages/snaps-cli/src/config.ts @@ -1,12 +1,11 @@ import { literal, union } from '@metamask/snaps-sdk'; import { createFromStruct, - file, indent, isFile, SnapsStructError, named, -} from '@metamask/snaps-utils'; +} from '@metamask/snaps-utils/node'; import { hasProperty } from '@metamask/utils'; import { transform } from '@swc/core'; import type { BrowserifyObject } from 'browserify'; @@ -14,7 +13,6 @@ import { dim } from 'chalk'; import { readFile } from 'fs/promises'; import Module from 'module'; import { basename, dirname, resolve } from 'path'; -import type { Infer } from 'superstruct'; import { array, boolean, @@ -31,10 +29,12 @@ import { unknown, empty, } from 'superstruct'; +import type { Infer } from 'superstruct'; import type { Configuration as WebpackConfiguration } from 'webpack'; import { TranspilationModes } from './builders'; import { ConfigError } from './errors'; +import { file } from './structs'; import type { YargsArgs } from './types/yargs'; import { CONFIG_FILE, TS_CONFIG_FILE } from './utils'; @@ -54,7 +54,7 @@ export type SnapBrowserifyConfig = { * deprecated and will be removed in a future release, so it's recommended to * use the Webpack bundler instead. */ - bundler?: 'browserify'; + bundler: 'browserify'; /** * The options for the Snaps CLI. These are merged with the options passed to @@ -206,7 +206,7 @@ export type SnapWebpackConfig = { * deprecated and will be removed in a future release, so it's recommended to * use the Webpack bundler instead. */ - bundler: 'webpack'; + bundler?: 'webpack'; /** * The path to the snap entry point. This should be a JavaScript or TypeScript @@ -415,6 +415,27 @@ export type SnapWebpackConfig = { zlib?: boolean; }; + /** + * Optional features to enable in the CLI. + * + * @example + * { + * features: { + * images: true, + * } + * } + */ + features?: { + /** + * Whether to enable support for images. If `true`, the Webpack + * configuration will be modified to support images. If `false`, the + * Webpack configuration will not be modified. + * + * @default true + */ + images?: boolean; + }; + /** * A function to customize the Webpack configuration used to build the snap. * This function will be called with the default Webpack configuration, and @@ -488,7 +509,7 @@ const SnapsBrowserifyBundlerCustomizerFunctionStruct = ); export const SnapsBrowserifyConfigStruct = object({ - bundler: defaulted(literal('browserify'), 'browserify'), + bundler: literal('browserify'), cliOptions: defaulted( object({ bundle: optional(file()), @@ -530,7 +551,7 @@ const SnapsWebpackCustomizeWebpackConfigFunctionStruct = ); export const SnapsWebpackConfigStruct = object({ - bundler: literal('webpack'), + bundler: defaulted(literal('webpack'), 'webpack'), input: defaulted(file(), resolve(process.cwd(), 'src/index.js')), sourceMap: defaulted(union([boolean(), literal('inline')]), false), evaluate: defaulted(boolean(), true), @@ -562,11 +583,7 @@ export const SnapsWebpackConfigStruct = object({ {}, ), - environment: defaulted(record(string(), unknown()), { - NODE_DEBUG: false, - NODE_ENV: 'production', - DEBUG: false, - }), + environment: defaulted(record(string(), unknown()), {}), stats: defaulted( object({ @@ -622,6 +639,13 @@ export const SnapsWebpackConfigStruct = object({ false, ), + features: defaulted( + object({ + images: defaulted(boolean(), true), + }), + {}, + ), + customizeWebpackConfig: optional( SnapsWebpackCustomizeWebpackConfigFunctionStruct, ), @@ -637,7 +661,7 @@ export const SnapsWebpackConfigStruct = object({ export const SnapsConfigStruct = type({ bundler: defaulted( union([literal('browserify'), literal('webpack')]), - 'browserify', + 'webpack', ), }); @@ -696,8 +720,7 @@ export type ProcessedConfig = ProcessedWebpackConfig; export function getConfig(config: unknown, argv: YargsArgs): ProcessedConfig { const prefix = 'The snap config file is invalid'; const suffix = dim( - // TODO: Link to `docs.metamask.io` once the docs are published. - 'Refer to the documentation for more information: https://github.com/MetaMask/snaps/tree/main/packages/snaps-cli/', + 'Refer to the documentation for more information: https://docs.metamask.io/snaps/reference/cli/options/', ); const { bundler } = createFromStruct( diff --git a/packages/snaps-cli/src/index.ts b/packages/snaps-cli/src/index.ts index 11339ecf49..fe0fa4f168 100644 --- a/packages/snaps-cli/src/index.ts +++ b/packages/snaps-cli/src/index.ts @@ -15,4 +15,4 @@ export type { } from './webpack'; // Re-exported from `snaps-cli` for convenience. -export { merge } from 'webpack-merge'; +export { merge, mergeWithCustomize, mergeWithRules } from 'webpack-merge'; diff --git a/packages/snaps-cli/src/structs.test.ts b/packages/snaps-cli/src/structs.test.ts new file mode 100644 index 0000000000..f2b722cdf1 --- /dev/null +++ b/packages/snaps-cli/src/structs.test.ts @@ -0,0 +1,21 @@ +import { resolve } from 'path/posix'; +import { create, is } from 'superstruct'; + +import { file } from './structs'; + +// Mock resolve so these tests work on Windows +jest.mock('path', () => ({ resolve })); + +describe('file', () => { + it('resolves a file path relative to the current working directory', () => { + jest.spyOn(process, 'cwd').mockReturnValue('/foo/bar'); + + expect(is('packages/snaps-utils/src/structs.test.ts', file())).toBe(true); + expect(create('packages/snaps-utils/src/structs.test.ts', file())).toBe( + '/foo/bar/packages/snaps-utils/src/structs.test.ts', + ); + expect(create('/packages/snaps-utils/src/structs.test.ts', file())).toBe( + '/packages/snaps-utils/src/structs.test.ts', + ); + }); +}); diff --git a/packages/snaps-cli/src/structs.ts b/packages/snaps-cli/src/structs.ts new file mode 100644 index 0000000000..5b67d870ce --- /dev/null +++ b/packages/snaps-cli/src/structs.ts @@ -0,0 +1,27 @@ +import { resolve } from 'path'; +import { coerce, string } from 'superstruct'; + +/** + * A wrapper of `superstruct`'s `string` struct that coerces a value to a string + * and resolves it relative to the current working directory. This is useful + * for specifying file paths in a configuration file, as it allows the user to + * use both relative and absolute paths. + * + * @returns The `superstruct` struct, which validates that the value is a + * string, and resolves it relative to the current working directory. + * @example + * ```ts + * const config = struct({ + * file: file(), + * // ... + * }); + * + * const value = create({ file: 'path/to/file' }, config); + * console.log(value.file); // /process/cwd/path/to/file + * ``` + */ +export function file() { + return coerce(string(), string(), (value) => { + return resolve(process.cwd(), value); + }); +} diff --git a/packages/snaps-cli/src/test-utils/e2e.ts b/packages/snaps-cli/src/test-utils/e2e.ts index 5139fcb6fd..2aa0015f05 100644 --- a/packages/snaps-cli/src/test-utils/e2e.ts +++ b/packages/snaps-cli/src/test-utils/e2e.ts @@ -124,6 +124,32 @@ export class TestRunner extends EventEmitter { }); } + /** + * Wait for a message to be written to stderr. + * + * @param message - The message to wait for. If a string, the message must be + * contained in the stderr. If a regular expression, the message must match + * the stderr. + * @returns The message that was written to stderr. + */ + async waitForStderr(message?: string | RegExp) { + assert( + this.running, + 'Cannot wait for stderr while process is not running.', + ); + + return new Promise((resolve) => { + const listener = (actual: string) => { + if (this.#matches(message, actual)) { + this.off('stderr', listener); + resolve(actual); + } + }; + + this.on('stderr', listener); + }); + } + /** * Check if `expected` matches `actual`. * diff --git a/packages/snaps-cli/src/test-utils/webpack.ts b/packages/snaps-cli/src/test-utils/webpack.ts index 272e6239e2..45241f97b2 100644 --- a/packages/snaps-cli/src/test-utils/webpack.ts +++ b/packages/snaps-cli/src/test-utils/webpack.ts @@ -4,7 +4,7 @@ import { hasProperty, isPlainObject } from '@metamask/utils'; import type { IFs } from 'memfs'; import { createFsFromVolume, Volume } from 'memfs'; import { promisify } from 'util'; -import type { Configuration } from 'webpack'; +import type { Configuration, StatsCompilation } from 'webpack'; import webpack from 'webpack'; export type CompileOptions = { @@ -65,7 +65,7 @@ export async function getCompiler({ */ export async function compile( options: CompileOptions, -): Promise<{ code: string }> { +): Promise<{ code: string; stats: StatsCompilation }> { const compiler = await getCompiler(options); return new Promise((resolve, reject) => { @@ -87,6 +87,7 @@ export async function compile( resolve({ code: output, + stats: stats.toJson(), }); }); }); @@ -134,6 +135,16 @@ export function normalizeConfig(config: Configuration): Configuration { }; } + if (rule.use.loader.includes('function.ts')) { + return { + ...rule, + use: { + ...rule.use, + loader: '/foo/bar/src/webpack/loaders/function.ts', + }, + }; + } + return rule; }); diff --git a/packages/snaps-cli/src/webpack/__snapshots__/config.test.ts.snap b/packages/snaps-cli/src/webpack/__snapshots__/config.test.ts.snap index af911671ca..95978620b1 100644 --- a/packages/snaps-cli/src/webpack/__snapshots__/config.test.ts.snap +++ b/packages/snaps-cli/src/webpack/__snapshots__/config.test.ts.snap @@ -4,6 +4,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t { "devtool": false, "entry": "/foo/bar/src/index.js", + "experiments": { + "topLevelAwait": false, + }, "infrastructureLogging": { "level": "none", }, @@ -12,7 +15,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "rules": [ { "exclude": /node_modules/u, - "test": /\\\\\\.\\[tj\\]sx\\?\\$/u, + "test": /\\\\\\.\\(js\\|mjs\\|cjs\\|ts\\)\\$/u, "use": { "loader": "/foo/bar/node_modules/swc-loader/index.js", "options": { @@ -25,7 +28,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t }, }, "module": { - "type": "commonjs", + "type": "es6", }, "sourceMaps": false, "sync": false, @@ -36,7 +39,25 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "fullySpecified": false, }, - "test": /\\\\\\.m\\?js/u, + "test": /\\\\\\.m\\?js\\$/u, + }, + { + "test": /\\\\\\.svg\\$/u, + "type": "asset/source", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.png\\$/u, + "type": "asset/inline", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.jpe\\?g\\$/u, + "type": "asset/inline", }, false, ], @@ -69,6 +90,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "path": "/foo/bar/dist", "publicPath": "/", }, + "performance": { + "hints": false, + }, "plugins": [ SnapsWebpackPlugin { "options": { @@ -82,17 +106,12 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "verbose": false, }, }, - EnvironmentPlugin { - "defaultValues": { - "DEBUG": false, - "NODE_DEBUG": false, - "NODE_ENV": "production", + DefinePlugin { + "definitions": { + "process.env.DEBUG": ""false"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""production"", }, - "keys": [ - "NODE_DEBUG", - "NODE_ENV", - "DEBUG", - ], }, ProgressPlugin { "dependenciesCount": 10000, @@ -121,6 +140,8 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "extensions": [ ".js", + ".mjs", + ".cjs", ".ts", ], "fallback": { @@ -146,6 +167,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t { "devtool": false, "entry": "/foo/bar/src/index.ts", + "experiments": { + "topLevelAwait": false, + }, "infrastructureLogging": { "level": "none", }, @@ -154,7 +178,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "rules": [ { "exclude": /node_modules/u, - "test": /\\\\\\.\\[tj\\]sx\\?\\$/u, + "test": /\\\\\\.\\(js\\|mjs\\|cjs\\|ts\\)\\$/u, "use": { "loader": "/foo/bar/node_modules/swc-loader/index.js", "options": { @@ -167,7 +191,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t }, }, "module": { - "type": "commonjs", + "type": "es6", }, "sourceMaps": false, "sync": false, @@ -178,7 +202,25 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "fullySpecified": false, }, - "test": /\\\\\\.m\\?js/u, + "test": /\\\\\\.m\\?js\\$/u, + }, + { + "test": /\\\\\\.svg\\$/u, + "type": "asset/source", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.png\\$/u, + "type": "asset/inline", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.jpe\\?g\\$/u, + "type": "asset/inline", }, false, ], @@ -211,6 +253,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "path": "/foo/bar/dist", "publicPath": "/", }, + "performance": { + "hints": false, + }, "plugins": [ SnapsWebpackPlugin { "options": { @@ -224,17 +269,12 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "verbose": true, }, }, - EnvironmentPlugin { - "defaultValues": { - "DEBUG": false, - "NODE_DEBUG": false, - "NODE_ENV": "production", + DefinePlugin { + "definitions": { + "process.env.DEBUG": ""false"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""production"", }, - "keys": [ - "NODE_DEBUG", - "NODE_ENV", - "DEBUG", - ], }, ProgressPlugin { "dependenciesCount": 10000, @@ -263,6 +303,8 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "extensions": [ ".js", + ".mjs", + ".cjs", ".ts", ], "fallback": { @@ -288,6 +330,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t { "devtool": "inline-source-map", "entry": "/foo/bar/src/index.ts", + "experiments": { + "topLevelAwait": false, + }, "infrastructureLogging": { "level": "none", }, @@ -296,7 +341,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "rules": [ { "exclude": /node_modules/u, - "test": /\\\\\\.\\[tj\\]sx\\?\\$/u, + "test": /\\\\\\.\\(js\\|mjs\\|cjs\\|ts\\)\\$/u, "use": { "loader": "/foo/bar/node_modules/swc-loader/index.js", "options": { @@ -309,7 +354,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t }, }, "module": { - "type": "commonjs", + "type": "es6", }, "sourceMaps": true, "sync": false, @@ -320,7 +365,25 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "fullySpecified": false, }, - "test": /\\\\\\.m\\?js/u, + "test": /\\\\\\.m\\?js\\$/u, + }, + { + "test": /\\\\\\.svg\\$/u, + "type": "asset/source", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.png\\$/u, + "type": "asset/inline", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.jpe\\?g\\$/u, + "type": "asset/inline", }, false, ], @@ -353,6 +416,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "path": "/foo/bar/dist", "publicPath": "/", }, + "performance": { + "hints": false, + }, "plugins": [ SnapsWebpackPlugin { "options": { @@ -366,17 +432,12 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "verbose": false, }, }, - EnvironmentPlugin { - "defaultValues": { - "DEBUG": false, - "NODE_DEBUG": false, - "NODE_ENV": "production", + DefinePlugin { + "definitions": { + "process.env.DEBUG": ""false"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""production"", }, - "keys": [ - "NODE_DEBUG", - "NODE_ENV", - "DEBUG", - ], }, ProgressPlugin { "dependenciesCount": 10000, @@ -405,6 +466,8 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "extensions": [ ".js", + ".mjs", + ".cjs", ".ts", ], "fallback": { @@ -430,6 +493,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t { "devtool": "source-map", "entry": "/foo/bar/src/index.ts", + "experiments": { + "topLevelAwait": false, + }, "infrastructureLogging": { "level": "none", }, @@ -438,7 +504,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "rules": [ { "exclude": /node_modules/u, - "test": /\\\\\\.\\[tj\\]sx\\?\\$/u, + "test": /\\\\\\.\\(js\\|mjs\\|cjs\\|ts\\)\\$/u, "use": { "loader": "/foo/bar/node_modules/swc-loader/index.js", "options": { @@ -451,7 +517,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t }, }, "module": { - "type": "commonjs", + "type": "es6", }, "sourceMaps": true, "sync": false, @@ -462,7 +528,25 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "fullySpecified": false, }, - "test": /\\\\\\.m\\?js/u, + "test": /\\\\\\.m\\?js\\$/u, + }, + { + "test": /\\\\\\.svg\\$/u, + "type": "asset/source", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.png\\$/u, + "type": "asset/inline", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.jpe\\?g\\$/u, + "type": "asset/inline", }, false, ], @@ -495,6 +579,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "path": "/foo/bar/dist", "publicPath": "/", }, + "performance": { + "hints": false, + }, "plugins": [ SnapsWebpackPlugin { "options": { @@ -508,19 +595,13 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "verbose": false, }, }, - EnvironmentPlugin { - "defaultValues": { - "DEBUG": false, - "FOO": "bar", - "NODE_DEBUG": false, - "NODE_ENV": "production", + DefinePlugin { + "definitions": { + "process.env.DEBUG": ""false"", + "process.env.FOO": ""bar"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""production"", }, - "keys": [ - "FOO", - "NODE_DEBUG", - "NODE_ENV", - "DEBUG", - ], }, ProgressPlugin { "dependenciesCount": 10000, @@ -544,6 +625,8 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "extensions": [ ".js", + ".mjs", + ".cjs", ".ts", ], "fallback": { @@ -564,6 +647,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t { "devtool": false, "entry": "/foo/bar/src/index.js", + "experiments": { + "topLevelAwait": false, + }, "infrastructureLogging": { "level": "none", }, @@ -572,7 +658,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "rules": [ { "exclude": /node_modules/u, - "test": /\\\\\\.\\[tj\\]sx\\?\\$/u, + "test": /\\\\\\.\\(js\\|mjs\\|cjs\\|ts\\)\\$/u, "use": { "loader": "/foo/bar/node_modules/swc-loader/index.js", "options": { @@ -585,7 +671,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t }, }, "module": { - "type": "commonjs", + "type": "es6", }, "sourceMaps": false, "sync": false, @@ -596,12 +682,33 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "fullySpecified": false, }, - "test": /\\\\\\.m\\?js/u, + "test": /\\\\\\.m\\?js\\$/u, + }, + { + "test": /\\\\\\.svg\\$/u, + "type": "asset/source", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.png\\$/u, + "type": "asset/inline", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.jpe\\?g\\$/u, + "type": "asset/inline", }, { "test": /\\\\\\.wasm\\$/u, "use": { - "loader": "/foo/bar/loaders/wasm", + "loader": "/foo/bar/src/webpack/loaders/function.ts", + "options": { + "fn": [Function], + }, }, }, ], @@ -634,6 +741,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "path": "/foo/bar/dist", "publicPath": "/", }, + "performance": { + "hints": false, + }, "plugins": [ SnapsWebpackPlugin { "options": { @@ -647,17 +757,12 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "verbose": false, }, }, - EnvironmentPlugin { - "defaultValues": { - "DEBUG": false, - "NODE_DEBUG": false, - "NODE_ENV": "production", + DefinePlugin { + "definitions": { + "process.env.DEBUG": ""false"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""production"", }, - "keys": [ - "NODE_DEBUG", - "NODE_ENV", - "DEBUG", - ], }, ProgressPlugin { "dependenciesCount": 10000, @@ -686,6 +791,8 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "extensions": [ ".js", + ".mjs", + ".cjs", ".ts", ], "fallback": { @@ -711,6 +818,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t { "devtool": false, "entry": "/foo/bar/src/index.js", + "experiments": { + "topLevelAwait": false, + }, "infrastructureLogging": { "level": "none", }, @@ -719,7 +829,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "rules": [ { "exclude": /node_modules/u, - "test": /\\\\\\.\\[tj\\]sx\\?\\$/u, + "test": /\\\\\\.\\(js\\|mjs\\|cjs\\|ts\\)\\$/u, "use": { "loader": "/foo/bar/node_modules/swc-loader/index.js", "options": { @@ -732,7 +842,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t }, }, "module": { - "type": "commonjs", + "type": "es6", }, "sourceMaps": false, "sync": false, @@ -743,7 +853,25 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "fullySpecified": false, }, - "test": /\\\\\\.m\\?js/u, + "test": /\\\\\\.m\\?js\\$/u, + }, + { + "test": /\\\\\\.svg\\$/u, + "type": "asset/source", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.png\\$/u, + "type": "asset/inline", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.jpe\\?g\\$/u, + "type": "asset/inline", }, false, ], @@ -776,6 +904,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "path": "/foo/bar/dist", "publicPath": "/", }, + "performance": { + "hints": false, + }, "plugins": [ SnapsWebpackPlugin { "options": { @@ -789,17 +920,12 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "verbose": false, }, }, - EnvironmentPlugin { - "defaultValues": { - "DEBUG": false, - "NODE_DEBUG": false, - "NODE_ENV": "production", + DefinePlugin { + "definitions": { + "process.env.DEBUG": ""false"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""production"", }, - "keys": [ - "NODE_DEBUG", - "NODE_ENV", - "DEBUG", - ], }, ProgressPlugin { "dependenciesCount": 10000, @@ -828,6 +954,8 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "extensions": [ ".js", + ".mjs", + ".cjs", ".ts", ], "fallback": { @@ -853,6 +981,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t { "devtool": false, "entry": "/foo/bar/src/index.js", + "experiments": { + "topLevelAwait": false, + }, "infrastructureLogging": { "level": "none", }, @@ -861,7 +992,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "rules": [ { "exclude": /node_modules/u, - "test": /\\\\\\.\\[tj\\]sx\\?\\$/u, + "test": /\\\\\\.\\(js\\|mjs\\|cjs\\|ts\\)\\$/u, "use": { "loader": "/foo/bar/node_modules/swc-loader/index.js", "options": { @@ -874,7 +1005,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t }, }, "module": { - "type": "commonjs", + "type": "es6", }, "sourceMaps": false, "sync": false, @@ -885,7 +1016,25 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "fullySpecified": false, }, - "test": /\\\\\\.m\\?js/u, + "test": /\\\\\\.m\\?js\\$/u, + }, + { + "test": /\\\\\\.svg\\$/u, + "type": "asset/source", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.png\\$/u, + "type": "asset/inline", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.jpe\\?g\\$/u, + "type": "asset/inline", }, false, ], @@ -918,6 +1067,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "path": "/foo/bar/dist", "publicPath": "/", }, + "performance": { + "hints": false, + }, "plugins": [ SnapsWebpackPlugin { "options": { @@ -931,17 +1083,12 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "verbose": false, }, }, - EnvironmentPlugin { - "defaultValues": { - "DEBUG": false, - "NODE_DEBUG": false, - "NODE_ENV": "production", + DefinePlugin { + "definitions": { + "process.env.DEBUG": ""false"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""production"", }, - "keys": [ - "NODE_DEBUG", - "NODE_ENV", - "DEBUG", - ], }, ProgressPlugin { "dependenciesCount": 10000, @@ -970,6 +1117,8 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "extensions": [ ".js", + ".mjs", + ".cjs", ".ts", ], "fallback": { @@ -995,6 +1144,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t { "devtool": false, "entry": "/foo/bar/src/index.js", + "experiments": { + "topLevelAwait": false, + }, "infrastructureLogging": { "level": "none", }, @@ -1003,7 +1155,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "rules": [ { "exclude": /node_modules/u, - "test": /\\\\\\.\\[tj\\]sx\\?\\$/u, + "test": /\\\\\\.\\(js\\|mjs\\|cjs\\|ts\\)\\$/u, "use": { "loader": "/foo/bar/node_modules/swc-loader/index.js", "options": { @@ -1016,7 +1168,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t }, }, "module": { - "type": "commonjs", + "type": "es6", }, "sourceMaps": false, "sync": false, @@ -1027,7 +1179,25 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "fullySpecified": false, }, - "test": /\\\\\\.m\\?js/u, + "test": /\\\\\\.m\\?js\\$/u, + }, + { + "test": /\\\\\\.svg\\$/u, + "type": "asset/source", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.png\\$/u, + "type": "asset/inline", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.jpe\\?g\\$/u, + "type": "asset/inline", }, false, ], @@ -1060,6 +1230,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "path": "/foo/bar/dist", "publicPath": "/", }, + "performance": { + "hints": false, + }, "plugins": [ SnapsWebpackPlugin { "options": { @@ -1073,17 +1246,12 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "verbose": false, }, }, - EnvironmentPlugin { - "defaultValues": { - "DEBUG": false, - "NODE_DEBUG": false, - "NODE_ENV": "production", + DefinePlugin { + "definitions": { + "process.env.DEBUG": ""false"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""production"", }, - "keys": [ - "NODE_DEBUG", - "NODE_ENV", - "DEBUG", - ], }, ProgressPlugin { "dependenciesCount": 10000, @@ -1120,6 +1288,8 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "extensions": [ ".js", + ".mjs", + ".cjs", ".ts", ], "fallback": { @@ -1145,6 +1315,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t { "devtool": false, "entry": "/foo/bar/src/index.js", + "experiments": { + "topLevelAwait": false, + }, "infrastructureLogging": { "level": "none", }, @@ -1153,7 +1326,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "rules": [ { "exclude": /node_modules/u, - "test": /\\\\\\.\\[tj\\]sx\\?\\$/u, + "test": /\\\\\\.\\(js\\|mjs\\|cjs\\|ts\\)\\$/u, "use": { "loader": "/foo/bar/node_modules/swc-loader/index.js", "options": { @@ -1166,7 +1339,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t }, }, "module": { - "type": "commonjs", + "type": "es6", }, "sourceMaps": false, "sync": false, @@ -1177,7 +1350,25 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "fullySpecified": false, }, - "test": /\\\\\\.m\\?js/u, + "test": /\\\\\\.m\\?js\\$/u, + }, + { + "test": /\\\\\\.svg\\$/u, + "type": "asset/source", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.png\\$/u, + "type": "asset/inline", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.jpe\\?g\\$/u, + "type": "asset/inline", }, false, ], @@ -1210,6 +1401,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "path": "/foo/bar/dist", "publicPath": "/", }, + "performance": { + "hints": false, + }, "plugins": [ SnapsWebpackPlugin { "options": { @@ -1223,17 +1417,12 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "verbose": false, }, }, - EnvironmentPlugin { - "defaultValues": { - "DEBUG": false, - "NODE_DEBUG": false, - "NODE_ENV": "production", + DefinePlugin { + "definitions": { + "process.env.DEBUG": ""false"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""production"", }, - "keys": [ - "NODE_DEBUG", - "NODE_ENV", - "DEBUG", - ], }, ProgressPlugin { "dependenciesCount": 10000, @@ -1270,6 +1459,8 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "extensions": [ ".js", + ".mjs", + ".cjs", ".ts", ], "fallback": { @@ -1291,10 +1482,161 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t } `; +exports[`getDefaultConfiguration returns the default Webpack configuration for the given CLI config 10`] = ` +{ + "devtool": false, + "entry": "/foo/bar/src/index.js", + "experiments": { + "topLevelAwait": false, + }, + "infrastructureLogging": { + "level": "none", + }, + "mode": "production", + "module": { + "rules": [ + { + "exclude": /node_modules/u, + "test": /\\\\\\.\\(js\\|mjs\\|cjs\\|ts\\)\\$/u, + "use": { + "loader": "/foo/bar/node_modules/swc-loader/index.js", + "options": { + "env": { + "targets": "chrome >= 90, firefox >= 91", + }, + "jsc": { + "parser": { + "syntax": "typescript", + }, + }, + "module": { + "type": "es6", + }, + "sourceMaps": false, + "sync": false, + }, + }, + }, + { + "resolve": { + "fullySpecified": false, + }, + "test": /\\\\\\.m\\?js\\$/u, + }, + false, + false, + false, + false, + ], + }, + "optimization": { + "minimize": false, + "minimizer": [ + TerserPlugin { + "options": { + "exclude": undefined, + "extractComments": true, + "include": undefined, + "minimizer": { + "implementation": [Function], + "options": {}, + }, + "parallel": true, + "test": /\\\\\\.\\[cm\\]\\?js\\(\\\\\\?\\.\\*\\)\\?\\$/i, + }, + }, + ], + }, + "output": { + "chunkFormat": "commonjs", + "clean": false, + "filename": "bundle.js", + "library": { + "type": "commonjs", + }, + "path": "/foo/bar/dist", + "publicPath": "/", + }, + "performance": { + "hints": false, + }, + "plugins": [ + SnapsWebpackPlugin { + "options": { + "eval": true, + "manifestPath": "/foo/bar/snap.manifest.json", + "writeManifest": true, + }, + }, + SnapsStatsPlugin { + "options": { + "verbose": false, + }, + }, + DefinePlugin { + "definitions": { + "process.env.DEBUG": ""false"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""production"", + }, + }, + ProgressPlugin { + "dependenciesCount": 10000, + "handler": [Function], + "modulesCount": 5000, + "percentBy": undefined, + "profile": false, + "showActiveModules": false, + "showDependencies": true, + "showEntries": true, + "showModules": true, + }, + SnapsBundleWarningsPlugin { + "options": { + "buffer": true, + "builtInResolver": SnapsBuiltInResolver { + "options": { + "ignore": [], + }, + "unresolvedModules": Set {}, + }, + "builtIns": true, + }, + }, + ], + "resolve": { + "extensions": [ + ".js", + ".mjs", + ".cjs", + ".ts", + ], + "fallback": { + "buffer": false, + "fs": false, + "path": false, + }, + "plugins": [ + SnapsBuiltInResolver { + "options": { + "ignore": [], + }, + "unresolvedModules": Set {}, + }, + ], + }, + "stats": "none", + "target": "browserslist:/foo/bar/.browserslistrc", +} +`; + exports[`getDefaultConfiguration returns the default Webpack configuration for the given CLI config and options 1`] = ` { "devtool": false, "entry": "/foo/bar/src/index.ts", + "experiments": { + "topLevelAwait": false, + }, "infrastructureLogging": { "level": "none", }, @@ -1303,7 +1645,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "rules": [ { "exclude": /node_modules/u, - "test": /\\\\\\.\\[tj\\]sx\\?\\$/u, + "test": /\\\\\\.\\(js\\|mjs\\|cjs\\|ts\\)\\$/u, "use": { "loader": "/foo/bar/node_modules/swc-loader/index.js", "options": { @@ -1316,7 +1658,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t }, }, "module": { - "type": "commonjs", + "type": "es6", }, "sourceMaps": false, "sync": false, @@ -1327,7 +1669,25 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "fullySpecified": false, }, - "test": /\\\\\\.m\\?js/u, + "test": /\\\\\\.m\\?js\\$/u, + }, + { + "test": /\\\\\\.svg\\$/u, + "type": "asset/source", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.png\\$/u, + "type": "asset/inline", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.jpe\\?g\\$/u, + "type": "asset/inline", }, false, ], @@ -1360,6 +1720,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "path": "/bar/baz", "publicPath": "/", }, + "performance": { + "hints": false, + }, "plugins": [ SnapsWebpackPlugin { "options": { @@ -1373,17 +1736,12 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "verbose": false, }, }, - EnvironmentPlugin { - "defaultValues": { - "DEBUG": false, - "NODE_DEBUG": false, - "NODE_ENV": "production", + DefinePlugin { + "definitions": { + "process.env.DEBUG": ""false"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""production"", }, - "keys": [ - "NODE_DEBUG", - "NODE_ENV", - "DEBUG", - ], }, ProgressPlugin { "dependenciesCount": 10000, @@ -1421,6 +1779,8 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "extensions": [ ".js", + ".mjs", + ".cjs", ".ts", ], "fallback": { @@ -1446,6 +1806,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t { "devtool": false, "entry": "/foo/bar/src/index.ts", + "experiments": { + "topLevelAwait": false, + }, "infrastructureLogging": { "level": "none", }, @@ -1454,7 +1817,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "rules": [ { "exclude": /node_modules/u, - "test": /\\\\\\.\\[tj\\]sx\\?\\$/u, + "test": /\\\\\\.\\(js\\|mjs\\|cjs\\|ts\\)\\$/u, "use": { "loader": "/foo/bar/node_modules/swc-loader/index.js", "options": { @@ -1467,7 +1830,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t }, }, "module": { - "type": "commonjs", + "type": "es6", }, "sourceMaps": false, "sync": false, @@ -1478,7 +1841,25 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "fullySpecified": false, }, - "test": /\\\\\\.m\\?js/u, + "test": /\\\\\\.m\\?js\\$/u, + }, + { + "test": /\\\\\\.svg\\$/u, + "type": "asset/source", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.png\\$/u, + "type": "asset/inline", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.jpe\\?g\\$/u, + "type": "asset/inline", }, false, ], @@ -1511,6 +1892,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "path": "/bar/baz", "publicPath": "/", }, + "performance": { + "hints": false, + }, "plugins": [ SnapsWebpackPlugin { "options": { @@ -1524,17 +1908,12 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "verbose": false, }, }, - EnvironmentPlugin { - "defaultValues": { - "DEBUG": false, - "NODE_DEBUG": false, - "NODE_ENV": "production", + DefinePlugin { + "definitions": { + "process.env.DEBUG": ""false"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""production"", }, - "keys": [ - "NODE_DEBUG", - "NODE_ENV", - "DEBUG", - ], }, ProgressPlugin { "dependenciesCount": 10000, @@ -1563,6 +1942,8 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "extensions": [ ".js", + ".mjs", + ".cjs", ".ts", ], "fallback": { @@ -1588,6 +1969,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t { "devtool": false, "entry": "/foo/bar/src/index.ts", + "experiments": { + "topLevelAwait": false, + }, "infrastructureLogging": { "level": "none", }, @@ -1596,7 +1980,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "rules": [ { "exclude": /node_modules/u, - "test": /\\\\\\.\\[tj\\]sx\\?\\$/u, + "test": /\\\\\\.\\(js\\|mjs\\|cjs\\|ts\\)\\$/u, "use": { "loader": "/foo/bar/node_modules/swc-loader/index.js", "options": { @@ -1609,7 +1993,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t }, }, "module": { - "type": "commonjs", + "type": "es6", }, "sourceMaps": false, "sync": false, @@ -1620,7 +2004,25 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "fullySpecified": false, }, - "test": /\\\\\\.m\\?js/u, + "test": /\\\\\\.m\\?js\\$/u, + }, + { + "test": /\\\\\\.svg\\$/u, + "type": "asset/source", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.png\\$/u, + "type": "asset/inline", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.jpe\\?g\\$/u, + "type": "asset/inline", }, false, ], @@ -1653,6 +2055,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "path": "/bar/baz", "publicPath": "/", }, + "performance": { + "hints": false, + }, "plugins": [ SnapsWebpackPlugin { "options": { @@ -1666,17 +2071,12 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "verbose": false, }, }, - EnvironmentPlugin { - "defaultValues": { - "DEBUG": false, - "NODE_DEBUG": false, - "NODE_ENV": "production", + DefinePlugin { + "definitions": { + "process.env.DEBUG": ""false"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""production"", }, - "keys": [ - "NODE_DEBUG", - "NODE_ENV", - "DEBUG", - ], }, ProgressPlugin { "dependenciesCount": 10000, @@ -1705,6 +2105,8 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "extensions": [ ".js", + ".mjs", + ".cjs", ".ts", ], "fallback": { @@ -1730,6 +2132,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t { "devtool": false, "entry": "/foo/bar/src/index.ts", + "experiments": { + "topLevelAwait": false, + }, "infrastructureLogging": { "level": "none", }, @@ -1738,7 +2143,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "rules": [ { "exclude": /node_modules/u, - "test": /\\\\\\.\\[tj\\]sx\\?\\$/u, + "test": /\\\\\\.\\(js\\|mjs\\|cjs\\|ts\\)\\$/u, "use": { "loader": "/foo/bar/node_modules/swc-loader/index.js", "options": { @@ -1751,7 +2156,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t }, }, "module": { - "type": "commonjs", + "type": "es6", }, "sourceMaps": false, "sync": false, @@ -1762,7 +2167,25 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "fullySpecified": false, }, - "test": /\\\\\\.m\\?js/u, + "test": /\\\\\\.m\\?js\\$/u, + }, + { + "test": /\\\\\\.svg\\$/u, + "type": "asset/source", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.png\\$/u, + "type": "asset/inline", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.jpe\\?g\\$/u, + "type": "asset/inline", }, false, ], @@ -1795,6 +2218,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "path": "/bar/baz", "publicPath": "/", }, + "performance": { + "hints": false, + }, "plugins": [ SnapsWebpackPlugin { "options": { @@ -1808,17 +2234,12 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "verbose": false, }, }, - EnvironmentPlugin { - "defaultValues": { - "DEBUG": false, - "NODE_DEBUG": false, - "NODE_ENV": "production", + DefinePlugin { + "definitions": { + "process.env.DEBUG": ""false"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""production"", }, - "keys": [ - "NODE_DEBUG", - "NODE_ENV", - "DEBUG", - ], }, ProgressPlugin { "dependenciesCount": 10000, @@ -1856,6 +2277,8 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "extensions": [ ".js", + ".mjs", + ".cjs", ".ts", ], "fallback": { @@ -1881,6 +2304,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t { "devtool": false, "entry": "/foo/bar/src/index.ts", + "experiments": { + "topLevelAwait": false, + }, "infrastructureLogging": { "level": "none", }, @@ -1889,7 +2315,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "rules": [ { "exclude": /node_modules/u, - "test": /\\\\\\.\\[tj\\]sx\\?\\$/u, + "test": /\\\\\\.\\(js\\|mjs\\|cjs\\|ts\\)\\$/u, "use": { "loader": "/foo/bar/node_modules/swc-loader/index.js", "options": { @@ -1902,7 +2328,7 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t }, }, "module": { - "type": "commonjs", + "type": "es6", }, "sourceMaps": false, "sync": false, @@ -1913,7 +2339,25 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "fullySpecified": false, }, - "test": /\\\\\\.m\\?js/u, + "test": /\\\\\\.m\\?js\\$/u, + }, + { + "test": /\\\\\\.svg\\$/u, + "type": "asset/source", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.png\\$/u, + "type": "asset/inline", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.jpe\\?g\\$/u, + "type": "asset/inline", }, false, ], @@ -1946,6 +2390,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "path": "/bar/baz", "publicPath": "/", }, + "performance": { + "hints": false, + }, "plugins": [ SnapsWebpackPlugin { "options": { @@ -1959,17 +2406,12 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "verbose": false, }, }, - EnvironmentPlugin { - "defaultValues": { - "DEBUG": false, - "NODE_DEBUG": false, - "NODE_ENV": "production", + DefinePlugin { + "definitions": { + "process.env.DEBUG": ""false"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""production"", }, - "keys": [ - "NODE_DEBUG", - "NODE_ENV", - "DEBUG", - ], }, ProgressPlugin { "dependenciesCount": 10000, @@ -2007,6 +2449,8 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "extensions": [ ".js", + ".mjs", + ".cjs", ".ts", ], "fallback": { @@ -2032,6 +2476,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t { "devtool": false, "entry": "/foo/bar/src/index.js", + "experiments": { + "topLevelAwait": false, + }, "infrastructureLogging": { "level": "none", }, @@ -2040,14 +2487,15 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "rules": [ { "exclude": /node_modules/u, - "test": /\\\\\\.\\[tj\\]sx\\?\\$/u, + "test": /\\\\\\.\\(js\\|mjs\\|cjs\\|ts\\)\\$/u, "use": { - "loader": "/foo/bar/loaders/browserify", + "loader": "/foo/bar/src/webpack/loaders/function.ts", "options": { "bundlerCustomizer": undefined, "depsToTranspile": [], "dist": "/foo/bar/dist", "eval": true, + "fn": [Function], "manifest": true, "outfileName": "bundle.js", "port": 8081, @@ -2067,7 +2515,25 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "fullySpecified": false, }, - "test": /\\\\\\.m\\?js/u, + "test": /\\\\\\.m\\?js\\$/u, + }, + { + "test": /\\\\\\.svg\\$/u, + "type": "asset/source", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.png\\$/u, + "type": "asset/inline", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.jpe\\?g\\$/u, + "type": "asset/inline", }, false, ], @@ -2100,6 +2566,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "path": "/foo/bar/dist", "publicPath": "/", }, + "performance": { + "hints": false, + }, "plugins": [ SnapsWebpackPlugin { "options": { @@ -2113,17 +2582,12 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "verbose": false, }, }, - EnvironmentPlugin { - "defaultValues": { - "DEBUG": false, - "NODE_DEBUG": false, - "NODE_ENV": "production", + DefinePlugin { + "definitions": { + "process.env.DEBUG": ""false"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""production"", }, - "keys": [ - "NODE_DEBUG", - "NODE_ENV", - "DEBUG", - ], }, ProgressPlugin { "dependenciesCount": 10000, @@ -2147,6 +2611,8 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "extensions": [ ".js", + ".mjs", + ".cjs", ".ts", ], "fallback": { @@ -2167,6 +2633,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t { "devtool": false, "entry": "/foo/bar/src/index.ts", + "experiments": { + "topLevelAwait": false, + }, "infrastructureLogging": { "level": "none", }, @@ -2175,14 +2644,15 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "rules": [ { "exclude": /node_modules/u, - "test": /\\\\\\.\\[tj\\]sx\\?\\$/u, + "test": /\\\\\\.\\(js\\|mjs\\|cjs\\|ts\\)\\$/u, "use": { - "loader": "/foo/bar/loaders/browserify", + "loader": "/foo/bar/src/webpack/loaders/function.ts", "options": { "bundlerCustomizer": undefined, "depsToTranspile": [], "dist": "/foo/bar/dist", "eval": true, + "fn": [Function], "manifest": true, "outfileName": "bundle.js", "port": 8081, @@ -2202,7 +2672,25 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "fullySpecified": false, }, - "test": /\\\\\\.m\\?js/u, + "test": /\\\\\\.m\\?js\\$/u, + }, + { + "test": /\\\\\\.svg\\$/u, + "type": "asset/source", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.png\\$/u, + "type": "asset/inline", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.jpe\\?g\\$/u, + "type": "asset/inline", }, false, ], @@ -2235,6 +2723,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "path": "/foo/bar/dist", "publicPath": "/", }, + "performance": { + "hints": false, + }, "plugins": [ SnapsWebpackPlugin { "options": { @@ -2248,17 +2739,12 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "verbose": false, }, }, - EnvironmentPlugin { - "defaultValues": { - "DEBUG": false, - "NODE_DEBUG": false, - "NODE_ENV": "production", + DefinePlugin { + "definitions": { + "process.env.DEBUG": ""false"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""production"", }, - "keys": [ - "NODE_DEBUG", - "NODE_ENV", - "DEBUG", - ], }, ProgressPlugin { "dependenciesCount": 10000, @@ -2282,6 +2768,8 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "extensions": [ ".js", + ".mjs", + ".cjs", ".ts", ], "fallback": { @@ -2302,6 +2790,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t { "devtool": false, "entry": "/foo/bar/src/index.ts", + "experiments": { + "topLevelAwait": false, + }, "infrastructureLogging": { "level": "none", }, @@ -2310,14 +2801,15 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "rules": [ { "exclude": /node_modules/u, - "test": /\\\\\\.\\[tj\\]sx\\?\\$/u, + "test": /\\\\\\.\\(js\\|mjs\\|cjs\\|ts\\)\\$/u, "use": { - "loader": "/foo/bar/loaders/browserify", + "loader": "/foo/bar/src/webpack/loaders/function.ts", "options": { "bundlerCustomizer": undefined, "depsToTranspile": [], "dist": "/foo/bar/dist", "eval": true, + "fn": [Function], "manifest": true, "outfileName": "bundle.js", "port": 8081, @@ -2337,7 +2829,25 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "fullySpecified": false, }, - "test": /\\\\\\.m\\?js/u, + "test": /\\\\\\.m\\?js\\$/u, + }, + { + "test": /\\\\\\.svg\\$/u, + "type": "asset/source", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.png\\$/u, + "type": "asset/inline", + }, + { + "generator": { + "dataUrl": [Function], + }, + "test": /\\\\\\.jpe\\?g\\$/u, + "type": "asset/inline", }, false, ], @@ -2370,6 +2880,9 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "path": "/foo/bar/dist", "publicPath": "/", }, + "performance": { + "hints": false, + }, "plugins": [ SnapsWebpackPlugin { "options": { @@ -2383,17 +2896,12 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "verbose": false, }, }, - EnvironmentPlugin { - "defaultValues": { - "DEBUG": false, - "NODE_DEBUG": false, - "NODE_ENV": "production", + DefinePlugin { + "definitions": { + "process.env.DEBUG": ""false"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""production"", }, - "keys": [ - "NODE_DEBUG", - "NODE_ENV", - "DEBUG", - ], }, ProgressPlugin { "dependenciesCount": 10000, @@ -2417,6 +2925,8 @@ exports[`getDefaultConfiguration returns the default Webpack configuration for t "resolve": { "extensions": [ ".js", + ".mjs", + ".cjs", ".ts", ], "fallback": { diff --git a/packages/snaps-cli/src/webpack/config.test.ts b/packages/snaps-cli/src/webpack/config.test.ts index 68355e8344..afc7e27c7f 100644 --- a/packages/snaps-cli/src/webpack/config.test.ts +++ b/packages/snaps-cli/src/webpack/config.test.ts @@ -150,6 +150,19 @@ describe('getDefaultConfiguration', () => { buffer: true, }, }), + getMockConfig('webpack', { + input: 'src/index.js', + output: { + path: 'dist', + minimize: false, + }, + manifest: { + path: 'snap.manifest.json', + }, + features: { + images: false, + }, + }), ])( 'returns the default Webpack configuration for the given CLI config', async (config) => { @@ -191,8 +204,10 @@ describe('getDefaultConfiguration', () => { async (config) => { jest.spyOn(process, 'cwd').mockReturnValue('/foo/bar'); + const output = await getDefaultConfiguration(config); + // eslint-disable-next-line jest/no-restricted-matchers - expect(await getDefaultConfiguration(config)).toMatchSnapshot(); + expect(normalizeConfig(output)).toMatchSnapshot(); }, ); diff --git a/packages/snaps-cli/src/webpack/config.ts b/packages/snaps-cli/src/webpack/config.ts index af693c0558..f10acac994 100644 --- a/packages/snaps-cli/src/webpack/config.ts +++ b/packages/snaps-cli/src/webpack/config.ts @@ -3,9 +3,10 @@ import type { Ora } from 'ora'; import { resolve } from 'path'; import TerserPlugin from 'terser-webpack-plugin'; import type { Configuration } from 'webpack'; -import { EnvironmentPlugin, ProgressPlugin, ProvidePlugin } from 'webpack'; +import { DefinePlugin, ProgressPlugin, ProvidePlugin } from 'webpack'; import type { ProcessedWebpackConfig } from '../config'; +import { getFunctionLoader, wasm } from './loaders'; import { SnapsBuiltInResolver, SnapsBundleWarningsPlugin, @@ -16,7 +17,9 @@ import { BROWSERSLIST_FILE, getDefaultLoader, getDevTool, + getEnvironmentVariables, getFallbacks, + getImageSVG, getProgressHandler, } from './utils'; @@ -185,7 +188,7 @@ export async function getDefaultConfiguration( module: { rules: [ { - test: /\.[tj]sx?$/u, + test: /\.(js|mjs|cjs|ts)$/u, exclude: /node_modules/u, use: await getDefaultLoader(config), }, @@ -197,17 +200,46 @@ export async function getDefaultConfiguration( * @see https://webpack.js.org/configuration/module/#resolvefullyspecified */ { - test: /\.m?js/u, + test: /\.m?js$/u, resolve: { fullySpecified: false, }, }, + /** + * This allows importing `.svg` files as a string. + */ + config.features.images && { + test: /\.svg$/u, + // `asset/source` returns the source as a UTF-8 string. + type: 'asset/source', + }, + + /** + * This allows importing `.png` files as a data URL. + */ + config.features.images && { + test: /\.png$/u, + type: 'asset/inline', + generator: { + dataUrl: getImageSVG.bind(null, 'image/png'), + }, + }, + + /** + * This allows importing `.jpe?g` files as a data URL. + */ + config.features.images && { + test: /\.jpe?g$/u, + type: 'asset/inline', + generator: { + dataUrl: getImageSVG.bind(null, 'image/jpeg'), + }, + }, + config.experimental.wasm && { test: /\.wasm$/u, - use: { - loader: resolve(__dirname, 'loaders', 'wasm'), - }, + use: getFunctionLoader(wasm, {}), }, ], }, @@ -220,10 +252,10 @@ export async function getDefaultConfiguration( */ resolve: { /** - * The extensions to resolve. We set it to resolve `.js` and `.ts` + * The extensions to resolve. We set it to resolve `.(c|m)?js` and `.ts` * files. */ - extensions: ['.js', '.ts'], + extensions: ['.js', '.mjs', '.cjs', '.ts'], /** * The fallback options. This tells Webpack how to handle imports that @@ -264,11 +296,12 @@ export async function getDefaultConfiguration( new SnapsStatsPlugin({ verbose: config.stats.verbose }, options.spinner), /** - * The `EnvironmentPlugin` is a Webpack plugin that adds environment - * variables to the bundle. We use it to add the `NODE_ENV` and `DEBUG` - * environment variables. + * The `DefinePlugin` is a Webpack plugin that adds static values to the + * bundle. We use it to add the `NODE_DEBUG`, `NODE_ENV`, and `DEBUG` + * environment variables, as well as any custom environment + * variables (as `process.env`). */ - new EnvironmentPlugin(config.environment), + new DefinePlugin(getEnvironmentVariables(config.environment)), /** * The `ProgressPlugin` is a Webpack plugin that logs the progress of @@ -283,14 +316,11 @@ export async function getDefaultConfiguration( * warning when the bundle is potentially incompatible with MetaMask * Snaps. */ - new SnapsBundleWarningsPlugin( - { - builtInResolver, - builtIns: Boolean(config.stats.builtIns), - buffer: config.stats.buffer, - }, - options.spinner, - ), + new SnapsBundleWarningsPlugin({ + builtInResolver, + builtIns: Boolean(config.stats.builtIns), + buffer: config.stats.buffer, + }), /** * The `WatchPlugin` is a Webpack plugin that adds extra files to watch @@ -336,6 +366,39 @@ export async function getDefaultConfiguration( ], }, + /** + * The experiments configuration. This configures which Webpack + * experiments to enable/disable. + * + * @see https://webpack.js.org/configuration/experiments + */ + experiments: { + /** + * Experimental support for top level await. + * + * This is unsupported in Snaps and therefore disabled. + * + * @see https://webpack.js.org/configuration/experiments/#experimentstoplevelawait + */ + topLevelAwait: false, + }, + + /** + * The performance configuration. This tells Webpack how to handle + * performance hints. + * + * @see https://webpack.js.org/configuration/performance/ + */ + performance: { + /** + * The hints to show. We set it to `false`, so that we don't get + * performance hints, as they are not relevant for Snaps. + * + * @see https://webpack.js.org/configuration/performance/#performancehints + */ + hints: false, + }, + /** * The infrastructure logging configuration. This tells Webpack how to * log messages. diff --git a/packages/snaps-cli/src/webpack/loaders/function.test.ts b/packages/snaps-cli/src/webpack/loaders/function.test.ts new file mode 100644 index 0000000000..3574a4d242 --- /dev/null +++ b/packages/snaps-cli/src/webpack/loaders/function.test.ts @@ -0,0 +1,38 @@ +import { resolve } from 'path'; + +import loader, { getFunctionLoader } from './function'; + +describe('getFunctionLoader', () => { + it('returns a loader definition', () => { + const fn = jest.fn(); + expect( + getFunctionLoader(fn, { + foo: 'bar', + }), + ).toStrictEqual({ + loader: resolve(__dirname, 'function.ts'), + options: { + fn, + foo: 'bar', + }, + }); + }); +}); + +describe('loader', () => { + it('executes the function', async () => { + const fn = jest.fn(); + + await loader.call( + // @ts-expect-error - Partial `this` object. + { + getOptions: () => ({ + fn, + }), + }, + 'test', + ); + + expect(fn).toHaveBeenCalledWith('test'); + }); +}); diff --git a/packages/snaps-cli/src/webpack/loaders/function.ts b/packages/snaps-cli/src/webpack/loaders/function.ts new file mode 100644 index 0000000000..3350448a2c --- /dev/null +++ b/packages/snaps-cli/src/webpack/loaders/function.ts @@ -0,0 +1,61 @@ +import type { LoaderDefinitionFunction } from 'webpack'; + +/** + * Options for the function loader. + */ +export type FunctionLoaderOptions = { + /** + * The function to execute. This is bound to the loader context, so it can + * access the loader options and other properties. + */ + fn: LoaderDefinitionFunction; +}; + +/** + * A loader that executes a function. See {@link getFunctionLoader} for more + * information. + * + * @param content - The input file contents as a `Uint8Array`. + * @returns The output of the function. + */ +const loader: LoaderDefinitionFunction = function ( + content, +) { + const { fn } = this.getOptions(); + return fn.bind(this)(content); +}; + +export default loader; + +/** + * Get a loader that executes the given function. This is useful for executing + * loaders without needing to pass a file to Webpack. + * + * @param fn - The function to execute. + * @param options - The options to pass to the loader. + * @returns The loader definition. + */ +export function getFunctionLoader( + fn: LoaderDefinitionFunction, + options: Options, +) { + return { + // We use `__filename` as the loader, so Webpack will execute the loader in + // this file, with the actual function in the options. + loader: __filename, + options: { + fn, + ...options, + }, + }; +} + +// When running as CJS, we need to export the loader as a default export, since +// `tsup` exports it as `loader_default`. +// istanbul ignore next 3 +// eslint-disable-next-line n/no-process-env +if (typeof module !== 'undefined' && process?.env?.NODE_ENV !== 'test') { + module.exports = loader; + module.exports.getFunctionLoader = getFunctionLoader; + module.exports.raw = true; +} diff --git a/packages/snaps-cli/src/webpack/loaders/index.ts b/packages/snaps-cli/src/webpack/loaders/index.ts new file mode 100644 index 0000000000..7c3da56aff --- /dev/null +++ b/packages/snaps-cli/src/webpack/loaders/index.ts @@ -0,0 +1,3 @@ +export * from './function'; +export { default as browserify } from './browserify'; +export { default as wasm } from './wasm'; diff --git a/packages/snaps-cli/src/webpack/loaders/wasm.test.ts b/packages/snaps-cli/src/webpack/loaders/wasm.test.ts index 8e21a89014..38217725c9 100644 --- a/packages/snaps-cli/src/webpack/loaders/wasm.test.ts +++ b/packages/snaps-cli/src/webpack/loaders/wasm.test.ts @@ -65,6 +65,7 @@ describe('loader', () => { // @ts-expect-error - We don't need to mock the entire `this` object. const result = await loader.bind({ addDependency: jest.fn(), + resourcePath: join(__dirname, '__fixtures__', 'program.wasm'), // @ts-expect-error - The type of this function seems to be incorrect. })(source); diff --git a/packages/snaps-cli/src/webpack/loaders/wasm.ts b/packages/snaps-cli/src/webpack/loaders/wasm.ts index 75af43650d..2b0bc15609 100644 --- a/packages/snaps-cli/src/webpack/loaders/wasm.ts +++ b/packages/snaps-cli/src/webpack/loaders/wasm.ts @@ -1,6 +1,7 @@ /* eslint-disable no-restricted-globals */ import { assert } from '@metamask/utils'; +import { dirname, resolve } from 'path'; import type { LoaderDefinitionFunction } from 'webpack'; /** @@ -95,8 +96,9 @@ const loader: LoaderDefinitionFunction = async function loader( // Add the WASM import as a dependency so that Webpack will watch it for // changes. + const path = dirname(this.resourcePath); for (const name of Object.keys(imports)) { - this.addDependency(name); + this.addDependency(resolve(path, name)); } return ` diff --git a/packages/snaps-cli/src/webpack/plugins.test.ts b/packages/snaps-cli/src/webpack/plugins.test.ts index d3fb660e92..a59614a3ef 100644 --- a/packages/snaps-cli/src/webpack/plugins.test.ts +++ b/packages/snaps-cli/src/webpack/plugins.test.ts @@ -1,8 +1,8 @@ import { createFsFromVolume, Volume } from 'memfs'; import ora from 'ora'; import { promisify } from 'util'; -import type { Watching } from 'webpack'; -import { ProvidePlugin } from 'webpack'; +import type { Compiler, Watching } from 'webpack'; +import { WebpackError, ProvidePlugin } from 'webpack'; import * as evalImplementation from '../commands/eval/implementation'; import { compile, getCompiler } from '../test-utils'; @@ -63,6 +63,40 @@ describe('SnapsStatsPlugin', () => { expect(process.exitCode).toBe(1); }); + it('logs any warnings', async () => { + const log = jest.spyOn(console, 'warn').mockImplementation(); + + class AddWarningPlugin { + apply(compiler: Compiler) { + compiler.hooks.afterEmit.tap('AddWarningPlugin', (compilation) => { + compilation.warnings.push(new WebpackError('This is a warning.')); + }); + } + } + + await compile({ + code: ` + console.log('foo'); + `, + config: { + plugins: [ + new AddWarningPlugin(), + new SnapsStatsPlugin({ + verbose: false, + }), + ], + }, + }); + + expect(log).toHaveBeenCalledWith( + expect.stringMatching(/Compiled 1 file in \d+ms with 1 warning\./u), + ); + + expect(log).toHaveBeenCalledWith( + expect.stringContaining('This is a warning.'), + ); + }); + it('logs stack traces when verbose is enabled', async () => { const log = jest.spyOn(console, 'error').mockImplementation(); @@ -405,14 +439,13 @@ describe('SnapsBuiltInResolver', () => { }); describe('SnapsBundleWarningsPlugin', () => { - it('logs a message when built-in modules are unresolved', async () => { - const log = jest.spyOn(console, 'warn').mockImplementation(); + it('adds a warning when built-in modules are unresolved', async () => { const builtInResolver = new SnapsBuiltInResolver(); builtInResolver.unresolvedModules.add('fs'); builtInResolver.unresolvedModules.add('path'); - await compile({ + const { stats } = await compile({ code: ` import fs from 'fs'; import path from 'path'; @@ -432,20 +465,18 @@ describe('SnapsBundleWarningsPlugin', () => { }, }); - expect(log).toHaveBeenCalledWith(expect.stringContaining('fs')); - expect(log).toHaveBeenCalledWith(expect.stringContaining('path')); - expect(log).toHaveBeenCalledWith( - expect.stringMatching( - /The snap attempted to use one or more Node.js builtins, but no browser fallback has been provided\./u, - ), + expect(stats.warnings).toHaveLength(1); + expect(stats.warnings?.[0].message).toMatch( + /The snap attempted to use one or more Node.js builtins, but no browser fallback has been provided\./u, ); + expect(stats.warnings?.[0].details).toContain('fs'); + expect(stats.warnings?.[0].details).toContain('path'); }); - it('does not log a message when built-in modules are resolved', async () => { - const log = jest.spyOn(console, 'warn').mockImplementation(); + it('does not add a warning when built-in modules are resolved', async () => { const builtInResolver = new SnapsBuiltInResolver(); - await compile({ + const { stats } = await compile({ code: ` import fs from 'fs'; import path from 'path'; @@ -467,13 +498,11 @@ describe('SnapsBundleWarningsPlugin', () => { }, }); - expect(log).not.toHaveBeenCalled(); + expect(stats.warnings).toHaveLength(0); }); - it('does not log a message when there is no resolver plugin', async () => { - const log = jest.spyOn(console, 'warn').mockImplementation(); - - await compile({ + it('does add a warning when there is no resolver plugin', async () => { + const { stats } = await compile({ code: ` import fs from 'fs'; import path from 'path'; @@ -498,13 +527,11 @@ describe('SnapsBundleWarningsPlugin', () => { }, }); - expect(log).not.toHaveBeenCalled(); + expect(stats.warnings).toHaveLength(0); }); - it('logs a message when the bundle contains Buffer', async () => { - const log = jest.spyOn(console, 'warn').mockImplementation(); - - await compile({ + it('adds a warning when the bundle contains Buffer', async () => { + const { stats } = await compile({ code: ` console.log(Buffer); `, @@ -513,17 +540,14 @@ describe('SnapsBundleWarningsPlugin', () => { }, }); - expect(log).toHaveBeenCalledWith( - expect.stringContaining( - 'The snap attempted to use the Node.js Buffer global, which is not supported in the MetaMask Snaps CLI by default.', - ), + expect(stats.warnings).toHaveLength(1); + expect(stats.warnings?.[0].message).toMatch( + /The snap attempted to use the Node\.js Buffer global, which is not supported in the MetaMask Snaps CLI by default\./u, ); }); - it('does not log a message when the buffer option is disabled', async () => { - const log = jest.spyOn(console, 'warn').mockImplementation(); - - await compile({ + it('does add a warning when the buffer option is disabled', async () => { + const { stats } = await compile({ code: ` console.log(Buffer); `, @@ -532,13 +556,11 @@ describe('SnapsBundleWarningsPlugin', () => { }, }); - expect(log).not.toHaveBeenCalled(); + expect(stats.warnings).toHaveLength(0); }); - it('does not log a message when the bundle does not contain Buffer', async () => { - const log = jest.spyOn(console, 'warn').mockImplementation(); - - await compile({ + it('does add a warning when the bundle does not contain Buffer', async () => { + const { stats } = await compile({ code: ` console.log('Hello, world!'); `, @@ -547,13 +569,11 @@ describe('SnapsBundleWarningsPlugin', () => { }, }); - expect(log).not.toHaveBeenCalled(); + expect(stats.warnings).toHaveLength(0); }); it('does not log a message when Buffer is provided', async () => { - const log = jest.spyOn(console, 'warn').mockImplementation(); - - await compile({ + const { stats } = await compile({ code: ` console.log(Buffer); `, @@ -567,6 +587,6 @@ describe('SnapsBundleWarningsPlugin', () => { }, }); - expect(log).not.toHaveBeenCalled(); + expect(stats.warnings).toHaveLength(0); }); }); diff --git a/packages/snaps-cli/src/webpack/plugins.ts b/packages/snaps-cli/src/webpack/plugins.ts index 0a04571d01..ca90580c7e 100644 --- a/packages/snaps-cli/src/webpack/plugins.ts +++ b/packages/snaps-cli/src/webpack/plugins.ts @@ -1,6 +1,5 @@ -import { indent } from '@metamask/snaps-utils'; import { assert, hasProperty, isObject } from '@metamask/utils'; -import { dim, red, yellow } from 'chalk'; +import { bold, dim, red, yellow } from 'chalk'; import { isBuiltin } from 'module'; import type { Ora } from 'ora'; import type { @@ -11,10 +10,11 @@ import type { StatsError, WebpackPluginInstance, } from 'webpack'; +import { WebpackError } from 'webpack'; import { evaluate } from '../commands/eval'; import { error, getErrorMessage, info, warn } from '../utils'; -import { pluralize } from './utils'; +import { formatText, pluralize } from './utils'; export type SnapsStatsPluginOptions = { /** @@ -59,14 +59,14 @@ export class SnapsStatsPlugin implements WebpackPluginInstance { return; } - const { modules, time, errors } = stats.toJson(); + const { modules, time, errors, warnings } = stats.toJson(); assert(modules, 'Modules must be defined in stats.'); assert(time, 'Time must be defined in stats.'); if (errors?.length) { const formattedErrors = errors - .map(this.#getStatsErrorMessage.bind(this)) + .map((statsError) => this.#getStatsErrorMessage(statsError)) .join('\n\n'); error( @@ -86,13 +86,32 @@ export class SnapsStatsPlugin implements WebpackPluginInstance { return; } - info( - `Compiled ${modules.length} ${pluralize( - modules.length, - 'file', - )} in ${time}ms.`, - this.#spinner, - ); + if (warnings?.length) { + const formattedWarnings = warnings + .map((statsWarning) => + this.#getStatsErrorMessage(statsWarning, yellow), + ) + .join('\n\n'); + + warn( + `Compiled ${modules.length} ${pluralize( + modules.length, + 'file', + )} in ${time}ms with ${warnings.length} ${pluralize( + warnings.length, + 'warning', + )}.\n\n${formattedWarnings}\n`, + this.#spinner, + ); + } else { + info( + `Compiled ${modules.length} ${pluralize( + modules.length, + 'file', + )} in ${time}ms.`, + this.#spinner, + ); + } if (compiler.watchMode) { // The spinner may be restarted by the watch plugin, outside of the @@ -106,9 +125,10 @@ export class SnapsStatsPlugin implements WebpackPluginInstance { * Get the error message for the given stats error. * * @param statsError - The stats error. + * @param color - The color to use for the error message. * @returns The error message. */ - #getStatsErrorMessage(statsError: StatsError) { + #getStatsErrorMessage(statsError: StatsError, color = red) { const baseMessage = this.options.verbose ? getErrorMessage(statsError) : statsError.message; @@ -116,9 +136,9 @@ export class SnapsStatsPlugin implements WebpackPluginInstance { const [first, ...rest] = baseMessage.split('\n'); return [ - indent(red(`• ${first}`), 2), - ...rest.map((message) => indent(red(message), 4)), - statsError.details && indent(dim(statsError.details), 4), + color(formatText(`• ${first}`, 4, 2)), + ...rest.map((message) => formatText(color(message), 4)), + statsError.details && `\n${formatText(dim(statsError.details), 6)}`, ] .filter(Boolean) .join('\n'); @@ -334,11 +354,6 @@ export type SnapsBundleWarningsPluginOptions = { */ export class SnapsBundleWarningsPlugin implements WebpackPluginInstance { - /** - * The spinner to use for logging. - */ - readonly #spinner?: Ora; - /** * The options for the plugin. */ @@ -349,10 +364,8 @@ export class SnapsBundleWarningsPlugin implements WebpackPluginInstance { buffer: true, builtIns: true, }, - spinner?: Ora, ) { this.options = options; - this.#spinner = spinner; } /** @@ -377,7 +390,7 @@ export class SnapsBundleWarningsPlugin implements WebpackPluginInstance { * @param compiler - The Webpack compiler. */ #checkBuiltIns(compiler: Compiler) { - compiler.hooks.afterCompile.tap(this.constructor.name, () => { + compiler.hooks.afterCompile.tap(this.constructor.name, (compilation) => { if (!this.options.builtInResolver) { return; } @@ -388,27 +401,27 @@ export class SnapsBundleWarningsPlugin implements WebpackPluginInstance { } const formattedModules = new Array(...unresolvedModules) - .map((name) => indent(`• ${name}`, 2)) + .map((name) => `• ${name}`) .join('\n'); - warn( - `The snap attempted to use one or more Node.js builtins, but no browser fallback has been provided.\n` + - `The MetaMask Snaps CLI does not support Node.js builtins by default. If you want to use this module, you must set ${yellow( - `polyfills`, - )} to ${yellow( - `true`, - )} or an object with the builtins to polyfill as the key and ${yellow( - `true`, - )} as the value.\n` + - `To disable this warning, set ${yellow( - '`stats.builtIns`', - )} to ${yellow( - '`false`', - )} in your snap config file, or add the module to the ${yellow( - '`stats.builtIns.ignore`', - )} array.\n\n${formattedModules}\n`, - this.#spinner, + const webpackError = new WebpackError( + `The snap attempted to use one or more Node.js builtins, but no browser fallback has been provided. The MetaMask Snaps CLI does not support Node.js builtins by default. If you want to use this module, you must set ${bold( + '`polyfills`', + )} to ${bold( + '`true`', + )} or an object with the builtins to polyfill as the key and ${bold( + '`true`', + )} as the value. To disable this warning, set ${bold( + '`stats.builtIns`', + )} to ${bold( + '`false`', + )} in your snap config file, or add the module to the ${bold( + '`stats.builtIns.ignore`', + )} array.`, ); + + webpackError.details = formattedModules; + compilation.warnings.push(webpackError); }); } @@ -463,17 +476,16 @@ export class SnapsBundleWarningsPlugin implements WebpackPluginInstance { return; } - warn( - `The snap attempted to use the Node.js Buffer global, which is not supported in the MetaMask Snaps CLI by default.\n` + - `To use the Buffer global, you must polyfill Buffer by setting ${yellow( - `buffer`, - )} to ${yellow(`true`)} in the ${yellow( - `polyfills`, - )} config object in your snap config.\n` + - `To disable this warning, set ${yellow( + compilation.warnings.push( + new WebpackError( + `The snap attempted to use the Node.js Buffer global, which is not supported in the MetaMask Snaps CLI by default. To use the Buffer global, you must polyfill Buffer by setting ${bold( + '`buffer`', + )} to ${bold('`true`')} in the ${bold( + '`polyfills`', + )} config object in your snap config. To disable this warning, set ${bold( '`stats.buffer`', - )} to ${yellow('`false`')} in your snap config file.`, - this.#spinner, + )} to ${bold('`false`')} in your snap config file.`, + ), ); }, ); diff --git a/packages/snaps-cli/src/webpack/server.ts b/packages/snaps-cli/src/webpack/server.ts index eb01d0e96b..8ca45588d3 100644 --- a/packages/snaps-cli/src/webpack/server.ts +++ b/packages/snaps-cli/src/webpack/server.ts @@ -3,7 +3,7 @@ import { logError, NpmSnapFileNames, readJsonFile, -} from '@metamask/snaps-utils'; +} from '@metamask/snaps-utils/node'; import type { IncomingMessage, Server, ServerResponse } from 'http'; import { createServer } from 'http'; import type { AddressInfo } from 'net'; diff --git a/packages/snaps-cli/src/webpack/utils.test.ts b/packages/snaps-cli/src/webpack/utils.test.ts index c89f64a888..26a440f697 100644 --- a/packages/snaps-cli/src/webpack/utils.test.ts +++ b/packages/snaps-cli/src/webpack/utils.test.ts @@ -2,6 +2,7 @@ import { dim } from 'chalk'; import type { ProcessedWebpackConfig } from '../config'; import { getMockConfig } from '../test-utils'; +import { browserify } from './loaders'; import { WEBPACK_FALLBACKS, getBrowserslistTargets, @@ -10,14 +11,20 @@ import { getFallbacks, getProgressHandler, pluralize, + getEnvironmentVariables, + formatText, + getImageSVG, } from './utils'; describe('getDefaultLoader', () => { it('returns the Browserify loader if `legacy` is set', async () => { const config = getMockConfig('browserify'); expect(await getDefaultLoader(config)).toStrictEqual({ - loader: expect.stringContaining('browserify'), - options: config.legacy, + loader: expect.stringContaining('function'), + options: { + ...config.legacy, + fn: browserify, + }, }); }); @@ -127,3 +134,115 @@ describe('getFallbacks', () => { expect(fallbacks.fs).toBe(false); }); }); + +describe('getEnvironmentVariables', () => { + it('returns the environment variables', () => { + const env = getEnvironmentVariables({ + NODE_ENV: 'development', + API_URL: 'https://example.com', + }); + + expect(env).toMatchInlineSnapshot(` + { + "process.env.API_URL": ""https://example.com"", + "process.env.DEBUG": ""false"", + "process.env.NODE_DEBUG": ""false"", + "process.env.NODE_ENV": ""development"", + } + `); + }); +}); + +describe('formatText', () => { + let originalColumns: number | undefined; + beforeAll(() => { + originalColumns = process.stdout.columns; + process.stdout.columns = 40; + }); + + afterAll(() => { + // @ts-expect-error - According to the type, `columns` cannot be undefined. + process.stdout.columns = originalColumns; + }); + + it('formats the text', () => { + expect( + formatText( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eget nulla mattis, sollicitudin enim tincidunt, vulputate libero. Pellentesque neque sapien, lobortis eu elit in, suscipit aliquet augue.', + 2, + ), + ).toMatchInlineSnapshot(` + " Lorem ipsum dolor sit amet, + consectetur adipiscing elit. Nam eget + nulla mattis, sollicitudin enim + tincidunt, vulputate libero. + Pellentesque neque sapien, lobortis eu + elit in, suscipit aliquet augue." + `); + }); + + it('formats the text with new lines', () => { + expect( + formatText( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eget nulla mattis, sollicitudin enim tincidunt, vulputate libero.\nPellentesque neque sapien, lobortis eu elit in, suscipit aliquet augue.', + 2, + ), + ).toMatchInlineSnapshot(` + " Lorem ipsum dolor sit amet, + consectetur adipiscing elit. Nam eget + nulla mattis, sollicitudin enim + tincidunt, vulputate libero. + Pellentesque neque sapien, lobortis eu + elit in, suscipit aliquet augue." + `); + }); + + it('formats the text with a custom initial indentation', () => { + process.stdout.columns = 40; + expect( + formatText( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eget nulla mattis, sollicitudin enim tincidunt, vulputate libero. Pellentesque neque sapien, lobortis eu elit in, suscipit aliquet augue.', + 4, + 2, + ), + ).toMatchInlineSnapshot(` + " Lorem ipsum dolor sit amet, + consectetur adipiscing elit. Nam + eget nulla mattis, sollicitudin enim + tincidunt, vulputate libero. + Pellentesque neque sapien, lobortis + eu elit in, suscipit aliquet augue." + `); + }); + + it('indents the text if the terminal width is not set', () => { + // @ts-expect-error - According to the type, `columns` cannot be undefined. + process.stdout.columns = undefined; + + expect( + formatText( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eget nulla mattis, sollicitudin enim tincidunt, vulputate libero.\nPellentesque neque sapien, lobortis eu elit in, suscipit aliquet augue.', + 2, + ), + ).toMatchInlineSnapshot(` + " Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eget nulla mattis, sollicitudin enim tincidunt, vulputate libero. + Pellentesque neque sapien, lobortis eu elit in, suscipit aliquet augue." + `); + }); +}); + +describe('getImageSVG', () => { + it('returns an SVG string for a PNG image', () => { + const image = getImageSVG('image/png', new Uint8Array([1, 2, 3])); + expect(image).toMatchInlineSnapshot( + `""`, + ); + }); + + it('returns an SVG string for a JPEG image', () => { + const image = getImageSVG('image/jpeg', new Uint8Array([1, 2, 3])); + expect(image).toMatchInlineSnapshot( + `""`, + ); + }); +}); diff --git a/packages/snaps-cli/src/webpack/utils.ts b/packages/snaps-cli/src/webpack/utils.ts index 74283ef631..3ea8422cb7 100644 --- a/packages/snaps-cli/src/webpack/utils.ts +++ b/packages/snaps-cli/src/webpack/utils.ts @@ -1,11 +1,14 @@ +import { bytesToBase64 } from '@metamask/utils'; import { dim } from 'chalk'; import { promises as fs } from 'fs'; import { builtinModules } from 'module'; import type { Ora } from 'ora'; import { dirname, resolve } from 'path'; +import stripAnsi from 'strip-ansi'; import type { Configuration } from 'webpack'; import type { ProcessedWebpackConfig } from '../config'; +import { browserify, getFunctionLoader } from './loaders'; export const BROWSERSLIST_FILE = resolve( dirname( @@ -71,21 +74,13 @@ export async function getDefaultLoader({ sourceMap, }: ProcessedWebpackConfig) { if (legacy) { - return { - /** - * If the snap uses the legacy config, we use the custom `browserify` - * loader. This uses the legacy Browserify config to transpile the code. - * This is necessary for backwards compatibility with the - * `bundlerCustomizer` function. - */ - loader: resolve(__dirname, 'loaders', 'browserify'), - - /** - * The options for the `browserify` loader. These can be overridden in the - * snap config. - */ - options: legacy, - }; + /** + * If the snap uses the legacy config, we use the custom `browserify` + * loader. This uses the legacy Browserify config to transpile the code. + * This is necessary for backwards compatibility with the + * `bundlerCustomizer` function. + */ + return getFunctionLoader(browserify, legacy); } const targets = await getBrowserslistTargets(); @@ -136,12 +131,14 @@ export async function getDefaultLoader({ */ module: { /** - * This tells SWC to output CommonJS modules. MetaMask Snaps - * doesn't support ES modules yet, so this is necessary. + * This tells SWC to output ES6 modules. This will allow Webpack to + * optimize the output code better. Snaps don't support ES6 however, so + * the output code will be transpiled to CommonJS by Webpack later in + * the build process. * - * @see https://swc.rs/docs/configuration/modules#commonjs + * @see https://swc.rs/docs/configuration/modules#es6 */ - type: 'commonjs', + type: 'es6', }, env: { @@ -269,3 +266,111 @@ export function getFallbacks(polyfills: ProcessedWebpackConfig['polyfills']): { ]), ); } + +/** + * Get an object that can be used as environment variables for Webpack's + * `DefinePlugin`. + * + * @param environment - The environment object from the Snap config. + * @param defaults - The default environment variables. + * @returns The Webpack environment variables. + */ +export function getEnvironmentVariables( + environment: Record, + defaults = { + NODE_DEBUG: 'false', + NODE_ENV: 'production', + DEBUG: 'false', + }, +) { + return Object.fromEntries( + Object.entries({ + ...defaults, + ...environment, + }).map(([key, value]) => [`process.env.${key}`, JSON.stringify(value)]), + ); +} + +/** + * Format the given line to fit within the terminal width. + * + * @param line - The line to format. + * @param indent - The indentation to use. + * @param initialIndent - The initial indentation to use, i.e., the indentation + * for the first line. + * @returns The formatted line. + */ +function formatLine(line: string, indent: number, initialIndent: number) { + const terminalWidth = process.stdout.columns; + if (!terminalWidth) { + return `${' '.repeat(initialIndent)}${line}`; + } + + return line.split(' ').reduce( + ({ formattedText, currentLineLength }, word, index) => { + // `chalk` adds ANSI escape codes to the text, which are not visible + // characters. We need to strip them to get the visible length of the + // text. + const visibleWord = stripAnsi(word); + + // Determine if a space should be added before the word. + const spaceBeforeWord = index > 0 ? ' ' : ''; + const wordLengthWithSpace = visibleWord.length + spaceBeforeWord.length; + + // If the word would exceed the terminal width, start a new line. + if (currentLineLength + wordLengthWithSpace > terminalWidth) { + return { + formattedText: `${formattedText}\n${' '.repeat(indent)}${word}`, + currentLineLength: indent + visibleWord.length, + }; + } + + // Otherwise, add the word to the current line. + return { + formattedText: formattedText + spaceBeforeWord + word, + currentLineLength: currentLineLength + wordLengthWithSpace, + }; + }, + { + formattedText: ' '.repeat(initialIndent), + currentLineLength: initialIndent, + }, + ).formattedText; +} + +/** + * Format the given text to fit within the terminal width. + * + * @param text - The text to format. + * @param indent - The indentation to use. + * @param initialIndent - The initial indentation to use, i.e., the indentation + * for the first line. + * @returns The formatted text. + */ +export function formatText( + text: string, + indent: number, + initialIndent = indent, +) { + const lines = text.split('\n'); + + // Apply formatting to each line separately and then join them. + return lines + .map((line, index) => { + const lineIndent = index === 0 ? initialIndent : indent; + return formatLine(line, indent, lineIndent); + }) + .join('\n'); +} + +/** + * Get an SVG from the given bytes and mime type. + * + * @param mimeType - The mime type of the image. + * @param bytes - The image bytes. + * @returns The SVG. + */ +export function getImageSVG(mimeType: string, bytes: Uint8Array) { + const dataUrl = `data:${mimeType};base64,${bytesToBase64(bytes)}`; + return ``; +} diff --git a/packages/snaps-cli/tsconfig.json b/packages/snaps-cli/tsconfig.json index 526029de4f..cf0ed65956 100644 --- a/packages/snaps-cli/tsconfig.json +++ b/packages/snaps-cli/tsconfig.json @@ -3,7 +3,13 @@ "compilerOptions": { "baseUrl": "./" }, - "include": ["./src", "./src/**/*.json", "jest.setup.ts"], + "include": [ + "./src", + "./src/**/*.json", + "jest.setup.ts", + "package.json", + "tsup.config.ts" + ], "references": [ { "path": "../snaps-utils" }, { "path": "../snaps-browserify-plugin" }, diff --git a/packages/snaps-cli/tsup.config.ts b/packages/snaps-cli/tsup.config.ts new file mode 100644 index 0000000000..3cfca96324 --- /dev/null +++ b/packages/snaps-cli/tsup.config.ts @@ -0,0 +1,16 @@ +import deepmerge from 'deepmerge'; +import type { Options } from 'tsup'; + +import packageJson from './package.json'; + +// `tsup.config.ts` is not under `rootDir`, so we need to use `require` instead. +// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires +const { default: baseConfig } = require('../../tsup.config'); + +const config: Options = { + name: packageJson.name, + external: ['@metamask/snaps-cli'], + platform: 'node', +}; + +export default deepmerge(baseConfig, config); diff --git a/packages/snaps-controllers/CHANGELOG.md b/packages/snaps-controllers/CHANGELOG.md index b324d48afe..18bab2a620 100644 --- a/packages/snaps-controllers/CHANGELOG.md +++ b/packages/snaps-controllers/CHANGELOG.md @@ -6,6 +6,64 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [8.0.0] +### Changed +- **BREAKING:** Move `maxInitTime` constructor argument from `SnapController` to `ExecutionService` ([#2348](https://github.com/MetaMask/snaps/pull/2348)) + +### Fixed +- Increase max UI size limit from 250 KB to 10 MB ([#2342](https://github.com/MetaMask/snaps/pull/2342)) +- Consider caveats in permissions difference calculation ([#2345](https://github.com/MetaMask/snaps/pull/2345)) + - This fixes a bug where certain caveats would not be correctly applied when updating Snaps. +- Gracefully handle errors for multiple simultaneous failing requests ([#2346](https://github.com/MetaMask/snaps/pull/2346)) +- Properly handle termination of Snaps that are currently executing ([#2304](https://github.com/MetaMask/snaps/pull/2304)) +- Properly tear down partially initialized executors and improve stability when executor initialization fails ([#2348](https://github.com/MetaMask/snaps/pull/2348)) + +## [7.0.1] +### Fixed +- Fix encryption key caching issues ([#2326](https://github.com/MetaMask/snaps/pull/2326)) + +## [7.0.0] +### Changed +- **BREAKING:** Refactor encryption to enable caching ([#2316](https://github.com/MetaMask/snaps/pull/2316)) + - New required constructor arguments `encryptor` and `getMnemonic` have been added. +- Include `initialConnections` in approval `requestState` ([#2322](https://github.com/MetaMask/snaps/pull/2322)) + +### Fixed +- Delete unencrypted state when uninstalling a Snap ([#2311](https://github.com/MetaMask/snaps/pull/2311)) + +## [6.0.4] +### Changed +- Bump MetaMask dependencies ([#2270](https://github.com/MetaMask/snaps/pull/2270)) + +## [6.0.3] +### Changed +- Handle unavailable registry more gracefully ([#2256](https://github.com/MetaMask/snaps/pull/2256)) +- Bump `@metamask/snaps-registry` to `^3.0.1` ([#2255](https://github.com/MetaMask/snaps/pull/2255)) +- Bump `@metamask/json-rpc-engine` to `^7.3.3` ([#2247](https://github.com/MetaMask/snaps/pull/2247)) + +## [6.0.2] +### Changed +- Improve timeout handling when the execution environment fails to load ([#2242](https://github.com/MetaMask/snaps/pull/2242)) + +## [6.0.1] +### Fixed +- Fix minor build configuration problems ([#2220](https://github.com/MetaMask/snaps/pull/2220)) + +## [6.0.0] +### Changed +- **BREAKING:** Update ESM build to be fully compliant with the ESM standard ([#2210](https://github.com/MetaMask/snaps/pull/2210)) +- **BREAKING:** Move Node.js exports to separate export ([#2210](https://github.com/MetaMask/snaps/pull/2210)) + - The default export is now browser-compatible. + - Node.js APIs can be imported from `/node`. +- Bump `@metamask/rpc-errors` to `^6.2.1` ([#2209](https://github.com/MetaMask/snaps/pull/2209)) + +### Fixed +- Add sizing limits for custom UI ([#2199](https://github.com/MetaMask/snaps/pull/2199)) + +## [5.0.1] +### Fixed +- Fix issue installing non-allowlisted Snaps in allowlist mode ([#2196](https://github.com/MetaMask/snaps/pull/2196)) + ## [5.0.0] ### Added - Add support for dynamic user interfaces ([#1465](https://github.com/MetaMask/snaps/pull/1465), [#2126](https://github.com/MetaMask/snaps/pull/2126), [#2144](https://github.com/MetaMask/snaps/pull/2144), [#2152](https://github.com/MetaMask/snaps/pull/2152), [#2143](https://github.com/MetaMask/snaps/pull/2143)) @@ -188,7 +246,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@5.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@8.0.0...HEAD +[8.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@7.0.1...@metamask/snaps-controllers@8.0.0 +[7.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@7.0.0...@metamask/snaps-controllers@7.0.1 +[7.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@6.0.4...@metamask/snaps-controllers@7.0.0 +[6.0.4]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@6.0.3...@metamask/snaps-controllers@6.0.4 +[6.0.3]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@6.0.2...@metamask/snaps-controllers@6.0.3 +[6.0.2]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@6.0.1...@metamask/snaps-controllers@6.0.2 +[6.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@6.0.0...@metamask/snaps-controllers@6.0.1 +[6.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@5.0.1...@metamask/snaps-controllers@6.0.0 +[5.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@5.0.0...@metamask/snaps-controllers@5.0.1 [5.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@4.1.0...@metamask/snaps-controllers@5.0.0 [4.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@4.0.0...@metamask/snaps-controllers@4.1.0 [4.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@3.6.0...@metamask/snaps-controllers@4.0.0 diff --git a/packages/snaps-controllers/coverage.json b/packages/snaps-controllers/coverage.json index 07490e0a43..75adb5c738 100644 --- a/packages/snaps-controllers/coverage.json +++ b/packages/snaps-controllers/coverage.json @@ -1,6 +1,6 @@ { - "branches": 90.97, - "functions": 96.56, - "lines": 97.78, - "statements": 97.44 + "branches": 91.55, + "functions": 96.67, + "lines": 97.89, + "statements": 97.57 } diff --git a/packages/snaps-controllers/package.json b/packages/snaps-controllers/package.json index 8324855a4b..13abbc1838 100644 --- a/packages/snaps-controllers/package.json +++ b/packages/snaps-controllers/package.json @@ -1,23 +1,35 @@ { "name": "@metamask/snaps-controllers", - "version": "5.0.0", + "version": "8.0.0", "description": "Controllers for MetaMask Snaps.", "repository": { "type": "git", "url": "https://github.com/MetaMask/snaps.git" }, "sideEffects": false, - "main": "./dist/cjs/index.js", - "module": "./dist/esm/index.js", - "browser": { - "./dist/cjs/services": "./dist/cjs/services/browser.js", - "./dist/esm/services": "./dist/esm/services/browser.js" + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/types/index.d.ts" + }, + "./node": { + "import": "./dist/node.mjs", + "require": "./dist/node.js", + "types": "./dist/types/node.d.ts" + }, + "./react-native": { + "import": "./dist/react-native.mjs", + "require": "./dist/react-native.js", + "types": "./dist/types/react-native.d.ts" + }, + "./package.json": "./package.json" }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", "types": "./dist/types/index.d.ts", "files": [ - "dist/cjs/**", - "dist/esm/**", - "dist/types/**" + "dist" ], "scripts": { "test:prepare": "yarn mkdirp test/fixtures && ./scripts/generate-fixtures.sh", @@ -25,12 +37,8 @@ "posttest": "ts-node scripts/coverage.ts && rimraf coverage/jest coverage/wdio", "test:browser": "wdio run wdio.config.js", "test:ci": "yarn test", - "build": "yarn build:source && yarn build:types", - "build:source": "yarn build:esm && yarn build:cjs", + "build": "tsup --clean && yarn build:types", "build:types": "tsc --project tsconfig.build.json", - "build:esm": "swc src --out-dir dist/esm --config-file ../../.swcrc.build.json --config module.type=es6", - "build:cjs": "swc src --out-dir dist/cjs --config-file ../../.swcrc.build.json --config module.type=commonjs", - "build:clean": "yarn clean && yarn build", "clean": "rimraf '*.tsbuildinfo' 'dist'", "lint:eslint": "eslint . --cache --ext js,ts,jsx,tsx", "lint:misc": "prettier --no-error-on-unmatched-pattern --loglevel warn \"**/*.json\" \"**/*.md\" \"**/*.html\" \"!CHANGELOG.md\" --ignore-path ../../.gitignore", @@ -39,18 +47,20 @@ "lint:changelog": "../../scripts/validate-changelog.sh @metamask/snaps-controllers", "publish:preview": "yarn npm publish --tag preview", "lint:ci": "yarn lint", - "lint:dependencies": "depcheck" + "lint:dependencies": "depcheck", + "build:ci": "tsup --clean" }, "dependencies": { - "@metamask/approval-controller": "^5.1.2", - "@metamask/base-controller": "^4.1.0", - "@metamask/json-rpc-engine": "^7.3.2", + "@metamask/approval-controller": "^6.0.1", + "@metamask/base-controller": "^5.0.1", + "@metamask/json-rpc-engine": "^8.0.1", + "@metamask/json-rpc-middleware-stream": "^7.0.1", "@metamask/object-multiplex": "^2.0.0", - "@metamask/permission-controller": "^8.0.0", - "@metamask/phishing-controller": "^8.0.2", + "@metamask/permission-controller": "^9.0.2", + "@metamask/phishing-controller": "^9.0.1", "@metamask/post-message-stream": "^8.0.0", - "@metamask/rpc-errors": "^6.1.0", - "@metamask/snaps-registry": "^3.0.0", + "@metamask/rpc-errors": "^6.2.1", + "@metamask/snaps-registry": "^3.1.0", "@metamask/snaps-rpc-methods": "workspace:^", "@metamask/snaps-sdk": "workspace:^", "@metamask/snaps-utils": "workspace:^", @@ -58,9 +68,9 @@ "@xstate/fsm": "^2.0.0", "browserify-zlib": "^0.2.0", "concat-stream": "^2.0.0", + "fast-deep-equal": "^3.1.3", "get-npm-tarball-url": "^2.0.3", "immer": "^9.0.6", - "json-rpc-middleware-stream": "^5.0.0", "nanoid": "^3.1.31", "readable-stream": "^3.6.2", "readable-web-to-node-stream": "^3.0.2", @@ -69,14 +79,14 @@ "devDependencies": { "@esbuild-plugins/node-globals-polyfill": "^0.2.3", "@esbuild-plugins/node-modules-polyfill": "^0.2.2", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", + "@metamask/browser-passworder": "^5.0.0", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", "@metamask/eslint-config-nodejs": "^12.1.0", "@metamask/eslint-config-typescript": "^12.1.0", "@metamask/template-snap": "^0.7.0", - "@swc/cli": "^0.1.62", "@swc/core": "1.3.78", "@swc/jest": "^0.2.26", "@types/chrome": "^0.0.237", @@ -117,6 +127,7 @@ "prettier-plugin-packagejson": "^2.2.11", "rimraf": "^4.1.2", "ts-node": "^10.9.1", + "tsup": "^8.0.1", "typescript": "~4.8.4", "vite": "^4.3.9", "vite-tsconfig-paths": "^4.0.5", diff --git a/packages/snaps-controllers/src/interface/SnapInterfaceController.test.ts b/packages/snaps-controllers/src/interface/SnapInterfaceController.test.ts index 04a34eafb4..e1340e3b77 100644 --- a/packages/snaps-controllers/src/interface/SnapInterfaceController.test.ts +++ b/packages/snaps-controllers/src/interface/SnapInterfaceController.test.ts @@ -1,5 +1,5 @@ import type { SnapId } from '@metamask/snaps-sdk'; -import { form, input, panel, text } from '@metamask/snaps-sdk'; +import { form, image, input, panel, text } from '@metamask/snaps-sdk'; import { MOCK_SNAP_ID } from '@metamask/snaps-utils/test-utils'; import { @@ -104,6 +104,53 @@ describe('SnapInterfaceController', () => { 'foo.bar', ); }); + + it('throws if UI content is too large', async () => { + const rootMessenger = getRootSnapInterfaceControllerMessenger(); + const controllerMessenger = getRestrictedSnapInterfaceControllerMessenger( + rootMessenger, + false, + ); + + /* eslint-disable-next-line no-new */ + new SnapInterfaceController({ + messenger: controllerMessenger, + }); + + const images = new Array(800_000).fill(image(``)); + const components = panel(images); + + await expect( + rootMessenger.call( + 'SnapInterfaceController:createInterface', + MOCK_SNAP_ID, + components, + ), + ).rejects.toThrow('A Snap UI may not be larger than 10 MB.'); + }); + + it('throws if text content is too large', async () => { + const rootMessenger = getRootSnapInterfaceControllerMessenger(); + const controllerMessenger = getRestrictedSnapInterfaceControllerMessenger( + rootMessenger, + false, + ); + + /* eslint-disable-next-line no-new */ + new SnapInterfaceController({ + messenger: controllerMessenger, + }); + + const components = panel([text('[foo](https://foo.bar)'.repeat(2500))]); + + await expect( + rootMessenger.call( + 'SnapInterfaceController:createInterface', + MOCK_SNAP_ID, + components, + ), + ).rejects.toThrow('The text in a Snap UI may not be larger than 50 kB.'); + }); }); describe('getInterface', () => { @@ -292,6 +339,73 @@ describe('SnapInterfaceController', () => { ); }); + it('throws if UI content is too large', async () => { + const rootMessenger = getRootSnapInterfaceControllerMessenger(); + const controllerMessenger = + getRestrictedSnapInterfaceControllerMessenger(rootMessenger); + + /* eslint-disable-next-line no-new */ + new SnapInterfaceController({ + messenger: controllerMessenger, + }); + + const components = form({ + name: 'foo', + children: [input({ name: 'bar' })], + }); + + const images = new Array(800_000).fill(image(``)); + const newContent = panel(images); + + const id = await rootMessenger.call( + 'SnapInterfaceController:createInterface', + MOCK_SNAP_ID, + components, + ); + + await expect( + rootMessenger.call( + 'SnapInterfaceController:updateInterface', + MOCK_SNAP_ID, + id, + newContent, + ), + ).rejects.toThrow('A Snap UI may not be larger than 10 MB.'); + }); + + it('throws if text content is too large', async () => { + const rootMessenger = getRootSnapInterfaceControllerMessenger(); + const controllerMessenger = + getRestrictedSnapInterfaceControllerMessenger(rootMessenger); + + /* eslint-disable-next-line no-new */ + new SnapInterfaceController({ + messenger: controllerMessenger, + }); + + const components = form({ + name: 'foo', + children: [input({ name: 'bar' })], + }); + + const newContent = panel([text('[foo](https://foo.bar)'.repeat(2500))]); + + const id = await rootMessenger.call( + 'SnapInterfaceController:createInterface', + MOCK_SNAP_ID, + components, + ); + + await expect( + rootMessenger.call( + 'SnapInterfaceController:updateInterface', + MOCK_SNAP_ID, + id, + newContent, + ), + ).rejects.toThrow('The text in a Snap UI may not be larger than 50 kB.'); + }); + it('throws if the interface does not exist', async () => { const rootMessenger = getRootSnapInterfaceControllerMessenger(); const controllerMessenger = diff --git a/packages/snaps-controllers/src/interface/SnapInterfaceController.ts b/packages/snaps-controllers/src/interface/SnapInterfaceController.ts index 1dd0cecae1..0b3b074b26 100644 --- a/packages/snaps-controllers/src/interface/SnapInterfaceController.ts +++ b/packages/snaps-controllers/src/interface/SnapInterfaceController.ts @@ -5,12 +5,19 @@ import type { TestOrigin, } from '@metamask/phishing-controller'; import type { Component, InterfaceState, SnapId } from '@metamask/snaps-sdk'; -import { validateComponentLinks } from '@metamask/snaps-utils'; +import { + getJsonSizeUnsafe, + getTotalTextLength, + validateComponentLinks, +} from '@metamask/snaps-utils'; import { assert } from '@metamask/utils'; import { nanoid } from 'nanoid'; import { constructState } from './utils'; +const MAX_UI_CONTENT_SIZE = 10_000_000; // 10 mb +const MAX_TEXT_LENGTH = 50_000; // 50 kb + const controllerName = 'SnapInterfaceController'; export type CreateInterface = { @@ -247,11 +254,27 @@ export class SnapInterfaceController extends BaseController< * Utility function to validate the components of an interface. * Throws if something is invalid. * - * Right now this only checks links against the phighing list. - * * @param content - The components to verify. */ async #validateContent(content: Component) { + // We assume the validity of this JSON to be validated by the caller. + // E.g. in the RPC method implementation. + const size = getJsonSizeUnsafe(content); + + assert( + size <= MAX_UI_CONTENT_SIZE, + `A Snap UI may not be larger than ${MAX_UI_CONTENT_SIZE / 1000000} MB.`, + ); + + const textSize = getTotalTextLength(content); + + assert( + textSize <= MAX_TEXT_LENGTH, + `The text in a Snap UI may not be larger than ${ + MAX_TEXT_LENGTH / 1000 + } kB.`, + ); + await this.#triggerPhishingListUpdate(); validateComponentLinks(content, this.#checkPhishingList.bind(this)); diff --git a/packages/snaps-controllers/src/node.ts b/packages/snaps-controllers/src/node.ts new file mode 100644 index 0000000000..b3c6706f5d --- /dev/null +++ b/packages/snaps-controllers/src/node.ts @@ -0,0 +1,4 @@ +/* eslint-disable import/export */ + +export * from '.'; +export * from './services/node'; diff --git a/packages/snaps-controllers/src/react-native.ts b/packages/snaps-controllers/src/react-native.ts new file mode 100644 index 0000000000..7112e72260 --- /dev/null +++ b/packages/snaps-controllers/src/react-native.ts @@ -0,0 +1,4 @@ +/* eslint-disable import/export */ + +export * from '.'; +export * from './services/react-native'; diff --git a/packages/snaps-controllers/src/services/AbstractExecutionService.test.ts b/packages/snaps-controllers/src/services/AbstractExecutionService.test.ts index 695b806856..49dc43740c 100644 --- a/packages/snaps-controllers/src/services/AbstractExecutionService.test.ts +++ b/packages/snaps-controllers/src/services/AbstractExecutionService.test.ts @@ -1,5 +1,7 @@ +import { BasePostMessageStream } from '@metamask/post-message-stream'; import { HandlerType } from '@metamask/snaps-utils'; import { MOCK_SNAP_ID } from '@metamask/snaps-utils/test-utils'; +import { Duration, inMilliseconds } from '@metamask/utils'; import { createService } from '../test-utils'; import type { ExecutionServiceArgs } from './AbstractExecutionService'; @@ -10,6 +12,7 @@ class MockExecutionService extends NodeThreadExecutionService { super({ messenger, setupSnapProvider, + initTimeout: inMilliseconds(5, Duration.Second), }); } @@ -31,9 +34,9 @@ describe('AbstractExecutionService', () => { await service.executeSnap({ snapId: 'TestSnap', sourceCode: ` - console.log('foo'); + module.exports.onRpcRequest = () => null; `, - endowments: ['console'], + endowments: [], }); const { streams } = service.getJobs().values().next().value; @@ -58,9 +61,9 @@ describe('AbstractExecutionService', () => { await service.executeSnap({ snapId: 'TestSnap', sourceCode: ` - console.log('foo'); + module.exports.onRpcRequest = () => null; `, - endowments: ['console'], + endowments: [], }); const { streams } = service.getJobs().values().next().value; @@ -108,9 +111,9 @@ describe('AbstractExecutionService', () => { await service.executeSnap({ snapId: MOCK_SNAP_ID, sourceCode: ` - console.log('foo'); + module.exports.onRpcRequest = () => null; `, - endowments: ['console'], + endowments: [], }); await expect( @@ -126,5 +129,65 @@ describe('AbstractExecutionService', () => { ).rejects.toThrow( 'Invalid JSON-RPC request: At path: params -- Expected the value to satisfy a union of `record | array`, but received: [object Object].', ); + + await service.terminateAllSnaps(); + }); + + it('throws an error if execution environment fails to respond to ping', async () => { + const { service } = createService(MockExecutionService); + + class MockStream extends BasePostMessageStream { + protected _postMessage(_data?: unknown): void { + // no-op + } + } + + // @ts-expect-error Accessing private property and returning unusable worker. + service.initEnvStream = async () => + Promise.resolve({ worker: null, stream: new MockStream() }); + + await expect( + service.executeSnap({ + snapId: MOCK_SNAP_ID, + sourceCode: ` + console.log('foo'); + `, + endowments: ['console'], + }), + ).rejects.toThrow('The Snaps execution environment failed to start.'); + }); + + it('throws an error if execution environment fails to init', async () => { + const { service } = createService(MockExecutionService); + + // @ts-expect-error Accessing private property and returning unusable worker. + service.initEnvStream = async () => + new Promise((_resolve) => { + // no-op + }); + + await expect( + service.executeSnap({ + snapId: MOCK_SNAP_ID, + sourceCode: ` + console.log('foo'); + `, + endowments: ['console'], + }), + ).rejects.toThrow('The Snaps execution environment failed to start.'); + }); + + it('throws an error if Snap fails to init', async () => { + const { service } = createService(MockExecutionService); + + await expect( + service.executeSnap({ + snapId: MOCK_SNAP_ID, + sourceCode: ` + while(true) {} + `, + endowments: ['console'], + }), + ).rejects.toThrow(`${MOCK_SNAP_ID} failed to start.`); }); }); diff --git a/packages/snaps-controllers/src/services/AbstractExecutionService.ts b/packages/snaps-controllers/src/services/AbstractExecutionService.ts index 9a751b9cd1..b4486dd628 100644 --- a/packages/snaps-controllers/src/services/AbstractExecutionService.ts +++ b/packages/snaps-controllers/src/services/AbstractExecutionService.ts @@ -1,4 +1,5 @@ import { JsonRpcEngine } from '@metamask/json-rpc-engine'; +import { createStreamMiddleware } from '@metamask/json-rpc-middleware-stream'; import ObjectMultiplex from '@metamask/object-multiplex'; import type { BasePostMessageStream } from '@metamask/post-message-stream'; import { JsonRpcError } from '@metamask/rpc-errors'; @@ -13,15 +14,16 @@ import type { import { Duration, assertIsJsonRpcRequest, + inMilliseconds, isJsonRpcNotification, isObject, } from '@metamask/utils'; -import { createStreamMiddleware } from 'json-rpc-middleware-stream'; import { nanoid } from 'nanoid'; import { pipeline } from 'readable-stream'; import type { Duplex } from 'readable-stream'; import { log } from '../logging'; +import { Timer } from '../snaps/Timer'; import { hasTimedOut, withTimeout } from '../utils'; import type { ExecutionService, @@ -37,6 +39,8 @@ export type SetupSnapProvider = (snapId: string, stream: Duplex) => void; export type ExecutionServiceArgs = { setupSnapProvider: SetupSnapProvider; messenger: ExecutionServiceMessenger; + initTimeout?: number; + pingTimeout?: number; terminationTimeout?: number; }; @@ -53,6 +57,9 @@ export type Job = { worker: WorkerType; }; +export type TerminateJobArgs = Partial> & + Pick, 'id'>; + export abstract class AbstractExecutionService implements ExecutionService { @@ -70,12 +77,18 @@ export abstract class AbstractExecutionService #messenger: ExecutionServiceMessenger; + #initTimeout: number; + + #pingTimeout: number; + #terminationTimeout: number; constructor({ setupSnapProvider, messenger, - terminationTimeout = Duration.Second, + initTimeout = inMilliseconds(60, Duration.Second), + pingTimeout = inMilliseconds(2, Duration.Second), + terminationTimeout = inMilliseconds(1, Duration.Second), }: ExecutionServiceArgs) { this.#snapRpcHooks = new Map(); this.jobs = new Map(); @@ -83,6 +96,8 @@ export abstract class AbstractExecutionService this.#snapToJobMap = new Map(); this.#jobToSnapMap = new Map(); this.#messenger = messenger; + this.#initTimeout = initTimeout; + this.#pingTimeout = pingTimeout; this.#terminationTimeout = terminationTimeout; this.registerMessageHandlers(); @@ -101,7 +116,7 @@ export abstract class AbstractExecutionService this.#messenger.registerActionHandler( `${controllerName}:executeSnap`, - async (snapData: SnapExecutionData) => this.executeSnap(snapData), + async (data: SnapExecutionData) => this.executeSnap(data), ); this.#messenger.registerActionHandler( @@ -122,7 +137,7 @@ export abstract class AbstractExecutionService * * @param job - The object corresponding to the job to be terminated. */ - protected abstract terminateJob(job: Job): void; + protected abstract terminateJob(job: TerminateJobArgs): void; /** * Terminates the job with the specified ID and deletes all its associated @@ -138,24 +153,28 @@ export abstract class AbstractExecutionService throw new Error(`Job with id "${jobId}" not found.`); } - // Ping worker and tell it to run teardown, continue with termination if it takes too long - const result = await withTimeout( - this.command(jobId, { - jsonrpc: '2.0', - method: 'terminate', - params: [], - id: nanoid(), - }), - this.#terminationTimeout, - ); + try { + // Ping worker and tell it to run teardown, continue with termination if it takes too long + const result = await withTimeout( + this.command(jobId, { + jsonrpc: '2.0', + method: 'terminate', + params: [], + id: nanoid(), + }), + this.#terminationTimeout, + ); - if (result === hasTimedOut || result !== 'OK') { - // We tried to shutdown gracefully but failed. This probably means the Snap is in infinite loop and - // hogging down the whole JS process. - // TODO(ritave): It might be doing weird things such as posting a lot of setTimeouts. Add a test to ensure that this behaviour - // doesn't leak into other workers. Especially important in IframeExecutionEnvironment since they all share the same - // JS process. - logError(`Job "${jobId}" failed to terminate gracefully.`, result); + if (result === hasTimedOut || result !== 'OK') { + // We tried to shutdown gracefully but failed. This probably means the Snap is in infinite loop and + // hogging down the whole JS process. + // TODO(ritave): It might be doing weird things such as posting a lot of setTimeouts. Add a test to ensure that this behaviour + // doesn't leak into other workers. Especially important in IframeExecutionEnvironment since they all share the same + // JS process. + logError(`Job "${jobId}" failed to terminate gracefully.`, result); + } + } catch { + // Ignore } Object.values(jobWrapper.streams).forEach((stream) => { @@ -169,21 +188,24 @@ export abstract class AbstractExecutionService this.terminateJob(jobWrapper); - this.#removeSnapAndJobMapping(jobId); this.jobs.delete(jobId); + this.#removeSnapAndJobMapping(jobId); log(`Job "${jobId}" terminated.`); } /** * Initiates a job for a snap. * - * Depending on the execution environment, this may run forever if the Snap fails to start up properly, therefore any call to this function should be wrapped in a timeout. - * + * @param jobId - The ID of the job to initiate. + * @param timer - The timer to use for timeouts. * @returns Information regarding the created job. + * @throws If the execution service returns an error or execution times out. */ - protected async initJob(): Promise> { - const jobId = nanoid(); - const { streams, worker } = await this.initStreams(jobId); + protected async initJob( + jobId: string, + timer: Timer, + ): Promise> { + const { streams, worker } = await this.initStreams(jobId, timer); const rpcEngine = new JsonRpcEngine(); const jsonRpcConnection = createStreamMiddleware(); @@ -215,15 +237,24 @@ export abstract class AbstractExecutionService /** * Sets up the streams for an initiated job. * - * Depending on the execution environment, this may run forever if the Snap fails to start up properly, therefore any call to this function should be wrapped in a timeout. - * * @param jobId - The id of the job. + * @param timer - The timer to use for timeouts. * @returns The streams to communicate with the worker and the worker itself. + * @throws If the execution service returns an error or execution times out. */ protected async initStreams( jobId: string, + timer: Timer, ): Promise<{ streams: JobStreams; worker: WorkerType }> { - const { worker, stream: envStream } = await this.initEnvStream(jobId); + const result = await withTimeout(this.initEnvStream(jobId), timer); + + if (result === hasTimedOut) { + // For certain environments, such as the iframe we may have already created the worker and wish to terminate it. + this.terminateJob({ id: jobId }); + throw new Error('The Snaps execution environment failed to start.'); + } + + const { worker, stream: envStream } = result; const mux = setupMultiplex(envStream, `Job: "${jobId}"`); const commandStream = mux.createStream(SNAP_STREAM_NAMES.COMMAND); @@ -327,38 +358,65 @@ export abstract class AbstractExecutionService /** * Initializes and executes a snap, setting up the communication channels to the snap etc. * - * Depending on the execution environment, this may run forever if the Snap fails to start up properly, therefore any call to this function should be wrapped in a timeout. - * * @param snapData - Data needed for Snap execution. + * @param snapData.snapId - The ID of the Snap to execute. + * @param snapData.sourceCode - The source code of the Snap to execute. + * @param snapData.endowments - The endowments available to the executing Snap. * @returns A string `OK` if execution succeeded. - * @throws If the execution service returns an error. + * @throws If the execution service returns an error or execution times out. */ - async executeSnap(snapData: SnapExecutionData): Promise { - if (this.#snapToJobMap.has(snapData.snapId)) { - throw new Error(`Snap "${snapData.snapId}" is already being executed.`); + async executeSnap({ + snapId, + sourceCode, + endowments, + }: SnapExecutionData): Promise { + if (this.#snapToJobMap.has(snapId)) { + throw new Error(`Snap "${snapId}" is already being executed.`); } - const job = await this.initJob(); - this.#mapSnapAndJob(snapData.snapId, job.id); + const jobId = nanoid(); + const timer = new Timer(this.#initTimeout); + + // This may resolve even if the environment has failed to start up fully + const job = await this.initJob(jobId, timer); + + this.#mapSnapAndJob(snapId, job.id); // Ping the worker to ensure that it started up - await this.command(job.id, { - jsonrpc: '2.0', - method: 'ping', - id: nanoid(), - }); + const pingResult = await withTimeout( + this.command(job.id, { + jsonrpc: '2.0', + method: 'ping', + id: nanoid(), + }), + this.#pingTimeout, + ); + + if (pingResult === hasTimedOut) { + throw new Error('The Snaps execution environment failed to start.'); + } const rpcStream = job.streams.rpc; - this.setupSnapProvider(snapData.snapId, rpcStream); + this.setupSnapProvider(snapId, rpcStream); - const result = await this.command(job.id, { - jsonrpc: '2.0', - method: 'executeSnap', - params: snapData, - id: nanoid(), - }); - this.#createSnapHooks(snapData.snapId, job.id); + const remainingTime = timer.remaining; + + const result = await withTimeout( + this.command(job.id, { + jsonrpc: '2.0', + method: 'executeSnap', + params: { snapId, sourceCode, endowments }, + id: nanoid(), + }), + remainingTime, + ); + + if (result === hasTimedOut) { + throw new Error(`${snapId} failed to start.`); + } + + this.#createSnapHooks(snapId, job.id); return result as string; } diff --git a/packages/snaps-controllers/src/services/ExecutionService.ts b/packages/snaps-controllers/src/services/ExecutionService.ts index e920d52669..dfda49a45d 100644 --- a/packages/snaps-controllers/src/services/ExecutionService.ts +++ b/packages/snaps-controllers/src/services/ExecutionService.ts @@ -21,7 +21,7 @@ export interface ExecutionService { export type SnapExecutionData = { snapId: string; sourceCode: string; - endowments?: Json; + endowments: Json; }; export type SnapErrorJson = { diff --git a/packages/snaps-controllers/src/services/iframe/IframeExecutionService.test.browser.ts b/packages/snaps-controllers/src/services/iframe/IframeExecutionService.test.browser.ts index 48c33eddd8..e81f4decf0 100644 --- a/packages/snaps-controllers/src/services/iframe/IframeExecutionService.test.browser.ts +++ b/packages/snaps-controllers/src/services/iframe/IframeExecutionService.test.browser.ts @@ -30,9 +30,9 @@ describe('IframeExecutionService', () => { const response = await service.executeSnap({ snapId: 'TestSnap', sourceCode: ` - console.log('foo'); + module.exports.onRpcRequest = () => null; `, - endowments: ['console'], + endowments: [], }); expect(response).toBe('OK'); diff --git a/packages/snaps-controllers/src/services/iframe/IframeExecutionService.ts b/packages/snaps-controllers/src/services/iframe/IframeExecutionService.ts index 52b6082dab..f0c563fe8c 100644 --- a/packages/snaps-controllers/src/services/iframe/IframeExecutionService.ts +++ b/packages/snaps-controllers/src/services/iframe/IframeExecutionService.ts @@ -2,7 +2,10 @@ import type { BasePostMessageStream } from '@metamask/post-message-stream'; import { WindowPostMessageStream } from '@metamask/post-message-stream'; import { createWindow } from '@metamask/snaps-utils'; -import type { Job, ExecutionServiceArgs } from '../AbstractExecutionService'; +import type { + ExecutionServiceArgs, + TerminateJobArgs, +} from '../AbstractExecutionService'; import { AbstractExecutionService } from '../AbstractExecutionService'; type IframeExecutionEnvironmentServiceArgs = { @@ -24,7 +27,7 @@ export class IframeExecutionService extends AbstractExecutionService { this.iframeUrl = iframeUrl; } - protected terminateJob(jobWrapper: Job): void { + protected terminateJob(jobWrapper: TerminateJobArgs): void { document.getElementById(jobWrapper.id)?.remove(); } diff --git a/packages/snaps-controllers/src/services/index.ts b/packages/snaps-controllers/src/services/index.ts index f95386c29f..73171d1ecf 100644 --- a/packages/snaps-controllers/src/services/index.ts +++ b/packages/snaps-controllers/src/services/index.ts @@ -2,7 +2,5 @@ export * from './AbstractExecutionService'; export * from './ExecutionService'; export * from './ProxyPostMessageStream'; export * from './iframe'; -export * from './node'; export * from './offscreen'; -export * from './webview'; export { WebWorkerExecutionService } from './webworker'; diff --git a/packages/snaps-controllers/src/services/node/NodeProcessExecutionService.test.ts b/packages/snaps-controllers/src/services/node-js/NodeProcessExecutionService.test.ts similarity index 98% rename from packages/snaps-controllers/src/services/node/NodeProcessExecutionService.test.ts rename to packages/snaps-controllers/src/services/node-js/NodeProcessExecutionService.test.ts index a199d9b212..ef8a72d52f 100644 --- a/packages/snaps-controllers/src/services/node/NodeProcessExecutionService.test.ts +++ b/packages/snaps-controllers/src/services/node-js/NodeProcessExecutionService.test.ts @@ -20,9 +20,9 @@ describe('NodeProcessExecutionService', () => { const response = await service.executeSnap({ snapId: 'TestSnap', sourceCode: ` - console.log('foo'); + module.exports.onRpcRequest = () => null; `, - endowments: ['console'], + endowments: [], }); expect(response).toBe('OK'); await service.terminateAllSnaps(); @@ -205,6 +205,7 @@ describe('NodeProcessExecutionService', () => { const result = await service.executeSnap({ snapId, sourceCode: ` + module.exports.onRpcRequest = () => null; console.log('foo'); console.error('bar'); `, diff --git a/packages/snaps-controllers/src/services/node/NodeProcessExecutionService.ts b/packages/snaps-controllers/src/services/node-js/NodeProcessExecutionService.ts similarity index 89% rename from packages/snaps-controllers/src/services/node/NodeProcessExecutionService.ts rename to packages/snaps-controllers/src/services/node-js/NodeProcessExecutionService.ts index 8437015fb4..39777e2920 100644 --- a/packages/snaps-controllers/src/services/node/NodeProcessExecutionService.ts +++ b/packages/snaps-controllers/src/services/node-js/NodeProcessExecutionService.ts @@ -3,7 +3,7 @@ import { ProcessParentMessageStream } from '@metamask/post-message-stream'; import type { ChildProcess } from 'child_process'; import { fork } from 'child_process'; -import type { Job } from '..'; +import type { TerminateJobArgs } from '..'; import { AbstractExecutionService } from '..'; export class NodeProcessExecutionService extends AbstractExecutionService { @@ -36,7 +36,7 @@ export class NodeProcessExecutionService extends AbstractExecutionService): void { - jobWrapper.worker.kill(); + protected terminateJob(jobWrapper: TerminateJobArgs): void { + jobWrapper.worker?.kill(); } } diff --git a/packages/snaps-controllers/src/services/node/NodeThreadExecutionService.test.ts b/packages/snaps-controllers/src/services/node-js/NodeThreadExecutionService.test.ts similarity index 98% rename from packages/snaps-controllers/src/services/node/NodeThreadExecutionService.test.ts rename to packages/snaps-controllers/src/services/node-js/NodeThreadExecutionService.test.ts index 33f63cc27d..96431c08fd 100644 --- a/packages/snaps-controllers/src/services/node/NodeThreadExecutionService.test.ts +++ b/packages/snaps-controllers/src/services/node-js/NodeThreadExecutionService.test.ts @@ -20,9 +20,9 @@ describe('NodeThreadExecutionService', () => { const response = await service.executeSnap({ snapId: 'TestSnap', sourceCode: ` - console.log('foo'); + module.exports.onRpcRequest = () => null; `, - endowments: ['console'], + endowments: [], }); expect(response).toBe('OK'); await service.terminateAllSnaps(); @@ -206,6 +206,7 @@ describe('NodeThreadExecutionService', () => { const result = await service.executeSnap({ snapId, sourceCode: ` + module.exports.onRpcRequest = () => null; console.log('foo'); console.error('bar'); `, diff --git a/packages/snaps-controllers/src/services/node/NodeThreadExecutionService.ts b/packages/snaps-controllers/src/services/node-js/NodeThreadExecutionService.ts similarity index 87% rename from packages/snaps-controllers/src/services/node/NodeThreadExecutionService.ts rename to packages/snaps-controllers/src/services/node-js/NodeThreadExecutionService.ts index 8bf6c9bc4c..a7912a561c 100644 --- a/packages/snaps-controllers/src/services/node/NodeThreadExecutionService.ts +++ b/packages/snaps-controllers/src/services/node-js/NodeThreadExecutionService.ts @@ -3,7 +3,7 @@ import { ThreadParentMessageStream } from '@metamask/post-message-stream'; // eslint-disable-next-line @typescript-eslint/no-shadow import { Worker } from 'worker_threads'; -import type { Job } from '..'; +import type { TerminateJobArgs } from '..'; import { AbstractExecutionService } from '..'; export class NodeThreadExecutionService extends AbstractExecutionService { @@ -37,7 +37,9 @@ export class NodeThreadExecutionService extends AbstractExecutionService return Promise.resolve({ worker, stream }); } - protected async terminateJob(jobWrapper: Job): Promise { - await jobWrapper.worker.terminate(); + protected async terminateJob( + jobWrapper: TerminateJobArgs, + ): Promise { + await jobWrapper.worker?.terminate(); } } diff --git a/packages/snaps-controllers/src/services/node/index.ts b/packages/snaps-controllers/src/services/node-js/index.ts similarity index 100% rename from packages/snaps-controllers/src/services/node/index.ts rename to packages/snaps-controllers/src/services/node-js/index.ts diff --git a/packages/snaps-controllers/src/services/node.ts b/packages/snaps-controllers/src/services/node.ts new file mode 100644 index 0000000000..e88858623f --- /dev/null +++ b/packages/snaps-controllers/src/services/node.ts @@ -0,0 +1,2 @@ +export * from '.'; +export * from './node-js'; diff --git a/packages/snaps-controllers/src/services/offscreen/OffscreenExecutionService.test.ts b/packages/snaps-controllers/src/services/offscreen/OffscreenExecutionService.test.ts index 911dac7b02..39f8866703 100644 --- a/packages/snaps-controllers/src/services/offscreen/OffscreenExecutionService.test.ts +++ b/packages/snaps-controllers/src/services/offscreen/OffscreenExecutionService.test.ts @@ -134,6 +134,7 @@ describe('OffscreenExecutionService', () => { await service.executeSnap({ snapId: MOCK_SNAP_ID, sourceCode: DEFAULT_SNAP_BUNDLE, + endowments: [], }), ).toBe('OK'); @@ -141,6 +142,7 @@ describe('OffscreenExecutionService', () => { await service.executeSnap({ snapId: MOCK_LOCAL_SNAP_ID, sourceCode: DEFAULT_SNAP_BUNDLE, + endowments: [], }), ).toBe('OK'); @@ -164,6 +166,7 @@ describe('OffscreenExecutionService', () => { await service.executeSnap({ snapId: MOCK_SNAP_ID, sourceCode: DEFAULT_SNAP_BUNDLE, + endowments: [], }), ).toBe('OK'); diff --git a/packages/snaps-controllers/src/services/proxy/ProxyExecutionService.ts b/packages/snaps-controllers/src/services/proxy/ProxyExecutionService.ts index 8cfaa79bdf..c1ac3c0045 100644 --- a/packages/snaps-controllers/src/services/proxy/ProxyExecutionService.ts +++ b/packages/snaps-controllers/src/services/proxy/ProxyExecutionService.ts @@ -1,7 +1,10 @@ import type { BasePostMessageStream } from '@metamask/post-message-stream'; import { nanoid } from 'nanoid'; -import type { ExecutionServiceArgs, Job } from '../AbstractExecutionService'; +import type { + ExecutionServiceArgs, + TerminateJobArgs, +} from '../AbstractExecutionService'; import { AbstractExecutionService } from '../AbstractExecutionService'; import { ProxyPostMessageStream } from '../ProxyPostMessageStream'; @@ -41,7 +44,7 @@ export class ProxyExecutionService extends AbstractExecutionService { * * @param job - The job to terminate. */ - protected async terminateJob(job: Job) { + protected async terminateJob(job: TerminateJobArgs) { // The `AbstractExecutionService` will have already closed the job stream, // so we write to the runtime stream directly. this.#stream.write({ diff --git a/packages/snaps-controllers/src/services/react-native.ts b/packages/snaps-controllers/src/services/react-native.ts new file mode 100644 index 0000000000..0ff2738272 --- /dev/null +++ b/packages/snaps-controllers/src/services/react-native.ts @@ -0,0 +1,2 @@ +export * from '.'; +export * from './webview'; diff --git a/packages/snaps-controllers/src/services/webview/WebViewExecutionService.test.ts b/packages/snaps-controllers/src/services/webview/WebViewExecutionService.test.ts index 8773947bd0..dbce1a6073 100644 --- a/packages/snaps-controllers/src/services/webview/WebViewExecutionService.test.ts +++ b/packages/snaps-controllers/src/services/webview/WebViewExecutionService.test.ts @@ -119,6 +119,7 @@ describe('WebViewExecutionService', () => { await service.executeSnap({ snapId: MOCK_SNAP_ID, sourceCode: DEFAULT_SNAP_BUNDLE, + endowments: [], }), ).toBe('OK'); }); diff --git a/packages/snaps-controllers/src/services/webworker/WebWorkerExecutionService.test.browser.ts b/packages/snaps-controllers/src/services/webworker/WebWorkerExecutionService.test.browser.ts index 7a3e6d18f0..a9eb5e5c71 100644 --- a/packages/snaps-controllers/src/services/webworker/WebWorkerExecutionService.test.browser.ts +++ b/packages/snaps-controllers/src/services/webworker/WebWorkerExecutionService.test.browser.ts @@ -37,17 +37,17 @@ describe('WebWorkerExecutionService', () => { await service.executeSnap({ snapId: MOCK_SNAP_ID, sourceCode: ` - console.log('foo'); + module.exports.onRpcRequest = () => null; `, - endowments: ['console'], + endowments: [], }); await service.executeSnap({ snapId: MOCK_LOCAL_SNAP_ID, sourceCode: ` - console.log('foo'); + module.exports.onRpcRequest = () => null; `, - endowments: ['console'], + endowments: [], }); expect(document.getElementById(WORKER_POOL_ID)).not.toBeNull(); @@ -64,9 +64,9 @@ describe('WebWorkerExecutionService', () => { const response = await service.executeSnap({ snapId: 'TestSnap', sourceCode: ` - console.log('foo'); + module.exports.onRpcRequest = () => null; `, - endowments: ['console'], + endowments: [], }); expect(response).toBe('OK'); diff --git a/packages/snaps-controllers/src/services/webworker/WebWorkerExecutionService.ts b/packages/snaps-controllers/src/services/webworker/WebWorkerExecutionService.ts index 4d6c425f6a..3a25027440 100644 --- a/packages/snaps-controllers/src/services/webworker/WebWorkerExecutionService.ts +++ b/packages/snaps-controllers/src/services/webworker/WebWorkerExecutionService.ts @@ -4,7 +4,10 @@ import { createWindow } from '@metamask/snaps-utils'; import { assert } from '@metamask/utils'; import { nanoid } from 'nanoid'; -import type { ExecutionServiceArgs, Job } from '../AbstractExecutionService'; +import type { + ExecutionServiceArgs, + TerminateJobArgs, +} from '../AbstractExecutionService'; import { AbstractExecutionService } from '../AbstractExecutionService'; import { ProxyPostMessageStream } from '../ProxyPostMessageStream'; @@ -48,7 +51,7 @@ export class WebWorkerExecutionService extends AbstractExecutionService * * @param job - The job to terminate. */ - protected async terminateJob(job: Job) { + protected async terminateJob(job: TerminateJobArgs) { // The `AbstractExecutionService` will have already closed the job stream, // so we write to the runtime stream directly. assert(this.#runtimeStream, 'Runtime stream not initialized.'); diff --git a/packages/snaps-controllers/src/snaps/SnapController.test.ts b/packages/snaps-controllers/src/snaps/SnapController.test.ts index 976128f96e..175a780f26 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.test.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.test.ts @@ -1,8 +1,10 @@ import { getPersistentState } from '@metamask/base-controller'; +import { encrypt } from '@metamask/browser-passworder'; import { createAsyncMiddleware, JsonRpcEngine, } from '@metamask/json-rpc-engine'; +import { createEngineStream } from '@metamask/json-rpc-middleware-stream'; import { SubjectType, type Caveat, @@ -56,15 +58,16 @@ import { stringToBytes, } from '@metamask/utils'; import { File } from 'buffer'; +import { webcrypto } from 'crypto'; import fetchMock from 'jest-fetch-mock'; -import { createEngineStream } from 'json-rpc-middleware-stream'; import { pipeline } from 'readable-stream'; import type { Duplex } from 'readable-stream'; -import type { NodeThreadExecutionService } from '../services'; import { setupMultiplex } from '../services'; +import type { NodeThreadExecutionService } from '../services/node'; import { approvalControllerMock, + DEFAULT_ENCRYPTION_KEY_DERIVATION_OPTIONS, ExecutionEnvironmentStub, getControllerMessenger, getNodeEESMessenger, @@ -99,16 +102,34 @@ import { SNAP_APPROVAL_UPDATE, } from './SnapController'; -Object.defineProperty(globalThis, 'crypto', { - value: { - // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires - ...require('node:crypto').webcrypto, - getRandomValues: jest.fn().mockReturnValue(new Uint32Array(32)), - }, -}); +if (!('CryptoKey' in globalThis)) { + // We can remove this once we drop Node 18 + Object.defineProperty(globalThis, 'CryptoKey', { + value: webcrypto.CryptoKey, + }); +} + +globalThis.crypto ??= webcrypto as typeof globalThis.crypto; +globalThis.crypto.getRandomValues = ( + array: Type, +) => { + if (array === null) { + return null as Type; + } + + return new Uint8Array(array.buffer).fill(0) as unknown as Type; +}; fetchMock.enableMocks(); +// Encryption key for `MOCK_SNAP_ID`. +const ENCRYPTION_KEY = + '0xd2f0a8e994b871ba4451ac383bf323cdaad8d554736355f2223e155692fbc446'; + +// Encryption key for `MOCK_LOCAL_SNAP_ID`. +const OTHER_ENCRYPTION_KEY = + '0x7cd340349a41e0f7af62a9d97c76e96b12485e0206791d6b5638dd59736af8f5'; + describe('SnapController', () => { beforeEach(() => { // eslint-disable-next-line @typescript-eslint/require-await @@ -125,28 +146,6 @@ describe('SnapController', () => { await service.terminateAllSnaps(); }); - it('creates a worker and snap controller, adds a snap, and update its state', async () => { - const [snapController, service] = getSnapControllerWithEES( - getSnapControllerWithEESOptions({ - state: { - snaps: getPersistedSnapsState(), - }, - }), - ); - - const snap = snapController.getExpect(MOCK_SNAP_ID); - const state = 'foo'; - - await snapController.startSnap(snap.id); - snapController.updateSnapState(snap.id, state, true); - const snapState = snapController.getSnapState(snap.id, true); - expect(snapState).toStrictEqual(state); - - expect(snapController.state.snapStates[MOCK_SNAP_ID]).toStrictEqual(state); - snapController.destroy(); - await service.terminateAllSnaps(); - }); - it('adds a snap and uses its JSON-RPC api with a NodeThreadExecutionService', async () => { const [snapController, service] = getSnapControllerWithEES( getSnapControllerWithEESOptions({ @@ -472,7 +471,7 @@ describe('SnapController', () => { id: 1, }, }), - ).rejects.toThrow(/request timed out/u); + ).rejects.toThrow(`${snap.id} failed to respond to the request in time.`); expect(snapController.state.snaps[snap.id].status).toBe('crashed'); snapController.destroy(); @@ -539,6 +538,114 @@ describe('SnapController', () => { await service.terminateAllSnaps(); }); + it('includes the initialConnections data in the approval requestState when installing a Snap', async () => { + const rootMessenger = getControllerMessenger(); + const messenger = getSnapControllerMessenger(rootMessenger); + + rootMessenger.registerActionHandler( + 'PermissionController:getPermissions', + () => ({}), + ); + + const initialConnections = { + 'npm:filsnap': {}, + 'https://snaps.metamask.io': {}, + }; + + const { manifest } = await getMockSnapFilesWithUpdatedChecksum({ + manifest: getSnapManifest({ + initialConnections, + }), + }); + + const snapController = getSnapController( + getSnapControllerOptions({ + messenger, + detectSnapLocation: loopbackDetect({ manifest }), + }), + ); + + await snapController.installSnaps(MOCK_ORIGIN, { + [MOCK_SNAP_ID]: {}, + }); + + expect(messenger.call).toHaveBeenNthCalledWith( + 4, + 'ApprovalController:updateRequestState', + { + id: expect.any(String), + requestState: { + loading: false, + connections: initialConnections, + permissions: expect.anything(), + }, + }, + ); + + snapController.destroy(); + }); + + it('includes the initialConnections data in the approval requestState when updating a Snap', async () => { + const rootMessenger = getControllerMessenger(); + const messenger = getSnapControllerMessenger(rootMessenger); + + rootMessenger.registerActionHandler( + 'PermissionController:getPermissions', + () => ({}), + ); + + const initialConnections = { + 'npm:filsnap': {}, + 'https://snaps.metamask.io': {}, + }; + + const { manifest } = await getMockSnapFilesWithUpdatedChecksum({ + manifest: getSnapManifest({ + version: '1.1.0' as SemVerVersion, + initialConnections, + }), + }); + + const detectSnapLocation = loopbackDetect({ + manifest: manifest.result, + }); + + const snapController = getSnapController( + getSnapControllerOptions({ + messenger, + state: { + snaps: getPersistedSnapsState(), + }, + detectSnapLocation, + }), + ); + + await snapController.updateSnap( + MOCK_ORIGIN, + MOCK_SNAP_ID, + detectSnapLocation(), + ); + + expect(messenger.call).toHaveBeenNthCalledWith( + 4, + 'ApprovalController:updateRequestState', + { + id: expect.any(String), + requestState: { + connections: initialConnections, + permissions: expect.anything(), + newVersion: '1.1.0', + newPermissions: expect.anything(), + approvedPermissions: {}, + unusedPermissions: {}, + loading: false, + }, + }, + ); + + snapController.destroy(); + }); + it('installs a snap via installSnaps', async () => { const messenger = getSnapControllerMessenger(); const snapController = getSnapController( @@ -614,6 +721,7 @@ describe('SnapController', () => { id: expect.any(String), requestState: { loading: false, + connections: {}, permissions, }, }, @@ -775,6 +883,38 @@ describe('SnapController', () => { controller.destroy(); }); + it('throws an error if the registry is unavailable and allowlisting is required but resolve succeeds', async () => { + const registry = new MockSnapsRegistry(); + const rootMessenger = getControllerMessenger(registry); + const messenger = getSnapControllerMessenger(rootMessenger); + const controller = getSnapController( + getSnapControllerOptions({ + featureFlags: { requireAllowlist: true }, + messenger, + detectSnapLocation: (_location, options) => + new LoopbackLocation(options), + }), + ); + + // Mock resolve to succeed, but registry.get() will fail later + registry.resolveVersion.mockReturnValue('1.0.0'); + registry.get.mockReturnValue({ + [MOCK_SNAP_ID]: { status: SnapsRegistryStatus.Unavailable }, + }); + + await expect( + controller.installSnaps(MOCK_ORIGIN, { + [MOCK_SNAP_ID]: { version: DEFAULT_REQUESTED_SNAP_VERSION }, + }), + ).rejects.toThrow( + 'Cannot install version "1.0.0" of snap "npm:@metamask/example-snap": The registry is temporarily unavailable.', + ); + + expect(registry.resolveVersion).toHaveBeenCalled(); + + controller.destroy(); + }); + it('throws an error if snap is not on allowlist and allowlisting is required', async () => { const { manifest, sourceCode, svgIcon } = await getMockSnapFilesWithUpdatedChecksum({ @@ -1141,7 +1281,7 @@ describe('SnapController', () => { id: 1, }, }), - ).rejects.toThrow(/request timed out/u); + ).rejects.toThrow(`${snap.id} failed to respond to the request in time.`); expect(snapController.state.snaps[snap.id].status).toBe('crashed'); snapController.destroy(); @@ -1226,7 +1366,7 @@ describe('SnapController', () => { id: 1, }, }), - ).rejects.toThrow(/request timed out/u); + ).rejects.toThrow(`${snap.id} failed to respond to the request in time.`); expect(snapController.state.snaps[snap.id].status).toBe('crashed'); snapController.destroy(); @@ -1395,33 +1535,176 @@ describe('SnapController', () => { await service.terminateAllSnaps(); }); - it('times out on stuck starting snap', async () => { - const rootMessenger = getControllerMessenger(); - const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( - getSnapControllerOptions({ - messenger, - maxRequestTime: 50, - state: { - snaps: getPersistedSnapsState(), + it('gracefully throws for multiple failing requests', async () => { + const sourceCode = ` + module.exports.onRpcRequest = async () => snap.request({ method: 'snap_dialog', params: null }); + `; + + const options = getSnapControllerWithEESOptions({ + environmentEndowmentPermissions: [SnapEndowments.EthereumProvider], + idleTimeCheckInterval: 30000, + maxIdleTime: 160000, + state: { + snaps: getPersistedSnapsState( + getPersistedSnapObject({ + sourceCode, + manifest: getSnapManifest({ + shasum: await getSnapChecksum(getMockSnapFiles({ sourceCode })), + }), + }), + ), + }, + }); + + const { rootMessenger } = options; + const [snapController, service] = getSnapControllerWithEES(options); + const snap = snapController.getExpect(MOCK_SNAP_ID); + + rootMessenger.registerActionHandler( + 'PermissionController:hasPermission', + () => true, + ); + + const results = (await Promise.allSettled([ + snapController.handleRequest({ + snapId: snap.id, + origin: 'foo.com', + handler: HandlerType.OnRpcRequest, + request: { + jsonrpc: '2.0', + method: 'test', + params: {}, + id: 1, + }, + }), + snapController.handleRequest({ + snapId: snap.id, + origin: 'foo.com', + handler: HandlerType.OnRpcRequest, + request: { + jsonrpc: '2.0', + method: 'test', + params: {}, + id: 1, }, }), + ])) as PromiseRejectedResult[]; + + expect(results[0].status).toBe('rejected'); + expect(results[0].reason.message).toBe( + "'args.params' must be an object or array if provided.", + ); + expect(results[1].status).toBe('rejected'); + expect(results[1].reason.message).toBe( + "'args.params' must be an object or array if provided.", ); + snapController.destroy(); + await service.terminateAllSnaps(); + }); + + // This isn't stable in CI unfortunately + it.skip('throws if the Snap is terminated while executing', async () => { + const { manifest, sourceCode, svgIcon } = + await getMockSnapFilesWithUpdatedChecksum({ + sourceCode: ` + module.exports.onRpcRequest = () => { + return new Promise((resolve) => {}); + }; + `, + }); + + const [snapController] = getSnapControllerWithEES( + getSnapControllerWithEESOptions({ + detectSnapLocation: loopbackDetect({ + manifest, + files: [sourceCode, svgIcon as VirtualFile], + }), + }), + ); + + await snapController.installSnaps(MOCK_ORIGIN, { + [MOCK_SNAP_ID]: {}, + }); + const snap = snapController.getExpect(MOCK_SNAP_ID); - rootMessenger.registerActionHandler( - 'ExecutionService:executeSnap', - async () => await sleep(100), + expect(snapController.state.snaps[snap.id].status).toBe('running'); + + const promise = snapController.handleRequest({ + snapId: snap.id, + origin: 'foo.com', + handler: HandlerType.OnRpcRequest, + request: { + jsonrpc: '2.0', + method: 'test', + params: {}, + id: 1, + }, + }); + + const results = await Promise.allSettled([ + snapController.removeSnap(snap.id), + promise, + ]); + + expect(results[0].status).toBe('fulfilled'); + expect(results[1].status).toBe('rejected'); + expect((results[1] as PromiseRejectedResult).reason.message).toBe( + `The snap "${snap.id}" has been terminated during execution.`, ); - rootMessenger.registerActionHandler( - 'PermissionController:hasPermission', - () => false, + snapController.destroy(); + }); + + it('throws if unresponsive Snap is terminated while executing', async () => { + const { manifest, sourceCode, svgIcon } = + await getMockSnapFilesWithUpdatedChecksum({ + sourceCode: ` + module.exports.onRpcRequest = () => { + while(true) {} + }; + `, + }); + + const [snapController] = getSnapControllerWithEES( + getSnapControllerWithEESOptions({ + detectSnapLocation: loopbackDetect({ + manifest, + files: [sourceCode, svgIcon as VirtualFile], + }), + }), ); - await expect(snapController.startSnap(snap.id)).rejects.toThrow( - /request timed out/u, + await snapController.installSnaps(MOCK_ORIGIN, { + [MOCK_SNAP_ID]: {}, + }); + + const snap = snapController.getExpect(MOCK_SNAP_ID); + + expect(snapController.state.snaps[snap.id].status).toBe('running'); + + const promise = snapController.handleRequest({ + snapId: snap.id, + origin: 'foo.com', + handler: HandlerType.OnRpcRequest, + request: { + jsonrpc: '2.0', + method: 'test', + params: {}, + id: 1, + }, + }); + + const results = await Promise.allSettled([ + snapController.removeSnap(snap.id), + promise, + ]); + + expect(results[0].status).toBe('fulfilled'); + expect(results[1].status).toBe('rejected'); + expect((results[1] as PromiseRejectedResult).reason.message).toBe( + `${snap.id} failed to respond to the request in time.`, ); snapController.destroy(); @@ -1583,7 +1866,7 @@ describe('SnapController', () => { id: 1, }, }), - ).rejects.toThrow(/request timed out/u); + ).rejects.toThrow(`${snap.id} failed to respond to the request in time.`); expect(snapController.state.snaps[snap.id].status).toBe('crashed'); await snapController.removeSnap(snap.id); @@ -3563,6 +3846,7 @@ describe('SnapController', () => { expect.objectContaining({ id: expect.any(String), requestState: { + connections: {}, permissions, loading: false, }, @@ -3713,6 +3997,7 @@ describe('SnapController', () => { id: expect.any(String), requestState: { loading: false, + connections: {}, permissions, }, }), @@ -3807,6 +4092,7 @@ describe('SnapController', () => { id: expect.any(String), requestState: { loading: false, + connections: {}, permissions, }, }), @@ -4170,7 +4456,7 @@ describe('SnapController', () => { manifest: getSnapManifest({ version: '1.2.3', initialPermissions: { - 'endowment:rpc': { dapps: true }, + 'endowment:rpc': { dapps: false, snaps: true }, // eslint-disable-next-line @typescript-eslint/naming-convention snap_getEntropy: {}, }, @@ -4432,6 +4718,7 @@ describe('SnapController', () => { id: expect.any(String), requestState: { loading: false, + connections: {}, permissions, }, }), @@ -4620,6 +4907,7 @@ describe('SnapController', () => { id: expect.any(String), requestState: { loading: false, + connections: {}, permissions: { [handlerEndowments.onRpcRequest as string]: { caveats: [ @@ -4749,6 +5037,7 @@ describe('SnapController', () => { id: expect.any(String), requestState: { loading: false, + connections: {}, permissions: { [SnapEndowments.Rpc]: { caveats: [caveat], @@ -4869,6 +5158,81 @@ describe('SnapController', () => { snapController.destroy(); }); + it('overwrites caveats on update for already approved permissions', async () => { + const initialPermissions = { + [handlerEndowments.onRpcRequest as string]: { + allowedOrigins: ['https://metamask.io'], + }, + }; + const { manifest } = await getMockSnapFilesWithUpdatedChecksum({ + manifest: getSnapManifest({ + version: '1.1.0' as SemVerVersion, + initialPermissions, + }), + }); + + const detectSnapLocation = loopbackDetect({ + manifest: manifest.result, + }); + + const rootMessenger = getControllerMessenger(); + const messenger = getSnapControllerMessenger(rootMessenger); + const snapController = getSnapController( + getSnapControllerOptions({ + messenger, + state: { + snaps: getPersistedSnapsState(), + }, + detectSnapLocation, + }), + ); + + await snapController.updateSnap( + MOCK_ORIGIN, + MOCK_SNAP_ID, + detectSnapLocation(), + ); + + expect(messenger.call).toHaveBeenNthCalledWith( + 7, + 'PermissionController:revokePermissions', + { + [MOCK_SNAP_ID]: [SnapEndowments.Rpc, 'snap_dialog'], + }, + ); + + expect(messenger.call).toHaveBeenNthCalledWith( + 8, + 'PermissionController:grantPermissions', + { + approvedPermissions: { + [handlerEndowments.onRpcRequest as string]: { + caveats: [ + { + type: SnapCaveatType.RpcOrigin, + value: { + allowedOrigins: ['https://metamask.io'], + }, + }, + ], + }, + }, + subject: { origin: MOCK_SNAP_ID }, + requestData: { + metadata: { + origin: MOCK_SNAP_ID, + dappOrigin: MOCK_ORIGIN, + id: expect.any(String), + }, + + snapId: MOCK_SNAP_ID, + }, + }, + ); + + snapController.destroy(); + }); + it('returns an error on invalid snap id', async () => { const snapId = 'foo'; const messenger = getSnapControllerMessenger(); @@ -4981,6 +5345,7 @@ describe('SnapController', () => { id: expect.any(String), requestState: { loading: false, + connections: {}, permissions: {}, newVersion, newPermissions: {}, @@ -5705,6 +6070,7 @@ describe('SnapController', () => { id: expect.any(String), requestState: { loading: false, + connections: {}, permissions: {}, newVersion: '1.1.0', newPermissions: {}, @@ -5896,6 +6262,7 @@ describe('SnapController', () => { id: expect.any(String), requestState: { loading: false, + connections: {}, permissions: {}, newVersion: '1.1.0', newPermissions: {}, @@ -6055,6 +6422,7 @@ describe('SnapController', () => { id: expect.any(String), requestState: { loading: false, + connections: {}, permissions, newVersion: '1.1.0', newPermissions: permissions, @@ -6229,6 +6597,7 @@ describe('SnapController', () => { id: expect.any(String), requestState: { loading: false, + connections: {}, permissions: { 'endowment:network-access': {} }, newVersion: '1.1.0', newPermissions: { 'endowment:network-access': {} }, @@ -6760,6 +7129,30 @@ describe('SnapController', () => { snapController.destroy(); }); + + it('removes snap state', async () => { + const messenger = getSnapControllerMessenger(); + const snapController = getSnapController( + getSnapControllerOptions({ + messenger, + state: { + snaps: getPersistedSnapsState(), + snapStates: { + [MOCK_SNAP_ID]: 'foo', + }, + unencryptedSnapStates: { + [MOCK_SNAP_ID]: 'bar', + }, + }, + }), + ); + + await snapController.removeSnap(MOCK_SNAP_ID); + expect(snapController.state.snapStates).toStrictEqual({}); + expect(snapController.state.unencryptedSnapStates).toStrictEqual({}); + + snapController.destroy(); + }); }); describe('enableSnap', () => { @@ -7371,7 +7764,15 @@ describe('SnapController', () => { it(`gets the snap's state`, async () => { const messenger = getSnapControllerMessenger(); - const state = 'foo'; + const state = { myVariable: 1 }; + + const mockEncryptedState = await encrypt( + ENCRYPTION_KEY, + state, + undefined, + undefined, + DEFAULT_ENCRYPTION_KEY_DERIVATION_OPTIONS, + ); const snapController = getSnapController( getSnapControllerOptions({ @@ -7381,14 +7782,14 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: getPersistedSnapObject(), }, snapStates: { - [MOCK_SNAP_ID]: state, + [MOCK_SNAP_ID]: mockEncryptedState, }, }, }), ); const getSnapStateSpy = jest.spyOn(snapController, 'getSnapState'); - const result = messenger.call( + const result = await messenger.call( 'SnapController:getSnapState', MOCK_SNAP_ID, true, @@ -7400,10 +7801,161 @@ describe('SnapController', () => { snapController.destroy(); }); + it('migrates user storage to latest key derivation options', async () => { + const messenger = getSnapControllerMessenger(); + + const state = { myVariable: 1 }; + + const initialEncryptedState = await encrypt( + ENCRYPTION_KEY, + state, + undefined, + undefined, + { + ...DEFAULT_ENCRYPTION_KEY_DERIVATION_OPTIONS, + params: { iterations: 10_000 }, + }, + ); + + const snapController = getSnapController( + getSnapControllerOptions({ + messenger, + state: { + snaps: { + [MOCK_SNAP_ID]: getPersistedSnapObject(), + }, + snapStates: { + [MOCK_SNAP_ID]: initialEncryptedState, + }, + }, + }), + ); + + const newState = { myVariable: 2 }; + + await messenger.call( + 'SnapController:updateSnapState', + MOCK_SNAP_ID, + newState, + true, + ); + + const upgradedEncryptedState = await encrypt( + ENCRYPTION_KEY, + newState, + undefined, + undefined, + DEFAULT_ENCRYPTION_KEY_DERIVATION_OPTIONS, + ); + + const result = await messenger.call( + 'SnapController:getSnapState', + MOCK_SNAP_ID, + true, + ); + + expect(result).toStrictEqual(newState); + expect(snapController.state.snapStates[MOCK_SNAP_ID]).toStrictEqual( + upgradedEncryptedState, + ); + + snapController.destroy(); + }); + + it('different snaps use different encryption keys', async () => { + const messenger = getSnapControllerMessenger(); + + const state = { foo: 'bar' }; + + const snapController = getSnapController( + getSnapControllerOptions({ + messenger, + state: { + snaps: { + [MOCK_SNAP_ID]: getPersistedSnapObject(), + [MOCK_LOCAL_SNAP_ID]: getPersistedSnapObject({ + id: MOCK_LOCAL_SNAP_ID, + }), + }, + }, + }), + ); + + await messenger.call( + 'SnapController:updateSnapState', + MOCK_SNAP_ID, + state, + true, + ); + + await messenger.call( + 'SnapController:updateSnapState', + MOCK_LOCAL_SNAP_ID, + state, + true, + ); + + const encryptedState1 = await encrypt( + ENCRYPTION_KEY, + state, + undefined, + undefined, + DEFAULT_ENCRYPTION_KEY_DERIVATION_OPTIONS, + ); + + const encryptedState2 = await encrypt( + OTHER_ENCRYPTION_KEY, + state, + undefined, + undefined, + DEFAULT_ENCRYPTION_KEY_DERIVATION_OPTIONS, + ); + + expect(snapController.state.snapStates[MOCK_SNAP_ID]).toStrictEqual( + encryptedState1, + ); + expect(snapController.state.snapStates[MOCK_LOCAL_SNAP_ID]).toStrictEqual( + encryptedState2, + ); + expect(snapController.state.snapStates[MOCK_SNAP_ID]).not.toStrictEqual( + snapController.state.snapStates[MOCK_LOCAL_SNAP_ID], + ); + + snapController.destroy(); + }); + + it('throws an error if the state is corrupt', async () => { + const messenger = getSnapControllerMessenger(); + + const snapController = getSnapController( + getSnapControllerOptions({ + messenger, + state: { + snaps: { + [MOCK_SNAP_ID]: getPersistedSnapObject(), + }, + snapStates: { + [MOCK_SNAP_ID]: 'foo', + }, + }, + }), + ); + + await expect( + messenger.call('SnapController:getSnapState', MOCK_SNAP_ID, true), + ).rejects.toThrow( + rpcErrors.internal({ + message: 'Failed to decrypt snap state, the state must be corrupted.', + }), + ); + + snapController.destroy(); + }); + it(`gets the snap's unencrypted state`, async () => { const messenger = getSnapControllerMessenger(); - const state = 'foo'; + const state = { foo: 'bar' }; const snapController = getSnapController( getSnapControllerOptions({ @@ -7413,14 +7965,14 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: getPersistedSnapObject(), }, unencryptedSnapStates: { - [MOCK_SNAP_ID]: state, + [MOCK_SNAP_ID]: JSON.stringify(state), }, }, }), ); const getSnapStateSpy = jest.spyOn(snapController, 'getSnapState'); - const result = messenger.call( + const result = await messenger.call( 'SnapController:getSnapState', MOCK_SNAP_ID, false, @@ -7431,6 +7983,35 @@ describe('SnapController', () => { snapController.destroy(); }); + + it(`returns null if the Snap has no state yet`, async () => { + const messenger = getSnapControllerMessenger(); + + const snapController = getSnapController( + getSnapControllerOptions({ + messenger, + state: { + snaps: { + [MOCK_SNAP_ID]: getPersistedSnapObject(), + }, + }, + }), + ); + + expect( + await messenger.call( + 'SnapController:getSnapState', + MOCK_SNAP_ID, + false, + ), + ).toBeNull(); + + expect( + await messenger.call('SnapController:getSnapState', MOCK_SNAP_ID, true), + ).toBeNull(); + + snapController.destroy(); + }); }); describe('SnapController:has', () => { @@ -7479,8 +8060,15 @@ describe('SnapController', () => { ); const updateSnapStateSpy = jest.spyOn(snapController, 'updateSnapState'); - const state = 'bar'; - messenger.call( + const state = { foo: 'bar' }; + const mockEncryptedState = await encrypt( + ENCRYPTION_KEY, + state, + undefined, + undefined, + DEFAULT_ENCRYPTION_KEY_DERIVATION_OPTIONS, + ); + await messenger.call( 'SnapController:updateSnapState', MOCK_SNAP_ID, state, @@ -7489,7 +8077,7 @@ describe('SnapController', () => { expect(updateSnapStateSpy).toHaveBeenCalledTimes(1); expect(snapController.state.snapStates[MOCK_SNAP_ID]).toStrictEqual( - state, + mockEncryptedState, ); snapController.destroy(); @@ -7508,8 +8096,8 @@ describe('SnapController', () => { ); const updateSnapStateSpy = jest.spyOn(snapController, 'updateSnapState'); - const state = 'bar'; - messenger.call( + const state = { foo: 'bar' }; + await messenger.call( 'SnapController:updateSnapState', MOCK_SNAP_ID, state, @@ -7519,7 +8107,7 @@ describe('SnapController', () => { expect(updateSnapStateSpy).toHaveBeenCalledTimes(1); expect( snapController.state.unencryptedSnapStates[MOCK_SNAP_ID], - ).toStrictEqual(state); + ).toStrictEqual(JSON.stringify(state)); snapController.destroy(); }); @@ -7544,7 +8132,7 @@ describe('SnapController', () => { ); messenger.call('SnapController:clearSnapState', MOCK_SNAP_ID, true); - const clearedState = messenger.call( + const clearedState = await messenger.call( 'SnapController:getSnapState', MOCK_SNAP_ID, true, @@ -7572,7 +8160,7 @@ describe('SnapController', () => { ); messenger.call('SnapController:clearSnapState', MOCK_SNAP_ID, false); - const clearedState = messenger.call( + const clearedState = await messenger.call( 'SnapController:getSnapState', MOCK_SNAP_ID, false, diff --git a/packages/snaps-controllers/src/snaps/SnapController.ts b/packages/snaps-controllers/src/snaps/SnapController.ts index bbb56cc3ed..19a4cd663c 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.ts @@ -35,6 +35,7 @@ import { getKeyringCaveatOrigins, getRpcCaveatOrigins, processSnapPermissions, + getEncryptionEntropy, } from '@metamask/snaps-rpc-methods'; import type { RequestSnapsParams, @@ -80,6 +81,7 @@ import { NpmSnapFileNames, OnNameLookupResponseStruct, getLocalizedSnapManifest, + parseJson, } from '@metamask/snaps-utils'; import type { Json, NonEmptyArray, SemVerRange } from '@metamask/utils'; import { @@ -92,6 +94,7 @@ import { hasProperty, inMilliseconds, isNonEmptyArray, + isValidJson, isValidSemVerRange, satisfiesVersionRange, timeSince, @@ -112,7 +115,18 @@ import type { TerminateAllSnapsAction, TerminateSnapAction, } from '../services'; -import { fetchSnap, hasTimedOut, setDiff, withTimeout } from '../utils'; +import type { EncryptionResult } from '../types'; +import { + type ExportableKeyEncryptor, + type KeyDerivationOptions, +} from '../types'; +import { + fetchSnap, + hasTimedOut, + permissionsDiff, + setDiff, + withTimeout, +} from '../utils'; import { ALLOWED_PERMISSIONS } from './constants'; import type { SnapLocation } from './location'; import { detectSnapLocation } from './location'; @@ -209,6 +223,21 @@ export interface SnapRuntimeData { * @see {@link SnapController:constructor} */ interpreter: StateMachine.Service; + + /** + * Cached encryption key used for state encryption. + */ + encryptionKey: string | null; + + /** + * Cached encryption salt used for state encryption. + */ + encryptionSalt: string | null; + + /** + * A boolean flag to determine whether the Snap is currently being stopped. + */ + stopping: boolean; } export type SnapError = { @@ -619,6 +648,18 @@ type SnapControllerArgs = { * A list of snaps to be preinstalled into the SnapController state on initialization. */ preinstalledSnaps?: PreinstalledSnap[]; + + /** + * A utility object containing functions required for state encryption. + */ + encryptor: ExportableKeyEncryptor; + + /** + * A hook to access the mnemonic of the user's primary keyring. + * + * @returns The mnemonic as bytes. + */ + getMnemonic: () => Promise; }; type AddSnapArgs = { id: SnapId; @@ -699,6 +740,10 @@ export class SnapController extends BaseController< // This property cannot be hash private yet because of tests. private readonly maxRequestTime: number; + #encryptor: ExportableKeyEncryptor; + + #getMnemonic: () => Promise; + #detectSnapLocation: typeof detectSnapLocation; #snapsRuntimeData: Map; @@ -727,6 +772,8 @@ export class SnapController extends BaseController< featureFlags = {}, detectSnapLocation: detectSnapLocationFunction = detectSnapLocation, preinstalledSnaps, + encryptor, + getMnemonic, }: SnapControllerArgs) { super({ messenger, @@ -779,6 +826,8 @@ export class SnapController extends BaseController< this.#maxIdleTime = maxIdleTime; this.maxRequestTime = maxRequestTime; this.#detectSnapLocation = detectSnapLocationFunction; + this.#encryptor = encryptor; + this.#getMnemonic = getMnemonic; this._onUnhandledSnapError = this._onUnhandledSnapError.bind(this); this._onOutboundRequest = this._onOutboundRequest.bind(this); this._onOutboundResponse = this._onOutboundResponse.bind(this); @@ -920,7 +969,7 @@ export class SnapController extends BaseController< this.messagingSystem.registerActionHandler( `${controllerName}:getSnapState`, - (...args) => this.getSnapState(...args), + async (...args) => this.getSnapState(...args), ); this.messagingSystem.registerActionHandler( @@ -940,7 +989,7 @@ export class SnapController extends BaseController< this.messagingSystem.registerActionHandler( `${controllerName}:updateSnapState`, - (...args) => this.updateSnapState(...args), + async (...args) => this.updateSnapState(...args), ); this.messagingSystem.registerActionHandler( @@ -1224,7 +1273,11 @@ export class SnapController extends BaseController< result.status !== SnapsRegistryStatus.Verified ) { throw new Error( - `Cannot install version "${snapInfo.version}" of snap "${snapId}": The snap is not on the allowlist.`, + `Cannot install version "${snapInfo.version}" of snap "${snapId}": ${ + result.status === SnapsRegistryStatus.Unavailable + ? 'The registry is temporarily unavailable.' + : 'The snap is not on the allowlist.' + }`, ); } } @@ -1380,16 +1433,26 @@ export class SnapController extends BaseController< throw new Error(`The snap "${snapId}" is not running.`); } - // Reset request tracking - runtime.lastRequest = null; - runtime.pendingInboundRequests = []; - runtime.pendingOutboundRequests = 0; + // No-op if the Snap is already stopping. + if (runtime.stopping) { + return; + } + + // Flag that the Snap is actively stopping, this prevents other calls to stopSnap + // while we are handling termination of the Snap + runtime.stopping = true; + try { if (this.isRunning(snapId)) { this.#closeAllConnections?.(snapId); await this.#terminateSnap(snapId); } } finally { + // Reset request tracking + runtime.lastRequest = null; + runtime.pendingInboundRequests = []; + runtime.pendingOutboundRequests = 0; + runtime.stopping = false; if (this.isRunning(snapId)) { this.#transition(snapId, statusEvent); } @@ -1403,6 +1466,19 @@ export class SnapController extends BaseController< */ async #terminateSnap(snapId: SnapId) { await this.messagingSystem.call('ExecutionService:terminateSnap', snapId); + + // Hack to give up execution for a bit to let gracefully terminating Snaps return. + await new Promise((resolve) => setTimeout(resolve, 1)); + + const runtime = this.#getRuntimeExpect(snapId); + // Unresponsive requests may still be timed, time them out. + runtime.pendingInboundRequests + .filter((pendingRequest) => pendingRequest.timer.status !== 'finished') + .forEach((pendingRequest) => pendingRequest.timer.finish()); + + // Hack to give up execution for a bit to let timed out requests return. + await new Promise((resolve) => setTimeout(resolve, 1)); + this.messagingSystem.publish( 'SnapController:snapTerminated', this.getTruncatedExpect(snapId), @@ -1483,6 +1559,107 @@ export class SnapController extends BaseController< return truncateSnap(this.getExpect(snapId)); } + /** + * Generate an encryption key to be used for state encryption for a given Snap. + * + * @param options - An options bag. + * @param options.snapId - The Snap ID. + * @param options.salt - A salt to be used for the encryption key. + * @param options.useCache - Whether to use caching or not. + * @param options.keyMetadata - Optional metadata about how to derive the encryption key. + * @returns An encryption key. + */ + async #getSnapEncryptionKey({ + snapId, + salt: passedSalt, + useCache, + keyMetadata, + }: { + snapId: SnapId; + salt?: string; + useCache: boolean; + keyMetadata?: KeyDerivationOptions; + }): Promise<{ key: unknown; salt: string }> { + const runtime = this.#getRuntimeExpect(snapId); + + if (runtime.encryptionKey && runtime.encryptionSalt && useCache) { + return { + key: await this.#encryptor.importKey(runtime.encryptionKey), + salt: runtime.encryptionSalt, + }; + } + + const salt = passedSalt ?? this.#encryptor.generateSalt(); + const mnemonicPhrase = await this.#getMnemonic(); + const entropy = await getEncryptionEntropy({ snapId, mnemonicPhrase }); + const encryptionKey = await this.#encryptor.keyFromPassword( + entropy, + salt, + true, + keyMetadata, + ); + const exportedKey = await this.#encryptor.exportKey(encryptionKey); + + // Cache exported encryption key in runtime + if (useCache) { + runtime.encryptionKey = exportedKey; + runtime.encryptionSalt = salt; + } + return { key: encryptionKey, salt }; + } + + /** + * Decrypt the encrypted state for a given Snap. + * + * @param snapId - The Snap ID. + * @param state - The encrypted state as a string. + * @returns A valid JSON object derived from the encrypted state. + * @throws If the decryption fails or the decrypted state is not valid JSON. + */ + async #decryptSnapState(snapId: SnapId, state: string) { + try { + const parsed = parseJson(state); + const { salt, keyMetadata } = parsed; + const useCache = this.#encryptor.isVaultUpdated(state); + const { key } = await this.#getSnapEncryptionKey({ + snapId, + salt, + useCache, + keyMetadata, + }); + const decryptedState = await this.#encryptor.decryptWithKey(key, parsed); + + assert(isValidJson(decryptedState)); + + return decryptedState as Record; + } catch { + throw rpcErrors.internal({ + message: 'Failed to decrypt snap state, the state must be corrupted.', + }); + } + } + + /** + * Encrypt a JSON state object for a given Snap. + * + * Note: This function does not assert the validity of the object, + * please ensure only valid JSON is passed to it. + * + * @param snapId - The Snap ID. + * @param state - The state object. + * @returns A string containing the encrypted JSON object. + */ + async #encryptSnapState(snapId: SnapId, state: Record) { + const { key, salt } = await this.#getSnapEncryptionKey({ + snapId, + useCache: true, + }); + const encryptedState = await this.#encryptor.encryptWithKey(key, state); + + encryptedState.salt = salt; + return JSON.stringify(encryptedState); + } + /** * Updates the own state of the snap with the given id. * This is distinct from the state MetaMask uses to manage snaps. @@ -1491,14 +1668,22 @@ export class SnapController extends BaseController< * @param newSnapState - The new state of the snap. * @param encrypted - A flag to indicate whether to use encrypted storage or not. */ - updateSnapState(snapId: SnapId, newSnapState: string, encrypted: boolean) { - this.update((state) => { - if (encrypted) { - state.snapStates[snapId] = newSnapState; - } else { - state.unencryptedSnapStates[snapId] = newSnapState; - } - }); + async updateSnapState( + snapId: SnapId, + newSnapState: Record, + encrypted: boolean, + ) { + if (encrypted) { + const encryptedState = await this.#encryptSnapState(snapId, newSnapState); + + this.update((state) => { + state.snapStates[snapId] = encryptedState; + }); + } else { + this.update((state) => { + state.unencryptedSnapStates[snapId] = JSON.stringify(newSnapState); + }); + } } /** @@ -1526,11 +1711,21 @@ export class SnapController extends BaseController< * @param encrypted - A flag to indicate whether to use encrypted storage or not. * @returns The requested snap state or null if no state exists. */ - getSnapState(snapId: SnapId, encrypted: boolean): Json { + async getSnapState(snapId: SnapId, encrypted: boolean): Promise { const state = encrypted ? this.state.snapStates[snapId] : this.state.unencryptedSnapStates[snapId]; - return state ?? null; + + if (state === null || state === undefined) { + return null; + } + + if (!encrypted) { + return parseJson(state); + } + + const decrypted = await this.#decryptSnapState(snapId, state); + return decrypted; } /** @@ -1624,6 +1819,7 @@ export class SnapController extends BaseController< this.update((state: any) => { delete state.snaps[snapId]; delete state.snapStates[snapId]; + delete state.unencryptedSnapStates[snapId]; }); // If the snap has been fully installed before, also emit snapUninstalled. @@ -2216,6 +2412,7 @@ export class SnapController extends BaseController< this.#calculatePermissionsChange(snapId, processedPermissions); this.#updateApproval(pendingApproval.id, { + connections: manifest.initialConnections ?? {}, permissions: newPermissions, newVersion: manifest.version, newPermissions, @@ -2406,12 +2603,14 @@ export class SnapController extends BaseController< try { const runtime = this.#getRuntimeExpect(snapId); - const result = await this.#executeWithTimeout( - this.messagingSystem.call('ExecutionService:executeSnap', { + const result = await this.messagingSystem.call( + 'ExecutionService:executeSnap', + { ...snapData, endowments: await this.#getEndowments(snapId), - }), + }, ); + this.#transition(snapId, SnapStatusEvents.Start); // We treat the initialization of the snap as the first request, for idle timing purposes. runtime.lastRequest = Date.now(); @@ -2562,6 +2761,7 @@ export class SnapController extends BaseController< preinstalled, id: snapId, + initialConnections: manifest.result.initialConnections, initialPermissions: manifest.result.initialPermissions, manifest: manifest.result, status: this.#statusMachine.config.initial as StatusStates['value'], @@ -2659,7 +2859,7 @@ export class SnapController extends BaseController< log(`Authorizing snap: ${snapId}`); const snapsState = this.state.snaps; const snap = snapsState[snapId]; - const { initialPermissions } = snap; + const { initialPermissions, initialConnections } = snap; try { const processedPermissions = processSnapPermissions(initialPermissions); @@ -2668,6 +2868,7 @@ export class SnapController extends BaseController< this.#updateApproval(pendingApproval.id, { loading: false, + connections: initialConnections ?? {}, permissions: processedPermissions, }); @@ -2899,15 +3100,28 @@ export class SnapController extends BaseController< // This will either get the result or reject due to the timeout. try { - const result = await this.#executeWithTimeout( - handleRpcRequestPromise, - timer, - ); + const result = await withTimeout(handleRpcRequestPromise, timer); + + if (result === hasTimedOut) { + throw new Error( + `${snapId} failed to respond to the request in time.`, + ); + } await this.#assertSnapRpcRequestResult(snapId, handlerType, result); - return this.#transformSnapRpcRequestResult(snapId, handlerType, result); + const transformedResult = await this.#transformSnapRpcRequestResult( + snapId, + handlerType, + result, + ); + + this.#recordSnapRpcRequestFinish(snapId, request.id); + + return transformedResult; } catch (error) { + // We flag the RPC request as finished early since termination may affect pending requests + this.#recordSnapRpcRequestFinish(snapId, request.id); const [jsonRpcError, handled] = unwrapError(error); if (!handled) { @@ -2915,8 +3129,6 @@ export class SnapController extends BaseController< } throw jsonRpcError; - } finally { - this.#recordSnapRpcRequestFinish(snapId, request.id); } }; @@ -3033,26 +3245,6 @@ export class SnapController extends BaseController< } } - /** - * Awaits the specified promise and rejects if the promise doesn't resolve - * before the timeout. - * - * @param promise - The promise to await. - * @param timer - An optional timer object to control the timeout. - * @returns The result of the promise or rejects if the promise times out. - * @template PromiseValue - The value of the Promise. - */ - async #executeWithTimeout( - promise: Promise, - timer?: Timer, - ): Promise { - const result = await withTimeout(promise, timer ?? this.maxRequestTime); - if (result === hasTimedOut) { - throw new Error('The request timed out.'); - } - return result; - } - #recordSnapRpcRequestStart(snapId: SnapId, requestId: unknown, timer: Timer) { const runtime = this.#getRuntimeExpect(snapId); runtime.pendingInboundRequests.push({ requestId, timer }); @@ -3209,10 +3401,13 @@ export class SnapController extends BaseController< lastRequest: null, rpcHandler: null, installPromise: null, + encryptionKey: null, + encryptionSalt: null, activeReferences: 0, pendingInboundRequests: [], pendingOutboundRequests: 0, interpreter, + stopping: false, }); } @@ -3237,14 +3432,23 @@ export class SnapController extends BaseController< snapId, ) ?? {}; - const newPermissions = setDiff(desiredPermissionsSet, oldPermissions); + const newPermissions = permissionsDiff( + desiredPermissionsSet, + oldPermissions, + ); // TODO(ritave): The assumption that these are unused only holds so long as we do not // permit dynamic permission requests. - const unusedPermissions = setDiff(oldPermissions, desiredPermissionsSet); + const unusedPermissions = permissionsDiff( + oldPermissions, + desiredPermissionsSet, + ); // It's a Set Intersection of oldPermissions and desiredPermissionsSet // oldPermissions ∖ (oldPermissions ∖ desiredPermissionsSet) ⟺ oldPermissions ∩ desiredPermissionsSet - const approvedPermissions = setDiff(oldPermissions, unusedPermissions); + const approvedPermissions = permissionsDiff( + oldPermissions, + unusedPermissions, + ); return { newPermissions, unusedPermissions, approvedPermissions }; } @@ -3303,7 +3507,7 @@ export class SnapController extends BaseController< * to return false in that scenario. * * @param snapId - The snap id. - * @param newVersionRange - The new version range being requsted. + * @param newVersionRange - The new version range being requested. * @returns `true` if validation checks pass and `false` if they do not. */ #isValidUpdate(snapId: SnapId, newVersionRange: SemVerRange): boolean { diff --git a/packages/snaps-controllers/src/snaps/Timer.test.ts b/packages/snaps-controllers/src/snaps/Timer.test.ts index 96bfe2687b..47d6603e6a 100644 --- a/packages/snaps-controllers/src/snaps/Timer.test.ts +++ b/packages/snaps-controllers/src/snaps/Timer.test.ts @@ -19,6 +19,7 @@ describe('Timer', () => { expect(callback).not.toHaveBeenCalled(); jest.advanceTimersByTime(500); expect(callback).toHaveBeenCalled(); + expect(timer.remaining).toBe(0); }); it('calls the callback', () => { @@ -40,6 +41,18 @@ describe('Timer', () => { timer.cancel(); jest.advanceTimersByTime(1000); expect(callback).not.toHaveBeenCalled(); + expect(timer.remaining).toBe(1000); + }); + + it('can finish', () => { + const timer = new Timer(1000); + const callback = jest.fn(); + timer.start(callback); + timer.finish(); + expect(callback).toHaveBeenCalled(); + expect(timer.status).toBe('finished'); + jest.advanceTimersByTime(1000); + expect(callback).toHaveBeenCalledTimes(1); }); it('works with +Infinity', () => { @@ -148,6 +161,29 @@ describe('Timer', () => { expect(callback).toHaveBeenCalled(); }); + it('updates remaining', () => { + const timer = new Timer(1000); + const callback = jest.fn(); + + expect(timer.remaining).toBe(1000); + + timer.start(callback); + + jest.advanceTimersByTime(200); + + timer.pause(); + expect(timer.remaining).toBe(800); + + timer.resume(); + expect(timer.status).toBe('running'); + + jest.advanceTimersByTime(800); + expect(timer.status).toBe('finished'); + expect(timer.remaining).toBe(0); + + expect(callback).toHaveBeenCalled(); + }); + it('throws when negative number given', () => { const ERROR = new TypeError("Can't start a timer with negative time"); expect(() => new Timer(-1000)).toThrow(ERROR); diff --git a/packages/snaps-controllers/src/snaps/Timer.ts b/packages/snaps-controllers/src/snaps/Timer.ts index d4ddd12b18..3c50853ca4 100644 --- a/packages/snaps-controllers/src/snaps/Timer.ts +++ b/packages/snaps-controllers/src/snaps/Timer.ts @@ -17,7 +17,7 @@ export class Timer { start: number; timeout?: unknown; } - | { value: 'finished' }; + | { value: 'finished'; remaining: number }; /** * If `ms` is smaller or equal to zero (including -Infinity), the callback is added to the event loop and executed async immediately @@ -39,6 +39,10 @@ export class Timer { return this.state.value; } + get remaining(): number { + return this.state.remaining; + } + /** * Cancels the currently running timer and marks it finished. * @@ -52,6 +56,19 @@ export class Timer { this.onFinish(false); } + /** + * Marks the timer as finished prematurely and triggers the callback. + * + * @throws {@link Error}. If it wasn't running or paused. + */ + finish() { + assert( + this.status !== 'finished', + new Error('Tried to finish a finished Timer.'), + ); + this.onFinish(true); + } + /** * Pauses a currently running timer, allowing it to resume later. * @@ -119,8 +136,14 @@ export class Timer { clearTimeout(this.state.timeout as any); } - const { callback } = this.state; - this.state = { value: 'finished' }; + const { callback, remaining } = this.state; + this.state = { + value: 'finished', + remaining: + this.state.value === 'running' + ? remaining - (Date.now() - this.state.start) + : remaining, + }; if (shouldCall) { callback(); diff --git a/packages/snaps-controllers/src/snaps/location/npm.test.ts b/packages/snaps-controllers/src/snaps/location/npm.test.ts index 364a6056ad..dd38d3a38b 100644 --- a/packages/snaps-controllers/src/snaps/location/npm.test.ts +++ b/packages/snaps-controllers/src/snaps/location/npm.test.ts @@ -380,7 +380,7 @@ describe('NpmLocation', () => { }); // TODO(ritave): Doing this tests requires writing tarball packing utility function - // With the the current changeset going way out of scope, I'm leaving this for future. + // With the current changeset going way out of scope, I'm leaving this for future. it.todo('sets canonical path properly'); // TODO(ritave): Requires writing tarball packing utility out of scope for a hot-fix blocking release. it.todo('paths are normalized to remove "./" prefix'); diff --git a/packages/snaps-controllers/src/snaps/registry/json.test.ts b/packages/snaps-controllers/src/snaps/registry/json.test.ts index 5441367f81..b31afd1f78 100644 --- a/packages/snaps-controllers/src/snaps/registry/json.test.ts +++ b/packages/snaps-controllers/src/snaps/registry/json.test.ts @@ -242,11 +242,15 @@ describe('JsonSnapsRegistry', () => { }); }); - it('returns unverified for unavailable database if failOnUnavailableRegistry is set to false', async () => { + it('uses existing state if registry is unavailable', async () => { fetchMock.mockResponse('', { status: 404 }); const { messenger } = getRegistry({ - failOnUnavailableRegistry: false, + refetchOnAllowlistMiss: true, + state: { + lastUpdated: 0, + database: MOCK_DATABASE, + }, }); const result = await messenger.call('SnapsRegistry:get', { @@ -258,48 +262,56 @@ describe('JsonSnapsRegistry', () => { expect(result).toStrictEqual({ [MOCK_SNAP_ID]: { - status: SnapsRegistryStatus.Unverified, + status: SnapsRegistryStatus.Verified, }, }); }); - it('does not verify the signature if no public key is provided', async () => { - fetchMock.mockResponse(JSON.stringify(MOCK_DATABASE)); + it(`doesn't use existing state if existing state isn't sufficient`, async () => { + fetchMock.mockResponse('', { status: 404 }); const { messenger } = getRegistry({ - publicKey: undefined, + refetchOnAllowlistMiss: true, + state: { + lastUpdated: 0, + database: MOCK_DATABASE, + }, }); const result = await messenger.call('SnapsRegistry:get', { [MOCK_SNAP_ID]: { - version: '1.0.0' as SemVerVersion, + version: '1.0.1' as SemVerVersion, checksum: DEFAULT_SNAP_SHASUM, }, }); expect(result).toStrictEqual({ [MOCK_SNAP_ID]: { - status: SnapsRegistryStatus.Verified, + status: SnapsRegistryStatus.Unavailable, }, }); }); - it('throws for unavailable database by default', async () => { + it('returns unavailable if the database is unavailable', async () => { fetchMock.mockResponse('', { status: 404 }); const { messenger } = getRegistry(); - await expect( - messenger.call('SnapsRegistry:get', { - [MOCK_SNAP_ID]: { - version: '1.0.0' as SemVerVersion, - checksum: DEFAULT_SNAP_SHASUM, - }, - }), - ).rejects.toThrow('Snaps registry is unavailable, installation blocked.'); + const result = await messenger.call('SnapsRegistry:get', { + [MOCK_SNAP_ID]: { + version: '1.0.0' as SemVerVersion, + checksum: DEFAULT_SNAP_SHASUM, + }, + }); + + expect(result).toStrictEqual({ + [MOCK_SNAP_ID]: { + status: SnapsRegistryStatus.Unavailable, + }, + }); }); - it('throws for unavailable signature', async () => { + it('returns unavailable if signature is unavailable', async () => { fetchMock .mockResponseOnce(JSON.stringify(MOCK_DATABASE)) .mockResponseOnce('', { @@ -308,17 +320,21 @@ describe('JsonSnapsRegistry', () => { const { messenger } = getRegistry(); - await expect( - messenger.call('SnapsRegistry:get', { - [MOCK_SNAP_ID]: { - version: '1.0.0' as SemVerVersion, - checksum: DEFAULT_SNAP_SHASUM, - }, - }), - ).rejects.toThrow('Snaps registry is unavailable, installation blocked.'); + const result = await messenger.call('SnapsRegistry:get', { + [MOCK_SNAP_ID]: { + version: '1.0.0' as SemVerVersion, + checksum: DEFAULT_SNAP_SHASUM, + }, + }); + + expect(result).toStrictEqual({ + [MOCK_SNAP_ID]: { + status: SnapsRegistryStatus.Unavailable, + }, + }); }); - it('throws for invalid signature', async () => { + it('returns unavailable if signature is invalid', async () => { fetchMock .mockResponseOnce(JSON.stringify(MOCK_DATABASE)) .mockResponseOnce(JSON.stringify(MOCK_SIGNATURE_FILE)); @@ -328,14 +344,18 @@ describe('JsonSnapsRegistry', () => { '0x034ca27b046507d1a9997bddc991b56d96b93d4adac3a96dfe01ce450bfb661455', }); - await expect( - messenger.call('SnapsRegistry:get', { - [MOCK_SNAP_ID]: { - version: '1.0.0' as SemVerVersion, - checksum: DEFAULT_SNAP_SHASUM, - }, - }), - ).rejects.toThrow('Snaps registry is unavailable, installation blocked.'); + const result = await messenger.call('SnapsRegistry:get', { + [MOCK_SNAP_ID]: { + version: '1.0.0' as SemVerVersion, + checksum: DEFAULT_SNAP_SHASUM, + }, + }); + + expect(result).toStrictEqual({ + [MOCK_SNAP_ID]: { + status: SnapsRegistryStatus.Unavailable, + }, + }); }); describe('resolveVersion', () => { @@ -354,38 +374,38 @@ describe('JsonSnapsRegistry', () => { expect(result).toBe('1.0.0'); }); - it('throws if snap is not on the allowlist', async () => { + it('returns version range if snap is not on the allowlist', async () => { fetchMock .mockResponseOnce( JSON.stringify({ verifiedSnaps: {}, blockedSnaps: [] }), ) .mockResponseOnce(JSON.stringify(MOCK_EMPTY_SIGNATURE_FILE)); + const range = '^1.0.0' as SemVerRange; const { messenger } = getRegistry(); - await expect( - messenger.call( + expect( + await messenger.call( 'SnapsRegistry:resolveVersion', MOCK_SNAP_ID, - '^1.0.0' as SemVerRange, + range, ), - ).rejects.toThrow('The snap is not on the allowlist'); + ).toBe(range); }); - it('throws if resolved version is not on the allowlist', async () => { + it('returns version range if resolved version is not on the allowlist', async () => { fetchMock .mockResponseOnce(JSON.stringify(MOCK_DATABASE)) .mockResponseOnce(JSON.stringify(MOCK_SIGNATURE_FILE)); + const range = '^1.2.0' as SemVerRange; const { messenger } = getRegistry(); - await expect( - messenger.call( + expect( + await messenger.call( 'SnapsRegistry:resolveVersion', MOCK_SNAP_ID, - '^1.2.0' as SemVerRange, + range, ), - ).rejects.toThrow( - 'No matching versions of the snap are on the allowlist', - ); + ).toBe(range); }); it('refetches the database on allowlist miss if configured', async () => { diff --git a/packages/snaps-controllers/src/snaps/registry/json.ts b/packages/snaps-controllers/src/snaps/registry/json.ts index 437d4b8eff..f9cf3af5ad 100644 --- a/packages/snaps-controllers/src/snaps/registry/json.ts +++ b/packages/snaps-controllers/src/snaps/registry/json.ts @@ -21,12 +21,14 @@ import type { } from './registry'; import { SnapsRegistryStatus } from './registry'; -// TODO: Replace with a Codefi URL const SNAP_REGISTRY_URL = - 'https://cdn.jsdelivr.net/gh/MetaMask/snaps-registry@gh-pages/latest/registry.json'; + 'https://acl.execution.metamask.io/latest/registry.json'; const SNAP_REGISTRY_SIGNATURE_URL = - 'https://cdn.jsdelivr.net/gh/MetaMask/snaps-registry@gh-pages/latest/signature.json'; + 'https://acl.execution.metamask.io/latest/signature.json'; + +const DEFAULT_PUBLIC_KEY = + '0x025b65308f0f0fb8bc7f7ff87bfc296e0330eee5d3c1d1ee4a048b2fd6a86fa0a6'; type JsonSnapsRegistryUrl = { registry: string; @@ -40,7 +42,6 @@ export type JsonSnapsRegistryArgs = { url?: JsonSnapsRegistryUrl; recentFetchThreshold?: number; refetchOnAllowlistMiss?: boolean; - failOnUnavailableRegistry?: boolean; publicKey?: Hex; }; @@ -83,6 +84,7 @@ export type SnapsRegistryMessenger = RestrictedControllerMessenger< export type SnapsRegistryState = { database: SnapsRegistryDatabase | null; lastUpdated: number | null; + databaseUnavailable: boolean; }; const controllerName = 'SnapsRegistry'; @@ -90,6 +92,7 @@ const controllerName = 'SnapsRegistry'; const defaultState = { database: null, lastUpdated: null, + databaseUnavailable: false, }; export class JsonSnapsRegistry extends BaseController< @@ -99,7 +102,7 @@ export class JsonSnapsRegistry extends BaseController< > { #url: JsonSnapsRegistryUrl; - #publicKey?: Hex; + #publicKey: Hex; #fetchFunction: typeof fetch; @@ -107,8 +110,6 @@ export class JsonSnapsRegistry extends BaseController< #refetchOnAllowlistMiss: boolean; - #failOnUnavailableRegistry: boolean; - #currentUpdate: Promise | null; constructor({ @@ -118,10 +119,9 @@ export class JsonSnapsRegistry extends BaseController< registry: SNAP_REGISTRY_URL, signature: SNAP_REGISTRY_SIGNATURE_URL, }, - publicKey, + publicKey = DEFAULT_PUBLIC_KEY, fetchFunction = globalThis.fetch.bind(globalThis), recentFetchThreshold = inMilliseconds(5, Duration.Minute), - failOnUnavailableRegistry = true, refetchOnAllowlistMiss = true, }: JsonSnapsRegistryArgs) { super({ @@ -129,6 +129,7 @@ export class JsonSnapsRegistry extends BaseController< metadata: { database: { persist: true, anonymous: false }, lastUpdated: { persist: true, anonymous: false }, + databaseUnavailable: { persist: true, anonymous: false }, }, name: controllerName, state: { @@ -141,7 +142,6 @@ export class JsonSnapsRegistry extends BaseController< this.#fetchFunction = fetchFunction; this.#recentFetchThreshold = recentFetchThreshold; this.#refetchOnAllowlistMiss = refetchOnAllowlistMiss; - this.#failOnUnavailableRegistry = failOnUnavailableRegistry; this.#currentUpdate = null; this.messagingSystem.registerActionHandler( @@ -205,17 +205,19 @@ export class JsonSnapsRegistry extends BaseController< try { const database = await this.#safeFetch(this.#url.registry); - if (this.#publicKey) { - const signature = await this.#safeFetch(this.#url.signature); - this.#verifySignature(database, signature); - } + const signature = await this.#safeFetch(this.#url.signature); + this.#verifySignature(database, signature); this.update((state) => { state.database = JSON.parse(database); state.lastUpdated = Date.now(); + state.databaseUnavailable = false; }); } catch { // Ignore + this.update((state) => { + state.databaseUnavailable = true; + }); } } @@ -224,10 +226,6 @@ export class JsonSnapsRegistry extends BaseController< await this.#triggerUpdate(); } - // If the database is still null and we require it, throw. - if (this.#failOnUnavailableRegistry && this.state.database === null) { - throw new Error('Snaps registry is unavailable, installation blocked.'); - } return this.state.database; } @@ -266,7 +264,11 @@ export class JsonSnapsRegistry extends BaseController< await this.#triggerUpdate(); return this.#getSingle(snapId, snapInfo, true); } - return { status: SnapsRegistryStatus.Unverified }; + return { + status: this.state.databaseUnavailable + ? SnapsRegistryStatus.Unavailable + : SnapsRegistryStatus.Unverified, + }; } async #get( @@ -283,13 +285,12 @@ export class JsonSnapsRegistry extends BaseController< } /** - * Find an allowlisted version within a specified version range. + * Find an allowlisted version within a specified version range. Otherwise return the version range itself. * * @param snapId - The ID of the snap we are trying to resolve a version for. * @param versionRange - The version range. * @param refetch - An optional flag used to determine if we are refetching the registry. - * @returns An allowlisted version within the specified version range. - * @throws If an allowlisted version does not exist within the version range. + * @returns An allowlisted version within the specified version range if available otherwise returns the input version range. */ async #resolveVersion( snapId: string, @@ -304,7 +305,10 @@ export class JsonSnapsRegistry extends BaseController< return this.#resolveVersion(snapId, versionRange, true); } - assert(versions, 'The snap is not on the allowlist'); + // If we cannot narrow down the version range we return the unaltered version range. + if (!versions) { + return versionRange; + } const targetVersion = getTargetVersion( Object.keys(versions) as SemVerVersion[], @@ -316,10 +320,10 @@ export class JsonSnapsRegistry extends BaseController< return this.#resolveVersion(snapId, versionRange, true); } - assert( - targetVersion, - 'No matching versions of the snap are on the allowlist', - ); + // If we cannot narrow down the version range we return the unaltered version range. + if (!targetVersion) { + return versionRange; + } // A semver version is technically also a valid semver range. assertIsSemVerRange(targetVersion); diff --git a/packages/snaps-controllers/src/snaps/registry/registry.ts b/packages/snaps-controllers/src/snaps/registry/registry.ts index a9e4125c13..68e6f9f5ca 100644 --- a/packages/snaps-controllers/src/snaps/registry/registry.ts +++ b/packages/snaps-controllers/src/snaps/registry/registry.ts @@ -10,11 +10,11 @@ export type SnapsRegistryRequest = Record; export type SnapsRegistryMetadata = SnapsRegistryDatabase['verifiedSnaps'][SnapId]['metadata']; -// TODO: Decide on names for these export enum SnapsRegistryStatus { Unverified = 0, Blocked = 1, Verified = 2, + Unavailable = 3, } export type SnapsRegistryResult = { diff --git a/packages/snaps-controllers/src/test-utils/controller.ts b/packages/snaps-controllers/src/test-utils/controller.ts index 8aa5818803..6267b27148 100644 --- a/packages/snaps-controllers/src/test-utils/controller.ts +++ b/packages/snaps-controllers/src/test-utils/controller.ts @@ -1,4 +1,13 @@ import type { ApprovalRequest } from '@metamask/approval-controller'; +import { + encryptWithKey, + decryptWithKey, + keyFromPassword, + importKey, + exportKey, + generateSalt, + isVaultUpdated, +} from '@metamask/browser-passworder'; import type { PermissionConstraint, SubjectPermissions, @@ -21,6 +30,7 @@ import { MOCK_LOCAL_SNAP_ID, MOCK_ORIGIN, MOCK_SNAP_ID, + TEST_SECRET_RECOVERY_PHRASE_BYTES, } from '@metamask/snaps-utils/test-utils'; import type { Json } from '@metamask/utils'; @@ -43,6 +53,7 @@ import type { SnapsRegistryEvents, } from '../snaps'; import { SnapController } from '../snaps'; +import type { KeyDerivationOptions } from '../types'; import { MOCK_CRONJOB_PERMISSION } from './cronjob'; import { getNodeEES, getNodeEESMessenger } from './execution-environment'; import { MockSnapsRegistry } from './registry'; @@ -162,6 +173,19 @@ export const MOCK_DAPPS_RPC_ORIGINS_PERMISSION: PermissionConstraint = { parentCapability: SnapEndowments.Rpc, }; +export const MOCK_ALLOWED_RPC_ORIGINS_PERMISSION: PermissionConstraint = { + caveats: [ + { + type: SnapCaveatType.RpcOrigin, + value: { allowedOrigins: ['https://metamask.io'] }, + }, + ], + date: 1664187844588, + id: 'izn0WGUO8cvq_jqvLQuQP', + invoker: MOCK_SNAP_ID, + parentCapability: SnapEndowments.Rpc, +}; + export const MOCK_SNAP_DIALOG_PERMISSION: PermissionConstraint = { caveats: null, date: 1664187844588, @@ -424,6 +448,37 @@ export type PartialSnapControllerConstructorParams = Partial< } >; +export const DEFAULT_ENCRYPTION_KEY_DERIVATION_OPTIONS = { + algorithm: 'PBKDF2' as const, + params: { + iterations: 600_000, + }, +}; + +const getSnapControllerEncryptor = () => { + return { + encryptWithKey, + decryptWithKey, + keyFromPassword: async ( + password: string, + salt: string, + exportable: boolean, + opts?: KeyDerivationOptions, + ) => + keyFromPassword( + password, + salt, + exportable, + opts ?? DEFAULT_ENCRYPTION_KEY_DERIVATION_OPTIONS, + ), + importKey, + exportKey, + generateSalt, + isVaultUpdated: (vault: string) => + isVaultUpdated(vault, DEFAULT_ENCRYPTION_KEY_DERIVATION_OPTIONS), + }; +}; + export const getSnapControllerOptions = ( opts?: PartialSnapControllerConstructorParams, ) => { @@ -434,6 +489,8 @@ export const getSnapControllerOptions = ( featureFlags: { dappsCanUpdateSnaps: true }, state: undefined, fetchFunction: jest.fn(), + getMnemonic: async () => Promise.resolve(TEST_SECRET_RECOVERY_PHRASE_BYTES), + encryptor: getSnapControllerEncryptor(), ...opts, } as SnapControllerConstructorParams; @@ -462,6 +519,8 @@ export const getSnapControllerWithEESOptions = ({ closeAllConnections: jest.fn(), messenger: snapControllerMessenger, rootMessenger, + getMnemonic: async () => Promise.resolve(TEST_SECRET_RECOVERY_PHRASE_BYTES), + encryptor: getSnapControllerEncryptor(), fetchFunction: jest.fn(), ...options, } as SnapControllerConstructorParams & { diff --git a/packages/snaps-controllers/src/test-utils/execution-environment.ts b/packages/snaps-controllers/src/test-utils/execution-environment.ts index ec2ade36a7..dc8e847071 100644 --- a/packages/snaps-controllers/src/test-utils/execution-environment.ts +++ b/packages/snaps-controllers/src/test-utils/execution-environment.ts @@ -1,7 +1,7 @@ import { JsonRpcEngine } from '@metamask/json-rpc-engine'; +import { createEngineStream } from '@metamask/json-rpc-middleware-stream'; import { logError, type SnapRpcHookArgs } from '@metamask/snaps-utils'; import type { MockControllerMessenger } from '@metamask/snaps-utils/test-utils'; -import { createEngineStream } from 'json-rpc-middleware-stream'; import { pipeline } from 'readable-stream'; import type { @@ -10,7 +10,7 @@ import type { ExecutionServiceEvents, SnapExecutionData, } from '../services'; -import { NodeThreadExecutionService, setupMultiplex } from '../services'; +import { NodeThreadExecutionService, setupMultiplex } from '../services/node'; export const MOCK_BLOCK_NUMBER = '0xa70e75'; diff --git a/packages/snaps-controllers/src/test-utils/service.ts b/packages/snaps-controllers/src/test-utils/service.ts index c36917f319..90f150b493 100644 --- a/packages/snaps-controllers/src/test-utils/service.ts +++ b/packages/snaps-controllers/src/test-utils/service.ts @@ -1,7 +1,7 @@ import { ControllerMessenger } from '@metamask/base-controller'; import { JsonRpcEngine } from '@metamask/json-rpc-engine'; +import { createEngineStream } from '@metamask/json-rpc-middleware-stream'; import { logError } from '@metamask/snaps-utils'; -import { createEngineStream } from 'json-rpc-middleware-stream'; import { pipeline } from 'readable-stream'; import type { Duplex } from 'readable-stream'; diff --git a/packages/snaps-controllers/src/types/encryptor.ts b/packages/snaps-controllers/src/types/encryptor.ts new file mode 100644 index 0000000000..c0fd001fb5 --- /dev/null +++ b/packages/snaps-controllers/src/types/encryptor.ts @@ -0,0 +1,121 @@ +import type { Json } from '@metamask/utils'; + +export type PBKDF2Params = { + iterations: number; +}; + +export type KeyDerivationOptions = { + algorithm: 'PBKDF2'; + params: PBKDF2Params; +}; + +export type EncryptionResult = { + data: string; + iv: string; + salt: string; + keyMetadata?: KeyDerivationOptions; +}; + +/** + * A generic encryptor interface that supports encrypting and decrypting + * serializable data with a password. + */ +export type GenericEncryptor = { + /** + * Encrypt the given object with the given password. + * + * @param password - The password to encrypt with. + * @param object - The object to encrypt. + * @returns The encrypted string. + */ + encrypt: (password: string, object: Json) => Promise; + + /** + * Decrypt the given encrypted string with the given password. + * + * @param password - The password to decrypt with. + * @param encryptedString - The encrypted string to decrypt. + * @returns The decrypted object. + */ + decrypt: (password: string, encryptedString: string) => Promise; + + /** + * Check if the provided vault is up to date with the + * desired encryption algorithm. + * + * @param vault - The encrypted string to check. + * @param targetDerivationParams - The desired target derivation params. + * @returns The updated encrypted string. + */ + isVaultUpdated: ( + vault: string, + targetDerivationParams?: KeyDerivationOptions, + ) => boolean; +}; + +/** + * An encryptor interface that supports encrypting and decrypting + * serializable data with a password, and exporting and importing keys. + */ +export type ExportableKeyEncryptor = GenericEncryptor & { + /** + * Encrypt the given object with the given encryption key. + * + * @param key - The encryption key to encrypt with. + * @param object - The object to encrypt. + * @returns The encryption result. + */ + encryptWithKey: (key: unknown, object: Json) => Promise; + + /** + * Decrypt the given encrypted string with the given encryption key. + * + * @param key - The encryption key to decrypt with. + * @param encryptedString - The encrypted string to decrypt. + * @returns The decrypted object. + */ + decryptWithKey: ( + key: unknown, + encryptedString: EncryptionResult, + ) => Promise; + + /** + * Generate an encryption key from exported key string. + * + * @param key - The exported key string. + * @returns The encryption key. + */ + importKey: (key: string) => Promise; + + /** + * Export a generated key as a string. + * + * @param key - The encryption key. + * @returns The exported key string. + */ + exportKey: (key: unknown) => Promise; + + /** + * Generate a salt with a given length. + * + * @param length - The length of the salt, default is 32 bytes. + * @returns The base64 encoded salt bytes. + */ + generateSalt: (length?: number) => string; + + /** + * Generate an encryption key using a password. + * + * @param password - The password to use to generate key. + * @param salt - The salt string to use in key derivation. + * @param exportable - Whether or not the key should be exportable. + * @param opts - The options to use for key derivation. + * @returns The encryption key. + */ + keyFromPassword: ( + password: string, + salt: string, + exportable?: boolean, + opts?: KeyDerivationOptions, + ) => Promise; +}; diff --git a/packages/snaps-controllers/src/types/index.ts b/packages/snaps-controllers/src/types/index.ts new file mode 100644 index 0000000000..2accd3ddb4 --- /dev/null +++ b/packages/snaps-controllers/src/types/index.ts @@ -0,0 +1 @@ +export * from './encryptor'; diff --git a/packages/snaps-controllers/src/utils.test.ts b/packages/snaps-controllers/src/utils.test.ts index 246378470f..2abc7b8974 100644 --- a/packages/snaps-controllers/src/utils.test.ts +++ b/packages/snaps-controllers/src/utils.test.ts @@ -5,8 +5,16 @@ import { } from '@metamask/snaps-utils/test-utils'; import { assert } from '@metamask/utils'; -import { LoopbackLocation } from './test-utils'; -import { getSnapFiles, setDiff } from './utils'; +import { SnapEndowments } from '../../snaps-rpc-methods/src/endowments'; +import { + LoopbackLocation, + MOCK_ALLOWED_RPC_ORIGINS_PERMISSION, + MOCK_DAPPS_RPC_ORIGINS_PERMISSION, + MOCK_LIFECYCLE_HOOKS_PERMISSION, + MOCK_RPC_ORIGINS_PERMISSION, + MOCK_SNAP_DIALOG_PERMISSION, +} from './test-utils'; +import { getSnapFiles, permissionsDiff, setDiff } from './utils'; describe('setDiff', () => { it('does nothing on empty type {}-B', () => { @@ -38,6 +46,93 @@ describe('setDiff', () => { }); }); +describe('permissionsDiff', () => { + it('does nothing on empty type {}-B', () => { + expect( + permissionsDiff( + {}, + { [SnapEndowments.Rpc]: MOCK_RPC_ORIGINS_PERMISSION }, + ), + ).toStrictEqual({}); + }); + + it('does nothing on empty type A-{}', () => { + expect( + permissionsDiff( + { [SnapEndowments.Rpc]: MOCK_RPC_ORIGINS_PERMISSION }, + {}, + ), + ).toStrictEqual({ + [SnapEndowments.Rpc]: MOCK_RPC_ORIGINS_PERMISSION, + }); + }); + + it('does a difference', () => { + expect( + permissionsDiff( + { + [SnapEndowments.Rpc]: MOCK_RPC_ORIGINS_PERMISSION, + [SnapEndowments.LifecycleHooks]: MOCK_LIFECYCLE_HOOKS_PERMISSION, + }, + { [SnapEndowments.Rpc]: MOCK_RPC_ORIGINS_PERMISSION }, + ), + ).toStrictEqual({ + [SnapEndowments.LifecycleHooks]: MOCK_LIFECYCLE_HOOKS_PERMISSION, + }); + }); + + it('additional B permissions have no effect in A-B', () => { + expect( + permissionsDiff( + { + [SnapEndowments.Rpc]: MOCK_RPC_ORIGINS_PERMISSION, + [SnapEndowments.LifecycleHooks]: MOCK_LIFECYCLE_HOOKS_PERMISSION, + }, + { + [SnapEndowments.Rpc]: MOCK_RPC_ORIGINS_PERMISSION, + // eslint-disable-next-line @typescript-eslint/naming-convention + snap_dialog: MOCK_SNAP_DIALOG_PERMISSION, + }, + ), + ).toStrictEqual({ + [SnapEndowments.LifecycleHooks]: MOCK_LIFECYCLE_HOOKS_PERMISSION, + }); + }); + + it('considers caveats in diff', () => { + expect( + permissionsDiff( + { + [SnapEndowments.Rpc]: MOCK_DAPPS_RPC_ORIGINS_PERMISSION, + }, + { + [SnapEndowments.Rpc]: MOCK_ALLOWED_RPC_ORIGINS_PERMISSION, + }, + ), + ).toStrictEqual({ + [SnapEndowments.Rpc]: MOCK_DAPPS_RPC_ORIGINS_PERMISSION, + }); + + expect( + permissionsDiff( + { + [SnapEndowments.Rpc]: MOCK_ALLOWED_RPC_ORIGINS_PERMISSION, + }, + { + [SnapEndowments.Rpc]: MOCK_DAPPS_RPC_ORIGINS_PERMISSION, + }, + ), + ).toStrictEqual({ + [SnapEndowments.Rpc]: MOCK_ALLOWED_RPC_ORIGINS_PERMISSION, + }); + }); + + it('works for the same permissions A-A', () => { + const object = { [SnapEndowments.Rpc]: MOCK_RPC_ORIGINS_PERMISSION }; + expect(permissionsDiff(object, object)).toStrictEqual({}); + }); +}); + describe('getSnapFiles', () => { it('returns an empty array if `files` is undefined', async () => { const location = new LoopbackLocation(); diff --git a/packages/snaps-controllers/src/utils.ts b/packages/snaps-controllers/src/utils.ts index 961e1749b9..cf662dd065 100644 --- a/packages/snaps-controllers/src/utils.ts +++ b/packages/snaps-controllers/src/utils.ts @@ -1,3 +1,4 @@ +import type { PermissionConstraint } from '@metamask/permission-controller'; import type { SnapId } from '@metamask/snaps-sdk'; import { getErrorMessage } from '@metamask/snaps-sdk'; import { @@ -5,6 +6,7 @@ import { getValidatedLocalizationFiles, validateFetchedSnap, } from '@metamask/snaps-utils'; +import deepEqual from 'fast-deep-equal'; import type { SnapLocation } from './snaps'; import { Timer } from './snaps/Timer'; @@ -38,6 +40,38 @@ export function setDiff< ) as Diff; } +/** + * Calculate a difference between two permissions objects. + * + * Similar to `setDiff` except for one additional condition: + * Permissions in B should be removed from A if they exist in both and have differing caveats. + * + * @param permissionsA - An object containing one or more partial permissions. + * @param permissionsB - An object containing one or more partial permissions to be subtracted from A. + * @returns The permissions set A without properties from B. + */ +export function permissionsDiff< + PermissionsA extends Record>, + PermissionsB extends Record>, +>( + permissionsA: PermissionsA, + permissionsB: PermissionsB, +): Diff { + return Object.entries(permissionsA).reduce< + Record> + >((acc, [key, value]) => { + const isIncluded = key in permissionsB; + if ( + !isIncluded || + (isIncluded && + !deepEqual(value.caveats ?? [], permissionsB[key].caveats ?? [])) + ) { + acc[key] = value; + } + return acc; + }, {}) as Diff; +} + /** * A Promise that delays its return for a given amount of milliseconds. * diff --git a/packages/snaps-controllers/tsconfig.json b/packages/snaps-controllers/tsconfig.json index f2434f8276..24bec24e1b 100644 --- a/packages/snaps-controllers/tsconfig.json +++ b/packages/snaps-controllers/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "baseUrl": "./" }, - "include": ["./src", "scripts"], + "include": ["./src", "scripts", "package.json", "tsup.config.ts"], "references": [ { "path": "../snaps-execution-environments" }, { "path": "../snaps-rpc-methods" }, diff --git a/packages/snaps-controllers/tsup.config.ts b/packages/snaps-controllers/tsup.config.ts new file mode 100644 index 0000000000..3eaf645296 --- /dev/null +++ b/packages/snaps-controllers/tsup.config.ts @@ -0,0 +1,14 @@ +import deepmerge from 'deepmerge'; +import type { Options } from 'tsup'; + +import packageJson from './package.json'; + +// `tsup.config.ts` is not under `rootDir`, so we need to use `require` instead. +// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires +const { default: baseConfig } = require('../../tsup.config'); + +const config: Options = { + name: packageJson.name, +}; + +export default deepmerge(baseConfig, config); diff --git a/packages/snaps-execution-environments/CHANGELOG.md b/packages/snaps-execution-environments/CHANGELOG.md index 64b34fb75f..a1995c573b 100644 --- a/packages/snaps-execution-environments/CHANGELOG.md +++ b/packages/snaps-execution-environments/CHANGELOG.md @@ -6,6 +6,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [6.0.2] +### Fixed +- Throw an error if starting Snap has no exports ([#2357](https://github.com/MetaMask/snaps/pull/2357)) + +## [6.0.1] +### Fixed +- Allow `null` in `FormSubmitEventStruct` form state ([#2333](https://github.com/MetaMask/snaps/pull/2333)) + +## [6.0.0] +### Removed +- **BREAKING:** Remove broken `ethereum` properties ([#2296](https://github.com/MetaMask/snaps/pull/2296)) + - Snaps can no longer access `on` and `removeListener` on `ethereum`. + - This feature was already non-functional. + +## [5.0.4] +### Changed +- Bump MetaMask dependencies ([#2270](https://github.com/MetaMask/snaps/pull/2270)) + +## [5.0.3] +### Changed +- Bump `@metamask/providers` to `^15.0.0` ([#2231](https://github.com/MetaMask/snaps/pull/2231)) +- Bump `@metamask/json-rpc-engine` to `^7.3.3` ([#2247](https://github.com/MetaMask/snaps/pull/2247)) + +## [5.0.2] +### Changed +- Bump LavaMoat packages ([#2234](https://github.com/MetaMask/snaps/pull/2234)) + +## [5.0.1] +### Fixed +- Fix minor build configuration problems ([#2220](https://github.com/MetaMask/snaps/pull/2220)) + +## [5.0.0] +### Changed +- **BREAKING:** Update ESM build to be fully compliant with the ESM standard ([#2210](https://github.com/MetaMask/snaps/pull/2210)) +- Bump `@metamask/rpc-errors` to `^6.2.1` ([#2209](https://github.com/MetaMask/snaps/pull/2209)) + +### Fixed +- Enforce JSON-RPC response size limits ([#2201](https://github.com/MetaMask/snaps/pull/2201)) + +## [4.0.1] +### Changed +- Update several LavaMoat packages ([#2173](https://github.com/MetaMask/snaps/pull/2173)) + ## [4.0.0] ### Added - Add WebView execution environment ([#2005](https://github.com/MetaMask/snaps/pull/2005)) @@ -141,7 +184,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-execution-environments@4.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-execution-environments@6.0.2...HEAD +[6.0.2]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-execution-environments@6.0.1...@metamask/snaps-execution-environments@6.0.2 +[6.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-execution-environments@6.0.0...@metamask/snaps-execution-environments@6.0.1 +[6.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-execution-environments@5.0.4...@metamask/snaps-execution-environments@6.0.0 +[5.0.4]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-execution-environments@5.0.3...@metamask/snaps-execution-environments@5.0.4 +[5.0.3]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-execution-environments@5.0.2...@metamask/snaps-execution-environments@5.0.3 +[5.0.2]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-execution-environments@5.0.1...@metamask/snaps-execution-environments@5.0.2 +[5.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-execution-environments@5.0.0...@metamask/snaps-execution-environments@5.0.1 +[5.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-execution-environments@4.0.1...@metamask/snaps-execution-environments@5.0.0 +[4.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-execution-environments@4.0.0...@metamask/snaps-execution-environments@4.0.1 [4.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-execution-environments@3.5.0...@metamask/snaps-execution-environments@4.0.0 [3.5.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-execution-environments@3.4.3...@metamask/snaps-execution-environments@3.5.0 [3.4.3]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-execution-environments@3.4.2...@metamask/snaps-execution-environments@3.4.3 diff --git a/packages/snaps-execution-environments/coverage.json b/packages/snaps-execution-environments/coverage.json index 183928de47..a897dfc0c9 100644 --- a/packages/snaps-execution-environments/coverage.json +++ b/packages/snaps-execution-environments/coverage.json @@ -1,6 +1,6 @@ { - "branches": 81.04, - "functions": 90.13, - "lines": 90.73, - "statements": 90.11 + "branches": 80, + "functions": 90.06, + "lines": 90.76, + "statements": 90.13 } diff --git a/packages/snaps-execution-environments/lavamoat/browserify/iframe/policy.json b/packages/snaps-execution-environments/lavamoat/browserify/iframe/policy.json index e3a25e30e3..0dfa39a79b 100644 --- a/packages/snaps-execution-environments/lavamoat/browserify/iframe/policy.json +++ b/packages/snaps-execution-environments/lavamoat/browserify/iframe/policy.json @@ -42,25 +42,24 @@ "console": true }, "packages": { - "@metamask/json-rpc-engine": true, "@metamask/object-multiplex": true, + "@metamask/providers>@metamask/json-rpc-engine": true, + "@metamask/providers>@metamask/json-rpc-middleware-stream": true, "@metamask/providers>@metamask/safe-event-emitter": true, "@metamask/providers>is-stream": true, - "@metamask/providers>json-rpc-middleware-stream": true, "@metamask/rpc-errors": true, "eslint>fast-deep-equal": true, "readable-stream": true } }, - "@metamask/providers>@metamask/safe-event-emitter": { - "globals": { - "setTimeout": true - }, + "@metamask/providers>@metamask/json-rpc-engine": { "packages": { - "browserify>events": true + "@metamask/providers>@metamask/safe-event-emitter": true, + "@metamask/rpc-errors": true, + "@metamask/utils": true } }, - "@metamask/providers>json-rpc-middleware-stream": { + "@metamask/providers>@metamask/json-rpc-middleware-stream": { "globals": { "console.warn": true, "setTimeout": true @@ -70,24 +69,54 @@ "readable-stream": true } }, + "@metamask/providers>@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "browserify>events": true + } + }, "@metamask/rpc-errors": { "packages": { "@metamask/rpc-errors>fast-safe-stringify": true, "@metamask/utils": true } }, - "@metamask/snaps-sdk>is-svg": { + "@metamask/snaps-sdk": { + "globals": { + "fetch": true + }, "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser": true + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk>fast-xml-parser": true, + "@metamask/utils": true, + "superstruct": true } }, - "@metamask/snaps-sdk>is-svg>fast-xml-parser": { + "@metamask/snaps-sdk>fast-xml-parser": { "globals": { "entityName": true, "val": true }, "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser>strnum": true + "@metamask/snaps-sdk>fast-xml-parser>strnum": true + } + }, + "@metamask/snaps-utils": { + "globals": { + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true + }, + "packages": { + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/utils": true, + "superstruct": true } }, "@metamask/utils": { @@ -99,10 +128,10 @@ "@metamask/utils>@noble/hashes": true, "@metamask/utils>@scure/base": true, "@metamask/utils>pony-cause": true, - "@swc/cli>semver": true, "browserify>buffer": true, - "eslint>debug": true, - "superstruct": true + "depcheck>semver": true, + "superstruct": true, + "tsup>debug": true } }, "@metamask/utils>@noble/hashes": { @@ -117,20 +146,6 @@ "TextEncoder": true } }, - "@swc/cli>semver": { - "globals": { - "console.error": true - }, - "packages": { - "@swc/cli>semver>lru-cache": true, - "browserify>process": true - } - }, - "@swc/cli>semver>lru-cache": { - "packages": { - "@swc/cli>semver>lru-cache>yallist": true - } - }, "browserify>browser-pack>safe-buffer": { "packages": { "browserify>buffer": true @@ -161,333 +176,18 @@ "browserify>browser-pack>safe-buffer": true } }, - "eslint>debug": { + "depcheck>semver": { "globals": { - "console": true, - "document": true, - "localStorage": true, - "navigator": true, - "process": true + "console.error": true }, "packages": { "browserify>process": true, - "eslint>debug>ms": true - } - }, - "external:../snaps-sdk/src/error-wrappers.ts": { - "packages": { - "@metamask/rpc-errors": true, - "external:../snaps-sdk/src/internals/index.ts": true - } - }, - "external:../snaps-sdk/src/errors.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true - } - }, - "external:../snaps-sdk/src/images.ts": { - "globals": { - "fetch": true - }, - "packages": { - "@metamask/utils": true, - "external:../snaps-sdk/src/ui/index.ts": true - } - }, - "external:../snaps-sdk/src/index.ts": { - "packages": { - "@metamask/utils": true, - "external:../snaps-sdk/src/error-wrappers.ts": true, - "external:../snaps-sdk/src/errors.ts": true, - "external:../snaps-sdk/src/images.ts": true, - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/types/index.ts": true, - "external:../snaps-sdk/src/ui/index.ts": true - } - }, - "external:../snaps-sdk/src/internals/error-wrappers.ts": { - "packages": { - "external:../snaps-sdk/src/errors.ts": true - } - }, - "external:../snaps-sdk/src/internals/errors.ts": { - "packages": { - "@metamask/utils": true - } - }, - "external:../snaps-sdk/src/internals/index.ts": { - "packages": { - "external:../snaps-sdk/src/internals/error-wrappers.ts": true, - "external:../snaps-sdk/src/internals/errors.ts": true, - "external:../snaps-sdk/src/internals/helpers.ts": true, - "external:../snaps-sdk/src/internals/structs.ts": true - } - }, - "external:../snaps-sdk/src/internals/structs.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-sdk/src/types/handlers/index.ts": { - "packages": { - "external:../snaps-sdk/src/types/handlers/cronjob.ts": true, - "external:../snaps-sdk/src/types/handlers/home-page.ts": true, - "external:../snaps-sdk/src/types/handlers/keyring.ts": true, - "external:../snaps-sdk/src/types/handlers/lifecycle.ts": true, - "external:../snaps-sdk/src/types/handlers/name-lookup.ts": true, - "external:../snaps-sdk/src/types/handlers/rpc-request.ts": true, - "external:../snaps-sdk/src/types/handlers/signature.ts": true, - "external:../snaps-sdk/src/types/handlers/transaction.ts": true, - "external:../snaps-sdk/src/types/handlers/user-input.ts": true - } - }, - "external:../snaps-sdk/src/types/handlers/user-input.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-sdk/src/types/index.ts": { - "packages": { - "external:../snaps-sdk/src/types/caip.ts": true, - "external:../snaps-sdk/src/types/global.ts": true, - "external:../snaps-sdk/src/types/handlers/index.ts": true, - "external:../snaps-sdk/src/types/interface.ts": true, - "external:../snaps-sdk/src/types/methods/index.ts": true, - "external:../snaps-sdk/src/types/permissions.ts": true, - "external:../snaps-sdk/src/types/provider.ts": true, - "external:../snaps-sdk/src/types/snap.ts": true - } - }, - "external:../snaps-sdk/src/types/interface.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-sdk/src/types/methods/index.ts": { - "packages": { - "external:../snaps-sdk/src/types/methods/create-interface.ts": true, - "external:../snaps-sdk/src/types/methods/dialog.ts": true, - "external:../snaps-sdk/src/types/methods/get-bip32-entropy.ts": true, - "external:../snaps-sdk/src/types/methods/get-bip32-public-key.ts": true, - "external:../snaps-sdk/src/types/methods/get-bip44-entropy.ts": true, - "external:../snaps-sdk/src/types/methods/get-client-status.ts": true, - "external:../snaps-sdk/src/types/methods/get-entropy.ts": true, - "external:../snaps-sdk/src/types/methods/get-file.ts": true, - "external:../snaps-sdk/src/types/methods/get-interface-state.ts": true, - "external:../snaps-sdk/src/types/methods/get-locale.ts": true, - "external:../snaps-sdk/src/types/methods/get-snaps.ts": true, - "external:../snaps-sdk/src/types/methods/invoke-keyring.ts": true, - "external:../snaps-sdk/src/types/methods/invoke-snap.ts": true, - "external:../snaps-sdk/src/types/methods/manage-accounts.ts": true, - "external:../snaps-sdk/src/types/methods/manage-state.ts": true, - "external:../snaps-sdk/src/types/methods/methods.ts": true, - "external:../snaps-sdk/src/types/methods/notify.ts": true, - "external:../snaps-sdk/src/types/methods/request-snaps.ts": true, - "external:../snaps-sdk/src/types/methods/update-interface.ts": true - } - }, - "external:../snaps-sdk/src/ui/builder.ts": { - "packages": { - "@metamask/utils": true - } - }, - "external:../snaps-sdk/src/ui/component.ts": { - "packages": { - "@metamask/utils": true, - "external:../snaps-sdk/src/ui/components/index.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/address.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/button.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/copyable.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/divider.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/form.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/components/button.ts": true, - "external:../snaps-sdk/src/ui/components/input.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/heading.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/image.ts": { - "packages": { - "@metamask/snaps-sdk>is-svg": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/index.ts": { - "packages": { - "external:../snaps-sdk/src/ui/components/address.ts": true, - "external:../snaps-sdk/src/ui/components/button.ts": true, - "external:../snaps-sdk/src/ui/components/copyable.ts": true, - "external:../snaps-sdk/src/ui/components/divider.ts": true, - "external:../snaps-sdk/src/ui/components/form.ts": true, - "external:../snaps-sdk/src/ui/components/heading.ts": true, - "external:../snaps-sdk/src/ui/components/image.ts": true, - "external:../snaps-sdk/src/ui/components/input.ts": true, - "external:../snaps-sdk/src/ui/components/panel.ts": true, - "external:../snaps-sdk/src/ui/components/row.ts": true, - "external:../snaps-sdk/src/ui/components/spinner.ts": true, - "external:../snaps-sdk/src/ui/components/text.ts": true - } - }, - "external:../snaps-sdk/src/ui/components/input.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true + "depcheck>semver>lru-cache": true } }, - "external:../snaps-sdk/src/ui/components/panel.ts": { + "depcheck>semver>lru-cache": { "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/components/address.ts": true, - "external:../snaps-sdk/src/ui/components/button.ts": true, - "external:../snaps-sdk/src/ui/components/copyable.ts": true, - "external:../snaps-sdk/src/ui/components/divider.ts": true, - "external:../snaps-sdk/src/ui/components/form.ts": true, - "external:../snaps-sdk/src/ui/components/heading.ts": true, - "external:../snaps-sdk/src/ui/components/image.ts": true, - "external:../snaps-sdk/src/ui/components/input.ts": true, - "external:../snaps-sdk/src/ui/components/row.ts": true, - "external:../snaps-sdk/src/ui/components/spinner.ts": true, - "external:../snaps-sdk/src/ui/components/text.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/row.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/components/address.ts": true, - "external:../snaps-sdk/src/ui/components/image.ts": true, - "external:../snaps-sdk/src/ui/components/text.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/spinner.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/text.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/index.ts": { - "packages": { - "external:../snaps-sdk/src/ui/component.ts": true, - "external:../snaps-sdk/src/ui/components/index.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true - } - }, - "external:../snaps-sdk/src/ui/nodes.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-utils/src/errors.ts": { - "packages": { - "@metamask/rpc-errors": true, - "@metamask/utils": true, - "external:../snaps-sdk/src/index.ts": true - } - }, - "external:../snaps-utils/src/handlers.ts": { - "packages": { - "external:../snaps-sdk/src/index.ts": true, - "external:../snaps-utils/src/handler-types.ts": true, - "superstruct": true - } - }, - "external:../snaps-utils/src/icon.ts": { - "builtin": { - "buffer": true - } - }, - "external:../snaps-utils/src/iframe.ts": { - "globals": { - "document.body.appendChild": true, - "document.createElement": true - } - }, - "external:../snaps-utils/src/index.executionenv.ts": { - "packages": { - "external:../snaps-utils/src/errors.ts": true, - "external:../snaps-utils/src/handler-types.ts": true, - "external:../snaps-utils/src/handlers.ts": true, - "external:../snaps-utils/src/iframe.ts": true, - "external:../snaps-utils/src/logging.ts": true, - "external:../snaps-utils/src/namespace.ts": true, - "external:../snaps-utils/src/types.ts": true - } - }, - "external:../snaps-utils/src/logging.ts": { - "globals": { - "console.error": true, - "console.log": true, - "console.warn": true - }, - "packages": { - "@metamask/utils": true - } - }, - "external:../snaps-utils/src/namespace.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-utils/src/types.ts": { - "globals": { - "URL": true - }, - "packages": { - "@metamask/utils": true, - "superstruct": true + "depcheck>semver>lru-cache>yallist": true } }, "readable-stream": { @@ -513,6 +213,19 @@ "console.warn": true, "define": true } + }, + "tsup>debug": { + "globals": { + "console": true, + "document": true, + "localStorage": true, + "navigator": true, + "process": true + }, + "packages": { + "browserify>process": true, + "tsup>debug>ms": true + } } } } \ No newline at end of file diff --git a/packages/snaps-execution-environments/lavamoat/browserify/node-process/policy.json b/packages/snaps-execution-environments/lavamoat/browserify/node-process/policy.json index 8d25fd68a0..ae4dafe535 100644 --- a/packages/snaps-execution-environments/lavamoat/browserify/node-process/policy.json +++ b/packages/snaps-execution-environments/lavamoat/browserify/node-process/policy.json @@ -49,28 +49,24 @@ "console": true }, "packages": { - "@metamask/json-rpc-engine": true, "@metamask/object-multiplex": true, + "@metamask/providers>@metamask/json-rpc-engine": true, + "@metamask/providers>@metamask/json-rpc-middleware-stream": true, "@metamask/providers>@metamask/safe-event-emitter": true, "@metamask/providers>is-stream": true, - "@metamask/providers>json-rpc-middleware-stream": true, "@metamask/rpc-errors": true, "eslint>fast-deep-equal": true, "readable-stream": true } }, - "@metamask/providers>@metamask/safe-event-emitter": { - "builtin": { - "events.EventEmitter": true - }, - "globals": { - "setTimeout": true - }, + "@metamask/providers>@metamask/json-rpc-engine": { "packages": { - "events": true + "@metamask/providers>@metamask/safe-event-emitter": true, + "@metamask/rpc-errors": true, + "@metamask/utils": true } }, - "@metamask/providers>json-rpc-middleware-stream": { + "@metamask/providers>@metamask/json-rpc-middleware-stream": { "globals": { "console.warn": true, "setTimeout": true @@ -80,24 +76,57 @@ "readable-stream": true } }, + "@metamask/providers>@metamask/safe-event-emitter": { + "builtin": { + "events.EventEmitter": true + }, + "globals": { + "setTimeout": true + }, + "packages": { + "events": true + } + }, "@metamask/rpc-errors": { "packages": { "@metamask/rpc-errors>fast-safe-stringify": true, "@metamask/utils": true } }, - "@metamask/snaps-sdk>is-svg": { + "@metamask/snaps-sdk": { + "globals": { + "fetch": true + }, "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser": true + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk>fast-xml-parser": true, + "@metamask/utils": true, + "superstruct": true } }, - "@metamask/snaps-sdk>is-svg>fast-xml-parser": { + "@metamask/snaps-sdk>fast-xml-parser": { "globals": { "entityName": true, "val": true }, "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser>strnum": true + "@metamask/snaps-sdk>fast-xml-parser>strnum": true + } + }, + "@metamask/snaps-utils": { + "globals": { + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true + }, + "packages": { + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/utils": true, + "superstruct": true } }, "@metamask/utils": { @@ -112,10 +141,10 @@ "@metamask/utils>@noble/hashes": true, "@metamask/utils>@scure/base": true, "@metamask/utils>pony-cause": true, - "@swc/cli>semver": true, "buffer": true, - "eslint>debug": true, - "superstruct": true + "depcheck>semver": true, + "superstruct": true, + "tsup>debug": true } }, "@metamask/utils>@noble/hashes": { @@ -130,20 +159,6 @@ "TextEncoder": true } }, - "@swc/cli>semver": { - "globals": { - "console.error": true, - "process": true - }, - "packages": { - "@swc/cli>semver>lru-cache": true - } - }, - "@swc/cli>semver>lru-cache": { - "packages": { - "@swc/cli>semver>lru-cache>yallist": true - } - }, "@wdio/mocha-framework>mocha>supports-color": { "builtin": { "os.release": true, @@ -180,341 +195,18 @@ "browserify>browser-pack>safe-buffer": true } }, - "eslint>debug": { - "builtin": { - "tty.isatty": true, - "util.deprecate": true, - "util.format": true, - "util.inspect": true - }, - "globals": { - "console": true, - "document": true, - "localStorage": true, - "navigator": true, - "process": true - }, - "packages": { - "@wdio/mocha-framework>mocha>supports-color": true, - "eslint>debug>ms": true, - "tty": true, - "util": true - } - }, - "external:../snaps-sdk/src/error-wrappers.ts": { - "packages": { - "@metamask/rpc-errors": true, - "external:../snaps-sdk/src/internals/index.ts": true - } - }, - "external:../snaps-sdk/src/errors.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true - } - }, - "external:../snaps-sdk/src/images.ts": { - "globals": { - "fetch": true - }, - "packages": { - "@metamask/utils": true, - "external:../snaps-sdk/src/ui/index.ts": true - } - }, - "external:../snaps-sdk/src/index.ts": { - "packages": { - "@metamask/utils": true, - "external:../snaps-sdk/src/error-wrappers.ts": true, - "external:../snaps-sdk/src/errors.ts": true, - "external:../snaps-sdk/src/images.ts": true, - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/types/index.ts": true, - "external:../snaps-sdk/src/ui/index.ts": true - } - }, - "external:../snaps-sdk/src/internals/error-wrappers.ts": { - "packages": { - "external:../snaps-sdk/src/errors.ts": true - } - }, - "external:../snaps-sdk/src/internals/errors.ts": { - "packages": { - "@metamask/utils": true - } - }, - "external:../snaps-sdk/src/internals/index.ts": { - "packages": { - "external:../snaps-sdk/src/internals/error-wrappers.ts": true, - "external:../snaps-sdk/src/internals/errors.ts": true, - "external:../snaps-sdk/src/internals/helpers.ts": true, - "external:../snaps-sdk/src/internals/structs.ts": true - } - }, - "external:../snaps-sdk/src/internals/structs.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-sdk/src/types/handlers/index.ts": { - "packages": { - "external:../snaps-sdk/src/types/handlers/cronjob.ts": true, - "external:../snaps-sdk/src/types/handlers/home-page.ts": true, - "external:../snaps-sdk/src/types/handlers/keyring.ts": true, - "external:../snaps-sdk/src/types/handlers/lifecycle.ts": true, - "external:../snaps-sdk/src/types/handlers/name-lookup.ts": true, - "external:../snaps-sdk/src/types/handlers/rpc-request.ts": true, - "external:../snaps-sdk/src/types/handlers/signature.ts": true, - "external:../snaps-sdk/src/types/handlers/transaction.ts": true, - "external:../snaps-sdk/src/types/handlers/user-input.ts": true - } - }, - "external:../snaps-sdk/src/types/handlers/user-input.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-sdk/src/types/index.ts": { - "packages": { - "external:../snaps-sdk/src/types/caip.ts": true, - "external:../snaps-sdk/src/types/global.ts": true, - "external:../snaps-sdk/src/types/handlers/index.ts": true, - "external:../snaps-sdk/src/types/interface.ts": true, - "external:../snaps-sdk/src/types/methods/index.ts": true, - "external:../snaps-sdk/src/types/permissions.ts": true, - "external:../snaps-sdk/src/types/provider.ts": true, - "external:../snaps-sdk/src/types/snap.ts": true - } - }, - "external:../snaps-sdk/src/types/interface.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-sdk/src/types/methods/index.ts": { - "packages": { - "external:../snaps-sdk/src/types/methods/create-interface.ts": true, - "external:../snaps-sdk/src/types/methods/dialog.ts": true, - "external:../snaps-sdk/src/types/methods/get-bip32-entropy.ts": true, - "external:../snaps-sdk/src/types/methods/get-bip32-public-key.ts": true, - "external:../snaps-sdk/src/types/methods/get-bip44-entropy.ts": true, - "external:../snaps-sdk/src/types/methods/get-client-status.ts": true, - "external:../snaps-sdk/src/types/methods/get-entropy.ts": true, - "external:../snaps-sdk/src/types/methods/get-file.ts": true, - "external:../snaps-sdk/src/types/methods/get-interface-state.ts": true, - "external:../snaps-sdk/src/types/methods/get-locale.ts": true, - "external:../snaps-sdk/src/types/methods/get-snaps.ts": true, - "external:../snaps-sdk/src/types/methods/invoke-keyring.ts": true, - "external:../snaps-sdk/src/types/methods/invoke-snap.ts": true, - "external:../snaps-sdk/src/types/methods/manage-accounts.ts": true, - "external:../snaps-sdk/src/types/methods/manage-state.ts": true, - "external:../snaps-sdk/src/types/methods/methods.ts": true, - "external:../snaps-sdk/src/types/methods/notify.ts": true, - "external:../snaps-sdk/src/types/methods/request-snaps.ts": true, - "external:../snaps-sdk/src/types/methods/update-interface.ts": true - } - }, - "external:../snaps-sdk/src/ui/builder.ts": { - "packages": { - "@metamask/utils": true - } - }, - "external:../snaps-sdk/src/ui/component.ts": { - "packages": { - "@metamask/utils": true, - "external:../snaps-sdk/src/ui/components/index.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/address.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/button.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/copyable.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/divider.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/form.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/components/button.ts": true, - "external:../snaps-sdk/src/ui/components/input.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/heading.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/image.ts": { - "packages": { - "@metamask/snaps-sdk>is-svg": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/index.ts": { - "packages": { - "external:../snaps-sdk/src/ui/components/address.ts": true, - "external:../snaps-sdk/src/ui/components/button.ts": true, - "external:../snaps-sdk/src/ui/components/copyable.ts": true, - "external:../snaps-sdk/src/ui/components/divider.ts": true, - "external:../snaps-sdk/src/ui/components/form.ts": true, - "external:../snaps-sdk/src/ui/components/heading.ts": true, - "external:../snaps-sdk/src/ui/components/image.ts": true, - "external:../snaps-sdk/src/ui/components/input.ts": true, - "external:../snaps-sdk/src/ui/components/panel.ts": true, - "external:../snaps-sdk/src/ui/components/row.ts": true, - "external:../snaps-sdk/src/ui/components/spinner.ts": true, - "external:../snaps-sdk/src/ui/components/text.ts": true - } - }, - "external:../snaps-sdk/src/ui/components/input.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/panel.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/components/address.ts": true, - "external:../snaps-sdk/src/ui/components/button.ts": true, - "external:../snaps-sdk/src/ui/components/copyable.ts": true, - "external:../snaps-sdk/src/ui/components/divider.ts": true, - "external:../snaps-sdk/src/ui/components/form.ts": true, - "external:../snaps-sdk/src/ui/components/heading.ts": true, - "external:../snaps-sdk/src/ui/components/image.ts": true, - "external:../snaps-sdk/src/ui/components/input.ts": true, - "external:../snaps-sdk/src/ui/components/row.ts": true, - "external:../snaps-sdk/src/ui/components/spinner.ts": true, - "external:../snaps-sdk/src/ui/components/text.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/row.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/components/address.ts": true, - "external:../snaps-sdk/src/ui/components/image.ts": true, - "external:../snaps-sdk/src/ui/components/text.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/spinner.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/text.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/index.ts": { - "packages": { - "external:../snaps-sdk/src/ui/component.ts": true, - "external:../snaps-sdk/src/ui/components/index.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true - } - }, - "external:../snaps-sdk/src/ui/nodes.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-utils/src/errors.ts": { - "packages": { - "@metamask/rpc-errors": true, - "@metamask/utils": true, - "external:../snaps-sdk/src/index.ts": true - } - }, - "external:../snaps-utils/src/handlers.ts": { - "packages": { - "external:../snaps-sdk/src/index.ts": true, - "external:../snaps-utils/src/handler-types.ts": true, - "superstruct": true - } - }, - "external:../snaps-utils/src/icon.ts": { - "builtin": { - "buffer": true - } - }, - "external:../snaps-utils/src/iframe.ts": { - "globals": { - "document.body.appendChild": true, - "document.createElement": true - } - }, - "external:../snaps-utils/src/index.executionenv.ts": { - "packages": { - "external:../snaps-utils/src/errors.ts": true, - "external:../snaps-utils/src/handler-types.ts": true, - "external:../snaps-utils/src/handlers.ts": true, - "external:../snaps-utils/src/iframe.ts": true, - "external:../snaps-utils/src/logging.ts": true, - "external:../snaps-utils/src/namespace.ts": true, - "external:../snaps-utils/src/types.ts": true - } - }, - "external:../snaps-utils/src/logging.ts": { + "depcheck>semver": { "globals": { "console.error": true, - "console.log": true, - "console.warn": true + "process": true }, "packages": { - "@metamask/utils": true + "depcheck>semver>lru-cache": true } }, - "external:../snaps-utils/src/namespace.ts": { + "depcheck>semver>lru-cache": { "packages": { - "superstruct": true - } - }, - "external:../snaps-utils/src/types.ts": { - "globals": { - "URL": true - }, - "packages": { - "@metamask/utils": true, - "superstruct": true + "depcheck>semver>lru-cache>yallist": true } }, "istanbul-lib-report>supports-color>has-flag": { @@ -558,6 +250,27 @@ "console.warn": true, "define": true } + }, + "tsup>debug": { + "builtin": { + "tty.isatty": true, + "util.deprecate": true, + "util.format": true, + "util.inspect": true + }, + "globals": { + "console": true, + "document": true, + "localStorage": true, + "navigator": true, + "process": true + }, + "packages": { + "@wdio/mocha-framework>mocha>supports-color": true, + "tsup>debug>ms": true, + "tty": true, + "util": true + } } } } \ No newline at end of file diff --git a/packages/snaps-execution-environments/lavamoat/browserify/node-thread/policy.json b/packages/snaps-execution-environments/lavamoat/browserify/node-thread/policy.json index 8d25fd68a0..ae4dafe535 100644 --- a/packages/snaps-execution-environments/lavamoat/browserify/node-thread/policy.json +++ b/packages/snaps-execution-environments/lavamoat/browserify/node-thread/policy.json @@ -49,28 +49,24 @@ "console": true }, "packages": { - "@metamask/json-rpc-engine": true, "@metamask/object-multiplex": true, + "@metamask/providers>@metamask/json-rpc-engine": true, + "@metamask/providers>@metamask/json-rpc-middleware-stream": true, "@metamask/providers>@metamask/safe-event-emitter": true, "@metamask/providers>is-stream": true, - "@metamask/providers>json-rpc-middleware-stream": true, "@metamask/rpc-errors": true, "eslint>fast-deep-equal": true, "readable-stream": true } }, - "@metamask/providers>@metamask/safe-event-emitter": { - "builtin": { - "events.EventEmitter": true - }, - "globals": { - "setTimeout": true - }, + "@metamask/providers>@metamask/json-rpc-engine": { "packages": { - "events": true + "@metamask/providers>@metamask/safe-event-emitter": true, + "@metamask/rpc-errors": true, + "@metamask/utils": true } }, - "@metamask/providers>json-rpc-middleware-stream": { + "@metamask/providers>@metamask/json-rpc-middleware-stream": { "globals": { "console.warn": true, "setTimeout": true @@ -80,24 +76,57 @@ "readable-stream": true } }, + "@metamask/providers>@metamask/safe-event-emitter": { + "builtin": { + "events.EventEmitter": true + }, + "globals": { + "setTimeout": true + }, + "packages": { + "events": true + } + }, "@metamask/rpc-errors": { "packages": { "@metamask/rpc-errors>fast-safe-stringify": true, "@metamask/utils": true } }, - "@metamask/snaps-sdk>is-svg": { + "@metamask/snaps-sdk": { + "globals": { + "fetch": true + }, "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser": true + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk>fast-xml-parser": true, + "@metamask/utils": true, + "superstruct": true } }, - "@metamask/snaps-sdk>is-svg>fast-xml-parser": { + "@metamask/snaps-sdk>fast-xml-parser": { "globals": { "entityName": true, "val": true }, "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser>strnum": true + "@metamask/snaps-sdk>fast-xml-parser>strnum": true + } + }, + "@metamask/snaps-utils": { + "globals": { + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true + }, + "packages": { + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/utils": true, + "superstruct": true } }, "@metamask/utils": { @@ -112,10 +141,10 @@ "@metamask/utils>@noble/hashes": true, "@metamask/utils>@scure/base": true, "@metamask/utils>pony-cause": true, - "@swc/cli>semver": true, "buffer": true, - "eslint>debug": true, - "superstruct": true + "depcheck>semver": true, + "superstruct": true, + "tsup>debug": true } }, "@metamask/utils>@noble/hashes": { @@ -130,20 +159,6 @@ "TextEncoder": true } }, - "@swc/cli>semver": { - "globals": { - "console.error": true, - "process": true - }, - "packages": { - "@swc/cli>semver>lru-cache": true - } - }, - "@swc/cli>semver>lru-cache": { - "packages": { - "@swc/cli>semver>lru-cache>yallist": true - } - }, "@wdio/mocha-framework>mocha>supports-color": { "builtin": { "os.release": true, @@ -180,341 +195,18 @@ "browserify>browser-pack>safe-buffer": true } }, - "eslint>debug": { - "builtin": { - "tty.isatty": true, - "util.deprecate": true, - "util.format": true, - "util.inspect": true - }, - "globals": { - "console": true, - "document": true, - "localStorage": true, - "navigator": true, - "process": true - }, - "packages": { - "@wdio/mocha-framework>mocha>supports-color": true, - "eslint>debug>ms": true, - "tty": true, - "util": true - } - }, - "external:../snaps-sdk/src/error-wrappers.ts": { - "packages": { - "@metamask/rpc-errors": true, - "external:../snaps-sdk/src/internals/index.ts": true - } - }, - "external:../snaps-sdk/src/errors.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true - } - }, - "external:../snaps-sdk/src/images.ts": { - "globals": { - "fetch": true - }, - "packages": { - "@metamask/utils": true, - "external:../snaps-sdk/src/ui/index.ts": true - } - }, - "external:../snaps-sdk/src/index.ts": { - "packages": { - "@metamask/utils": true, - "external:../snaps-sdk/src/error-wrappers.ts": true, - "external:../snaps-sdk/src/errors.ts": true, - "external:../snaps-sdk/src/images.ts": true, - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/types/index.ts": true, - "external:../snaps-sdk/src/ui/index.ts": true - } - }, - "external:../snaps-sdk/src/internals/error-wrappers.ts": { - "packages": { - "external:../snaps-sdk/src/errors.ts": true - } - }, - "external:../snaps-sdk/src/internals/errors.ts": { - "packages": { - "@metamask/utils": true - } - }, - "external:../snaps-sdk/src/internals/index.ts": { - "packages": { - "external:../snaps-sdk/src/internals/error-wrappers.ts": true, - "external:../snaps-sdk/src/internals/errors.ts": true, - "external:../snaps-sdk/src/internals/helpers.ts": true, - "external:../snaps-sdk/src/internals/structs.ts": true - } - }, - "external:../snaps-sdk/src/internals/structs.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-sdk/src/types/handlers/index.ts": { - "packages": { - "external:../snaps-sdk/src/types/handlers/cronjob.ts": true, - "external:../snaps-sdk/src/types/handlers/home-page.ts": true, - "external:../snaps-sdk/src/types/handlers/keyring.ts": true, - "external:../snaps-sdk/src/types/handlers/lifecycle.ts": true, - "external:../snaps-sdk/src/types/handlers/name-lookup.ts": true, - "external:../snaps-sdk/src/types/handlers/rpc-request.ts": true, - "external:../snaps-sdk/src/types/handlers/signature.ts": true, - "external:../snaps-sdk/src/types/handlers/transaction.ts": true, - "external:../snaps-sdk/src/types/handlers/user-input.ts": true - } - }, - "external:../snaps-sdk/src/types/handlers/user-input.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-sdk/src/types/index.ts": { - "packages": { - "external:../snaps-sdk/src/types/caip.ts": true, - "external:../snaps-sdk/src/types/global.ts": true, - "external:../snaps-sdk/src/types/handlers/index.ts": true, - "external:../snaps-sdk/src/types/interface.ts": true, - "external:../snaps-sdk/src/types/methods/index.ts": true, - "external:../snaps-sdk/src/types/permissions.ts": true, - "external:../snaps-sdk/src/types/provider.ts": true, - "external:../snaps-sdk/src/types/snap.ts": true - } - }, - "external:../snaps-sdk/src/types/interface.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-sdk/src/types/methods/index.ts": { - "packages": { - "external:../snaps-sdk/src/types/methods/create-interface.ts": true, - "external:../snaps-sdk/src/types/methods/dialog.ts": true, - "external:../snaps-sdk/src/types/methods/get-bip32-entropy.ts": true, - "external:../snaps-sdk/src/types/methods/get-bip32-public-key.ts": true, - "external:../snaps-sdk/src/types/methods/get-bip44-entropy.ts": true, - "external:../snaps-sdk/src/types/methods/get-client-status.ts": true, - "external:../snaps-sdk/src/types/methods/get-entropy.ts": true, - "external:../snaps-sdk/src/types/methods/get-file.ts": true, - "external:../snaps-sdk/src/types/methods/get-interface-state.ts": true, - "external:../snaps-sdk/src/types/methods/get-locale.ts": true, - "external:../snaps-sdk/src/types/methods/get-snaps.ts": true, - "external:../snaps-sdk/src/types/methods/invoke-keyring.ts": true, - "external:../snaps-sdk/src/types/methods/invoke-snap.ts": true, - "external:../snaps-sdk/src/types/methods/manage-accounts.ts": true, - "external:../snaps-sdk/src/types/methods/manage-state.ts": true, - "external:../snaps-sdk/src/types/methods/methods.ts": true, - "external:../snaps-sdk/src/types/methods/notify.ts": true, - "external:../snaps-sdk/src/types/methods/request-snaps.ts": true, - "external:../snaps-sdk/src/types/methods/update-interface.ts": true - } - }, - "external:../snaps-sdk/src/ui/builder.ts": { - "packages": { - "@metamask/utils": true - } - }, - "external:../snaps-sdk/src/ui/component.ts": { - "packages": { - "@metamask/utils": true, - "external:../snaps-sdk/src/ui/components/index.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/address.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/button.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/copyable.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/divider.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/form.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/components/button.ts": true, - "external:../snaps-sdk/src/ui/components/input.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/heading.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/image.ts": { - "packages": { - "@metamask/snaps-sdk>is-svg": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/index.ts": { - "packages": { - "external:../snaps-sdk/src/ui/components/address.ts": true, - "external:../snaps-sdk/src/ui/components/button.ts": true, - "external:../snaps-sdk/src/ui/components/copyable.ts": true, - "external:../snaps-sdk/src/ui/components/divider.ts": true, - "external:../snaps-sdk/src/ui/components/form.ts": true, - "external:../snaps-sdk/src/ui/components/heading.ts": true, - "external:../snaps-sdk/src/ui/components/image.ts": true, - "external:../snaps-sdk/src/ui/components/input.ts": true, - "external:../snaps-sdk/src/ui/components/panel.ts": true, - "external:../snaps-sdk/src/ui/components/row.ts": true, - "external:../snaps-sdk/src/ui/components/spinner.ts": true, - "external:../snaps-sdk/src/ui/components/text.ts": true - } - }, - "external:../snaps-sdk/src/ui/components/input.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/panel.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/components/address.ts": true, - "external:../snaps-sdk/src/ui/components/button.ts": true, - "external:../snaps-sdk/src/ui/components/copyable.ts": true, - "external:../snaps-sdk/src/ui/components/divider.ts": true, - "external:../snaps-sdk/src/ui/components/form.ts": true, - "external:../snaps-sdk/src/ui/components/heading.ts": true, - "external:../snaps-sdk/src/ui/components/image.ts": true, - "external:../snaps-sdk/src/ui/components/input.ts": true, - "external:../snaps-sdk/src/ui/components/row.ts": true, - "external:../snaps-sdk/src/ui/components/spinner.ts": true, - "external:../snaps-sdk/src/ui/components/text.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/row.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/components/address.ts": true, - "external:../snaps-sdk/src/ui/components/image.ts": true, - "external:../snaps-sdk/src/ui/components/text.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/spinner.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/text.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/index.ts": { - "packages": { - "external:../snaps-sdk/src/ui/component.ts": true, - "external:../snaps-sdk/src/ui/components/index.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true - } - }, - "external:../snaps-sdk/src/ui/nodes.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-utils/src/errors.ts": { - "packages": { - "@metamask/rpc-errors": true, - "@metamask/utils": true, - "external:../snaps-sdk/src/index.ts": true - } - }, - "external:../snaps-utils/src/handlers.ts": { - "packages": { - "external:../snaps-sdk/src/index.ts": true, - "external:../snaps-utils/src/handler-types.ts": true, - "superstruct": true - } - }, - "external:../snaps-utils/src/icon.ts": { - "builtin": { - "buffer": true - } - }, - "external:../snaps-utils/src/iframe.ts": { - "globals": { - "document.body.appendChild": true, - "document.createElement": true - } - }, - "external:../snaps-utils/src/index.executionenv.ts": { - "packages": { - "external:../snaps-utils/src/errors.ts": true, - "external:../snaps-utils/src/handler-types.ts": true, - "external:../snaps-utils/src/handlers.ts": true, - "external:../snaps-utils/src/iframe.ts": true, - "external:../snaps-utils/src/logging.ts": true, - "external:../snaps-utils/src/namespace.ts": true, - "external:../snaps-utils/src/types.ts": true - } - }, - "external:../snaps-utils/src/logging.ts": { + "depcheck>semver": { "globals": { "console.error": true, - "console.log": true, - "console.warn": true + "process": true }, "packages": { - "@metamask/utils": true + "depcheck>semver>lru-cache": true } }, - "external:../snaps-utils/src/namespace.ts": { + "depcheck>semver>lru-cache": { "packages": { - "superstruct": true - } - }, - "external:../snaps-utils/src/types.ts": { - "globals": { - "URL": true - }, - "packages": { - "@metamask/utils": true, - "superstruct": true + "depcheck>semver>lru-cache>yallist": true } }, "istanbul-lib-report>supports-color>has-flag": { @@ -558,6 +250,27 @@ "console.warn": true, "define": true } + }, + "tsup>debug": { + "builtin": { + "tty.isatty": true, + "util.deprecate": true, + "util.format": true, + "util.inspect": true + }, + "globals": { + "console": true, + "document": true, + "localStorage": true, + "navigator": true, + "process": true + }, + "packages": { + "@wdio/mocha-framework>mocha>supports-color": true, + "tsup>debug>ms": true, + "tty": true, + "util": true + } } } } \ No newline at end of file diff --git a/packages/snaps-execution-environments/lavamoat/browserify/policy-override.json b/packages/snaps-execution-environments/lavamoat/browserify/policy-override.json index 3f789c1b58..d6eebc58e8 100644 --- a/packages/snaps-execution-environments/lavamoat/browserify/policy-override.json +++ b/packages/snaps-execution-environments/lavamoat/browserify/policy-override.json @@ -1,9 +1,3 @@ { - "resources": { - "external:../snaps-utils/src/icon.ts": { - "builtin": { - "buffer": true - } - } - } + "resources": {} } diff --git a/packages/snaps-execution-environments/lavamoat/browserify/webview/policy.json b/packages/snaps-execution-environments/lavamoat/browserify/webview/policy.json index dd32fb1779..63e69a30b9 100644 --- a/packages/snaps-execution-environments/lavamoat/browserify/webview/policy.json +++ b/packages/snaps-execution-environments/lavamoat/browserify/webview/policy.json @@ -22,18 +22,40 @@ "@metamask/utils": true } }, - "@metamask/snaps-sdk>is-svg": { + "@metamask/snaps-sdk": { + "globals": { + "fetch": true + }, "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser": true + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk>fast-xml-parser": true, + "@metamask/utils": true, + "superstruct": true } }, - "@metamask/snaps-sdk>is-svg>fast-xml-parser": { + "@metamask/snaps-sdk>fast-xml-parser": { "globals": { "entityName": true, "val": true }, "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser>strnum": true + "@metamask/snaps-sdk>fast-xml-parser>strnum": true + } + }, + "@metamask/snaps-utils": { + "globals": { + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true + }, + "packages": { + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/utils": true, + "superstruct": true } }, "@metamask/utils": { @@ -45,10 +67,10 @@ "@metamask/utils>@noble/hashes": true, "@metamask/utils>@scure/base": true, "@metamask/utils>pony-cause": true, - "@swc/cli>semver": true, "browserify>buffer": true, - "eslint>debug": true, - "superstruct": true + "depcheck>semver": true, + "superstruct": true, + "tsup>debug": true } }, "@metamask/utils>@noble/hashes": { @@ -63,20 +85,6 @@ "TextEncoder": true } }, - "@swc/cli>semver": { - "globals": { - "console.error": true - }, - "packages": { - "@swc/cli>semver>lru-cache": true, - "browserify>process": true - } - }, - "@swc/cli>semver>lru-cache": { - "packages": { - "@swc/cli>semver>lru-cache>yallist": true - } - }, "browserify>browser-pack>safe-buffer": { "packages": { "browserify>buffer": true @@ -107,333 +115,18 @@ "browserify>browser-pack>safe-buffer": true } }, - "eslint>debug": { + "depcheck>semver": { "globals": { - "console": true, - "document": true, - "localStorage": true, - "navigator": true, - "process": true + "console.error": true }, "packages": { "browserify>process": true, - "eslint>debug>ms": true + "depcheck>semver>lru-cache": true } }, - "external:../snaps-sdk/src/error-wrappers.ts": { + "depcheck>semver>lru-cache": { "packages": { - "@metamask/rpc-errors": true, - "external:../snaps-sdk/src/internals/index.ts": true - } - }, - "external:../snaps-sdk/src/errors.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true - } - }, - "external:../snaps-sdk/src/images.ts": { - "globals": { - "fetch": true - }, - "packages": { - "@metamask/utils": true, - "external:../snaps-sdk/src/ui/index.ts": true - } - }, - "external:../snaps-sdk/src/index.ts": { - "packages": { - "@metamask/utils": true, - "external:../snaps-sdk/src/error-wrappers.ts": true, - "external:../snaps-sdk/src/errors.ts": true, - "external:../snaps-sdk/src/images.ts": true, - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/types/index.ts": true, - "external:../snaps-sdk/src/ui/index.ts": true - } - }, - "external:../snaps-sdk/src/internals/error-wrappers.ts": { - "packages": { - "external:../snaps-sdk/src/errors.ts": true - } - }, - "external:../snaps-sdk/src/internals/errors.ts": { - "packages": { - "@metamask/utils": true - } - }, - "external:../snaps-sdk/src/internals/index.ts": { - "packages": { - "external:../snaps-sdk/src/internals/error-wrappers.ts": true, - "external:../snaps-sdk/src/internals/errors.ts": true, - "external:../snaps-sdk/src/internals/helpers.ts": true, - "external:../snaps-sdk/src/internals/structs.ts": true - } - }, - "external:../snaps-sdk/src/internals/structs.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-sdk/src/types/handlers/index.ts": { - "packages": { - "external:../snaps-sdk/src/types/handlers/cronjob.ts": true, - "external:../snaps-sdk/src/types/handlers/home-page.ts": true, - "external:../snaps-sdk/src/types/handlers/keyring.ts": true, - "external:../snaps-sdk/src/types/handlers/lifecycle.ts": true, - "external:../snaps-sdk/src/types/handlers/name-lookup.ts": true, - "external:../snaps-sdk/src/types/handlers/rpc-request.ts": true, - "external:../snaps-sdk/src/types/handlers/signature.ts": true, - "external:../snaps-sdk/src/types/handlers/transaction.ts": true, - "external:../snaps-sdk/src/types/handlers/user-input.ts": true - } - }, - "external:../snaps-sdk/src/types/handlers/user-input.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-sdk/src/types/index.ts": { - "packages": { - "external:../snaps-sdk/src/types/caip.ts": true, - "external:../snaps-sdk/src/types/global.ts": true, - "external:../snaps-sdk/src/types/handlers/index.ts": true, - "external:../snaps-sdk/src/types/interface.ts": true, - "external:../snaps-sdk/src/types/methods/index.ts": true, - "external:../snaps-sdk/src/types/permissions.ts": true, - "external:../snaps-sdk/src/types/provider.ts": true, - "external:../snaps-sdk/src/types/snap.ts": true - } - }, - "external:../snaps-sdk/src/types/interface.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-sdk/src/types/methods/index.ts": { - "packages": { - "external:../snaps-sdk/src/types/methods/create-interface.ts": true, - "external:../snaps-sdk/src/types/methods/dialog.ts": true, - "external:../snaps-sdk/src/types/methods/get-bip32-entropy.ts": true, - "external:../snaps-sdk/src/types/methods/get-bip32-public-key.ts": true, - "external:../snaps-sdk/src/types/methods/get-bip44-entropy.ts": true, - "external:../snaps-sdk/src/types/methods/get-client-status.ts": true, - "external:../snaps-sdk/src/types/methods/get-entropy.ts": true, - "external:../snaps-sdk/src/types/methods/get-file.ts": true, - "external:../snaps-sdk/src/types/methods/get-interface-state.ts": true, - "external:../snaps-sdk/src/types/methods/get-locale.ts": true, - "external:../snaps-sdk/src/types/methods/get-snaps.ts": true, - "external:../snaps-sdk/src/types/methods/invoke-keyring.ts": true, - "external:../snaps-sdk/src/types/methods/invoke-snap.ts": true, - "external:../snaps-sdk/src/types/methods/manage-accounts.ts": true, - "external:../snaps-sdk/src/types/methods/manage-state.ts": true, - "external:../snaps-sdk/src/types/methods/methods.ts": true, - "external:../snaps-sdk/src/types/methods/notify.ts": true, - "external:../snaps-sdk/src/types/methods/request-snaps.ts": true, - "external:../snaps-sdk/src/types/methods/update-interface.ts": true - } - }, - "external:../snaps-sdk/src/ui/builder.ts": { - "packages": { - "@metamask/utils": true - } - }, - "external:../snaps-sdk/src/ui/component.ts": { - "packages": { - "@metamask/utils": true, - "external:../snaps-sdk/src/ui/components/index.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/address.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/button.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/copyable.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/divider.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/form.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/components/button.ts": true, - "external:../snaps-sdk/src/ui/components/input.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/heading.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/image.ts": { - "packages": { - "@metamask/snaps-sdk>is-svg": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/index.ts": { - "packages": { - "external:../snaps-sdk/src/ui/components/address.ts": true, - "external:../snaps-sdk/src/ui/components/button.ts": true, - "external:../snaps-sdk/src/ui/components/copyable.ts": true, - "external:../snaps-sdk/src/ui/components/divider.ts": true, - "external:../snaps-sdk/src/ui/components/form.ts": true, - "external:../snaps-sdk/src/ui/components/heading.ts": true, - "external:../snaps-sdk/src/ui/components/image.ts": true, - "external:../snaps-sdk/src/ui/components/input.ts": true, - "external:../snaps-sdk/src/ui/components/panel.ts": true, - "external:../snaps-sdk/src/ui/components/row.ts": true, - "external:../snaps-sdk/src/ui/components/spinner.ts": true, - "external:../snaps-sdk/src/ui/components/text.ts": true - } - }, - "external:../snaps-sdk/src/ui/components/input.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/panel.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/components/address.ts": true, - "external:../snaps-sdk/src/ui/components/button.ts": true, - "external:../snaps-sdk/src/ui/components/copyable.ts": true, - "external:../snaps-sdk/src/ui/components/divider.ts": true, - "external:../snaps-sdk/src/ui/components/form.ts": true, - "external:../snaps-sdk/src/ui/components/heading.ts": true, - "external:../snaps-sdk/src/ui/components/image.ts": true, - "external:../snaps-sdk/src/ui/components/input.ts": true, - "external:../snaps-sdk/src/ui/components/row.ts": true, - "external:../snaps-sdk/src/ui/components/spinner.ts": true, - "external:../snaps-sdk/src/ui/components/text.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/row.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/components/address.ts": true, - "external:../snaps-sdk/src/ui/components/image.ts": true, - "external:../snaps-sdk/src/ui/components/text.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/spinner.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/text.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/index.ts": { - "packages": { - "external:../snaps-sdk/src/ui/component.ts": true, - "external:../snaps-sdk/src/ui/components/index.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true - } - }, - "external:../snaps-sdk/src/ui/nodes.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-utils/src/errors.ts": { - "packages": { - "@metamask/rpc-errors": true, - "@metamask/utils": true, - "external:../snaps-sdk/src/index.ts": true - } - }, - "external:../snaps-utils/src/handlers.ts": { - "packages": { - "external:../snaps-sdk/src/index.ts": true, - "external:../snaps-utils/src/handler-types.ts": true, - "superstruct": true - } - }, - "external:../snaps-utils/src/icon.ts": { - "builtin": { - "buffer": true - } - }, - "external:../snaps-utils/src/iframe.ts": { - "globals": { - "document.body.appendChild": true, - "document.createElement": true - } - }, - "external:../snaps-utils/src/index.executionenv.ts": { - "packages": { - "external:../snaps-utils/src/errors.ts": true, - "external:../snaps-utils/src/handler-types.ts": true, - "external:../snaps-utils/src/handlers.ts": true, - "external:../snaps-utils/src/iframe.ts": true, - "external:../snaps-utils/src/logging.ts": true, - "external:../snaps-utils/src/namespace.ts": true, - "external:../snaps-utils/src/types.ts": true - } - }, - "external:../snaps-utils/src/logging.ts": { - "globals": { - "console.error": true, - "console.log": true, - "console.warn": true - }, - "packages": { - "@metamask/utils": true - } - }, - "external:../snaps-utils/src/namespace.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-utils/src/types.ts": { - "globals": { - "URL": true - }, - "packages": { - "@metamask/utils": true, - "superstruct": true + "depcheck>semver>lru-cache>yallist": true } }, "readable-stream": { @@ -459,6 +152,19 @@ "console.warn": true, "define": true } + }, + "tsup>debug": { + "globals": { + "console": true, + "document": true, + "localStorage": true, + "navigator": true, + "process": true + }, + "packages": { + "browserify>process": true, + "tsup>debug>ms": true + } } } } \ No newline at end of file diff --git a/packages/snaps-execution-environments/lavamoat/browserify/worker-executor/policy.json b/packages/snaps-execution-environments/lavamoat/browserify/worker-executor/policy.json index e3a25e30e3..0dfa39a79b 100644 --- a/packages/snaps-execution-environments/lavamoat/browserify/worker-executor/policy.json +++ b/packages/snaps-execution-environments/lavamoat/browserify/worker-executor/policy.json @@ -42,25 +42,24 @@ "console": true }, "packages": { - "@metamask/json-rpc-engine": true, "@metamask/object-multiplex": true, + "@metamask/providers>@metamask/json-rpc-engine": true, + "@metamask/providers>@metamask/json-rpc-middleware-stream": true, "@metamask/providers>@metamask/safe-event-emitter": true, "@metamask/providers>is-stream": true, - "@metamask/providers>json-rpc-middleware-stream": true, "@metamask/rpc-errors": true, "eslint>fast-deep-equal": true, "readable-stream": true } }, - "@metamask/providers>@metamask/safe-event-emitter": { - "globals": { - "setTimeout": true - }, + "@metamask/providers>@metamask/json-rpc-engine": { "packages": { - "browserify>events": true + "@metamask/providers>@metamask/safe-event-emitter": true, + "@metamask/rpc-errors": true, + "@metamask/utils": true } }, - "@metamask/providers>json-rpc-middleware-stream": { + "@metamask/providers>@metamask/json-rpc-middleware-stream": { "globals": { "console.warn": true, "setTimeout": true @@ -70,24 +69,54 @@ "readable-stream": true } }, + "@metamask/providers>@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "browserify>events": true + } + }, "@metamask/rpc-errors": { "packages": { "@metamask/rpc-errors>fast-safe-stringify": true, "@metamask/utils": true } }, - "@metamask/snaps-sdk>is-svg": { + "@metamask/snaps-sdk": { + "globals": { + "fetch": true + }, "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser": true + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk>fast-xml-parser": true, + "@metamask/utils": true, + "superstruct": true } }, - "@metamask/snaps-sdk>is-svg>fast-xml-parser": { + "@metamask/snaps-sdk>fast-xml-parser": { "globals": { "entityName": true, "val": true }, "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser>strnum": true + "@metamask/snaps-sdk>fast-xml-parser>strnum": true + } + }, + "@metamask/snaps-utils": { + "globals": { + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true + }, + "packages": { + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/utils": true, + "superstruct": true } }, "@metamask/utils": { @@ -99,10 +128,10 @@ "@metamask/utils>@noble/hashes": true, "@metamask/utils>@scure/base": true, "@metamask/utils>pony-cause": true, - "@swc/cli>semver": true, "browserify>buffer": true, - "eslint>debug": true, - "superstruct": true + "depcheck>semver": true, + "superstruct": true, + "tsup>debug": true } }, "@metamask/utils>@noble/hashes": { @@ -117,20 +146,6 @@ "TextEncoder": true } }, - "@swc/cli>semver": { - "globals": { - "console.error": true - }, - "packages": { - "@swc/cli>semver>lru-cache": true, - "browserify>process": true - } - }, - "@swc/cli>semver>lru-cache": { - "packages": { - "@swc/cli>semver>lru-cache>yallist": true - } - }, "browserify>browser-pack>safe-buffer": { "packages": { "browserify>buffer": true @@ -161,333 +176,18 @@ "browserify>browser-pack>safe-buffer": true } }, - "eslint>debug": { + "depcheck>semver": { "globals": { - "console": true, - "document": true, - "localStorage": true, - "navigator": true, - "process": true + "console.error": true }, "packages": { "browserify>process": true, - "eslint>debug>ms": true - } - }, - "external:../snaps-sdk/src/error-wrappers.ts": { - "packages": { - "@metamask/rpc-errors": true, - "external:../snaps-sdk/src/internals/index.ts": true - } - }, - "external:../snaps-sdk/src/errors.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true - } - }, - "external:../snaps-sdk/src/images.ts": { - "globals": { - "fetch": true - }, - "packages": { - "@metamask/utils": true, - "external:../snaps-sdk/src/ui/index.ts": true - } - }, - "external:../snaps-sdk/src/index.ts": { - "packages": { - "@metamask/utils": true, - "external:../snaps-sdk/src/error-wrappers.ts": true, - "external:../snaps-sdk/src/errors.ts": true, - "external:../snaps-sdk/src/images.ts": true, - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/types/index.ts": true, - "external:../snaps-sdk/src/ui/index.ts": true - } - }, - "external:../snaps-sdk/src/internals/error-wrappers.ts": { - "packages": { - "external:../snaps-sdk/src/errors.ts": true - } - }, - "external:../snaps-sdk/src/internals/errors.ts": { - "packages": { - "@metamask/utils": true - } - }, - "external:../snaps-sdk/src/internals/index.ts": { - "packages": { - "external:../snaps-sdk/src/internals/error-wrappers.ts": true, - "external:../snaps-sdk/src/internals/errors.ts": true, - "external:../snaps-sdk/src/internals/helpers.ts": true, - "external:../snaps-sdk/src/internals/structs.ts": true - } - }, - "external:../snaps-sdk/src/internals/structs.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-sdk/src/types/handlers/index.ts": { - "packages": { - "external:../snaps-sdk/src/types/handlers/cronjob.ts": true, - "external:../snaps-sdk/src/types/handlers/home-page.ts": true, - "external:../snaps-sdk/src/types/handlers/keyring.ts": true, - "external:../snaps-sdk/src/types/handlers/lifecycle.ts": true, - "external:../snaps-sdk/src/types/handlers/name-lookup.ts": true, - "external:../snaps-sdk/src/types/handlers/rpc-request.ts": true, - "external:../snaps-sdk/src/types/handlers/signature.ts": true, - "external:../snaps-sdk/src/types/handlers/transaction.ts": true, - "external:../snaps-sdk/src/types/handlers/user-input.ts": true - } - }, - "external:../snaps-sdk/src/types/handlers/user-input.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-sdk/src/types/index.ts": { - "packages": { - "external:../snaps-sdk/src/types/caip.ts": true, - "external:../snaps-sdk/src/types/global.ts": true, - "external:../snaps-sdk/src/types/handlers/index.ts": true, - "external:../snaps-sdk/src/types/interface.ts": true, - "external:../snaps-sdk/src/types/methods/index.ts": true, - "external:../snaps-sdk/src/types/permissions.ts": true, - "external:../snaps-sdk/src/types/provider.ts": true, - "external:../snaps-sdk/src/types/snap.ts": true - } - }, - "external:../snaps-sdk/src/types/interface.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-sdk/src/types/methods/index.ts": { - "packages": { - "external:../snaps-sdk/src/types/methods/create-interface.ts": true, - "external:../snaps-sdk/src/types/methods/dialog.ts": true, - "external:../snaps-sdk/src/types/methods/get-bip32-entropy.ts": true, - "external:../snaps-sdk/src/types/methods/get-bip32-public-key.ts": true, - "external:../snaps-sdk/src/types/methods/get-bip44-entropy.ts": true, - "external:../snaps-sdk/src/types/methods/get-client-status.ts": true, - "external:../snaps-sdk/src/types/methods/get-entropy.ts": true, - "external:../snaps-sdk/src/types/methods/get-file.ts": true, - "external:../snaps-sdk/src/types/methods/get-interface-state.ts": true, - "external:../snaps-sdk/src/types/methods/get-locale.ts": true, - "external:../snaps-sdk/src/types/methods/get-snaps.ts": true, - "external:../snaps-sdk/src/types/methods/invoke-keyring.ts": true, - "external:../snaps-sdk/src/types/methods/invoke-snap.ts": true, - "external:../snaps-sdk/src/types/methods/manage-accounts.ts": true, - "external:../snaps-sdk/src/types/methods/manage-state.ts": true, - "external:../snaps-sdk/src/types/methods/methods.ts": true, - "external:../snaps-sdk/src/types/methods/notify.ts": true, - "external:../snaps-sdk/src/types/methods/request-snaps.ts": true, - "external:../snaps-sdk/src/types/methods/update-interface.ts": true - } - }, - "external:../snaps-sdk/src/ui/builder.ts": { - "packages": { - "@metamask/utils": true - } - }, - "external:../snaps-sdk/src/ui/component.ts": { - "packages": { - "@metamask/utils": true, - "external:../snaps-sdk/src/ui/components/index.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/address.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/button.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/copyable.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/divider.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/form.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/components/button.ts": true, - "external:../snaps-sdk/src/ui/components/input.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/heading.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/image.ts": { - "packages": { - "@metamask/snaps-sdk>is-svg": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/index.ts": { - "packages": { - "external:../snaps-sdk/src/ui/components/address.ts": true, - "external:../snaps-sdk/src/ui/components/button.ts": true, - "external:../snaps-sdk/src/ui/components/copyable.ts": true, - "external:../snaps-sdk/src/ui/components/divider.ts": true, - "external:../snaps-sdk/src/ui/components/form.ts": true, - "external:../snaps-sdk/src/ui/components/heading.ts": true, - "external:../snaps-sdk/src/ui/components/image.ts": true, - "external:../snaps-sdk/src/ui/components/input.ts": true, - "external:../snaps-sdk/src/ui/components/panel.ts": true, - "external:../snaps-sdk/src/ui/components/row.ts": true, - "external:../snaps-sdk/src/ui/components/spinner.ts": true, - "external:../snaps-sdk/src/ui/components/text.ts": true - } - }, - "external:../snaps-sdk/src/ui/components/input.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true + "depcheck>semver>lru-cache": true } }, - "external:../snaps-sdk/src/ui/components/panel.ts": { + "depcheck>semver>lru-cache": { "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/components/address.ts": true, - "external:../snaps-sdk/src/ui/components/button.ts": true, - "external:../snaps-sdk/src/ui/components/copyable.ts": true, - "external:../snaps-sdk/src/ui/components/divider.ts": true, - "external:../snaps-sdk/src/ui/components/form.ts": true, - "external:../snaps-sdk/src/ui/components/heading.ts": true, - "external:../snaps-sdk/src/ui/components/image.ts": true, - "external:../snaps-sdk/src/ui/components/input.ts": true, - "external:../snaps-sdk/src/ui/components/row.ts": true, - "external:../snaps-sdk/src/ui/components/spinner.ts": true, - "external:../snaps-sdk/src/ui/components/text.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/row.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/components/address.ts": true, - "external:../snaps-sdk/src/ui/components/image.ts": true, - "external:../snaps-sdk/src/ui/components/text.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/spinner.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/text.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/index.ts": { - "packages": { - "external:../snaps-sdk/src/ui/component.ts": true, - "external:../snaps-sdk/src/ui/components/index.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true - } - }, - "external:../snaps-sdk/src/ui/nodes.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-utils/src/errors.ts": { - "packages": { - "@metamask/rpc-errors": true, - "@metamask/utils": true, - "external:../snaps-sdk/src/index.ts": true - } - }, - "external:../snaps-utils/src/handlers.ts": { - "packages": { - "external:../snaps-sdk/src/index.ts": true, - "external:../snaps-utils/src/handler-types.ts": true, - "superstruct": true - } - }, - "external:../snaps-utils/src/icon.ts": { - "builtin": { - "buffer": true - } - }, - "external:../snaps-utils/src/iframe.ts": { - "globals": { - "document.body.appendChild": true, - "document.createElement": true - } - }, - "external:../snaps-utils/src/index.executionenv.ts": { - "packages": { - "external:../snaps-utils/src/errors.ts": true, - "external:../snaps-utils/src/handler-types.ts": true, - "external:../snaps-utils/src/handlers.ts": true, - "external:../snaps-utils/src/iframe.ts": true, - "external:../snaps-utils/src/logging.ts": true, - "external:../snaps-utils/src/namespace.ts": true, - "external:../snaps-utils/src/types.ts": true - } - }, - "external:../snaps-utils/src/logging.ts": { - "globals": { - "console.error": true, - "console.log": true, - "console.warn": true - }, - "packages": { - "@metamask/utils": true - } - }, - "external:../snaps-utils/src/namespace.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-utils/src/types.ts": { - "globals": { - "URL": true - }, - "packages": { - "@metamask/utils": true, - "superstruct": true + "depcheck>semver>lru-cache>yallist": true } }, "readable-stream": { @@ -513,6 +213,19 @@ "console.warn": true, "define": true } + }, + "tsup>debug": { + "globals": { + "console": true, + "document": true, + "localStorage": true, + "navigator": true, + "process": true + }, + "packages": { + "browserify>process": true, + "tsup>debug>ms": true + } } } } \ No newline at end of file diff --git a/packages/snaps-execution-environments/lavamoat/browserify/worker-pool/policy.json b/packages/snaps-execution-environments/lavamoat/browserify/worker-pool/policy.json index dd32fb1779..63e69a30b9 100644 --- a/packages/snaps-execution-environments/lavamoat/browserify/worker-pool/policy.json +++ b/packages/snaps-execution-environments/lavamoat/browserify/worker-pool/policy.json @@ -22,18 +22,40 @@ "@metamask/utils": true } }, - "@metamask/snaps-sdk>is-svg": { + "@metamask/snaps-sdk": { + "globals": { + "fetch": true + }, "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser": true + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk>fast-xml-parser": true, + "@metamask/utils": true, + "superstruct": true } }, - "@metamask/snaps-sdk>is-svg>fast-xml-parser": { + "@metamask/snaps-sdk>fast-xml-parser": { "globals": { "entityName": true, "val": true }, "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser>strnum": true + "@metamask/snaps-sdk>fast-xml-parser>strnum": true + } + }, + "@metamask/snaps-utils": { + "globals": { + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true + }, + "packages": { + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/utils": true, + "superstruct": true } }, "@metamask/utils": { @@ -45,10 +67,10 @@ "@metamask/utils>@noble/hashes": true, "@metamask/utils>@scure/base": true, "@metamask/utils>pony-cause": true, - "@swc/cli>semver": true, "browserify>buffer": true, - "eslint>debug": true, - "superstruct": true + "depcheck>semver": true, + "superstruct": true, + "tsup>debug": true } }, "@metamask/utils>@noble/hashes": { @@ -63,20 +85,6 @@ "TextEncoder": true } }, - "@swc/cli>semver": { - "globals": { - "console.error": true - }, - "packages": { - "@swc/cli>semver>lru-cache": true, - "browserify>process": true - } - }, - "@swc/cli>semver>lru-cache": { - "packages": { - "@swc/cli>semver>lru-cache>yallist": true - } - }, "browserify>browser-pack>safe-buffer": { "packages": { "browserify>buffer": true @@ -107,333 +115,18 @@ "browserify>browser-pack>safe-buffer": true } }, - "eslint>debug": { + "depcheck>semver": { "globals": { - "console": true, - "document": true, - "localStorage": true, - "navigator": true, - "process": true + "console.error": true }, "packages": { "browserify>process": true, - "eslint>debug>ms": true + "depcheck>semver>lru-cache": true } }, - "external:../snaps-sdk/src/error-wrappers.ts": { + "depcheck>semver>lru-cache": { "packages": { - "@metamask/rpc-errors": true, - "external:../snaps-sdk/src/internals/index.ts": true - } - }, - "external:../snaps-sdk/src/errors.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true - } - }, - "external:../snaps-sdk/src/images.ts": { - "globals": { - "fetch": true - }, - "packages": { - "@metamask/utils": true, - "external:../snaps-sdk/src/ui/index.ts": true - } - }, - "external:../snaps-sdk/src/index.ts": { - "packages": { - "@metamask/utils": true, - "external:../snaps-sdk/src/error-wrappers.ts": true, - "external:../snaps-sdk/src/errors.ts": true, - "external:../snaps-sdk/src/images.ts": true, - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/types/index.ts": true, - "external:../snaps-sdk/src/ui/index.ts": true - } - }, - "external:../snaps-sdk/src/internals/error-wrappers.ts": { - "packages": { - "external:../snaps-sdk/src/errors.ts": true - } - }, - "external:../snaps-sdk/src/internals/errors.ts": { - "packages": { - "@metamask/utils": true - } - }, - "external:../snaps-sdk/src/internals/index.ts": { - "packages": { - "external:../snaps-sdk/src/internals/error-wrappers.ts": true, - "external:../snaps-sdk/src/internals/errors.ts": true, - "external:../snaps-sdk/src/internals/helpers.ts": true, - "external:../snaps-sdk/src/internals/structs.ts": true - } - }, - "external:../snaps-sdk/src/internals/structs.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-sdk/src/types/handlers/index.ts": { - "packages": { - "external:../snaps-sdk/src/types/handlers/cronjob.ts": true, - "external:../snaps-sdk/src/types/handlers/home-page.ts": true, - "external:../snaps-sdk/src/types/handlers/keyring.ts": true, - "external:../snaps-sdk/src/types/handlers/lifecycle.ts": true, - "external:../snaps-sdk/src/types/handlers/name-lookup.ts": true, - "external:../snaps-sdk/src/types/handlers/rpc-request.ts": true, - "external:../snaps-sdk/src/types/handlers/signature.ts": true, - "external:../snaps-sdk/src/types/handlers/transaction.ts": true, - "external:../snaps-sdk/src/types/handlers/user-input.ts": true - } - }, - "external:../snaps-sdk/src/types/handlers/user-input.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-sdk/src/types/index.ts": { - "packages": { - "external:../snaps-sdk/src/types/caip.ts": true, - "external:../snaps-sdk/src/types/global.ts": true, - "external:../snaps-sdk/src/types/handlers/index.ts": true, - "external:../snaps-sdk/src/types/interface.ts": true, - "external:../snaps-sdk/src/types/methods/index.ts": true, - "external:../snaps-sdk/src/types/permissions.ts": true, - "external:../snaps-sdk/src/types/provider.ts": true, - "external:../snaps-sdk/src/types/snap.ts": true - } - }, - "external:../snaps-sdk/src/types/interface.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-sdk/src/types/methods/index.ts": { - "packages": { - "external:../snaps-sdk/src/types/methods/create-interface.ts": true, - "external:../snaps-sdk/src/types/methods/dialog.ts": true, - "external:../snaps-sdk/src/types/methods/get-bip32-entropy.ts": true, - "external:../snaps-sdk/src/types/methods/get-bip32-public-key.ts": true, - "external:../snaps-sdk/src/types/methods/get-bip44-entropy.ts": true, - "external:../snaps-sdk/src/types/methods/get-client-status.ts": true, - "external:../snaps-sdk/src/types/methods/get-entropy.ts": true, - "external:../snaps-sdk/src/types/methods/get-file.ts": true, - "external:../snaps-sdk/src/types/methods/get-interface-state.ts": true, - "external:../snaps-sdk/src/types/methods/get-locale.ts": true, - "external:../snaps-sdk/src/types/methods/get-snaps.ts": true, - "external:../snaps-sdk/src/types/methods/invoke-keyring.ts": true, - "external:../snaps-sdk/src/types/methods/invoke-snap.ts": true, - "external:../snaps-sdk/src/types/methods/manage-accounts.ts": true, - "external:../snaps-sdk/src/types/methods/manage-state.ts": true, - "external:../snaps-sdk/src/types/methods/methods.ts": true, - "external:../snaps-sdk/src/types/methods/notify.ts": true, - "external:../snaps-sdk/src/types/methods/request-snaps.ts": true, - "external:../snaps-sdk/src/types/methods/update-interface.ts": true - } - }, - "external:../snaps-sdk/src/ui/builder.ts": { - "packages": { - "@metamask/utils": true - } - }, - "external:../snaps-sdk/src/ui/component.ts": { - "packages": { - "@metamask/utils": true, - "external:../snaps-sdk/src/ui/components/index.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/address.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/button.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/copyable.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/divider.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/form.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/components/button.ts": true, - "external:../snaps-sdk/src/ui/components/input.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/heading.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/image.ts": { - "packages": { - "@metamask/snaps-sdk>is-svg": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/index.ts": { - "packages": { - "external:../snaps-sdk/src/ui/components/address.ts": true, - "external:../snaps-sdk/src/ui/components/button.ts": true, - "external:../snaps-sdk/src/ui/components/copyable.ts": true, - "external:../snaps-sdk/src/ui/components/divider.ts": true, - "external:../snaps-sdk/src/ui/components/form.ts": true, - "external:../snaps-sdk/src/ui/components/heading.ts": true, - "external:../snaps-sdk/src/ui/components/image.ts": true, - "external:../snaps-sdk/src/ui/components/input.ts": true, - "external:../snaps-sdk/src/ui/components/panel.ts": true, - "external:../snaps-sdk/src/ui/components/row.ts": true, - "external:../snaps-sdk/src/ui/components/spinner.ts": true, - "external:../snaps-sdk/src/ui/components/text.ts": true - } - }, - "external:../snaps-sdk/src/ui/components/input.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/panel.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/components/address.ts": true, - "external:../snaps-sdk/src/ui/components/button.ts": true, - "external:../snaps-sdk/src/ui/components/copyable.ts": true, - "external:../snaps-sdk/src/ui/components/divider.ts": true, - "external:../snaps-sdk/src/ui/components/form.ts": true, - "external:../snaps-sdk/src/ui/components/heading.ts": true, - "external:../snaps-sdk/src/ui/components/image.ts": true, - "external:../snaps-sdk/src/ui/components/input.ts": true, - "external:../snaps-sdk/src/ui/components/row.ts": true, - "external:../snaps-sdk/src/ui/components/spinner.ts": true, - "external:../snaps-sdk/src/ui/components/text.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/row.ts": { - "packages": { - "external:../snaps-sdk/src/internals/index.ts": true, - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/components/address.ts": true, - "external:../snaps-sdk/src/ui/components/image.ts": true, - "external:../snaps-sdk/src/ui/components/text.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/spinner.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/components/text.ts": { - "packages": { - "external:../snaps-sdk/src/ui/builder.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true, - "superstruct": true - } - }, - "external:../snaps-sdk/src/ui/index.ts": { - "packages": { - "external:../snaps-sdk/src/ui/component.ts": true, - "external:../snaps-sdk/src/ui/components/index.ts": true, - "external:../snaps-sdk/src/ui/nodes.ts": true - } - }, - "external:../snaps-sdk/src/ui/nodes.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-utils/src/errors.ts": { - "packages": { - "@metamask/rpc-errors": true, - "@metamask/utils": true, - "external:../snaps-sdk/src/index.ts": true - } - }, - "external:../snaps-utils/src/handlers.ts": { - "packages": { - "external:../snaps-sdk/src/index.ts": true, - "external:../snaps-utils/src/handler-types.ts": true, - "superstruct": true - } - }, - "external:../snaps-utils/src/icon.ts": { - "builtin": { - "buffer": true - } - }, - "external:../snaps-utils/src/iframe.ts": { - "globals": { - "document.body.appendChild": true, - "document.createElement": true - } - }, - "external:../snaps-utils/src/index.executionenv.ts": { - "packages": { - "external:../snaps-utils/src/errors.ts": true, - "external:../snaps-utils/src/handler-types.ts": true, - "external:../snaps-utils/src/handlers.ts": true, - "external:../snaps-utils/src/iframe.ts": true, - "external:../snaps-utils/src/logging.ts": true, - "external:../snaps-utils/src/namespace.ts": true, - "external:../snaps-utils/src/types.ts": true - } - }, - "external:../snaps-utils/src/logging.ts": { - "globals": { - "console.error": true, - "console.log": true, - "console.warn": true - }, - "packages": { - "@metamask/utils": true - } - }, - "external:../snaps-utils/src/namespace.ts": { - "packages": { - "superstruct": true - } - }, - "external:../snaps-utils/src/types.ts": { - "globals": { - "URL": true - }, - "packages": { - "@metamask/utils": true, - "superstruct": true + "depcheck>semver>lru-cache>yallist": true } }, "readable-stream": { @@ -459,6 +152,19 @@ "console.warn": true, "define": true } + }, + "tsup>debug": { + "globals": { + "console": true, + "document": true, + "localStorage": true, + "navigator": true, + "process": true + }, + "packages": { + "browserify>process": true, + "tsup>debug>ms": true + } } } } \ No newline at end of file diff --git a/packages/snaps-execution-environments/lavamoat/build-system/policy.json b/packages/snaps-execution-environments/lavamoat/build-system/policy.json index e3998644e4..f0cf64f764 100644 --- a/packages/snaps-execution-environments/lavamoat/build-system/policy.json +++ b/packages/snaps-execution-environments/lavamoat/build-system/policy.json @@ -22,7 +22,6 @@ }, "packages": { "@babel/core>@ampproject/remapping": true, - "@babel/core>@babel/code-frame": true, "@babel/core>@babel/generator": true, "@babel/core>@babel/helper-compilation-targets": true, "@babel/core>@babel/helper-module-transforms": true, @@ -36,7 +35,8 @@ "depcheck>@babel/parser": true, "depcheck>@babel/traverse": true, "depcheck>json5": true, - "eslint>debug": true + "lavamoat>@babel/code-frame": true, + "tsup>debug": true } }, "@babel/core>@ampproject/remapping": { @@ -53,107 +53,8 @@ "define": true }, "packages": { - "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/set-array": true, - "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": true - } - }, - "@babel/core>@babel/code-frame": { - "globals": { - "console.warn": true, - "process.emitWarning": true - }, - "packages": { - "@babel/core>@babel/code-frame>@babel/highlight": true, - "@babel/core>@babel/code-frame>chalk": true - } - }, - "@babel/core>@babel/code-frame>@babel/highlight": { - "packages": { - "@babel/core>@babel/code-frame>@babel/highlight>chalk": true, - "lavamoat>@babel/highlight>@babel/helper-validator-identifier": true, - "lavamoat>@babel/highlight>js-tokens": true - } - }, - "@babel/core>@babel/code-frame>@babel/highlight>chalk": { - "globals": { - "process.env.TERM": true, - "process.platform": true - }, - "packages": { - "@babel/core>@babel/code-frame>@babel/highlight>chalk>ansi-styles": true, - "@babel/core>@babel/code-frame>@babel/highlight>chalk>escape-string-regexp": true, - "@babel/core>@babel/code-frame>@babel/highlight>chalk>supports-color": true - } - }, - "@babel/core>@babel/code-frame>@babel/highlight>chalk>ansi-styles": { - "packages": { - "@babel/core>@babel/code-frame>@babel/highlight>chalk>ansi-styles>color-convert": true - } - }, - "@babel/core>@babel/code-frame>@babel/highlight>chalk>ansi-styles>color-convert": { - "packages": { - "@babel/core>@babel/code-frame>@babel/highlight>chalk>ansi-styles>color-convert>color-name": true - } - }, - "@babel/core>@babel/code-frame>@babel/highlight>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": { - "@babel/core>@babel/code-frame>@babel/highlight>chalk>supports-color>has-flag": true - } - }, - "@babel/core>@babel/code-frame>@babel/highlight>chalk>supports-color>has-flag": { - "globals": { - "process.argv": true - } - }, - "@babel/core>@babel/code-frame>chalk": { - "globals": { - "process.env.TERM": true, - "process.platform": true - }, - "packages": { - "@babel/core>@babel/code-frame>chalk>ansi-styles": true, - "@babel/core>@babel/code-frame>chalk>escape-string-regexp": true, - "@babel/core>@babel/code-frame>chalk>supports-color": true - } - }, - "@babel/core>@babel/code-frame>chalk>ansi-styles": { - "packages": { - "@babel/core>@babel/code-frame>chalk>ansi-styles>color-convert": true - } - }, - "@babel/core>@babel/code-frame>chalk>ansi-styles>color-convert": { - "packages": { - "@babel/core>@babel/code-frame>chalk>ansi-styles>color-convert>color-name": true - } - }, - "@babel/core>@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": { - "@babel/core>@babel/code-frame>chalk>supports-color>has-flag": true - } - }, - "@babel/core>@babel/code-frame>chalk>supports-color>has-flag": { - "globals": { - "process.argv": true + "tsup>sucrase>@jridgewell/gen-mapping>@jridgewell/set-array": true, + "tsup>sucrase>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": true } }, "@babel/core>@babel/generator": { @@ -164,8 +65,8 @@ "packages": { "@babel/core>@babel/generator>jsesc": true, "@babel/core>@babel/types": true, - "terser>@jridgewell/source-map>@jridgewell/gen-mapping": true, - "terser>@jridgewell/source-map>@jridgewell/trace-mapping": true + "terser>@jridgewell/source-map>@jridgewell/trace-mapping": true, + "tsup>sucrase>@jridgewell/gen-mapping": true } }, "@babel/core>@babel/generator>jsesc": { @@ -255,9 +156,9 @@ }, "@babel/core>@babel/template": { "packages": { - "@babel/core>@babel/code-frame": true, "@babel/core>@babel/types": true, - "depcheck>@babel/parser": true + "depcheck>@babel/parser": true, + "lavamoat>@babel/code-frame": true } }, "@babel/core>@babel/types": { @@ -1047,9 +948,9 @@ "packages": { "@lavamoat/lavapack>combine-source-map": true, "@lavamoat/lavapack>convert-source-map": true, + "@lavamoat/lavapack>through2": true, "@lavamoat/lavapack>umd": true, "browserify>JSONStream": true, - "browserify>through2": true, "eslint>espree": true, "lavamoat>json-stable-stringify": true, "lavamoat>lavamoat-core": true, @@ -1096,6 +997,11 @@ "value": true } }, + "@lavamoat/lavapack>through2": { + "packages": { + "readable-stream": true + } + }, "@metamask/object-multiplex>once": { "packages": { "@metamask/object-multiplex>once>wrappy": true @@ -1111,9 +1017,9 @@ "@metamask/utils>@noble/hashes": true, "@metamask/utils>@scure/base": true, "@metamask/utils>pony-cause": true, - "@swc/cli>semver": true, - "eslint>debug": true, - "superstruct": true + "depcheck>semver": true, + "superstruct": true, + "tsup>debug": true } }, "@metamask/utils>@noble/hashes": { @@ -1128,20 +1034,6 @@ "TextEncoder": true } }, - "@swc/cli>semver": { - "globals": { - "console.error": true, - "process": true - }, - "packages": { - "@swc/cli>semver>lru-cache": true - } - }, - "@swc/cli>semver>lru-cache": { - "packages": { - "@swc/cli>semver>lru-cache>yallist": true - } - }, "@wdio/mocha-framework>mocha>supports-color": { "builtin": { "os.release": true, @@ -1913,12 +1805,16 @@ "browserify>through2>readable-stream>safe-buffer": true } }, + "browserify>util>is-typed-array>gopd": { + "packages": { + "eslint-plugin-import>array-includes>get-intrinsic": true + } + }, "depcheck>@babel/traverse": { "globals": { "console.log": true }, "packages": { - "@babel/core>@babel/code-frame": true, "@babel/core>@babel/generator": true, "@babel/core>@babel/types": true, "depcheck>@babel/parser": true, @@ -1927,7 +1823,8 @@ "depcheck>@babel/traverse>@babel/helper-hoist-variables": true, "depcheck>@babel/traverse>@babel/helper-split-export-declaration": true, "depcheck>@babel/traverse>globals": true, - "eslint>debug": true + "lavamoat>@babel/code-frame": true, + "tsup>debug": true } }, "depcheck>@babel/traverse>@babel/helper-function-name": { @@ -1998,6 +1895,44 @@ "process.platform": true } }, + "depcheck>semver": { + "globals": { + "console.error": true, + "process": true + }, + "packages": { + "depcheck>semver>lru-cache": true + } + }, + "depcheck>semver>lru-cache": { + "packages": { + "depcheck>semver>lru-cache>yallist": true + } + }, + "eslint-plugin-import>array-includes>get-intrinsic": { + "globals": { + "AggregateError": true, + "FinalizationRegistry": true, + "WeakRef": true + }, + "packages": { + "browserify>has>function-bind": true, + "eslint-plugin-import>array-includes>get-intrinsic>has-proto": true, + "eslint-plugin-import>array-includes>get-intrinsic>hasown": true, + "eslint-plugin-import>object.values>es-abstract>has-symbols": true, + "lavamoat>json-stable-stringify>call-bind>es-errors": true + } + }, + "eslint-plugin-import>array-includes>get-intrinsic>hasown": { + "packages": { + "browserify>has>function-bind": true + } + }, + "eslint-plugin-import>object.values>es-abstract>has-property-descriptors": { + "packages": { + "eslint-plugin-import>array-includes>get-intrinsic": true + } + }, "eslint-plugin-import>tsconfig-paths": { "builtin": { "fs.existsSync": true, @@ -2040,25 +1975,6 @@ "eslint>chalk>ansi-styles>color-convert>color-name": true } }, - "eslint>debug": { - "builtin": { - "tty.isatty": true, - "util.deprecate": true, - "util.format": true, - "util.inspect": true - }, - "globals": { - "console": true, - "document": true, - "localStorage": true, - "navigator": true, - "process": true - }, - "packages": { - "@wdio/mocha-framework>mocha>supports-color": true, - "eslint>debug>ms": true - } - }, "eslint>espree": { "packages": { "eslint>eslint-visitor-keys": true, @@ -2108,9 +2024,9 @@ "packages": { "@lavamoat/lavapack": true, "browserify>browser-resolve": true, - "browserify>through2": true, "lavamoat-browserify>concat-stream": true, "lavamoat-browserify>duplexify": true, + "lavamoat-browserify>through2": true, "lavamoat>@lavamoat/aa": true, "lavamoat>json-stable-stringify": true, "lavamoat>lavamoat-core": true, @@ -2125,70 +2041,142 @@ "packages": { "browserify>concat-stream>typedarray": true, "browserify>inherits": true, - "lavamoat-browserify>concat-stream>readable-stream": true, + "readable-stream": true, "terser>source-map-support>buffer-from": true } }, - "lavamoat-browserify>concat-stream>readable-stream": { + "lavamoat-browserify>duplexify": { + "globals": { + "Buffer": true, + "process.nextTick": true + }, + "packages": { + "browserify>inherits": true, + "lavamoat-browserify>duplexify>end-of-stream": true, + "lavamoat-browserify>duplexify>stream-shift": true, + "readable-stream": true + } + }, + "lavamoat-browserify>duplexify>end-of-stream": { + "globals": { + "process.nextTick": true + }, + "packages": { + "@metamask/object-multiplex>once": true + } + }, + "lavamoat-browserify>through2": { + "packages": { + "readable-stream": true + } + }, + "lavamoat>@babel/code-frame": { + "globals": { + "console.warn": true, + "process.emitWarning": true + }, + "packages": { + "lavamoat>@babel/code-frame>chalk": true, + "lavamoat>@babel/highlight": true + } + }, + "lavamoat>@babel/code-frame>chalk": { + "globals": { + "process.env.TERM": true, + "process.platform": true + }, + "packages": { + "lavamoat>@babel/code-frame>chalk>ansi-styles": true, + "lavamoat>@babel/code-frame>chalk>escape-string-regexp": true, + "lavamoat>@babel/code-frame>chalk>supports-color": true + } + }, + "lavamoat>@babel/code-frame>chalk>ansi-styles": { + "packages": { + "lavamoat>@babel/code-frame>chalk>ansi-styles>color-convert": true + } + }, + "lavamoat>@babel/code-frame>chalk>ansi-styles>color-convert": { + "packages": { + "lavamoat>@babel/code-frame>chalk>ansi-styles>color-convert>color-name": true + } + }, + "lavamoat>@babel/code-frame>chalk>supports-color": { "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true + "os.release": true }, "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, + "process.env": true, + "process.platform": true, "process.stderr": true, "process.stdout": true, - "process.version.slice": true, - "setImmediate": true + "process.versions.node.split": true }, "packages": { - "browserify>inherits": true, - "browserify>readable-stream>core-util-is": true, - "browserify>readable-stream>process-nextick-args": true, - "lavamoat-browserify>concat-stream>readable-stream>isarray": true, - "lavamoat-browserify>concat-stream>readable-stream>safe-buffer": true, - "lavamoat-browserify>concat-stream>readable-stream>string_decoder": true, - "readable-stream>util-deprecate": true + "lavamoat>@babel/code-frame>chalk>supports-color>has-flag": true } }, - "lavamoat-browserify>concat-stream>readable-stream>safe-buffer": { - "builtin": { - "buffer": true + "lavamoat>@babel/code-frame>chalk>supports-color>has-flag": { + "globals": { + "process.argv": true } }, - "lavamoat-browserify>concat-stream>readable-stream>string_decoder": { + "lavamoat>@babel/highlight": { "packages": { - "lavamoat-browserify>concat-stream>readable-stream>safe-buffer": true + "lavamoat>@babel/highlight>@babel/helper-validator-identifier": true, + "lavamoat>@babel/highlight>chalk": true, + "lavamoat>@babel/highlight>js-tokens": true } }, - "lavamoat-browserify>duplexify": { + "lavamoat>@babel/highlight>chalk": { "globals": { - "Buffer": true, - "process.nextTick": true + "process.env.TERM": true, + "process.platform": true }, "packages": { - "browserify>inherits": true, - "lavamoat-browserify>duplexify>end-of-stream": true, - "lavamoat-browserify>duplexify>stream-shift": true, - "readable-stream": true + "lavamoat>@babel/highlight>chalk>ansi-styles": true, + "lavamoat>@babel/highlight>chalk>escape-string-regexp": true, + "lavamoat>@babel/highlight>chalk>supports-color": true } }, - "lavamoat-browserify>duplexify>end-of-stream": { + "lavamoat>@babel/highlight>chalk>ansi-styles": { + "packages": { + "lavamoat>@babel/highlight>chalk>ansi-styles>color-convert": true + } + }, + "lavamoat>@babel/highlight>chalk>ansi-styles>color-convert": { + "packages": { + "lavamoat>@babel/highlight>chalk>ansi-styles>color-convert>color-name": true + } + }, + "lavamoat>@babel/highlight>chalk>supports-color": { + "builtin": { + "os.release": true + }, "globals": { - "process.nextTick": true + "process.env": true, + "process.platform": true, + "process.stderr": true, + "process.stdout": true, + "process.versions.node.split": true }, "packages": { - "@metamask/object-multiplex>once": true + "lavamoat>@babel/highlight>chalk>supports-color>has-flag": true + } + }, + "lavamoat>@babel/highlight>chalk>supports-color>has-flag": { + "globals": { + "process.argv": true } }, "lavamoat>@lavamoat/aa": { "builtin": { - "fs.readFileSync": true, - "path.dirname": true, - "path.join": true, - "path.relative": true + "node:fs.lstatSync": true, + "node:fs.readFileSync": true, + "node:fs.realpathSync": true, + "node:path.dirname": true, + "node:path.join": true, + "node:path.relative": true }, "packages": { "depcheck>resolve": true @@ -2196,23 +2184,59 @@ }, "lavamoat>json-stable-stringify": { "packages": { - "lavamoat>json-stable-stringify>jsonify": true + "lavamoat>json-stable-stringify>call-bind": true, + "lavamoat>json-stable-stringify>isarray": true, + "lavamoat>json-stable-stringify>jsonify": true, + "lavamoat>json-stable-stringify>object-keys": true + } + }, + "lavamoat>json-stable-stringify>call-bind": { + "packages": { + "browserify>has>function-bind": true, + "eslint-plugin-import>array-includes>get-intrinsic": true, + "lavamoat>json-stable-stringify>call-bind>es-errors": true, + "lavamoat>json-stable-stringify>call-bind>set-function-length": true + } + }, + "lavamoat>json-stable-stringify>call-bind>set-function-length": { + "packages": { + "browserify>util>is-typed-array>gopd": true, + "eslint-plugin-import>array-includes>get-intrinsic": true, + "eslint-plugin-import>object.values>es-abstract>has-property-descriptors": true, + "lavamoat>json-stable-stringify>call-bind>es-errors": true, + "lavamoat>json-stable-stringify>call-bind>set-function-length>define-data-property": true + } + }, + "lavamoat>json-stable-stringify>call-bind>set-function-length>define-data-property": { + "packages": { + "browserify>util>is-typed-array>gopd": true, + "eslint-plugin-import>array-includes>get-intrinsic": true, + "eslint-plugin-import>object.values>es-abstract>has-property-descriptors": true, + "lavamoat>json-stable-stringify>call-bind>es-errors": true } }, "lavamoat>lavamoat-core": { "builtin": { "events": true, "fs.readFileSync": true, - "node:fs/promises.readFile": true, + "node:fs.readFileSync": true, "node:fs/promises.writeFile": true, "path.extname": true, "path.join": true }, "globals": { "__dirname": true, + "ast": true, "console.error": true, "console.warn": true, - "define": true + "content": true, + "define": true, + "file": true, + "importMap": true, + "moduleInitializer": true, + "packageName": true, + "specifier": true, + "type": true }, "packages": { "lavamoat>json-stable-stringify": true, @@ -2345,58 +2369,77 @@ "define": true } }, - "terser>@jridgewell/source-map>@jridgewell/gen-mapping": { + "terser>@jridgewell/source-map>@jridgewell/trace-mapping": { "globals": { "define": true }, "packages": { - "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/set-array": true, - "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": true, - "terser>@jridgewell/source-map>@jridgewell/trace-mapping": true + "terser>@jridgewell/source-map>@jridgewell/trace-mapping>@jridgewell/resolve-uri": true, + "terser>@jridgewell/source-map>@jridgewell/trace-mapping>@jridgewell/sourcemap-codec": true } }, - "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/set-array": { + "terser>@jridgewell/source-map>@jridgewell/trace-mapping>@jridgewell/resolve-uri": { "globals": { "define": true } }, - "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": { + "terser>@jridgewell/source-map>@jridgewell/trace-mapping>@jridgewell/sourcemap-codec": { "globals": { "Buffer": true, "TextDecoder": true, "define": true } }, - "terser>@jridgewell/source-map>@jridgewell/trace-mapping": { + "terser>acorn": { "globals": { + "console": true, "define": true - }, - "packages": { - "terser>@jridgewell/source-map>@jridgewell/trace-mapping>@jridgewell/resolve-uri": true, - "terser>@jridgewell/source-map>@jridgewell/trace-mapping>@jridgewell/sourcemap-codec": true } }, - "terser>@jridgewell/source-map>@jridgewell/trace-mapping>@jridgewell/resolve-uri": { + "terser>source-map-support>buffer-from": { "globals": { - "define": true + "Buffer": true } }, - "terser>@jridgewell/source-map>@jridgewell/trace-mapping>@jridgewell/sourcemap-codec": { + "tsup>debug": { + "builtin": { + "tty.isatty": true, + "util.deprecate": true, + "util.format": true, + "util.inspect": true + }, + "globals": { + "console": true, + "document": true, + "localStorage": true, + "navigator": true, + "process": true + }, + "packages": { + "@wdio/mocha-framework>mocha>supports-color": true, + "tsup>debug>ms": true + } + }, + "tsup>sucrase>@jridgewell/gen-mapping": { "globals": { - "Buffer": true, - "TextDecoder": true, "define": true + }, + "packages": { + "terser>@jridgewell/source-map>@jridgewell/trace-mapping": true, + "tsup>sucrase>@jridgewell/gen-mapping>@jridgewell/set-array": true, + "tsup>sucrase>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": true } }, - "terser>acorn": { + "tsup>sucrase>@jridgewell/gen-mapping>@jridgewell/set-array": { "globals": { - "console": true, "define": true } }, - "terser>source-map-support>buffer-from": { + "tsup>sucrase>@jridgewell/gen-mapping>@jridgewell/sourcemap-codec": { "globals": { - "Buffer": true + "Buffer": true, + "TextDecoder": true, + "define": true } }, "yargs": { diff --git a/packages/snaps-execution-environments/package.json b/packages/snaps-execution-environments/package.json index 9f7ca0bc4a..7338e1cd95 100644 --- a/packages/snaps-execution-environments/package.json +++ b/packages/snaps-execution-environments/package.json @@ -1,20 +1,31 @@ { "name": "@metamask/snaps-execution-environments", - "version": "4.0.0", + "version": "6.0.2", "description": "Snap sandbox environments for executing SES javascript", "repository": { "type": "git", "url": "https://github.com/MetaMask/snaps.git" }, "sideEffects": false, - "main": "./dist/cjs/index.js", - "module": "./dist/esm/index.js", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/types/index.d.ts" + }, + "./dist/browserify/node-process/bundle.js": { + "default": "./dist/browserify/node-process/bundle.js" + }, + "./dist/browserify/node-thread/bundle.js": { + "default": "./dist/browserify/node-thread/bundle.js" + }, + "./package.json": "./package.json" + }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", "types": "./dist/types/index.d.ts", "files": [ - "dist/cjs/**", - "dist/esm/**", - "dist/types/**", - "dist/browserify/**" + "dist" ], "scripts": { "test": "rimraf coverage && jest && yarn test:browser && yarn posttest", @@ -28,27 +39,23 @@ "lint:changelog": "../../scripts/validate-changelog.sh @metamask/snaps-execution-environments", "lint": "yarn lint:eslint && yarn lint:misc --check && yarn lint:changelog && yarn lint:dependencies", "clean": "rimraf '*.tsbuildinfo' 'dist' 'src/__GENERATED__/' 'coverage/*' '__test__/*'", - "build": "yarn build:source && yarn build:types", - "build:source": "yarn build:esm && yarn build:cjs", + "build": "tsup --clean && yarn build:types && yarn build:lavamoat", "build:types": "tsc --project tsconfig.build.json", - "build:esm": "swc src --out-dir dist/esm --config-file ../../.swcrc.build.json --config module.type=es6", - "build:cjs": "swc src --out-dir dist/cjs --config-file ../../.swcrc.build.json --config module.type=commonjs", - "build:clean": "yarn clean && yarn build", - "build:post-tsc": "yarn build:lavamoat", "build:lavamoat": "lavamoat scripts/build.js --policy lavamoat/build-system/policy.json --policyOverride lavamoat/build-system/policy-override.json", "build:lavamoat:policy": "yarn build:lavamoat --writeAutoPolicy && node scripts/build.js --writeAutoPolicy", "auto-changelog-init": "auto-changelog init", "publish:preview": "yarn npm publish --tag preview", "lint:ci": "yarn lint", "start": "node scripts/start.js", - "lint:dependencies": "depcheck" + "lint:dependencies": "depcheck", + "build:ci": "tsup --clean" }, "dependencies": { - "@metamask/json-rpc-engine": "^7.3.2", + "@metamask/json-rpc-engine": "^8.0.1", "@metamask/object-multiplex": "^2.0.0", "@metamask/post-message-stream": "^8.0.0", - "@metamask/providers": "^14.0.2", - "@metamask/rpc-errors": "^6.1.0", + "@metamask/providers": "^16.0.0", + "@metamask/rpc-errors": "^6.2.1", "@metamask/snaps-sdk": "workspace:^", "@metamask/snaps-utils": "workspace:^", "@metamask/utils": "^8.3.0", @@ -62,15 +69,14 @@ "@babel/preset-typescript": "^7.23.2", "@esbuild-plugins/node-globals-polyfill": "^0.2.3", "@esbuild-plugins/node-modules-polyfill": "^0.2.2", - "@lavamoat/allow-scripts": "^3.0.0", - "@lavamoat/lavapack": "^6.0.2", + "@lavamoat/allow-scripts": "^3.0.3", + "@lavamoat/lavapack": "^6.1.1", "@lavamoat/lavatube": "^1.0.0", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", "@metamask/eslint-config-nodejs": "^12.1.0", "@metamask/eslint-config-typescript": "^12.1.0", - "@swc/cli": "^0.1.62", "@swc/core": "1.3.78", "@swc/jest": "^0.2.26", "@types/express": "^4.17.17", @@ -105,8 +111,8 @@ "jest": "^29.0.2", "jest-environment-node": "^29.5.0", "jest-fetch-mock": "^3.0.3", - "lavamoat": "^8.0.2", - "lavamoat-browserify": "^17.0.2", + "lavamoat": "^8.0.4", + "lavamoat-browserify": "^17.0.5", "prettier": "^2.7.1", "prettier-plugin-packagejson": "^2.2.11", "rimraf": "^4.1.2", @@ -114,6 +120,7 @@ "ses": "^1.1.0", "terser": "^5.17.7", "ts-node": "^10.9.1", + "tsup": "^8.0.1", "typescript": "~4.8.4", "vite": "^4.3.9", "vite-tsconfig-paths": "^4.0.5", diff --git a/packages/snaps-execution-environments/src/common/BaseSnapExecutor.test.browser.ts b/packages/snaps-execution-environments/src/common/BaseSnapExecutor.test.browser.ts index bdf74d29bc..6b3bff43e4 100644 --- a/packages/snaps-execution-environments/src/common/BaseSnapExecutor.test.browser.ts +++ b/packages/snaps-execution-environments/src/common/BaseSnapExecutor.test.browser.ts @@ -40,6 +40,8 @@ describe('BaseSnapExecutor', () => { const CODE = ` setTimeout(() => { throw new Error('setTimeout executed'); }, 10); setInterval(() => { throw new Error('setInterval executed'); }, 10); + + exports.onRpcRequest = () => null; `; const executor = new TestSnapExecutor(); @@ -390,9 +392,6 @@ describe('BaseSnapExecutor', () => { it('allows direct access to ethereum public properties', async () => { const CODE = ` module.exports.onRpcRequest = () => { - const listener = () => undefined; - ethereum.on('accountsChanged', listener); - ethereum.removeListener('accountsChanged', listener); return ethereum.request({ method: 'eth_blockNumber', params: [] }); }; `; @@ -1245,6 +1244,28 @@ describe('BaseSnapExecutor', () => { }); }); + it("throws if the Snap doesn't export anything", async () => { + const CODE = ``; + + const executor = new TestSnapExecutor(); + await executor.executeSnap(1, MOCK_SNAP_ID, CODE, []); + + expect(await executor.readCommand()).toStrictEqual({ + jsonrpc: '2.0', + id: 1, + error: expect.objectContaining({ + code: -32603, + message: `Error while running snap '${MOCK_SNAP_ID}': Snap has no valid exports.`, + data: { + cause: expect.objectContaining({ + code: -32603, + message: 'Snap has no valid exports.', + }), + }, + }), + }); + }); + it('supports onTransaction export', async () => { const CODE = ` module.exports.onTransaction = ({ transaction, chainId, transactionOrigin }) => @@ -2007,6 +2028,44 @@ describe('BaseSnapExecutor', () => { }); }); + it('throws when trying to respond with value that is too large', async () => { + const CODE = ` + module.exports.onRpcRequest = () => '1'.repeat(100_000_000); + `; + + const executor = new TestSnapExecutor(); + await executor.executeSnap(1, MOCK_SNAP_ID, CODE, []); + + expect(await executor.readCommand()).toStrictEqual({ + jsonrpc: '2.0', + id: 1, + result: 'OK', + }); + + await executor.writeCommand({ + jsonrpc: '2.0', + id: 2, + method: 'snapRpc', + params: [ + MOCK_SNAP_ID, + HandlerType.OnRpcRequest, + MOCK_ORIGIN, + { jsonrpc: '2.0', method: '', params: [] }, + ], + }); + + expect(await executor.readCommand()).toStrictEqual({ + jsonrpc: '2.0', + id: 2, + error: { + code: -32603, + message: + 'JSON-RPC responses must be JSON serializable objects smaller than 64 MB.', + stack: expect.any(String), + }, + }); + }); + it('throws when receiving an invalid RPC request', async () => { const executor = new TestSnapExecutor(); @@ -2377,8 +2436,8 @@ describe('BaseSnapExecutor', () => { hasMethods: { ethereum: { request: true, - on: true, - removeListener: true, + on: false, + removeListener: false, rpcEngine: false, }, snap: { diff --git a/packages/snaps-execution-environments/src/common/BaseSnapExecutor.ts b/packages/snaps-execution-environments/src/common/BaseSnapExecutor.ts index 8f0f266408..b6ee90843c 100644 --- a/packages/snaps-execution-environments/src/common/BaseSnapExecutor.ts +++ b/packages/snaps-execution-environments/src/common/BaseSnapExecutor.ts @@ -1,7 +1,7 @@ // eslint-disable-next-line @typescript-eslint/triple-slash-reference, spaced-comment /// import { createIdRemapMiddleware } from '@metamask/json-rpc-engine'; -import type { RequestArguments } from '@metamask/providers/dist/BaseProvider'; +import type { RequestArguments } from '@metamask/providers'; import { StreamProvider } from '@metamask/providers/dist/StreamProvider'; import { errorCodes, rpcErrors, serializeError } from '@metamask/rpc-errors'; import type { SnapsProvider } from '@metamask/snaps-sdk'; @@ -26,8 +26,6 @@ import type { Json, } from '@metamask/utils'; import { - isObject, - isValidJson, assert, isJsonRpcRequest, hasProperty, @@ -49,6 +47,7 @@ import { sanitizeRequestArguments, proxyStreamProvider, withTeardown, + isValidResponse, } from './utils'; import { ExecuteSnapRequestArgumentsStruct, @@ -301,27 +300,27 @@ export class BaseSnapExecutor { }); } - async #notify(requestObject: Omit) { - if (!isValidJson(requestObject) || !isObject(requestObject)) { + async #notify(notification: Omit) { + if (!isValidResponse(notification)) { throw rpcErrors.internal( - 'JSON-RPC notifications must be JSON serializable objects', + 'JSON-RPC notifications must be JSON serializable objects smaller than 64 MB.', ); } await this.#write({ - ...requestObject, + ...notification, jsonrpc: '2.0', }); } - async #respond(id: JsonRpcId, requestObject: Record) { - if (!isValidJson(requestObject) || !isObject(requestObject)) { + async #respond(id: JsonRpcId, response: Record) { + if (!isValidResponse(response)) { // Instead of throwing, we directly respond with an error. // This prevents an issue where we wouldn't respond when errors were non-serializable await this.#write({ error: serializeError( rpcErrors.internal( - 'JSON-RPC responses must be JSON serializable objects.', + 'JSON-RPC responses must be JSON serializable objects smaller than 64 MB.', ), ), id, @@ -331,7 +330,7 @@ export class BaseSnapExecutor { } await this.#write({ - ...requestObject, + ...response, id, jsonrpc: '2.0', }); @@ -462,6 +461,9 @@ export class BaseSnapExecutor { } return acc; }, {}); + + // If the Snap has no valid exports after this, fail. + assert(Object.keys(data.exports).length > 0, 'Snap has no valid exports.'); } /** @@ -495,23 +497,7 @@ export class BaseSnapExecutor { ); }; - // Proxy target is intentionally set to be an empty object, to ensure - // that access to the prototype chain is not possible. - const snapGlobalProxy = new Proxy( - {}, - { - has(_target: object, prop: string | symbol) { - return typeof prop === 'string' && ['request'].includes(prop); - }, - get(_target, prop: keyof StreamProvider) { - if (prop === 'request') { - return request; - } - - return undefined; - }, - }, - ) as SnapsProvider; + const snapGlobalProxy = proxyStreamProvider(request) as SnapsProvider; return harden(snapGlobalProxy); } @@ -547,7 +533,7 @@ export class BaseSnapExecutor { ); }; - const streamProviderProxy = proxyStreamProvider(provider, request); + const streamProviderProxy = proxyStreamProvider(request); return harden(streamProviderProxy); } diff --git a/packages/snaps-execution-environments/src/common/utils.test.ts b/packages/snaps-execution-environments/src/common/utils.test.ts index b6883ccddb..2acacc371a 100644 --- a/packages/snaps-execution-environments/src/common/utils.test.ts +++ b/packages/snaps-execution-environments/src/common/utils.test.ts @@ -2,6 +2,7 @@ import { BLOCKED_RPC_METHODS, assertEthereumOutboundRequest, assertSnapOutboundRequest, + isValidResponse, } from './utils'; describe('assertSnapOutboundRequest', () => { @@ -77,3 +78,22 @@ describe('assertEthereumOutboundRequest', () => { ); }); }); + +describe('isValidResponse', () => { + it('returns false if the value is not an object', () => { + // @ts-expect-error Intentionally using bad params + expect(isValidResponse('foo')).toBe(false); + }); + + it('returns false if the value is not JSON serializable', () => { + expect(isValidResponse({ foo: BigInt(0) })).toBe(false); + }); + + it('returns false if the value is too large', () => { + expect(isValidResponse({ foo: '1'.repeat(100_000_000) })).toBe(false); + }); + + it('returns true if the value is a valid JSON object', () => { + expect(isValidResponse({ foo: 'bar' })).toBe(true); + }); +}); diff --git a/packages/snaps-execution-environments/src/common/utils.ts b/packages/snaps-execution-environments/src/common/utils.ts index 4786155721..9cdf688897 100644 --- a/packages/snaps-execution-environments/src/common/utils.ts +++ b/packages/snaps-execution-environments/src/common/utils.ts @@ -1,9 +1,20 @@ -import type { StreamProvider } from '@metamask/providers'; -import type { RequestArguments } from '@metamask/providers/dist/BaseProvider'; +import type { StreamProvider, RequestArguments } from '@metamask/providers'; import { rpcErrors } from '@metamask/rpc-errors'; -import { assert, assertStruct, getSafeJson, JsonStruct } from '@metamask/utils'; +import { + assert, + assertStruct, + getJsonSize, + getSafeJson, + isObject, + JsonStruct, +} from '@metamask/utils'; import { log } from '../logging'; + +// 64 MB - we chose this number because it is the size limit for postMessage +// between the extension and the dapp enforced by Chrome. +const MAX_RESPONSE_JSON_SIZE = 64_000_000; + /** * Make proxy for Promise and handle the teardown process properly. * If the teardown is called in the meanwhile, Promise result will not be @@ -43,33 +54,24 @@ export async function withTeardown( } /** - * Returns a Proxy that narrows down (attenuates) the fields available on - * the StreamProvider and replaces the request implementation. + * Returns a Proxy that only allows access to a `request` function. + * This is useful for replacing StreamProvider with an attenuated version. * - * @param provider - Instance of a StreamProvider to be limited. - * @param request - Custom attenuated request object. - * @returns Proxy to the StreamProvider instance. + * @param request - Custom attenuated request function. + * @returns Proxy that mimics a StreamProvider instance. */ -export function proxyStreamProvider( - provider: StreamProvider, - request: unknown, -): StreamProvider { +export function proxyStreamProvider(request: unknown): StreamProvider { // Proxy target is intentionally set to be an empty object, to ensure // that access to the prototype chain is not possible. const proxy = new Proxy( {}, { has(_target: object, prop: string | symbol) { - return ( - typeof prop === 'string' && - ['request', 'on', 'removeListener'].includes(prop) - ); + return typeof prop === 'string' && ['request'].includes(prop); }, get(_target, prop: keyof StreamProvider) { if (prop === 'request') { return request; - } else if (['on', 'removeListener'].includes(prop)) { - return provider[prop]; } return undefined; @@ -174,3 +176,23 @@ export function sanitizeRequestArguments(value: unknown): RequestArguments { const json = JSON.parse(JSON.stringify(value)); return getSafeJson(json) as RequestArguments; } + +/** + * Check if the input is a valid response. + * + * @param response - The response. + * @returns True if the response is valid, otherwise false. + */ +export function isValidResponse(response: Record) { + if (!isObject(response)) { + return false; + } + + try { + // If the JSON is invalid this will throw and we should return false. + const size = getJsonSize(response); + return size < MAX_RESPONSE_JSON_SIZE; + } catch { + return false; + } +} diff --git a/packages/snaps-execution-environments/src/common/validation.test.ts b/packages/snaps-execution-environments/src/common/validation.test.ts index b9b9ec766a..dfe1d246ba 100644 --- a/packages/snaps-execution-environments/src/common/validation.test.ts +++ b/packages/snaps-execution-environments/src/common/validation.test.ts @@ -165,6 +165,14 @@ describe('assertIsOnUserInputRequestArguments', () => { value: { foo: 'bar' }, }, }, + { + id: 'foo', + event: { + type: UserInputEventType.InputChangeEvent, + name: 'input', + value: 'bar', + }, + }, ])('does not throw for a valid user input param object', (value) => { expect(() => assertIsOnUserInputRequestArguments(value)).not.toThrow(); }); diff --git a/packages/snaps-execution-environments/src/types/vendor/providers.d.ts b/packages/snaps-execution-environments/src/types/vendor/providers.d.ts new file mode 100644 index 0000000000..ee47bdc797 --- /dev/null +++ b/packages/snaps-execution-environments/src/types/vendor/providers.d.ts @@ -0,0 +1,8 @@ +// TODO: Remove this file once we switch to `Node16` module resolution in +// `tsconfig.json`. +// eslint-disable-next-line import/unambiguous +declare module '@metamask/providers/dist/StreamProvider' { + import { StreamProvider } from '@metamask/providers'; + + export { StreamProvider }; +} diff --git a/packages/snaps-execution-environments/tsconfig.json b/packages/snaps-execution-environments/tsconfig.json index ce93b29b10..9bb5b593d8 100644 --- a/packages/snaps-execution-environments/tsconfig.json +++ b/packages/snaps-execution-environments/tsconfig.json @@ -5,7 +5,13 @@ "typeRoots": ["../../node_modules/@types", "./node_modules/@types"], "allowJs": true }, - "include": ["./src", "webpack.config.js", "scripts"], + "include": [ + "./src", + "webpack.config.js", + "scripts", + "package.json", + "tsup.config.ts" + ], "references": [ { "path": "../snaps-sdk" diff --git a/packages/snaps-execution-environments/tsup.config.ts b/packages/snaps-execution-environments/tsup.config.ts new file mode 100644 index 0000000000..b341957eee --- /dev/null +++ b/packages/snaps-execution-environments/tsup.config.ts @@ -0,0 +1,15 @@ +import deepmerge from 'deepmerge'; +import type { Options } from 'tsup'; + +import packageJson from './package.json'; + +// `tsup.config.ts` is not under `rootDir`, so we need to use `require` instead. +// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires +const { default: baseConfig } = require('../../tsup.config'); + +const config: Options = { + name: packageJson.name, + external: ['@metamask/snaps-execution-environments'], +}; + +export default deepmerge(baseConfig, config); diff --git a/packages/snaps-jest/CHANGELOG.md b/packages/snaps-jest/CHANGELOG.md index dda5c5ed42..4e69652e8e 100644 --- a/packages/snaps-jest/CHANGELOG.md +++ b/packages/snaps-jest/CHANGELOG.md @@ -6,6 +6,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [7.0.2] +### Changed +- Bump `@metamask/snaps-execution-environments` to latest ([#2339](https://github.com/MetaMask/snaps/pull/2339)) + +## [7.0.1] +### Fixed +- Improve correctness of `clickElement` ([#2334](https://github.com/MetaMask/snaps/pull/2334)) + - The function should now behave closer to the client implementation. + +## [7.0.0] +### Added +- **BREAKING:** Support Interactive UI in `snaps-jest` ([#2286](https://github.com/MetaMask/snaps/pull/2286)) + - Remove `content` from the Snap response, instead `getInterface()` must be used + - `clickElement` and `typeInField` can be used on the interface return value to simulate actions + +### Changed +- Improve Jest expect types ([#2308](https://github.com/MetaMask/snaps/pull/2308)) +- Refactor to support changes to encryption ([#2316](https://github.com/MetaMask/snaps/pull/2316)) + +## [6.0.2] +### Changed +- Bump MetaMask dependencies ([#2270](https://github.com/MetaMask/snaps/pull/2270)) +- Bump @metamask/json-rpc-engine from 7.3.2 to 7.3.3 ([#2247](https://github.com/MetaMask/snaps/pull/2247)) + +## [6.0.1] +### Fixed +- Fix minor build configuration problems ([#2220](https://github.com/MetaMask/snaps/pull/2220)) + +## [6.0.0] +### Changed +- **BREAKING:** Update ESM build to be fully compliant with the ESM standard ([#2210](https://github.com/MetaMask/snaps/pull/2210)) +- **BREAKING:** Move Node.js exports to separate export ([#2210](https://github.com/MetaMask/snaps/pull/2210)) + - The default export is now browser-compatible. + - Node.js APIs can be imported from `/node`. +- Bump `@metamask/rpc-errors` to `^6.2.1` ([#2209](https://github.com/MetaMask/snaps/pull/2209)) + ## [5.0.0] ### Added - **BREAKING:** Implement testing framework using Node.js executor ([#1982](https://github.com/MetaMask/snaps/pull/1982), [#2118](https://github.com/MetaMask/snaps/pull/2118)) @@ -95,7 +131,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-jest@5.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-jest@7.0.2...HEAD +[7.0.2]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-jest@7.0.1...@metamask/snaps-jest@7.0.2 +[7.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-jest@7.0.0...@metamask/snaps-jest@7.0.1 +[7.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-jest@6.0.2...@metamask/snaps-jest@7.0.0 +[6.0.2]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-jest@6.0.1...@metamask/snaps-jest@6.0.2 +[6.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-jest@6.0.0...@metamask/snaps-jest@6.0.1 +[6.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-jest@5.0.0...@metamask/snaps-jest@6.0.0 [5.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-jest@4.0.1...@metamask/snaps-jest@5.0.0 [4.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-jest@4.0.0...@metamask/snaps-jest@4.0.1 [4.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-jest@3.1.0...@metamask/snaps-jest@4.0.0 diff --git a/packages/snaps-jest/README.md b/packages/snaps-jest/README.md index 08d4cbdd03..21b96188c5 100644 --- a/packages/snaps-jest/README.md +++ b/packages/snaps-jest/README.md @@ -194,7 +194,7 @@ All properties are optional, and have sensible defaults. The addresses are randomly generated by default. Most values can be specified as a hex string, or a decimal number. -It returns an object with the user interface that was shown by the snap, in the +It returns a `getInterface` function that gets the user interface that was shown by the snap, in the [onTransaction](https://docs.metamask.io/snaps/reference/exports/#ontransaction) function. @@ -214,7 +214,9 @@ describe('MySnap', () => { nonce: '0x0', }); - expect(response).toRender(panel([text('Hello, world!')])); + const screen = response.getInterface(); + + expect(screen).toRender(panel([text('Hello, world!')])); }); }); ``` @@ -233,7 +235,7 @@ All properties are optional, and have sensible defaults. The addresses are randomly generated by default. Most values can be specified as a hex string, or a decimal number. -It returns an object with the user interface that was shown by the snap, in the +It returns a `getInterface` function that gets the user interface that was shown by the snap, in the [onSignature](https://docs.metamask.io/snaps/reference/exports/#onsignature) function. @@ -246,7 +248,9 @@ describe('MySnap', () => { const { onSignature } = await installSnap(/* optional snap ID */); const response = await onSignature(); - expect(response).toRender( + const screen = response.getInterface(); + + expect(screen).toRender( panel([text('You are using the personal_sign method')]), ); }); @@ -303,7 +307,7 @@ describe('MySnap', () => { ### `snap.onHomePage` The `onHomePage` function can be used to request the home page of the snap. It -takes no arguments, and returns a promise that resolves to the response from the +takes no arguments, and returns a promise that contains a `getInterface` function to get the response from the [onHomePage](https://docs.metamask.io/snaps/reference/entry-points/#onhomepage) function. @@ -318,7 +322,9 @@ describe('MySnap', () => { params: [], }); - expect(response).toRender(/* ... */); + const screen = response.getInterface(); + + expect(screen).toRender(/* ... */); }); }); ``` @@ -344,6 +350,8 @@ assert that a response from a snap matches an expected value: ### Interacting with user interfaces +#### `snap_dialog` + If your snap uses `snap_dialog` to show user interfaces, you can use the `request.getInterface` function to interact with them. This method is present on the return value of the `snap.request` function. @@ -351,7 +359,7 @@ the return value of the `snap.request` function. It waits for the user interface to be shown, and returns an object with functions that can be used to interact with the user interface. -#### Example +##### Example ```js import { installSnap } from '@metamask/snaps-jest'; @@ -384,6 +392,91 @@ describe('MySnap', () => { }); ``` +#### handlers + +If your snap uses handlers that shows user interfaces (`onTransaction`, `onSignature`, `onHomePage`), you can use the +`response.getInterface` function to interact with them. This method is present on +the return value of the `snap.request` function. + +It returns an object with functions that can be used to interact with the user interface. + +##### Example + +```js +import { installSnap } from '@metamask/snaps-jest'; + +describe('MySnap', () => { + it('should do something', async () => { + const { onHomePage } = await installSnap(/* optional snap ID */); + const response = await onHomePage({ + method: 'foo', + params: [], + }); + + const screen = response.getInterface(); + + expect(screen).toRender(/* ... */); + }); +}); +``` + +### User interactions in user interfaces + +The object returned by the `getInterface` function exposes other functions to trigger user interactions in the user interface. + +- `clickElement(elementName)`: Click on a button inside the user interface. If the button with the given name does not exist in the interface this method will throw. +- `typeInField(elementName, valueToType)`: Enter a value in a field inside the user interface. If the input field with the given name des not exist in the interface this method will throw. + +#### Example + +```js +import { installSnap } from '@metamask/snaps-jest'; + +describe('MySnap', () => { + it('should do something', async () => { + const { onHomePage } = await installSnap(/* optional snap ID */); + const response = await onHomePage({ + method: 'foo', + params: [], + }); + + const screen = response.getInterface(); + + expect(screen).toRender(/* ... */); + + await screen.clickElement('myButton'); + + const screen = response.getInterface(); + + expect(screen).toRender(/* ... */); + }); +}); +``` + +```js +import { installSnap } from '@metamask/snaps-jest'; + +describe('MySnap', () => { + it('should do something', async () => { + const { onHomePage } = await installSnap(/* optional snap ID */); + const response = await onHomePage({ + method: 'foo', + params: [], + }); + + const screen = response.getInterface(); + + expect(screen).toRender(/* ... */); + + await screen.typeInField('myField', 'the value to type'); + + const screen = response.getInterface(); + + expect(screen).toRender(/* ... */); + }); +}); +``` + ## Options You can pass options to the test environment by adding a diff --git a/packages/snaps-jest/jest-preset.js b/packages/snaps-jest/jest-preset.js index 4b713a7256..62b1e54322 100644 --- a/packages/snaps-jest/jest-preset.js +++ b/packages/snaps-jest/jest-preset.js @@ -12,7 +12,7 @@ const config = { // timeout to 30 seconds by default. testTimeout: 30000, - setupFilesAfterEnv: [resolve(__dirname, 'dist', 'cjs', 'setup.js')], + setupFilesAfterEnv: [resolve(__dirname, 'dist', 'setup.js')], }; module.exports = config; diff --git a/packages/snaps-jest/package.json b/packages/snaps-jest/package.json index acd1443a70..4e100bc28f 100644 --- a/packages/snaps-jest/package.json +++ b/packages/snaps-jest/package.json @@ -1,15 +1,22 @@ { "name": "@metamask/snaps-jest", - "version": "5.0.0", + "version": "7.0.2", "description": "A Jest preset for end-to-end testing MetaMask Snaps, including a Jest environment, and a set of Jest matchers.", "sideEffects": false, - "main": "./dist/cjs/index.js", - "module": "./dist/esm/index.js", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/types/index.d.ts" + }, + "./jest-preset": "./jest-preset.js", + "./package.json": "./package.json" + }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", "types": "./dist/types/index.d.ts", "files": [ - "dist/cjs/**", - "dist/esm/**", - "dist/types/**", + "dist", "jest-preset.js" ], "scripts": { @@ -22,26 +29,23 @@ "lint:ci": "yarn lint", "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write", "lint:changelog": "../../scripts/validate-changelog.sh @metamask/snaps-jest", - "build": "yarn build:source && yarn build:types", - "build:source": "yarn build:esm && yarn build:cjs", + "build": "tsup --clean && yarn build:types", "build:types": "tsc --project tsconfig.build.json", - "build:esm": "swc src --out-dir dist/esm --config-file ../../.swcrc.build.json --config module.type=es6", - "build:cjs": "swc src --out-dir dist/cjs --config-file ../../.swcrc.build.json --config module.type=commonjs", - "build:clean": "yarn clean && yarn build", "clean": "rimraf '*.tsbuildinfo' 'dist'", "publish:preview": "yarn npm publish --tag preview", - "lint:dependencies": "depcheck" + "lint:dependencies": "depcheck", + "build:ci": "tsup --clean" }, "dependencies": { "@jest/environment": "^29.5.0", "@jest/expect": "^29.5.0", "@jest/globals": "^29.5.0", - "@metamask/base-controller": "^4.1.0", + "@metamask/base-controller": "^5.0.1", "@metamask/eth-json-rpc-middleware": "^12.1.0", - "@metamask/json-rpc-engine": "^7.3.2", - "@metamask/json-rpc-middleware-stream": "^6.0.2", + "@metamask/json-rpc-engine": "^8.0.1", + "@metamask/json-rpc-middleware-stream": "^7.0.1", "@metamask/key-tree": "^9.0.0", - "@metamask/permission-controller": "^8.0.0", + "@metamask/permission-controller": "^9.0.2", "@metamask/snaps-controllers": "workspace:^", "@metamask/snaps-execution-environments": "workspace:^", "@metamask/snaps-rpc-methods": "workspace:^", @@ -59,13 +63,12 @@ }, "devDependencies": { "@jest/types": "^29.6.3", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", "@metamask/eslint-config-nodejs": "^12.1.0", "@metamask/eslint-config-typescript": "^12.1.0", - "@swc/cli": "^0.1.62", "@swc/core": "1.3.78", "@swc/jest": "^0.2.26", "@types/jest": "^27.5.1", @@ -87,6 +90,7 @@ "prettier": "^2.7.1", "prettier-plugin-packagejson": "^2.2.11", "rimraf": "^4.1.2", + "tsup": "^8.0.1", "typescript": "~4.8.4" }, "engines": { diff --git a/packages/snaps-jest/src/global.ts b/packages/snaps-jest/src/global.ts new file mode 100644 index 0000000000..ae856ba18d --- /dev/null +++ b/packages/snaps-jest/src/global.ts @@ -0,0 +1,91 @@ +/* eslint-disable @typescript-eslint/consistent-type-definitions, @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface, @typescript-eslint/no-unused-vars, @typescript-eslint/no-namespace */ + +import type { + Component, + EnumToUnion, + NotificationType, +} from '@metamask/snaps-sdk'; + +interface SnapsMatchers { + /** + * Assert that the response is a JSON-RPC response with the given result. This + * is equivalent to calling `expect(response.result).toStrictEqual(result)`. + * + * @param response - The expected response result. + * @throws If the response is not a JSON-RPC response with the given result. + * @example + * const response = await request({ method: 'foo' }); + * expect(response).toRespondWith('bar'); + */ + toRespondWith(response: unknown): void; + + /** + * Assert that the response is a JSON-RPC response with the given error. This + * is equivalent to calling `expect(response.error).toStrictEqual(error)`. + * + * @param error - The expected response error. + * @throws If the response is not a JSON-RPC response with the given error. + * @example + * const response = await request({ method: 'foo' }); + * expect(response).toRespondWithError({ + * code: -32601, + * message: 'The method does not exist / is not available.', + * stack: expect.any(String), + * data: { method: 'foo', cause: null }, + * }); + */ + toRespondWithError(error: unknown): void; + + /** + * Assert that the Snap sent a notification with the expected message, and + * optionally the expected notification type. This is equivalent to calling + * `expect(response.notifications).toContainEqual({ message, type })`. + * + * @param message - The expected notification message. + * @param type - The expected notification type, i.e., 'inApp' or 'native'. If + * not provided, the type will be ignored. + * @throws If the snap did not send a notification with the expected message. + * @example + * const response = await request({ method: 'foo' }); + * expect(response).toSendNotification('bar', NotificationType.InApp); + */ + toSendNotification( + message: string, + type?: EnumToUnion, + ): void; + + /** + * Assert that the Snap rendered the expected component. This is equivalent to + * calling `expect(interface.content).toStrictEqual(component)`. + * + * @param component - The expected rendered component. + * @throws If the snap did not render the expected component. + * @example + * const response = request({ method: 'foo' }); + * const ui = await response.getInterface(); + * expect(ui).toRender(panel([heading('Hello, world!')])); + */ + toRender(component: Component): void; +} + +// Extend the `expect` interface with the new matchers. This is used when +// importing `expect` from `@jest/globals`. +declare module 'expect' { + interface AsymmetricMatchers extends SnapsMatchers {} + + // Ideally we would use `Matchers` instead of `Matchers`, but + // TypeScript doesn't allow this: + // TS2428: All declarations of 'Matchers' must have identical type parameters. + interface Matchers extends SnapsMatchers {} +} + +// Extend the Jest global namespace with the new matchers. This is used when +// using the global `expect` function. +declare global { + namespace jest { + // Ideally we would use `Matchers` instead of `Matchers`, but + // TypeScript doesn't allow this: + // TS2428: All declarations of 'Matchers' must have identical type parameters. + interface Matchers extends SnapsMatchers {} + } +} diff --git a/packages/snaps-jest/src/helpers.test.ts b/packages/snaps-jest/src/helpers.test.ts index eedf390009..2956a44553 100644 --- a/packages/snaps-jest/src/helpers.test.ts +++ b/packages/snaps-jest/src/helpers.test.ts @@ -403,6 +403,8 @@ describe('installSnap', () => { type: 'text', value: 'Hello, world!', }, + clickElement: expect.any(Function), + typeInField: expect.any(Function), ok: expect.any(Function), cancel: expect.any(Function), }); @@ -462,6 +464,8 @@ describe('installSnap', () => { type: 'text', value: 'Hello, world!', }, + clickElement: expect.any(Function), + typeInField: expect.any(Function), ok: expect.any(Function), cancel: expect.any(Function), }); @@ -520,6 +524,8 @@ describe('installSnap', () => { type: 'text', value: 'Hello, world!', }, + clickElement: expect.any(Function), + typeInField: expect.any(Function), ok: expect.any(Function), }); @@ -707,7 +713,7 @@ describe('installSnap', () => { expect(response).toStrictEqual( expect.objectContaining({ - content: { type: 'text', value: 'Hello, world!' }, + getInterface: expect.any(Function), }), ); diff --git a/packages/snaps-jest/src/helpers.ts b/packages/snaps-jest/src/helpers.ts index 0895a1b9a1..438c892dac 100644 --- a/packages/snaps-jest/src/helpers.ts +++ b/packages/snaps-jest/src/helpers.ts @@ -1,7 +1,7 @@ import type { AbstractExecutionService } from '@metamask/snaps-controllers'; import type { SnapId } from '@metamask/snaps-sdk'; import { HandlerType, logInfo } from '@metamask/snaps-utils'; -import { createModuleLogger } from '@metamask/utils'; +import { assertStruct, createModuleLogger } from '@metamask/utils'; import { create } from 'superstruct'; import { @@ -11,6 +11,7 @@ import { getEnvironment, JsonRpcMockOptionsStruct, SignatureOptionsStruct, + SnapResponseWithInterfaceStruct, } from './internals'; import type { InstallSnapOptions } from './internals'; import { @@ -18,6 +19,7 @@ import { removeJsonRpcMock, } from './internals/simulation/store/mocks'; import type { + SnapResponseWithInterface, CronjobOptions, JsonRpcMockOptions, Snap, @@ -49,6 +51,17 @@ function getOptions< return [snapId, options]; } +/** + * Ensure that the actual response contains `getInterface`. + * + * @param response - The response of the handler. + */ +function assertIsResponseWithInterface( + response: SnapResponse, +): asserts response is SnapResponseWithInterface { + assertStruct(response, SnapResponseWithInterfaceStruct); +} + /** * Load a snap into the environment. This is the main entry point for testing * snaps: It returns a {@link Snap} object that can be used to interact with the @@ -199,7 +212,7 @@ export async function installSnap< const onTransaction = async ( request: TransactionOptions, - ): Promise => { + ): Promise => { log('Sending transaction %o.', request); const { @@ -208,7 +221,7 @@ export async function installSnap< ...transaction } = create(request, TransactionOptionsStruct); - return handleRequest({ + const response = await handleRequest({ snapId: installedSnapId, store, executionService, @@ -224,6 +237,10 @@ export async function installSnap< }, }, }); + + assertIsResponseWithInterface(response); + + return response; }; const onCronjob = (request: CronjobOptions) => { @@ -258,7 +275,9 @@ export async function installSnap< onTransaction, sendTransaction: onTransaction, - onSignature: async (request: unknown): Promise => { + onSignature: async ( + request: unknown, + ): Promise => { log('Requesting signature %o.', request); const { origin: signatureOrigin, ...signature } = create( @@ -266,7 +285,7 @@ export async function installSnap< SignatureOptionsStruct, ); - return handleRequest({ + const response = await handleRequest({ snapId: installedSnapId, store, executionService, @@ -281,15 +300,19 @@ export async function installSnap< }, }, }); + + assertIsResponseWithInterface(response); + + return response; }, onCronjob, runCronjob: onCronjob, - onHomePage: async (): Promise => { + onHomePage: async (): Promise => { log('Rendering home page.'); - return handleRequest({ + const response = await handleRequest({ snapId: installedSnapId, store, executionService, @@ -300,6 +323,10 @@ export async function installSnap< method: '', }, }); + + assertIsResponseWithInterface(response); + + return response; }, mockJsonRpc(mock: JsonRpcMockOptions) { diff --git a/packages/snaps-jest/src/index.ts b/packages/snaps-jest/src/index.ts index 0e92c750f6..030365797a 100644 --- a/packages/snaps-jest/src/index.ts +++ b/packages/snaps-jest/src/index.ts @@ -1,3 +1,6 @@ +// eslint-disable-next-line import/no-unassigned-import +import './global'; + export { default, default as TestEnvironment } from './environment'; export * from './helpers'; export * from './options'; diff --git a/packages/snaps-jest/src/internals/request.test.ts b/packages/snaps-jest/src/internals/request.test.ts index ab4e67c587..8017baae81 100644 --- a/packages/snaps-jest/src/internals/request.test.ts +++ b/packages/snaps-jest/src/internals/request.test.ts @@ -1,14 +1,19 @@ import { SnapInterfaceController } from '@metamask/snaps-controllers'; import type { SnapId } from '@metamask/snaps-sdk'; -import { text } from '@metamask/snaps-sdk'; +import { UserInputEventType, button, input, text } from '@metamask/snaps-sdk'; import { HandlerType } from '@metamask/snaps-utils'; +import { MOCK_SNAP_ID } from '@metamask/snaps-utils/test-utils'; import { getMockServer, getRestrictedSnapInterfaceControllerMessenger, getRootControllerMessenger, } from '../test-utils'; -import { getContentFromResult, handleRequest } from './request'; +import { + getInterfaceApi, + getInterfaceFromResult, + handleRequest, +} from './request'; import { handleInstallSnap } from './simulation'; describe('handleRequest', () => { @@ -32,7 +37,6 @@ describe('handleRequest', () => { }); expect(response).toStrictEqual({ - content: undefined, id: expect.any(String), response: { result: 'Hello, world!', @@ -85,7 +89,7 @@ describe('handleRequest', () => { }, }, notifications: [], - content, + getInterface: expect.any(Function), }); await closeServer(); @@ -127,46 +131,199 @@ describe('handleRequest', () => { }); }); -describe('getContentFromResult', () => { - it('gets the content from the SnapInterfaceController if the result contains an ID', async () => { +describe('getInterfaceFromResult', () => { + const controllerMessenger = getRootControllerMessenger(); + // eslint-disable-next-line no-new + new SnapInterfaceController({ + messenger: + getRestrictedSnapInterfaceControllerMessenger(controllerMessenger), + }); + + it('returns the interface ID if the result includes it', async () => { + const result = await getInterfaceFromResult( + { id: 'foo' }, + MOCK_SNAP_ID, + controllerMessenger, + ); + + expect(result).toBe('foo'); + }); + + it('creates a new interface and returns its ID if the result contains content', async () => { + jest.spyOn(controllerMessenger, 'call'); + + const result = await getInterfaceFromResult( + { content: text('foo') }, + MOCK_SNAP_ID, + controllerMessenger, + ); + + expect(result).toStrictEqual(expect.any(String)); + + expect(controllerMessenger.call).toHaveBeenCalledWith( + 'SnapInterfaceController:createInterface', + MOCK_SNAP_ID, + text('foo'), + ); + }); +}); + +describe('getInterfaceApi', () => { + it('gets the content from the SnapInterfaceController if the result contains an interface ID', async () => { const controllerMessenger = getRootControllerMessenger(); const interfaceController = new SnapInterfaceController({ messenger: getRestrictedSnapInterfaceControllerMessenger(controllerMessenger), }); - - const snapId = 'foo' as SnapId; const content = text('foo'); - const id = await interfaceController.createInterface(snapId, content); + const id = await interfaceController.createInterface(MOCK_SNAP_ID, content); + + const getInterface = await getInterfaceApi( + { id }, + MOCK_SNAP_ID, + controllerMessenger, + ); + + expect(getInterface).toStrictEqual(expect.any(Function)); - const result = getContentFromResult({ id }, snapId, controllerMessenger); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const result = getInterface!(); - expect(result).toStrictEqual(content); + expect(result).toStrictEqual({ + content, + clickElement: expect.any(Function), + typeInField: expect.any(Function), + }); }); - it('gets the content from the result if the result contains the content', () => { + it('gets the content from the SnapInterfaceController if the result contains content', async () => { const controllerMessenger = getRootControllerMessenger(); - const snapId = 'foo' as SnapId; + // eslint-disable-next-line no-new + new SnapInterfaceController({ + messenger: + getRestrictedSnapInterfaceControllerMessenger(controllerMessenger), + }); + const content = text('foo'); - const result = getContentFromResult( + const getInterface = await getInterfaceApi( { content }, - snapId, + MOCK_SNAP_ID, controllerMessenger, ); - expect(result).toStrictEqual(content); + expect(getInterface).toStrictEqual(expect.any(Function)); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const result = getInterface!(); + + expect(result).toStrictEqual({ + content, + clickElement: expect.any(Function), + typeInField: expect.any(Function), + }); }); - it('returns undefined if there is no content associated with the result', () => { + it('returns undefined if there is no interface ID associated with the result', async () => { const controllerMessenger = getRootControllerMessenger(); - const snapId = 'foo' as SnapId; - - const result = getContentFromResult({}, snapId, controllerMessenger); + const result = await getInterfaceApi({}, MOCK_SNAP_ID, controllerMessenger); expect(result).toBeUndefined(); }); + + it('sends the request to the snap when using `clickElement`', async () => { + const controllerMessenger = getRootControllerMessenger(); + + jest.spyOn(controllerMessenger, 'call'); + + // eslint-disable-next-line no-new + new SnapInterfaceController({ + messenger: + getRestrictedSnapInterfaceControllerMessenger(controllerMessenger), + }); + + const content = button({ value: 'foo', name: 'foo' }); + + const getInterface = await getInterfaceApi( + { content }, + MOCK_SNAP_ID, + controllerMessenger, + ); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const snapInterface = getInterface!(); + + await snapInterface.clickElement('foo'); + + expect(controllerMessenger.call).toHaveBeenNthCalledWith( + 4, + 'ExecutionService:handleRpcRequest', + MOCK_SNAP_ID, + { + origin: '', + handler: HandlerType.OnUserInput, + request: { + jsonrpc: '2.0', + method: ' ', + params: { + event: { + type: UserInputEventType.ButtonClickEvent, + name: 'foo', + }, + id: expect.any(String), + }, + }, + }, + ); + }); + + it('sends the request to the snap when using `typeInField`', async () => { + const controllerMessenger = getRootControllerMessenger(); + + jest.spyOn(controllerMessenger, 'call'); + + // eslint-disable-next-line no-new + new SnapInterfaceController({ + messenger: + getRestrictedSnapInterfaceControllerMessenger(controllerMessenger), + }); + + const content = input('foo'); + + const getInterface = await getInterfaceApi( + { content }, + MOCK_SNAP_ID, + controllerMessenger, + ); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const snapInterface = getInterface!(); + + await snapInterface.typeInField('foo', 'bar'); + + expect(controllerMessenger.call).toHaveBeenNthCalledWith( + 6, + 'ExecutionService:handleRpcRequest', + MOCK_SNAP_ID, + { + origin: '', + handler: HandlerType.OnUserInput, + request: { + jsonrpc: '2.0', + method: ' ', + params: { + event: { + type: UserInputEventType.InputChangeEvent, + name: 'foo', + value: 'bar', + }, + id: expect.any(String), + }, + }, + }, + ); + }); }); diff --git a/packages/snaps-jest/src/internals/request.ts b/packages/snaps-jest/src/internals/request.ts index 4fc3c8b812..c34222fbf8 100644 --- a/packages/snaps-jest/src/internals/request.ts +++ b/packages/snaps-jest/src/internals/request.ts @@ -5,11 +5,17 @@ import { unwrapError } from '@metamask/snaps-utils'; import { getSafeJson, hasProperty, isPlainObject } from '@metamask/utils'; import { nanoid } from '@reduxjs/toolkit'; -import type { RequestOptions, SnapRequest } from '../types'; +import type { + RequestOptions, + SnapHandlerInterface, + SnapRequest, +} from '../types'; import { clearNotifications, + clickElement, getInterface, getNotifications, + typeInField, } from './simulation'; import type { RunSagaFunction, Store } from './simulation'; import type { RootControllerMessenger } from './simulation/controllers'; @@ -63,11 +69,15 @@ export function handleRequest({ ...options, }, }) - .then((result) => { + .then(async (result) => { const notifications = getNotifications(store.getState()); store.dispatch(clearNotifications()); - const content = getContentFromResult(result, snapId, controllerMessenger); + const getInterfaceFn = await getInterfaceApi( + result, + snapId, + controllerMessenger, + ); return { id: String(id), @@ -75,7 +85,7 @@ export function handleRequest({ result: getSafeJson(result), }, notifications, - content, + ...(getInterfaceFn ? { getInterface: getInterfaceFn } : {}), }; }) .catch((error) => { @@ -103,28 +113,85 @@ export function handleRequest({ } /** - * Get the response content either from the SnapInterfaceController or the response object if there is one. + * Get the interface ID from the result if it's available or create a new interface if the result contains static components. * * @param result - The handler result object. * @param snapId - The Snap ID. * @param controllerMessenger - The controller messenger. - * @returns The content components if any. + * @returns The interface ID or undefined if the result doesn't include content. */ -export function getContentFromResult( +export async function getInterfaceFromResult( result: unknown, snapId: SnapId, controllerMessenger: RootControllerMessenger, -): Component | undefined { +) { if (isPlainObject(result) && hasProperty(result, 'id')) { - return controllerMessenger.call( - 'SnapInterfaceController:getInterface', - snapId, - result.id as string, - ).content; + return result.id as string; } if (isPlainObject(result) && hasProperty(result, 'content')) { - return result.content as Component; + const id = await controllerMessenger.call( + 'SnapInterfaceController:createInterface', + snapId, + result.content as Component, + ); + + return id; + } + + return undefined; +} + +/** + * Get the response content from the SnapInterfaceController and include the interaction methods. + * + * @param result - The handler result object. + * @param snapId - The Snap ID. + * @param controllerMessenger - The controller messenger. + * @returns The content components if any. + */ +export async function getInterfaceApi( + result: unknown, + snapId: SnapId, + controllerMessenger: RootControllerMessenger, +): Promise<(() => SnapHandlerInterface) | undefined> { + const interfaceId = await getInterfaceFromResult( + result, + snapId, + controllerMessenger, + ); + + if (interfaceId) { + return () => { + const { content } = controllerMessenger.call( + 'SnapInterfaceController:getInterface', + snapId, + interfaceId, + ); + + return { + content, + clickElement: async (name) => { + await clickElement( + controllerMessenger, + interfaceId, + content, + snapId, + name, + ); + }, + typeInField: async (name, value) => { + await typeInField( + controllerMessenger, + interfaceId, + content, + snapId, + name, + value, + ); + }, + }; + }; } return undefined; diff --git a/packages/snaps-jest/src/internals/server.test.ts b/packages/snaps-jest/src/internals/server.test.ts index a29f12d490..a4ab5156fc 100644 --- a/packages/snaps-jest/src/internals/server.test.ts +++ b/packages/snaps-jest/src/internals/server.test.ts @@ -30,7 +30,7 @@ describe('startServer', () => { "registry": "https://registry.npmjs.org", }, }, - "shasum": "D3ANeNZ7C1Ynx0GTP07afj72Jq06Srlq49QZkhICY+E=", + "shasum": "uaLwMO39qzKbshqPM6W2Ju7gkO/czuwgNKpjzXRXJj0=", }, "version": "1.0.0", } diff --git a/packages/snaps-jest/src/internals/server.ts b/packages/snaps-jest/src/internals/server.ts index 8b66e5dba2..5ce77d2104 100644 --- a/packages/snaps-jest/src/internals/server.ts +++ b/packages/snaps-jest/src/internals/server.ts @@ -3,7 +3,7 @@ import { assertIsSnapManifest, isDirectory, isFile, -} from '@metamask/snaps-utils'; +} from '@metamask/snaps-utils/node'; import { createModuleLogger } from '@metamask/utils'; import express from 'express'; import { promises as fs } from 'fs'; diff --git a/packages/snaps-jest/src/internals/simulation/controllers.ts b/packages/snaps-jest/src/internals/simulation/controllers.ts index a4b88b78d7..633e76bfcd 100644 --- a/packages/snaps-jest/src/internals/simulation/controllers.ts +++ b/packages/snaps-jest/src/internals/simulation/controllers.ts @@ -70,6 +70,8 @@ export function getControllers(options: GetControllersOptions): Controllers { const subjectMetadataController = new SubjectMetadataController({ messenger: controllerMessenger.getRestricted({ name: 'SubjectMetadataController', + allowedActions: [], + allowedEvents: [], }), subjectCacheLimit: 100, }); @@ -81,6 +83,7 @@ export function getControllers(options: GetControllersOptions): Controllers { 'PhishingController:maybeUpdateState', 'PhishingController:testOrigin', ], + allowedEvents: [], }), }); @@ -116,6 +119,7 @@ function getPermissionController(options: GetControllersOptions) { `SnapController:install`, `SubjectMetadataController:getSubjectMetadata`, ], + allowedEvents: [], }), caveatSpecifications: { ...snapsCaveatsSpecifications, diff --git a/packages/snaps-jest/src/internals/simulation/interface.test.ts b/packages/snaps-jest/src/internals/simulation/interface.test.ts index 872bed543d..d937086b0e 100644 --- a/packages/snaps-jest/src/internals/simulation/interface.test.ts +++ b/packages/snaps-jest/src/internals/simulation/interface.test.ts @@ -1,6 +1,16 @@ import { SnapInterfaceController } from '@metamask/snaps-controllers'; -import type { SnapId } from '@metamask/snaps-sdk'; -import { DialogType, text } from '@metamask/snaps-sdk'; +import { + ButtonType, + DialogType, + UserInputEventType, + button, + form, + input, + panel, + text, +} from '@metamask/snaps-sdk'; +import { HandlerType, WrappedSnapError } from '@metamask/snaps-utils'; +import { MOCK_SNAP_ID } from '@metamask/snaps-utils/test-utils'; import { assert } from '@metamask/utils'; import type { SagaIterator } from 'redux-saga'; import { take } from 'redux-saga/effects'; @@ -10,7 +20,14 @@ import { getRestrictedSnapInterfaceControllerMessenger, getRootControllerMessenger, } from '../../test-utils'; -import { getInterface, getInterfaceResponse } from './interface'; +import { + clickElement, + getElement, + getInterface, + getInterfaceResponse, + mergeValue, + typeInField, +} from './interface'; import type { RunSagaFunction } from './store'; import { createStore, resolveInterface, setInterface } from './store'; @@ -29,17 +46,21 @@ async function getResolve(runSaga: RunSagaFunction) { } describe('getInterfaceResponse', () => { + const interfaceActions = { clickElement: jest.fn(), typeInField: jest.fn() }; it('returns an `ok` function that resolves the user interface with `null` for alert dialogs', async () => { - const { runSaga } = createStore('password', getMockOptions()); + const { runSaga } = createStore(getMockOptions()); const response = getInterfaceResponse( runSaga, DialogType.Alert, text('foo'), + interfaceActions, ); expect(response).toStrictEqual({ type: DialogType.Alert, content: text('foo'), + clickElement: expect.any(Function), + typeInField: expect.any(Function), ok: expect.any(Function), }); @@ -49,16 +70,19 @@ describe('getInterfaceResponse', () => { }); it('returns an `ok` function that resolves the user interface with `true` for confirmation dialogs', async () => { - const { runSaga } = createStore('password', getMockOptions()); + const { runSaga } = createStore(getMockOptions()); const response = getInterfaceResponse( runSaga, DialogType.Confirmation, text('foo'), + interfaceActions, ); expect(response).toStrictEqual({ type: DialogType.Confirmation, content: text('foo'), + clickElement: expect.any(Function), + typeInField: expect.any(Function), ok: expect.any(Function), cancel: expect.any(Function), }); @@ -69,17 +93,20 @@ describe('getInterfaceResponse', () => { }); it('returns a `cancel` function that resolves the user interface with `false` for confirmation dialogs', async () => { - const { runSaga } = createStore('password', getMockOptions()); + const { runSaga } = createStore(getMockOptions()); const response = getInterfaceResponse( runSaga, DialogType.Confirmation, text('foo'), + interfaceActions, ); assert(response.type === DialogType.Confirmation); expect(response).toStrictEqual({ type: DialogType.Confirmation, content: text('foo'), + clickElement: expect.any(Function), + typeInField: expect.any(Function), ok: expect.any(Function), cancel: expect.any(Function), }); @@ -90,16 +117,19 @@ describe('getInterfaceResponse', () => { }); it('returns an `ok` function that resolves the user interface with the input value for prompt dialogs', async () => { - const { runSaga } = createStore('password', getMockOptions()); + const { runSaga } = createStore(getMockOptions()); const response = getInterfaceResponse( runSaga, DialogType.Prompt, text('foo'), + interfaceActions, ); expect(response).toStrictEqual({ type: DialogType.Prompt, content: text('foo'), + clickElement: expect.any(Function), + typeInField: expect.any(Function), ok: expect.any(Function), cancel: expect.any(Function), }); @@ -110,16 +140,19 @@ describe('getInterfaceResponse', () => { }); it('returns an `ok` function that resolves the user interface with an empty string for prompt dialogs', async () => { - const { runSaga } = createStore('password', getMockOptions()); + const { runSaga } = createStore(getMockOptions()); const response = getInterfaceResponse( runSaga, DialogType.Prompt, text('foo'), + interfaceActions, ); expect(response).toStrictEqual({ type: DialogType.Prompt, content: text('foo'), + clickElement: expect.any(Function), + typeInField: expect.any(Function), ok: expect.any(Function), cancel: expect.any(Function), }); @@ -130,17 +163,20 @@ describe('getInterfaceResponse', () => { }); it('returns a `cancel` function that resolves the user interface with `null` for prompt dialogs', async () => { - const { runSaga } = createStore('password', getMockOptions()); + const { runSaga } = createStore(getMockOptions()); const response = getInterfaceResponse( runSaga, DialogType.Prompt, text('foo'), + interfaceActions, ); assert(response.type === DialogType.Prompt); expect(response).toStrictEqual({ type: DialogType.Prompt, content: text('foo'), + clickElement: expect.any(Function), + typeInField: expect.any(Function), ok: expect.any(Function), cancel: expect.any(Function), }); @@ -151,7 +187,7 @@ describe('getInterfaceResponse', () => { }); it('throws an error for unknown dialog types', () => { - const { runSaga } = createStore('password', getMockOptions()); + const { runSaga } = createStore(getMockOptions()); expect(() => { // @ts-expect-error - Invalid dialog type. @@ -160,6 +196,290 @@ describe('getInterfaceResponse', () => { }); }); +describe('getElement', () => { + it('gets an element at the root', () => { + const content = button({ value: 'foo', name: 'bar' }); + + const result = getElement(content, 'bar'); + + expect(result).toStrictEqual({ + element: button({ value: 'foo', name: 'bar' }), + }); + }); + + it('gets an element with a given name inside a panel', () => { + const content = panel([button({ value: 'foo', name: 'bar' })]); + + const result = getElement(content, 'bar'); + + expect(result).toStrictEqual({ + element: button({ value: 'foo', name: 'bar' }), + form: undefined, + }); + }); + + it('gets an element in a form', () => { + const content = form('foo', [button({ value: 'foo', name: 'bar' })]); + + const result = getElement(content, 'bar'); + + expect(result).toStrictEqual({ + element: button({ value: 'foo', name: 'bar' }), + form: 'foo', + }); + }); +}); + +describe('clickElement', () => { + const rootControllerMessenger = getRootControllerMessenger(); + const controllerMessenger = getRestrictedSnapInterfaceControllerMessenger( + rootControllerMessenger, + ); + + const interfaceController = new SnapInterfaceController({ + messenger: controllerMessenger, + }); + + const handleRpcRequestMock = jest.fn(); + + rootControllerMessenger.registerActionHandler( + 'ExecutionService:handleRpcRequest', + handleRpcRequestMock, + ); + + it('sends a ButtonClickEvent to the snap', async () => { + const content = button({ value: 'foo', name: 'bar' }); + + const interfaceId = await interfaceController.createInterface( + MOCK_SNAP_ID, + content, + ); + + await clickElement( + rootControllerMessenger, + interfaceId, + content, + MOCK_SNAP_ID, + 'bar', + ); + + expect(handleRpcRequestMock).toHaveBeenCalledWith(MOCK_SNAP_ID, { + origin: '', + handler: HandlerType.OnUserInput, + request: { + jsonrpc: '2.0', + method: ' ', + params: { + event: { + type: UserInputEventType.ButtonClickEvent, + name: 'bar', + }, + id: interfaceId, + }, + }, + }); + }); + + it('sends a FormSubmitEvent to the snap', async () => { + const content = form('bar', [ + input({ value: 'foo', name: 'foo' }), + button({ value: 'baz', name: 'baz', buttonType: ButtonType.Submit }), + ]); + + const interfaceId = await interfaceController.createInterface( + MOCK_SNAP_ID, + content, + ); + + await clickElement( + rootControllerMessenger, + interfaceId, + content, + MOCK_SNAP_ID, + 'baz', + ); + + expect(handleRpcRequestMock).toHaveBeenCalledWith(MOCK_SNAP_ID, { + origin: '', + handler: HandlerType.OnUserInput, + request: { + jsonrpc: '2.0', + method: ' ', + params: { + event: { + type: UserInputEventType.ButtonClickEvent, + name: 'baz', + }, + id: interfaceId, + }, + }, + }); + + expect(handleRpcRequestMock).toHaveBeenCalledWith(MOCK_SNAP_ID, { + origin: '', + handler: HandlerType.OnUserInput, + request: { + jsonrpc: '2.0', + method: ' ', + params: { + event: { + type: UserInputEventType.FormSubmitEvent, + name: 'bar', + value: { + foo: 'foo', + }, + }, + id: interfaceId, + }, + }, + }); + }); + + it('throws if there is no button with the given name in the interface', async () => { + const content = button({ value: 'foo', name: 'foo' }); + + const interfaceId = await interfaceController.createInterface( + MOCK_SNAP_ID, + content, + ); + + await expect( + clickElement( + rootControllerMessenger, + interfaceId, + content, + MOCK_SNAP_ID, + 'baz', + ), + ).rejects.toThrow('No button found in the interface.'); + + expect(handleRpcRequestMock).not.toHaveBeenCalled(); + }); + + it('unwraps errors', async () => { + const content = button({ value: 'foo', name: 'foo' }); + + const interfaceId = await interfaceController.createInterface( + MOCK_SNAP_ID, + content, + ); + + handleRpcRequestMock.mockRejectedValue( + new WrappedSnapError(new Error('bar')), + ); + + await expect( + clickElement( + rootControllerMessenger, + interfaceId, + content, + MOCK_SNAP_ID, + 'foo', + ), + ).rejects.toThrow('bar'); + + expect(handleRpcRequestMock).toHaveBeenCalled(); + }); +}); + +describe('mergeValue', () => { + it('merges a value outside of a form', () => { + const state = { foo: 'bar' }; + + const result = mergeValue(state, 'foo', 'baz'); + + expect(result).toStrictEqual({ foo: 'baz' }); + }); + + it('merges a value inside of a form', () => { + const state = { foo: { bar: 'baz' } }; + + const result = mergeValue(state, 'bar', 'test', 'foo'); + + expect(result).toStrictEqual({ foo: { bar: 'test' } }); + }); +}); + +describe('typeInField', () => { + const rootControllerMessenger = getRootControllerMessenger(); + const controllerMessenger = getRestrictedSnapInterfaceControllerMessenger( + rootControllerMessenger, + ); + + const interfaceController = new SnapInterfaceController({ + messenger: controllerMessenger, + }); + + const handleRpcRequestMock = jest.fn(); + + rootControllerMessenger.registerActionHandler( + 'ExecutionService:handleRpcRequest', + handleRpcRequestMock, + ); + it('updates the interface state and sends an InputChangeEvent', async () => { + jest.spyOn(rootControllerMessenger, 'call'); + + const content = input('bar'); + + const interfaceId = await interfaceController.createInterface( + MOCK_SNAP_ID, + content, + ); + + await typeInField( + rootControllerMessenger, + interfaceId, + content, + MOCK_SNAP_ID, + 'bar', + 'baz', + ); + + expect(rootControllerMessenger.call).toHaveBeenCalledWith( + 'SnapInterfaceController:updateInterfaceState', + interfaceId, + { bar: 'baz' }, + ); + + expect(handleRpcRequestMock).toHaveBeenCalledWith(MOCK_SNAP_ID, { + origin: '', + handler: HandlerType.OnUserInput, + request: { + jsonrpc: '2.0', + method: ' ', + params: { + event: { + type: UserInputEventType.InputChangeEvent, + name: 'bar', + value: 'baz', + }, + id: interfaceId, + }, + }, + }); + }); + + it('throws if there is no inputs in the interface', async () => { + const content = text('bar'); + + const interfaceId = await interfaceController.createInterface( + MOCK_SNAP_ID, + content, + ); + + await expect( + typeInField( + rootControllerMessenger, + interfaceId, + content, + MOCK_SNAP_ID, + 'bar', + 'baz', + ), + ).rejects.toThrow('No input found in the interface.'); + }); +}); + describe('getInterface', () => { const rootControllerMessenger = getRootControllerMessenger(); const controllerMessenger = getRestrictedSnapInterfaceControllerMessenger( @@ -170,11 +490,10 @@ describe('getInterface', () => { messenger: controllerMessenger, }); it('returns the current user interface, if any', async () => { - const { store, runSaga } = createStore('password', getMockOptions()); + const { store, runSaga } = createStore(getMockOptions()); - const snapId = 'foo' as SnapId; const content = text('foo'); - const id = await interfaceController.createInterface(snapId, content); + const id = await interfaceController.createInterface(MOCK_SNAP_ID, content); const type = DialogType.Alert; const ui = { type, id }; @@ -183,30 +502,30 @@ describe('getInterface', () => { const result = await runSaga( getInterface, runSaga, - snapId, + MOCK_SNAP_ID, rootControllerMessenger, ).toPromise(); expect(result).toStrictEqual({ type, content, + clickElement: expect.any(Function), + typeInField: expect.any(Function), ok: expect.any(Function), }); }); it('waits for a user interface to be set if none is currently set', async () => { - const { store, runSaga } = createStore('password', getMockOptions()); - - const snapId = 'foo' as SnapId; + const { store, runSaga } = createStore(getMockOptions()); const promise = runSaga( getInterface, runSaga, - snapId, + MOCK_SNAP_ID, rootControllerMessenger, ).toPromise(); const content = text('foo'); - const id = await interfaceController.createInterface(snapId, content); + const id = await interfaceController.createInterface(MOCK_SNAP_ID, content); const type = DialogType.Alert; const ui = { type, id }; store.dispatch(setInterface(ui)); @@ -215,7 +534,92 @@ describe('getInterface', () => { expect(result).toStrictEqual({ type, content, + clickElement: expect.any(Function), + typeInField: expect.any(Function), ok: expect.any(Function), }); }); + + it('sends a request to the snap when `clickElement` is called', async () => { + jest.spyOn(rootControllerMessenger, 'call'); + const { store, runSaga } = createStore(getMockOptions()); + + const content = button({ value: 'foo', name: 'foo' }); + const id = await interfaceController.createInterface(MOCK_SNAP_ID, content); + const type = DialogType.Alert; + const ui = { type, id }; + + store.dispatch(setInterface(ui)); + + const result = await runSaga( + getInterface, + runSaga, + MOCK_SNAP_ID, + rootControllerMessenger, + ).toPromise(); + + await result.clickElement('foo'); + + expect(rootControllerMessenger.call).toHaveBeenCalledWith( + 'ExecutionService:handleRpcRequest', + MOCK_SNAP_ID, + { + origin: '', + handler: HandlerType.OnUserInput, + request: { + jsonrpc: '2.0', + method: ' ', + params: { + event: { + type: UserInputEventType.ButtonClickEvent, + name: 'foo', + }, + id, + }, + }, + }, + ); + }); + + it('sends a request to the snap when `typeInField` is called', async () => { + jest.spyOn(rootControllerMessenger, 'call'); + const { store, runSaga } = createStore(getMockOptions()); + + const content = input('foo'); + const id = await interfaceController.createInterface(MOCK_SNAP_ID, content); + const type = DialogType.Alert; + const ui = { type, id }; + + store.dispatch(setInterface(ui)); + + const result = await runSaga( + getInterface, + runSaga, + MOCK_SNAP_ID, + rootControllerMessenger, + ).toPromise(); + + await result.typeInField('foo', 'bar'); + + expect(rootControllerMessenger.call).toHaveBeenCalledWith( + 'ExecutionService:handleRpcRequest', + MOCK_SNAP_ID, + { + origin: '', + handler: HandlerType.OnUserInput, + request: { + jsonrpc: '2.0', + method: ' ', + params: { + event: { + type: UserInputEventType.InputChangeEvent, + name: 'foo', + value: 'bar', + }, + id, + }, + }, + }, + ); + }); }); diff --git a/packages/snaps-jest/src/internals/simulation/interface.ts b/packages/snaps-jest/src/internals/simulation/interface.ts index aa49baf38d..458593a4f8 100644 --- a/packages/snaps-jest/src/internals/simulation/interface.ts +++ b/packages/snaps-jest/src/internals/simulation/interface.ts @@ -1,10 +1,25 @@ -import type { Component, SnapId } from '@metamask/snaps-sdk'; -import { DialogType } from '@metamask/snaps-sdk'; +import type { + Button, + Component, + FormState, + Input, + InterfaceState, + SnapId, + UserInputEvent, +} from '@metamask/snaps-sdk'; +import { + ButtonType, + DialogType, + NodeType, + UserInputEventType, + assert, +} from '@metamask/snaps-sdk'; +import { HandlerType, hasChildren, unwrapError } from '@metamask/snaps-utils'; import type { PayloadAction } from '@reduxjs/toolkit'; -import type { SagaIterator } from 'redux-saga'; -import { put, select, take } from 'redux-saga/effects'; +import { type SagaIterator } from 'redux-saga'; +import { call, put, select, take } from 'redux-saga/effects'; -import type { SnapInterface } from '../../types'; +import type { SnapInterface, SnapInterfaceActions } from '../../types'; import type { RootControllerMessenger } from './controllers'; import type { Interface, RunSagaFunction } from './store'; import { getCurrentInterface, resolveInterface, setInterface } from './store'; @@ -15,16 +30,19 @@ import { getCurrentInterface, resolveInterface, setInterface } from './store'; * @param runSaga - A function to run a saga outside the usual Redux flow. * @param type - The type of the interface. * @param content - The content to show in the interface. + * @param interfaceActions - The actions to interact with the interface. * @returns The user interface object. */ export function getInterfaceResponse( runSaga: RunSagaFunction, type: DialogType, content: Component, + interfaceActions: SnapInterfaceActions, ): SnapInterface { switch (type) { case DialogType.Alert: return { + ...interfaceActions, type, content, ok: resolveWith(runSaga, null), @@ -32,6 +50,7 @@ export function getInterfaceResponse( case DialogType.Confirmation: return { + ...interfaceActions, type, content, @@ -41,6 +60,7 @@ export function getInterfaceResponse( case DialogType.Prompt: return { + ...interfaceActions, type, content, @@ -100,37 +120,271 @@ function resolveWithInput(runSaga: RunSagaFunction) { } /** - * Get a user interface object from a Snap. + * Get the stored user interface from the store. * - * @param runSaga - A function to run a saga outside the usual Redux flow. - * @param snapId - The Snap ID. * @param controllerMessenger - The controller messenger used to call actions. + * @param snapId - The Snap ID. * @yields Takes the set interface action. * @returns The user interface object. */ -export function* getInterface( - runSaga: RunSagaFunction, - snapId: SnapId, +function* getStoredInterface( controllerMessenger: RootControllerMessenger, -): SagaIterator { + snapId: SnapId, +): SagaIterator { const currentInterface: Interface | null = yield select(getCurrentInterface); + if (currentInterface) { const { content } = controllerMessenger.call( 'SnapInterfaceController:getInterface', snapId, currentInterface.id, ); - return getInterfaceResponse(runSaga, currentInterface.type, content); + + return { ...currentInterface, content }; } const { payload }: PayloadAction = yield take(setInterface.type); - const { type, id } = payload; const { content } = controllerMessenger.call( + 'SnapInterfaceController:getInterface', + snapId, + payload.id, + ); + + return { ...payload, content }; +} + +/** + * Get a Button or an Input from an interface. + * + * @param content - The interface content. + * @param name - The element name. + * @returns An object containing the element and the form name if it's contained in a form, otherwise undefined. + */ +export function getElement( + content: Component, + name: string, +): + | { + element: Button | Input; + form?: string; + } + | undefined { + const { type } = content; + + if ( + (type === NodeType.Button || type === NodeType.Input) && + content.name === name + ) { + return { element: content }; + } + + if (hasChildren(content)) { + for (const element of content.children) { + const result = getElement(element, name); + const form = type === NodeType.Form ? content.name : result?.form; + + if (result) { + return { element: result.element, form }; + } + } + } + + return undefined; +} +/** + * Handle submitting event requests to OnUserInput including unwrapping potential errors. + * + * @param controllerMessenger - The controller messenger used to call actions. + * @param snapId - The Snap ID. + * @param id - The interface ID. + * @param event - The event to submit. + */ +async function handleEvent( + controllerMessenger: RootControllerMessenger, + snapId: SnapId, + id: string, + event: UserInputEvent, +) { + try { + await controllerMessenger.call( + 'ExecutionService:handleRpcRequest', + snapId, + { + origin: '', + handler: HandlerType.OnUserInput, + request: { + jsonrpc: '2.0', + method: ' ', + params: { + event, + id, + }, + }, + }, + ); + } catch (error) { + const [unwrapped] = unwrapError(error); + throw unwrapped; + } +} + +/** + * Click on an element of the Snap interface. + * + * @param controllerMessenger - The controller messenger used to call actions. + * @param id - The interface ID. + * @param content - The interface content. + * @param snapId - The Snap ID. + * @param name - The element name. + */ +export async function clickElement( + controllerMessenger: RootControllerMessenger, + id: string, + content: Component, + snapId: SnapId, + name: string, +): Promise { + const result = getElement(content, name); + assert( + result !== undefined && result.element.type === NodeType.Button, + 'No button found in the interface.', + ); + + // Button click events are always triggered. + await handleEvent(controllerMessenger, snapId, id, { + type: UserInputEventType.ButtonClickEvent, + name: result.element.name, + }); + + if (result.form && result.element.buttonType === ButtonType.Submit) { + const { state } = controllerMessenger.call( + 'SnapInterfaceController:getInterface', + snapId, + id, + ); + + await handleEvent(controllerMessenger, snapId, id, { + type: UserInputEventType.FormSubmitEvent, + name: result.form, + value: state[result.form] as Record, + }); + } +} + +/** + * Merge a value in the interface state. + * + * @param state - The actual interface state. + * @param name - The component name that changed value. + * @param value - The new value. + * @param form - The form name if the element is in one. + * @returns The state with the merged value. + */ +export function mergeValue( + state: InterfaceState, + name: string, + value: string | null, + form?: string, +): InterfaceState { + if (form) { + return { + ...state, + [form]: { + ...(state[form] as FormState), + [name]: value, + }, + }; + } + + return { ...state, [name]: value }; +} + +/** + * Type a value in an interface element. + * + * @param controllerMessenger - The controller messenger used to call actions. + * @param id - The interface ID. + * @param content - The interface Components. + * @param snapId - The Snap ID. + * @param name - The element name. + * @param value - The value to type in the element. + */ +export async function typeInField( + controllerMessenger: RootControllerMessenger, + id: string, + content: Component, + snapId: SnapId, + name: string, + value: string, +) { + const result = getElement(content, name); + + assert( + result !== undefined && result.element.type === NodeType.Input, + 'No input found in the interface.', + ); + + const { state } = controllerMessenger.call( 'SnapInterfaceController:getInterface', snapId, id, ); - return getInterfaceResponse(runSaga, type, content); + const newState = mergeValue(state, name, value, result.form); + + controllerMessenger.call( + 'SnapInterfaceController:updateInterfaceState', + id, + newState, + ); + + await controllerMessenger.call('ExecutionService:handleRpcRequest', snapId, { + origin: '', + handler: HandlerType.OnUserInput, + request: { + jsonrpc: '2.0', + method: ' ', + params: { + event: { + type: UserInputEventType.InputChangeEvent, + name: result.element.name, + value, + }, + id, + }, + }, + }); +} + +/** + * Get a user interface object from a Snap. + * + * @param runSaga - A function to run a saga outside the usual Redux flow. + * @param snapId - The Snap ID. + * @param controllerMessenger - The controller messenger used to call actions. + * @yields Takes the set interface action. + * @returns The user interface object. + */ +export function* getInterface( + runSaga: RunSagaFunction, + snapId: SnapId, + controllerMessenger: RootControllerMessenger, +): SagaIterator { + const { type, id, content } = yield call( + getStoredInterface, + controllerMessenger, + snapId, + ); + + const interfaceActions = { + clickElement: async (name: string) => { + await clickElement(controllerMessenger, id, content, snapId, name); + }, + typeInField: async (name: string, value: string) => { + await typeInField(controllerMessenger, id, content, snapId, name, value); + }, + }; + + return getInterfaceResponse(runSaga, type, content, interfaceActions); } diff --git a/packages/snaps-jest/src/internals/simulation/methods/hooks/encryption.test.ts b/packages/snaps-jest/src/internals/simulation/methods/hooks/encryption.test.ts deleted file mode 100644 index a16bc1f8e7..0000000000 --- a/packages/snaps-jest/src/internals/simulation/methods/hooks/encryption.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { decryptImplementation, encryptImplementation } from './encryption'; - -describe('encryptImplementation', () => { - it('stringifies the value', () => { - expect(encryptImplementation('password', 'value')).toBe( - '{"password":"password","value":"value"}', - ); - }); -}); - -describe('decryptImplementation', () => { - it('parses the value', () => { - expect( - decryptImplementation( - 'password', - '{"password":"password","value":"value"}', - ), - ).toBe('value'); - }); - - it('throws an error if the password is incorrect', () => { - expect(() => - decryptImplementation( - 'incorrect password', - '{"password":"password","value":"value"}', - ), - ).toThrowErrorMatchingInlineSnapshot( - '"Incorrect password. This is a bug in `@metamask/snaps-jest`, please report it."', - ); - }); -}); diff --git a/packages/snaps-jest/src/internals/simulation/methods/hooks/encryption.ts b/packages/snaps-jest/src/internals/simulation/methods/hooks/encryption.ts deleted file mode 100644 index 49bbf8c791..0000000000 --- a/packages/snaps-jest/src/internals/simulation/methods/hooks/encryption.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { Json } from '@metamask/utils'; - -/** - * Mocks encrypting a value using a password. This is not a real encryption - * method, but rather is used to simulate encryption in the tests. - * - * Ideally we would use a real encryption method, such as - * `@metamask/browser-passworder`, but it doesn't seem to work with Node.js 18 - * without some mocking. We can switch to a real encryption method once we - * drop support for Node.js 18. - * - * @param password - The password to use. - * @param value - The value to encrypt. - * @returns The "encrypted" value. - */ -export function encryptImplementation(password: string, value: Json) { - return JSON.stringify({ - password, - value, - }); -} - -/** - * Mocks decrypting a value using a password. This is not a real encryption - * method, but rather is used to simulate encryption in the tests. - * - * Ideally we would use a real decryption method, such as - * `@metamask/browser-passworder`, but it doesn't seem to work with Node.js 18 - * without some mocking. We can switch to a real encryption method once we - * drop support for Node.js 18. - * - * @param password - The password to use. - * @param value - The value to decrypt. - * @returns The "decrypted" value. - */ -export function decryptImplementation(password: string, value: string) { - const decryptedValue = JSON.parse(value); - if (decryptedValue.password !== password) { - throw new Error( - 'Incorrect password. This is a bug in `@metamask/snaps-jest`, please report it.', - ); - } - - return decryptedValue.value; -} diff --git a/packages/snaps-jest/src/internals/simulation/methods/hooks/index.ts b/packages/snaps-jest/src/internals/simulation/methods/hooks/index.ts index fd1bb93883..58c625aa6d 100644 --- a/packages/snaps-jest/src/internals/simulation/methods/hooks/index.ts +++ b/packages/snaps-jest/src/internals/simulation/methods/hooks/index.ts @@ -1,4 +1,3 @@ -export * from './encryption'; export * from './get-locale'; export * from './notifications'; export * from './show-dialog'; diff --git a/packages/snaps-jest/src/internals/simulation/methods/hooks/interface.test.ts b/packages/snaps-jest/src/internals/simulation/methods/hooks/interface.test.ts index e794a7e857..81ed8d2c55 100644 --- a/packages/snaps-jest/src/internals/simulation/methods/hooks/interface.test.ts +++ b/packages/snaps-jest/src/internals/simulation/methods/hooks/interface.test.ts @@ -1,6 +1,6 @@ import { SnapInterfaceController } from '@metamask/snaps-controllers'; -import type { SnapId } from '@metamask/snaps-sdk'; import { text } from '@metamask/snaps-sdk'; +import { MOCK_SNAP_ID } from '@metamask/snaps-utils/test-utils'; import { getRestrictedSnapInterfaceControllerMessenger, @@ -24,16 +24,15 @@ describe('getCreateInterfaceImplementation', () => { const fn = getCreateInterfaceImplementation(controllerMessenger); - const snapId = 'foo' as SnapId; const content = text('bar'); - const id = await fn(snapId, content); + const id = await fn(MOCK_SNAP_ID, content); - const result = interfaceController.getInterface(snapId, id); + const result = interfaceController.getInterface(MOCK_SNAP_ID, id); expect(controllerMessenger.call).toHaveBeenCalledWith( 'SnapInterfaceController:createInterface', - snapId, + MOCK_SNAP_ID, content, ); expect(result.content).toStrictEqual(content); @@ -53,18 +52,17 @@ describe('getGetInterfaceImplementation', () => { const fn = getGetInterfaceImplementation(controllerMessenger); - const snapId = 'foo' as SnapId; const content = text('bar'); - const id = await interfaceController.createInterface(snapId, content); + const id = await interfaceController.createInterface(MOCK_SNAP_ID, content); - const result = fn(snapId, id); + const result = fn(MOCK_SNAP_ID, id); expect(controllerMessenger.call).toHaveBeenCalledWith( 'SnapInterfaceController:getInterface', - snapId, + MOCK_SNAP_ID, id, ); - expect(result).toStrictEqual({ content, state: {}, snapId }); + expect(result).toStrictEqual({ content, state: {}, snapId: MOCK_SNAP_ID }); }); }); diff --git a/packages/snaps-jest/src/internals/simulation/methods/hooks/notifications.test.ts b/packages/snaps-jest/src/internals/simulation/methods/hooks/notifications.test.ts index 088184e8d8..4c6c739596 100644 --- a/packages/snaps-jest/src/internals/simulation/methods/hooks/notifications.test.ts +++ b/packages/snaps-jest/src/internals/simulation/methods/hooks/notifications.test.ts @@ -9,7 +9,7 @@ import { describe('getShowNativeNotificationImplementation', () => { it('returns the implementation of the `showNativeNotification` hook', async () => { - const { store, runSaga } = createStore('password', getMockOptions()); + const { store, runSaga } = createStore(getMockOptions()); const fn = getShowNativeNotificationImplementation(runSaga); expect( @@ -31,7 +31,7 @@ describe('getShowNativeNotificationImplementation', () => { describe('getShowInAppNotificationImplementation', () => { it('returns the implementation of the `showInAppNotification` hook', async () => { - const { store, runSaga } = createStore('password', getMockOptions()); + const { store, runSaga } = createStore(getMockOptions()); const fn = getShowInAppNotificationImplementation(runSaga); expect( diff --git a/packages/snaps-jest/src/internals/simulation/methods/hooks/show-dialog.test.ts b/packages/snaps-jest/src/internals/simulation/methods/hooks/show-dialog.test.ts index 8bb034a504..bf97b80e3f 100644 --- a/packages/snaps-jest/src/internals/simulation/methods/hooks/show-dialog.test.ts +++ b/packages/snaps-jest/src/internals/simulation/methods/hooks/show-dialog.test.ts @@ -7,7 +7,7 @@ import { getShowDialogImplementation } from './show-dialog'; describe('getShowDialogImplementation', () => { it('returns the implementation of the `showDialog` hook', async () => { - const { store, runSaga } = createStore('password', getMockOptions()); + const { store, runSaga } = createStore(getMockOptions()); const fn = getShowDialogImplementation(runSaga); const promise = fn(MOCK_SNAP_ID, DialogType.Alert, 'foo'); diff --git a/packages/snaps-jest/src/internals/simulation/methods/hooks/state.test.ts b/packages/snaps-jest/src/internals/simulation/methods/hooks/state.test.ts index b5e6749da9..7218b242e0 100644 --- a/packages/snaps-jest/src/internals/simulation/methods/hooks/state.test.ts +++ b/packages/snaps-jest/src/internals/simulation/methods/hooks/state.test.ts @@ -10,16 +10,16 @@ import { describe('getGetSnapStateMethodImplementation', () => { it('returns the implementation of the `getSnapState` hook', async () => { - const { store, runSaga } = createStore('password', getMockOptions()); + const { store, runSaga } = createStore(getMockOptions()); const fn = getGetSnapStateMethodImplementation(runSaga); expect(await fn(MOCK_SNAP_ID)).toBeNull(); store.dispatch( setState({ - state: { + state: JSON.stringify({ foo: 'bar', - }, + }), encrypted: true, }), ); @@ -30,16 +30,16 @@ describe('getGetSnapStateMethodImplementation', () => { }); it('returns the implementation of the `getSnapState` hook for unencrypted state', async () => { - const { store, runSaga } = createStore('password', getMockOptions()); + const { store, runSaga } = createStore(getMockOptions()); const fn = getGetSnapStateMethodImplementation(runSaga); expect(await fn(MOCK_SNAP_ID, false)).toBeNull(); store.dispatch( setState({ - state: { + state: JSON.stringify({ foo: 'bar', - }, + }), encrypted: false, }), ); @@ -52,42 +52,46 @@ describe('getGetSnapStateMethodImplementation', () => { describe('getUpdateSnapStateMethodImplementation', () => { it('returns the implementation of the `updateSnapState` hook', async () => { - const { store, runSaga } = createStore('password', getMockOptions()); + const { store, runSaga } = createStore(getMockOptions()); const fn = getUpdateSnapStateMethodImplementation(runSaga); expect(getState(true)(store.getState())).toBeNull(); fn(MOCK_SNAP_ID, { foo: 'bar' }); - expect(getState(true)(store.getState())).toStrictEqual({ - foo: 'bar', - }); + expect(getState(true)(store.getState())).toStrictEqual( + JSON.stringify({ + foo: 'bar', + }), + ); }); it('returns the implementation of the `updateSnapState` hook for unencrypted state', async () => { - const { store, runSaga } = createStore('password', getMockOptions()); + const { store, runSaga } = createStore(getMockOptions()); const fn = getUpdateSnapStateMethodImplementation(runSaga); expect(getState(false)(store.getState())).toBeNull(); fn(MOCK_SNAP_ID, { foo: 'bar' }, false); - expect(getState(false)(store.getState())).toStrictEqual({ - foo: 'bar', - }); + expect(getState(false)(store.getState())).toStrictEqual( + JSON.stringify({ + foo: 'bar', + }), + ); }); }); describe('getClearSnapStateMethodImplementation', () => { it('returns the implementation of the `clearSnapState` hook', async () => { - const { store, runSaga } = createStore('password', getMockOptions()); + const { store, runSaga } = createStore(getMockOptions()); const fn = getClearSnapStateMethodImplementation(runSaga); store.dispatch( setState({ - state: { + state: JSON.stringify({ foo: 'bar', - }, + }), encrypted: true, }), ); @@ -98,14 +102,14 @@ describe('getClearSnapStateMethodImplementation', () => { }); it('returns the implementation of the `clearSnapState` hook for unencrypted state', async () => { - const { store, runSaga } = createStore('password', getMockOptions()); + const { store, runSaga } = createStore(getMockOptions()); const fn = getClearSnapStateMethodImplementation(runSaga); store.dispatch( setState({ - state: { + state: JSON.stringify({ foo: 'bar', - }, + }), encrypted: false, }), ); diff --git a/packages/snaps-jest/src/internals/simulation/methods/hooks/state.ts b/packages/snaps-jest/src/internals/simulation/methods/hooks/state.ts index 81202d264e..60a8d726e4 100644 --- a/packages/snaps-jest/src/internals/simulation/methods/hooks/state.ts +++ b/packages/snaps-jest/src/internals/simulation/methods/hooks/state.ts @@ -1,3 +1,5 @@ +import { parseJson } from '@metamask/snaps-utils'; +import type { Json } from '@metamask/utils'; import type { SagaIterator } from 'redux-saga'; import { put, select } from 'redux-saga/effects'; @@ -18,7 +20,9 @@ function* getSnapStateImplementation( _snapId: string, encrypted = true, ): SagaIterator { - return yield select(getState(encrypted)); + const state = yield select(getState(encrypted)); + // TODO: Use actual decryption implementation + return parseJson(state); } /** @@ -45,10 +49,11 @@ export function getGetSnapStateMethodImplementation(runSaga: RunSagaFunction) { */ function* updateSnapStateImplementation( _snapId: string, - newState: string, + newState: Record, encrypted = true, ): SagaIterator { - yield put(setState({ state: newState, encrypted })); + // TODO: Use actual encryption implementation + yield put(setState({ state: JSON.stringify(newState), encrypted })); } /** diff --git a/packages/snaps-jest/src/internals/simulation/methods/specifications.ts b/packages/snaps-jest/src/internals/simulation/methods/specifications.ts index 6a9e4a580b..fa72d67804 100644 --- a/packages/snaps-jest/src/internals/simulation/methods/specifications.ts +++ b/packages/snaps-jest/src/internals/simulation/methods/specifications.ts @@ -22,8 +22,6 @@ import { getShowDialogImplementation, getShowInAppNotificationImplementation, getShowNativeNotificationImplementation, - encryptImplementation, - decryptImplementation, getCreateInterfaceImplementation, getGetInterfaceImplementation, } from './hooks'; @@ -88,11 +86,6 @@ export function getPermissionSpecifications({ // Shared hooks. ...hooks, - // Encryption and decryption hooks. - // TODO: Swap these out for the real implementations. - encrypt: encryptImplementation, - decrypt: decryptImplementation, - // Snaps-specific hooks. clearSnapState: getClearSnapStateMethodImplementation(runSaga), getLocale: getGetLocaleMethodImplementation(options), diff --git a/packages/snaps-jest/src/internals/simulation/middleware/engine.test.ts b/packages/snaps-jest/src/internals/simulation/middleware/engine.test.ts index 4c656943b9..e55ac0c7d4 100644 --- a/packages/snaps-jest/src/internals/simulation/middleware/engine.test.ts +++ b/packages/snaps-jest/src/internals/simulation/middleware/engine.test.ts @@ -4,7 +4,7 @@ import { createJsonRpcEngine } from './engine'; describe('createJsonRpcEngine', () => { it('creates a JSON-RPC engine', async () => { - const { store } = createStore('password', getMockOptions()); + const { store } = createStore(getMockOptions()); const engine = createJsonRpcEngine({ store, hooks: { diff --git a/packages/snaps-jest/src/internals/simulation/middleware/mock.test.ts b/packages/snaps-jest/src/internals/simulation/middleware/mock.test.ts index a12ce3f294..576c6f4eba 100644 --- a/packages/snaps-jest/src/internals/simulation/middleware/mock.test.ts +++ b/packages/snaps-jest/src/internals/simulation/middleware/mock.test.ts @@ -7,7 +7,7 @@ import { createMockMiddleware } from './mock'; describe('createMockMiddleware', () => { it('mocks a JSON-RPC method', async () => { - const { store } = createStore('password', getMockOptions()); + const { store } = createStore(getMockOptions()); store.dispatch( addJsonRpcMock({ method: 'foo', @@ -32,7 +32,7 @@ describe('createMockMiddleware', () => { }); it('calls the next middleware if no mock is found', async () => { - const { store } = createStore('password', getMockOptions()); + const { store } = createStore(getMockOptions()); const engine = new JsonRpcEngine(); engine.push(createMockMiddleware(store)); diff --git a/packages/snaps-jest/src/internals/simulation/simulation.test.ts b/packages/snaps-jest/src/internals/simulation/simulation.test.ts index c15ea102cf..2e9d1968dc 100644 --- a/packages/snaps-jest/src/internals/simulation/simulation.test.ts +++ b/packages/snaps-jest/src/internals/simulation/simulation.test.ts @@ -4,7 +4,7 @@ import { fetchSnap, NodeThreadExecutionService, SnapInterfaceController, -} from '@metamask/snaps-controllers'; +} from '@metamask/snaps-controllers/node'; import { AuxiliaryFileEncoding, text } from '@metamask/snaps-sdk'; import { VirtualFile } from '@metamask/snaps-utils'; import { getSnapManifest } from '@metamask/snaps-utils/test-utils'; diff --git a/packages/snaps-jest/src/internals/simulation/simulation.ts b/packages/snaps-jest/src/internals/simulation/simulation.ts index 1138451ee5..93cbce907c 100644 --- a/packages/snaps-jest/src/internals/simulation/simulation.ts +++ b/packages/snaps-jest/src/internals/simulation/simulation.ts @@ -11,8 +11,7 @@ import { detectSnapLocation, NodeThreadExecutionService, setupMultiplex, -} from '@metamask/snaps-controllers'; -import { getEncryptionKey } from '@metamask/snaps-rpc-methods'; +} from '@metamask/snaps-controllers/node'; import type { SnapId, AuxiliaryFileEncoding, @@ -149,12 +148,7 @@ export async function handleInstallSnap< const snapFiles = await fetchSnap(snapId, location); // Create Redux store. - const password = await getEncryptionKey({ - mnemonicPhrase: mnemonicPhraseToBytes(options.secretRecoveryPhrase), - snapId, - }); - - const { store, runSaga } = createStore(password, options); + const { store, runSaga } = createStore(options); const controllerMessenger = new ControllerMessenger(); @@ -184,6 +178,8 @@ export async function handleInstallSnap< ...executionServiceOptions, messenger: controllerMessenger.getRestricted({ name: 'ExecutionService', + allowedActions: [], + allowedEvents: [], }), setupSnapProvider: (_snapId: string, rpcStream: Duplex) => { const mux = setupMultiplex(rpcStream, 'snapStream'); diff --git a/packages/snaps-jest/src/internals/simulation/store/store.test.ts b/packages/snaps-jest/src/internals/simulation/store/store.test.ts index 0fe6fa3d94..974b77bdea 100644 --- a/packages/snaps-jest/src/internals/simulation/store/store.test.ts +++ b/packages/snaps-jest/src/internals/simulation/store/store.test.ts @@ -3,7 +3,7 @@ import { createStore } from './store'; describe('createStore', () => { it('creates a Redux store', () => { - const { store } = createStore('password', getMockOptions()); + const { store } = createStore(getMockOptions()); expect(store).toBeDefined(); expect(store.getState()).toMatchInlineSnapshot(` @@ -27,7 +27,6 @@ describe('createStore', () => { it('creates a Redux store with initial state', () => { const { store } = createStore( - 'password', getMockOptions({ state: { foo: 'bar', @@ -45,7 +44,7 @@ describe('createStore', () => { "notifications": [], }, "state": { - "encrypted": "{"password":"password","value":{"foo":"bar"}}", + "encrypted": "{"foo":"bar"}", "unencrypted": null, }, "ui": { @@ -57,7 +56,6 @@ describe('createStore', () => { it('creates a Redux store with initial unencrypted state', () => { const { store } = createStore( - 'password', getMockOptions({ unencryptedState: { foo: 'bar', diff --git a/packages/snaps-jest/src/internals/simulation/store/store.ts b/packages/snaps-jest/src/internals/simulation/store/store.ts index 8212118941..20d3780d9a 100644 --- a/packages/snaps-jest/src/internals/simulation/store/store.ts +++ b/packages/snaps-jest/src/internals/simulation/store/store.ts @@ -10,16 +10,12 @@ import { uiSlice } from './ui'; /** * Create a Redux store. * - * @param password - The password to use for state encryption. * @param options - The simulation options. * @param options.state - The initial state for the Snap. * @param options.unencryptedState - The initial unencrypted state for the Snap. * @returns A Redux store with the default state. */ -export function createStore( - password: string, - { state, unencryptedState }: SimulationOptions, -) { +export function createStore({ state, unencryptedState }: SimulationOptions) { const sagaMiddleware = createSagaMiddleware(); const store = configureStore({ reducer: { @@ -36,10 +32,7 @@ export function createStore( if (state) { store.dispatch( setState({ - state: JSON.stringify({ - password, - value: state, - }), + state: JSON.stringify(state), encrypted: true, }), ); diff --git a/packages/snaps-jest/src/internals/structs.test.ts b/packages/snaps-jest/src/internals/structs.test.ts index 700d0736a2..f27d6720ea 100644 --- a/packages/snaps-jest/src/internals/structs.test.ts +++ b/packages/snaps-jest/src/internals/structs.test.ts @@ -7,6 +7,8 @@ import { SignatureOptionsStruct, SnapOptionsStruct, SnapResponseStruct, + SnapResponseWithInterfaceStruct, + SnapResponseWithoutInterfaceStruct, TransactionOptionsStruct, } from './structs'; @@ -202,7 +204,7 @@ describe('InterfaceStruct', () => { }); }); -describe('SnapResponseStruct', () => { +describe('SnapResponseWithInterfaceStruct', () => { it('accepts a valid object', () => { const options = create( { @@ -217,8 +219,9 @@ describe('SnapResponseStruct', () => { message: 'Hello, world!', }, ], + getInterface: () => undefined, }, - SnapResponseStruct, + SnapResponseWithInterfaceStruct, ); expect(options).toStrictEqual({ @@ -233,6 +236,120 @@ describe('SnapResponseStruct', () => { message: 'Hello, world!', }, ], + getInterface: expect.any(Function), + }); + }); + + it.each(INVALID_VALUES)('throws for invalid value: %p', (value) => { + // eslint-disable-next-line jest/require-to-throw-message + expect(() => create(value, SnapResponseWithInterfaceStruct)).toThrow(); + }); +}); + +describe('SnapResponseWithoutInterfaceStruct', () => { + it('accepts a valid object', () => { + const options = create( + { + id: '1', + response: { + result: '0x1', + }, + notifications: [ + { + id: '1', + type: 'native', + message: 'Hello, world!', + }, + ], + }, + SnapResponseWithoutInterfaceStruct, + ); + + expect(options).toStrictEqual({ + id: '1', + response: { + result: '0x1', + }, + notifications: [ + { + id: '1', + type: 'native', + message: 'Hello, world!', + }, + ], + }); + }); + + it.each(INVALID_VALUES)('throws for invalid value: %p', (value) => { + // eslint-disable-next-line jest/require-to-throw-message + expect(() => create(value, SnapResponseWithoutInterfaceStruct)).toThrow(); + }); +}); + +describe('SnapResponseStruct', () => { + it('accepts a valid object', () => { + const optionsWithInterface = create( + { + id: '1', + response: { + result: '0x1', + }, + notifications: [ + { + id: '1', + type: 'native', + message: 'Hello, world!', + }, + ], + getInterface: () => undefined, + }, + SnapResponseStruct, + ); + + expect(optionsWithInterface).toStrictEqual({ + id: '1', + response: { + result: '0x1', + }, + notifications: [ + { + id: '1', + type: 'native', + message: 'Hello, world!', + }, + ], + getInterface: expect.any(Function), + }); + + const optionsWithoutInterface = create( + { + id: '1', + response: { + result: '0x1', + }, + notifications: [ + { + id: '1', + type: 'native', + message: 'Hello, world!', + }, + ], + }, + SnapResponseStruct, + ); + + expect(optionsWithoutInterface).toStrictEqual({ + id: '1', + response: { + result: '0x1', + }, + notifications: [ + { + id: '1', + type: 'native', + message: 'Hello, world!', + }, + ], }); }); diff --git a/packages/snaps-jest/src/internals/structs.ts b/packages/snaps-jest/src/internals/structs.ts index 61a0fa37df..a1dacb2754 100644 --- a/packages/snaps-jest/src/internals/structs.ts +++ b/packages/snaps-jest/src/internals/structs.ts @@ -1,6 +1,6 @@ import { - NotificationType, ComponentStruct, + NotificationType, enumValue, } from '@metamask/snaps-sdk'; import { @@ -22,10 +22,11 @@ import { object, optional, string, - type, union, record, any, + func, + type, } from 'superstruct'; // TODO: Export this from `@metamask/utils` instead. @@ -207,29 +208,38 @@ export const InterfaceStruct = type({ content: optional(ComponentStruct), }); -export const SnapResponseStruct = assign( - InterfaceStruct, - object({ - id: string(), - - response: union([ - object({ - result: JsonStruct, - }), - object({ - error: JsonStruct, - }), - ]), +export const SnapResponseWithoutInterfaceStruct = object({ + id: string(), + + response: union([ + object({ + result: JsonStruct, + }), + object({ + error: JsonStruct, + }), + ]), + + notifications: array( + object({ + id: string(), + message: string(), + type: union([ + enumValue(NotificationType.InApp), + enumValue(NotificationType.Native), + ]), + }), + ), +}); - notifications: array( - object({ - id: string(), - message: string(), - type: union([ - enumValue(NotificationType.InApp), - enumValue(NotificationType.Native), - ]), - }), - ), +export const SnapResponseWithInterfaceStruct = assign( + SnapResponseWithoutInterfaceStruct, + object({ + getInterface: func(), }), ); + +export const SnapResponseStruct = union([ + SnapResponseWithoutInterfaceStruct, + SnapResponseWithInterfaceStruct, +]); diff --git a/packages/snaps-jest/src/matchers.test.ts b/packages/snaps-jest/src/matchers.test.ts index 9fa035bbb9..618ba158a7 100644 --- a/packages/snaps-jest/src/matchers.test.ts +++ b/packages/snaps-jest/src/matchers.test.ts @@ -7,7 +7,7 @@ import { toRespondWithError, toSendNotification, } from './matchers'; -import { getMockResponse } from './test-utils'; +import { getMockInterfaceResponse, getMockResponse } from './test-utils'; expect.extend({ toRespondWith, @@ -284,30 +284,26 @@ describe('toSendNotification', () => { describe('toRender', () => { it('passes when the component is correct', () => { - expect( - getMockResponse({ - content: panel([text('Hello, world!')]), - }), - ).toRender(panel([text('Hello, world!')])); + expect(getMockInterfaceResponse(panel([text('Hello, world!')]))).toRender( + panel([text('Hello, world!')]), + ); }); it('fails when the component is incorrect', () => { expect(() => - expect( - getMockResponse({ - content: panel([text('Hello, world!')]), - }), - ).toRender(panel([text('Hello, world?')])), + expect(getMockInterfaceResponse(panel([text('Hello, world!')]))).toRender( + panel([text('Hello, world?')]), + ), ).toThrow('Received:'); }); it('fails when the component is missing', () => { expect(() => expect( - getMockResponse({ + getMockInterfaceResponse( // @ts-expect-error - Invalid response. - content: null, - }), + null, + ), ).not.toRender(panel([text('Hello, world!')])), ).toThrow('Received has type:'); }); @@ -315,18 +311,14 @@ describe('toRender', () => { describe('not', () => { it('passes when the component is correct', () => { expect( - getMockResponse({ - content: panel([text('Hello, world!')]), - }), + getMockInterfaceResponse(panel([text('Hello, world!')])), ).not.toRender(panel([text('Hello, world?')])); }); it('fails when the component is incorrect', () => { expect(() => expect( - getMockResponse({ - content: panel([text('Hello, world!')]), - }), + getMockInterfaceResponse(panel([text('Hello, world!')])), ).not.toRender(panel([text('Hello, world!')])), ).toThrow('Received:'); }); diff --git a/packages/snaps-jest/src/test-utils/controller.ts b/packages/snaps-jest/src/test-utils/controller.ts index c4661f0324..8fe8d0fbe8 100644 --- a/packages/snaps-jest/src/test-utils/controller.ts +++ b/packages/snaps-jest/src/test-utils/controller.ts @@ -19,6 +19,11 @@ export const getRootControllerMessenger = (mocked = true) => { result: false, type: 'all', })); + + messenger.registerActionHandler( + 'ExecutionService:handleRpcRequest', + jest.fn(), + ); } return messenger; @@ -39,6 +44,7 @@ export const getRestrictedSnapInterfaceControllerMessenger = ( 'PhishingController:testOrigin', 'PhishingController:maybeUpdateState', ], + allowedEvents: [], }); return snapInterfaceControllerMessenger; diff --git a/packages/snaps-jest/src/test-utils/response.ts b/packages/snaps-jest/src/test-utils/response.ts index 9c79ef50d2..4f63bdff96 100644 --- a/packages/snaps-jest/src/test-utils/response.ts +++ b/packages/snaps-jest/src/test-utils/response.ts @@ -1,4 +1,6 @@ -import type { SnapResponse } from '../types'; +import type { Component } from '@metamask/snaps-sdk'; + +import type { SnapHandlerInterface, SnapResponse } from '../types'; /** * Get a mock response. @@ -7,7 +9,6 @@ import type { SnapResponse } from '../types'; * @param options.id - The ID to use. * @param options.response - The response to use. * @param options.notifications - The notifications to use. - * @param options.content - The content to use. * @returns The mock response. */ export function getMockResponse({ @@ -16,12 +17,26 @@ export function getMockResponse({ result: 'foo', }, notifications = [], - content = undefined, }: Partial): SnapResponse { return { id, response, notifications, + }; +} + +/** + * Get a mock handler interface. + * + * @param content - The content to use. + * @returns The mock handler interface. + */ +export function getMockInterfaceResponse( + content: Component, +): SnapHandlerInterface { + return { content, + clickElement: jest.fn(), + typeInField: jest.fn(), }; } diff --git a/packages/snaps-jest/src/test-utils/snap/snap.js b/packages/snaps-jest/src/test-utils/snap/snap.js index 58c7e2af9a..a033fc24c7 100644 --- a/packages/snaps-jest/src/test-utils/snap/snap.js +++ b/packages/snaps-jest/src/test-utils/snap/snap.js @@ -1,2 +1,4 @@ // eslint-disable-next-line no-console console.log('Hello, world!'); + +module.exports.onRpcRequest = () => null; diff --git a/packages/snaps-jest/src/test-utils/snap/snap.manifest.json b/packages/snaps-jest/src/test-utils/snap/snap.manifest.json index d329dd36eb..e2dc9f902b 100644 --- a/packages/snaps-jest/src/test-utils/snap/snap.manifest.json +++ b/packages/snaps-jest/src/test-utils/snap/snap.manifest.json @@ -3,7 +3,7 @@ "description": "baz", "version": "1.0.0", "source": { - "shasum": "D3ANeNZ7C1Ynx0GTP07afj72Jq06Srlq49QZkhICY+E=", + "shasum": "uaLwMO39qzKbshqPM6W2Ju7gkO/czuwgNKpjzXRXJj0=", "location": { "npm": { "filePath": "snap.js", diff --git a/packages/snaps-jest/src/types.ts b/packages/snaps-jest/src/types.ts index cbff45d265..b769772121 100644 --- a/packages/snaps-jest/src/types.ts +++ b/packages/snaps-jest/src/types.ts @@ -1,8 +1,9 @@ import type { + Component, NotificationType, EnumToUnion, - Component, } from '@metamask/snaps-sdk'; +import type { InferMatching } from '@metamask/snaps-utils'; import type { Json, JsonRpcId, JsonRpcParams } from '@metamask/utils'; import type { Infer } from 'superstruct'; @@ -13,34 +14,6 @@ import type { TransactionOptionsStruct, } from './internals'; -/* eslint-disable @typescript-eslint/consistent-type-definitions */ -declare module 'expect' { - interface AsymmetricMatchers { - toRespondWith(response: unknown): void; - toRespondWithError(error: unknown): void; - toSendNotification( - message: string, - type?: EnumToUnion, - ): void; - toRender(component: Component): void; - } - - // Ideally we would use `Matchers` instead of `Matchers`, but - // TypeScript doesn't allow this: - // TS2428: All declarations of 'Matchers' must have identical type parameters. - // eslint-disable-next-line @typescript-eslint/naming-convention - interface Matchers { - toRespondWith(response: unknown): R; - toRespondWithError(error: unknown): R; - toSendNotification( - message: string, - type?: EnumToUnion, - ): R; - toRender(component: Component): R; - } -} -/* eslint-enable @typescript-eslint/consistent-type-definitions */ - export type RequestOptions = { /** * The JSON-RPC request ID. @@ -111,6 +84,23 @@ export type SignatureOptions = Infer; */ export type SnapOptions = Infer; +export type SnapInterfaceActions = { + /** + * Click on an interface element. + * + * @param name - The element name to click. + */ + clickElement(name: string): Promise; + + /** + * Type a value in a interface field. + * + * @param name - The element name to type in. + * @param value - The value to type. + */ + typeInField(name: string, value: string): Promise; +}; + /** * A `snap_dialog` alert interface. */ @@ -183,10 +173,12 @@ export type SnapPromptInterface = { cancel(): Promise; }; -export type SnapInterface = +export type SnapInterface = ( | SnapAlertInterface | SnapConfirmationInterface - | SnapPromptInterface; + | SnapPromptInterface +) & + SnapInterfaceActions; export type SnapRequestObject = { /** @@ -255,7 +247,7 @@ export type Snap = { */ onTransaction( transaction?: Partial, - ): Promise; + ): Promise; /** * Send a transaction to the snap. @@ -268,7 +260,7 @@ export type Snap = { */ sendTransaction( transaction?: Partial, - ): Promise; + ): Promise; /** * Send a signature request to the snap. @@ -278,7 +270,9 @@ export type Snap = { * Any missing fields will be filled in with default values. * @returns The response. */ - onSignature(signature?: Partial): Promise; + onSignature( + signature?: Partial, + ): Promise; /** * Run a cronjob in the snap. This is similar to {@link request}, but the @@ -308,7 +302,7 @@ export type Snap = { * * @returns The response. */ - onHomePage(): Promise; + onHomePage(): Promise; /** * Mock a JSON-RPC request. This will cause the snap to respond with the @@ -347,4 +341,31 @@ export type Snap = { close(): Promise; }; -export type SnapResponse = Infer; +export type SnapHandlerInterface = { + content: Component; +} & SnapInterfaceActions; + +export type SnapResponseWithInterface = { + id: string; + response: { result: Json } | { error: Json }; + notifications: { + id: string; + message: string; + type: EnumToUnion; + }[]; + getInterface(): SnapHandlerInterface; +}; + +export type SnapResponseWithoutInterface = Omit< + SnapResponseWithInterface, + 'getInterface' +>; + +export type SnapResponseType = + | SnapResponseWithoutInterface + | SnapResponseWithInterface; + +export type SnapResponse = InferMatching< + typeof SnapResponseStruct, + SnapResponseType +>; diff --git a/packages/snaps-jest/tsconfig.json b/packages/snaps-jest/tsconfig.json index 81d407e968..8fafac73fc 100644 --- a/packages/snaps-jest/tsconfig.json +++ b/packages/snaps-jest/tsconfig.json @@ -4,7 +4,7 @@ "baseUrl": "./", "jsx": "preserve" }, - "include": ["./src"], + "include": ["./src", "package.json", "tsup.config.ts"], "references": [ { "path": "../snaps-utils" diff --git a/packages/snaps-jest/tsup.config.ts b/packages/snaps-jest/tsup.config.ts new file mode 100644 index 0000000000..3eaf645296 --- /dev/null +++ b/packages/snaps-jest/tsup.config.ts @@ -0,0 +1,14 @@ +import deepmerge from 'deepmerge'; +import type { Options } from 'tsup'; + +import packageJson from './package.json'; + +// `tsup.config.ts` is not under `rootDir`, so we need to use `require` instead. +// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires +const { default: baseConfig } = require('../../tsup.config'); + +const config: Options = { + name: packageJson.name, +}; + +export default deepmerge(baseConfig, config); diff --git a/packages/snaps-rollup-plugin/CHANGELOG.md b/packages/snaps-rollup-plugin/CHANGELOG.md index de937d2d6f..4adf7529d8 100644 --- a/packages/snaps-rollup-plugin/CHANGELOG.md +++ b/packages/snaps-rollup-plugin/CHANGELOG.md @@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [4.0.1] +### Fixed +- Fix minor build configuration problems ([#2220](https://github.com/MetaMask/snaps/pull/2220)) + +## [4.0.0] +### Changed +- **BREAKING:** Update ESM build to be fully compliant with the ESM standard ([#2210](https://github.com/MetaMask/snaps/pull/2210)) + +## [3.0.2] +### Changed +- Bump several MetaMask dependencies ([#2054](https://github.com/MetaMask/snaps/pull/2054), [#2100](https://github.com/MetaMask/snaps/pull/2100), [#2105](https://github.com/MetaMask/snaps/pull/2105), [#2173](https://github.com/MetaMask/snaps/pull/2173)) + ## [3.0.1] ### Changed - Update multiple MetaMask dependencies ([#1841](https://github.com/MetaMask/snaps/pull/1841)) @@ -28,7 +40,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-rollup-plugin@3.0.1...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-rollup-plugin@4.0.1...HEAD +[4.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-rollup-plugin@4.0.0...@metamask/snaps-rollup-plugin@4.0.1 +[4.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-rollup-plugin@3.0.2...@metamask/snaps-rollup-plugin@4.0.0 +[3.0.2]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-rollup-plugin@3.0.1...@metamask/snaps-rollup-plugin@3.0.2 [3.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-rollup-plugin@3.0.0...@metamask/snaps-rollup-plugin@3.0.1 [3.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-rollup-plugin@2.0.0...@metamask/snaps-rollup-plugin@3.0.0 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-rollup-plugin@0.37.3-flask.1...@metamask/snaps-rollup-plugin@2.0.0 diff --git a/packages/snaps-rollup-plugin/package.json b/packages/snaps-rollup-plugin/package.json index a4c2d21db0..6015586907 100644 --- a/packages/snaps-rollup-plugin/package.json +++ b/packages/snaps-rollup-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/snaps-rollup-plugin", - "version": "3.0.1", + "version": "4.0.1", "keywords": [ "rollup", "rollup-plugin" @@ -10,13 +10,19 @@ "url": "https://github.com/MetaMask/snaps.git" }, "sideEffects": false, - "main": "./dist/cjs/index.js", - "module": "./dist/esm/index.js", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/types/index.d.ts" + }, + "./package.json": "./package.json" + }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", "types": "./dist/types/index.d.ts", "files": [ - "dist/cjs/**", - "dist/esm/**", - "dist/types/**" + "dist" ], "scripts": { "test": "jest && yarn posttest", @@ -27,29 +33,25 @@ "lint": "yarn lint:eslint && yarn lint:misc --check && yarn lint:changelog && yarn lint:dependencies", "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write", "lint:changelog": "../../scripts/validate-changelog.sh @metamask/snaps-rollup-plugin", - "build": "yarn build:source && yarn build:types", - "build:source": "yarn build:esm && yarn build:cjs", + "build": "tsup --clean && yarn build:types", "build:types": "tsc --project tsconfig.build.json", - "build:esm": "swc src --out-dir dist/esm --config-file ../../.swcrc.build.json --config module.type=es6", - "build:cjs": "swc src --out-dir dist/cjs --config-file ../../.swcrc.build.json --config module.type=commonjs", - "build:clean": "yarn clean && yarn build", "clean": "rimraf '*.tsbuildinfo' 'dist'", "publish:preview": "yarn npm publish --tag preview", "lint:ci": "yarn lint", - "lint:dependencies": "depcheck" + "lint:dependencies": "depcheck", + "build:ci": "tsup --clean" }, "dependencies": { "@metamask/snaps-utils": "workspace:^" }, "devDependencies": { - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", "@metamask/eslint-config-nodejs": "^12.1.0", "@metamask/eslint-config-typescript": "^12.1.0", "@rollup/plugin-virtual": "^2.1.0", - "@swc/cli": "^0.1.62", "@swc/core": "1.3.78", "@swc/jest": "^0.2.26", "@types/jest": "^27.5.1", @@ -72,6 +74,7 @@ "prettier-plugin-packagejson": "^2.2.11", "rimraf": "^4.1.2", "rollup": "^2.73.0", + "tsup": "^8.0.1", "typescript": "~4.8.4" }, "engines": { diff --git a/packages/snaps-rollup-plugin/src/plugin.test.ts b/packages/snaps-rollup-plugin/src/plugin.test.ts index 451bbc2967..df1ff892c8 100644 --- a/packages/snaps-rollup-plugin/src/plugin.test.ts +++ b/packages/snaps-rollup-plugin/src/plugin.test.ts @@ -2,7 +2,7 @@ import { checkManifest, evalBundle, PostProcessWarning, -} from '@metamask/snaps-utils'; +} from '@metamask/snaps-utils/node'; import { DEFAULT_SNAP_BUNDLE, getSnapManifest, @@ -19,8 +19,8 @@ import snaps from './plugin'; jest.mock('fs'); -jest.mock('@metamask/snaps-utils', () => ({ - ...jest.requireActual('@metamask/snaps-utils'), +jest.mock('@metamask/snaps-utils/node', () => ({ + ...jest.requireActual('@metamask/snaps-utils/node'), evalBundle: jest.fn(), checkManifest: jest.fn(), })); diff --git a/packages/snaps-rollup-plugin/src/plugin.ts b/packages/snaps-rollup-plugin/src/plugin.ts index cd8d4fbaaf..a86a6e2c51 100644 --- a/packages/snaps-rollup-plugin/src/plugin.ts +++ b/packages/snaps-rollup-plugin/src/plugin.ts @@ -1,9 +1,9 @@ -import type { PostProcessOptions } from '@metamask/snaps-utils'; +import type { PostProcessOptions } from '@metamask/snaps-utils/node'; import { checkManifest, evalBundle, postProcessBundle, -} from '@metamask/snaps-utils'; +} from '@metamask/snaps-utils/node'; import { promises as fs } from 'fs'; import pathUtils from 'path'; // eslint-disable-next-line @typescript-eslint/no-shadow diff --git a/packages/snaps-rollup-plugin/tsconfig.json b/packages/snaps-rollup-plugin/tsconfig.json index a4f2b5a12e..dc42b0cdc0 100644 --- a/packages/snaps-rollup-plugin/tsconfig.json +++ b/packages/snaps-rollup-plugin/tsconfig.json @@ -3,6 +3,6 @@ "compilerOptions": { "baseUrl": "./" }, - "include": ["./src"], + "include": ["./src", "package.json", "tsup.config.ts"], "references": [{ "path": "../snaps-utils" }] } diff --git a/packages/snaps-rollup-plugin/tsup.config.ts b/packages/snaps-rollup-plugin/tsup.config.ts new file mode 100644 index 0000000000..3eaf645296 --- /dev/null +++ b/packages/snaps-rollup-plugin/tsup.config.ts @@ -0,0 +1,14 @@ +import deepmerge from 'deepmerge'; +import type { Options } from 'tsup'; + +import packageJson from './package.json'; + +// `tsup.config.ts` is not under `rootDir`, so we need to use `require` instead. +// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires +const { default: baseConfig } = require('../../tsup.config'); + +const config: Options = { + name: packageJson.name, +}; + +export default deepmerge(baseConfig, config); diff --git a/packages/snaps-rpc-methods/CHANGELOG.md b/packages/snaps-rpc-methods/CHANGELOG.md index 481a958067..30164db4b9 100644 --- a/packages/snaps-rpc-methods/CHANGELOG.md +++ b/packages/snaps-rpc-methods/CHANGELOG.md @@ -6,13 +6,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [8.0.0] +### Changed +- **BREAKING:** Refactor to support changes to encryption ([#2316](https://github.com/MetaMask/snaps/pull/2316)) + - No longer expects `encrypt` or `decrypt`, instead expects `updateSnapState` and `getSnapState` to be asynchronous + +## [7.0.2] +### Changed +- Bump MetaMask dependencies ([#2270](https://github.com/MetaMask/snaps/pull/2270)) +- Bump @metamask/json-rpc-engine from 7.3.2 to 7.3.3 ([#2247](https://github.com/MetaMask/snaps/pull/2247)) + +## [7.0.1] +### Fixed +- Fix minor build configuration problems ([#2220](https://github.com/MetaMask/snaps/pull/2220)) + +## [7.0.0] +### Changed +- **BREAKING:** Update ESM build to be fully compliant with the ESM standard ([#2210](https://github.com/MetaMask/snaps/pull/2210)) +- Bump `@metamask/rpc-errors` to `^6.2.1` ([#2209](https://github.com/MetaMask/snaps/pull/2209)) + ## [6.0.0] ### Added - **BREAKING:** Add support for dynamic user interfaces ([#1465](https://github.com/MetaMask/snaps/pull/1465), [#2144](https://github.com/MetaMask/snaps/pull/2144), [#2143](https://github.com/MetaMask/snaps/pull/2143)) - This adds the `snap_createInterface`, `snap_updateInterface`, and `snap_getInterfaceState` methods. - This is breaking because it changes the expected type of the `showDialog` RPC method hook. - **BREAKING:** Update the permission format for the name lookup endowment ([#2113](https://github.com/MetaMask/snaps/pull/2113)) - - The new format is documented in [SIP-12](https://metamask.github.io/SIPs/SIPS/sip-12). + - The new format is documented in [SIP-12](https://metamask.github.io/SIPs/SIPS/sip-12). - Add endowment permission specifications to this package ([#2155](https://github.com/MetaMask/snaps/pull/2155)) ### Changed @@ -111,7 +130,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-rpc-methods@6.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-rpc-methods@8.0.0...HEAD +[8.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-rpc-methods@7.0.2...@metamask/snaps-rpc-methods@8.0.0 +[7.0.2]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-rpc-methods@7.0.1...@metamask/snaps-rpc-methods@7.0.2 +[7.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-rpc-methods@7.0.0...@metamask/snaps-rpc-methods@7.0.1 +[7.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-rpc-methods@6.0.0...@metamask/snaps-rpc-methods@7.0.0 [6.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-rpc-methods@5.0.0...@metamask/snaps-rpc-methods@6.0.0 [5.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-rpc-methods@4.1.0...@metamask/snaps-rpc-methods@5.0.0 [4.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-rpc-methods@4.0.3...@metamask/snaps-rpc-methods@4.1.0 diff --git a/packages/snaps-rpc-methods/jest.config.js b/packages/snaps-rpc-methods/jest.config.js index e092fb58df..a00a3ec4a2 100644 --- a/packages/snaps-rpc-methods/jest.config.js +++ b/packages/snaps-rpc-methods/jest.config.js @@ -10,10 +10,10 @@ module.exports = deepmerge(baseConfig, { ], coverageThreshold: { global: { - branches: 91.42, - functions: 97, - lines: 97.53, - statements: 97, + branches: 91.21, + functions: 96.96, + lines: 97.51, + statements: 96.97, }, }, }); diff --git a/packages/snaps-rpc-methods/package.json b/packages/snaps-rpc-methods/package.json index afb294a778..e49c5aa081 100644 --- a/packages/snaps-rpc-methods/package.json +++ b/packages/snaps-rpc-methods/package.json @@ -1,19 +1,25 @@ { "name": "@metamask/snaps-rpc-methods", - "version": "6.0.0", + "version": "8.0.0", "description": "MetaMask Snaps JSON-RPC method implementations.", "repository": { "type": "git", "url": "https://github.com/MetaMask/snaps.git" }, "sideEffects": false, - "main": "./dist/cjs/index.js", - "module": "./dist/esm/index.js", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/types/index.d.ts" + }, + "./package.json": "./package.json" + }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", "types": "./dist/types/index.d.ts", "files": [ - "dist/cjs/**", - "dist/esm/**", - "dist/types/**" + "dist" ], "scripts": { "test": "jest && yarn posttest", @@ -24,21 +30,18 @@ "lint": "yarn lint:eslint && yarn lint:misc --check && yarn lint:changelog && yarn lint:dependencies", "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write", "lint:changelog": "../../scripts/validate-changelog.sh @metamask/snaps-rpc-methods", - "build": "yarn build:source && yarn build:types", - "build:source": "yarn build:esm && yarn build:cjs", + "build": "tsup --clean && yarn build:types", "build:types": "tsc --project tsconfig.build.json", - "build:esm": "swc src --out-dir dist/esm --config-file ../../.swcrc.build.json --config module.type=es6", - "build:cjs": "swc src --out-dir dist/cjs --config-file ../../.swcrc.build.json --config module.type=commonjs", - "build:clean": "yarn clean && yarn build", "clean": "rimraf '*.tsbuildinfo' 'dist'", "publish:preview": "yarn npm publish --tag preview", "lint:ci": "yarn lint", - "lint:dependencies": "depcheck" + "lint:dependencies": "depcheck", + "build:ci": "tsup --clean" }, "dependencies": { "@metamask/key-tree": "^9.0.0", - "@metamask/permission-controller": "^8.0.0", - "@metamask/rpc-errors": "^6.1.0", + "@metamask/permission-controller": "^9.0.2", + "@metamask/rpc-errors": "^6.2.1", "@metamask/snaps-sdk": "workspace:^", "@metamask/snaps-utils": "workspace:^", "@metamask/utils": "^8.3.0", @@ -46,15 +49,13 @@ "superstruct": "^1.0.3" }, "devDependencies": { - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", - "@metamask/browser-passworder": "^4.3.0", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", "@metamask/eslint-config-nodejs": "^12.1.0", "@metamask/eslint-config-typescript": "^12.1.0", - "@metamask/json-rpc-engine": "^7.3.2", - "@swc/cli": "^0.1.62", + "@metamask/json-rpc-engine": "^8.0.1", "@swc/core": "1.3.78", "@swc/jest": "^0.2.26", "@types/node": "18.14.2", @@ -75,6 +76,7 @@ "prettier": "^2.7.1", "prettier-plugin-packagejson": "^2.2.11", "rimraf": "^4.1.2", + "tsup": "^8.0.1", "typescript": "~4.8.4" }, "engines": { diff --git a/packages/snaps-rpc-methods/src/restricted/index.ts b/packages/snaps-rpc-methods/src/restricted/index.ts index 722053db37..2805b879fc 100644 --- a/packages/snaps-rpc-methods/src/restricted/index.ts +++ b/packages/snaps-rpc-methods/src/restricted/index.ts @@ -20,7 +20,7 @@ import type { NotifyMethodHooks } from './notify'; import { notifyBuilder } from './notify'; export { WALLET_SNAP_PERMISSION_KEY } from './invokeSnap'; -export { getEncryptionKey } from './manageState'; +export { getEncryptionEntropy } from './manageState'; export type RestrictedMethodHooks = DialogMethodHooks & GetBip32EntropyMethodHooks & diff --git a/packages/snaps-rpc-methods/src/restricted/manageState.test.ts b/packages/snaps-rpc-methods/src/restricted/manageState.test.ts index 3bdf8548f0..8d0ab5f31e 100644 --- a/packages/snaps-rpc-methods/src/restricted/manageState.test.ts +++ b/packages/snaps-rpc-methods/src/restricted/manageState.test.ts @@ -1,43 +1,25 @@ -import { decrypt, encrypt } from '@metamask/browser-passworder'; import { PermissionType, SubjectType } from '@metamask/permission-controller'; -import { rpcErrors } from '@metamask/rpc-errors'; import { ManageStateOperation } from '@metamask/snaps-sdk'; import { - MOCK_LOCAL_SNAP_ID, MOCK_SNAP_ID, TEST_SECRET_RECOVERY_PHRASE_BYTES, } from '@metamask/snaps-utils/test-utils'; import { webcrypto } from 'crypto'; import { - getEncryptionKey, + getEncryptionEntropy, getManageStateImplementation, getValidatedParams, specificationBuilder, } from './manageState'; -globalThis.crypto ??= webcrypto as typeof globalThis.crypto; -globalThis.crypto.getRandomValues = ( - array: Type, -) => { - if (array === null) { - return null as Type; - } - - return new Uint8Array(array.buffer).fill(0) as unknown as Type; -}; - // Encryption key for `MOCK_SNAP_ID`. const ENCRYPTION_KEY = '0xd2f0a8e994b871ba4451ac383bf323cdaad8d554736355f2223e155692fbc446'; -// Encryption key for `MOCK_LOCAL_SNAP_ID`. -const OTHER_ENCRYPTION_KEY = - '0x7cd340349a41e0f7af62a9d97c76e96b12485e0206791d6b5638dd59736af8f5'; - -describe('getEncryptionKey', () => { - it('returns the encryption key for the snap ID', async () => { - const result = await getEncryptionKey({ +describe('getEncryptionEntropy', () => { + it('returns the encryption entropy for the snap ID', async () => { + const result = await getEncryptionEntropy({ mnemonicPhrase: TEST_SECRET_RECOVERY_PHRASE_BYTES, snapId: MOCK_SNAP_ID, }); @@ -55,10 +37,7 @@ describe('snap_manageState', () => { clearSnapState: jest.fn(), getSnapState: jest.fn(), updateSnapState: jest.fn(), - getMnemonic: jest.fn(), getUnlockPromise: jest.fn(), - encrypt, - decrypt, }; expect( @@ -91,22 +70,15 @@ describe('snap_manageState', () => { }, }; - const mockEncryptedState = await encrypt(ENCRYPTION_KEY, mockSnapState); - const clearSnapState = jest.fn().mockReturnValueOnce(true); - const getSnapState = jest.fn().mockReturnValueOnce(mockEncryptedState); + const getSnapState = jest.fn().mockReturnValueOnce(mockSnapState); const updateSnapState = jest.fn().mockReturnValueOnce(true); const manageStateImplementation = getManageStateImplementation({ clearSnapState, getSnapState, updateSnapState, - getMnemonic: jest - .fn() - .mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES), getUnlockPromise: jest.fn(), - encrypt, - decrypt, }); const result = await manageStateImplementation({ @@ -127,23 +99,15 @@ describe('snap_manageState', () => { }; const clearSnapState = jest.fn().mockReturnValueOnce(true); - const getSnapState = jest - .fn() - .mockReturnValueOnce(JSON.stringify(mockSnapState)); + const getSnapState = jest.fn().mockReturnValueOnce(mockSnapState); const updateSnapState = jest.fn().mockReturnValueOnce(true); const getUnlockPromise = jest.fn(); - const getMnemonic = jest - .fn() - .mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES); const manageStateImplementation = getManageStateImplementation({ clearSnapState, getSnapState, updateSnapState, - getMnemonic, getUnlockPromise, - encrypt, - decrypt, }); const result = await manageStateImplementation({ @@ -154,7 +118,6 @@ describe('snap_manageState', () => { expect(getSnapState).toHaveBeenCalledWith(MOCK_SNAP_ID, false); expect(getUnlockPromise).not.toHaveBeenCalled(); - expect(getMnemonic).not.toHaveBeenCalled(); expect(result).toStrictEqual(mockSnapState); }); @@ -167,12 +130,7 @@ describe('snap_manageState', () => { clearSnapState, getSnapState, updateSnapState, - getMnemonic: jest - .fn() - .mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES), getUnlockPromise: jest.fn(), - encrypt, - decrypt, }); const result = await manageStateImplementation({ @@ -190,18 +148,12 @@ describe('snap_manageState', () => { const getSnapState = jest.fn().mockReturnValueOnce(true); const updateSnapState = jest.fn().mockReturnValueOnce(true); const getUnlockPromise = jest.fn(); - const getMnemonic = jest - .fn() - .mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES); const manageStateImplementation = getManageStateImplementation({ clearSnapState, getSnapState, updateSnapState, - getMnemonic, getUnlockPromise, - encrypt, - decrypt, }); await manageStateImplementation({ @@ -211,7 +163,6 @@ describe('snap_manageState', () => { }); expect(clearSnapState).toHaveBeenCalledWith(MOCK_SNAP_ID, true); - expect(getMnemonic).not.toHaveBeenCalled(); expect(getUnlockPromise).not.toHaveBeenCalled(); }); @@ -220,18 +171,12 @@ describe('snap_manageState', () => { const getSnapState = jest.fn().mockReturnValueOnce(true); const updateSnapState = jest.fn().mockReturnValueOnce(true); const getUnlockPromise = jest.fn(); - const getMnemonic = jest - .fn() - .mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES); const manageStateImplementation = getManageStateImplementation({ clearSnapState, getSnapState, updateSnapState, - getMnemonic, getUnlockPromise, - encrypt, - decrypt, }); await manageStateImplementation({ @@ -244,7 +189,6 @@ describe('snap_manageState', () => { }); expect(clearSnapState).toHaveBeenCalledWith(MOCK_SNAP_ID, false); - expect(getMnemonic).not.toHaveBeenCalled(); expect(getUnlockPromise).not.toHaveBeenCalled(); }); @@ -255,8 +199,6 @@ describe('snap_manageState', () => { }, }; - const mockEncryptedState = await encrypt(ENCRYPTION_KEY, mockSnapState); - const clearSnapState = jest.fn().mockReturnValueOnce(true); const getSnapState = jest.fn().mockReturnValueOnce(true); const updateSnapState = jest.fn().mockReturnValueOnce(true); @@ -265,12 +207,7 @@ describe('snap_manageState', () => { clearSnapState, getSnapState, updateSnapState, - getMnemonic: jest - .fn() - .mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES), getUnlockPromise: jest.fn(), - encrypt, - decrypt, }); await manageStateImplementation({ @@ -284,70 +221,7 @@ describe('snap_manageState', () => { expect(updateSnapState).toHaveBeenCalledWith( MOCK_SNAP_ID, - mockEncryptedState, - true, - ); - }); - - it('uses different encryption for different snap IDs', async () => { - const mockSnapState = { - some: { - data: 'for a snap state', - }, - }; - - const mockEncryptedState = await encrypt(ENCRYPTION_KEY, mockSnapState); - const mockOtherEncryptedState = await encrypt( - OTHER_ENCRYPTION_KEY, mockSnapState, - ); - - const clearSnapState = jest.fn().mockReturnValueOnce(true); - const getSnapState = jest.fn().mockReturnValueOnce(true); - const updateSnapState = jest.fn().mockReturnValueOnce(true); - - const manageStateImplementation = getManageStateImplementation({ - clearSnapState, - getSnapState, - updateSnapState, - getMnemonic: jest - .fn() - .mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES), - getUnlockPromise: jest.fn(), - encrypt, - decrypt, - }); - - await manageStateImplementation({ - context: { origin: MOCK_SNAP_ID }, - method: 'snap_manageState', - params: { - operation: ManageStateOperation.UpdateState, - newState: mockSnapState, - }, - }); - - await manageStateImplementation({ - context: { origin: MOCK_LOCAL_SNAP_ID }, - method: 'snap_manageState', - params: { - operation: ManageStateOperation.UpdateState, - newState: mockSnapState, - }, - }); - - expect(updateSnapState).toHaveBeenCalledTimes(2); - expect(updateSnapState).toHaveBeenNthCalledWith( - 1, - MOCK_SNAP_ID, - mockEncryptedState, - true, - ); - - expect(updateSnapState).toHaveBeenNthCalledWith( - 2, - MOCK_LOCAL_SNAP_ID, - mockOtherEncryptedState, true, ); }); @@ -365,18 +239,12 @@ describe('snap_manageState', () => { .mockReturnValueOnce(JSON.stringify(mockSnapState)); const updateSnapState = jest.fn().mockReturnValueOnce(true); const getUnlockPromise = jest.fn(); - const getMnemonic = jest - .fn() - .mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES); const manageStateImplementation = getManageStateImplementation({ clearSnapState, getSnapState, updateSnapState, - getMnemonic, getUnlockPromise, - encrypt, - decrypt, }); await manageStateImplementation({ @@ -391,10 +259,9 @@ describe('snap_manageState', () => { expect(updateSnapState).toHaveBeenCalledWith( MOCK_SNAP_ID, - JSON.stringify(mockSnapState), + mockSnapState, false, ); - expect(getMnemonic).not.toHaveBeenCalled(); expect(getUnlockPromise).not.toHaveBeenCalled(); }); @@ -413,12 +280,7 @@ describe('snap_manageState', () => { clearSnapState, getSnapState, updateSnapState, - getMnemonic: jest - .fn() - .mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES), getUnlockPromise: jest.fn(), - encrypt, - decrypt, }); expect(async () => @@ -433,36 +295,6 @@ describe('snap_manageState', () => { ).not.toThrow(); }); - it('throws an error if the state is corrupt', async () => { - const clearSnapState = jest.fn().mockReturnValueOnce(true); - const getSnapState = jest.fn().mockReturnValueOnce('foo'); - const updateSnapState = jest.fn().mockReturnValueOnce(true); - - const manageStateImplementation = getManageStateImplementation({ - clearSnapState, - getSnapState, - updateSnapState, - getMnemonic: jest - .fn() - .mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES), - getUnlockPromise: jest.fn(), - encrypt, - decrypt, - }); - - await expect( - manageStateImplementation({ - context: { origin: MOCK_SNAP_ID }, - method: 'snap_manageState', - params: { operation: ManageStateOperation.GetState }, - }), - ).rejects.toThrow( - rpcErrors.internal({ - message: 'Failed to decrypt snap state, the state must be corrupted.', - }), - ); - }); - it('throws an error on update if the new state is not plain object', async () => { const clearSnapState = jest.fn().mockReturnValueOnce(true); const getSnapState = jest.fn().mockReturnValueOnce(true); @@ -472,12 +304,7 @@ describe('snap_manageState', () => { clearSnapState, getSnapState, updateSnapState, - getMnemonic: jest - .fn() - .mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES), getUnlockPromise: jest.fn(), - encrypt, - decrypt, }); const newState = (a: unknown) => { @@ -515,12 +342,7 @@ describe('snap_manageState', () => { clearSnapState, getSnapState, updateSnapState, - getMnemonic: jest - .fn() - .mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES), getUnlockPromise: jest.fn(), - encrypt, - decrypt, }); const newState = { diff --git a/packages/snaps-rpc-methods/src/restricted/manageState.ts b/packages/snaps-rpc-methods/src/restricted/manageState.ts index 22182e7a9b..73a9c5f43c 100644 --- a/packages/snaps-rpc-methods/src/restricted/manageState.ts +++ b/packages/snaps-rpc-methods/src/restricted/manageState.ts @@ -7,9 +7,9 @@ import { PermissionType, SubjectType } from '@metamask/permission-controller'; import { rpcErrors } from '@metamask/rpc-errors'; import type { ManageStateParams, ManageStateResult } from '@metamask/snaps-sdk'; import { ManageStateOperation } from '@metamask/snaps-sdk'; -import { STATE_ENCRYPTION_MAGIC_VALUE, parseJson } from '@metamask/snaps-utils'; -import type { Json, NonEmptyArray, Hex } from '@metamask/utils'; -import { isObject, getJsonSize, assert, isValidJson } from '@metamask/utils'; +import { STATE_ENCRYPTION_MAGIC_VALUE } from '@metamask/snaps-utils'; +import type { Json, NonEmptyArray } from '@metamask/utils'; +import { isObject, getJsonSize } from '@metamask/utils'; import type { MethodHooksObject } from '../utils'; import { deriveEntropy } from '../utils'; @@ -20,11 +20,6 @@ export const STATE_ENCRYPTION_SALT = 'snap_manageState encryption'; const methodName = 'snap_manageState'; export type ManageStateMethodHooks = { - /** - * @returns The mnemonic of the user's primary keyring. - */ - getMnemonic: () => Promise; - /** * Waits for the extension to be unlocked. * @@ -42,7 +37,10 @@ export type ManageStateMethodHooks = { * * @returns The current state of the Snap. */ - getSnapState: (snapId: string, encrypted: boolean) => string; + getSnapState: ( + snapId: string, + encrypted: boolean, + ) => Promise>; /** * A function that updates the state of the requesting Snap. @@ -51,29 +49,9 @@ export type ManageStateMethodHooks = { */ updateSnapState: ( snapId: string, - newState: string, + newState: Record, encrypted: boolean, - ) => void; - - /** - * Encrypts data with a key. This is assumed to perform symmetric encryption. - * - * @param key - The key to use for encryption, in hexadecimal format. - * @param data - The JSON data to encrypt. - * @returns The ciphertext as a string. The format for this string is - * dependent on the implementation, but MUST be a string. - */ - encrypt: (key: string, data: Json) => Promise; - - /** - * Decrypts data with a key. This is assumed to perform symmetric decryption. - * - * @param key - The key to use for decryption, in hexadecimal format. - * @param cipherText - The ciphertext to decrypt. The format for this string - * is dependent on the implementation, but MUST be a string. - * @returns The decrypted data as a JSON object. - */ - decrypt: (key: Hex, cipherText: string) => Promise; + ) => Promise; }; type ManageStateSpecificationBuilderOptions = { @@ -116,13 +94,10 @@ export const specificationBuilder: PermissionSpecificationBuilder< }; const methodHooks: MethodHooksObject = { - getMnemonic: true, getUnlockPromise: true, clearSnapState: true, getSnapState: true, updateSnapState: true, - encrypt: true, - decrypt: true, }; export const manageStateBuilder = Object.freeze({ @@ -151,7 +126,7 @@ type GetEncryptionKeyArgs = { * from. * @returns The state encryption key. */ -export async function getEncryptionKey({ +export async function getEncryptionEntropy({ mnemonicPhrase, snapId, }: GetEncryptionKeyArgs) { @@ -163,68 +138,6 @@ export async function getEncryptionKey({ }); } -type EncryptStateArgs = GetEncryptionKeyArgs & { - state: Json; - encryptFunction: ManageStateMethodHooks['encrypt']; -}; - -/** - * Encrypt the state using a deterministic encryption algorithm, based on the - * snap ID and mnemonic phrase. - * - * @param args - The encryption args. - * @param args.state - The state to encrypt. - * @param args.encryptFunction - The function to use for encrypting the state. - * @param args.snapId - The ID of the snap to get the encryption key for. - * @param args.mnemonicPhrase - The mnemonic phrase to derive the encryption key - * from. - * @returns The encrypted state. - */ -async function encryptState({ - state, - encryptFunction, - ...keyArgs -}: EncryptStateArgs) { - const encryptionKey = await getEncryptionKey(keyArgs); - return await encryptFunction(encryptionKey, state); -} - -type DecryptStateArgs = GetEncryptionKeyArgs & { - state: string; - decryptFunction: ManageStateMethodHooks['decrypt']; -}; - -/** - * Decrypt the state using a deterministic decryption algorithm, based on the - * snap ID and mnemonic phrase. - * - * @param args - The encryption args. - * @param args.state - The state to decrypt. - * @param args.decryptFunction - The function to use for decrypting the state. - * @param args.snapId - The ID of the snap to get the encryption key for. - * @param args.mnemonicPhrase - The mnemonic phrase to derive the encryption key - * from. - * @returns The encrypted state. - */ -async function decryptState({ - state, - decryptFunction, - ...keyArgs -}: DecryptStateArgs) { - try { - const encryptionKey = await getEncryptionKey(keyArgs); - const decryptedState = await decryptFunction(encryptionKey, state); - - assert(isValidJson(decryptedState)); - - return decryptedState as Record; - } catch { - throw rpcErrors.internal({ - message: 'Failed to decrypt snap state, the state must be corrupted.', - }); - } -} - /** * Builds the method implementation for `snap_manageState`. * @@ -235,25 +148,18 @@ async function decryptState({ * state for a snap. * @param hooks.updateSnapState - A function that updates the state stored for a * snap. - * @param hooks.getMnemonic - A function to retrieve the Secret Recovery Phrase - * of the user. * @param hooks.getUnlockPromise - A function that resolves once the MetaMask * extension is unlocked and prompts the user to unlock their MetaMask if it is * locked. - * @param hooks.encrypt - A function that encrypts the given state. - * @param hooks.decrypt - A function that decrypts the given state. * @returns The method implementation which either returns `null` for a * successful state update/deletion or returns the decrypted state. * @throws If the params are invalid. */ export function getManageStateImplementation({ - getMnemonic, getUnlockPromise, clearSnapState, getSnapState, updateSnapState, - encrypt, - decrypt, }: ManageStateMethodHooks) { return async function manageState( options: RestrictedMethodOptions, @@ -283,31 +189,11 @@ export function getManageStateImplementation({ return null; case ManageStateOperation.GetState: { - const state = getSnapState(origin, shouldEncrypt); - if (state === null) { - return state; - } - return shouldEncrypt - ? await decryptState({ - state, - decryptFunction: decrypt, - mnemonicPhrase: await getMnemonic(), - snapId: origin, - }) - : parseJson>(state); + return await getSnapState(origin, shouldEncrypt); } case ManageStateOperation.UpdateState: { - const finalizedState = shouldEncrypt - ? await encryptState({ - state: validatedParams.newState, - encryptFunction: encrypt, - mnemonicPhrase: await getMnemonic(), - snapId: origin, - }) - : JSON.stringify(validatedParams.newState); - - updateSnapState(origin, finalizedState, shouldEncrypt); + await updateSnapState(origin, validatedParams.newState, shouldEncrypt); return null; } diff --git a/packages/snaps-rpc-methods/tsconfig.json b/packages/snaps-rpc-methods/tsconfig.json index 2db16954a8..87b8b62915 100644 --- a/packages/snaps-rpc-methods/tsconfig.json +++ b/packages/snaps-rpc-methods/tsconfig.json @@ -3,6 +3,6 @@ "compilerOptions": { "baseUrl": "./" }, - "include": ["./src"], + "include": ["./src", "package.json", "tsup.config.ts"], "references": [{ "path": "../snaps-sdk" }, { "path": "../snaps-utils" }] } diff --git a/packages/snaps-rpc-methods/tsup.config.ts b/packages/snaps-rpc-methods/tsup.config.ts new file mode 100644 index 0000000000..3eaf645296 --- /dev/null +++ b/packages/snaps-rpc-methods/tsup.config.ts @@ -0,0 +1,14 @@ +import deepmerge from 'deepmerge'; +import type { Options } from 'tsup'; + +import packageJson from './package.json'; + +// `tsup.config.ts` is not under `rootDir`, so we need to use `require` instead. +// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires +const { default: baseConfig } = require('../../tsup.config'); + +const config: Options = { + name: packageJson.name, +}; + +export default deepmerge(baseConfig, config); diff --git a/packages/snaps-sdk/CHANGELOG.md b/packages/snaps-sdk/CHANGELOG.md index ea3ccefa52..fd9b337451 100644 --- a/packages/snaps-sdk/CHANGELOG.md +++ b/packages/snaps-sdk/CHANGELOG.md @@ -6,6 +6,54 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [4.0.1] +### Fixed +- Allow `null` in `FormSubmitEventStruct` form state ([#2333](https://github.com/MetaMask/snaps/pull/2333)) + +## [4.0.0] +### Removed +- **BREAKING:** Remove broken `ethereum` properties ([#2296](https://github.com/MetaMask/snaps/pull/2296)) + - Snaps can no longer access `on` and `removeListener` on `ethereum`. + - This feature was already non-functional. + +## [3.2.0] +### Added +- Add support for importing SVG, PNG, and JPEG files directly ([#2284](https://github.com/MetaMask/snaps/pull/2284)) + +### Changed +- Narrow type for `endowment:name-lookup` ([#2293](https://github.com/MetaMask/snaps/pull/2293)) +- Bump MetaMask dependencies ([#2270](https://github.com/MetaMask/snaps/pull/2270)) + +## [3.1.1] +### Changed +- Bump `@metamask/providers` to `^15.0.0` ([#2231](https://github.com/MetaMask/snaps/pull/2231)) + +### Fixed +- Fix address validation in row component ([#2257](https://github.com/MetaMask/snaps/pull/2257)) + +## [3.1.0] +### Added +- Add `InputChangeEvent` event ([#2237](https://github.com/MetaMask/snaps/pull/2237)) +- Add `error` prop to input component ([#2239](https://github.com/MetaMask/snaps/pull/2239)) + +## [3.0.1] +### Fixed +- Fix minor build configuration problems ([#2220](https://github.com/MetaMask/snaps/pull/2220)) + +## [3.0.0] +### Changed +- **BREAKING:** Update ESM build to be fully compliant with the ESM standard ([#2210](https://github.com/MetaMask/snaps/pull/2210)) +- Bump `@metamask/rpc-errors` to `^6.2.1` ([#2209](https://github.com/MetaMask/snaps/pull/2209)) + +## [2.1.0] +### Changed +- Improve support for Snap errors without a message ([#2176](https://github.com/MetaMask/snaps/pull/2176)) + - You can now add data to an error without having to specify a message. For example: + ```ts + throw new MethodNotFoundError({ method: "some method name" }); + ``` +- Strip empty `data` from Snap errors ([#2179](https://github.com/MetaMask/snaps/pull/2179)) + ## [2.0.0] ### Changed - **BREAKING:** Update name lookup API types ([#2113](https://github.com/MetaMask/snaps/pull/2113)) @@ -55,7 +103,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Initial release of this package. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-sdk@2.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-sdk@4.0.1...HEAD +[4.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-sdk@4.0.0...@metamask/snaps-sdk@4.0.1 +[4.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-sdk@3.2.0...@metamask/snaps-sdk@4.0.0 +[3.2.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-sdk@3.1.1...@metamask/snaps-sdk@3.2.0 +[3.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-sdk@3.1.0...@metamask/snaps-sdk@3.1.1 +[3.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-sdk@3.0.1...@metamask/snaps-sdk@3.1.0 +[3.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-sdk@3.0.0...@metamask/snaps-sdk@3.0.1 +[3.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-sdk@2.1.0...@metamask/snaps-sdk@3.0.0 +[2.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-sdk@2.0.0...@metamask/snaps-sdk@2.1.0 [2.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-sdk@1.4.0...@metamask/snaps-sdk@2.0.0 [1.4.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-sdk@1.3.2...@metamask/snaps-sdk@1.4.0 [1.3.2]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-sdk@1.3.1...@metamask/snaps-sdk@1.3.2 diff --git a/packages/snaps-sdk/package.json b/packages/snaps-sdk/package.json index ad5bb6c6db..97146d3d09 100644 --- a/packages/snaps-sdk/package.json +++ b/packages/snaps-sdk/package.json @@ -1,18 +1,24 @@ { "name": "@metamask/snaps-sdk", - "version": "2.0.0", + "version": "4.0.1", "repository": { "type": "git", "url": "https://github.com/MetaMask/snaps.git" }, "sideEffects": false, - "main": "./dist/cjs/index.js", - "module": "./dist/esm/index.js", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/types/index.d.ts" + }, + "./package.json": "./package.json" + }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", "types": "./dist/types/index.d.ts", "files": [ - "dist/cjs/**", - "dist/esm/**", - "dist/types/**" + "dist" ], "scripts": { "test": "jest && yarn posttest", @@ -23,33 +29,29 @@ "lint": "yarn lint:eslint && yarn lint:misc --check && yarn lint:changelog && yarn lint:dependencies", "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write", "lint:changelog": "../../scripts/validate-changelog.sh @metamask/snaps-sdk", - "build": "yarn build:source && yarn build:types", - "build:source": "yarn build:esm && yarn build:cjs", + "build": "tsup --clean && yarn build:types", "build:types": "tsc --project tsconfig.build.json", - "build:esm": "swc src --out-dir dist/esm --config-file ../../.swcrc.build.json --config module.type=es6", - "build:cjs": "swc src --out-dir dist/cjs --config-file ../../.swcrc.build.json --config module.type=commonjs", - "build:clean": "yarn clean && yarn build", "clean": "rimraf '*.tsbuildinfo' 'dist'", "publish:preview": "yarn npm publish --tag preview", "lint:ci": "yarn lint", - "lint:dependencies": "depcheck" + "lint:dependencies": "depcheck", + "build:ci": "tsup --clean" }, "dependencies": { "@metamask/key-tree": "^9.0.0", - "@metamask/providers": "^14.0.2", - "@metamask/rpc-errors": "^6.1.0", + "@metamask/providers": "^16.0.0", + "@metamask/rpc-errors": "^6.2.1", "@metamask/utils": "^8.3.0", - "is-svg": "^4.4.0", + "fast-xml-parser": "^4.3.4", "superstruct": "^1.0.3" }, "devDependencies": { - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", "@metamask/eslint-config-nodejs": "^12.1.0", "@metamask/eslint-config-typescript": "^12.1.0", - "@swc/cli": "^0.1.62", "@swc/core": "1.3.78", "@types/jest": "^27.5.1", "@typescript-eslint/eslint-plugin": "^5.42.1", @@ -72,6 +74,7 @@ "prettier-plugin-packagejson": "^2.2.11", "rimraf": "^4.1.2", "ts-jest": "^29.1.1", + "tsup": "^8.0.1", "typescript": "~4.8.4" }, "engines": { diff --git a/packages/snaps-sdk/src/error-wrappers.test.ts b/packages/snaps-sdk/src/error-wrappers.test.ts index 38b4dcfc5b..8afd2ff185 100644 --- a/packages/snaps-sdk/src/error-wrappers.test.ts +++ b/packages/snaps-sdk/src/error-wrappers.test.ts @@ -28,7 +28,7 @@ describe('Snap errors', () => { expect(wrapped).toBeInstanceOf(InternalError); expect(wrapped.message).toBe('foo'); expect(wrapped.code).toBe(-32603); - expect(wrapped.data).toStrictEqual({}); + expect(wrapped.data).toBeUndefined(); expect(wrapped.stack).toBeDefined(); expect(wrapped.toJSON()).toStrictEqual({ code: -31002, @@ -38,7 +38,6 @@ describe('Snap errors', () => { code: -32603, message: 'foo', stack: wrapped.stack, - data: {}, }, }, }); @@ -60,6 +59,17 @@ describe('Snap errors', () => { expect(wrapped.message).toBe('Internal JSON-RPC error.'); }); + + it('creates a SnapError without a message and with data', () => { + const wrapped = new InternalError({ + foo: 'bar', + }); + + expect(wrapped.message).toBe('Internal JSON-RPC error.'); + expect(wrapped.data).toStrictEqual({ + foo: 'bar', + }); + }); }); describe('InvalidInputError', () => { @@ -71,7 +81,7 @@ describe('Snap errors', () => { expect(wrapped).toBeInstanceOf(InvalidInputError); expect(wrapped.message).toBe('foo'); expect(wrapped.code).toBe(-32000); - expect(wrapped.data).toStrictEqual({}); + expect(wrapped.data).toBeUndefined(); expect(wrapped.stack).toBeDefined(); expect(wrapped.toJSON()).toStrictEqual({ code: -31002, @@ -81,7 +91,6 @@ describe('Snap errors', () => { code: -32000, message: 'foo', stack: wrapped.stack, - data: {}, }, }, }); @@ -103,6 +112,17 @@ describe('Snap errors', () => { expect(wrapped.message).toBe('Invalid input.'); }); + + it('creates a SnapError without a message and with data', () => { + const wrapped = new InvalidInputError({ + foo: 'bar', + }); + + expect(wrapped.message).toBe('Invalid input.'); + expect(wrapped.data).toStrictEqual({ + foo: 'bar', + }); + }); }); describe('InvalidParamsError', () => { @@ -114,7 +134,7 @@ describe('Snap errors', () => { expect(wrapped).toBeInstanceOf(InvalidParamsError); expect(wrapped.message).toBe('foo'); expect(wrapped.code).toBe(-32602); - expect(wrapped.data).toStrictEqual({}); + expect(wrapped.data).toBeUndefined(); expect(wrapped.stack).toBeDefined(); expect(wrapped.toJSON()).toStrictEqual({ code: -31002, @@ -124,7 +144,6 @@ describe('Snap errors', () => { code: -32602, message: 'foo', stack: wrapped.stack, - data: {}, }, }, }); @@ -146,6 +165,17 @@ describe('Snap errors', () => { expect(wrapped.message).toBe('Invalid method parameter(s).'); }); + + it('creates a SnapError without a message and with data', () => { + const wrapped = new InvalidParamsError({ + foo: 'bar', + }); + + expect(wrapped.message).toBe('Invalid method parameter(s).'); + expect(wrapped.data).toStrictEqual({ + foo: 'bar', + }); + }); }); describe('InvalidRequestError', () => { @@ -157,7 +187,7 @@ describe('Snap errors', () => { expect(wrapped).toBeInstanceOf(InvalidRequestError); expect(wrapped.message).toBe('foo'); expect(wrapped.code).toBe(-32600); - expect(wrapped.data).toStrictEqual({}); + expect(wrapped.data).toBeUndefined(); expect(wrapped.stack).toBeDefined(); expect(wrapped.toJSON()).toStrictEqual({ code: -31002, @@ -167,7 +197,6 @@ describe('Snap errors', () => { code: -32600, message: 'foo', stack: wrapped.stack, - data: {}, }, }, }); @@ -191,6 +220,19 @@ describe('Snap errors', () => { 'The JSON sent is not a valid Request object.', ); }); + + it('creates a SnapError without a message and with data', () => { + const wrapped = new InvalidRequestError({ + foo: 'bar', + }); + + expect(wrapped.message).toBe( + 'The JSON sent is not a valid Request object.', + ); + expect(wrapped.data).toStrictEqual({ + foo: 'bar', + }); + }); }); describe('LimitExceededError', () => { @@ -202,7 +244,7 @@ describe('Snap errors', () => { expect(wrapped).toBeInstanceOf(LimitExceededError); expect(wrapped.message).toBe('foo'); expect(wrapped.code).toBe(-32005); - expect(wrapped.data).toStrictEqual({}); + expect(wrapped.data).toBeUndefined(); expect(wrapped.stack).toBeDefined(); expect(wrapped.toJSON()).toStrictEqual({ code: -31002, @@ -212,7 +254,6 @@ describe('Snap errors', () => { code: -32005, message: 'foo', stack: wrapped.stack, - data: {}, }, }, }); @@ -234,6 +275,17 @@ describe('Snap errors', () => { expect(wrapped.message).toBe('Request limit exceeded.'); }); + + it('creates a SnapError without a message and with data', () => { + const wrapped = new LimitExceededError({ + foo: 'bar', + }); + + expect(wrapped.message).toBe('Request limit exceeded.'); + expect(wrapped.data).toStrictEqual({ + foo: 'bar', + }); + }); }); describe('MethodNotFoundError', () => { @@ -245,7 +297,7 @@ describe('Snap errors', () => { expect(wrapped).toBeInstanceOf(MethodNotFoundError); expect(wrapped.message).toBe('foo'); expect(wrapped.code).toBe(-32601); - expect(wrapped.data).toStrictEqual({}); + expect(wrapped.data).toBeUndefined(); expect(wrapped.stack).toBeDefined(); expect(wrapped.toJSON()).toStrictEqual({ code: -31002, @@ -255,7 +307,6 @@ describe('Snap errors', () => { code: -32601, message: 'foo', stack: wrapped.stack, - data: {}, }, }, }); @@ -279,6 +330,19 @@ describe('Snap errors', () => { 'The method does not exist / is not available.', ); }); + + it('creates a SnapError without a message and with data', () => { + const wrapped = new MethodNotFoundError({ + foo: 'bar', + }); + + expect(wrapped.message).toBe( + 'The method does not exist / is not available.', + ); + expect(wrapped.data).toStrictEqual({ + foo: 'bar', + }); + }); }); describe('MethodNotSupportedError', () => { @@ -290,7 +354,7 @@ describe('Snap errors', () => { expect(wrapped).toBeInstanceOf(MethodNotSupportedError); expect(wrapped.message).toBe('foo'); expect(wrapped.code).toBe(-32004); - expect(wrapped.data).toStrictEqual({}); + expect(wrapped.data).toBeUndefined(); expect(wrapped.stack).toBeDefined(); expect(wrapped.toJSON()).toStrictEqual({ code: -31002, @@ -300,7 +364,6 @@ describe('Snap errors', () => { code: -32004, message: 'foo', stack: wrapped.stack, - data: {}, }, }, }); @@ -322,6 +385,17 @@ describe('Snap errors', () => { expect(wrapped.message).toBe('Method not supported.'); }); + + it('creates a SnapError without a message and with data', () => { + const wrapped = new MethodNotSupportedError({ + foo: 'bar', + }); + + expect(wrapped.message).toBe('Method not supported.'); + expect(wrapped.data).toStrictEqual({ + foo: 'bar', + }); + }); }); describe('ParseError', () => { @@ -333,7 +407,7 @@ describe('Snap errors', () => { expect(wrapped).toBeInstanceOf(ParseError); expect(wrapped.message).toBe('foo'); expect(wrapped.code).toBe(-32700); - expect(wrapped.data).toStrictEqual({}); + expect(wrapped.data).toBeUndefined(); expect(wrapped.stack).toBeDefined(); expect(wrapped.toJSON()).toStrictEqual({ code: -31002, @@ -343,7 +417,6 @@ describe('Snap errors', () => { code: -32700, message: 'foo', stack: wrapped.stack, - data: {}, }, }, }); @@ -367,6 +440,19 @@ describe('Snap errors', () => { 'Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.', ); }); + + it('creates a SnapError without a message and with data', () => { + const wrapped = new ParseError({ + foo: 'bar', + }); + + expect(wrapped.message).toBe( + 'Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.', + ); + expect(wrapped.data).toStrictEqual({ + foo: 'bar', + }); + }); }); describe('ResourceNotFoundError', () => { @@ -378,7 +464,7 @@ describe('Snap errors', () => { expect(wrapped).toBeInstanceOf(ResourceNotFoundError); expect(wrapped.message).toBe('foo'); expect(wrapped.code).toBe(-32001); - expect(wrapped.data).toStrictEqual({}); + expect(wrapped.data).toBeUndefined(); expect(wrapped.stack).toBeDefined(); expect(wrapped.toJSON()).toStrictEqual({ code: -31002, @@ -388,7 +474,6 @@ describe('Snap errors', () => { code: -32001, message: 'foo', stack: wrapped.stack, - data: {}, }, }, }); @@ -410,6 +495,17 @@ describe('Snap errors', () => { expect(wrapped.message).toBe('Resource not found.'); }); + + it('creates a SnapError without a message and with data', () => { + const wrapped = new ResourceNotFoundError({ + foo: 'bar', + }); + + expect(wrapped.message).toBe('Resource not found.'); + expect(wrapped.data).toStrictEqual({ + foo: 'bar', + }); + }); }); describe('ResourceUnavailableError', () => { @@ -421,7 +517,7 @@ describe('Snap errors', () => { expect(wrapped).toBeInstanceOf(ResourceUnavailableError); expect(wrapped.message).toBe('foo'); expect(wrapped.code).toBe(-32002); - expect(wrapped.data).toStrictEqual({}); + expect(wrapped.data).toBeUndefined(); expect(wrapped.stack).toBeDefined(); expect(wrapped.toJSON()).toStrictEqual({ code: -31002, @@ -431,7 +527,6 @@ describe('Snap errors', () => { code: -32002, message: 'foo', stack: wrapped.stack, - data: {}, }, }, }); @@ -453,6 +548,17 @@ describe('Snap errors', () => { expect(wrapped.message).toBe('Resource unavailable.'); }); + + it('creates a SnapError without a message and with data', () => { + const wrapped = new ResourceUnavailableError({ + foo: 'bar', + }); + + expect(wrapped.message).toBe('Resource unavailable.'); + expect(wrapped.data).toStrictEqual({ + foo: 'bar', + }); + }); }); describe('TransactionRejected', () => { @@ -464,7 +570,7 @@ describe('Snap errors', () => { expect(wrapped).toBeInstanceOf(TransactionRejected); expect(wrapped.message).toBe('foo'); expect(wrapped.code).toBe(-32003); - expect(wrapped.data).toStrictEqual({}); + expect(wrapped.data).toBeUndefined(); expect(wrapped.stack).toBeDefined(); expect(wrapped.toJSON()).toStrictEqual({ code: -31002, @@ -474,7 +580,6 @@ describe('Snap errors', () => { code: -32003, message: 'foo', stack: wrapped.stack, - data: {}, }, }, }); @@ -496,6 +601,17 @@ describe('Snap errors', () => { expect(wrapped.message).toBe('Transaction rejected.'); }); + + it('creates a SnapError without a message and with data', () => { + const wrapped = new TransactionRejected({ + foo: 'bar', + }); + + expect(wrapped.message).toBe('Transaction rejected.'); + expect(wrapped.data).toStrictEqual({ + foo: 'bar', + }); + }); }); describe('ChainDisconnectedError', () => { @@ -507,7 +623,7 @@ describe('Snap errors', () => { expect(wrapped).toBeInstanceOf(ChainDisconnectedError); expect(wrapped.message).toBe('foo'); expect(wrapped.code).toBe(4901); - expect(wrapped.data).toStrictEqual({}); + expect(wrapped.data).toBeUndefined(); expect(wrapped.stack).toBeDefined(); expect(wrapped.toJSON()).toStrictEqual({ code: -31002, @@ -517,7 +633,6 @@ describe('Snap errors', () => { code: 4901, message: 'foo', stack: wrapped.stack, - data: {}, }, }, }); @@ -541,6 +656,19 @@ describe('Snap errors', () => { 'The provider is disconnected from the specified chain.', ); }); + + it('creates a SnapError without a message and with data', () => { + const wrapped = new ChainDisconnectedError({ + foo: 'bar', + }); + + expect(wrapped.message).toBe( + 'The provider is disconnected from the specified chain.', + ); + expect(wrapped.data).toStrictEqual({ + foo: 'bar', + }); + }); }); describe('DisconnectedError', () => { @@ -552,7 +680,7 @@ describe('Snap errors', () => { expect(wrapped).toBeInstanceOf(DisconnectedError); expect(wrapped.message).toBe('foo'); expect(wrapped.code).toBe(4900); - expect(wrapped.data).toStrictEqual({}); + expect(wrapped.data).toBeUndefined(); expect(wrapped.stack).toBeDefined(); expect(wrapped.toJSON()).toStrictEqual({ code: -31002, @@ -562,7 +690,6 @@ describe('Snap errors', () => { code: 4900, message: 'foo', stack: wrapped.stack, - data: {}, }, }, }); @@ -586,6 +713,19 @@ describe('Snap errors', () => { 'The provider is disconnected from all chains.', ); }); + + it('creates a SnapError without a message and with data', () => { + const wrapped = new DisconnectedError({ + foo: 'bar', + }); + + expect(wrapped.message).toBe( + 'The provider is disconnected from all chains.', + ); + expect(wrapped.data).toStrictEqual({ + foo: 'bar', + }); + }); }); describe('UnauthorizedError', () => { @@ -597,7 +737,7 @@ describe('Snap errors', () => { expect(wrapped).toBeInstanceOf(UnauthorizedError); expect(wrapped.message).toBe('foo'); expect(wrapped.code).toBe(4100); - expect(wrapped.data).toStrictEqual({}); + expect(wrapped.data).toBeUndefined(); expect(wrapped.stack).toBeDefined(); expect(wrapped.toJSON()).toStrictEqual({ code: -31002, @@ -607,7 +747,6 @@ describe('Snap errors', () => { code: 4100, message: 'foo', stack: wrapped.stack, - data: {}, }, }, }); @@ -631,6 +770,19 @@ describe('Snap errors', () => { 'The requested account and/or method has not been authorized by the user.', ); }); + + it('creates a SnapError without a message and with data', () => { + const wrapped = new UnauthorizedError({ + foo: 'bar', + }); + + expect(wrapped.message).toBe( + 'The requested account and/or method has not been authorized by the user.', + ); + expect(wrapped.data).toStrictEqual({ + foo: 'bar', + }); + }); }); describe('UnsupportedMethodError', () => { @@ -642,7 +794,7 @@ describe('Snap errors', () => { expect(wrapped).toBeInstanceOf(UnsupportedMethodError); expect(wrapped.message).toBe('foo'); expect(wrapped.code).toBe(4200); - expect(wrapped.data).toStrictEqual({}); + expect(wrapped.data).toBeUndefined(); expect(wrapped.stack).toBeDefined(); expect(wrapped.toJSON()).toStrictEqual({ code: -31002, @@ -652,7 +804,6 @@ describe('Snap errors', () => { code: 4200, message: 'foo', stack: wrapped.stack, - data: {}, }, }, }); @@ -676,6 +827,19 @@ describe('Snap errors', () => { 'The requested method is not supported by this Ethereum provider.', ); }); + + it('creates a SnapError without a message and with data', () => { + const wrapped = new UnsupportedMethodError({ + foo: 'bar', + }); + + expect(wrapped.message).toBe( + 'The requested method is not supported by this Ethereum provider.', + ); + expect(wrapped.data).toStrictEqual({ + foo: 'bar', + }); + }); }); describe('UserRejectedRequestError', () => { @@ -687,7 +851,7 @@ describe('Snap errors', () => { expect(wrapped).toBeInstanceOf(UserRejectedRequestError); expect(wrapped.message).toBe('foo'); expect(wrapped.code).toBe(4001); - expect(wrapped.data).toStrictEqual({}); + expect(wrapped.data).toBeUndefined(); expect(wrapped.stack).toBeDefined(); expect(wrapped.toJSON()).toStrictEqual({ code: -31002, @@ -697,7 +861,6 @@ describe('Snap errors', () => { code: 4001, message: 'foo', stack: wrapped.stack, - data: {}, }, }, }); @@ -719,5 +882,16 @@ describe('Snap errors', () => { expect(wrapped.message).toBe('User rejected the request.'); }); + + it('creates a SnapError without a message and with data', () => { + const wrapped = new UserRejectedRequestError({ + foo: 'bar', + }); + + expect(wrapped.message).toBe('User rejected the request.'); + expect(wrapped.data).toStrictEqual({ + foo: 'bar', + }); + }); }); }); diff --git a/packages/snaps-sdk/src/errors.test.ts b/packages/snaps-sdk/src/errors.test.ts index 3baef080b0..902c407bc0 100644 --- a/packages/snaps-sdk/src/errors.test.ts +++ b/packages/snaps-sdk/src/errors.test.ts @@ -10,7 +10,7 @@ describe('SnapError', () => { expect(error).toBeInstanceOf(SnapError); expect(error.message).toBe('foo'); expect(error.code).toBe(-32603); - expect(error.data).toStrictEqual({}); + expect(error.data).toBeUndefined(); expect(error.stack).toBeDefined(); expect(error.toJSON()).toStrictEqual({ code: -31002, @@ -20,7 +20,6 @@ describe('SnapError', () => { code: -32603, message: 'foo', stack: error.stack, - data: {}, }, }, }); @@ -36,7 +35,7 @@ describe('SnapError', () => { expect(error).toBeInstanceOf(SnapError); expect(error.message).toBe('foo'); expect(error.code).toBe(-32000); - expect(error.data).toStrictEqual({}); + expect(error.data).toBeUndefined(); expect(error.stack).toBeDefined(); expect(error.toJSON()).toStrictEqual({ code: -31002, @@ -46,7 +45,6 @@ describe('SnapError', () => { code: -32000, message: 'foo', stack: error.stack, - data: {}, }, }, }); @@ -101,7 +99,7 @@ describe('SnapError', () => { expect(error).toBeInstanceOf(SnapError); expect(error.message).toBe('foo'); expect(error.code).toBe(-32603); - expect(error.data).toStrictEqual({}); + expect(error.data).toBeUndefined(); expect(error.stack).toBeDefined(); expect(error.toJSON()).toStrictEqual({ code: -31002, @@ -111,7 +109,6 @@ describe('SnapError', () => { code: -32603, message: 'foo', stack: error.stack, - data: {}, }, }, }); @@ -149,7 +146,7 @@ describe('SnapError', () => { expect(error).toBeInstanceOf(SnapError); expect(error.message).toBe('foo'); expect(error.code).toBe(-32602); - expect(error.data).toStrictEqual({}); + expect(error.data).toBeUndefined(); expect(error.stack).toBeDefined(); expect(error.toJSON()).toStrictEqual({ code: -31002, @@ -159,7 +156,6 @@ describe('SnapError', () => { code: -32602, message: 'foo', stack: error.stack, - data: {}, }, }, }); @@ -202,7 +198,7 @@ describe('SnapError', () => { expect(error).toBeInstanceOf(SnapError); expect(error.message).toBe('foo'); expect(error.code).toBe(0); - expect(error.data).toStrictEqual({}); + expect(error.data).toBeUndefined(); expect(error.stack).toBeDefined(); expect(error.toJSON()).toStrictEqual({ code: -31002, @@ -212,7 +208,6 @@ describe('SnapError', () => { code: 0, message: 'foo', stack: error.stack, - data: {}, }, }, }); diff --git a/packages/snaps-sdk/src/errors.ts b/packages/snaps-sdk/src/errors.ts index 93f3c80f31..14db0b9ba7 100644 --- a/packages/snaps-sdk/src/errors.ts +++ b/packages/snaps-sdk/src/errors.ts @@ -17,7 +17,7 @@ export class SnapError extends Error { readonly #message: string; - readonly #data: Record; + readonly #data?: Record; readonly #stack?: string; @@ -42,7 +42,12 @@ export class SnapError extends Error { this.#message = message; this.#code = getErrorCode(error); - this.#data = { ...getErrorData(error), ...data }; + + const mergedData = { ...getErrorData(error), ...data }; + if (Object.keys(mergedData).length > 0) { + this.#data = mergedData; + } + this.#stack = super.stack; } @@ -109,7 +114,7 @@ export class SnapError extends Error { code: this.code, message: this.message, stack: this.stack, - data: this.data, + ...(this.data ? { data: this.data } : {}), }, }, }; @@ -144,8 +149,6 @@ export type SerializedSnapError = { code: typeof SNAP_ERROR_CODE; message: typeof SNAP_ERROR_MESSAGE; data: { - cause: JsonRpcError & { - data: Record; - }; + cause: JsonRpcError; }; }; diff --git a/packages/snaps-sdk/src/index.ts b/packages/snaps-sdk/src/index.ts index c2a0cdd457..245f224e34 100644 --- a/packages/snaps-sdk/src/index.ts +++ b/packages/snaps-sdk/src/index.ts @@ -9,6 +9,8 @@ export { literal, union, enumValue, + parseSvg, + isSvg, } from './internals'; // Re-exported from `@metamask/utils` for convenience. diff --git a/packages/snaps-sdk/src/internals/error-wrappers.test.ts b/packages/snaps-sdk/src/internals/error-wrappers.test.ts index e5e53f2b18..26fcb0637e 100644 --- a/packages/snaps-sdk/src/internals/error-wrappers.test.ts +++ b/packages/snaps-sdk/src/internals/error-wrappers.test.ts @@ -12,7 +12,7 @@ describe('createSnapError', () => { expect(error).toBeInstanceOf(SnapError); expect(error.message).toBe('foo'); expect(error.code).toBe(-32602); - expect(error.data).toStrictEqual({}); + expect(error.data).toBeUndefined(); expect(error.stack).toBeDefined(); expect(error.toJSON()).toStrictEqual({ code: -31002, @@ -22,7 +22,6 @@ describe('createSnapError', () => { code: -32602, message: 'foo', stack: error.stack, - data: {}, }, }, }); diff --git a/packages/snaps-sdk/src/internals/error-wrappers.ts b/packages/snaps-sdk/src/internals/error-wrappers.ts index 2a35bab03a..74feebfbf2 100644 --- a/packages/snaps-sdk/src/internals/error-wrappers.ts +++ b/packages/snaps-sdk/src/internals/error-wrappers.ts @@ -18,9 +18,53 @@ export type JsonRpcErrorFunction = typeof rpcErrors.parse; */ export function createSnapError(fn: JsonRpcErrorFunction) { return class SnapJsonRpcError extends SnapError { - constructor(message?: string, data?: Record) { - const error = fn(message); + /** + * Create a new `SnapJsonRpcError` from a message. + * + * @param message - The message to create the error from. + */ + constructor(message?: string); + + /** + * Create a new `SnapJsonRpcError` from data. + * + * @param data - The data to create the error from. + */ + constructor(data?: Record); + + /** + * Create a new `SnapJsonRpcError` from a message and data. + * + * @param message - The message to create the error from. + * @param data - The data to create the error from. + */ + constructor( + message?: string | Record, + data?: Record, + ); + /** + * Create a new `SnapJsonRpcError` from a message and data. + * + * @param message - The message to create the error from. + * @param data - The data to create the error from. + */ + constructor( + message?: string | Record, + data?: Record, + ) { + if (typeof message === 'object') { + const error = fn(); + super({ + code: error.code, + message: error.message, + data: message, + }); + + return; + } + + const error = fn(message); super({ code: error.code, message: error.message, diff --git a/packages/snaps-sdk/src/internals/index.ts b/packages/snaps-sdk/src/internals/index.ts index a0bd1a67fb..71f174fe9d 100644 --- a/packages/snaps-sdk/src/internals/index.ts +++ b/packages/snaps-sdk/src/internals/index.ts @@ -2,3 +2,4 @@ export * from './error-wrappers'; export * from './errors'; export * from './helpers'; export * from './structs'; +export * from './svg'; diff --git a/packages/snaps-sdk/src/internals/svg.test.ts b/packages/snaps-sdk/src/internals/svg.test.ts new file mode 100644 index 0000000000..06e4eb1541 --- /dev/null +++ b/packages/snaps-sdk/src/internals/svg.test.ts @@ -0,0 +1,30 @@ +import { parseSvg } from './svg'; + +const SNAP_ICON = + ''; + +describe('parseSvg', () => { + it('parses valid SVGs', () => { + expect(parseSvg(SNAP_ICON)).toMatchInlineSnapshot(` + { + "@_fill": "none", + "@_height": 25, + "@_width": 24, + "@_xmlns": "http://www.w3.org/2000/svg", + "path": { + "@_d": "M17.037 0H6.975C2.605 0 0 2.617 0 6.987v10.05c0 4.37 2.605 6.975 6.975 6.975h10.05c4.37 0 6.975-2.605 6.975-6.976V6.988C24.012 2.617 21.407 0 17.037 0ZM11.49 17.757c0 .36-.18.684-.492.876a.975.975 0 0 1-.54.156 1.11 1.11 0 0 1-.469-.108l-4.202-2.1a1.811 1.811 0 0 1-.985-1.61v-3.973c0-.36.18-.685.493-.877a1.04 1.04 0 0 1 1.008-.048l4.202 2.101a1.8 1.8 0 0 1 .997 1.609v3.974h-.012Zm-.252-6.423L6.723 8.896a1.045 1.045 0 0 1-.528-.924c0-.384.204-.744.528-.924l4.515-2.438a1.631 1.631 0 0 1 1.524 0l4.515 2.438c.324.18.528.528.528.924s-.204.744-.528.924l-4.515 2.438c-.24.132-.504.192-.768.192a1.54 1.54 0 0 1-.756-.192Zm7.972 3.638c0 .684-.385 1.308-.997 1.608l-4.202 2.101c-.144.072-.3.108-.468.108a.975.975 0 0 1-.54-.156 1.017 1.017 0 0 1-.493-.876v-3.974c0-.684.384-1.309.997-1.609l4.202-2.101a1.04 1.04 0 0 1 1.008.048c.313.192.493.516.493.877v3.974Z", + "@_fill": "#24272A", + }, + } + `); + }); + + it('returns an empty object for empty SVGs', () => { + expect(parseSvg('')).toStrictEqual({}); + }); + + it('throws for invalid SVGs', () => { + expect(() => parseSvg('')).toThrow('Snap icon must be a valid SVG.'); + expect(() => parseSvg('foo')).toThrow('Snap icon must be a valid SVG.'); + }); +}); diff --git a/packages/snaps-sdk/src/internals/svg.ts b/packages/snaps-sdk/src/internals/svg.ts new file mode 100644 index 0000000000..9ff170308c --- /dev/null +++ b/packages/snaps-sdk/src/internals/svg.ts @@ -0,0 +1,48 @@ +import { assert, hasProperty, isObject } from '@metamask/utils'; +import { XMLParser } from 'fast-xml-parser'; + +/** + * Parse and validate a string as an SVG. + * + * @param svg - An SVG string. + * @returns The SVG, its attributes and children in an object format. + */ +export function parseSvg(svg: string) { + try { + const trimmed = svg.trim(); + + assert(trimmed.length > 0); + + const parser = new XMLParser({ + ignoreAttributes: false, + parseAttributeValue: true, + }); + const parsed = parser.parse(trimmed, true); + + assert(hasProperty(parsed, 'svg')); + + // Empty SVGs are not returned as objects + if (!isObject(parsed.svg)) { + return {}; + } + + return parsed.svg; + } catch { + throw new Error('Snap icon must be a valid SVG.'); + } +} + +/** + * Validate that a string is a valid SVG. + * + * @param svg - An SVG string. + * @returns True if the SVG is valid otherwise false. + */ +export function isSvg(svg: string) { + try { + parseSvg(svg); + return true; + } catch { + return false; + } +} diff --git a/packages/snaps-sdk/src/types/handlers/user-input.test.ts b/packages/snaps-sdk/src/types/handlers/user-input.test.ts index d7be7575f9..97194fc796 100644 --- a/packages/snaps-sdk/src/types/handlers/user-input.test.ts +++ b/packages/snaps-sdk/src/types/handlers/user-input.test.ts @@ -2,8 +2,9 @@ import { UserInputEventType } from './user-input'; describe('UserInputEventType', () => { it('has the correct values', () => { - expect(Object.values(UserInputEventType)).toHaveLength(2); + expect(Object.values(UserInputEventType)).toHaveLength(3); expect(UserInputEventType.ButtonClickEvent).toBe('ButtonClickEvent'); expect(UserInputEventType.FormSubmitEvent).toBe('FormSubmitEvent'); + expect(UserInputEventType.InputChangeEvent).toBe('InputChangeEvent'); }); }); diff --git a/packages/snaps-sdk/src/types/handlers/user-input.ts b/packages/snaps-sdk/src/types/handlers/user-input.ts index 89d5d44c93..2e7ddb8531 100644 --- a/packages/snaps-sdk/src/types/handlers/user-input.ts +++ b/packages/snaps-sdk/src/types/handlers/user-input.ts @@ -2,6 +2,7 @@ import type { Infer } from 'superstruct'; import { assign, literal, + nullable, object, optional, record, @@ -11,14 +12,16 @@ import { /** * The type of user input event fired. - * Currently only two events are supported: + * Currently only three events are supported: * * - `ButtonClickEvent` - A button has been clicked in the UI. * - `FormSubmitEvent` - A Form has been submitted in the UI. + * - `InputChangeEvent` - The value of an input field has changed in the UI. */ export enum UserInputEventType { ButtonClickEvent = 'ButtonClickEvent', FormSubmitEvent = 'FormSubmitEvent', + InputChangeEvent = 'InputChangeEvent', } export const GenericEventStruct = object({ @@ -30,6 +33,7 @@ export const ButtonClickEventStruct = assign( GenericEventStruct, object({ type: literal(UserInputEventType.ButtonClickEvent), + name: optional(string()), }), ); @@ -37,14 +41,24 @@ export const FormSubmitEventStruct = assign( GenericEventStruct, object({ type: literal(UserInputEventType.FormSubmitEvent), - value: record(string(), string()), + value: record(string(), nullable(string())), name: string(), }), ); +export const InputChangeEventStruct = assign( + GenericEventStruct, + object({ + type: literal(UserInputEventType.InputChangeEvent), + name: string(), + value: string(), + }), +); + export const UserInputEventStruct = union([ ButtonClickEventStruct, FormSubmitEventStruct, + InputChangeEventStruct, ]); /** @@ -55,7 +69,7 @@ export const UserInputEventStruct = union([ * @property value - The value associated with the event. Only available when an {@link UserInputEventType.FormSubmitEvent} is fired. * It contains the form values submitted. */ -type UserInputEvent = Infer; +export type UserInputEvent = Infer; /** * The `onUserInput` handler. This is called when an user input event is fired in the UI. diff --git a/packages/snaps-sdk/src/types/images.ts b/packages/snaps-sdk/src/types/images.ts new file mode 100644 index 0000000000..c36e8789cd --- /dev/null +++ b/packages/snaps-sdk/src/types/images.ts @@ -0,0 +1,15 @@ +// eslint-disable-next-line import/unambiguous +declare module '*.svg' { + const content: string; + export default content; +} + +declare module '*.png' { + const content: string; + export default content; +} + +declare module '*.jpg' { + const content: string; + export default content; +} diff --git a/packages/snaps-sdk/src/types/index.ts b/packages/snaps-sdk/src/types/index.ts index a297aeca0c..fafc24fe65 100644 --- a/packages/snaps-sdk/src/types/index.ts +++ b/packages/snaps-sdk/src/types/index.ts @@ -1,6 +1,8 @@ // This is intentionally imported, rather than re-exported. -// eslint-disable-next-line import/no-unassigned-import +/* eslint-disable import/no-unassigned-import */ import './global'; +import './images'; +/* eslint-enable import/no-unassigned-import */ export * from './caip'; export * from './handlers'; diff --git a/packages/snaps-sdk/src/types/permissions.ts b/packages/snaps-sdk/src/types/permissions.ts index 29aaff6956..e30fc39dfa 100644 --- a/packages/snaps-sdk/src/types/permissions.ts +++ b/packages/snaps-sdk/src/types/permissions.ts @@ -9,6 +9,18 @@ export type Cronjob = { request: Omit; }; +export type NameLookupMatchers = + | { + tlds: string[]; + } + | { + schemes: string[]; + } + | { + tlds: string[]; + schemes: string[]; + }; + export type Bip32Entropy = { curve: 'secp256k1' | 'ed25519'; path: string[]; @@ -37,7 +49,7 @@ export type InitialPermissions = Partial<{ }; 'endowment:name-lookup': { chains?: ChainId[]; - matchers?: { tlds?: string[]; schemes?: string[] }; + matchers?: NameLookupMatchers; maxRequestTime?: number; }; 'endowment:network-access': EmptyObject; diff --git a/packages/snaps-sdk/src/types/provider.test.ts b/packages/snaps-sdk/src/types/provider.test.ts index 9eba420a2f..fa2c14f107 100644 --- a/packages/snaps-sdk/src/types/provider.test.ts +++ b/packages/snaps-sdk/src/types/provider.test.ts @@ -5,8 +5,8 @@ import type { SnapsEthereumProvider, SnapsProvider } from './provider'; describe('SnapsEthereumProvider', () => { it('only has the expected methods', () => { expectTypeOf().toHaveProperty('request'); - expectTypeOf().toHaveProperty('on'); - expectTypeOf().toHaveProperty('removeListener'); + expectTypeOf().not.toHaveProperty('on'); + expectTypeOf().not.toHaveProperty('removeListener'); expectTypeOf().not.toHaveProperty('isConnected'); }); }); diff --git a/packages/snaps-sdk/src/types/provider.ts b/packages/snaps-sdk/src/types/provider.ts index 2739746209..8aa6f1babd 100644 --- a/packages/snaps-sdk/src/types/provider.ts +++ b/packages/snaps-sdk/src/types/provider.ts @@ -11,7 +11,7 @@ export type SnapsEthereumProvider = Pick< BaseProviderInstance, // Snaps have access to a limited set of methods. This is enforced by the // `proxyStreamProvider` function in `@metamask/snaps-execution-environments`. - 'request' | 'on' | 'removeListener' + 'request' >; /** diff --git a/packages/snaps-sdk/src/ui/components/address.test.ts b/packages/snaps-sdk/src/ui/components/address.test.ts index 9dee12da70..4e1a5e4de3 100644 --- a/packages/snaps-sdk/src/ui/components/address.test.ts +++ b/packages/snaps-sdk/src/ui/components/address.test.ts @@ -18,8 +18,15 @@ describe('address', () => { }); it('validates the args', () => { + // @ts-expect-error - Invalid args. expect(() => address({ value: 'foo' })).toThrow( - 'Invalid address component: At path: value -- Expected a string matching `/0x[a-fA-F0-9]{40}/` but received "foo"', + 'Invalid address component: At path: value -- Expected a string matching `/^0x[0-9a-fA-F]{40}$/` but received "foo"', + ); + + expect(() => + address({ value: '0x4bbeEB066eD09B7AEd07bF39EEe0460DFa2615200' }), + ).toThrow( + 'Invalid address component: At path: value -- Expected a string matching `/^0x[0-9a-fA-F]{40}$/` but received "0x4bbeEB066eD09B7AEd07bF39EEe0460DFa2615200"', ); // @ts-expect-error - Invalid args. diff --git a/packages/snaps-sdk/src/ui/components/address.ts b/packages/snaps-sdk/src/ui/components/address.ts index 349e1ae7a7..a51684ede6 100644 --- a/packages/snaps-sdk/src/ui/components/address.ts +++ b/packages/snaps-sdk/src/ui/components/address.ts @@ -1,5 +1,6 @@ +import { HexChecksumAddressStruct } from '@metamask/utils'; import type { Infer } from 'superstruct'; -import { assign, literal, object, pattern, string } from 'superstruct'; +import { assign, literal, object } from 'superstruct'; import { createBuilder } from '../builder'; import { LiteralStruct, NodeType } from '../nodes'; @@ -8,7 +9,7 @@ export const AddressStruct = assign( LiteralStruct, object({ type: literal(NodeType.Address), - value: pattern(string(), /0x[a-fA-F0-9]{40}/u), + value: HexChecksumAddressStruct, }), ); diff --git a/packages/snaps-sdk/src/ui/components/image.ts b/packages/snaps-sdk/src/ui/components/image.ts index 2201df272c..c498cab685 100644 --- a/packages/snaps-sdk/src/ui/components/image.ts +++ b/packages/snaps-sdk/src/ui/components/image.ts @@ -1,7 +1,7 @@ -import isSvg from 'is-svg'; import type { Infer } from 'superstruct'; import { assign, literal, object, refine, string } from 'superstruct'; +import { isSvg } from '../../internals'; import { createBuilder } from '../builder'; import { NodeStruct, NodeType } from '../nodes'; diff --git a/packages/snaps-sdk/src/ui/components/input.test.ts b/packages/snaps-sdk/src/ui/components/input.test.ts index 08fbe3d4a3..87a0bfc0c3 100644 --- a/packages/snaps-sdk/src/ui/components/input.test.ts +++ b/packages/snaps-sdk/src/ui/components/input.test.ts @@ -10,6 +10,7 @@ describe('Input', () => { inputType: InputType.Text, placeholder: 'Type here...', label: 'Hello', + error: 'Invalid input', }), ).toStrictEqual({ type: NodeType.Input, @@ -18,6 +19,7 @@ describe('Input', () => { inputType: InputType.Text, placeholder: 'Type here...', label: 'Hello', + error: 'Invalid input', }); expect( diff --git a/packages/snaps-sdk/src/ui/components/input.ts b/packages/snaps-sdk/src/ui/components/input.ts index 915b24fed5..3bc47f3c11 100644 --- a/packages/snaps-sdk/src/ui/components/input.ts +++ b/packages/snaps-sdk/src/ui/components/input.ts @@ -32,6 +32,7 @@ export const InputStruct = assign( ), placeholder: optional(string()), label: optional(string()), + error: optional(string()), }), ); @@ -44,6 +45,7 @@ export const InputStruct = assign( * @property inputType - An optional type, either `text`, `password` or `number`. * @property placeholder - An optional input placeholder. * @property label - An optional input label. + * @property error - An optional error text. */ export type Input = Infer; @@ -57,6 +59,7 @@ export type Input = Infer; * @param args.inputType - An optional type, either `text`, `password` or `number`. * @param args.placeholder - An optional input placeholder. * @param args.label - An optional input label. + * @param args.error - An optional error text. * @returns The input node as an object. * @example * const node = input('myInput'); diff --git a/packages/snaps-sdk/src/ui/index.ts b/packages/snaps-sdk/src/ui/index.ts index 722e59d03c..e3b417437f 100644 --- a/packages/snaps-sdk/src/ui/index.ts +++ b/packages/snaps-sdk/src/ui/index.ts @@ -1,3 +1,4 @@ export * from './components'; export * from './component'; +export type { NodeWithChildren } from './nodes'; export { NodeType } from './nodes'; diff --git a/packages/snaps-sdk/src/ui/nodes.ts b/packages/snaps-sdk/src/ui/nodes.ts index 6daa430153..ca299e835e 100644 --- a/packages/snaps-sdk/src/ui/nodes.ts +++ b/packages/snaps-sdk/src/ui/nodes.ts @@ -1,6 +1,8 @@ import type { Infer } from 'superstruct'; import { assign, object, string, unknown } from 'superstruct'; +import type { Form, Panel } from './components'; + /** * The supported node types. This is based on SIP-7. * @@ -22,6 +24,11 @@ export enum NodeType { Form = 'form', } +/** + * The nodes with a children. + */ +export type NodeWithChildren = Panel | Form; + /** * @internal */ diff --git a/packages/snaps-sdk/tsconfig.json b/packages/snaps-sdk/tsconfig.json index 7d6092a4d3..c3cd554d5a 100644 --- a/packages/snaps-sdk/tsconfig.json +++ b/packages/snaps-sdk/tsconfig.json @@ -3,5 +3,5 @@ "compilerOptions": { "baseUrl": "./" }, - "include": ["./src"] + "include": ["./src", "package.json", "tsup.config.ts"] } diff --git a/packages/snaps-sdk/tsup.config.ts b/packages/snaps-sdk/tsup.config.ts new file mode 100644 index 0000000000..d1b43a0b64 --- /dev/null +++ b/packages/snaps-sdk/tsup.config.ts @@ -0,0 +1,22 @@ +import deepmerge from 'deepmerge'; +import type { Options } from 'tsup'; + +import packageJson from './package.json'; + +// `tsup.config.ts` is not under `rootDir`, so we need to use `require` instead. +// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires +const { default: baseConfig } = require('../../tsup.config'); + +delete baseConfig.entry; + +const config: Options = { + name: packageJson.name, + entry: ['src/index.ts'], + + // Esbuild is not deterministic when code splitting is enabled. This is + // problematic for building the example Snaps in this repository, so we + // disable code splitting here. + splitting: false, +}; + +export default deepmerge(baseConfig, config); diff --git a/packages/snaps-simulator/jest.config.js b/packages/snaps-simulator/jest.config.js index 7fabb91b41..270b1d2258 100644 --- a/packages/snaps-simulator/jest.config.js +++ b/packages/snaps-simulator/jest.config.js @@ -8,7 +8,7 @@ module.exports = deepmerge(baseConfig, { branches: 72.5, functions: 79.28, lines: 89.37, - statements: 89.26, + statements: 89.16, }, }, setupFiles: ['./jest.setup.js'], diff --git a/packages/snaps-simulator/package.json b/packages/snaps-simulator/package.json index 071d66164d..267ba7e5ca 100644 --- a/packages/snaps-simulator/package.json +++ b/packages/snaps-simulator/package.json @@ -1,35 +1,14 @@ { "name": "@metamask/snaps-simulator", "version": "2.4.3", + "private": true, "description": "A simulator for MetaMask Snaps, to be used for testing and development", "homepage": "https://github.com/MetaMask/snaps#readme", - "bugs": { - "url": "https://github.com/MetaMask/snaps/issues" - }, - "repository": { - "type": "git", - "url": "https://github.com/MetaMask/snaps.git" - }, "sideEffects": false, - "main": "./dist/cjs/index.js", - "module": "./dist/esm/index.js", - "types": "./dist/types/index.d.ts", - "files": [ - "dist/cjs/**", - "dist/esm/**", - "dist/src/**", - "dist/webpack/**" - ], "scripts": { - "build": "yarn build:source && yarn build:types && yarn build:webpack", - "build:source": "yarn build:esm && yarn build:cjs", - "build:types": "tsc --project tsconfig.build.json", - "build:esm": "swc src --out-dir dist/esm --config-file ../../.swcrc.build.json --config module.type=es6", - "build:cjs": "swc src --out-dir dist/cjs --config-file ../../.swcrc.build.json --config module.type=commonjs", + "build": "yarn build:vendor && yarn build:webpack", "build:webpack": "yarn webpack --config-name main --config-name test --mode production --progress", "build:vendor": "webpack --config-name vendor --mode production --progress", - "build:clean": "rimraf dist && yarn build", - "build:post-tsc": "yarn build:vendor && yarn build:webpack", "lint": "yarn lint:eslint && yarn lint:misc --check && yarn lint:changelog && yarn lint:dependencies", "lint:changelog": "../../scripts/validate-changelog.sh @metamask/snaps-simulator", "lint:eslint": "eslint . --cache --ext js,ts,jsx,tsx", @@ -51,12 +30,12 @@ "@emotion/react": "^11.10.8", "@emotion/styled": "^11.10.8", "@ethersproject/units": "^5.7.0", - "@metamask/base-controller": "^4.1.0", - "@metamask/browser-passworder": "^4.3.0", + "@metamask/base-controller": "^5.0.1", "@metamask/eth-json-rpc-middleware": "^12.1.0", - "@metamask/json-rpc-engine": "^7.3.2", + "@metamask/json-rpc-engine": "^8.0.1", + "@metamask/json-rpc-middleware-stream": "^7.0.1", "@metamask/key-tree": "^9.0.0", - "@metamask/permission-controller": "^8.0.0", + "@metamask/permission-controller": "^9.0.2", "@metamask/snaps-controllers": "workspace:^", "@metamask/snaps-execution-environments": "workspace:^", "@metamask/snaps-rpc-methods": "workspace:^", @@ -68,7 +47,6 @@ "date-fns": "^2.30.0", "fast-deep-equal": "^3.1.3", "framer-motion": "^10.12.8", - "json-rpc-middleware-stream": "^5.0.0", "monaco-editor": "^0.38.0", "react": "^18.2.0", "react-dnd": "^16.0.1", @@ -91,7 +69,6 @@ "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10", "@redux-saga/is": "^1.1.3", "@redux-saga/symbols": "^1.1.3", - "@swc/cli": "^0.1.62", "@swc/core": "1.3.78", "@testing-library/react": "^14.0.0", "@types/express": "^4.17.17", @@ -103,7 +80,6 @@ "@typescript-eslint/eslint-plugin": "^5.42.1", "@typescript-eslint/parser": "^5.42.1", "assert": "^2.0.0", - "constants-browserify": "^1.0.0", "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.7.3", "deepmerge": "^4.2.2", @@ -140,6 +116,7 @@ "terser-webpack-plugin": "^5.3.9", "ts-node": "^10.9.1", "tsconfig-paths-webpack-plugin": "^4.0.1", + "tsup": "^8.0.1", "typescript": "~4.8.4", "webpack": "^5.88.0", "webpack-cli": "^5.1.4", @@ -149,9 +126,5 @@ "engines": { "node": "^18.16 || >=20" }, - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.org/" - }, "packageManager": "yarn@3.2.1" } diff --git a/packages/snaps-simulator/src/components/Icon.tsx b/packages/snaps-simulator/src/components/Icon.tsx index 751f88e096..2aa7b2d5e4 100644 --- a/packages/snaps-simulator/src/components/Icon.tsx +++ b/packages/snaps-simulator/src/components/Icon.tsx @@ -225,7 +225,7 @@ export const Icon: ForwardRefExoticComponent = forwardRef( return ( {alt} { describe('updateSnapState', () => { it('puts the new snap state', async () => { - await expectSaga(updateSnapState, snapId, 'foo', true) + await expectSaga(updateSnapState, snapId, { foo: 'bar' }, true) .withState({ simulation: { snapState: null, }, }) - .put(setSnapState('foo')) + .put(setSnapState(JSON.stringify({ foo: 'bar' }))) .silentRun(); }); it('puts the new unencrypted snap state', async () => { - await expectSaga(updateSnapState, snapId, 'bar', false) + await expectSaga(updateSnapState, snapId, { foo: 'bar' }, false) .withState({ simulation: { unencryptedSnapState: null, }, }) - .put(setUnencryptedSnapState('bar')) + .put(setUnencryptedSnapState(JSON.stringify({ foo: 'bar' }))) .silentRun(); }); }); @@ -166,11 +166,11 @@ describe('getSnapState', () => { await expectSaga(getSnapState, snapId, true) .withState({ simulation: { - snapState: 'foo', + snapState: JSON.stringify({ foo: 'bar' }), }, }) .select(getSnapStateSelector) - .returns('foo') + .returns({ foo: 'bar' }) .silentRun(); }); @@ -178,11 +178,11 @@ describe('getSnapState', () => { await expectSaga(getSnapState, snapId, false) .withState({ simulation: { - unencryptedSnapState: 'bar', + unencryptedSnapState: JSON.stringify({ foo: 'bar' }), }, }) .select(getUnencryptedSnapStateSelector) - .returns('bar') + .returns({ foo: 'bar' }) .silentRun(); }); }); diff --git a/packages/snaps-simulator/src/features/simulation/hooks.ts b/packages/snaps-simulator/src/features/simulation/hooks.ts index 8090d8cb04..cb9c521b34 100644 --- a/packages/snaps-simulator/src/features/simulation/hooks.ts +++ b/packages/snaps-simulator/src/features/simulation/hooks.ts @@ -1,7 +1,16 @@ import { AuxiliaryFileEncoding } from '@metamask/snaps-sdk'; -import type { DialogType, NotifyParams, Component } from '@metamask/snaps-sdk'; +import type { + DialogType, + NotifyParams, + Component, + Json, +} from '@metamask/snaps-sdk'; import type { VirtualFile } from '@metamask/snaps-utils'; -import { encodeAuxiliaryFile, normalizeRelative } from '@metamask/snaps-utils'; +import { + encodeAuxiliaryFile, + normalizeRelative, + parseJson, +} from '@metamask/snaps-utils'; import { nanoid } from '@reduxjs/toolkit'; import type { SagaIterator } from 'redux-saga'; import { call, put, select, take } from 'redux-saga/effects'; @@ -144,13 +153,14 @@ export function* showInAppNotification( */ export function* updateSnapState( _snapId: string, - newSnapState: string | null, + newSnapState: Record | null, encrypted: boolean, ): SagaIterator { + const stringified = JSON.stringify(newSnapState); yield put( encrypted - ? setSnapState(newSnapState) - : setUnencryptedSnapState(newSnapState), + ? setSnapState(stringified) + : setUnencryptedSnapState(stringified), ); } @@ -169,7 +179,7 @@ export function* getSnapState( const state: string = yield select( encrypted ? getSnapStateSelector : getUnencryptedSnapStateSelector, ); - return state; + return parseJson(state); } /** diff --git a/packages/snaps-simulator/src/features/simulation/sagas.ts b/packages/snaps-simulator/src/features/simulation/sagas.ts index fca0e93c9f..ec5d522d06 100644 --- a/packages/snaps-simulator/src/features/simulation/sagas.ts +++ b/packages/snaps-simulator/src/features/simulation/sagas.ts @@ -1,7 +1,7 @@ import { ControllerMessenger } from '@metamask/base-controller'; -import { encrypt, decrypt } from '@metamask/browser-passworder'; import { createFetchMiddleware } from '@metamask/eth-json-rpc-middleware'; import { JsonRpcEngine } from '@metamask/json-rpc-engine'; +import { createEngineStream } from '@metamask/json-rpc-middleware-stream'; import { mnemonicPhraseToBytes } from '@metamask/key-tree'; import type { GenericPermissionController } from '@metamask/permission-controller'; import { @@ -30,7 +30,6 @@ import type { import { logError, unwrapError } from '@metamask/snaps-utils'; import { getSafeJson } from '@metamask/utils'; import type { PayloadAction } from '@reduxjs/toolkit'; -import { createEngineStream } from 'json-rpc-middleware-stream'; import { pipeline } from 'readable-stream'; import type { SagaIterator } from 'redux-saga'; import { all, call, put, select, takeLatest } from 'redux-saga/effects'; @@ -92,8 +91,6 @@ export function* initSaga({ payload }: PayloadAction) { ...buildSnapRestrictedMethodSpecifications([], { ...sharedHooks, // TODO: Add all the hooks required - encrypt, - decrypt, // TODO: Allow changing this? getLocale: async () => Promise.resolve('en'), getUnlockPromise: async () => Promise.resolve(true), @@ -120,6 +117,8 @@ export function* initSaga({ payload }: PayloadAction) { const subjectMetadataController = new SubjectMetadataController({ messenger: controllerMessenger.getRestricted({ name: 'SubjectMetadataController', + allowedActions: [], + allowedEvents: [], }), subjectCacheLimit: 100, }); @@ -136,6 +135,7 @@ export function* initSaga({ payload }: PayloadAction) { `SnapController:install`, `SubjectMetadataController:getSubjectMetadata`, ] as any, + allowedEvents: [], }), caveatSpecifications: { ...snapsCaveatsSpecifications, @@ -179,6 +179,8 @@ export function* initSaga({ payload }: PayloadAction) { iframeUrl: new URL(environmentUrl), messenger: controllerMessenger.getRestricted({ name: 'ExecutionService', + allowedActions: [], + allowedEvents: [], }), setupSnapProvider: (_snapId, rpcStream) => { const mux = setupMultiplex(rpcStream, 'snapStream'); diff --git a/packages/snaps-simulator/src/types.d.ts b/packages/snaps-simulator/src/types.d.ts deleted file mode 100644 index b0c64559e1..0000000000 --- a/packages/snaps-simulator/src/types.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line import/unambiguous -declare module '*.svg'; diff --git a/packages/snaps-simulator/tsconfig.json b/packages/snaps-simulator/tsconfig.json index ff5642e437..ab6982a118 100644 --- a/packages/snaps-simulator/tsconfig.json +++ b/packages/snaps-simulator/tsconfig.json @@ -5,7 +5,7 @@ "jsx": "react-jsx", "resolveJsonModule": true }, - "include": ["./src", "webpack.config.ts", "package.json"], + "include": ["./src", "webpack.config.ts", "package.json", "tsup.config.ts"], "references": [ { "path": "../snaps-rpc-methods" }, { "path": "../snaps-controllers" }, diff --git a/packages/snaps-simulator/tsup.config.ts b/packages/snaps-simulator/tsup.config.ts new file mode 100644 index 0000000000..3eaf645296 --- /dev/null +++ b/packages/snaps-simulator/tsup.config.ts @@ -0,0 +1,14 @@ +import deepmerge from 'deepmerge'; +import type { Options } from 'tsup'; + +import packageJson from './package.json'; + +// `tsup.config.ts` is not under `rootDir`, so we need to use `require` instead. +// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires +const { default: baseConfig } = require('../../tsup.config'); + +const config: Options = { + name: packageJson.name, +}; + +export default deepmerge(baseConfig, config); diff --git a/packages/snaps-simulator/webpack.config.ts b/packages/snaps-simulator/webpack.config.ts index c66c6df481..e0dd408e52 100644 --- a/packages/snaps-simulator/webpack.config.ts +++ b/packages/snaps-simulator/webpack.config.ts @@ -14,7 +14,6 @@ import { DllPlugin, DllReferencePlugin, EnvironmentPlugin, - NormalModuleReplacementPlugin, } from 'webpack'; import type { Configuration as DevServerConfiguration } from 'webpack-dev-server'; import { merge } from 'webpack-merge'; @@ -59,27 +58,9 @@ const baseConfig: Configuration = { baseUrl: __dirname, }), ], - /* eslint-disable @typescript-eslint/naming-convention */ fallback: { - assert: false, - child_process: false, - constants: false, - crypto: false, - fs: false, - http: false, - https: false, - module: false, stream: require.resolve('stream-browserify'), - _stream_transform: require.resolve( - 'readable-stream/lib/_stream_transform.js', - ), - os: false, - path: false, - util: false, - worker_threads: false, - zlib: false, }, - /* eslint-enable @typescript-eslint/naming-convention */ }, plugins: [ new ProvidePlugin({ @@ -119,6 +100,11 @@ const vendorConfig: Configuration = merge(baseConfig, { }, resolve: { extensions: ['.js', '.jsx'], + fallback: { + http: false, + https: false, + zlib: false, + }, }, plugins: [ new DllPlugin({ @@ -152,17 +138,6 @@ const baseAppConfig = merge( }, ], }, - /* eslint-disable @typescript-eslint/naming-convention */ - externals: { - 'node:module': 'commonjs module', - }, - resolve: { - fallback: { - assert: require.resolve('assert/'), - constants: require.resolve('constants-browserify'), - }, - }, - /* eslint-enable @typescript-eslint/naming-convention */ plugins: [ new DllReferencePlugin({ manifest: VENDOR_MANIFEST_PATH, @@ -183,14 +158,6 @@ const baseAppConfig = merge( }, ], }), - - // Stop attempting to bundle the Node.js execution services. They are - // not used in the browser, and attempting to bundle them causes - // errors. - new NormalModuleReplacementPlugin( - /.*services\/node.*/u, - resolve(__dirname, 'src', 'stub.ts'), - ), ], cache: { type: 'filesystem', diff --git a/packages/snaps-utils/CHANGELOG.md b/packages/snaps-utils/CHANGELOG.md index 75665dcb48..f061a4b36e 100644 --- a/packages/snaps-utils/CHANGELOG.md +++ b/packages/snaps-utils/CHANGELOG.md @@ -6,6 +6,58 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [7.2.0] +### Added +- Add `getJsonSizeUnsafe` ([#2342](https://github.com/MetaMask/snaps/pull/2342)) + +## [7.1.0] +### Added +- Add derivation path for Nimiq ([#2309](https://github.com/MetaMask/snaps/pull/2309)) + +### Fixed +- Disable GitHub flavored Markdown when lexing ([#2317](https://github.com/MetaMask/snaps/pull/2317)) + +## [7.0.4] +### Changed +- Bump MetaMask dependencies ([#2270](https://github.com/MetaMask/snaps/pull/2270)) + +### Fixed +- Allow `maxRequestTime` on `endowment:rpc` ([#2291](https://github.com/MetaMask/snaps/pull/2291)) + +## [7.0.3] +### Changed +- Update markdown parsing for better link validation ([#2261](https://github.com/MetaMask/snaps/pull/2261)) +- Bump `@metamask/snaps-registry` to `^3.0.1` ([#2255](https://github.com/MetaMask/snaps/pull/2255)) + +## [7.0.2] +### Fixed +- Remove usage of `Buffer` from browser entrypoint ([#2238](https://github.com/MetaMask/snaps/pull/2238)) + +## [7.0.1] +### Fixed +- Fix minor build configuration problems ([#2220](https://github.com/MetaMask/snaps/pull/2220)) +- Fix regex for HTML comment tokens ([#2222](https://github.com/MetaMask/snaps/pull/2222)) + +## [7.0.0] +### Changed +- **BREAKING:** Update ESM build to be fully compliant with the ESM standard ([#2210](https://github.com/MetaMask/snaps/pull/2210)) +- **BREAKING:** Move Node.js exports to separate export ([#2210](https://github.com/MetaMask/snaps/pull/2210)) + - The default export is now browser-compatible. + - Node.js APIs can be imported from `/node`. +- Bump `@metamask/rpc-errors` to `^6.2.1` ([#2209](https://github.com/MetaMask/snaps/pull/2209)) + +### Removed +- **BREAKING:** Move `file` struct to CLI ([#2207](https://github.com/MetaMask/snaps/pull/2207)) + - The previously exported `file` struct can now be found in `@metamask/snaps-cli`. + +### Fixed +- Add sizing limits for custom UI ([#2199](https://github.com/MetaMask/snaps/pull/2199)) +- Properly validate links contained in rows ([#2205](https://github.com/MetaMask/snaps/pull/2205)) + +## [6.1.0] +### Added +- Add a manifest warning when no icon is found and when icon is not square ([#2185](https://github.com/MetaMask/snaps/pull/2185)) + ## [6.0.0] ### Added - Add support for dynamic user interfaces ([#1465](https://github.com/MetaMask/snaps/pull/1465), [#2126](https://github.com/MetaMask/snaps/pull/2126)) @@ -165,7 +217,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The version of the package no longer needs to match the version of all other MetaMask Snaps packages. -[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-utils@6.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-utils@7.2.0...HEAD +[7.2.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-utils@7.1.0...@metamask/snaps-utils@7.2.0 +[7.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-utils@7.0.4...@metamask/snaps-utils@7.1.0 +[7.0.4]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-utils@7.0.3...@metamask/snaps-utils@7.0.4 +[7.0.3]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-utils@7.0.2...@metamask/snaps-utils@7.0.3 +[7.0.2]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-utils@7.0.1...@metamask/snaps-utils@7.0.2 +[7.0.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-utils@7.0.0...@metamask/snaps-utils@7.0.1 +[7.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-utils@6.1.0...@metamask/snaps-utils@7.0.0 +[6.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-utils@6.0.0...@metamask/snaps-utils@6.1.0 [6.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-utils@5.2.0...@metamask/snaps-utils@6.0.0 [5.2.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-utils@5.1.2...@metamask/snaps-utils@5.2.0 [5.1.2]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-utils@5.1.1...@metamask/snaps-utils@5.1.2 diff --git a/packages/snaps-utils/coverage.json b/packages/snaps-utils/coverage.json index f4da9adeef..40a27577c7 100644 --- a/packages/snaps-utils/coverage.json +++ b/packages/snaps-utils/coverage.json @@ -1,6 +1,6 @@ { - "branches": 96.19, - "functions": 98.61, - "lines": 98.69, - "statements": 94.32 + "branches": 96.48, + "functions": 98.65, + "lines": 98.75, + "statements": 94.54 } diff --git a/packages/snaps-utils/package.json b/packages/snaps-utils/package.json index efd933869d..a4a22ba42e 100644 --- a/packages/snaps-utils/package.json +++ b/packages/snaps-utils/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/snaps-utils", - "version": "6.0.0", + "version": "7.2.0", "repository": { "type": "git", "url": "https://github.com/MetaMask/snaps.git" @@ -8,40 +8,27 @@ "sideEffects": false, "exports": { ".": { - "browser": { - "import": "./dist/esm/index.browser.js", - "require": "./dist/cjs/index.browser.js" - }, - "import": "./dist/esm/index.js", - "require": "./dist/cjs/index.js" + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/types/index.d.ts" + }, + "./node": { + "import": "./dist/node.mjs", + "require": "./dist/node.js", + "types": "./dist/types/node.d.ts" }, "./test-utils": { - "import": "./dist/esm/test-utils/index.js", - "require": "./dist/cjs/test-utils/index.js", + "import": "./dist/test-utils/index.mjs", + "require": "./dist/test-utils/index.js", "types": "./dist/types/test-utils/index.d.ts" - } - }, - "main": "./dist/cjs/index.js", - "module": "./dist/esm/index.js", - "browser": { - "./dist/cjs/index.js": "./dist/cjs/index.browser.js", - "./dist/esm/index.js": "./dist/esm/index.browser.js" + }, + "./package.json": "./package.json" }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", "types": "./dist/types/index.d.ts", - "typesVersions": { - "*": { - "*": [ - "./dist/types/index.d.ts" - ], - "test-utils": [ - "./dist/types/test-utils/index.d.ts" - ] - } - }, "files": [ - "dist/cjs/**", - "dist/esm/**", - "dist/types/**" + "dist" ], "scripts": { "test": "rimraf coverage && jest && yarn test:browser && yarn posttest", @@ -53,26 +40,24 @@ "lint": "yarn lint:eslint && yarn lint:misc --check && yarn lint:changelog && yarn lint:dependencies", "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write", "lint:changelog": "../../scripts/validate-changelog.sh @metamask/snaps-utils", - "build": "yarn build:source && yarn build:types", - "build:source": "yarn build:esm && yarn build:cjs", + "build": "tsup --clean && yarn build:types", "build:types": "tsc --project tsconfig.build.json", - "build:esm": "swc src --out-dir dist/esm --config-file ../../.swcrc.build.json --config module.type=es6", - "build:cjs": "swc src --out-dir dist/cjs --config-file ../../.swcrc.build.json --config module.type=commonjs", "build:clean": "yarn clean && yarn build", "clean": "rimraf '*.tsbuildinfo' 'dist'", "publish:preview": "yarn npm publish --tag preview", "lint:ci": "yarn lint", - "lint:dependencies": "depcheck" + "lint:dependencies": "depcheck", + "build:ci": "tsup --clean" }, "dependencies": { "@babel/core": "^7.23.2", "@babel/types": "^7.23.0", - "@metamask/base-controller": "^4.1.0", + "@metamask/base-controller": "^5.0.1", "@metamask/key-tree": "^9.0.0", - "@metamask/permission-controller": "^8.0.0", - "@metamask/rpc-errors": "^6.1.0", + "@metamask/permission-controller": "^9.0.2", + "@metamask/rpc-errors": "^6.2.1", "@metamask/slip44": "^3.1.0", - "@metamask/snaps-registry": "^3.0.0", + "@metamask/snaps-registry": "^3.1.0", "@metamask/snaps-sdk": "workspace:^", "@metamask/utils": "^8.3.0", "@noble/hashes": "^1.3.1", @@ -81,7 +66,7 @@ "cron-parser": "^4.5.0", "fast-deep-equal": "^3.1.3", "fast-json-stable-stringify": "^2.1.0", - "is-svg": "^4.4.0", + "marked": "^12.0.1", "rfdc": "^1.3.0", "semver": "^7.5.4", "ses": "^1.1.0", @@ -91,14 +76,13 @@ "devDependencies": { "@esbuild-plugins/node-globals-polyfill": "^0.2.3", "@esbuild-plugins/node-modules-polyfill": "^0.2.2", - "@lavamoat/allow-scripts": "^3.0.0", + "@lavamoat/allow-scripts": "^3.0.3", "@metamask/auto-changelog": "^3.4.4", "@metamask/eslint-config": "^12.1.0", "@metamask/eslint-config-jest": "^12.1.0", "@metamask/eslint-config-nodejs": "^12.1.0", "@metamask/eslint-config-typescript": "^12.1.0", "@metamask/post-message-stream": "^8.0.0", - "@swc/cli": "^0.1.62", "@swc/core": "1.3.78", "@swc/jest": "^0.2.26", "@types/jest": "^27.5.1", @@ -136,6 +120,7 @@ "prettier-plugin-packagejson": "^2.2.11", "rimraf": "^4.1.2", "ts-node": "^10.9.1", + "tsup": "^8.0.1", "typescript": "~4.8.4", "vite": "^4.3.9", "vite-tsconfig-paths": "^4.0.5", diff --git a/packages/snaps-utils/src/bytes.ts b/packages/snaps-utils/src/bytes.ts index 81076ad43d..c4b59a992f 100644 --- a/packages/snaps-utils/src/bytes.ts +++ b/packages/snaps-utils/src/bytes.ts @@ -1,6 +1,6 @@ import { stringToBytes } from '@metamask/utils'; -import { VirtualFile } from './virtual-file'; +import { VirtualFile } from './virtual-file/VirtualFile'; /** * Convert a bytes-like input value to a Uint8Array. diff --git a/packages/snaps-utils/src/checksum.test.ts b/packages/snaps-utils/src/checksum.test.ts index 0b46309483..8db6875a4b 100644 --- a/packages/snaps-utils/src/checksum.test.ts +++ b/packages/snaps-utils/src/checksum.test.ts @@ -3,7 +3,7 @@ import { base64 } from '@scure/base'; import { webcrypto } from 'crypto'; import { checksum, checksumFiles } from './checksum'; -import { VirtualFile } from './index.browser'; +import { VirtualFile } from './virtual-file'; const EMPTY_SHA256 = '47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='; diff --git a/packages/snaps-utils/src/checksum.ts b/packages/snaps-utils/src/checksum.ts index 8f3edf21de..86be4dac42 100644 --- a/packages/snaps-utils/src/checksum.ts +++ b/packages/snaps-utils/src/checksum.ts @@ -2,7 +2,7 @@ import { assert, concatBytes } from '@metamask/utils'; import { sha256 } from '@noble/hashes/sha256'; import { getBytes } from './bytes'; -import type { VirtualFile } from './virtual-file/VirtualFile'; +import type { VirtualFile } from './virtual-file'; /** * Calculates checksum for a single byte array. diff --git a/packages/snaps-utils/src/derivation-paths.ts b/packages/snaps-utils/src/derivation-paths.ts index 35811c56f5..5b76478f37 100644 --- a/packages/snaps-utils/src/derivation-paths.ts +++ b/packages/snaps-utils/src/derivation-paths.ts @@ -135,6 +135,11 @@ export const SNAPS_DERIVATION_PATHS: SnapsDerivationPath[] = [ curve: 'secp256k1', name: 'Mina', }, + { + path: ['m', `44'`, `242'`], + curve: 'ed25519', + name: 'Nimiq', + }, { path: ['m', `44'`, `1729'`, `0'`, `0'`], curve: 'ed25519', diff --git a/packages/snaps-utils/src/errors.test.ts b/packages/snaps-utils/src/errors.test.ts index f674bda695..177e708bf2 100644 --- a/packages/snaps-utils/src/errors.test.ts +++ b/packages/snaps-utils/src/errors.test.ts @@ -79,7 +79,6 @@ describe('WrappedSnapError', () => { code: -32603, message: 'foo', stack: error.stack, - data: {}, }, }, }, @@ -104,7 +103,6 @@ describe('WrappedSnapError', () => { code: -32603, message: 'foo', stack: error.stack, - data: {}, }, }, }, diff --git a/packages/snaps-utils/src/fs.ts b/packages/snaps-utils/src/fs.ts index c4b5e37ce8..fedd52c4b7 100644 --- a/packages/snaps-utils/src/fs.ts +++ b/packages/snaps-utils/src/fs.ts @@ -5,7 +5,7 @@ import pathUtils from 'path'; import { parseJson } from './json'; import type { VirtualFile } from './virtual-file'; -import { readVirtualFile } from './virtual-file'; +import { readVirtualFile } from './virtual-file/node'; /** * Checks whether the given path string resolves to an existing directory, and diff --git a/packages/snaps-utils/src/icon.test.ts b/packages/snaps-utils/src/icon.test.ts index ba61c604a3..0d05b4b09a 100644 --- a/packages/snaps-utils/src/icon.test.ts +++ b/packages/snaps-utils/src/icon.test.ts @@ -4,6 +4,7 @@ import { SVG_MAX_BYTE_SIZE, SVG_MAX_BYTE_SIZE_TEXT, assertIsSnapIcon, + getSvgDimensions, } from './icon'; import { ALTERNATIVE_SNAP_ICON } from './test-utils'; import { VirtualFile } from './virtual-file'; @@ -29,6 +30,16 @@ describe('assertIsSnapIcon', () => { ); }); + it('asserts that the file is an appropriate size when the file is represented by a string', () => { + const icon = new VirtualFile({ + value: '1'.repeat(SVG_MAX_BYTE_SIZE + 1), + path: 'foo.svg', + }); + expect(() => assertIsSnapIcon(icon)).toThrow( + `The specified SVG icon exceeds the maximum size of ${SVG_MAX_BYTE_SIZE_TEXT}.`, + ); + }); + it('asserts that the file is a valid SVG', () => { const icon = new VirtualFile({ value: stringToBytes('foo'), @@ -47,3 +58,39 @@ describe('assertIsSnapIcon', () => { expect(() => assertIsSnapIcon(icon)).not.toThrow(); }); }); + +describe('getSvgDimensions', () => { + it('uses height and width when available', () => { + expect(getSvgDimensions(ALTERNATIVE_SNAP_ICON)).toStrictEqual({ + height: 25, + width: 24, + }); + }); + + it('uses viewBox as a fallback', () => { + expect( + getSvgDimensions( + '', + ), + ).toStrictEqual({ + height: 24, + width: 24, + }); + }); + + it('returns null if no dimensions are found', () => { + expect( + getSvgDimensions( + '', + ), + ).toBeNull(); + }); + + it('throws if invalid dimensions are found', () => { + expect(() => + getSvgDimensions( + '', + ), + ).toThrow('Snap icon must be a valid SVG.'); + }); +}); diff --git a/packages/snaps-utils/src/icon.ts b/packages/snaps-utils/src/icon.ts index 17882eab18..96d121e08e 100644 --- a/packages/snaps-utils/src/icon.ts +++ b/packages/snaps-utils/src/icon.ts @@ -1,5 +1,5 @@ -import { assert } from '@metamask/utils'; -import isSvg from 'is-svg'; +import { isSvg, parseSvg } from '@metamask/snaps-sdk'; +import { assert, stringToBytes } from '@metamask/utils'; import type { VirtualFile } from './virtual-file'; @@ -8,13 +8,68 @@ export const SVG_MAX_BYTE_SIZE_TEXT = `${Math.floor( SVG_MAX_BYTE_SIZE / 1000, )}kb`; -export const assertIsSnapIcon = (icon: VirtualFile) => { +/** + * Assert that a virtual file containing a Snap icon is valid. + * + * @param icon - A virtual file containing a Snap icon. + */ +export function assertIsSnapIcon(icon: VirtualFile) { assert(icon.path.endsWith('.svg'), 'Expected snap icon to end in ".svg".'); + const byteLength = + typeof icon.value === 'string' + ? stringToBytes(icon.value).byteLength + : icon.value.byteLength; + assert( - Buffer.byteLength(icon.value, 'utf8') <= SVG_MAX_BYTE_SIZE, + byteLength <= SVG_MAX_BYTE_SIZE, `The specified SVG icon exceeds the maximum size of ${SVG_MAX_BYTE_SIZE_TEXT}.`, ); assert(isSvg(icon.toString()), 'Snap icon must be a valid SVG.'); -}; +} + +/** + * Extract the dimensions of an image from an SVG string if possible. + * + * @param svg - An SVG string. + * @returns The height and width of the SVG or null. + */ +export function getSvgDimensions(svg: string): { + height: number; + width: number; +} | null { + try { + const parsed = parseSvg(svg); + + const height = parsed['@_height']; + const width = parsed['@_width']; + + if (height && width) { + return { height, width }; + } + + const viewBox = parsed['@_viewBox']; + if (viewBox) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_minX, _minY, viewBoxWidth, viewBoxHeight] = viewBox.split(' '); + + if (viewBoxWidth && viewBoxHeight) { + const parsedWidth = parseInt(viewBoxWidth, 10); + const parsedHeight = parseInt(viewBoxHeight, 10); + + assert(Number.isInteger(parsedWidth) && parsedWidth > 0); + assert(Number.isInteger(parsedHeight) && parsedHeight > 0); + + return { + width: parsedWidth, + height: parsedHeight, + }; + } + } + } catch { + throw new Error('Snap icon must be a valid SVG.'); + } + + return null; +} diff --git a/packages/snaps-utils/src/index.browser.ts b/packages/snaps-utils/src/index.browser.ts deleted file mode 100644 index 683011275e..0000000000 --- a/packages/snaps-utils/src/index.browser.ts +++ /dev/null @@ -1,30 +0,0 @@ -export * from './array'; -export * from './auxiliary-files'; -export * from './base64'; -export * from './bytes'; -export * from './caveats'; -export * from './checksum'; -export * from './cronjob'; -export * from './deep-clone'; -export * from './default-endowments'; -export * from './derivation-paths'; -export * from './entropy'; -export * from './errors'; -export * from './handlers'; -export * from './handler-types'; -export * from './iframe'; -export * from './json'; -export * from './json-rpc'; -export * from './localization'; -export * from './logging'; -export * from './manifest/index.browser'; -export * from './namespace'; -export * from './path'; -export * from './snaps'; -export * from './strings'; -export * from './structs'; -export * from './types'; -export * from './ui'; -export * from './validation'; -export * from './versions'; -export * from './virtual-file/index.browser'; diff --git a/packages/snaps-utils/src/index.ts b/packages/snaps-utils/src/index.ts index c14c760e4c..9f07f9750d 100644 --- a/packages/snaps-utils/src/index.ts +++ b/packages/snaps-utils/src/index.ts @@ -3,15 +3,13 @@ export * from './auxiliary-files'; export * from './base64'; export * from './bytes'; export * from './caveats'; -export * from './cronjob'; export * from './checksum'; +export * from './cronjob'; export * from './deep-clone'; export * from './default-endowments'; export * from './derivation-paths'; export * from './entropy'; -export * from './eval'; export * from './errors'; -export * from './fs'; export * from './handlers'; export * from './handler-types'; export * from './iframe'; @@ -20,11 +18,8 @@ export * from './json-rpc'; export * from './localization'; export * from './logging'; export * from './manifest'; -export * from './mock'; export * from './namespace'; -export * from './npm'; export * from './path'; -export * from './post-process'; export * from './snaps'; export * from './strings'; export * from './structs'; diff --git a/packages/snaps-utils/src/json.test.ts b/packages/snaps-utils/src/json.test.ts index c1f88a2dc0..7d8b54d101 100644 --- a/packages/snaps-utils/src/json.test.ts +++ b/packages/snaps-utils/src/json.test.ts @@ -1,4 +1,4 @@ -import { parseJson } from './json'; +import { getJsonSizeUnsafe, parseJson } from './json'; describe('parseJson', () => { it('strips __proto__ and constructor', () => { @@ -7,3 +7,10 @@ describe('parseJson', () => { expect(parseJson(input)).toStrictEqual({ test: {}, test2: {} }); }); }); + +describe('getJsonSizeUnsafe', () => { + it('calculates the size of the JSON input', () => { + const input = { foo: 'bar' }; + expect(getJsonSizeUnsafe(input)).toBe(13); + }); +}); diff --git a/packages/snaps-utils/src/json.ts b/packages/snaps-utils/src/json.ts index 01053dca97..36656730db 100644 --- a/packages/snaps-utils/src/json.ts +++ b/packages/snaps-utils/src/json.ts @@ -17,3 +17,16 @@ import { getSafeJson } from '@metamask/utils'; export function parseJson(json: string) { return getSafeJson(JSON.parse(json)); } + +/** + * Get the size of a JSON blob without validating that is valid JSON. + * + * This may sometimes be preferred over `getJsonSize` for performance reasons. + * + * @param value - The JSON value to get the size of. + * @returns The size of the JSON value in bytes. + */ +export function getJsonSizeUnsafe(value: Json): number { + const json = JSON.stringify(value); + return new TextEncoder().encode(json).byteLength; +} diff --git a/packages/snaps-utils/src/manifest/index.browser.ts b/packages/snaps-utils/src/manifest/index.browser.ts deleted file mode 100644 index 4d5ffa36ab..0000000000 --- a/packages/snaps-utils/src/manifest/index.browser.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './validation'; diff --git a/packages/snaps-utils/src/manifest/index.ts b/packages/snaps-utils/src/manifest/index.ts index c7b83f75f6..4d5ffa36ab 100644 --- a/packages/snaps-utils/src/manifest/index.ts +++ b/packages/snaps-utils/src/manifest/index.ts @@ -1,2 +1 @@ -export * from './manifest'; export * from './validation'; diff --git a/packages/snaps-utils/src/manifest/manifest.test.ts b/packages/snaps-utils/src/manifest/manifest.test.ts index f460c7f935..1dfe7eedbd 100644 --- a/packages/snaps-utils/src/manifest/manifest.test.ts +++ b/packages/snaps-utils/src/manifest/manifest.test.ts @@ -128,6 +128,37 @@ describe('checkManifest', () => { expect(warnings[0]).toMatch('Missing recommended package.json properties'); }); + it('returns a warning if manifest has no defined icon', async () => { + const manifest = getSnapManifest(); + + // Remove icon + manifest.source.location.npm.iconPath = undefined; + + await fs.writeFile(MANIFEST_PATH, JSON.stringify(manifest)); + + const { warnings } = await checkManifest(BASE_PATH); + expect(warnings).toHaveLength(1); + expect(warnings[0]).toMatch( + 'No icon found in the Snap manifest. It is recommended to include an icon for the Snap. See https://docs.metamask.io/snaps/how-to/design-a-snap/#guidelines-at-a-glance for more information.', + ); + }); + + it('returns a warning if manifest has with a non 1:1 ratio', async () => { + const manifest = getSnapManifest(); + + await fs.writeFile( + join(BASE_PATH, 'images/icon.svg'), + '', + ); + await fs.writeFile(MANIFEST_PATH, JSON.stringify(manifest)); + + const { warnings } = await checkManifest(BASE_PATH); + expect(warnings).toHaveLength(1); + expect(warnings[0]).toMatch( + 'The icon in the Snap manifest is not square. It is recommended to use a square icon for the Snap.', + ); + }); + it('return errors if the manifest is invalid', async () => { await fs.writeFile( MANIFEST_PATH, diff --git a/packages/snaps-utils/src/manifest/manifest.ts b/packages/snaps-utils/src/manifest/manifest.ts index ed75f32229..558fdf4070 100644 --- a/packages/snaps-utils/src/manifest/manifest.ts +++ b/packages/snaps-utils/src/manifest/manifest.ts @@ -7,6 +7,7 @@ import pathUtils from 'path'; import { deepClone } from '../deep-clone'; import { readJsonFile } from '../fs'; +import { getSvgDimensions } from '../icon'; import { validateNpmSnap } from '../npm'; import { getSnapChecksum, @@ -15,7 +16,7 @@ import { } from '../snaps'; import type { SnapFiles, UnvalidatedSnapFiles } from '../types'; import { NpmSnapFileNames, SnapValidationFailureReason } from '../types'; -import { readVirtualFile, VirtualFile } from '../virtual-file'; +import { readVirtualFile, VirtualFile } from '../virtual-file/node'; import type { SnapManifest } from './validation'; const MANIFEST_SORT_ORDER: Record = { @@ -191,6 +192,20 @@ export async function checkManifest( ); } + if (!snapFiles.svgIcon) { + warnings.push( + 'No icon found in the Snap manifest. It is recommended to include an icon for the Snap. See https://docs.metamask.io/snaps/how-to/design-a-snap/#guidelines-at-a-glance for more information.', + ); + } + + const iconDimensions = + snapFiles.svgIcon && getSvgDimensions(snapFiles.svgIcon.toString()); + if (iconDimensions && iconDimensions.height !== iconDimensions.width) { + warnings.push( + 'The icon in the Snap manifest is not square. It is recommended to use a square icon for the Snap.', + ); + } + if (writeManifest) { try { const newManifest = `${JSON.stringify( diff --git a/packages/snaps-utils/src/manifest/node.ts b/packages/snaps-utils/src/manifest/node.ts new file mode 100644 index 0000000000..92e3b3dd96 --- /dev/null +++ b/packages/snaps-utils/src/manifest/node.ts @@ -0,0 +1,2 @@ +export * from '.'; +export * from './manifest'; diff --git a/packages/snaps-utils/src/manifest/validation.test.ts b/packages/snaps-utils/src/manifest/validation.test.ts index 9481359ace..2932a5890c 100644 --- a/packages/snaps-utils/src/manifest/validation.test.ts +++ b/packages/snaps-utils/src/manifest/validation.test.ts @@ -6,6 +6,7 @@ import { Bip32EntropyStruct, Bip32PathStruct, createSnapManifest, + EmptyObjectStruct, isSnapManifest, SnapIdsStruct, } from './validation'; @@ -152,6 +153,16 @@ describe('SnapIdsStruct', () => { }); }); +describe('EmptyObjectStruct', () => { + it('accepts an empty object', () => { + expect(is({}, EmptyObjectStruct)).toBe(true); + }); + + it('rejects non-empty objects', () => { + expect(is({ foo: 'bar' }, EmptyObjectStruct)).toBe(false); + }); +}); + describe('isSnapManifest', () => { it('returns true for a valid snap manifest', () => { expect(isSnapManifest(getSnapManifest())).toBe(true); diff --git a/packages/snaps-utils/src/manifest/validation.ts b/packages/snaps-utils/src/manifest/validation.ts index f9a1772561..469294cfc4 100644 --- a/packages/snaps-utils/src/manifest/validation.ts +++ b/packages/snaps-utils/src/manifest/validation.ts @@ -1,5 +1,5 @@ import { isValidBIP32PathSegment } from '@metamask/key-tree'; -import type { InitialPermissions } from '@metamask/snaps-sdk'; +import type { EmptyObject, InitialPermissions } from '@metamask/snaps-sdk'; import { assertStruct, ChecksumStruct, @@ -8,7 +8,7 @@ import { inMilliseconds, Duration, } from '@metamask/utils'; -import type { Infer, Struct } from 'superstruct'; +import type { Describe, Infer, Struct } from 'superstruct'; import { array, boolean, @@ -175,15 +175,20 @@ export const HandlerCaveatsStruct = object({ export type HandlerCaveats = Infer; +export const EmptyObjectStruct = object({}) as unknown as Struct< + EmptyObject, + null +>; + /* eslint-disable @typescript-eslint/naming-convention */ -export const PermissionsStruct = type({ +export const PermissionsStruct: Describe = type({ 'endowment:cronjob': optional( assign( HandlerCaveatsStruct, object({ jobs: CronjobSpecificationArrayStruct }), ), ), - 'endowment:ethereum-provider': optional(object({})), + 'endowment:ethereum-provider': optional(EmptyObjectStruct), 'endowment:keyring': optional( assign(HandlerCaveatsStruct, KeyringOriginsStruct), ), @@ -197,9 +202,9 @@ export const PermissionsStruct = type({ }), ), ), - 'endowment:network-access': optional(object({})), + 'endowment:network-access': optional(EmptyObjectStruct), 'endowment:page-home': optional(HandlerCaveatsStruct), - 'endowment:rpc': optional(RpcOriginsStruct), + 'endowment:rpc': optional(assign(HandlerCaveatsStruct, RpcOriginsStruct)), 'endowment:signature-insight': optional( assign( HandlerCaveatsStruct, @@ -216,11 +221,11 @@ export const PermissionsStruct = type({ }), ), ), - 'endowment:webassembly': optional(object({})), - snap_dialog: optional(object({})), - snap_manageState: optional(object({})), - snap_manageAccounts: optional(object({})), - snap_notify: optional(object({})), + 'endowment:webassembly': optional(EmptyObjectStruct), + snap_dialog: optional(EmptyObjectStruct), + snap_manageState: optional(EmptyObjectStruct), + snap_manageAccounts: optional(EmptyObjectStruct), + snap_notify: optional(EmptyObjectStruct), snap_getBip32Entropy: optional(SnapGetBip32EntropyPermissionsStruct), snap_getBip32PublicKey: optional(SnapGetBip32EntropyPermissionsStruct), snap_getBip44Entropy: optional( @@ -230,8 +235,8 @@ export const PermissionsStruct = type({ Infinity, ), ), - snap_getEntropy: optional(object({})), - snap_getLocale: optional(object({})), + snap_getEntropy: optional(EmptyObjectStruct), + snap_getLocale: optional(EmptyObjectStruct), wallet_snap: optional(SnapIdsStruct), }); /* eslint-enable @typescript-eslint/naming-convention */ diff --git a/packages/snaps-utils/src/node.ts b/packages/snaps-utils/src/node.ts new file mode 100644 index 0000000000..d56f0d97ef --- /dev/null +++ b/packages/snaps-utils/src/node.ts @@ -0,0 +1,10 @@ +/* eslint-disable import/export */ + +export * from '.'; +export * from './eval'; +export * from './fs'; +export * from './manifest/node'; +export * from './mock'; +export * from './npm'; +export * from './post-process'; +export * from './virtual-file/node'; diff --git a/packages/snaps-utils/src/post-process.test.ts b/packages/snaps-utils/src/post-process.test.ts index 82863df703..a84761b7fa 100644 --- a/packages/snaps-utils/src/post-process.test.ts +++ b/packages/snaps-utils/src/post-process.test.ts @@ -173,6 +173,21 @@ describe('postProcessBundle', () => { `); }); + it('breaks up HTML comment terminators in string literals with alternative comment end', () => { + const code = ` + const foo = ''; + `; + + const processedCode = postProcessBundle(code); + expect(processedCode).toMatchInlineSnapshot(` + { + "code": "const foo = "";", + "sourceMap": null, + "warnings": [], + } + `); + }); + it('breaks up `import()` in string literals', () => { const code = ` const foo = 'foo bar import() baz'; @@ -205,6 +220,21 @@ describe('postProcessBundle', () => { `); }); + it('breaks up HTML comment terminators in template literals with alternative comment end', () => { + const code = ` + const foo = \` \${''} \${qux}\`; + `; + + const processedCode = postProcessBundle(code); + expect(processedCode).toMatchInlineSnapshot(` + { + "code": "const foo = \`\${""} \${""} \${qux}\`;", + "sourceMap": null, + "warnings": [], + } + `); + }); + it('breaks up `import()` in template literals', () => { const code = ` const foo = \`foo bar import() baz\`; diff --git a/packages/snaps-utils/src/post-process.ts b/packages/snaps-utils/src/post-process.ts index 717c8e5fdf..49c39ba708 100644 --- a/packages/snaps-utils/src/post-process.ts +++ b/packages/snaps-utils/src/post-process.ts @@ -58,13 +58,13 @@ export type PostProcessedBundle = { }; export enum PostProcessWarning { - UnsafeMathRandom = '`Math.random` was detected in the bundle. This is not a secure source of randomness.', + UnsafeMathRandom = '`Math.random` was detected in the Snap bundle. This is not a secure source of randomness, and should not be used in a secure context. Use `crypto.getRandomValues` instead.', } // The RegEx below consists of multiple groups joined by a boolean OR. // Each part consists of two groups which capture a part of each string // which needs to be split up, e.g., `