diff --git a/.eslintrc b/.eslintrc index f66c592e8..277fc3956 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,7 +2,7 @@ "env": { "browser": true, "commonjs": true, - "es6": true, + "es2021": true, "node": true, "jest": true }, @@ -11,26 +11,24 @@ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended", - "prettier", - "prettier/@typescript-eslint" + "prettier" ], "plugins": [ "import" ], "parserOptions": { "project": "tsconfig.json", - "sourceType": "module", - "ecmaVersion": 2020 + "sourceType": "module" }, "rules": { "linebreak-style": ["error", "unix"], "no-empty": 1, - "no-undef": 0, "no-useless-catch": 1, "no-prototype-builtins": 1, "no-constant-condition": 0, - "no-useless-escape" : 0, + "no-useless-escape": 0, "no-console": "error", + "require-yield": 0, "eqeqeq": ["error", "smart"], "spaced-comment": [ "warn", @@ -113,6 +111,7 @@ "@typescript-eslint/no-misused-promises": ["error", { "checksVoidReturn": false }], + "@typescript-eslint/await-thenable": ["error"], "@typescript-eslint/naming-convention": [ "error", { @@ -121,6 +120,12 @@ "leadingUnderscore": "allow", "trailingUnderscore": "allowSingleOrDouble" }, + { + "selector": "function", + "format": ["camelCase", "PascalCase"], + "leadingUnderscore": "allow", + "trailingUnderscore": "allowSingleOrDouble" + }, { "selector": "variable", "format": ["camelCase", "UPPER_CASE", "PascalCase"], diff --git a/.github/workflows/codesee-arch-diagram.yml b/.github/workflows/codesee-arch-diagram.yml new file mode 100644 index 000000000..fe56ca3f8 --- /dev/null +++ b/.github/workflows/codesee-arch-diagram.yml @@ -0,0 +1,87 @@ +on: + push: + branches: + - master + pull_request_target: + types: [opened, synchronize, reopened] + +name: CodeSee Map + +jobs: + test_map_action: + runs-on: ubuntu-latest + continue-on-error: true + name: Run CodeSee Map Analysis + steps: + - name: checkout + id: checkout + uses: actions/checkout@v2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} + fetch-depth: 0 + + # codesee-detect-languages has an output with id languages. + - name: Detect Languages + id: detect-languages + uses: Codesee-io/codesee-detect-languages-action@latest + + - name: Configure JDK 16 + uses: actions/setup-java@v2 + if: ${{ fromJSON(steps.detect-languages.outputs.languages).java }} + with: + java-version: '16' + distribution: 'zulu' + + # CodeSee Maps Go support uses a static binary so there's no setup step required. + + - name: Configure Node.js 14 + uses: actions/setup-node@v2 + if: ${{ fromJSON(steps.detect-languages.outputs.languages).javascript }} + with: + node-version: '14' + + - name: Configure Python 3.x + uses: actions/setup-python@v2 + if: ${{ fromJSON(steps.detect-languages.outputs.languages).python }} + with: + python-version: '3.10' + architecture: 'x64' + + - name: Configure Ruby '3.x' + uses: ruby/setup-ruby@v1 + if: ${{ fromJSON(steps.detect-languages.outputs.languages).ruby }} + with: + ruby-version: '3.0' + + # We need the rust toolchain because it uses rustc and cargo to inspect the package + - name: Configure Rust 1.x stable + uses: actions-rs/toolchain@v1 + if: ${{ fromJSON(steps.detect-languages.outputs.languages).rust }} + with: + toolchain: stable + + - name: Generate Map + id: generate-map + uses: Codesee-io/codesee-map-action@latest + with: + step: map + api_token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} + github_ref: ${{ github.ref }} + languages: ${{ steps.detect-languages.outputs.languages }} + + - name: Upload Map + id: upload-map + uses: Codesee-io/codesee-map-action@latest + with: + step: mapUpload + api_token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} + github_ref: ${{ github.ref }} + + - name: Insights + id: insights + uses: Codesee-io/codesee-map-action@latest + with: + step: insights + api_token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} + github_ref: ${{ github.ref }} diff --git a/.gitignore b/.gitignore index cd3216165..78a1b31bb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,13 @@ /dist .env* !.env.example +# nix /result* /builds +# node-gyp +/build +# prebuildify +/prebuilds # Logs logs diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 11a1eb825..febe7602f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,85 +1,254 @@ +workflow: + rules: + # Disable merge request pipelines + - if: $CI_MERGE_REQUEST_ID + when: never + - when: always + +default: + interruptible: true + variables: - GIT_SUBMODULE_STRATEGY: "recursive" + GH_PROJECT_PATH: "MatrixAI/${CI_PROJECT_NAME}" + GH_PROJECT_URL: "https://${GITHUB_TOKEN}@github.com/${GH_PROJECT_PATH}.git" + GIT_SUBMODULE_STRATEGY: recursive # Cache .npm - NPM_CONFIG_CACHE: "./tmp/npm" + NPM_CONFIG_CACHE: "${CI_PROJECT_DIR}/tmp/npm" # Prefer offline node module installation NPM_CONFIG_PREFER_OFFLINE: "true" # `ts-node` has its own cache - # It must use an absolute path, otherwise ts-node calls will CWD TS_CACHED_TRANSPILE_CACHE: "${CI_PROJECT_DIR}/tmp/ts-node-cache" TS_CACHED_TRANSPILE_PORTABLE: "true" + # Homebrew cache only used by macos runner + HOMEBREW_CACHE: "${CI_PROJECT_DIR}/tmp/Homebrew" -# Cached directories shared between jobs & pipelines per-branch +# Cached directories shared between jobs & pipelines per-branch per-runner cache: key: $CI_COMMIT_REF_SLUG paths: - ./tmp/npm/ - ./tmp/ts-node-cache/ + # Homebrew cache is only used by the macos runner + - ./tmp/Homebrew # `jest` cache is configured in jest.config.js - ./tmp/jest/ stages: - - check - - test - - build - - quality - - release - -lint: - image: registry.gitlab.com/matrixai/engineering/maintenance/gitlab-runner + - check # Linting, unit tests + - build # Cross-platform library compilation, unit tests + - integration # Cross-platform application bundling, integration tests, and pre-release + - release # Cross-platform distribution and deployment + +image: registry.gitlab.com/matrixai/engineering/maintenance/gitlab-runner + +check:lint: stage: check - interruptible: true + needs: [] script: - > - nix-shell -I nixpkgs=./pkgs.nix --packages nodejs --run ' - npm ci; + nix-shell --run ' npm run lint; ' + rules: + # Runs on feature and staging commits and ignores version commits + - if: $CI_COMMIT_BRANCH =~ /^(?:feature.*|staging)$/ && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Runs on tag pipeline where the tag is a prerelease or release version + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Manually run on commits other than master and ignore version commits + - if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != 'master' && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + when: manual -nix-dry: +check:nix-dry: stage: check - image: registry.gitlab.com/matrixai/engineering/maintenance/gitlab-runner + needs: [] script: - - nix-build -v -v --dry-run ./release.nix --attr application - - nix-build -v -v --dry-run ./release.nix --attr docker - - nix-build -v -v --dry-run ./release.nix --attr package.linux.x64.elf - - nix-build -v -v --dry-run ./release.nix --attr package.windows.x64.exe - - nix-build -v -v --dry-run ./release.nix --attr package.macos.x64.macho + - nix-build -v -v --dry-run ./release.nix + rules: + # Runs on feature and staging commits and ignores version commits + - if: $CI_COMMIT_BRANCH =~ /^(?:feature.*|staging)$/ && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Runs on tag pipeline where the tag is a prerelease or release version + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Manually run on commits other than master and ignore version commits + - if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != 'master' && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + when: manual -test-generate: - image: registry.gitlab.com/matrixai/engineering/maintenance/gitlab-runner +check:test-generate: stage: check interruptible: true script: - mkdir -p ./tmp - > - nix-shell -I nixpkgs=./pkgs.nix --packages bash --run ' + nix-shell --run ' ./scripts/test-pipelines.sh > ./tmp/test-pipelines.yml ' artifacts: + when: always paths: - ./tmp/test-pipelines.yml + rules: + # Runs on feature and staging commits and ignores version commits + - if: $CI_COMMIT_BRANCH =~ /^(?:feature.*|staging)$/ && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Runs on tag pipeline where the tag is a prerelease or release version + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Manually run on commits other than master and ignore version commits + - if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != 'master' && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + when: manual -test: - stage: test - # Don't implicitly inherit top-level variables in child pipeline - # All inherited variables should be explicitly defined here - # Note that variables defined here override any variables defined in the child pipeline - # This causes a bug with $CI_PROJECT_DIR, which is expanded into an empty string +check:test: + stage: check + needs: + - check:test-generate inherit: variables: false trigger: include: - artifact: tmp/test-pipelines.yml - job: test-generate + job: check:test-generate strategy: depend + rules: + # Runs on feature and staging commits and ignores version commits + - if: $CI_COMMIT_BRANCH =~ /^(?:feature.*|staging)$/ && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Runs on tag pipeline where the tag is a prerelease or release version + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Manually run on commits other than master and ignore version commits + - if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != 'master' && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + when: manual + +build:merge: + stage: build + needs: [] + allow_failure: true + script: + # Required for `gh pr create` + - git remote add upstream "$GH_PROJECT_URL" + - > + nix-shell -I nixpkgs=./pkgs.nix --packages gitAndTools.gh --run ' + gh pr create \ + --head staging \ + --base master \ + --title "ci: merge staging to master" \ + --body "This is an automatic PR generated by the pipeline CI/CD. This will be automatically fast-forward merged if successful." \ + --assignee "@me" \ + --no-maintainer-edit \ + --repo "$GH_PROJECT_PATH" || true; + printf "Pipeline Attempt on ${CI_PIPELINE_ID} for ${CI_COMMIT_SHA}\n\n${CI_PIPELINE_URL}" \ + | gh pr comment staging \ + --body-file - \ + --repo "$GH_PROJECT_PATH"; + ' + rules: + # Runs on staging commits and ignores version commits + - if: $CI_COMMIT_BRANCH == 'staging' && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Runs on tag pipeline where the tag is a prerelease or release version + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + +build:linux: + stage: build + needs: [] + script: + - > + nix-shell --run ' + npm run build --verbose; + ' + artifacts: + when: always + paths: + # Only the build:linux preserves the dist + - ./dist + rules: + # Runs on staging commits and ignores version commits + - if: $CI_COMMIT_BRANCH == 'staging' && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Runs on tag pipeline where the tag is a prerelease or release version + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + +build:windows: + stage: build + needs: [] + tags: + - windows + before_script: + - choco install nodejs --version=16.14.2 -y + - refreshenv + script: + - npm config set msvs_version 2019 + - npm install --ignore-scripts + - $env:Path = "$(npm bin);" + $env:Path + - npm run build --verbose + # - npm test -- --ci + # artifacts: + # when: always + # reports: + # junit: + # - ./tmp/junit/junit.xml + rules: + # Runs on staging commits and ignores version commits + - if: $CI_COMMIT_BRANCH == 'staging' && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Runs on tag pipeline where the tag is a prerelease or release version + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + +build:macos: + stage: build + needs: [] + tags: + - shared-macos-amd64 + image: macos-11-xcode-12 + variables: + HOMEBREW_NO_INSTALL_UPGRADE: "true" + HOMEBREW_NO_INSTALL_CLEANUP: "true" + before_script: + - eval "$(brew shellenv)" + - brew install node@16 + - brew link --overwrite node@16 + - hash -r + script: + - npm install --ignore-scripts + - export PATH="$(npm bin):$PATH" + - npm run build --verbose + # - npm test -- --ci + # artifacts: + # when: always + # reports: + # junit: + # - ./tmp/junit/junit.xml + rules: + # Runs on staging commits and ignores version commits + - if: $CI_COMMIT_BRANCH == 'staging' && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Runs on tag pipeline where the tag is a prerelease or release version + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ -nix: +build:prerelease: stage: build - image: registry.gitlab.com/matrixai/engineering/maintenance/gitlab-runner + needs: + - build:linux + - build:windows + - build:macos + # Don't interrupt publishing job + interruptible: false + before_script: + - echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ./.npmrc + script: + - echo 'Publishing library prerelease' + - > + nix-shell --run ' + npm publish --tag prerelease --access public; + ' + after_script: + - rm -f ./.npmrc + rules: + # Only runs on tag pipeline where the tag is a prerelease version + # This requires dependencies to also run on tag pipeline + # However version tag comes with a version commit + # Dependencies must not run on the version commit + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+-.*[0-9]+$/ + +integration:builds: + stage: integration + needs: + - build:linux + - build:windows + - build:macos script: - mkdir -p ./builds - # nix-specific application target - > build_application="$(nix-build \ --max-jobs "$(nproc)" --cores "$(nproc)" \ @@ -98,19 +267,40 @@ nix: --attr docker \ --attr package.linux.x64.elf \ --attr package.windows.x64.exe \ - --attr package.macos.x64.macho)" + --attr package.macos.x64.macho \ + --attr package.macos.arm64.macho)" - cp -r $builds ./builds/ - only: - - master artifacts: paths: - ./builds/ + rules: + # Runs on staging commits and ignores version commits + - if: $CI_COMMIT_BRANCH == 'staging' && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Runs on tag pipeline where the tag is a prerelease or release version + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + +integration:deployment: + stage: integration + needs: + - integration:builds + # Don't interrupt deploying job + interruptible: false + # Requires mutual exclusion + resource_group: integration:deployment + script: + - echo 'Perform service deployment for integration testing' + rules: + # Runs on staging commits and ignores version commits + - if: $CI_COMMIT_BRANCH == 'staging' && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Runs on tag pipeline where the tag is a prerelease or release version + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ -application run: - stage: quality - image: registry.gitlab.com/matrixai/engineering/maintenance/gitlab-runner - dependencies: - - nix +integration:nix: + stage: integration + needs: + - integration:builds + - job: integration:deployment + optional: true script: - > build_application="$( \ @@ -119,14 +309,19 @@ application run: tail -1 \ )" - $build_application/bin/polykey - only: - - master + rules: + # Runs on staging commits and ignores version commits + - if: $CI_COMMIT_BRANCH == 'staging' && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Runs on tag pipeline where the tag is a prerelease or release version + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ -docker run: - stage: quality +integration:docker: + stage: integration + needs: + - integration:builds + - job: integration:deployment + optional: true image: docker:20.10.11 - dependencies: - - nix services: - docker:20.10.11-dind variables: @@ -136,61 +331,233 @@ docker run: script: - image="$(docker load --input ./builds/*docker* | cut -d' ' -f3)" - docker run "$image" - only: - - master + rules: + # Runs on staging commits and ignores version commits + - if: $CI_COMMIT_BRANCH == 'staging' && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Runs on tag pipeline where the tag is a prerelease or release version + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ -linux run: - stage: quality +integration:linux: + stage: integration + needs: + - integration:builds + - job: integration:deployment + optional: true image: ubuntu:latest - dependencies: - - nix script: - for f in ./builds/*-linux-*; do "$f"; done - only: - - master + rules: + # Runs on staging commits and ignores version commits + - if: $CI_COMMIT_BRANCH == 'staging' && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Runs on tag pipeline where the tag is a prerelease or release version + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ -windows run: - stage: quality - dependencies: - - nix - script: - - Get-ChildItem -File ./builds/*-win32-* | ForEach {& $_.FullName} +integration:windows: + stage: integration + needs: + - integration:builds + - job: integration:deployment + optional: true tags: - windows - only: - - master - -macos run: - stage: quality - image: macos-11-xcode-12 - dependencies: - - nix - script: - - for f in ./builds/*-macos-*; do "$f"; done - only: - - master - tags: - - shared-macos-amd64 - -packages: + script: + - Get-ChildItem -File ./builds/*-win-* | ForEach {& $_.FullName} + rules: + # Runs on staging commits and ignores version commits + - if: $CI_COMMIT_BRANCH == 'staging' && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Runs on tag pipeline where the tag is a prerelease or release version + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + +integration:macos: + stage: integration + needs: + - integration:builds + - job: integration:deployment + optional: true + tags: + - shared-macos-amd64 + image: macos-11-xcode-12 + script: + - for f in ./builds/*-macos-x64*; do "$f"; done + rules: + # Runs on staging commits and ignores version commits + - if: $CI_COMMIT_BRANCH == 'staging' && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Runs on tag pipeline where the tag is a prerelease or release version + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + +integration:prerelease: + stage: integration + needs: + - integration:builds + - job: build:prerelease + optional: true + - job: integration:nix + optional: true + - job: integration:docker + optional: true + - job: integration:linux + optional: true + - job: integration:windows + optional: true + - job: integration:macos + optional: true + # Don't interrupt publishing job + interruptible: false + script: + - echo 'Publishing application prerelease' + - > + nix-shell -I nixpkgs=./pkgs.nix --packages gitAndTools.gh --run ' + if gh release view "$CI_COMMIT_TAG" --repo "$GH_PROJECT_PATH" >/dev/null; then \ + gh release \ + upload "$CI_COMMIT_TAG" \ + builds/*.closure.gz \ + builds/*-docker-* \ + builds/*-linux-* \ + builds/*-win-* \ + builds/*-macos-* \ + --clobber \ + --repo "$GH_PROJECT_PATH"; \ + else \ + gh release \ + create "$CI_COMMIT_TAG" \ + builds/*.closure.gz \ + builds/*-docker-* \ + builds/*-linux-* \ + builds/*-win-* \ + builds/*-macos-* \ + --title "${CI_COMMIT_TAG}-$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \ + --notes "" \ + --prerelease \ + --target staging \ + --repo "$GH_PROJECT_PATH"; \ + fi; + ' + rules: + # Only runs on tag pipeline where the tag is a prerelease version + # This requires dependencies to also run on tag pipeline + # However version tag comes with a version commit + # Dependencies must not run on the version commit + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+-.*[0-9]+$/ + +integration:merge: + stage: integration + needs: + - build:merge + - job: build:linux + optional: true + - job: build:windows + optional: true + - job: build:macos + optional: true + - job: integration:nix + optional: true + - job: integration:docker + optional: true + - job: integration:linux + optional: true + - job: integration:windows + optional: true + - job: integration:macos + optional: true + # Requires mutual exclusion + resource_group: integration:merge + allow_failure: true + variables: + # Ensure that CI/CD is fetching all commits + # this is necessary to checkout origin/master + # and to also merge origin/staging + GIT_DEPTH: 0 + script: + - > + nix-shell -I nixpkgs=./pkgs.nix --packages gitAndTools.gh --run ' + printf "Pipeline Succeeded on ${CI_PIPELINE_ID} for ${CI_COMMIT_SHA}\n\n${CI_PIPELINE_URL}" \ + | gh pr comment staging \ + --body-file - \ + --repo "$GH_PROJECT_PATH"; + ' + - git remote add upstream "$GH_PROJECT_URL" + - git checkout origin/master + # Merge up to the current commit (not the latest commit) + - git merge --ff-only "$CI_COMMIT_SHA" + - git push upstream HEAD:master + rules: + # Runs on staging commits and ignores version commits + - if: $CI_COMMIT_BRANCH == 'staging' && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + # Runs on tag pipeline where the tag is a prerelease or release version + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + +release:deployment:branch: stage: release - image: registry.gitlab.com/matrixai/engineering/maintenance/gitlab-runner - dependencies: - - nix + # Only needs integration:builds from the staging branch pipeline + needs: + - project: $CI_PROJECT_PATH + job: integration:builds + ref: staging + artifacts: true + # Don't interrupt deploying job + interruptible: false + # Requires mutual exclusion (also with release:deployment:tag) + resource_group: release:deployment script: + - echo 'Perform service deployment for production' + rules: + # Runs on master commits and ignores version commits + - if: $CI_COMMIT_BRANCH == 'master' && $CI_COMMIT_TITLE !~ /^[0-9]+\.[0-9]+\.[0-9]+(?:-.*[0-9]+)?$/ + +release:deployment:tag: + stage: release + # Tag pipelines run independently + needs: + - integration:builds + - integration:merge + # Don't interrupt deploying job + interruptible: false + # Requires mutual exclusion (also with release:deployment:branch) + resource_group: release:deployment + script: + - echo 'Perform service deployment for production' + rules: + # Runs on tag pipeline where the tag is a release version + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/ + +release:distribution: + stage: release + needs: + - build:linux + - build:windows + - build:macos + - integration:builds + - integration:merge + - release:deployment:tag + # Don't interrupt publishing job + interruptible: false + before_script: + - echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ./.npmrc + script: + - echo 'Publishing library & application release' + - > + nix-shell --run ' + npm publish --access public; + ' - > - nix-shell -I nixpkgs=./pkgs.nix --packages git gitAndTools.gh --run ' - commit="$(git rev-parse --short HEAD)"; + nix-shell -I nixpkgs=./pkgs.nix --packages gitAndTools.gh --run ' gh release \ - create "$commit" \ + create "$CI_COMMIT_TAG" \ builds/*.closure.gz \ + builds/*-docker-* \ builds/*-linux-* \ - builds/*-win32-* \ + builds/*-win-* \ builds/*-macos-* \ - --title "Build-$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \ - --prerelease \ + --title "${CI_COMMIT_TAG}-$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \ --notes "" \ - --repo MatrixAI/js-polykey; + --target master \ + --repo "$GH_PROJECT_PATH"; ' - only: - - master + after_script: + - rm -f ./.npmrc + rules: + # Only runs on tag pipeline where the tag is a release version + # This requires dependencies to also run on tag pipeline + # However version tag comes with a version commit + # Dependencies must not run on the version commit + - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/ diff --git a/.npmignore b/.npmignore index 4a1857eea..6bb02a31f 100644 --- a/.npmignore +++ b/.npmignore @@ -1,16 +1,16 @@ .* +/*.nix /nix -/pkgs.nix -/default.nix -/shell.nix -/release.nix /tsconfig.json /tsconfig.build.json /babel.config.js /jest.config.js /src +/scripts /tests /tmp /docs /benches +/build /builds +/dist/tsbuildinfo diff --git a/README.md b/README.md index 04e68a90f..73da94d71 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Polykey -[![pipeline status](https://gitlab.com/MatrixAI/open-source/js-polykey/badges/master/pipeline.svg)](https://gitlab.com/MatrixAI/open-source/js-polykey/commits/master) +staging:[![pipeline status](https://gitlab.com/MatrixAI/open-source/js-polykey/badges/staging/pipeline.svg)](https://gitlab.com/MatrixAI/open-source/js-polykey/commits/staging) +master:[![pipeline status](https://gitlab.com/MatrixAI/open-source/js-polykey/badges/master/pipeline.svg)](https://gitlab.com/MatrixAI/open-source/js-polykey/commits/master) This is the core library for running PolyKey. It provides a CLI `polykey` or `pk` for interacting with the PolyKey system. diff --git a/default.nix b/default.nix index 79e7317a4..283de7665 100644 --- a/default.nix +++ b/default.nix @@ -1,5 +1,6 @@ { runCommandNoCC , callPackage +, jq }: let @@ -11,20 +12,46 @@ let packageName = utils.node2nixDev.packageName; } '' - mkdir -p $out/lib/node_modules/${utils.node2nixDev.packageName} + mkdir -p "$out/lib/node_modules/$packageName" # copy the package.json - cp ${utils.node2nixDev}/lib/node_modules/${utils.node2nixDev.packageName}/package.json $out/lib/node_modules/${utils.node2nixDev.packageName}/ + cp \ + "${utils.node2nixDev}/lib/node_modules/$packageName/package.json" \ + "$out/lib/node_modules/$packageName/" + # copy the native addons + if [ -d "${utils.node2nixDev}/lib/node_modules/$packageName/prebuilds" ]; then + cp -r \ + "${utils.node2nixDev}/lib/node_modules/$packageName/prebuilds" \ + "$out/lib/node_modules/$packageName/" + fi # copy the dist - cp -r ${utils.node2nixDev}/lib/node_modules/${utils.node2nixDev.packageName}/dist $out/lib/node_modules/${utils.node2nixDev.packageName}/ + cp -r \ + "${utils.node2nixDev}/lib/node_modules/$packageName/dist" \ + "$out/lib/node_modules/$packageName/" # copy over the production dependencies if [ -d "${utils.node2nixProd}/lib/node_modules" ]; then - cp -r ${utils.node2nixProd}/lib/node_modules $out/lib/node_modules/${utils.node2nixDev.packageName}/ + cp -r \ + "${utils.node2nixProd}/lib/node_modules" \ + "$out/lib/node_modules/$packageName/" fi - # create symlink to the deployed executable folder, if applicable - if [ -d "${utils.node2nixDev}/lib/node_modules/.bin" ]; then - cp -r ${utils.node2nixDev}/lib/node_modules/.bin $out/lib/node_modules/ - ln -s $out/lib/node_modules/.bin $out/bin + # symlink bin executables + if [ \ + "$(${jq}/bin/jq 'has("bin")' "$out/lib/node_modules/$packageName/package.json")" \ + == \ + "true" \ + ]; then + mkdir -p "$out/bin" + while IFS= read -r bin_name && IFS= read -r bin_path; do + # make files executable + chmod a+x "$out/lib/node_modules/$packageName/$bin_path" + # create the symlink + ln -s \ + "../lib/node_modules/$packageName/$bin_path" \ + "$out/bin/$bin_name" + done < <( + ${jq}/bin/jq -r 'select(.bin != null) | .bin | to_entries[] | (.key, .value)' \ + "$out/lib/node_modules/$packageName/package.json" + ) fi - ''; + ''; in drv diff --git a/jest.config.js b/jest.config.js index 46fe908e2..39d84744b 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,20 +2,19 @@ const os = require('os'); const path = require('path'); const fs = require('fs'); const process = require('process'); -const { pathsToModuleNameMapper } = require('ts-jest/utils'); +const { pathsToModuleNameMapper } = require('ts-jest'); const { compilerOptions } = require('./tsconfig'); -const moduleNameMapper = pathsToModuleNameMapper( - compilerOptions.paths, - { prefix: "/src/" } -); +const moduleNameMapper = pathsToModuleNameMapper(compilerOptions.paths, { + prefix: '/src/', +}); // using panva/jose with jest requires subpath exports // https://github.com/panva/jose/discussions/105 moduleNameMapper['^jose/(.*)$'] = "/node_modules/jose/dist/node/cjs/$1"; // Global variables that are shared across the jest worker pool -// These variables must be static and serialisable +// These variables must be static and serializable const globals = { // Absolute directory to the project root projectDir: __dirname, @@ -39,34 +38,34 @@ const globals = { process.env['GLOBAL_DATA_DIR'] = globals.dataDir; module.exports = { - testEnvironment: "node", - cacheDirectory: '/tmp/jest', + testEnvironment: 'node', verbose: true, - roots: [ - "/tests" - ], - testMatch: [ - "**/?(*.)+(spec|test|unit.test).+(ts|tsx|js)" - ], + collectCoverage: false, + cacheDirectory: '/tmp/jest', + coverageDirectory: '/tmp/coverage', + roots: ['/tests'], + testMatch: ['**/?(*.)+(spec|test|unit.test).+(ts|tsx|js|jsx)'], transform: { - "^.+\\.tsx?$": "ts-jest", - "^.+\\.jsx?$": "babel-jest" + '^.+\\.tsx?$': 'ts-jest', + '^.+\\.jsx?$': 'babel-jest', }, + reporters: [ + 'default', + ['jest-junit', { outputDirectory: '/tmp/junit' }], + ], + collectCoverageFrom: ['src/**/*.{ts,tsx,js,jsx}', '!src/**/*.d.ts'], + coverageReporters: ['text', 'cobertura'], globals, // Global setup script executed once before all test files - globalSetup: "/tests/globalSetup.ts", + globalSetup: '/tests/globalSetup.ts', // Global teardown script executed once after all test files - globalTeardown: "/tests/globalTeardown.ts", + globalTeardown: '/tests/globalTeardown.ts', // Setup files are executed before each test file // Can access globals - setupFiles: [ - "/tests/setup.ts" - ], + setupFiles: ['/tests/setup.ts'], // Setup files after env are executed before each test file // after the jest test environment is installed // Can access globals - setupFilesAfterEnv: [ - "/tests/setupAfterEnv.ts" - ], - moduleNameMapper: moduleNameMapper + setupFilesAfterEnv: ['/tests/setupAfterEnv.ts'], + moduleNameMapper: moduleNameMapper, }; diff --git a/nix/leveldown.patch b/nix/leveldown.patch deleted file mode 100644 index 0d35bab41..000000000 --- a/nix/leveldown.patch +++ /dev/null @@ -1,14 +0,0 @@ ---- dictionary/leveldown.js 2021-08-04 15:43:31.836337623 +1000 -+++ dictionary/leveldown_.js 2021-08-04 15:43:11.977266316 +1000 -@@ -1,10 +1,3 @@ - 'use strict'; - --module.exports = { -- pkg: { -- patches: { -- 'binding.js': ['__dirname', "require('path').dirname(process.execPath)"], -- }, -- deployFiles: [['prebuilds', 'prebuilds', 'directory']], -- }, --}; -+module.exports = {}; diff --git a/package-lock.json b/package-lock.json index eca4f5a4d..be8b574bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,117 +1,12021 @@ { "name": "@matrixai/polykey", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "@matrixai/polykey", + "version": "1.0.0", + "license": "GPL-3.0", + "dependencies": { + "@grpc/grpc-js": "1.6.7", + "@matrixai/async-init": "^1.8.1", + "@matrixai/async-locks": "^2.3.1", + "@matrixai/db": "^4.0.5", + "@matrixai/errors": "^1.1.1", + "@matrixai/id": "^3.3.3", + "@matrixai/logger": "^2.2.2", + "@matrixai/resources": "^1.1.3", + "@matrixai/workers": "^1.3.3", + "ajv": "^7.0.4", + "bip39": "^3.0.3", + "canonicalize": "^1.0.5", + "cheerio": "^1.0.0-rc.5", + "commander": "^8.3.0", + "cross-fetch": "^3.0.6", + "cross-spawn": "^7.0.3", + "encryptedfs": "^3.5.3", + "fast-fuzzy": "^1.10.8", + "fd-lock": "^1.2.0", + "google-protobuf": "^3.14.0", + "ip-num": "^1.3.3-0", + "isomorphic-git": "^1.8.1", + "jest-junit": "^13.2.0", + "jose": "^4.3.6", + "lexicographic-integer": "^1.1.0", + "multiformats": "^9.4.8", + "node-forge": "^0.10.0", + "pako": "^1.0.11", + "prompts": "^2.4.1", + "readable-stream": "^3.6.0", + "resource-counter": "^1.2.4", + "threads": "^1.6.5", + "utp-native": "^2.5.3", + "uuid": "^8.3.0" + }, + "bin": { + "pk": "dist/bin/polykey.js", + "polykey": "dist/bin/polykey.js" + }, + "devDependencies": { + "@babel/preset-env": "^7.13.10", + "@types/cross-spawn": "^6.0.2", + "@types/google-protobuf": "^3.7.4", + "@types/jest": "^27.0.2", + "@types/nexpect": "^0.4.31", + "@types/node": "^16.11.7", + "@types/node-forge": "^0.10.4", + "@types/pako": "^1.0.2", + "@types/prompts": "^2.0.13", + "@types/readable-stream": "^2.3.11", + "@types/uuid": "^8.3.0", + "@typescript-eslint/eslint-plugin": "^5.23.0", + "@typescript-eslint/parser": "^5.23.0", + "babel-jest": "^27.0.0", + "eslint": "^8.15.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-prettier": "^4.0.0", + "grpc_tools_node_protoc_ts": "^5.1.3", + "jest": "^27.2.5", + "jest-mock-process": "^1.4.1", + "jest-mock-props": "^1.9.0", + "mocked-env": "^1.3.5", + "nexpect": "^0.6.0", + "node-gyp-build": "^4.4.0", + "pkg": "5.6.0", + "prettier": "^2.6.2", + "shelljs": "^0.8.5", + "shx": "^0.3.4", + "ts-jest": "^27.0.5", + "ts-node": "10.7.0", + "tsconfig-paths": "^3.9.0", + "typedoc": "^0.22.15", + "typescript": "^4.5.2", + "typescript-cached-transpile": "0.0.6" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.5.tgz", + "integrity": "sha512-BxhE40PVCBxVEJsSBhB6UWyAuqJRxGsAw8BdHMJ3AKGydcwuWW4kOO3HmqBQAdcq/OP+/DlTVxLvsCzRTnZuGg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.5.tgz", + "integrity": "sha512-MGY8vg3DxMnctw0LdvSEojOsumc70g0t18gNyUdAZqB1Rpd1Bqo/svHGvt+UJ6JcGX+DIekGFDxxIWofBxLCnQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.18.2", + "@babel/helper-compilation-targets": "^7.18.2", + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helpers": "^7.18.2", + "@babel/parser": "^7.18.5", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.5", + "@babel/types": "^7.18.4", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz", + "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.2", + "@jridgewell/gen-mapping": "^0.3.0", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", + "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", + "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", + "dev": true, + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz", + "integrity": "sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.10", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.20.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.0.tgz", + "integrity": "sha512-Kh8zTGR9de3J63e5nS0rQUdRs/kbtwoeQQ0sriS0lItjC96u8XXZN6lKpuyWd2coKSU13py/y+LTmThLuVX0Pg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-member-expression-to-functions": "^7.17.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.12.tgz", + "integrity": "sha512-b2aZrV4zvutr9AIa6/gA3wsZKRwTKYoDxYiFKcESS3Ug2GTXzwBEvMuuFLhCQpEnRXs1zng4ISAXSUxxKBIcxw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "regexpu-core": "^5.0.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", + "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz", + "integrity": "sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", + "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", + "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.16.7", + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz", + "integrity": "sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz", + "integrity": "sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.0", + "@babel/types": "^7.18.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", + "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz", + "integrity": "sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", + "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-wrap-function": "^7.16.8", + "@babel/types": "^7.16.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.2.tgz", + "integrity": "sha512-XzAIyxx+vFnrOxiQrToSUOzUOn0e1J2Li40ntddek1Y69AXUTXoDJ40/D5RdjFu7s7qHiaeoTiempZcbuVXh2Q==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.2", + "@babel/helper-member-expression-to-functions": "^7.17.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/traverse": "^7.18.2", + "@babel/types": "^7.18.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz", + "integrity": "sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", + "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", + "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.8", + "@babel/types": "^7.16.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.2.tgz", + "integrity": "sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.2", + "@babel/types": "^7.18.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", + "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.5.tgz", + "integrity": "sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.17.12.tgz", + "integrity": "sha512-xCJQXl4EeQ3J9C4yOmpTrtVGmzpm2iSzyxbkZHw7UCnZBftHpF/hpII80uWVyVrc40ytIClHjgWGTG1g/yB+aw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.17.12.tgz", + "integrity": "sha512-/vt0hpIw0x4b6BLKUkwlvEoiGZYYLNZ96CzyHYPbtG2jZGz6LBe7/V+drYrc/d+ovrF9NBi0pmtvmNb/FsWtRQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.17.12.tgz", + "integrity": "sha512-RWVvqD1ooLKP6IqWTA5GyFVX2isGEgC5iFxKzfYOIy/QEFdxYyCybBDtIGjipHpb9bDWHzcqGqFakf+mVmBTdQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-remap-async-to-generator": "^7.16.8", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.17.12.tgz", + "integrity": "sha512-U0mI9q8pW5Q9EaTHFPwSVusPMV/DV9Mm8p7csqROFLtIE9rBF5piLqyrBGigftALrBcsBGu4m38JneAe7ZDLXw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.0.tgz", + "integrity": "sha512-t+8LsRMMDE74c6sV7KShIw13sqbqd58tlqNrsWoWBTIMw7SVQ0cZ905wLNS/FBCy/3PyooRHLFFlfrUNyyz5lA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", + "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.17.12.tgz", + "integrity": "sha512-j7Ye5EWdwoXOpRmo5QmRyHPsDIe6+u70ZYZrd7uz+ebPYFKfRcLcNu3Ro0vOlJ5zuv8rU7xa+GttNiRzX56snQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.17.12.tgz", + "integrity": "sha512-rKJ+rKBoXwLnIn7n6o6fulViHMrOThz99ybH+hKHcOZbnN14VuMnH9fo2eHE69C8pO4uX1Q7t2HYYIDmv8VYkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.17.12.tgz", + "integrity": "sha512-EqFo2s1Z5yy+JeJu7SFfbIUtToJTVlC61/C7WLKDntSw4Sz6JNAIfL7zQ74VvirxpjB5kz/kIx0gCcb+5OEo2Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.17.12.tgz", + "integrity": "sha512-ws/g3FSGVzv+VH86+QvgtuJL/kR67xaEIF2x0iPqdDfYW6ra6JF3lKVBkWynRLcNtIC1oCTfDRVxmm2mKzy+ag==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", + "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.0.tgz", + "integrity": "sha512-nbTv371eTrFabDfHLElkn9oyf9VG+VKK6WMzhY2o4eHKaG19BToD9947zzGMO6I/Irstx9d8CwX6njPNIAR/yw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.10", + "@babel/helper-compilation-targets": "^7.17.10", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", + "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.17.12.tgz", + "integrity": "sha512-7wigcOs/Z4YWlK7xxjkvaIw84vGhDv/P1dFGQap0nHkc8gFKY/r+hXc8Qzf5k1gY7CvGIcHqAnOagVKJJ1wVOQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.17.12.tgz", + "integrity": "sha512-SllXoxo19HmxhDWm3luPz+cPhtoTSKLJE9PXshsfrOzBqs60QP0r8OaJItrPhAj0d7mZMnNF0Y1UUggCDgMz1A==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.17.12.tgz", + "integrity": "sha512-/6BtVi57CJfrtDNKfK5b66ydK2J5pXUKBKSPD2G1whamMuEnZWgoOIfO8Vf9F/DoD4izBLD/Au4NMQfruzzykg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-create-class-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.17.12.tgz", + "integrity": "sha512-Wb9qLjXf3ZazqXA7IvI7ozqRIXIGPtSo+L5coFmEkhTQK18ao4UDDD0zdTGAarmbLj2urpRwrc6893cu5Bfh0A==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.17.12.tgz", + "integrity": "sha512-n/loy2zkq9ZEM8tEOwON9wTQSTNDTDEz6NujPtJGLU7qObzT1N4c4YZZf8E6ATB2AjNQg/Ib2AIpO03EZaCehw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz", + "integrity": "sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.17.12.tgz", + "integrity": "sha512-PHln3CNi/49V+mza4xMwrg+WGYevSF1oaiXaC2EQfdp4HWlSjRsrDXWJiQBKpP7749u6vQ9mcry2uuFOv5CXvA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.17.12.tgz", + "integrity": "sha512-J8dbrWIOO3orDzir57NRsjg4uxucvhby0L/KZuGsWDj0g7twWK3g7JhJhOrXtuXiw8MeiSdJ3E0OW9H8LYEzLQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-remap-async-to-generator": "^7.16.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", + "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.4.tgz", + "integrity": "sha512-+Hq10ye+jlvLEogSOtq4mKvtk7qwcUQ1f0Mrueai866C82f844Yom2cttfJdMdqRLTxWpsbfbkIkOIfovyUQXw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.4.tgz", + "integrity": "sha512-e42NSG2mlKWgxKUAD9EJJSkZxR67+wZqzNxLSpc51T8tRU5SLFHsPmgYR5yr7sdgX4u+iHA1C5VafJ6AyImV3A==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.18.2", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-replace-supers": "^7.18.2", + "@babel/helper-split-export-declaration": "^7.16.7", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.17.12.tgz", + "integrity": "sha512-a7XINeplB5cQUWMg1E/GI1tFz3LfK021IjV1rj1ypE+R7jHm+pIHmHl25VNkZxtx9uuYp7ThGk8fur1HHG7PgQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.0.tgz", + "integrity": "sha512-Mo69klS79z6KEfrLg/1WkmVnB8javh75HX4pi2btjvlIoasuxilEyjtsQW6XPrubNd7AQy0MMaNIaQE4e7+PQw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", + "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.17.12.tgz", + "integrity": "sha512-EA5eYFUG6xeerdabina/xIoB95jJ17mAkR8ivx6ZSu9frKShBjpOGZPn511MTDTkiCO+zXnzNczvUM69YSf3Zw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", + "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.1.tgz", + "integrity": "sha512-+TTB5XwvJ5hZbO8xvl2H4XaMDOAK57zF4miuC9qQJgysPNEAZZ9Z69rdF5LJkozGdZrjBIUAIyKUWRMmebI7vg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", + "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.17.12.tgz", + "integrity": "sha512-8iRkvaTjJciWycPIZ9k9duu663FT7VrBdNqNgxnVXEFwOIp55JWcZd23VBRySYbnS3PwQ3rGiabJBBBGj5APmQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", + "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.0.tgz", + "integrity": "sha512-h8FjOlYmdZwl7Xm2Ug4iX2j7Qy63NANI+NQVWQzv6r25fqgg7k2dZl03p95kvqNclglHs4FZ+isv4p1uXMA+QA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.2.tgz", + "integrity": "sha512-f5A865gFPAJAEE0K7F/+nm5CmAE3y8AWlMBG9unu5j9+tk50UQVK0QS8RNxSp7MJf0wh97uYyLWt3Zvu71zyOQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-simple-access": "^7.18.2", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.5.tgz", + "integrity": "sha512-SEewrhPpcqMF1V7DhnEbhVJLrC+nnYfe1E0piZMZXBpxi9WvZqWGwpsk7JYP7wPWeqaBh4gyKlBhHJu3uz5g4Q==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-validator-identifier": "^7.16.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.0.tgz", + "integrity": "sha512-d/zZ8I3BWli1tmROLxXLc9A6YXvGK8egMxHp+E/rRwMh1Kip0AP77VwZae3snEJ33iiWwvNv2+UIIhfalqhzZA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.17.12.tgz", + "integrity": "sha512-vWoWFM5CKaTeHrdUJ/3SIOTRV+MBVGybOC9mhJkaprGNt5demMymDW24yC74avb915/mIRe3TgNb/d8idvnCRA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.5.tgz", + "integrity": "sha512-TuRL5uGW4KXU6OsRj+mLp9BM7pO8e7SGNTEokQRRxHFkXYMFiy2jlKSZPFtI/mKORDzciH+hneskcSOp0gU8hg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", + "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.17.12.tgz", + "integrity": "sha512-6qW4rWo1cyCdq1FkYri7AHpauchbGLXpdwnYsfxFb+KtddHENfsY5JZb35xUwkK5opOLcJ3BNd2l7PhRYGlwIA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", + "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.0.tgz", + "integrity": "sha512-C8YdRw9uzx25HSIzwA7EM7YP0FhCe5wNvJbZzjVNHHPGVcDJ3Aie+qGYYdS1oVQgn+B3eAIJbWFLrJ4Jipv7nw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "regenerator-transform": "^0.15.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.17.12.tgz", + "integrity": "sha512-1KYqwbJV3Co03NIi14uEHW8P50Md6KqFgt0FfpHdK6oyAHQVTosgPuPSiWud1HX0oYJ1hGRRlk0fP87jFpqXZA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", + "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.17.12.tgz", + "integrity": "sha512-9pgmuQAtFi3lpNUstvG9nGfk9DkrdmWNp9KeKPFmuZCpEnxRzYlS8JgwPjYj+1AWDOSvoGN0H30p1cBOmT/Svg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", + "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.2.tgz", + "integrity": "sha512-/cmuBVw9sZBGZVOMkpAEaVLwm4JmK2GZ1dFKOGGpMzEHWFmyZZ59lUU0PdRr8YNYeQdNzTDwuxP2X2gzydTc9g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.17.12.tgz", + "integrity": "sha512-Q8y+Jp7ZdtSPXCThB6zjQ74N3lj0f6TDh1Hnf5B+sYlzQ8i5Pjp8gW0My79iekSpT4WnI06blqP6DT0OmaXXmw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", + "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", + "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.2.tgz", + "integrity": "sha512-PfpdxotV6afmXMU47S08F9ZKIm2bJIQ0YbAAtDfIENX7G1NUAXigLREh69CWDjtgUy7dYn7bsMzkgdtAlmS68Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.10", + "@babel/helper-compilation-targets": "^7.18.2", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.17.12", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.17.12", + "@babel/plugin-proposal-async-generator-functions": "^7.17.12", + "@babel/plugin-proposal-class-properties": "^7.17.12", + "@babel/plugin-proposal-class-static-block": "^7.18.0", + "@babel/plugin-proposal-dynamic-import": "^7.16.7", + "@babel/plugin-proposal-export-namespace-from": "^7.17.12", + "@babel/plugin-proposal-json-strings": "^7.17.12", + "@babel/plugin-proposal-logical-assignment-operators": "^7.17.12", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.17.12", + "@babel/plugin-proposal-numeric-separator": "^7.16.7", + "@babel/plugin-proposal-object-rest-spread": "^7.18.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", + "@babel/plugin-proposal-optional-chaining": "^7.17.12", + "@babel/plugin-proposal-private-methods": "^7.17.12", + "@babel/plugin-proposal-private-property-in-object": "^7.17.12", + "@babel/plugin-proposal-unicode-property-regex": "^7.17.12", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.17.12", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.17.12", + "@babel/plugin-transform-async-to-generator": "^7.17.12", + "@babel/plugin-transform-block-scoped-functions": "^7.16.7", + "@babel/plugin-transform-block-scoping": "^7.17.12", + "@babel/plugin-transform-classes": "^7.17.12", + "@babel/plugin-transform-computed-properties": "^7.17.12", + "@babel/plugin-transform-destructuring": "^7.18.0", + "@babel/plugin-transform-dotall-regex": "^7.16.7", + "@babel/plugin-transform-duplicate-keys": "^7.17.12", + "@babel/plugin-transform-exponentiation-operator": "^7.16.7", + "@babel/plugin-transform-for-of": "^7.18.1", + "@babel/plugin-transform-function-name": "^7.16.7", + "@babel/plugin-transform-literals": "^7.17.12", + "@babel/plugin-transform-member-expression-literals": "^7.16.7", + "@babel/plugin-transform-modules-amd": "^7.18.0", + "@babel/plugin-transform-modules-commonjs": "^7.18.2", + "@babel/plugin-transform-modules-systemjs": "^7.18.0", + "@babel/plugin-transform-modules-umd": "^7.18.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.17.12", + "@babel/plugin-transform-new-target": "^7.17.12", + "@babel/plugin-transform-object-super": "^7.16.7", + "@babel/plugin-transform-parameters": "^7.17.12", + "@babel/plugin-transform-property-literals": "^7.16.7", + "@babel/plugin-transform-regenerator": "^7.18.0", + "@babel/plugin-transform-reserved-words": "^7.17.12", + "@babel/plugin-transform-shorthand-properties": "^7.16.7", + "@babel/plugin-transform-spread": "^7.17.12", + "@babel/plugin-transform-sticky-regex": "^7.16.7", + "@babel/plugin-transform-template-literals": "^7.18.2", + "@babel/plugin-transform-typeof-symbol": "^7.17.12", + "@babel/plugin-transform-unicode-escapes": "^7.16.7", + "@babel/plugin-transform-unicode-regex": "^7.16.7", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.18.2", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.5.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", + "core-js-compat": "^3.22.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.3.tgz", + "integrity": "sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.5.tgz", + "integrity": "sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.18.2", + "@babel/helper-environment-visitor": "^7.18.2", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.18.5", + "@babel/types": "^7.18.4", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz", + "integrity": "sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.3.2", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.7.tgz", + "integrity": "sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==", + "dependencies": { + "@grpc/proto-loader": "^0.6.4", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.11.3", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", + "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", + "dev": true, + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/console/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/console/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", + "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", + "dev": true, + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/reporters": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^27.5.1", + "jest-config": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-resolve-dependencies": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "jest-watcher": "^27.5.1", + "micromatch": "^4.0.4", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/core/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/core/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/environment": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", + "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", + "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", + "dev": true, + "dependencies": { + "@jest/types": "^27.5.1", + "@sinonjs/fake-timers": "^8.0.1", + "@types/node": "*", + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz", + "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", + "dev": true, + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/types": "^27.5.1", + "expect": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz", + "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-haste-map": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^8.1.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/reporters/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/source-map": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", + "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9", + "source-map": "^0.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", + "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", + "dev": true, + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", + "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-runtime": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", + "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.1.0", + "@jest/types": "^27.5.1", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-util": "^27.5.1", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/transform/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/transform/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", + "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", + "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", + "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz", + "integrity": "sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@matrixai/async-init": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@matrixai/async-init/-/async-init-1.8.1.tgz", + "integrity": "sha512-ZAS1yd/PC+r3NwvT9fEz3OtAm68A8mKXXGdZRcYQF1ajl43jsV8/B4aDwr2oLFlV+RYZgWl7UwjZj4rtoZSycQ==", + "dependencies": { + "@matrixai/async-locks": "^2.3.1", + "@matrixai/errors": "^1.1.1" + } + }, + "node_modules/@matrixai/async-locks": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@matrixai/async-locks/-/async-locks-2.3.1.tgz", + "integrity": "sha512-STz8VyiIXleaa72zMsq01x/ZO1gPzukUgMe25+uqMWn/nPrC9EtJOR7e3CW0DODfYDZ0748z196GeOjS3jh+4g==", + "dependencies": { + "@matrixai/errors": "^1.1.1", + "@matrixai/resources": "^1.1.3", + "async-mutex": "^0.3.2" + } + }, + "node_modules/@matrixai/db": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@matrixai/db/-/db-4.0.5.tgz", + "integrity": "sha512-X3gBcyPxC+bTEfi1J1Y49n1bglvg7HjM8MKNH5s+OUEswqKSZgeg1uWfXqvUqq72yjBtgRi4Ghmy4MdrIB1oMw==", + "dependencies": { + "@matrixai/async-init": "^1.7.3", + "@matrixai/errors": "^1.1.1", + "@matrixai/logger": "^2.1.1", + "@matrixai/resources": "^1.1.3", + "@matrixai/workers": "^1.3.3", + "@types/abstract-leveldown": "^7.2.0", + "level": "7.0.1", + "threads": "^1.6.5" + } + }, + "node_modules/@matrixai/errors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@matrixai/errors/-/errors-1.1.2.tgz", + "integrity": "sha512-JSi2SIqdlqqDruANrTG8RMvLrJZAwduY19y26LZHx7DDkqhkqzF9fblbWaE9Fo1lhSTGk65oKRx2UjGn3v5gWw==", + "dependencies": { + "ts-custom-error": "^3.2.0" + } + }, + "node_modules/@matrixai/id": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@matrixai/id/-/id-3.3.3.tgz", + "integrity": "sha512-eXkxv68sCT17f6XQU8zMwgLLqwbdFm+8DoL3gXfBqfiDYxOCCQBS4L9kjUby8gRdlP7mIpkJ6oVAWCUnykGInA==", + "dependencies": { + "multiformats": "^9.4.8", + "uuid": "^8.3.2" + } + }, + "node_modules/@matrixai/logger": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@matrixai/logger/-/logger-2.2.2.tgz", + "integrity": "sha512-6/G1svkcFiBMvmIdBv6YbxoLKwMWpXNzt93Cc4XbXXygCQrsn6oYwLvnRk/JNr6uM29M2T+Aa7K1o3n2XMTuLw==" + }, + "node_modules/@matrixai/resources": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@matrixai/resources/-/resources-1.1.3.tgz", + "integrity": "sha512-9zbA0NtgCtA+2hILpojshH6Pd679bIPtB8DcsPLVDzvGZP1TDwvtvZWCC3SG7oJUTzxqBI2Bfe+hypqwpvYPCw==" + }, + "node_modules/@matrixai/workers": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@matrixai/workers/-/workers-1.3.3.tgz", + "integrity": "sha512-ID1sSJDXjM0hdWC10euWGcFofuys7+IDP+XTBh8Gq6jirn18xJs71wSy357qxLVSa7mL00qRJJfW6rljcFUK4A==", + "dependencies": { + "@matrixai/async-init": "^1.7.3", + "@matrixai/errors": "^1.1.1", + "@matrixai/logger": "^2.1.1", + "threads": "^1.6.5" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.10.tgz", + "integrity": "sha512-N+srakvPaYMGkwjNDx3ASx65Zl3QG8dJgVtIB+YMOkucU+zctlv/hdP5250VKdDHSDoW9PFZoCqbqNcAPjCjXA==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.2.tgz", + "integrity": "sha512-YwrUA5ysDXHFYfL0Xed9x3sNS4P+aKlCOnnbqUa2E5HdQshHFleCJVrj1PlGTb4GgFUCDyte1v3JWLy2sz8Oqg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "node_modules/@types/abstract-leveldown": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz", + "integrity": "sha512-q5veSX6zjUy/DlDhR4Y4cU0k2Ar+DT2LUraP00T19WLmTO6Se1djepCCaqU6nQrwcJ5Hyo/CWqxTzrrFg8eqbQ==" + }, + "node_modules/@types/babel__core": { + "version": "7.1.19", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", + "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.17.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.17.1.tgz", + "integrity": "sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/cross-spawn": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.2.tgz", + "integrity": "sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/google-protobuf": { + "version": "3.15.6", + "resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.15.6.tgz", + "integrity": "sha512-pYVNNJ+winC4aek+lZp93sIKxnXt5qMkuKmaqS3WGuTq0Bw1ZDYNBgzG5kkdtwcv+GmYJGo3yEg6z2cKKAiEdw==", + "dev": true + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "27.5.2", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz", + "integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==", + "dev": true, + "dependencies": { + "jest-matcher-utils": "^27.0.0", + "pretty-format": "^27.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "node_modules/@types/nexpect": { + "version": "0.4.31", + "resolved": "https://registry.npmjs.org/@types/nexpect/-/nexpect-0.4.31.tgz", + "integrity": "sha512-Plh9Dlj2AKdsblgF1Pv7s2BjlojqW93d1zIUtK5xVVrUjkZQezyWIOAq0Xfwp0e0SDQ70YmaDqzhoJru2kqVPA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "16.11.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.39.tgz", + "integrity": "sha512-K0MsdV42vPwm9L6UwhIxMAOmcvH/1OoVkZyCgEtVu4Wx7sElGloy/W7kMBNe/oJ7V/jW9BVt1F6RahH6e7tPXw==" + }, + "node_modules/@types/node-forge": { + "version": "0.10.10", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-0.10.10.tgz", + "integrity": "sha512-iixn5bedlE9fm/5mN7fPpXraXlxCVrnNWHZekys8c5fknridLVWGnNRqlaWpenwaijIuB3bNI0lEOm+JD6hZUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/pako": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.4.tgz", + "integrity": "sha512-Z+5bJSm28EXBSUJEgx29ioWeEEHUh6TiMkZHDhLwjc9wVFH+ressbkmX6waUZc5R3Gobn4Qu5llGxaoflZ+yhA==", + "dev": true + }, + "node_modules/@types/prettier": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.3.tgz", + "integrity": "sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg==", + "dev": true + }, + "node_modules/@types/prompts": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.0.14.tgz", + "integrity": "sha512-HZBd99fKxRWpYCErtm2/yxUZv6/PBI9J7N4TNFffl5JbrYMHBwF25DjQGTW3b3jmXq+9P6/8fCIb2ee57BFfYA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/readable-stream": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.13.tgz", + "integrity": "sha512-4JSCx8EUzaW9Idevt+9lsRAt1lcSccoQfE+AouM1gk8sFxnnytKNIO3wTl9Dy+4m6jRJ1yXhboLHHT/LXBQiEw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "safe-buffer": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.28.0.tgz", + "integrity": "sha512-DXVU6Cg29H2M6EybqSg2A+x8DgO9TCUBRp4QEXQHJceLS7ogVDP0g3Lkg/SZCqcvkAP/RruuQqK0gdlkgmhSUA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.28.0", + "@typescript-eslint/type-utils": "5.28.0", + "@typescript-eslint/utils": "5.28.0", + "debug": "^4.3.4", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.2.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.28.0.tgz", + "integrity": "sha512-ekqoNRNK1lAcKhZESN/PdpVsWbP9jtiNqzFWkp/yAUdZvJalw2heCYuqRmM5eUJSIYEkgq5sGOjq+ZqsLMjtRA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.28.0", + "@typescript-eslint/types": "5.28.0", + "@typescript-eslint/typescript-estree": "5.28.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.28.0.tgz", + "integrity": "sha512-LeBLTqF/he1Z+boRhSqnso6YrzcKMTQ8bO/YKEe+6+O/JGof9M0g3IJlIsqfrK/6K03MlFIlycbf1uQR1IjE+w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.28.0", + "@typescript-eslint/visitor-keys": "5.28.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.28.0.tgz", + "integrity": "sha512-SyKjKh4CXPglueyC6ceAFytjYWMoPHMswPQae236zqe1YbhvCVQyIawesYywGiu98L9DwrxsBN69vGIVxJ4mQQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "5.28.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.28.0.tgz", + "integrity": "sha512-2OOm8ZTOQxqkPbf+DAo8oc16sDlVR5owgJfKheBkxBKg1vAfw2JsSofH9+16VPlN9PWtv8Wzhklkqw3k/zCVxA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.28.0.tgz", + "integrity": "sha512-9GX+GfpV+F4hdTtYc6OV9ZkyYilGXPmQpm6AThInpBmKJEyRSIjORJd1G9+bknb7OTFYL+Vd4FBJAO6T78OVqA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.28.0", + "@typescript-eslint/visitor-keys": "5.28.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.28.0.tgz", + "integrity": "sha512-E60N5L0fjv7iPJV3UGc4EC+A3Lcj4jle9zzR0gW7vXhflO7/J29kwiTGITA2RlrmPokKiZbBy2DgaclCaEUs6g==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.28.0", + "@typescript-eslint/types": "5.28.0", + "@typescript-eslint/typescript-estree": "5.28.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.28.0.tgz", + "integrity": "sha512-BtfP1vCor8cWacovzzPFOoeW4kBQxzmhxGoOpt0v1SFvG+nJ0cWaVdJk7cky1ArTcFHHKNIxyo2LLr3oNkSuXA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.28.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "node_modules/abstract-leveldown": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz", + "integrity": "sha512-DnhQwcFEaYsvYDnACLZhMmCWd3rkOeEvglpa4q5i/5Jlm3UIsWaxVzuXvDLFCSCWRO3yy2/+V/G7FusFgejnfQ==", + "dependencies": { + "buffer": "^6.0.3", + "catering": "^2.0.0", + "is-buffer": "^2.0.5", + "level-concat-iterator": "^3.0.0", + "level-supports": "^2.0.1", + "queue-microtask": "^1.2.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.2.4.tgz", + "integrity": "sha512-nBeQgg/ZZA3u3SYxyaDvpvDtgZ/EZPF547ARgZBrG9Bhu1vKDwAIjtIf+sDtJUKa2zOcEbmRLBRSyMraS/Oy1A==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "node_modules/are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/are-we-there-yet/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/are-we-there-yet/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-includes": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", + "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.reduce": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz", + "integrity": "sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async-lock": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.3.1.tgz", + "integrity": "sha512-zK7xap9UnttfbE23JmcrNIyueAn6jWshihJqA33U/hEnKprF/lVGBDsBv/bqLm2YMMl1DnpHhUY044eA0t1TUw==" + }, + "node_modules/async-mutex": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz", + "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==", + "dependencies": { + "tslib": "^2.3.1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/babel-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", + "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "dev": true, + "dependencies": { + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/babel-jest/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/babel-jest/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-jest/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "dependencies": { + "object.assign": "^4.1.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", + "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", + "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.3.1", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", + "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.1", + "core-js-compat": "^3.21.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", + "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", + "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^27.5.1", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bip39": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.4.tgz", + "integrity": "sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw==", + "dependencies": { + "@types/node": "11.11.6", + "create-hash": "^1.1.0", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1" + } + }, + "node_modules/bip39/node_modules/@types/node": { + "version": "11.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", + "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==" + }, + "node_modules/bitset": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bitset/-/bitset-5.1.1.tgz", + "integrity": "sha512-oKaRp6mzXedJ1Npo86PKhWfDelI6HxxJo+it9nAcBB0HLVvYVp+5i6yj6DT5hfFgo+TS5T57MRWtw8zhwdTs3g==", + "engines": { + "node": "*" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.20.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.4.tgz", + "integrity": "sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001349", + "electron-to-chromium": "^1.4.147", + "escalade": "^3.1.1", + "node-releases": "^2.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001352", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz", + "integrity": "sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/canonicalize": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-1.0.8.tgz", + "integrity": "sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==" + }, + "node_modules/catering": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", + "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.11.tgz", + "integrity": "sha512-bQwNaDIBKID5ts/DsdhxrjqFXYfLw4ste+wMKqWA8DyKcS4qwsPP4Bk8ZNaTJjvpiX/qW3BT4sU7d6Bh5i+dag==", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/ci-info": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.1.tgz", + "integrity": "sha512-SXgeMX9VwDe7iFFaEWkA5AstuER9YKqy4EhHqr4DVqkwmD9rpVimkMKWHdjn30Ja45txyjhSn63lVX69eVCckg==", + "dev": true + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "node_modules/clean-git-ref": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz", + "integrity": "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw==" + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/convert-source-map/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true + }, + "node_modules/core-js-compat": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.23.0.tgz", + "integrity": "sha512-i4FgbtahOArZBEteiL+czI5N/bp17w16bXmLagGThdA2zuX1a5X4HbBmOVD7ERRtk3wMtPOFEmlXpVV4lsvwNw==", + "dev": true, + "dependencies": { + "browserslist": "^4.20.4", + "semver": "7.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dependencies": { + "node-fetch": "2.6.7" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "node_modules/data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", + "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", + "dev": true + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deferred-leveldown": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-7.0.0.tgz", + "integrity": "sha512-QKN8NtuS3BC6m0B8vAnBls44tX1WXAFATUsJlruyAYbZpysWV3siH6o/i3g9DCHauzodksO60bdj5NazNbjCmg==", + "dependencies": { + "abstract-leveldown": "^7.2.0", + "inherits": "^2.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/diff3": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz", + "integrity": "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g==" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dev": true, + "dependencies": { + "webidl-conversions": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.154", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.154.tgz", + "integrity": "sha512-GbV9djOkrnj6xmW+YYVVEI3VCQnJ0pnSTu7TW2JyjKd5cakoiSaG5R4RbEtfaD92GsY10DzbU3GYRe+IOA9kqA==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", + "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/encoding-down": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-7.1.0.tgz", + "integrity": "sha512-ky47X5jP84ryk5EQmvedQzELwVJPjCgXDQZGeb9F6r4PdChByCGHTBrVcF3h8ynKVJ1wVbkxTsDC8zBROPypgQ==", + "dependencies": { + "abstract-leveldown": "^7.2.0", + "inherits": "^2.0.3", + "level-codec": "^10.0.0", + "level-errors": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/encryptedfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/encryptedfs/-/encryptedfs-3.5.3.tgz", + "integrity": "sha512-2cTz6/8lUF2WFv6YNA9RwSASBh6bHIJqCbOWFr1RCo/vEHeR1+OKK0F+Xu4ujBlLsz3/a6NwT6/UoHl8Zn5rCg==", + "dependencies": { + "@matrixai/async-init": "^1.7.3", + "@matrixai/async-locks": "^2.2.4", + "@matrixai/db": "^4.0.2", + "@matrixai/errors": "^1.1.1", + "@matrixai/logger": "^2.1.1", + "@matrixai/resources": "^1.1.3", + "@matrixai/workers": "^1.3.3", + "errno": "^0.1.7", + "lexicographic-integer": "^1.1.0", + "node-forge": "^1.3.1", + "readable-stream": "^3.6.0", + "resource-counter": "^1.2.4", + "threads": "^1.6.5", + "util-callbackify": "^1.0.0" + } + }, + "node_modules/encryptedfs/node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.3.0.tgz", + "integrity": "sha512-/iP1rZrSEJ0DTlPiX+jbzlA3eVkY/e8L8SozroF395fIqE3TYF/Nz7YOMAawta+vLmyJ/hkGNNPcSbMADCCXbg==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", + "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.17.0.tgz", + "integrity": "sha512-gq0m0BTJfci60Fz4nczYxNAlED+sMcihltndR8t9t1evnU/azx53x3t2UHXC/uRjcbvRw/XctpaNygSTcQD+Iw==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.9.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.2", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", + "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", + "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/espree": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "dev": true, + "dependencies": { + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/expect": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", + "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", + "dev": true, + "dependencies": { + "@jest/types": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "node_modules/fast-fuzzy": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/fast-fuzzy/-/fast-fuzzy-1.11.2.tgz", + "integrity": "sha512-H1ct10Pzx+pSO4h7F1uBXET91ay2hy67J1aQZFKL23EXsOoanpwjPNQQoc+NhClKJMmlGGN+0bXhIdFJX70BJw==", + "dependencies": { + "graphemesplit": "^2.4.1" + } + }, + "node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fd-lock": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fd-lock/-/fd-lock-1.2.0.tgz", + "integrity": "sha512-Lk/pKH2DldLpG4Yh/sOOY84k5VqNzxHPffGwf1+yYI+/qMXzTPp9KJMX+Wh6n4xqGSA1Mu7JPmaDArfJGw2O/A==", + "hasInstallScript": true, + "dependencies": { + "napi-macros": "^2.0.0", + "node-gyp-build": "^4.2.2" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, + "node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/from2/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/from2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/from2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", + "dev": true, + "dependencies": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "node_modules/gauge/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/google-protobuf": { + "version": "3.20.1", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.20.1.tgz", + "integrity": "sha512-XMf1+O32FjYIV3CYu6Tuh5PNbfNEU5Xu22X+Xkdb/DUexFlCzhvv7d5Iirm4AOwn8lv4al1YvIhzGrg2j9Zfzw==" + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/graphemesplit": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/graphemesplit/-/graphemesplit-2.4.4.tgz", + "integrity": "sha512-lKrpp1mk1NH26USxC/Asw4OHbhSQf5XfrWZ+CDv/dFVvd1j17kFgMotdJvOesmHkbFX9P9sBfpH8VogxOWLg8w==", + "dependencies": { + "js-base64": "^3.6.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/grpc_tools_node_protoc_ts": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/grpc_tools_node_protoc_ts/-/grpc_tools_node_protoc_ts-5.3.2.tgz", + "integrity": "sha512-7xPSeu8bwjcird3i9R5+9O4BF2Lhv9fMBdeobfUc2Bys9tSVtm/VB3WjTpKV78WlLYJyD94+wL/8hJqaMZ53Hw==", + "dev": true, + "dependencies": { + "google-protobuf": "3.15.8", + "handlebars": "4.7.7" + }, + "bin": { + "protoc-gen-ts": "bin/protoc-gen-ts" + } + }, + "node_modules/grpc_tools_node_protoc_ts/node_modules/google-protobuf": { + "version": "3.15.8", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.15.8.tgz", + "integrity": "sha512-2jtfdqTaSxk0cuBJBtTTWsot4WtR9RVr2rXg7x7OoqiuOKopPrwXpM1G4dXIkLcUNRh3RKzz76C8IOkksZSeOw==", + "dev": true + }, + "node_modules/handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^1.0.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/htmlparser2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", + "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "entities": "^4.3.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/into-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", + "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==", + "dev": true, + "dependencies": { + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ip-num": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ip-num/-/ip-num-1.4.0.tgz", + "integrity": "sha512-MP+gq4uBvrvm+G7EwP14GcJeFK49/p6sZrNOarMUoExLRodULJQM8mnkb/SbT1YKxRsZfh8rgwei2pUJIa35jA==" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-observable": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-2.1.0.tgz", + "integrity": "sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/isomorphic-git": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.18.1.tgz", + "integrity": "sha512-SNYmHxzDMwmB0N5jNaac9xmUOYv9VpWqBoPVQFvPEp47dfREOfsWy32IDEM00jzrDLGn+GsaPL2j6RYIDaKtJw==", + "dependencies": { + "async-lock": "^1.1.0", + "clean-git-ref": "^2.0.1", + "crc-32": "^1.2.0", + "diff3": "0.0.3", + "ignore": "^5.1.4", + "minimisted": "^2.0.0", + "pako": "^1.0.10", + "pify": "^4.0.1", + "readable-stream": "^3.4.0", + "sha.js": "^2.4.9", + "simple-get": "^4.0.1" + }, + "bin": { + "isogit": "cli.cjs" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", + "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", + "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", + "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", + "dev": true, + "dependencies": { + "@jest/core": "^27.5.1", + "import-local": "^3.0.2", + "jest-cli": "^27.5.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", + "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", + "dev": true, + "dependencies": { + "@jest/types": "^27.5.1", + "execa": "^5.0.0", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", + "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", + "dev": true, + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^27.5.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-circus/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-circus/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-circus/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", + "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", + "dev": true, + "dependencies": { + "@jest/core": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "prompts": "^2.0.1", + "yargs": "^16.2.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", + "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.8.0", + "@jest/test-sequencer": "^27.5.1", + "@jest/types": "^27.5.1", + "babel-jest": "^27.5.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.9", + "jest-circus": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-jasmine2": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-config/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-config/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-docblock": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", + "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", + "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", + "dev": true, + "dependencies": { + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "jest-get-type": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-each/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-each/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-each/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", + "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", + "dev": true, + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1", + "jsdom": "^16.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", + "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", + "dev": true, + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", + "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", + "dev": true, + "dependencies": { + "@jest/types": "^27.5.1", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^27.5.1", + "jest-serializer": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "micromatch": "^4.0.4", + "walker": "^1.0.7" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-jasmine2": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", + "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^27.5.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-jasmine2/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-jasmine2/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-jasmine2/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-jasmine2/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-junit": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-13.2.0.tgz", + "integrity": "sha512-B0XNlotl1rdsvFZkFfoa19mc634+rrd8E4Sskb92Bb8MmSXeWV9XJGUyctunZS1W410uAxcyYuPUGVnbcOH8cg==", + "dependencies": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/jest-leak-detector": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", + "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", + "dev": true, + "dependencies": { + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-matcher-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-message-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", + "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "dev": true, + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-mock-process": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/jest-mock-process/-/jest-mock-process-1.5.1.tgz", + "integrity": "sha512-CPu46KyUiVSxE+LkqBuscqGmy1bvW2vJQuNstt83iLtFaFjgrgmp6LY04IKuOhhlGhcrdi86Gqq5/fTE2wG6lg==", + "dev": true, + "peerDependencies": { + "jest": ">=23.4 <29" + } + }, + "node_modules/jest-mock-props": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/jest-mock-props/-/jest-mock-props-1.9.1.tgz", + "integrity": "sha512-PvTySOTw/K4dwL7XrVGq/VUZRm/qXPrV4+NuhgxuWkmE3h/Fd+g+qB0evK5vSBAkI8TaxvTXYv17IdxWdEze1g==", + "dev": true, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "jest": ">=24.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", + "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz", + "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", + "dev": true, + "dependencies": { + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", + "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", + "dev": true, + "dependencies": { + "@jest/types": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-snapshot": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-resolve/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-resolve/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-resolve/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", + "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", + "dev": true, + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-leak-detector": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "source-map-support": "^0.5.6", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runner/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-runner/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", + "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", + "dev": true, + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/globals": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runtime/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-runtime/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-serializer": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", + "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", + "dev": true, + "dependencies": { + "@types/node": "*", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz", + "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.2", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.0.0", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^27.5.1", + "semver": "^7.3.2" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-snapshot/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dev": true, + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", + "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", + "dev": true, + "dependencies": { + "@jest/types": "^27.5.1", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^27.5.1", + "leven": "^3.1.0", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-validate/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-validate/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", + "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^27.5.1", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-watcher/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-watcher/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-watcher/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jose": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.8.1.tgz", + "integrity": "sha512-+/hpTbRcCw9YC0TOfN1W47pej4a9lRmltdOVdRLz5FP5UvUq3CenhXjQK7u/8NdMIIShMXYAh9VLPhc7TjhvFw==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-base64": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz", + "integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", + "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "dev": true, + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.2.4", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", + "html-encoding-sniffer": "^2.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.6", + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", + "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "engines": { + "node": ">=6" + } + }, + "node_modules/lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", + "dev": true, + "engines": { + "node": "> 0.8" + } + }, + "node_modules/level": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/level/-/level-7.0.1.tgz", + "integrity": "sha512-w3E64+ALx2eZf8RV5JL4kIcE0BFAvQscRYd1yU4YVqZN9RGTQxXSvH202xvK15yZwFFxRXe60f13LJjcJ//I4Q==", + "dependencies": { + "level-js": "^6.1.0", + "level-packager": "^6.0.1", + "leveldown": "^6.1.0" + }, + "engines": { + "node": ">=10.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/level" + } + }, + "node_modules/level-codec": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-10.0.0.tgz", + "integrity": "sha512-QW3VteVNAp6c/LuV6nDjg7XDXx9XHK4abmQarxZmlRSDyXYk20UdaJTSX6yzVvQ4i0JyWSB7jert0DsyD/kk6g==", + "dependencies": { + "buffer": "^6.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/level-concat-iterator": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-3.1.0.tgz", + "integrity": "sha512-BWRCMHBxbIqPxJ8vHOvKUsaO0v1sLYZtjN3K2iZJsRBYtp+ONsY6Jfi6hy9K3+zolgQRryhIn2NRZjZnWJ9NmQ==", + "dependencies": { + "catering": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/level-errors": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-3.0.1.tgz", + "integrity": "sha512-tqTL2DxzPDzpwl0iV5+rBCv65HWbHp6eutluHNcVIftKZlQN//b6GEnZDM2CvGZvzGYMwyPtYppYnydBQd2SMQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/level-iterator-stream": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-5.0.0.tgz", + "integrity": "sha512-wnb1+o+CVFUDdiSMR/ZymE2prPs3cjVLlXuDeSq9Zb8o032XrabGEXcTCsBxprAtseO3qvFeGzh6406z9sOTRA==", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/level-js": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/level-js/-/level-js-6.1.0.tgz", + "integrity": "sha512-i7mPtkZm68aewfv0FnIUWvFUFfoyzIvVKnUmuQGrelEkP72vSPTaA1SGneWWoCV5KZJG4wlzbJLp1WxVNGuc6A==", + "dependencies": { + "abstract-leveldown": "^7.2.0", + "buffer": "^6.0.3", + "inherits": "^2.0.3", + "ltgt": "^2.1.2", + "run-parallel-limit": "^1.1.0" + } + }, + "node_modules/level-packager": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-6.0.1.tgz", + "integrity": "sha512-8Ezr0XM6hmAwqX9uu8IGzGNkWz/9doyPA8Oo9/D7qcMI6meJC+XhIbNYHukJhIn8OGdlzQs/JPcL9B8lA2F6EQ==", + "dependencies": { + "encoding-down": "^7.1.0", + "levelup": "^5.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/level-supports": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-2.1.0.tgz", + "integrity": "sha512-E486g1NCjW5cF78KGPrMDRBYzPuueMZ6VBXHT6gC7A8UYWGiM14fGgp+s/L1oFfDWSPV/+SFkYCmZ0SiESkRKA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/leveldown": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-6.1.1.tgz", + "integrity": "sha512-88c+E+Eizn4CkQOBHwqlCJaTNEjGpaEIikn1S+cINc5E9HEvJ77bqY4JY/HxT5u0caWqsc3P3DcFIKBI1vHt+A==", + "hasInstallScript": true, + "dependencies": { + "abstract-leveldown": "^7.2.0", + "napi-macros": "~2.0.0", + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/levelup": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-5.1.1.tgz", + "integrity": "sha512-0mFCcHcEebOwsQuk00WJwjLI6oCjbBuEYdh/RaRqhjnyVlzqf41T1NnDtCedumZ56qyIh8euLFDqV1KfzTAVhg==", + "dependencies": { + "catering": "^2.0.0", + "deferred-leveldown": "^7.0.0", + "level-errors": "^3.0.1", + "level-iterator-stream": "^5.0.0", + "level-supports": "^2.0.1", + "queue-microtask": "^1.2.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lexicographic-integer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/lexicographic-integer/-/lexicographic-integer-1.1.0.tgz", + "integrity": "sha512-MQCrf1gG31DJSNQDiIfgk7CQVlXkO6xC+DFGExs5WQWlxWSSAroH5k/UrKrS6LThHDHBoc3X1pNoYHDKOCPWRQ==" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ltgt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==" + }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/marked": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.17.tgz", + "integrity": "sha512-Wfk0ATOK5iPxM4ptrORkFemqroz0ZDxp5MWfYA7H/F+wO17NRWV5Ypxi6p3g2Xmw2bKeiYOl6oVnLHKxBA0VhA==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + }, + "node_modules/minimisted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minimisted/-/minimisted-2.0.1.tgz", + "integrity": "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA==", + "dependencies": { + "minimist": "^1.2.5" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/mocked-env": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/mocked-env/-/mocked-env-1.3.5.tgz", + "integrity": "sha512-GyYY6ynVOdEoRlaGpaq8UYwdWkvrsU2xRme9B+WPSuJcNjh17+3QIxSYU6zwee0SbehhV6f06VZ4ahjG+9zdrA==", + "dev": true, + "dependencies": { + "check-more-types": "2.24.0", + "debug": "4.3.2", + "lazy-ass": "1.6.0", + "ramda": "0.27.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/mocked-env/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multiformats": { + "version": "9.6.5", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.6.5.tgz", + "integrity": "sha512-vMwf/FUO+qAPvl3vlSZEgEVFY/AxeZq5yg761ScF3CZsXgmTi/HGkicUiNN0CI4PW8FiY2P0OLklOcmQjdQJhw==" + }, + "node_modules/multistream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/multistream/-/multistream-4.1.0.tgz", + "integrity": "sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "once": "^1.4.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true + }, + "node_modules/napi-macros": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", + "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/nexpect": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/nexpect/-/nexpect-0.6.0.tgz", + "integrity": "sha512-gG4cO0zoNG+kaPesw516hPVEKLW3YizGU8UWMr5lpkHKOgoTWcu4sPQN7rWVAIL4Ck87zM4N8immPUhYPdDz3g==", + "dev": true, + "dependencies": { + "cross-spawn": "^6.0.5" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/nexpect/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/nexpect/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/nexpect/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/nexpect/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nexpect/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nexpect/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/node-abi": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", + "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", + "dev": true, + "dependencies": { + "semver": "^5.4.1" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", + "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", + "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz", + "integrity": "sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==", + "dependencies": { + "array.prototype.reduce": "^1.0.4", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/observable-fns": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/observable-fns/-/observable-fns-0.6.1.tgz", + "integrity": "sha512-9gRK4+sRWzeN6AOewNBTLXir7Zl/i3GB6Yl26gK4flxz8BXVpD3kt8amREmWNb0mxYOGDotvE5a4N+PtGGKdkg==" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-is-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz", + "integrity": "sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==", + "dependencies": { + "entities": "^4.3.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pkg/-/pkg-5.6.0.tgz", + "integrity": "sha512-mHrAVSQWmHA41RnUmRpC7pK9lNnMfdA16CF3cqOI22a8LZxOQzF7M8YWtA2nfs+d7I0MTDXOtkDsAsFXeCpYjg==", + "dev": true, + "dependencies": { + "@babel/parser": "7.16.2", + "@babel/types": "7.16.0", + "chalk": "^4.1.2", + "escodegen": "^2.0.0", + "fs-extra": "^9.1.0", + "globby": "^11.0.4", + "into-stream": "^6.0.0", + "minimist": "^1.2.5", + "multistream": "^4.1.0", + "pkg-fetch": "3.3.0", + "prebuild-install": "6.1.4", + "progress": "^2.0.3", + "resolve": "^1.20.0", + "stream-meter": "^1.0.4", + "tslib": "2.3.1" + }, + "bin": { + "pkg": "lib-es5/bin.js" + }, + "peerDependencies": { + "node-notifier": ">=9.0.1" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-fetch": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.3.0.tgz", + "integrity": "sha512-xJnIZ1KP+8rNN+VLafwu4tEeV4m8IkFBDdCFqmAJz9K1aiXEtbARmdbEe6HlXWGSVuShSHjFXpfkKRkDBQ5kiA==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "fs-extra": "^9.1.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.6", + "progress": "^2.0.3", + "semver": "^7.3.5", + "tar-fs": "^2.1.1", + "yargs": "^16.2.0" + }, + "bin": { + "pkg-fetch": "lib-es5/bin.js" + } + }, + "node_modules/pkg-fetch/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pkg-fetch/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/pkg-fetch/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/pkg-fetch/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/pkg-fetch/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-fetch/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pkg-fetch/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg/node_modules/@babel/parser": { + "version": "7.16.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.2.tgz", + "integrity": "sha512-RUVpT0G2h6rOZwqLDTrKk7ksNv7YpAilTnYe1/Q+eDjxEceRMKVWbCsX7t8h6C1qCFi/1Y8WZjcEPBAFG27GPw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pkg/node_modules/@babel/types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", + "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.15.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/pkg/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pkg/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/pkg/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/pkg/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/pkg/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, + "node_modules/prebuild-install": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", + "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==", + "dev": true, + "dependencies": { + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.21.0", + "npmlog": "^4.0.1", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^3.0.3", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/prebuild-install/node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prebuild-install/node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prebuild-install/node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dev": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", + "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==" + }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ramda": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", + "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==", + "dev": true + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", + "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/regexpu-core": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", + "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.0.1", + "regjsgen": "^0.6.0", + "regjsparser": "^0.8.2", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", + "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", + "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/resource-counter": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/resource-counter/-/resource-counter-1.2.4.tgz", + "integrity": "sha512-DGJChvE5r4smqPE+xYNv9r1u/I9cCfRR5yfm7D6EQckdKqMyVpJ5z0s40yn0EM0puFxHg6mPORrQLQdEbJ/RnQ==", + "dependencies": { + "babel-runtime": "^6.26.0", + "bitset": "^5.0.3" + }, + "engines": { + "node": ">=6.4.0" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/run-parallel-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz", + "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shiki": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz", + "integrity": "sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.0.0", + "vscode-oniguruma": "^1.6.1", + "vscode-textmate": "5.2.0" + } + }, + "node_modules/shx": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "dev": true, + "dependencies": { + "minimist": "^1.2.3", + "shelljs": "^0.8.5" + }, + "bin": { + "shx": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stream-meter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stream-meter/-/stream-meter-1.0.4.tgz", + "integrity": "sha512-4sOEtrbgFotXwnEuzzsQBYEV1elAeFSO8rSGeTwabuX1RRn/kEq9JVH7I0MRBhKVRR0sJkr0M0QCH7yOLf9fhQ==", + "dev": true, + "dependencies": { + "readable-stream": "^2.1.4" + } + }, + "node_modules/stream-meter/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/stream-meter/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/stream-meter/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/threads": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/threads/-/threads-1.7.0.tgz", + "integrity": "sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==", + "dependencies": { + "callsites": "^3.1.0", + "debug": "^4.2.0", + "is-observable": "^2.1.0", + "observable-fns": "^0.6.1" + }, + "funding": { + "url": "https://github.com/andywer/threads.js?sponsor=1" + }, + "optionalDependencies": { + "tiny-worker": ">= 2" + } + }, + "node_modules/throat": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", + "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", + "dev": true + }, + "node_modules/timeout-refresh": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-1.0.3.tgz", + "integrity": "sha512-Mz0CX4vBGM5lj8ttbIFt7o4ZMxk/9rgudJRh76EvB7xXZMur7T/cjRiH2w4Fmkq0zxf2QpM8IFvOSRn8FEu3gA==" + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" + }, + "node_modules/tiny-worker": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tiny-worker/-/tiny-worker-2.3.0.tgz", + "integrity": "sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==", + "optional": true, + "dependencies": { + "esm": "^3.2.25" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-custom-error": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.2.0.tgz", + "integrity": "sha512-cBvC2QjtvJ9JfWLvstVnI45Y46Y5dMxIaG1TDMGAD/R87hpvqFL+7LhvUDhnRCfOnx/xitollFWWvUKKKhbN0A==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/ts-jest": { + "version": "27.1.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.5.tgz", + "integrity": "sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^27.0.0", + "json5": "2.x", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "20.x" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@types/jest": "^27.0.0", + "babel-jest": ">=27.0.0 <28", + "jest": "^27.0.0", + "typescript": ">=3.8 <5.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@types/jest": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-node": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.0", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typedoc": { + "version": "0.22.17", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.17.tgz", + "integrity": "sha512-h6+uXHVVCPDaANzjwzdsj9aePBjZiBTpiMpBBeyh1zcN2odVsDCNajz8zyKnixF93HJeGpl34j/70yoEE5BfNg==", + "dev": true, + "dependencies": { + "glob": "^8.0.3", + "lunr": "^2.3.9", + "marked": "^4.0.16", + "minimatch": "^5.1.0", + "shiki": "^0.10.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 12.10.0" + }, + "peerDependencies": { + "typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x || 4.6.x || 4.7.x" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/typescript": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz", + "integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/typescript-cached-transpile": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typescript-cached-transpile/-/typescript-cached-transpile-0.0.6.tgz", + "integrity": "sha512-bfPc7YUW0PrVkQHU0xN0ANRuxdPgoYYXtZEW6PNkH5a97/AOM+kPPxSTMZbpWA3BG1do22JUkfC60KoCKJ9VZQ==", + "dev": true, + "dependencies": { + "@types/node": "^12.12.7", + "fs-extra": "^8.1.0", + "tslib": "^1.10.0" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/typescript-cached-transpile/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "dev": true + }, + "node_modules/typescript-cached-transpile/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/typescript-cached-transpile/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/typescript-cached-transpile/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/typescript-cached-transpile/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/uglify-js": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.16.0.tgz", + "integrity": "sha512-FEikl6bR30n0T3amyBh3LoiBdqHRy/f4H80+My34HOesOKyHfOsxAPAxOoqC0JUnC1amnO0IwkYC3sko51caSw==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unicode-trie/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unordered-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unordered-set/-/unordered-set-2.0.1.tgz", + "integrity": "sha512-eUmNTPzdx+q/WvOHW0bgGYLWvWHNT3PTKEQLg0MAQhc0AHASHVHoP/9YytYd4RBVariqno/mEUhVZN98CmD7bg==" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-callbackify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util-callbackify/-/util-callbackify-1.0.0.tgz", + "integrity": "sha512-5vEPPSM6DCHlCpq9FZryeIkY5FQMUqXLUz4yHtU369Z/abWUVdgInPVeINjWJV3Bk9DZhCr+JzGarEByPLsxBQ==", + "dependencies": { + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utp-native": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/utp-native/-/utp-native-2.5.3.tgz", + "integrity": "sha512-sWTrWYXPhhWJh+cS2baPzhaZc89zwlWCfwSthUjGhLkZztyPhcQllo+XVVCbNGi7dhyRlxkWxN4NKU6FbA9Y8w==", + "hasInstallScript": true, + "dependencies": { + "napi-macros": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.0.2", + "timeout-refresh": "^1.0.0", + "unordered-set": "^2.0.1" + }, + "bin": { + "ucat": "ucat.js" + }, + "engines": { + "node": ">=8.12" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", + "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/vscode-oniguruma": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz", + "integrity": "sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz", + "integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==", + "dev": true + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "dependencies": { + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true, + "engines": { + "node": ">=10.4" + } + }, + "node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "dev": true, + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.8.tgz", + "integrity": "sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==" + }, + "node_modules/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + }, "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, "@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "requires": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.16.7" } }, "@babel/compat-data": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", - "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.5.tgz", + "integrity": "sha512-BxhE40PVCBxVEJsSBhB6UWyAuqJRxGsAw8BdHMJ3AKGydcwuWW4kOO3HmqBQAdcq/OP+/DlTVxLvsCzRTnZuGg==", "dev": true }, "@babel/core": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.8.tgz", - "integrity": "sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.15.8", - "@babel/generator": "^7.15.8", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.8", - "@babel/helpers": "^7.15.4", - "@babel/parser": "^7.15.8", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6", + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.5.tgz", + "integrity": "sha512-MGY8vg3DxMnctw0LdvSEojOsumc70g0t18gNyUdAZqB1Rpd1Bqo/svHGvt+UJ6JcGX+DIekGFDxxIWofBxLCnQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.18.2", + "@babel/helper-compilation-targets": "^7.18.2", + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helpers": "^7.18.2", + "@babel/parser": "^7.18.5", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.5", + "@babel/types": "^7.18.4", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" + "json5": "^2.2.1", + "semver": "^6.3.0" } }, "@babel/generator": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz", - "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz", + "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==", "dev": true, "requires": { - "@babel/types": "^7.15.6", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.18.2", + "@jridgewell/gen-mapping": "^0.3.0", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", + "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } } }, "@babel/helper-annotate-as-pure": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.15.4.tgz", - "integrity": "sha512-QwrtdNvUNsPCj2lfNQacsGSQvGX8ee1ttrBrcozUP2Sv/jylewBP/8QFe6ZkBsC8T/GYWonNAWJV4aRR9AL2DA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.15.4.tgz", - "integrity": "sha512-P8o7JP2Mzi0SdC6eWr1zF+AEYvrsZa7GSY1lTayjF5XJhVH0kjLYUZPvTMflP7tBgZoe9gIhTa60QwFpqh/E0Q==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", + "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/helper-explode-assignable-expression": "^7.16.7", + "@babel/types": "^7.16.7" } }, "@babel/helper-compilation-targets": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", - "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz", + "integrity": "sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", + "@babel/compat-data": "^7.17.10", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.20.2", "semver": "^6.3.0" } }, "@babel/helper-create-class-features-plugin": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.15.4.tgz", - "integrity": "sha512-7ZmzFi+DwJx6A7mHRwbuucEYpyBwmh2Ca0RvI6z2+WLZYCqV0JOaLb+u0zbtmDicebgKBZgqbYfLaKNqSgv5Pw==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.0.tgz", + "integrity": "sha512-Kh8zTGR9de3J63e5nS0rQUdRs/kbtwoeQQ0sriS0lItjC96u8XXZN6lKpuyWd2coKSU13py/y+LTmThLuVX0Pg==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4" + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-member-expression-to-functions": "^7.17.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7" } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.5.tgz", - "integrity": "sha512-TLawwqpOErY2HhWbGJ2nZT5wSkR192QpN+nBg1THfBfftrlvOh+WbhrxXCH4q4xJ9Gl16BGPR/48JA+Ryiho/A==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.12.tgz", + "integrity": "sha512-b2aZrV4zvutr9AIa6/gA3wsZKRwTKYoDxYiFKcESS3Ug2GTXzwBEvMuuFLhCQpEnRXs1zng4ISAXSUxxKBIcxw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "regexpu-core": "^4.7.1" + "@babel/helper-annotate-as-pure": "^7.16.7", + "regexpu-core": "^5.0.1" } }, "@babel/helper-define-polyfill-provider": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.3.tgz", - "integrity": "sha512-RH3QDAfRMzj7+0Nqu5oqgO5q9mFtQEVvCRsi8qCEfzLR9p2BHfn5FzhSB2oj1fF7I2+DcTORkYaQ6aTR9Cofew==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", + "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", "dev": true, "requires": { "@babel/helper-compilation-targets": "^7.13.0", @@ -124,362 +12028,368 @@ "semver": "^6.1.2" } }, + "@babel/helper-environment-visitor": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz", + "integrity": "sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ==", + "dev": true + }, "@babel/helper-explode-assignable-expression": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.15.4.tgz", - "integrity": "sha512-J14f/vq8+hdC2KoWLIQSsGrC9EFBKE4NFts8pfMpymfApds+fPqR30AOUWc4tyr56h9l/GA1Sxv2q3dLZWbQ/g==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", + "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, "@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", + "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/template": "^7.16.7", + "@babel/types": "^7.17.0" } }, "@babel/helper-hoist-variables": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", - "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", - "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz", + "integrity": "sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.17.0" } }, "@babel/helper-module-imports": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", - "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, "@babel/helper-module-transforms": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.8.tgz", - "integrity": "sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz", + "integrity": "sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-simple-access": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/helper-validator-identifier": "^7.15.7", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6" + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.0", + "@babel/types": "^7.18.0" } }, "@babel/helper-optimise-call-expression": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", - "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", + "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz", + "integrity": "sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA==", "dev": true }, "@babel/helper-remap-async-to-generator": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.15.4.tgz", - "integrity": "sha512-v53MxgvMK/HCwckJ1bZrq6dNKlmwlyRNYM6ypaRTdXWGOE2c1/SCa6dL/HimhPulGhZKw9W0QhREM583F/t0vQ==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", + "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-wrap-function": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-wrap-function": "^7.16.8", + "@babel/types": "^7.16.8" } }, "@babel/helper-replace-supers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", - "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.2.tgz", + "integrity": "sha512-XzAIyxx+vFnrOxiQrToSUOzUOn0e1J2Li40ntddek1Y69AXUTXoDJ40/D5RdjFu7s7qHiaeoTiempZcbuVXh2Q==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/helper-environment-visitor": "^7.18.2", + "@babel/helper-member-expression-to-functions": "^7.17.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/traverse": "^7.18.2", + "@babel/types": "^7.18.2" } }, "@babel/helper-simple-access": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", - "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz", + "integrity": "sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.18.2" } }, "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.15.4.tgz", - "integrity": "sha512-BMRLsdh+D1/aap19TycS4eD1qELGrCBJwzaY9IE8LrpJtJb+H7rQkPIdsfgnMtLBA6DJls7X9z93Z4U8h7xw0A==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", + "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.0" } }, "@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", "dev": true }, "@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", "dev": true }, "@babel/helper-wrap-function": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.15.4.tgz", - "integrity": "sha512-Y2o+H/hRV5W8QhIfTpRIBwl57y8PrZt6JM3V8FOo5qarjshHItyH5lXlpMfBfmBefOqSCpKZs/6Dxqp0E/U+uw==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", + "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/helper-function-name": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.8", + "@babel/types": "^7.16.8" } }, "@babel/helpers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", - "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.2.tgz", + "integrity": "sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg==", "dev": true, "requires": { - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.2", + "@babel/types": "^7.18.2" } }, "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", + "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", - "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.5.tgz", + "integrity": "sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw==", "dev": true }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.17.12.tgz", + "integrity": "sha512-xCJQXl4EeQ3J9C4yOmpTrtVGmzpm2iSzyxbkZHw7UCnZBftHpF/hpII80uWVyVrc40ytIClHjgWGTG1g/yB+aw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.15.4.tgz", - "integrity": "sha512-eBnpsl9tlhPhpI10kU06JHnrYXwg3+V6CaP2idsCXNef0aeslpqyITXQ74Vfk5uHgY7IG7XP0yIH8b42KSzHog==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.17.12.tgz", + "integrity": "sha512-/vt0hpIw0x4b6BLKUkwlvEoiGZYYLNZ96CzyHYPbtG2jZGz6LBe7/V+drYrc/d+ovrF9NBi0pmtvmNb/FsWtRQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.15.4", - "@babel/plugin-proposal-optional-chaining": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.17.12" } }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.15.8.tgz", - "integrity": "sha512-2Z5F2R2ibINTc63mY7FLqGfEbmofrHU9FitJW1Q7aPaKFhiPvSq6QEt/BoWN5oME3GVyjcRuNNSRbb9LC0CSWA==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.17.12.tgz", + "integrity": "sha512-RWVvqD1ooLKP6IqWTA5GyFVX2isGEgC5iFxKzfYOIy/QEFdxYyCybBDtIGjipHpb9bDWHzcqGqFakf+mVmBTdQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-remap-async-to-generator": "^7.15.4", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-remap-async-to-generator": "^7.16.8", "@babel/plugin-syntax-async-generators": "^7.8.4" } }, "@babel/plugin-proposal-class-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.14.5.tgz", - "integrity": "sha512-q/PLpv5Ko4dVc1LYMpCY7RVAAO4uk55qPwrIuJ5QJ8c6cVuAmhu7I/49JOppXL6gXf7ZHzpRVEUZdYoPLM04Gg==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.17.12.tgz", + "integrity": "sha512-U0mI9q8pW5Q9EaTHFPwSVusPMV/DV9Mm8p7csqROFLtIE9rBF5piLqyrBGigftALrBcsBGu4m38JneAe7ZDLXw==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-proposal-class-static-block": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.15.4.tgz", - "integrity": "sha512-M682XWrrLNk3chXCjoPUQWOyYsB93B9z3mRyjtqqYJWDf2mfCdIYgDrA11cgNVhAQieaq6F2fn2f3wI0U4aTjA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.0.tgz", + "integrity": "sha512-t+8LsRMMDE74c6sV7KShIw13sqbqd58tlqNrsWoWBTIMw7SVQ0cZ905wLNS/FBCy/3PyooRHLFFlfrUNyyz5lA==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-create-class-features-plugin": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", "@babel/plugin-syntax-class-static-block": "^7.14.5" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.5.tgz", - "integrity": "sha512-ExjiNYc3HDN5PXJx+bwC50GIx/KKanX2HiggnIUAYedbARdImiCU4RhhHfdf0Kd7JNXGpsBBBCOm+bBVy3Gb0g==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", + "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3" } }, "@babel/plugin-proposal-export-namespace-from": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.5.tgz", - "integrity": "sha512-g5POA32bXPMmSBu5Dx/iZGLGnKmKPc5AiY7qfZgurzrCYgIztDlHFbznSNCoQuv57YQLnQfaDi7dxCtLDIdXdA==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.17.12.tgz", + "integrity": "sha512-j7Ye5EWdwoXOpRmo5QmRyHPsDIe6+u70ZYZrd7uz+ebPYFKfRcLcNu3Ro0vOlJ5zuv8rU7xa+GttNiRzX56snQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.17.12", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.5.tgz", - "integrity": "sha512-NSq2fczJYKVRIsUJyNxrVUMhB27zb7N7pOFGQOhBKJrChbGcgEAqyZrmZswkPk18VMurEeJAaICbfm57vUeTbQ==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.17.12.tgz", + "integrity": "sha512-rKJ+rKBoXwLnIn7n6o6fulViHMrOThz99ybH+hKHcOZbnN14VuMnH9fo2eHE69C8pO4uX1Q7t2HYYIDmv8VYkg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.17.12", "@babel/plugin-syntax-json-strings": "^7.8.3" } }, "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.14.5.tgz", - "integrity": "sha512-YGn2AvZAo9TwyhlLvCCWxD90Xq8xJ4aSgaX3G5D/8DW94L8aaT+dS5cSP+Z06+rCJERGSr9GxMBZ601xoc2taw==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.17.12.tgz", + "integrity": "sha512-EqFo2s1Z5yy+JeJu7SFfbIUtToJTVlC61/C7WLKDntSw4Sz6JNAIfL7zQ74VvirxpjB5kz/kIx0gCcb+5OEo2Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.17.12", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" } }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.5.tgz", - "integrity": "sha512-gun/SOnMqjSb98Nkaq2rTKMwervfdAoz6NphdY0vTfuzMfryj+tDGb2n6UkDKwez+Y8PZDhE3D143v6Gepp4Hg==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.17.12.tgz", + "integrity": "sha512-ws/g3FSGVzv+VH86+QvgtuJL/kR67xaEIF2x0iPqdDfYW6ra6JF3lKVBkWynRLcNtIC1oCTfDRVxmm2mKzy+ag==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.17.12", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.14.5.tgz", - "integrity": "sha512-yiclALKe0vyZRZE0pS6RXgjUOt87GWv6FYa5zqj15PvhOGFO69R5DusPlgK/1K5dVnCtegTiWu9UaBSrLLJJBg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", + "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.15.6.tgz", - "integrity": "sha512-qtOHo7A1Vt+O23qEAX+GdBpqaIuD3i9VRrWgCJeq7WO6H2d14EK3q11urj5Te2MAeK97nMiIdRpwd/ST4JFbNg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.0.tgz", + "integrity": "sha512-nbTv371eTrFabDfHLElkn9oyf9VG+VKK6WMzhY2o4eHKaG19BToD9947zzGMO6I/Irstx9d8CwX6njPNIAR/yw==", "dev": true, "requires": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/compat-data": "^7.17.10", + "@babel/helper-compilation-targets": "^7.17.10", + "@babel/helper-plugin-utils": "^7.17.12", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.15.4" + "@babel/plugin-transform-parameters": "^7.17.12" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.5.tgz", - "integrity": "sha512-3Oyiixm0ur7bzO5ybNcZFlmVsygSIQgdOa7cTfOYCMY+wEPAYhZAJxi3mixKFCTCKUhQXuCTtQ1MzrpL3WT8ZQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", + "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.5.tgz", - "integrity": "sha512-ycz+VOzo2UbWNI1rQXxIuMOzrDdHGrI23fRiz/Si2R4kv2XZQ1BK8ccdHwehMKBlcH/joGW/tzrUmo67gbJHlQ==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.17.12.tgz", + "integrity": "sha512-7wigcOs/Z4YWlK7xxjkvaIw84vGhDv/P1dFGQap0nHkc8gFKY/r+hXc8Qzf5k1gY7CvGIcHqAnOagVKJJ1wVOQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", "@babel/plugin-syntax-optional-chaining": "^7.8.3" } }, "@babel/plugin-proposal-private-methods": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.14.5.tgz", - "integrity": "sha512-838DkdUA1u+QTCplatfq4B7+1lnDa/+QMI89x5WZHBcnNv+47N8QEj2k9I2MUU9xIv8XJ4XvPCviM/Dj7Uwt9g==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.17.12.tgz", + "integrity": "sha512-SllXoxo19HmxhDWm3luPz+cPhtoTSKLJE9PXshsfrOzBqs60QP0r8OaJItrPhAj0d7mZMnNF0Y1UUggCDgMz1A==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-proposal-private-property-in-object": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.15.4.tgz", - "integrity": "sha512-X0UTixkLf0PCCffxgu5/1RQyGGbgZuKoI+vXP4iSbJSYwPb7hu06omsFGBvQ9lJEvwgrxHdS8B5nbfcd8GyUNA==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.17.12.tgz", + "integrity": "sha512-/6BtVi57CJfrtDNKfK5b66ydK2J5pXUKBKSPD2G1whamMuEnZWgoOIfO8Vf9F/DoD4izBLD/Au4NMQfruzzykg==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-create-class-features-plugin": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-create-class-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.14.5.tgz", - "integrity": "sha512-6axIeOU5LnY471KenAB9vI8I5j7NQ2d652hIYwVyRfgaZT5UpiqFKCuVXCDMSrU+3VFafnu2c5m3lrWIlr6A5Q==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.17.12.tgz", + "integrity": "sha512-Wb9qLjXf3ZazqXA7IvI7ozqRIXIGPtSo+L5coFmEkhTQK18ao4UDDD0zdTGAarmbLj2urpRwrc6893cu5Bfh0A==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-regexp-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-syntax-async-generators": { @@ -536,6 +12446,15 @@ "@babel/helper-plugin-utils": "^7.8.3" } }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.17.12.tgz", + "integrity": "sha512-n/loy2zkq9ZEM8tEOwON9wTQSTNDTDEz6NujPtJGLU7qObzT1N4c4YZZf8E6ATB2AjNQg/Ib2AIpO03EZaCehw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, "@babel/plugin-syntax-import-meta": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", @@ -626,349 +12545,364 @@ "@babel/helper-plugin-utils": "^7.14.5" } }, + "@babel/plugin-syntax-typescript": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz", + "integrity": "sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, "@babel/plugin-transform-arrow-functions": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.14.5.tgz", - "integrity": "sha512-KOnO0l4+tD5IfOdi4x8C1XmEIRWUjNRV8wc6K2vz/3e8yAOoZZvsRXRRIF/yo/MAOFb4QjtAw9xSxMXbSMRy8A==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.17.12.tgz", + "integrity": "sha512-PHln3CNi/49V+mza4xMwrg+WGYevSF1oaiXaC2EQfdp4HWlSjRsrDXWJiQBKpP7749u6vQ9mcry2uuFOv5CXvA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.14.5.tgz", - "integrity": "sha512-szkbzQ0mNk0rpu76fzDdqSyPu0MuvpXgC+6rz5rpMb5OIRxdmHfQxrktL8CYolL2d8luMCZTR0DpIMIdL27IjA==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.17.12.tgz", + "integrity": "sha512-J8dbrWIOO3orDzir57NRsjg4uxucvhby0L/KZuGsWDj0g7twWK3g7JhJhOrXtuXiw8MeiSdJ3E0OW9H8LYEzLQ==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-remap-async-to-generator": "^7.14.5" + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-remap-async-to-generator": "^7.16.8" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.14.5.tgz", - "integrity": "sha512-dtqWqdWZ5NqBX3KzsVCWfQI3A53Ft5pWFCT2eCVUftWZgjc5DpDponbIF1+c+7cSGk2wN0YK7HGL/ezfRbpKBQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", + "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.15.3.tgz", - "integrity": "sha512-nBAzfZwZb4DkaGtOes1Up1nOAp9TDRRFw4XBzBBSG9QK7KVFmYzgj9o9sbPv7TX5ofL4Auq4wZnxCoPnI/lz2Q==", + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.4.tgz", + "integrity": "sha512-+Hq10ye+jlvLEogSOtq4mKvtk7qwcUQ1f0Mrueai866C82f844Yom2cttfJdMdqRLTxWpsbfbkIkOIfovyUQXw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, - "@babel/plugin-transform-classes": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.15.4.tgz", - "integrity": "sha512-Yjvhex8GzBmmPQUvpXRPWQ9WnxXgAFuZSrqOK/eJlOGIXwvv8H3UEdUigl1gb/bnjTrln+e8bkZUYCBt/xYlBg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/plugin-transform-classes": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.4.tgz", + "integrity": "sha512-e42NSG2mlKWgxKUAD9EJJSkZxR67+wZqzNxLSpc51T8tRU5SLFHsPmgYR5yr7sdgX4u+iHA1C5VafJ6AyImV3A==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.18.2", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-replace-supers": "^7.18.2", + "@babel/helper-split-export-declaration": "^7.16.7", "globals": "^11.1.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.14.5.tgz", - "integrity": "sha512-pWM+E4283UxaVzLb8UBXv4EIxMovU4zxT1OPnpHJcmnvyY9QbPPTKZfEj31EUvG3/EQRbYAGaYEUZ4yWOBC2xg==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.17.12.tgz", + "integrity": "sha512-a7XINeplB5cQUWMg1E/GI1tFz3LfK021IjV1rj1ypE+R7jHm+pIHmHl25VNkZxtx9uuYp7ThGk8fur1HHG7PgQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-destructuring": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz", - "integrity": "sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.0.tgz", + "integrity": "sha512-Mo69klS79z6KEfrLg/1WkmVnB8javh75HX4pi2btjvlIoasuxilEyjtsQW6XPrubNd7AQy0MMaNIaQE4e7+PQw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.14.5.tgz", - "integrity": "sha512-loGlnBdj02MDsFaHhAIJzh7euK89lBrGIdM9EAtHFo6xKygCUGuuWe07o1oZVk287amtW1n0808sQM99aZt3gw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", + "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.14.5.tgz", - "integrity": "sha512-iJjbI53huKbPDAsJ8EmVmvCKeeq21bAze4fu9GBQtSLqfvzj2oRuHVx4ZkDwEhg1htQ+5OBZh/Ab0XDf5iBZ7A==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.17.12.tgz", + "integrity": "sha512-EA5eYFUG6xeerdabina/xIoB95jJ17mAkR8ivx6ZSu9frKShBjpOGZPn511MTDTkiCO+zXnzNczvUM69YSf3Zw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.14.5.tgz", - "integrity": "sha512-jFazJhMBc9D27o9jDnIE5ZErI0R0m7PbKXVq77FFvqFbzvTMuv8jaAwLZ5PviOLSFttqKIW0/wxNSDbjLk0tYA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", + "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-for-of": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.15.4.tgz", - "integrity": "sha512-DRTY9fA751AFBDh2oxydvVm4SYevs5ILTWLs6xKXps4Re/KG5nfUkr+TdHCrRWB8C69TlzVgA9b3RmGWmgN9LA==", + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.1.tgz", + "integrity": "sha512-+TTB5XwvJ5hZbO8xvl2H4XaMDOAK57zF4miuC9qQJgysPNEAZZ9Z69rdF5LJkozGdZrjBIUAIyKUWRMmebI7vg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.14.5.tgz", - "integrity": "sha512-vbO6kv0fIzZ1GpmGQuvbwwm+O4Cbm2NrPzwlup9+/3fdkuzo1YqOZcXw26+YUJB84Ja7j9yURWposEHLYwxUfQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", + "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.14.5.tgz", - "integrity": "sha512-ql33+epql2F49bi8aHXxvLURHkxJbSmMKl9J5yHqg4PLtdE6Uc48CH1GS6TQvZ86eoB/ApZXwm7jlA+B3kra7A==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.17.12.tgz", + "integrity": "sha512-8iRkvaTjJciWycPIZ9k9duu663FT7VrBdNqNgxnVXEFwOIp55JWcZd23VBRySYbnS3PwQ3rGiabJBBBGj5APmQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.14.5.tgz", - "integrity": "sha512-WkNXxH1VXVTKarWFqmso83xl+2V3Eo28YY5utIkbsmXoItO8Q3aZxN4BTS2k0hz9dGUloHK26mJMyQEYfkn/+Q==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", + "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.5.tgz", - "integrity": "sha512-3lpOU8Vxmp3roC4vzFpSdEpGUWSMsHFreTWOMMLzel2gNGfHE5UWIh/LN6ghHs2xurUp4jRFYMUIZhuFbody1g==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.0.tgz", + "integrity": "sha512-h8FjOlYmdZwl7Xm2Ug4iX2j7Qy63NANI+NQVWQzv6r25fqgg7k2dZl03p95kvqNclglHs4FZ+isv4p1uXMA+QA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.15.4.tgz", - "integrity": "sha512-qg4DPhwG8hKp4BbVDvX1s8cohM8a6Bvptu4l6Iingq5rW+yRUAhe/YRup/YcW2zCOlrysEWVhftIcKzrEZv3sA==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.2.tgz", + "integrity": "sha512-f5A865gFPAJAEE0K7F/+nm5CmAE3y8AWlMBG9unu5j9+tk50UQVK0QS8RNxSp7MJf0wh97uYyLWt3Zvu71zyOQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-simple-access": "^7.15.4", + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-simple-access": "^7.18.2", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.15.4.tgz", - "integrity": "sha512-fJUnlQrl/mezMneR72CKCgtOoahqGJNVKpompKwzv3BrEXdlPspTcyxrZ1XmDTIr9PpULrgEQo3qNKp6dW7ssw==", + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.5.tgz", + "integrity": "sha512-SEewrhPpcqMF1V7DhnEbhVJLrC+nnYfe1E0piZMZXBpxi9WvZqWGwpsk7JYP7wPWeqaBh4gyKlBhHJu3uz5g4Q==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.9", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-validator-identifier": "^7.16.7", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.5.tgz", - "integrity": "sha512-RfPGoagSngC06LsGUYyM9QWSXZ8MysEjDJTAea1lqRjNECE3y0qIJF/qbvJxc4oA4s99HumIMdXOrd+TdKaAAA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.0.tgz", + "integrity": "sha512-d/zZ8I3BWli1tmROLxXLc9A6YXvGK8egMxHp+E/rRwMh1Kip0AP77VwZae3snEJ33iiWwvNv2+UIIhfalqhzZA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.9.tgz", - "integrity": "sha512-l666wCVYO75mlAtGFfyFwnWmIXQm3kSH0C3IRnJqWcZbWkoihyAdDhFm2ZWaxWTqvBvhVFfJjMRQ0ez4oN1yYA==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.17.12.tgz", + "integrity": "sha512-vWoWFM5CKaTeHrdUJ/3SIOTRV+MBVGybOC9mhJkaprGNt5demMymDW24yC74avb915/mIRe3TgNb/d8idvnCRA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5" + "@babel/helper-create-regexp-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-new-target": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.14.5.tgz", - "integrity": "sha512-Nx054zovz6IIRWEB49RDRuXGI4Gy0GMgqG0cII9L3MxqgXz/+rgII+RU58qpo4g7tNEx1jG7rRVH4ihZoP4esQ==", + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.5.tgz", + "integrity": "sha512-TuRL5uGW4KXU6OsRj+mLp9BM7pO8e7SGNTEokQRRxHFkXYMFiy2jlKSZPFtI/mKORDzciH+hneskcSOp0gU8hg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-object-super": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.14.5.tgz", - "integrity": "sha512-MKfOBWzK0pZIrav9z/hkRqIk/2bTv9qvxHzPQc12RcVkMOzpIKnFCNYJip00ssKWYkd8Sf5g0Wr7pqJ+cmtuFg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", + "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-replace-supers": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7" } }, "@babel/plugin-transform-parameters": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.15.4.tgz", - "integrity": "sha512-9WB/GUTO6lvJU3XQsSr6J/WKvBC2hcs4Pew8YxZagi6GkTdniyqp8On5kqdK8MN0LMeu0mGbhPN+O049NV/9FQ==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.17.12.tgz", + "integrity": "sha512-6qW4rWo1cyCdq1FkYri7AHpauchbGLXpdwnYsfxFb+KtddHENfsY5JZb35xUwkK5opOLcJ3BNd2l7PhRYGlwIA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-property-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.14.5.tgz", - "integrity": "sha512-r1uilDthkgXW8Z1vJz2dKYLV1tuw2xsbrp3MrZmD99Wh9vsfKoob+JTgri5VUb/JqyKRXotlOtwgu4stIYCmnw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", + "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-regenerator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.14.5.tgz", - "integrity": "sha512-NVIY1W3ITDP5xQl50NgTKlZ0GrotKtLna08/uGY6ErQt6VEQZXla86x/CTddm5gZdcr+5GSsvMeTmWA5Ii6pkg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.0.tgz", + "integrity": "sha512-C8YdRw9uzx25HSIzwA7EM7YP0FhCe5wNvJbZzjVNHHPGVcDJ3Aie+qGYYdS1oVQgn+B3eAIJbWFLrJ4Jipv7nw==", "dev": true, "requires": { - "regenerator-transform": "^0.14.2" + "@babel/helper-plugin-utils": "^7.17.12", + "regenerator-transform": "^0.15.0" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.14.5.tgz", - "integrity": "sha512-cv4F2rv1nD4qdexOGsRQXJrOcyb5CrgjUH9PKrrtyhSDBNWGxd0UIitjyJiWagS+EbUGjG++22mGH1Pub8D6Vg==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.17.12.tgz", + "integrity": "sha512-1KYqwbJV3Co03NIi14uEHW8P50Md6KqFgt0FfpHdK6oyAHQVTosgPuPSiWud1HX0oYJ1hGRRlk0fP87jFpqXZA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz", - "integrity": "sha512-xLucks6T1VmGsTB+GWK5Pl9Jl5+nRXD1uoFdA5TSO6xtiNjtXTjKkmPdFXVLGlK5A2/or/wQMKfmQ2Y0XJfn5g==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", + "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-spread": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.15.8.tgz", - "integrity": "sha512-/daZ8s2tNaRekl9YJa9X4bzjpeRZLt122cpgFnQPLGUe61PH8zMEBmYqKkW5xF5JUEh5buEGXJoQpqBmIbpmEQ==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.17.12.tgz", + "integrity": "sha512-9pgmuQAtFi3lpNUstvG9nGfk9DkrdmWNp9KeKPFmuZCpEnxRzYlS8JgwPjYj+1AWDOSvoGN0H30p1cBOmT/Svg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.15.4" + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.14.5.tgz", - "integrity": "sha512-Z7F7GyvEMzIIbwnziAZmnSNpdijdr4dWt+FJNBnBLz5mwDFkqIXU9wmBcWWad3QeJF5hMTkRe4dAq2sUZiG+8A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", + "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-template-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.14.5.tgz", - "integrity": "sha512-22btZeURqiepOfuy/VkFr+zStqlujWaarpMErvay7goJS6BWwdd6BY9zQyDLDa4x2S3VugxFb162IZ4m/S/+Gg==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.2.tgz", + "integrity": "sha512-/cmuBVw9sZBGZVOMkpAEaVLwm4JmK2GZ1dFKOGGpMzEHWFmyZZ59lUU0PdRr8YNYeQdNzTDwuxP2X2gzydTc9g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.14.5.tgz", - "integrity": "sha512-lXzLD30ffCWseTbMQzrvDWqljvZlHkXU+CnseMhkMNqU1sASnCsz3tSzAaH3vCUXb9PHeUb90ZT1BdFTm1xxJw==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.17.12.tgz", + "integrity": "sha512-Q8y+Jp7ZdtSPXCThB6zjQ74N3lj0f6TDh1Hnf5B+sYlzQ8i5Pjp8gW0My79iekSpT4WnI06blqP6DT0OmaXXmw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.14.5.tgz", - "integrity": "sha512-crTo4jATEOjxj7bt9lbYXcBAM3LZaUrbP2uUdxb6WIorLmjNKSpHfIybgY4B8SRpbf8tEVIWH3Vtm7ayCrKocA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", + "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.14.5.tgz", - "integrity": "sha512-UygduJpC5kHeCiRw/xDVzC+wj8VaYSoKl5JNVmbP7MadpNinAm3SvZCxZ42H37KZBKztz46YC73i9yV34d0Tzw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", + "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/preset-env": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.15.8.tgz", - "integrity": "sha512-rCC0wH8husJgY4FPbHsiYyiLxSY8oMDJH7Rl6RQMknbN9oDDHhM9RDFvnGM2MgkbUJzSQB4gtuwygY5mCqGSsA==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-validator-option": "^7.14.5", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.15.4", - "@babel/plugin-proposal-async-generator-functions": "^7.15.8", - "@babel/plugin-proposal-class-properties": "^7.14.5", - "@babel/plugin-proposal-class-static-block": "^7.15.4", - "@babel/plugin-proposal-dynamic-import": "^7.14.5", - "@babel/plugin-proposal-export-namespace-from": "^7.14.5", - "@babel/plugin-proposal-json-strings": "^7.14.5", - "@babel/plugin-proposal-logical-assignment-operators": "^7.14.5", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", - "@babel/plugin-proposal-numeric-separator": "^7.14.5", - "@babel/plugin-proposal-object-rest-spread": "^7.15.6", - "@babel/plugin-proposal-optional-catch-binding": "^7.14.5", - "@babel/plugin-proposal-optional-chaining": "^7.14.5", - "@babel/plugin-proposal-private-methods": "^7.14.5", - "@babel/plugin-proposal-private-property-in-object": "^7.15.4", - "@babel/plugin-proposal-unicode-property-regex": "^7.14.5", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.2.tgz", + "integrity": "sha512-PfpdxotV6afmXMU47S08F9ZKIm2bJIQ0YbAAtDfIENX7G1NUAXigLREh69CWDjtgUy7dYn7bsMzkgdtAlmS68Q==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.10", + "@babel/helper-compilation-targets": "^7.18.2", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.17.12", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.17.12", + "@babel/plugin-proposal-async-generator-functions": "^7.17.12", + "@babel/plugin-proposal-class-properties": "^7.17.12", + "@babel/plugin-proposal-class-static-block": "^7.18.0", + "@babel/plugin-proposal-dynamic-import": "^7.16.7", + "@babel/plugin-proposal-export-namespace-from": "^7.17.12", + "@babel/plugin-proposal-json-strings": "^7.17.12", + "@babel/plugin-proposal-logical-assignment-operators": "^7.17.12", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.17.12", + "@babel/plugin-proposal-numeric-separator": "^7.16.7", + "@babel/plugin-proposal-object-rest-spread": "^7.18.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", + "@babel/plugin-proposal-optional-chaining": "^7.17.12", + "@babel/plugin-proposal-private-methods": "^7.17.12", + "@babel/plugin-proposal-private-property-in-object": "^7.17.12", + "@babel/plugin-proposal-unicode-property-regex": "^7.17.12", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.17.12", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", @@ -978,51 +12912,51 @@ "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.14.5", - "@babel/plugin-transform-async-to-generator": "^7.14.5", - "@babel/plugin-transform-block-scoped-functions": "^7.14.5", - "@babel/plugin-transform-block-scoping": "^7.15.3", - "@babel/plugin-transform-classes": "^7.15.4", - "@babel/plugin-transform-computed-properties": "^7.14.5", - "@babel/plugin-transform-destructuring": "^7.14.7", - "@babel/plugin-transform-dotall-regex": "^7.14.5", - "@babel/plugin-transform-duplicate-keys": "^7.14.5", - "@babel/plugin-transform-exponentiation-operator": "^7.14.5", - "@babel/plugin-transform-for-of": "^7.15.4", - "@babel/plugin-transform-function-name": "^7.14.5", - "@babel/plugin-transform-literals": "^7.14.5", - "@babel/plugin-transform-member-expression-literals": "^7.14.5", - "@babel/plugin-transform-modules-amd": "^7.14.5", - "@babel/plugin-transform-modules-commonjs": "^7.15.4", - "@babel/plugin-transform-modules-systemjs": "^7.15.4", - "@babel/plugin-transform-modules-umd": "^7.14.5", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.14.9", - "@babel/plugin-transform-new-target": "^7.14.5", - "@babel/plugin-transform-object-super": "^7.14.5", - "@babel/plugin-transform-parameters": "^7.15.4", - "@babel/plugin-transform-property-literals": "^7.14.5", - "@babel/plugin-transform-regenerator": "^7.14.5", - "@babel/plugin-transform-reserved-words": "^7.14.5", - "@babel/plugin-transform-shorthand-properties": "^7.14.5", - "@babel/plugin-transform-spread": "^7.15.8", - "@babel/plugin-transform-sticky-regex": "^7.14.5", - "@babel/plugin-transform-template-literals": "^7.14.5", - "@babel/plugin-transform-typeof-symbol": "^7.14.5", - "@babel/plugin-transform-unicode-escapes": "^7.14.5", - "@babel/plugin-transform-unicode-regex": "^7.14.5", - "@babel/preset-modules": "^0.1.4", - "@babel/types": "^7.15.6", - "babel-plugin-polyfill-corejs2": "^0.2.2", - "babel-plugin-polyfill-corejs3": "^0.2.5", - "babel-plugin-polyfill-regenerator": "^0.2.2", - "core-js-compat": "^3.16.0", + "@babel/plugin-transform-arrow-functions": "^7.17.12", + "@babel/plugin-transform-async-to-generator": "^7.17.12", + "@babel/plugin-transform-block-scoped-functions": "^7.16.7", + "@babel/plugin-transform-block-scoping": "^7.17.12", + "@babel/plugin-transform-classes": "^7.17.12", + "@babel/plugin-transform-computed-properties": "^7.17.12", + "@babel/plugin-transform-destructuring": "^7.18.0", + "@babel/plugin-transform-dotall-regex": "^7.16.7", + "@babel/plugin-transform-duplicate-keys": "^7.17.12", + "@babel/plugin-transform-exponentiation-operator": "^7.16.7", + "@babel/plugin-transform-for-of": "^7.18.1", + "@babel/plugin-transform-function-name": "^7.16.7", + "@babel/plugin-transform-literals": "^7.17.12", + "@babel/plugin-transform-member-expression-literals": "^7.16.7", + "@babel/plugin-transform-modules-amd": "^7.18.0", + "@babel/plugin-transform-modules-commonjs": "^7.18.2", + "@babel/plugin-transform-modules-systemjs": "^7.18.0", + "@babel/plugin-transform-modules-umd": "^7.18.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.17.12", + "@babel/plugin-transform-new-target": "^7.17.12", + "@babel/plugin-transform-object-super": "^7.16.7", + "@babel/plugin-transform-parameters": "^7.17.12", + "@babel/plugin-transform-property-literals": "^7.16.7", + "@babel/plugin-transform-regenerator": "^7.18.0", + "@babel/plugin-transform-reserved-words": "^7.17.12", + "@babel/plugin-transform-shorthand-properties": "^7.16.7", + "@babel/plugin-transform-spread": "^7.17.12", + "@babel/plugin-transform-sticky-regex": "^7.16.7", + "@babel/plugin-transform-template-literals": "^7.18.2", + "@babel/plugin-transform-typeof-symbol": "^7.17.12", + "@babel/plugin-transform-unicode-escapes": "^7.16.7", + "@babel/plugin-transform-unicode-regex": "^7.16.7", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.18.2", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.5.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", + "core-js-compat": "^3.22.1", "semver": "^6.3.0" } }, "@babel/preset-modules": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", - "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1033,57 +12967,50 @@ } }, "@babel/runtime": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", - "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.3.tgz", + "integrity": "sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==", "dev": true, "requires": { "regenerator-runtime": "^0.13.4" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true - } } }, "@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", "dev": true, "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" } }, "@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.5.tgz", + "integrity": "sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.18.2", + "@babel/helper-environment-visitor": "^7.18.2", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.18.5", + "@babel/types": "^7.18.4", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz", + "integrity": "sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.9", + "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" } }, @@ -1093,16 +13020,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "@cnakazawa/watch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", - "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", - "dev": true, - "requires": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" - } - }, "@cspotcode/source-map-consumer": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", @@ -1119,19 +13036,19 @@ } }, "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", "dev": true, "requires": { "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "debug": "^4.3.2", + "espree": "^9.3.2", + "globals": "^13.15.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "dependencies": { @@ -1147,52 +13064,80 @@ "uri-js": "^4.2.2" } }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", "dev": true, "requires": { "type-fest": "^0.20.2" } }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true } } }, "@grpc/grpc-js": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.3.7.tgz", - "integrity": "sha512-CKQVuwuSPh40tgOkR7c0ZisxYRiN05PcKPW72mQL5y++qd7CwBRoaJZvU5xfXnCJDFBmS3qZGQ71Frx6Ofo2XA==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.7.tgz", + "integrity": "sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==", "requires": { + "@grpc/proto-loader": "^0.6.4", "@types/node": ">=12.12.47" } }, + "@grpc/proto-loader": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "requires": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.11.3", + "yargs": "^16.2.0" + } + }, "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^1.2.0", + "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", "minimatch": "^3.0.4" } }, "@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, "@istanbuljs/load-nyc-config": { @@ -1215,19 +13160,28 @@ "dev": true }, "@jest/console": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", - "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", + "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", "dev": true, "requires": { - "@jest/types": "^26.6.2", + "@jest/types": "^27.5.1", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^26.6.2", - "jest-util": "^26.6.2", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", "slash": "^3.0.0" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1238,6 +13192,21 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1256,41 +13225,50 @@ } }, "@jest/core": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", - "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", + "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", "dev": true, "requires": { - "@jest/console": "^26.6.2", - "@jest/reporters": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/console": "^27.5.1", + "@jest/reporters": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", + "emittery": "^0.8.1", "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^26.6.2", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-resolve-dependencies": "^26.6.3", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "jest-watcher": "^26.6.2", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^27.5.1", + "jest-config": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-resolve-dependencies": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "jest-watcher": "^27.5.1", + "micromatch": "^4.0.4", "rimraf": "^3.0.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1301,6 +13279,21 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1319,75 +13312,84 @@ } }, "@jest/environment": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", - "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", + "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", "dev": true, "requires": { - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", - "jest-mock": "^26.6.2" + "jest-mock": "^27.5.1" } }, "@jest/fake-timers": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", - "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", + "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "@sinonjs/fake-timers": "^6.0.1", + "@jest/types": "^27.5.1", + "@sinonjs/fake-timers": "^8.0.1", "@types/node": "*", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" } }, "@jest/globals": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", - "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz", + "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", "dev": true, "requires": { - "@jest/environment": "^26.6.2", - "@jest/types": "^26.6.2", - "expect": "^26.6.2" + "@jest/environment": "^27.5.1", + "@jest/types": "^27.5.1", + "expect": "^27.5.1" } }, "@jest/reporters": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", - "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz", + "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", "dev": true, "requires": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/console": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.2", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", + "istanbul-lib-instrument": "^5.1.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "node-notifier": "^8.0.0", + "istanbul-reports": "^3.1.3", + "jest-haste-map": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", "slash": "^3.0.0", "source-map": "^0.6.0", "string-length": "^4.0.1", "terminal-link": "^2.0.0", - "v8-to-istanbul": "^7.0.0" + "v8-to-istanbul": "^8.1.0" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1398,28 +13400,25 @@ "supports-color": "^7.1.0" } }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" + "color-name": "~1.1.4" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "supports-color": { @@ -1434,72 +13433,72 @@ } }, "@jest/source-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", - "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", + "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", "dev": true, "requires": { "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "@jest/test-result": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", - "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", + "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", "dev": true, "requires": { - "@jest/console": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/console": "^27.5.1", + "@jest/types": "^27.5.1", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "@jest/test-sequencer": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", - "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", + "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", "dev": true, "requires": { - "@jest/test-result": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3" + "@jest/test-result": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-runtime": "^27.5.1" } }, "@jest/transform": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", - "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", + "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/types": "^26.6.2", - "babel-plugin-istanbul": "^6.0.0", + "@jest/types": "^27.5.1", + "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^1.4.0", "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-util": "^26.6.2", - "micromatch": "^4.0.2", - "pirates": "^4.0.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-util": "^27.5.1", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", "slash": "^3.0.0", "source-map": "^0.6.1", "write-file-atomic": "^3.0.0" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1510,18 +13509,27 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -1534,18 +13542,27 @@ } }, "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", - "@types/yargs": "^15.0.0", + "@types/yargs": "^16.0.0", "chalk": "^4.0.0" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1556,6 +13573,21 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1573,75 +13605,114 @@ } } }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", + "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", + "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", + "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz", + "integrity": "sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "@matrixai/async-init": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@matrixai/async-init/-/async-init-1.6.0.tgz", - "integrity": "sha512-I24u6McZnSH2yX1l5e2H3O/Lu8IVb2fM/sVbDeRYrzejV2XLv/9g/goz2fglSrXgJ877BBFJNW2GMxVzvvyA5A==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@matrixai/async-init/-/async-init-1.8.1.tgz", + "integrity": "sha512-ZAS1yd/PC+r3NwvT9fEz3OtAm68A8mKXXGdZRcYQF1ajl43jsV8/B4aDwr2oLFlV+RYZgWl7UwjZj4rtoZSycQ==", "requires": { - "async-mutex": "^0.3.2", - "ts-custom-error": "^3.2.0" - }, - "dependencies": { - "async-mutex": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz", - "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==", - "requires": { - "tslib": "^2.3.1" - } - } + "@matrixai/async-locks": "^2.3.1", + "@matrixai/errors": "^1.1.1" + } + }, + "@matrixai/async-locks": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@matrixai/async-locks/-/async-locks-2.3.1.tgz", + "integrity": "sha512-STz8VyiIXleaa72zMsq01x/ZO1gPzukUgMe25+uqMWn/nPrC9EtJOR7e3CW0DODfYDZ0748z196GeOjS3jh+4g==", + "requires": { + "@matrixai/errors": "^1.1.1", + "@matrixai/resources": "^1.1.3", + "async-mutex": "^0.3.2" } }, "@matrixai/db": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@matrixai/db/-/db-1.1.5.tgz", - "integrity": "sha512-zPpP/J1A3TLRaQKaGa5smualzjW4Rin4K48cpU5/9ThyXfpVBBp/mrkbDfjL/O5z6YTcuGVf2+yLck8tF8kVUw==", - "requires": { - "@matrixai/async-init": "^1.6.0", - "@matrixai/logger": "^2.0.1", - "@matrixai/workers": "^1.2.3", - "abstract-leveldown": "^7.0.0", - "async-mutex": "^0.3.1", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@matrixai/db/-/db-4.0.5.tgz", + "integrity": "sha512-X3gBcyPxC+bTEfi1J1Y49n1bglvg7HjM8MKNH5s+OUEswqKSZgeg1uWfXqvUqq72yjBtgRi4Ghmy4MdrIB1oMw==", + "requires": { + "@matrixai/async-init": "^1.7.3", + "@matrixai/errors": "^1.1.1", + "@matrixai/logger": "^2.1.1", + "@matrixai/resources": "^1.1.3", + "@matrixai/workers": "^1.3.3", + "@types/abstract-leveldown": "^7.2.0", "level": "7.0.1", - "levelup": "^5.0.1", - "sublevel-prefixer": "^1.0.0", - "subleveldown": "^5.0.1", - "threads": "^1.6.5", + "threads": "^1.6.5" + } + }, + "@matrixai/errors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@matrixai/errors/-/errors-1.1.2.tgz", + "integrity": "sha512-JSi2SIqdlqqDruANrTG8RMvLrJZAwduY19y26LZHx7DDkqhkqzF9fblbWaE9Fo1lhSTGk65oKRx2UjGn3v5gWw==", + "requires": { "ts-custom-error": "^3.2.0" - }, - "dependencies": { - "async-mutex": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz", - "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==", - "requires": { - "tslib": "^2.3.1" - } - } } }, "@matrixai/id": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@matrixai/id/-/id-3.3.2.tgz", - "integrity": "sha512-gpW56P7jZILPc0oxyNQvZBkEBn30JPGpslOHIcDoKnCZR8VGZXEKX485xy1WE4MBiQHXdGeiYF8A2CluqTnu3w==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@matrixai/id/-/id-3.3.3.tgz", + "integrity": "sha512-eXkxv68sCT17f6XQU8zMwgLLqwbdFm+8DoL3gXfBqfiDYxOCCQBS4L9kjUby8gRdlP7mIpkJ6oVAWCUnykGInA==", "requires": { "multiformats": "^9.4.8", "uuid": "^8.3.2" } }, "@matrixai/logger": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@matrixai/logger/-/logger-2.1.0.tgz", - "integrity": "sha512-UmLuXi2PJ03v0Scfl57217RPnjEZDRLlpfdIjIwCfju+kofnhhCI9P7OZu3/FgW147vbvSzWCrrtpwJiLROUUA==" + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@matrixai/logger/-/logger-2.2.2.tgz", + "integrity": "sha512-6/G1svkcFiBMvmIdBv6YbxoLKwMWpXNzt93Cc4XbXXygCQrsn6oYwLvnRk/JNr6uM29M2T+Aa7K1o3n2XMTuLw==" + }, + "@matrixai/resources": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@matrixai/resources/-/resources-1.1.3.tgz", + "integrity": "sha512-9zbA0NtgCtA+2hILpojshH6Pd679bIPtB8DcsPLVDzvGZP1TDwvtvZWCC3SG7oJUTzxqBI2Bfe+hypqwpvYPCw==" }, "@matrixai/workers": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@matrixai/workers/-/workers-1.2.5.tgz", - "integrity": "sha512-ikI4K6RGKQbG68it7TXJJ5wX2csW+WpokUehTnz5r66d7o6FC3PkojE46LPLCDSwk3NVCGoQ743OZS2nuA8SRA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@matrixai/workers/-/workers-1.3.3.tgz", + "integrity": "sha512-ID1sSJDXjM0hdWC10euWGcFofuys7+IDP+XTBh8Gq6jirn18xJs71wSy357qxLVSa7mL00qRJJfW6rljcFUK4A==", "requires": { - "@matrixai/logger": "^2.1.0", - "threads": "^1.6.5", - "ts-custom-error": "^3.2.0" + "@matrixai/async-init": "^1.7.3", + "@matrixai/errors": "^1.1.1", + "@matrixai/logger": "^2.1.1", + "threads": "^1.6.5" } }, "@nodelib/fs.scandir": { @@ -1670,6 +13741,60 @@ "fastq": "^1.6.0" } }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -1680,9 +13805,9 @@ } }, "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", "dev": true, "requires": { "@sinonjs/commons": "^1.7.0" @@ -1695,33 +13820,38 @@ "dev": true }, "@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", "dev": true }, "@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.10.tgz", + "integrity": "sha512-N+srakvPaYMGkwjNDx3ASx65Zl3QG8dJgVtIB+YMOkucU+zctlv/hdP5250VKdDHSDoW9PFZoCqbqNcAPjCjXA==", "dev": true }, "@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.2.tgz", + "integrity": "sha512-YwrUA5ysDXHFYfL0Xed9x3sNS4P+aKlCOnnbqUa2E5HdQshHFleCJVrj1PlGTb4GgFUCDyte1v3JWLy2sz8Oqg==", "dev": true }, "@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, + "@types/abstract-leveldown": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz", + "integrity": "sha512-q5veSX6zjUy/DlDhR4Y4cU0k2Ar+DT2LUraP00T19WLmTO6Se1djepCCaqU6nQrwcJ5Hyo/CWqxTzrrFg8eqbQ==" + }, "@types/babel__core": { - "version": "7.1.16", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", - "integrity": "sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ==", + "version": "7.1.19", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", + "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -1732,9 +13862,9 @@ } }, "@types/babel__generator": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", - "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", "dev": true, "requires": { "@babel/types": "^7.0.0" @@ -1751,9 +13881,9 @@ } }, "@types/babel__traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", - "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", + "version": "7.17.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.17.1.tgz", + "integrity": "sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA==", "dev": true, "requires": { "@babel/types": "^7.3.0" @@ -1769,9 +13899,9 @@ } }, "@types/google-protobuf": { - "version": "3.15.5", - "resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.15.5.tgz", - "integrity": "sha512-6bgv24B+A2bo9AfzReeg5StdiijKzwwnRflA8RLd1V4Yv995LeTmo0z69/MPbBDFSiZWdZHQygLo/ccXhMEDgw==", + "version": "3.15.6", + "resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.15.6.tgz", + "integrity": "sha512-pYVNNJ+winC4aek+lZp93sIKxnXt5qMkuKmaqS3WGuTq0Bw1ZDYNBgzG5kkdtwcv+GmYJGo3yEg6z2cKKAiEdw==", "dev": true }, "@types/graceful-fs": { @@ -1784,9 +13914,9 @@ } }, "@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", "dev": true }, "@types/istanbul-lib-report": { @@ -1808,27 +13938,32 @@ } }, "@types/jest": { - "version": "26.0.24", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.24.tgz", - "integrity": "sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==", + "version": "27.5.2", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz", + "integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==", "dev": true, "requires": { - "jest-diff": "^26.0.0", - "pretty-format": "^26.0.0" + "jest-matcher-utils": "^27.0.0", + "pretty-format": "^27.0.0" } }, "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, "@types/nexpect": { "version": "0.4.31", "resolved": "https://registry.npmjs.org/@types/nexpect/-/nexpect-0.4.31.tgz", @@ -1839,35 +13974,29 @@ } }, "@types/node": { - "version": "14.17.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.27.tgz", - "integrity": "sha512-94+Ahf9IcaDuJTle/2b+wzvjmutxXAEXU6O81JHblYXUg2BDG+dnBy7VxIPHKAyEEDHzCMQydTJuWvrE+Aanzw==" + "version": "16.11.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.39.tgz", + "integrity": "sha512-K0MsdV42vPwm9L6UwhIxMAOmcvH/1OoVkZyCgEtVu4Wx7sElGloy/W7kMBNe/oJ7V/jW9BVt1F6RahH6e7tPXw==" }, "@types/node-forge": { - "version": "0.9.10", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-0.9.10.tgz", - "integrity": "sha512-+BbPlhZeYs/WETWftQi2LeRx9VviWSwawNo+Pid5qNrSZHb60loYjpph3OrbwXMMseadu9rE9NeK34r4BHT+QQ==", + "version": "0.10.10", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-0.10.10.tgz", + "integrity": "sha512-iixn5bedlE9fm/5mN7fPpXraXlxCVrnNWHZekys8c5fknridLVWGnNRqlaWpenwaijIuB3bNI0lEOm+JD6hZUA==", "dev": true, "requires": { "@types/node": "*" } }, - "@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", - "dev": true - }, "@types/pako": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.2.tgz", - "integrity": "sha512-8UJl2MjkqqS6ncpLZqRZ5LmGiFBkbYxocD4e4jmBqGvfRG1RS23gKsBQbdtV9O9GvRyjFTiRHRByjSlKCLlmZw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.4.tgz", + "integrity": "sha512-Z+5bJSm28EXBSUJEgx29ioWeEEHUh6TiMkZHDhLwjc9wVFH+ressbkmX6waUZc5R3Gobn4Qu5llGxaoflZ+yhA==", "dev": true }, "@types/prettier": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.1.tgz", - "integrity": "sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.3.tgz", + "integrity": "sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg==", "dev": true }, "@types/prompts": { @@ -1880,9 +14009,9 @@ } }, "@types/readable-stream": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.11.tgz", - "integrity": "sha512-0z+/apYJwKFz/RHp6mOMxz/y7xOvWPYPevuCEyAY3gXsjtaac02E26RvxA+I96rfvmVH/dEMGXNvyJfViR1FSQ==", + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.13.tgz", + "integrity": "sha512-4JSCx8EUzaW9Idevt+9lsRAt1lcSccoQfE+AouM1gk8sFxnnytKNIO3wTl9Dy+4m6jRJ1yXhboLHHT/LXBQiEw==", "dev": true, "requires": { "@types/node": "*", @@ -1896,46 +14025,47 @@ "dev": true }, "@types/uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", "dev": true }, "@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", "dev": true, "requires": { "@types/yargs-parser": "*" } }, "@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.4.0.tgz", - "integrity": "sha512-9/yPSBlwzsetCsGEn9j24D8vGQgJkOTr4oMLas/w886ZtzKIs1iyoqFrwsX2fqYEeUwsdBpC21gcjRGo57u0eg==", + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.28.0.tgz", + "integrity": "sha512-DXVU6Cg29H2M6EybqSg2A+x8DgO9TCUBRp4QEXQHJceLS7ogVDP0g3Lkg/SZCqcvkAP/RruuQqK0gdlkgmhSUA==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "5.4.0", - "@typescript-eslint/scope-manager": "5.4.0", - "debug": "^4.3.2", + "@typescript-eslint/scope-manager": "5.28.0", + "@typescript-eslint/type-utils": "5.28.0", + "@typescript-eslint/utils": "5.28.0", + "debug": "^4.3.4", "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", + "ignore": "^5.2.0", "regexpp": "^3.2.0", - "semver": "^7.3.5", + "semver": "^7.3.7", "tsutils": "^3.21.0" }, "dependencies": { "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -1943,67 +14073,64 @@ } } }, - "@typescript-eslint/experimental-utils": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.4.0.tgz", - "integrity": "sha512-Nz2JDIQUdmIGd6p33A+naQmwfkU5KVTLb/5lTk+tLVTDacZKoGQisj8UCxk7onJcrgjIvr8xWqkYI+DbI3TfXg==", + "@typescript-eslint/parser": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.28.0.tgz", + "integrity": "sha512-ekqoNRNK1lAcKhZESN/PdpVsWbP9jtiNqzFWkp/yAUdZvJalw2heCYuqRmM5eUJSIYEkgq5sGOjq+ZqsLMjtRA==", "dev": true, "requires": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.4.0", - "@typescript-eslint/types": "5.4.0", - "@typescript-eslint/typescript-estree": "5.4.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" + "@typescript-eslint/scope-manager": "5.28.0", + "@typescript-eslint/types": "5.28.0", + "@typescript-eslint/typescript-estree": "5.28.0", + "debug": "^4.3.4" } }, - "@typescript-eslint/parser": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.4.0.tgz", - "integrity": "sha512-JoB41EmxiYpaEsRwpZEYAJ9XQURPFer8hpkIW9GiaspVLX8oqbqNM8P4EP8HOZg96yaALiLEVWllA2E8vwsIKw==", + "@typescript-eslint/scope-manager": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.28.0.tgz", + "integrity": "sha512-LeBLTqF/he1Z+boRhSqnso6YrzcKMTQ8bO/YKEe+6+O/JGof9M0g3IJlIsqfrK/6K03MlFIlycbf1uQR1IjE+w==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.4.0", - "@typescript-eslint/types": "5.4.0", - "@typescript-eslint/typescript-estree": "5.4.0", - "debug": "^4.3.2" + "@typescript-eslint/types": "5.28.0", + "@typescript-eslint/visitor-keys": "5.28.0" } }, - "@typescript-eslint/scope-manager": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.4.0.tgz", - "integrity": "sha512-pRxFjYwoi8R+n+sibjgF9iUiAELU9ihPBtHzocyW8v8D8G8KeQvXTsW7+CBYIyTYsmhtNk50QPGLE3vrvhM5KA==", + "@typescript-eslint/type-utils": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.28.0.tgz", + "integrity": "sha512-SyKjKh4CXPglueyC6ceAFytjYWMoPHMswPQae236zqe1YbhvCVQyIawesYywGiu98L9DwrxsBN69vGIVxJ4mQQ==", "dev": true, "requires": { - "@typescript-eslint/types": "5.4.0", - "@typescript-eslint/visitor-keys": "5.4.0" + "@typescript-eslint/utils": "5.28.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.4.0.tgz", - "integrity": "sha512-GjXNpmn+n1LvnttarX+sPD6+S7giO+9LxDIGlRl4wK3a7qMWALOHYuVSZpPTfEIklYjaWuMtfKdeByx0AcaThA==", + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.28.0.tgz", + "integrity": "sha512-2OOm8ZTOQxqkPbf+DAo8oc16sDlVR5owgJfKheBkxBKg1vAfw2JsSofH9+16VPlN9PWtv8Wzhklkqw3k/zCVxA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.4.0.tgz", - "integrity": "sha512-nhlNoBdhKuwiLMx6GrybPT3SFILm5Gij2YBdPEPFlYNFAXUJWX6QRgvi/lwVoadaQEFsizohs6aFRMqsXI2ewA==", + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.28.0.tgz", + "integrity": "sha512-9GX+GfpV+F4hdTtYc6OV9ZkyYilGXPmQpm6AThInpBmKJEyRSIjORJd1G9+bknb7OTFYL+Vd4FBJAO6T78OVqA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.4.0", - "@typescript-eslint/visitor-keys": "5.4.0", - "debug": "^4.3.2", - "globby": "^11.0.4", + "@typescript-eslint/types": "5.28.0", + "@typescript-eslint/visitor-keys": "5.28.0", + "debug": "^4.3.4", + "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.5", + "semver": "^7.3.7", "tsutils": "^3.21.0" }, "dependencies": { "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -2011,28 +14138,34 @@ } } }, + "@typescript-eslint/utils": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.28.0.tgz", + "integrity": "sha512-E60N5L0fjv7iPJV3UGc4EC+A3Lcj4jle9zzR0gW7vXhflO7/J29kwiTGITA2RlrmPokKiZbBy2DgaclCaEUs6g==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.28.0", + "@typescript-eslint/types": "5.28.0", + "@typescript-eslint/typescript-estree": "5.28.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + } + }, "@typescript-eslint/visitor-keys": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.4.0.tgz", - "integrity": "sha512-PVbax7MeE7tdLfW5SA0fs8NGVVr+buMPrcj+CWYWPXsZCH8qZ1THufDzbXm1xrZ2b2PA1iENJ0sRq5fuUtvsJg==", + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.28.0.tgz", + "integrity": "sha512-BtfP1vCor8cWacovzzPFOoeW4kBQxzmhxGoOpt0v1SFvG+nJ0cWaVdJk7cky1ArTcFHHKNIxyo2LLr3oNkSuXA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.4.0", - "eslint-visitor-keys": "^3.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", - "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==", - "dev": true - } + "@typescript-eslint/types": "5.28.0", + "eslint-visitor-keys": "^3.3.0" } }, "abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, "abstract-leveldown": { @@ -2049,9 +14182,9 @@ } }, "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", "dev": true }, "acorn-globals": { @@ -2062,13 +14195,22 @@ "requires": { "acorn": "^7.1.1", "acorn-walk": "^7.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } } }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "acorn-walk": { "version": "7.2.0", @@ -2096,12 +14238,6 @@ "uri-js": "^4.2.2" } }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, "ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -2109,29 +14245,20 @@ "dev": true, "requires": { "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - } } }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^2.0.1" + "color-convert": "^1.9.0" } }, "anymatch": { @@ -2207,33 +14334,15 @@ "sprintf-js": "~1.0.2" } }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, "array-includes": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", - "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", "get-intrinsic": "^1.1.1", "is-string": "^1.0.7" } @@ -2244,39 +14353,34 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, "array.prototype.flat": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", - "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", + "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.19.0" + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" } }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true + "array.prototype.reduce": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz", + "integrity": "sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + } }, "async-lock": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.3.0.tgz", - "integrity": "sha512-8A7SkiisnEgME2zEedtDYPxUPzdv3x//E7n5IFktPAtMYSEAV7eNJF0rMwrVyUFj6d/8rgajLantbjcNRQYXIg==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.3.1.tgz", + "integrity": "sha512-zK7xap9UnttfbE23JmcrNIyueAn6jWshihJqA33U/hEnKprF/lVGBDsBv/bqLm2YMMl1DnpHhUY044eA0t1TUw==" }, "async-mutex": { "version": "0.3.2", @@ -2289,7 +14393,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, "at-least-node": { @@ -2298,28 +14402,31 @@ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, "babel-jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", - "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", + "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", "dev": true, "requires": { - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/babel__core": "^7.1.7", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^26.6.2", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^27.5.1", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2330,6 +14437,21 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2370,9 +14492,9 @@ } }, "babel-plugin-jest-hoist": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", - "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", + "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", "dev": true, "requires": { "@babel/template": "^7.3.3", @@ -2382,33 +14504,33 @@ } }, "babel-plugin-polyfill-corejs2": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.2.tgz", - "integrity": "sha512-kISrENsJ0z5dNPq5eRvcctITNHYXWOA4DUZRFYCz3jYCcvTb/A546LIddmoGNMVYg2U38OyFeNosQwI9ENTqIQ==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", + "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", "dev": true, "requires": { "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.2.2", + "@babel/helper-define-polyfill-provider": "^0.3.1", "semver": "^6.1.1" } }, "babel-plugin-polyfill-corejs3": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.5.tgz", - "integrity": "sha512-ninF5MQNwAX9Z7c9ED+H2pGt1mXdP4TqzlHKyPIYmJIYz0N+++uwdM7RnJukklhzJ54Q84vA4ZJkgs7lu5vqcw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", + "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.2.2", - "core-js-compat": "^3.16.2" + "@babel/helper-define-polyfill-provider": "^0.3.1", + "core-js-compat": "^3.21.0" } }, "babel-plugin-polyfill-regenerator": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.2.tgz", - "integrity": "sha512-Goy5ghsc21HgPDFtzRkSirpZVW35meGoTmTOb2bxqdl60ghub4xOidgNTHaZfQ2FaxQsKmwvXtOAkcIS4SMBWg==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", + "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.2.2" + "@babel/helper-define-polyfill-provider": "^0.3.1" } }, "babel-preset-current-node-syntax": { @@ -2432,22 +14554,29 @@ } }, "babel-preset-jest": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", - "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", + "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", "dev": true, "requires": { - "babel-plugin-jest-hoist": "^26.6.2", + "babel-plugin-jest-hoist": "^27.5.1", "babel-preset-current-node-syntax": "^1.0.0" } }, "babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", "requires": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } } }, "balanced-match": { @@ -2456,71 +14585,11 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, - "big-integer": { - "version": "1.6.50", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.50.tgz", - "integrity": "sha512-+O2uoQWFRo8ysZNo/rjtri2jIwjr3XfeAgRjAUADRqGG+ZITvyn8J1kvXLTaKVr3hhGXk+f23tKfdzmklVM9vQ==" - }, "bip39": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.4.tgz", @@ -2570,7 +14639,7 @@ "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, "brace-expansion": { "version": "1.1.11", @@ -2598,15 +14667,15 @@ "dev": true }, "browserslist": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.4.tgz", - "integrity": "sha512-Zg7RpbZpIJRW3am9Lyckue7PLytvVxxhJj1CaJVlCWENsGEAOlnlt8X0ZxGRPp7Bt9o8tIRM5SEXy4BCPMJjLQ==", + "version": "4.20.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.4.tgz", + "integrity": "sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001265", - "electron-to-chromium": "^1.3.867", + "caniuse-lite": "^1.0.30001349", + "electron-to-chromium": "^1.4.147", "escalade": "^3.1.1", - "node-releases": "^2.0.0", + "node-releases": "^2.0.5", "picocolors": "^1.0.0" } }, @@ -2643,23 +14712,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -2681,32 +14733,20 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001269", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001269.tgz", - "integrity": "sha512-UOy8okEVs48MyHYgV+RdW1Oiudl1H6KolybD6ZquD0VcrPSgj25omXO1S7rDydjpqaISCwA8Pyx+jUQKZwWO5w==", + "version": "1.0.30001352", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz", + "integrity": "sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA==", "dev": true }, "canonicalize": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-1.0.5.tgz", - "integrity": "sha512-mAjKJPIyP0xqqv6IAkvso07StOmz6cmGtNDg3pXCSzXVZOqka7StIkAhJl/zHOi4M2CgpYfD6aeRWbnrmtvBEA==" - }, - "capture-exit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", - "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", - "dev": true, - "requires": { - "rsvp": "^4.8.4" - } + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-1.0.8.tgz", + "integrity": "sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==" }, "catering": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.0.tgz", - "integrity": "sha512-M5imwzQn6y+ODBfgi+cfgZv2hIUI6oYU/0f35Mdb1ujGeqeoI5tOnl9Q13DTH7LW+7er+NYq8stNOKZD/Z3U/A==", - "requires": { - "queue-tick": "^1.0.0" - } + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", + "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==" }, "chalk": { "version": "2.4.2", @@ -2717,32 +14757,6 @@ "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - } } }, "char-regex": { @@ -2754,33 +14768,35 @@ "check-more-types": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", - "integrity": "sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=", + "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", "dev": true }, "cheerio": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", - "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==", + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.11.tgz", + "integrity": "sha512-bQwNaDIBKID5ts/DsdhxrjqFXYfLw4ste+wMKqWA8DyKcS4qwsPP4Bk8ZNaTJjvpiX/qW3BT4sU7d6Bh5i+dag==", "requires": { - "cheerio-select": "^1.5.0", - "dom-serializer": "^1.3.2", - "domhandler": "^4.2.0", - "htmlparser2": "^6.1.0", - "parse5": "^6.0.1", - "parse5-htmlparser2-tree-adapter": "^6.0.1", - "tslib": "^2.2.0" + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "tslib": "^2.4.0" } }, "cheerio-select": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.5.0.tgz", - "integrity": "sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", "requires": { - "css-select": "^4.1.3", - "css-what": "^5.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0", - "domutils": "^2.7.0" + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" } }, "chownr": { @@ -2790,9 +14806,9 @@ "dev": true }, "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.1.tgz", + "integrity": "sha512-SXgeMX9VwDe7iFFaEWkA5AstuER9YKqy4EhHqr4DVqkwmD9rpVimkMKWHdjn30Ja45txyjhSn63lVX69eVCckg==", "dev": true }, "cipher-base": { @@ -2805,34 +14821,11 @@ } }, "cjs-module-lexer": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", - "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", "dev": true }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, "clean-git-ref": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz", @@ -2842,23 +14835,39 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } } }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", "dev": true }, "collect-v8-coverage": { @@ -2867,29 +14876,19 @@ "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", "dev": true }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { - "color-name": "~1.1.4" + "color-name": "1.1.3" } }, "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, "combined-stream": { @@ -2906,22 +14905,16 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true }, "convert-source-map": { @@ -2941,24 +14934,18 @@ } } }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, "core-js": { "version": "2.6.12", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" }, "core-js-compat": { - "version": "3.18.3", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.18.3.tgz", - "integrity": "sha512-4zP6/y0a2RTHN5bRGT7PTq9lVt3WzvffTNjqnTKsXhkAYNDTkdCLOIfAdOLcQ/7TDdyRj3c+NeHe1NmF1eDScw==", + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.23.0.tgz", + "integrity": "sha512-i4FgbtahOArZBEteiL+czI5N/bp17w16bXmLagGThdA2zuX1a5X4HbBmOVD7ERRtk3wMtPOFEmlXpVV4lsvwNw==", "dev": true, "requires": { - "browserslist": "^4.17.3", + "browserslist": "^4.20.4", "semver": "7.0.0" }, "dependencies": { @@ -2977,13 +14964,9 @@ "dev": true }, "crc-32": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", - "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", - "requires": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.1.0" - } + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==" }, "create-hash": { "version": "1.2.0", @@ -3017,11 +15000,11 @@ "dev": true }, "cross-fetch": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz", - "integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", "requires": { - "node-fetch": "2.6.1" + "node-fetch": "2.6.7" } }, "cross-spawn": { @@ -3035,21 +15018,21 @@ } }, "css-select": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", - "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", "requires": { "boolbase": "^1.0.0", - "css-what": "^5.0.0", - "domhandler": "^4.2.0", - "domutils": "^2.6.0", - "nth-check": "^2.0.0" + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" } }, "css-what": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz", - "integrity": "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" }, "cssom": { "version": "0.4.4", @@ -3086,39 +15069,33 @@ } }, "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { "ms": "2.1.2" } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, "decimal.js": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", "dev": true }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, "decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "requires": { - "mimic-response": "^2.0.0" + "mimic-response": "^3.1.0" } }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -3143,79 +15120,34 @@ "integrity": "sha512-QKN8NtuS3BC6m0B8vAnBls44tX1WXAFATUsJlruyAYbZpysWV3siH6o/i3g9DCHauzodksO60bdj5NazNbjCmg==", "requires": { "abstract-leveldown": "^7.2.0", - "inherits": "^2.0.3" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } + "inherits": "^2.0.3" } }, - "defined": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", - "integrity": "sha1-817qfXBekzuvE7LwOz+D2SFAOz4=" + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "dev": true }, "detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", "dev": true }, "detect-newline": { @@ -3231,15 +15163,15 @@ "dev": true }, "diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", "dev": true }, "diff3": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz", - "integrity": "sha1-1OXDpM305f4SEatC5pP8tDIVgPw=" + "integrity": "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g==" }, "dir-glob": { "version": "3.0.1", @@ -3260,19 +15192,19 @@ } }, "dom-serializer": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", - "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" } }, "domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" }, "domexception": { "version": "2.0.1", @@ -3292,40 +15224,39 @@ } }, "domhandler": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz", - "integrity": "sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "requires": { - "domelementtype": "^2.2.0" + "domelementtype": "^2.3.0" } }, "domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" } }, "electron-to-chromium": { - "version": "1.3.871", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.871.tgz", - "integrity": "sha512-qcLvDUPf8DSIMWarHT2ptgcqrYg62n3vPA7vhrOF24d8UNzbUBaHu2CySiENR3nEDzYgaN60071t0F6KLYMQ7Q==", + "version": "1.4.154", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.154.tgz", + "integrity": "sha512-GbV9djOkrnj6xmW+YYVVEI3VCQnJ0pnSTu7TW2JyjKd5cakoiSaG5R4RbEtfaD92GsY10DzbU3GYRe+IOA9kqA==", "dev": true }, "emittery": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", - "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", + "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", "dev": true }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "encoding-down": { "version": "7.1.0", @@ -3339,23 +15270,31 @@ } }, "encryptedfs": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/encryptedfs/-/encryptedfs-3.4.3.tgz", - "integrity": "sha512-OQqsGw3eNrMdFpiYRX17nMq1NKKebaA0KXyM9IRY9aPOxpaeOwcdvWnOcvvO9wCxZFNxgy/A2SOZdxnhCe3paA==", - "requires": { - "@matrixai/async-init": "^1.6.0", - "@matrixai/db": "^1.1.5", - "@matrixai/logger": "^2.1.0", - "@matrixai/workers": "^1.2.5", - "async-mutex": "^0.3.2", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/encryptedfs/-/encryptedfs-3.5.3.tgz", + "integrity": "sha512-2cTz6/8lUF2WFv6YNA9RwSASBh6bHIJqCbOWFr1RCo/vEHeR1+OKK0F+Xu4ujBlLsz3/a6NwT6/UoHl8Zn5rCg==", + "requires": { + "@matrixai/async-init": "^1.7.3", + "@matrixai/async-locks": "^2.2.4", + "@matrixai/db": "^4.0.2", + "@matrixai/errors": "^1.1.1", + "@matrixai/logger": "^2.1.1", + "@matrixai/resources": "^1.1.3", + "@matrixai/workers": "^1.3.3", "errno": "^0.1.7", "lexicographic-integer": "^1.1.0", - "node-forge": "^0.10.0", + "node-forge": "^1.3.1", "readable-stream": "^3.6.0", "resource-counter": "^1.2.4", "threads": "^1.6.5", - "ts-custom-error": "^3.2.0", "util-callbackify": "^1.0.0" + }, + "dependencies": { + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" + } } }, "end-of-stream": { @@ -3367,19 +15306,10 @@ "once": "^1.4.0" } }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, "entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.3.0.tgz", + "integrity": "sha512-/iP1rZrSEJ0DTlPiX+jbzlA3eVkY/e8L8SozroF395fIqE3TYF/Nz7YOMAawta+vLmyJ/hkGNNPcSbMADCCXbg==" }, "errno": { "version": "0.1.8", @@ -3399,30 +15329,47 @@ } }, "es-abstract": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", - "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", + "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", "requires": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", "get-intrinsic": "^1.1.1", "get-symbol-description": "^1.0.0", "has": "^1.0.3", - "has-symbols": "^1.0.2", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", + "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", + "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-weakref": "^1.0.1", - "object-inspect": "^1.11.0", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", "object-keys": "^1.1.1", "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + } + }, + "es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" + }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" } }, "es-to-primitive": { @@ -3438,13 +15385,12 @@ "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true }, "escodegen": { @@ -3461,15 +15407,15 @@ }, "dependencies": { "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", "dev": true, "requires": { "prelude-ls": "~1.1.2", @@ -3493,20 +15439,13 @@ "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", "dev": true }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", "dev": true, "requires": { "prelude-ls": "~1.1.2" @@ -3515,62 +15454,48 @@ } }, "eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.17.0.tgz", + "integrity": "sha512-gq0m0BTJfci60Fz4nczYxNAlED+sMcihltndR8t9t1evnU/azx53x3t2UHXC/uRjcbvRw/XctpaNygSTcQD+Iw==", "dev": true, "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", - "debug": "^4.0.1", + "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.2", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", - "table": "^6.0.9", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3583,6 +15508,21 @@ "uri-js": "^4.2.2" } }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3593,33 +15533,47 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", "dev": true, "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" } }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, "globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -3631,11 +15585,14 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } }, "json-schema-traverse": { "version": "0.4.1", @@ -3643,15 +15600,6 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3660,14 +15608,21 @@ "requires": { "has-flag": "^4.0.0" } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true } } }, "eslint-config-prettier": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz", - "integrity": "sha512-rV4Qu0C3nfJKPOAhFujFxB7RMP+URFyQqqOZW9DMRD7ZDTFyjaIlETU3xzHELt++4ugC0+Jm084HQYkkJe+Ivg==", - "dev": true + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "requires": {} }, "eslint-import-resolver-node": { "version": "0.3.6", @@ -3691,14 +15646,13 @@ } }, "eslint-module-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz", - "integrity": "sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", + "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", "dev": true, "requires": { "debug": "^3.2.7", - "find-up": "^2.1.0", - "pkg-dir": "^2.0.0" + "find-up": "^2.1.0" }, "dependencies": { "debug": { @@ -3713,7 +15667,7 @@ "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", "dev": true, "requires": { "locate-path": "^2.0.0" @@ -3722,7 +15676,7 @@ "locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", "dev": true, "requires": { "p-locate": "^2.0.0", @@ -3741,7 +15695,7 @@ "p-locate": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", "dev": true, "requires": { "p-limit": "^1.1.0" @@ -3750,30 +15704,21 @@ "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", "dev": true }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", "dev": true - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } } } }, "eslint-plugin-import": { - "version": "2.25.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz", - "integrity": "sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg==", + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", "dev": true, "requires": { "array-includes": "^3.1.4", @@ -3781,14 +15726,14 @@ "debug": "^2.6.9", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.1", + "eslint-module-utils": "^2.7.3", "has": "^1.0.3", - "is-core-module": "^2.8.0", + "is-core-module": "^2.8.1", "is-glob": "^4.0.3", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "object.values": "^1.1.5", - "resolve": "^1.20.0", - "tsconfig-paths": "^3.11.0" + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" }, "dependencies": { "debug": { @@ -3812,15 +15757,15 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true } } }, "eslint-plugin-prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", - "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", + "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==", "dev": true, "requires": { "prettier-linter-helpers": "^1.0.0" @@ -3843,12 +15788,20 @@ "dev": true, "requires": { "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } } }, "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "dev": true }, "esm": { @@ -3858,22 +15811,14 @@ "optional": true }, "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", "dev": true, "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" } }, "esprima": { @@ -3892,9 +15837,9 @@ }, "dependencies": { "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } @@ -3909,9 +15854,9 @@ }, "dependencies": { "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } @@ -3928,139 +15873,29 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, - "exec-sh": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", - "integrity": "sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==", - "dev": true - }, "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" } }, "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true }, - "exit-on-epipe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", - "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, "expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -4068,103 +15903,15 @@ "dev": true }, "expect": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", - "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-styles": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0" - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", + "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", "dev": true, "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } + "@jest/types": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1" } }, "fast-deep-equal": { @@ -4179,17 +15926,17 @@ "dev": true }, "fast-fuzzy": { - "version": "1.10.10", - "resolved": "https://registry.npmjs.org/fast-fuzzy/-/fast-fuzzy-1.10.10.tgz", - "integrity": "sha512-TkXYYQcLyZ5tbDpg3kj5gq7PNl6vQQQEW99/sBpmYYRPcuCaZElm3FpoOOqwL51+1prhjrzsnGAjWgNCG7iVOA==", + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/fast-fuzzy/-/fast-fuzzy-1.11.2.tgz", + "integrity": "sha512-H1ct10Pzx+pSO4h7F1uBXET91ay2hy67J1aQZFKL23EXsOoanpwjPNQQoc+NhClKJMmlGGN+0bXhIdFJX70BJw==", "requires": { "graphemesplit": "^2.4.1" } }, "fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -4197,6 +15944,17 @@ "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } } }, "fast-json-stable-stringify": { @@ -4208,7 +15966,7 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "fastq": { @@ -4236,13 +15994,6 @@ "requires": { "napi-macros": "^2.0.0", "node-gyp-build": "^4.2.2" - }, - "dependencies": { - "node-gyp-build": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==" - } } }, "file-entry-cache": { @@ -4284,15 +16035,9 @@ } }, "flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "dev": true }, "form-data": { @@ -4306,19 +16051,10 @@ "mime-types": "^2.1.12" } }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", "dev": true, "requires": { "inherits": "^2.0.1", @@ -4373,20 +16109,12 @@ "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" - }, - "dependencies": { - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } } }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, "fsevents": { @@ -4401,16 +16129,32 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", "dev": true }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", "dev": true, "requires": { "aproba": "^1.0.3", @@ -4426,33 +16170,13 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", "dev": true }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", "dev": true, "requires": { "ansi-regex": "^2.0.0" @@ -4469,17 +16193,16 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", - "has-symbols": "^1.0.1" + "has-symbols": "^1.0.3" } }, "get-package-type": { @@ -4489,13 +16212,10 @@ "dev": true }, "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true }, "get-symbol-description": { "version": "1.0.0", @@ -4506,39 +16226,33 @@ "get-intrinsic": "^1.1.1" } }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, "github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", "dev": true }, "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "requires": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" } }, "globals": { @@ -4548,28 +16262,28 @@ "dev": true }, "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" } }, "google-protobuf": { - "version": "3.18.1", - "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.18.1.tgz", - "integrity": "sha512-cDqSamZ8rGs+pOzhIsBte7wpezUKg/sggeptDWN5odhnRY/eDLa5VWLeNeQvcfiqjS3yUwgM+6OePCJMB7aWZA==" + "version": "3.20.1", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.20.1.tgz", + "integrity": "sha512-XMf1+O32FjYIV3CYu6Tuh5PNbfNEU5Xu22X+Xkdb/DUexFlCzhvv7d5Iirm4AOwn8lv4al1YvIhzGrg2j9Zfzw==" }, "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, "graphemesplit": { @@ -4581,13 +16295,6 @@ "unicode-trie": "^2.0.0" } }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true, - "optional": true - }, "grpc_tools_node_protoc_ts": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/grpc_tools_node_protoc_ts/-/grpc_tools_node_protoc_ts-5.3.2.tgz", @@ -4617,14 +16324,6 @@ "source-map": "^0.6.1", "uglify-js": "^3.1.4", "wordwrap": "^1.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "has": { @@ -4636,20 +16335,28 @@ } }, "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "requires": { + "get-intrinsic": "^1.1.1" + } + }, "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "has-tostringtag": { "version": "1.0.0", @@ -4662,67 +16369,9 @@ "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "dev": true }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "hash-base": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", @@ -4733,12 +16382,6 @@ "safe-buffer": "^5.2.0" } }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, "html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -4755,14 +16398,14 @@ "dev": true }, "htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", + "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "entities": "^4.3.0" } }, "http-proxy-agent": { @@ -4777,9 +16420,9 @@ } }, "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "requires": { "agent-base": "6", @@ -4787,9 +16430,9 @@ } }, "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, "iconv-lite": { @@ -4807,14 +16450,9 @@ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" - }, - "immediate": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", - "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==" + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==" }, "import-fresh": { "version": "3.3.0", @@ -4835,9 +16473,9 @@ } }, "import-local": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz", - "integrity": "sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, "requires": { "pkg-dir": "^4.2.0", @@ -4847,13 +16485,13 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "requires": { "once": "^1.3.0", @@ -4881,6 +16519,12 @@ "side-channel": "^1.0.4" } }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, "into-stream": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", @@ -4892,43 +16536,14 @@ } }, "ip-num": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/ip-num/-/ip-num-1.3.3.tgz", - "integrity": "sha512-1QsiMKglDaemuIktincG1ntr3DvVTV/pU++eyG7vIm4xd+gvtJ9eoB34RRbI9YTqn1U5og16n7+1RgwLhv4RmA==", - "requires": { - "big-integer": "^1.6.48" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ip-num/-/ip-num-1.4.0.tgz", + "integrity": "sha512-MP+gq4uBvrvm+G7EwP14GcJeFK49/p6sZrNOarMUoExLRodULJQM8mnkb/SbT1YKxRsZfh8rgwei2pUJIa35jA==" }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, "is-bigint": { @@ -4958,50 +16573,15 @@ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==" }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, "is-core-module": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", - "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", "dev": true, "requires": { "has": "^1.0.3" } }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -5010,49 +16590,20 @@ "has-tostringtag": "^1.0.0" } }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "optional": true - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } }, "is-generator-fn": { "version": "2.1.0", @@ -5070,9 +16621,9 @@ } }, "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" }, "is-number": { "version": "7.0.0", @@ -5081,9 +16632,9 @@ "dev": true }, "is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "requires": { "has-tostringtag": "^1.0.0" } @@ -5093,15 +16644,6 @@ "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-2.1.0.tgz", "integrity": "sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw==" }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, "is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -5118,14 +16660,17 @@ } }, "is-shared-array-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", - "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "requires": { + "call-bind": "^1.0.2" + } }, "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, "is-string": { @@ -5147,54 +16692,32 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true }, "is-weakref": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", - "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", - "requires": { - "call-bind": "^1.0.0" - } - }, - "is-windows": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "optional": true, + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", "requires": { - "is-docker": "^2.0.0" + "call-bind": "^1.0.2" } }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "isomorphic-git": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.10.1.tgz", - "integrity": "sha512-abbPpKkykIVDJ92rtYoD4AOuT5/7PABHR2fDBrsm7H0r2ZT+MGpPL/FynrEJM6nTcFSieaIDxnHNGhfHO/v+bA==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.18.1.tgz", + "integrity": "sha512-SNYmHxzDMwmB0N5jNaac9xmUOYv9VpWqBoPVQFvPEp47dfREOfsWy32IDEM00jzrDLGn+GsaPL2j6RYIDaKtJw==", "requires": { "async-lock": "^1.1.0", "clean-git-ref": "^2.0.1", @@ -5206,25 +16729,25 @@ "pify": "^4.0.1", "readable-stream": "^3.4.0", "sha.js": "^2.4.9", - "simple-get": "^3.0.2" + "simple-get": "^4.0.1" } }, "istanbul-lib-coverage": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.1.0.tgz", - "integrity": "sha512-OFSPP1Csv3GxruycNA1iRJPnc5pon+N4Q89EUz8KYOFbdsqCoHRh0J8jwRdna5thveVcMTdgY27kUl/lZuAWdw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", "dev": true }, "istanbul-lib-instrument": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.0.4.tgz", - "integrity": "sha512-W6jJF9rLGEISGoCyXRqa/JCGQGmmxPO10TMu7izaUTynxvBvTjqzAIIGCK9USBmIbQAaSWD6XJPrM9Pv5INknw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", + "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", "dev": true, "requires": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, @@ -5265,20 +16788,12 @@ "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "istanbul-reports": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.5.tgz", - "integrity": "sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", + "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", "dev": true, "requires": { "html-escaper": "^2.0.0", @@ -5286,16 +16801,63 @@ } }, "jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", - "integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", + "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "dev": true, "requires": { - "@jest/core": "^26.6.3", + "@jest/core": "^27.5.1", "import-local": "^3.0.2", - "jest-cli": "^26.6.3" + "jest-cli": "^27.5.1" + } + }, + "jest-changed-files": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", + "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", + "dev": true, + "requires": { + "@jest/types": "^27.5.1", + "execa": "^5.0.0", + "throat": "^6.0.1" + } + }, + "jest-circus": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", + "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", + "dev": true, + "requires": { + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^27.5.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3", + "throat": "^6.0.1" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5306,181 +16868,150 @@ "supports-color": "^7.1.0" } }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "color-name": "~1.1.4" } }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-cli": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", - "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", - "dev": true, - "requires": { - "@jest/core": "^26.6.3", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "is-ci": "^2.0.0", - "jest-config": "^26.6.3", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "prompts": "^2.0.1", - "yargs": "^15.4.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "has-flag": "^4.0.0" } } } }, - "jest-changed-files": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", - "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", + "jest-cli": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", + "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "execa": "^4.0.0", - "throat": "^5.0.0" + "@jest/core": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "prompts": "^2.0.1", + "yargs": "^16.2.0" }, "dependencies": { - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" + "color-convert": "^2.0.1" } }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { - "pump": "^3.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "is-stream": { + "color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "path-key": "^3.0.0" + "has-flag": "^4.0.0" } } } }, "jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", + "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", "dev": true, "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", + "@babel/core": "^7.8.0", + "@jest/test-sequencer": "^27.5.1", + "@jest/types": "^27.5.1", + "babel-jest": "^27.5.1", "chalk": "^4.0.0", + "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" + "graceful-fs": "^4.2.9", + "jest-circus": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-jasmine2": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5491,6 +17022,21 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5509,17 +17055,26 @@ } }, "jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", "dev": true, "requires": { "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5530,6 +17085,21 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5548,27 +17118,36 @@ } }, "jest-docblock": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", - "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", + "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", "dev": true, "requires": { "detect-newline": "^3.0.0" } }, "jest-each": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", - "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", + "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", "dev": true, "requires": { - "@jest/types": "^26.6.2", + "@jest/types": "^27.5.1", "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2" + "jest-get-type": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5579,6 +17158,21 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5597,88 +17191,95 @@ } }, "jest-environment-jsdom": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", - "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", + "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", "dev": true, "requires": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2", - "jsdom": "^16.4.0" + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1", + "jsdom": "^16.6.0" } }, "jest-environment-node": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", - "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", + "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", "dev": true, "requires": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" } }, "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", "dev": true }, "jest-haste-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", - "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", + "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", "dev": true, "requires": { - "@jest/types": "^26.6.2", + "@jest/types": "^27.5.1", "@types/graceful-fs": "^4.1.2", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", - "fsevents": "^2.1.2", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^26.0.0", - "jest-serializer": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "micromatch": "^4.0.2", - "sane": "^4.0.3", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^27.5.1", + "jest-serializer": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "micromatch": "^4.0.4", "walker": "^1.0.7" } }, "jest-jasmine2": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", - "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", + "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", "dev": true, "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/environment": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "expect": "^26.6.2", + "expect": "^27.5.1", "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2", - "throat": "^5.0.0" + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", + "throat": "^6.0.1" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5689,6 +17290,21 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5706,28 +17322,48 @@ } } }, + "jest-junit": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-13.2.0.tgz", + "integrity": "sha512-B0XNlotl1rdsvFZkFfoa19mc634+rrd8E4Sskb92Bb8MmSXeWV9XJGUyctunZS1W410uAxcyYuPUGVnbcOH8cg==", + "requires": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" + } + }, "jest-leak-detector": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", - "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", + "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", "dev": true, "requires": { - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" } }, "jest-matcher-utils": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", - "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", "dev": true, "requires": { "chalk": "^4.0.0", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5738,6 +17374,21 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5756,22 +17407,31 @@ } }, "jest-message-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", - "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.6.2", + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", "slash": "^3.0.0", - "stack-utils": "^2.0.2" + "stack-utils": "^2.0.3" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5782,6 +17442,21 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5800,55 +17475,69 @@ } }, "jest-mock": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", - "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", + "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", "dev": true, "requires": { - "@jest/types": "^26.6.2", + "@jest/types": "^27.5.1", "@types/node": "*" } }, "jest-mock-process": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jest-mock-process/-/jest-mock-process-1.4.1.tgz", - "integrity": "sha512-ZZUKRlEBizutngoO4KngzN30YoeAYP3nnwimk4cpi9WqLxQUf6SlAPK5p1D9usEpxDS3Uif2MIez3Bq0vGYR+g==", - "dev": true + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/jest-mock-process/-/jest-mock-process-1.5.1.tgz", + "integrity": "sha512-CPu46KyUiVSxE+LkqBuscqGmy1bvW2vJQuNstt83iLtFaFjgrgmp6LY04IKuOhhlGhcrdi86Gqq5/fTE2wG6lg==", + "dev": true, + "requires": {} }, "jest-mock-props": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/jest-mock-props/-/jest-mock-props-1.9.0.tgz", - "integrity": "sha512-8IlIiZRvovnRuvqcvWZyDv4CyhrUGTbEW/1eKurHr2JY4VhIWQIPlbpt9lqL2nxdGnco+OcgpPBwGYCEeDb2+A==", - "dev": true + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/jest-mock-props/-/jest-mock-props-1.9.1.tgz", + "integrity": "sha512-PvTySOTw/K4dwL7XrVGq/VUZRm/qXPrV4+NuhgxuWkmE3h/Fd+g+qB0evK5vSBAkI8TaxvTXYv17IdxWdEze1g==", + "dev": true, + "requires": {} }, "jest-pnp-resolver": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true + "dev": true, + "requires": {} }, "jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", + "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", "dev": true }, "jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz", + "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", "dev": true, "requires": { - "@jest/types": "^26.6.2", + "@jest/types": "^27.5.1", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", "slash": "^3.0.0" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5859,6 +17548,21 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5877,44 +17581,54 @@ } }, "jest-resolve-dependencies": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", - "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", + "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.6.2" + "@jest/types": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-snapshot": "^27.5.1" } }, "jest-runner": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", - "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", + "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", "dev": true, "requires": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/console": "^27.5.1", + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", "chalk": "^4.0.0", - "emittery": "^0.7.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-docblock": "^26.0.0", - "jest-haste-map": "^26.6.2", - "jest-leak-detector": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", + "emittery": "^0.8.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-leak-detector": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", "source-map-support": "^0.5.6", - "throat": "^5.0.0" + "throat": "^6.0.1" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5925,6 +17639,21 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5943,40 +17672,44 @@ } }, "jest-runtime": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", - "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/globals": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/yargs": "^15.0.0", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", + "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", + "dev": true, + "requires": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/globals": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", "chalk": "^4.0.0", - "cjs-module-lexer": "^0.6.0", + "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", + "execa": "^5.0.0", "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^15.4.1" + "strip-bom": "^4.0.0" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5987,17 +17720,21 @@ "supports-color": "^7.1.0" } }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "color-name": "~1.1.4" } }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6012,89 +17749,58 @@ "requires": { "has-flag": "^4.0.0" } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, "jest-serializer": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", - "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", + "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", "dev": true, "requires": { "@types/node": "*", - "graceful-fs": "^4.2.4" + "graceful-fs": "^4.2.9" } }, "jest-snapshot": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", - "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz", + "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", "dev": true, "requires": { + "@babel/core": "^7.7.2", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", "@babel/types": "^7.0.0", - "@jest/types": "^26.6.2", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.0.0", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-haste-map": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", + "expect": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", "natural-compare": "^1.4.0", - "pretty-format": "^26.6.2", + "pretty-format": "^27.5.1", "semver": "^7.3.2" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6105,6 +17811,21 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6112,9 +17833,9 @@ "dev": true }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -6132,19 +17853,28 @@ } }, "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", "dev": true, "requires": { - "@jest/types": "^26.6.2", + "@jest/types": "^27.5.1", "@types/node": "*", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6155,6 +17885,21 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6173,23 +17918,32 @@ } }, "jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", + "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", + "@jest/types": "^27.5.1", + "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", + "jest-get-type": "^27.5.1", "leven": "^3.1.0", - "pretty-format": "^26.6.2" + "pretty-format": "^27.5.1" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true }, "chalk": { @@ -6202,6 +17956,21 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6220,20 +17989,29 @@ } }, "jest-watcher": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz", - "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", + "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", "dev": true, "requires": { - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "jest-util": "^26.6.2", + "jest-util": "^27.5.1", "string-length": "^4.0.1" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6244,6 +18022,21 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6262,14 +18055,14 @@ } }, "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" + "supports-color": "^8.0.0" }, "dependencies": { "has-flag": { @@ -6279,9 +18072,9 @@ "dev": true }, "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -6290,9 +18083,9 @@ } }, "jose": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.3.6.tgz", - "integrity": "sha512-A/JgZGUerqG2IMuxkUDBtZ4aTxg/l1Y+pt/QAAYiRAR3EFlxIE0Su0xdpB8tQcPZK5eudB7g1PHCZ5uHatbY+g==" + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.8.1.tgz", + "integrity": "sha512-+/hpTbRcCw9YC0TOfN1W47pej4a9lRmltdOVdRLz5FP5UvUq3CenhXjQK7u/8NdMIIShMXYAh9VLPhc7TjhvFw==" }, "js-base64": { "version": "3.7.2", @@ -6350,10 +18143,10 @@ "xml-name-validator": "^3.0.0" }, "dependencies": { - "acorn": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", - "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true } } @@ -6378,17 +18171,14 @@ "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true }, "jsonc-parser": { "version": "3.0.0", @@ -6404,22 +18194,8 @@ "requires": { "graceful-fs": "^4.1.6", "universalify": "^2.0.0" - }, - "dependencies": { - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } } }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -6428,7 +18204,7 @@ "lazy-ass": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", - "integrity": "sha1-eZllXoZGwX8In90YfRUNMyTVRRM=", + "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", "dev": true }, "level": { @@ -6483,14 +18259,6 @@ "run-parallel-limit": "^1.1.0" } }, - "level-option-wrap": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/level-option-wrap/-/level-option-wrap-1.1.0.tgz", - "integrity": "sha1-rSDmjZ88IsiJdTHMaqevWWse0Sk=", - "requires": { - "defined": "~0.0.0" - } - }, "level-packager": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-6.0.1.tgz", @@ -6506,20 +18274,13 @@ "integrity": "sha512-E486g1NCjW5cF78KGPrMDRBYzPuueMZ6VBXHT6gC7A8UYWGiM14fGgp+s/L1oFfDWSPV/+SFkYCmZ0SiESkRKA==" }, "leveldown": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-6.1.0.tgz", - "integrity": "sha512-8C7oJDT44JXxh04aSSsfcMI8YiaGRhOFI9/pMEL7nWJLVsWajDPTRxsSHTM2WcTVY5nXM+SuRHzPPi0GbnDX+w==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-6.1.1.tgz", + "integrity": "sha512-88c+E+Eizn4CkQOBHwqlCJaTNEjGpaEIikn1S+cINc5E9HEvJ77bqY4JY/HxT5u0caWqsc3P3DcFIKBI1vHt+A==", "requires": { "abstract-leveldown": "^7.2.0", "napi-macros": "~2.0.0", "node-gyp-build": "^4.3.0" - }, - "dependencies": { - "node-gyp-build": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==" - } } }, "levelup": { @@ -6554,12 +18315,12 @@ "lexicographic-integer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/lexicographic-integer/-/lexicographic-integer-1.1.0.tgz", - "integrity": "sha1-UsptmYpXLmMitRX1uA45bGBD6bg=" + "integrity": "sha512-MQCrf1gG31DJSNQDiIfgk7CQVlXkO6xC+DFGExs5WQWlxWSSAroH5k/UrKrS6LThHDHBoc3X1pNoYHDKOCPWRQ==" }, "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, "locate-path": { @@ -6577,16 +18338,21 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, "lodash.merge": { @@ -6595,11 +18361,10 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "lru-cache": { "version": "6.0.0", @@ -6613,7 +18378,7 @@ "ltgt": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", - "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=" + "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==" }, "lunr": { "version": "2.3.9", @@ -6637,33 +18402,18 @@ "dev": true }, "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "requires": { - "tmpl": "1.0.x" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, "requires": { - "object-visit": "^1.0.0" + "tmpl": "1.0.5" } }, "marked": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/marked/-/marked-3.0.7.tgz", - "integrity": "sha512-ctKqbnLuNbsHbI26cfMyOlKgXGfl1orOv1AvWWDX7AkgfMOwCWvmuYc+mVLeWhQ9W6hdWVBynOs96VkcscKo0Q==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.17.tgz", + "integrity": "sha512-Wfk0ATOK5iPxM4ptrORkFemqroz0ZDxp5MWfYA7H/F+wO17NRWV5Ypxi6p3g2Xmw2bKeiYOl6oVnLHKxBA0VhA==", "dev": true }, "md5.js": { @@ -6689,28 +18439,28 @@ "dev": true }, "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" } }, "mime-db": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", - "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true }, "mime-types": { - "version": "2.1.33", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", - "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "requires": { - "mime-db": "1.50.0" + "mime-db": "1.52.0" } }, "mimic-fn": { @@ -6720,23 +18470,23 @@ "dev": true }, "mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "minimisted": { "version": "2.0.1", @@ -6746,32 +18496,10 @@ "minimist": "^1.2.5" } }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, "mkdirp-classic": { "version": "0.5.3", @@ -6789,6 +18517,17 @@ "debug": "4.3.2", "lazy-ass": "1.6.0", "ramda": "0.27.1" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } } }, "ms": { @@ -6797,9 +18536,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "multiformats": { - "version": "9.4.9", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.4.9.tgz", - "integrity": "sha512-zA84TTJcRfRMpjvYqy63piBbSEdqlIGqNNSpP6kspqtougqjo60PRhIFo+oAxrjkof14WMCImvr7acK6rPpXLw==" + "version": "9.6.5", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.6.5.tgz", + "integrity": "sha512-vMwf/FUO+qAPvl3vlSZEgEVFY/AxeZq5yg761ScF3CZsXgmTi/HGkicUiNN0CI4PW8FiY2P0OLklOcmQjdQJhw==" }, "multistream": { "version": "4.1.0", @@ -6811,25 +18550,6 @@ "readable-stream": "^3.6.0" } }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, "napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", @@ -6844,7 +18564,7 @@ "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, "neo-async": { @@ -6878,7 +18598,7 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", "dev": true }, "semver": { @@ -6890,7 +18610,7 @@ "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "dev": true, "requires": { "shebang-regex": "^1.0.0" @@ -6899,7 +18619,7 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", "dev": true }, "which": { @@ -6937,9 +18657,33 @@ } }, "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } }, "node-forge": { "version": "0.10.0", @@ -6947,82 +18691,22 @@ "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" }, "node-gyp-build": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", - "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", - "dev": true + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", + "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==" }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true }, - "node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true - }, - "node-notifier": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz", - "integrity": "sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg==", - "dev": true, - "optional": true, - "requires": { - "growly": "^1.3.0", - "is-wsl": "^2.2.0", - "semver": "^7.3.2", - "shellwords": "^0.1.1", - "uuid": "^8.3.0", - "which": "^2.0.2" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "optional": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, "node-releases": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.0.tgz", - "integrity": "sha512-aA87l0flFYMzCHpTM3DERFSYxc6lv/BltdbRTOMZuxZ0cwZCD3mejE5n9vLhSJCN++/eOqr77G1IO5uXxlQYWA==", - "dev": true - }, - "noop-logger": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", - "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", + "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==", "dev": true }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -7030,20 +18714,12 @@ "dev": true }, "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { - "path-key": "^2.0.0" - }, - "dependencies": { - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - } + "path-key": "^3.0.0" } }, "npmlog": { @@ -7059,9 +18735,9 @@ } }, "nth-check": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", - "integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "requires": { "boolbase": "^1.0.0" } @@ -7069,7 +18745,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", "dev": true }, "nwsapi": { @@ -7081,65 +18757,19 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==" + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, "object.assign": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", @@ -7152,22 +18782,14 @@ } }, "object.getownpropertydescriptors": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz", - "integrity": "sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz", + "integrity": "sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==", "requires": { + "array.prototype.reduce": "^1.0.4", "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.1" } }, "object.values": { @@ -7189,7 +18811,7 @@ "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "requires": { "wrappy": "1" } @@ -7203,32 +18825,6 @@ "mimic-fn": "^2.1.0" } }, - "onigasm": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/onigasm/-/onigasm-2.2.5.tgz", - "integrity": "sha512-F+th54mPc0l1lp1ZcFMyL/jTs2Tlq4SqIHKIXGZOR/VkHkF9A7Fr5rRr5+ZG/lWeRsyrClLYRq7s/yFQ/XhWCA==", - "dev": true, - "requires": { - "lru-cache": "^5.1.1" - }, - "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -7243,18 +18839,6 @@ "word-wrap": "^1.2.3" } }, - "p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, "p-is-promise": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", @@ -7312,24 +18896,22 @@ } }, "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz", + "integrity": "sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==", + "requires": { + "entities": "^4.3.0" + } }, "parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", "requires": { - "parse5": "^6.0.1" + "domhandler": "^5.0.2", + "parse5": "^7.0.0" } }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7339,7 +18921,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true }, "path-key": { @@ -7378,9 +18960,9 @@ "dev": true }, "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, "pify": { @@ -7389,54 +18971,59 @@ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" }, "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, - "requires": { - "node-modules-regexp": "^1.0.0" - } + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true }, "pkg": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/pkg/-/pkg-5.3.0.tgz", - "integrity": "sha512-/DGG+QcSPraMAIxaoGCNqb2A6Xkm2jBQMsj2mjb4ag236ByTY9Xhpikvj5ixwlSQV0euuJw4fphKCd5YHRPS8w==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pkg/-/pkg-5.6.0.tgz", + "integrity": "sha512-mHrAVSQWmHA41RnUmRpC7pK9lNnMfdA16CF3cqOI22a8LZxOQzF7M8YWtA2nfs+d7I0MTDXOtkDsAsFXeCpYjg==", "dev": true, "requires": { - "@babel/parser": "7.13.13", - "@babel/types": "7.13.12", - "chalk": "^4.1.0", + "@babel/parser": "7.16.2", + "@babel/types": "7.16.0", + "chalk": "^4.1.2", "escodegen": "^2.0.0", "fs-extra": "^9.1.0", - "globby": "^11.0.3", + "globby": "^11.0.4", "into-stream": "^6.0.0", "minimist": "^1.2.5", "multistream": "^4.1.0", - "pkg-fetch": "3.1.1", - "prebuild-install": "6.0.1", + "pkg-fetch": "3.3.0", + "prebuild-install": "6.1.4", "progress": "^2.0.3", "resolve": "^1.20.0", "stream-meter": "^1.0.4", - "tslib": "2.1.0" + "tslib": "2.3.1" }, "dependencies": { "@babel/parser": { - "version": "7.13.13", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.13.tgz", - "integrity": "sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw==", + "version": "7.16.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.2.tgz", + "integrity": "sha512-RUVpT0G2h6rOZwqLDTrKk7ksNv7YpAilTnYe1/Q+eDjxEceRMKVWbCsX7t8h6C1qCFi/1Y8WZjcEPBAFG27GPw==", "dev": true }, "@babel/types": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.12.tgz", - "integrity": "sha512-K4nY2xFN4QMvQwkQ+zmBDp6ANMbVNw6BbxWmYA4qNjhR9W+Lj/8ky5MEY2Me5r+B2c6/v6F53oMndG+f9s3IiA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", + "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "lodash": "^4.17.19", + "@babel/helper-validator-identifier": "^7.15.7", "to-fast-properties": "^2.0.0" } }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7447,6 +19034,21 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -7463,9 +19065,9 @@ } }, "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", "dev": true } } @@ -7480,20 +19082,30 @@ } }, "pkg-fetch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.1.1.tgz", - "integrity": "sha512-3GfpNwbwoTxge2TrVp6Oyz/FZJOoxF1r0+1YikOhnBXa2Di/VOJKtUObFHap76BFnyFo1fwh5vARWFR9TzLKUg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.3.0.tgz", + "integrity": "sha512-xJnIZ1KP+8rNN+VLafwu4tEeV4m8IkFBDdCFqmAJz9K1aiXEtbARmdbEe6HlXWGSVuShSHjFXpfkKRkDBQ5kiA==", "dev": true, "requires": { - "chalk": "^4.1.0", + "chalk": "^4.1.2", "fs-extra": "^9.1.0", "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", + "node-fetch": "^2.6.6", "progress": "^2.0.3", "semver": "^7.3.5", + "tar-fs": "^2.1.1", "yargs": "^16.2.0" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7504,6 +19116,21 @@ "supports-color": "^7.1.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -7511,9 +19138,9 @@ "dev": true }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -7530,16 +19157,10 @@ } } }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, "prebuild-install": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.0.1.tgz", - "integrity": "sha512-7GOJrLuow8yeiyv75rmvZyeMGzl8mdEX5gY69d6a6bHWmiPevwqFw+tQavhK0EYMaSg3/KD24cWqeQv1EWsqDQ==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", + "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==", "dev": true, "requires": { "detect-libc": "^1.0.3", @@ -7548,15 +19169,41 @@ "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^1.0.1", - "node-abi": "^2.7.0", - "noop-logger": "^0.1.1", + "node-abi": "^2.21.0", "npmlog": "^4.0.1", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^3.0.3", "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0", - "which-pm-runs": "^1.0.0" + "tunnel-agent": "^0.6.0" + }, + "dependencies": { + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, + "requires": { + "mimic-response": "^2.0.0" + } + }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true + }, + "simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dev": true, + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + } } }, "prelude-ls": { @@ -7566,9 +19213,9 @@ "dev": true }, "prettier": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz", - "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", + "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", "dev": true }, "prettier-linter-helpers": { @@ -7581,22 +19228,24 @@ } }, "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } } }, - "printj": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", - "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" - }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -7618,10 +19267,30 @@ "sisteransi": "^1.0.5" } }, + "protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + } + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==" }, "psl": { "version": "1.8.0", @@ -7649,11 +19318,6 @@ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, - "queue-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.0.tgz", - "integrity": "sha512-ULWhjjE8BmiICGn3G8+1L9wFpERNxkf8ysxkAer4+TFdRefDaXOCV5m92aMB9FtBVmn/8sETXLXY6BfW7hyaWQ==" - }, "ramda": { "version": "0.27.1", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", @@ -7683,61 +19347,17 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true } } }, - "reachdown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reachdown/-/reachdown-1.1.0.tgz", - "integrity": "sha512-6LsdRe4cZyOjw4NnvbhUd/rGG7WQ9HMopPr+kyL018Uci4kijtxcGR5kVb5Ln13k4PEE+fEFQbjfOvNw7cnXmA==" - }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } - } - }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -7748,6 +19368,15 @@ "util-deprecate": "^1.0.1" } }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -7755,36 +19384,37 @@ "dev": true }, "regenerate-unicode-properties": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz", - "integrity": "sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", + "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", "dev": true, "requires": { "regenerate": "^1.4.2" } }, "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true }, "regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", "dev": true, "requires": { "@babel/runtime": "^7.8.4" } }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" } }, "regexpp": { @@ -7794,29 +19424,29 @@ "dev": true }, "regexpu-core": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.8.0.tgz", - "integrity": "sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", + "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==", "dev": true, "requires": { "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^9.0.0", - "regjsgen": "^0.5.2", - "regjsparser": "^0.7.0", + "regenerate-unicode-properties": "^10.0.1", + "regjsgen": "^0.6.0", + "regjsparser": "^0.8.2", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.0.0" } }, "regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", "dev": true }, "regjsparser": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.7.0.tgz", - "integrity": "sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", + "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", "dev": true, "requires": { "jsesc": "~0.5.0" @@ -7825,54 +19455,30 @@ "jsesc": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", "dev": true } } }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" }, "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", "dev": true, "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" } }, "resolve-cwd": { @@ -7890,10 +19496,10 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "resolve.exports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", + "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", "dev": true }, "resource-counter": { @@ -7905,12 +19511,6 @@ "bitset": "^5.0.3" } }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -7935,12 +19535,6 @@ "inherits": "^2.0.1" } }, - "rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "dev": true - }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -7963,168 +19557,12 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "dev": true, - "requires": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, "saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", @@ -8143,32 +19581,9 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, "sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", @@ -8191,24 +19606,38 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", "dev": true, - "optional": true + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } }, "shiki": { - "version": "0.9.12", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.9.12.tgz", - "integrity": "sha512-VXcROdldv0/Qu0w2XvzU4IrvTeBNs/Kj/FCmtcEXGz7Tic/veQzliJj6tEiAgoKianhQstpYmbPDStHU5Opqcw==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz", + "integrity": "sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==", "dev": true, "requires": { "jsonc-parser": "^3.0.0", - "onigasm": "^2.2.5", + "vscode-oniguruma": "^1.6.1", "vscode-textmate": "5.2.0" } }, + "shx": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "dev": true, + "requires": { + "minimist": "^1.2.3", + "shelljs": "^0.8.5" + } + }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -8220,9 +19649,9 @@ } }, "signal-exit": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", - "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, "simple-concat": { @@ -8231,11 +19660,11 @@ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" }, "simple-get": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", - "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", "requires": { - "decompress-response": "^4.2.0", + "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } @@ -8251,233 +19680,26 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, "source-map-support": { - "version": "0.5.20", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", - "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", - "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" } }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, "stack-utils": { @@ -8497,31 +19719,10 @@ } } }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, "stream-meter": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/stream-meter/-/stream-meter-1.0.4.tgz", - "integrity": "sha1-Uq+Vql6nYKJJFxZwTb/5D3Ov3R0=", + "integrity": "sha512-4sOEtrbgFotXwnEuzzsQBYEV1elAeFSO8rSGeTwabuX1RRn/kEq9JVH7I0MRBhKVRR0sJkr0M0QCH7yOLf9fhQ==", "dev": true, "requires": { "readable-stream": "^2.1.4" @@ -8559,6 +19760,14 @@ } } }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -8570,47 +19779,57 @@ } }, "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", "dev": true, "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } } }, "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" } }, "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" } }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -8621,12 +19840,6 @@ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -8639,132 +19852,6 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, - "sublevel-prefixer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/sublevel-prefixer/-/sublevel-prefixer-1.0.0.tgz", - "integrity": "sha1-TuRZ72Y6yFvyj8ZJ17eWX9ppEHM=" - }, - "subleveldown": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/subleveldown/-/subleveldown-5.0.1.tgz", - "integrity": "sha512-cVqd/URpp7si1HWu5YqQ3vqQkjuolAwHypY1B4itPlS71/lsf6TQPZ2Y0ijT22EYVkvH5ove9JFJf4u7VGPuZw==", - "requires": { - "abstract-leveldown": "^6.3.0", - "encoding-down": "^6.2.0", - "inherits": "^2.0.3", - "level-option-wrap": "^1.1.0", - "levelup": "^4.4.0", - "reachdown": "^1.1.0" - }, - "dependencies": { - "abstract-leveldown": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.3.0.tgz", - "integrity": "sha512-TU5nlYgta8YrBMNpc9FwQzRbiXsj49gsALsXadbGHt9CROPzX5fB0rWDR5mtdpOOKa5XqRFpbj1QroPAoPzVjQ==", - "requires": { - "buffer": "^5.5.0", - "immediate": "^3.2.3", - "level-concat-iterator": "~2.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "deferred-leveldown": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", - "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", - "requires": { - "abstract-leveldown": "~6.2.1", - "inherits": "^2.0.3" - }, - "dependencies": { - "abstract-leveldown": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", - "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", - "requires": { - "buffer": "^5.5.0", - "immediate": "^3.2.3", - "level-concat-iterator": "~2.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" - } - } - } - }, - "encoding-down": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", - "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==", - "requires": { - "abstract-leveldown": "^6.2.1", - "inherits": "^2.0.3", - "level-codec": "^9.0.0", - "level-errors": "^2.0.0" - } - }, - "level-codec": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.2.tgz", - "integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==", - "requires": { - "buffer": "^5.6.0" - } - }, - "level-concat-iterator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", - "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==" - }, - "level-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", - "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", - "requires": { - "errno": "~0.1.1" - } - }, - "level-iterator-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", - "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.4.0", - "xtend": "^4.0.2" - } - }, - "level-supports": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", - "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", - "requires": { - "xtend": "^4.0.2" - } - }, - "levelup": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.4.0.tgz", - "integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==", - "requires": { - "deferred-leveldown": "~5.3.0", - "level-errors": "~2.0.0", - "level-iterator-stream": "~4.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" - } - } - } - }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -8801,40 +19888,18 @@ } } }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, - "table": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.2.tgz", - "integrity": "sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ajv": { - "version": "8.6.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", - "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - } - } - }, "tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -8884,7 +19949,7 @@ "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, "threads": { @@ -8900,9 +19965,9 @@ } }, "throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", + "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", "dev": true }, "timeout-refresh": { @@ -8933,47 +19998,9 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -8992,6 +20019,14 @@ "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.1.2" + }, + "dependencies": { + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + } } }, "tr46": { @@ -9009,27 +20044,25 @@ "integrity": "sha512-cBvC2QjtvJ9JfWLvstVnI45Y46Y5dMxIaG1TDMGAD/R87hpvqFL+7LhvUDhnRCfOnx/xitollFWWvUKKKhbN0A==" }, "ts-jest": { - "version": "26.5.6", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.6.tgz", - "integrity": "sha512-rua+rCP8DxpA8b4DQD/6X2HQS8Zy/xzViVYfEs2OQu68tkCuKLV0Md8pmX55+W24uRIyAsf/BajRfxOs+R2MKA==", + "version": "27.1.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.5.tgz", + "integrity": "sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA==", "dev": true, "requires": { "bs-logger": "0.x", - "buffer-from": "1.x", "fast-json-stable-stringify": "2.x", - "jest-util": "^26.1.0", + "jest-util": "^27.0.0", "json5": "2.x", - "lodash": "4.x", + "lodash.memoize": "4.x", "make-error": "1.x", - "mkdirp": "1.x", "semver": "7.x", "yargs-parser": "20.x" }, "dependencies": { "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -9038,9 +20071,9 @@ } }, "ts-node": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", - "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", "dev": true, "requires": { "@cspotcode/source-map-support": "0.7.0", @@ -9054,15 +20087,10 @@ "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.0", "yn": "3.1.1" }, "dependencies": { - "acorn": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", - "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", - "dev": true - }, "acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", @@ -9072,14 +20100,14 @@ } }, "tsconfig-paths": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", - "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", "dev": true, "requires": { "@types/json5": "^0.0.29", "json5": "^1.0.1", - "minimist": "^1.2.0", + "minimist": "^1.2.6", "strip-bom": "^3.0.0" }, "dependencies": { @@ -9095,15 +20123,15 @@ "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true } } }, "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "tsutils": { "version": "3.21.0", @@ -9125,7 +20153,7 @@ "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, "requires": { "safe-buffer": "^5.0.1" @@ -9147,9 +20175,9 @@ "dev": true }, "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true }, "typedarray-to-buffer": { @@ -9162,31 +20190,55 @@ } }, "typedoc": { - "version": "0.21.9", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.21.9.tgz", - "integrity": "sha512-VRo7aII4bnYaBBM1lhw4bQFmUcDQV8m8tqgjtc7oXl87jc1Slbhfw2X5MccfcR2YnEClHDWgsiQGgNB8KJXocA==", + "version": "0.22.17", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.17.tgz", + "integrity": "sha512-h6+uXHVVCPDaANzjwzdsj9aePBjZiBTpiMpBBeyh1zcN2odVsDCNajz8zyKnixF93HJeGpl34j/70yoEE5BfNg==", "dev": true, "requires": { - "glob": "^7.1.7", - "handlebars": "^4.7.7", + "glob": "^8.0.3", "lunr": "^2.3.9", - "marked": "^3.0.2", - "minimatch": "^3.0.0", - "progress": "^2.0.3", - "shiki": "^0.9.8", - "typedoc-default-themes": "^0.12.10" + "marked": "^4.0.16", + "minimatch": "^5.1.0", + "shiki": "^0.10.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, - "typedoc-default-themes": { - "version": "0.12.10", - "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.12.10.tgz", - "integrity": "sha512-fIS001cAYHkyQPidWXmHuhs8usjP5XVJjWB8oZGqkTowZaz3v7g3KDZeeqE82FBrmkAnIBOY3jgy7lnPnqATbA==", - "dev": true - }, "typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", - "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz", + "integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==", "dev": true }, "typescript-cached-transpile": { @@ -9201,9 +20253,9 @@ }, "dependencies": { "@types/node": { - "version": "12.20.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.37.tgz", - "integrity": "sha512-i1KGxqcvJaLQali+WuypQnXwcplhtNtjs66eNsZpp2P2FL/trJJxx/VWsM0YCL2iMoIJrbXje48lvIQAQ4p2ZA==", + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", "dev": true }, "fs-extra": { @@ -9220,7 +20272,7 @@ "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, "requires": { "graceful-fs": "^4.1.6" @@ -9231,24 +20283,30 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true } } }, "uglify-js": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.2.tgz", - "integrity": "sha512-rtPMlmcO4agTUfz10CbgJ1k6UAoXM2gWb3GoMPPZB/+/Ackf8lNWk11K4rYi2D0apgoFRLtQOZhb+/iGNJq26A==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.16.0.tgz", + "integrity": "sha512-FEikl6bR30n0T3amyBh3LoiBdqHRy/f4H80+My34HOesOKyHfOsxAPAxOoqC0JUnC1amnO0IwkYC3sko51caSw==", "dev": true, "optional": true }, "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" } }, @@ -9292,26 +20350,14 @@ "pako": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=" + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" } } }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true }, "unordered-set": { @@ -9319,46 +20365,6 @@ "resolved": "https://registry.npmjs.org/unordered-set/-/unordered-set-2.0.1.tgz", "integrity": "sha512-eUmNTPzdx+q/WvOHW0bgGYLWvWHNT3PTKEQLg0MAQhc0AHASHVHoP/9YytYd4RBVariqno/mEUhVZN98CmD7bg==" }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - } - } - }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -9367,18 +20373,6 @@ "punycode": "^2.1.0" } }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, "util-callbackify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/util-callbackify/-/util-callbackify-1.0.0.tgz", @@ -9390,7 +20384,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "utp-native": { "version": "2.5.3", @@ -9402,13 +20396,6 @@ "readable-stream": "^3.0.2", "timeout-refresh": "^1.0.0", "unordered-set": "^2.0.1" - }, - "dependencies": { - "node-gyp-build": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==" - } } }, "uuid": { @@ -9422,10 +20409,16 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "v8-to-istanbul": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", - "integrity": "sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", + "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.1", @@ -9434,22 +20427,18 @@ }, "dependencies": { "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "dev": true } } }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } + "vscode-oniguruma": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz", + "integrity": "sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==", + "dev": true }, "vscode-textmate": { "version": "5.2.0", @@ -9476,12 +20465,12 @@ } }, "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, "requires": { - "makeerror": "1.0.x" + "makeerror": "1.0.12" } }, "webidl-conversions": { @@ -9536,18 +20525,6 @@ "is-symbol": "^1.0.3" } }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "which-pm-runs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", - "dev": true - }, "wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -9566,24 +20543,61 @@ "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } } }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "write-file-atomic": { "version": "3.0.3", @@ -9598,10 +20612,16 @@ } }, "ws": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", - "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", - "dev": true + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.8.tgz", + "integrity": "sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw==", + "dev": true, + "requires": {} + }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==" }, "xml-name-validator": { "version": "3.0.0", @@ -9615,16 +20635,10 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, "yallist": { "version": "4.0.0", @@ -9636,7 +20650,6 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -9645,13 +20658,29 @@ "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } } }, "yargs-parser": { "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" }, "yn": { "version": "3.1.1", diff --git a/package.json b/package.json index cf62e9042..47f502c95 100644 --- a/package.json +++ b/package.json @@ -51,46 +51,53 @@ "pkg": { "assets": [ "node_modules/jose/**/*", - "node_modules/utp-native/**/*", - "node_modules/leveldown/**/*", - "node_modules/fd-lock/**/*", - "dist/**/*.json" + "dist/**/*.json", + "node_modules/tslib/**/*.js" ], - "scripts": "dist/workers/polykeyWorker.js" + "scripts": [ + "dist/workers/polykeyWorker.js", + "dist/bin/polykey-agent.js" + ] }, "scripts": { - "build": "rm -r ./dist || true; tsc -p ./tsconfig.build.json", - "postbuild": "cp -fR src/proto dist; cp src/notifications/*.json dist/notifications/; cp src/claims/*.json dist/claims/; cp src/status/*.json dist/status/;", + "prepare": "tsc -p ./tsconfig.build.json", + "build": "shx rm -rf ./dist && tsc -p ./tsconfig.build.json", + "postbuild": "shx cp -fR src/proto dist && shx cp src/notifications/*.json dist/notifications/ && shx cp src/claims/*.json dist/claims/ && shx cp src/status/*.json dist/status/", + "postversion": "npm install --package-lock-only --ignore-scripts --silent", "ts-node": "ts-node --require tsconfig-paths/register", "test": "jest", "lint": "eslint '{src,tests}/**/*.{js,ts}'", "lintfix": "eslint '{src,tests}/**/*.{js,ts}' --fix", - "docs": "rm -r ./docs || true; typedoc --gitRevision master --tsconfig ./tsconfig.build.json --out ./docs src && touch ./docs/.nojekyll", - "bench": "rm -r ./benches/results || true; ts-node --require tsconfig-paths/register --compiler typescript-cached-transpile --transpile-only ./benches", + "docs": "shx rm -rf ./docs && typedoc --gitRevision master --tsconfig ./tsconfig.build.json --out ./docs src", + "bench": "shx rm -rf ./benches/results && ts-node --require tsconfig-paths/register --compiler typescript-cached-transpile --transpile-only ./benches", "proto-generate": "scripts/proto-generate.sh", + "pkg": "./scripts/pkg.js --no-dict=leveldown.js", "polykey": "ts-node --require tsconfig-paths/register --compiler typescript-cached-transpile --transpile-only src/bin/polykey.ts" }, "dependencies": { - "@grpc/grpc-js": "1.3.7", - "@matrixai/async-init": "^1.6.0", - "@matrixai/db": "^1.1.5", - "@matrixai/id": "^3.3.2", - "@matrixai/logger": "^2.1.0", - "@matrixai/workers": "^1.2.5", + "@grpc/grpc-js": "1.6.7", + "@matrixai/async-init": "^1.8.1", + "@matrixai/async-locks": "^2.3.1", + "@matrixai/db": "^4.0.5", + "@matrixai/errors": "^1.1.1", + "@matrixai/id": "^3.3.3", + "@matrixai/logger": "^2.2.2", + "@matrixai/resources": "^1.1.3", + "@matrixai/workers": "^1.3.3", "ajv": "^7.0.4", - "async-mutex": "^0.3.2", "bip39": "^3.0.3", "canonicalize": "^1.0.5", "cheerio": "^1.0.0-rc.5", "commander": "^8.3.0", "cross-fetch": "^3.0.6", "cross-spawn": "^7.0.3", - "encryptedfs": "^3.4.3", + "encryptedfs": "^3.5.3", "fast-fuzzy": "^1.10.8", "fd-lock": "^1.2.0", "google-protobuf": "^3.14.0", "ip-num": "^1.3.3-0", "isomorphic-git": "^1.8.1", + "jest-junit": "^13.2.0", "jose": "^4.3.6", "lexicographic-integer": "^1.1.0", "multiformats": "^9.4.8", @@ -100,7 +107,6 @@ "readable-stream": "^3.6.0", "resource-counter": "^1.2.4", "threads": "^1.6.5", - "ts-custom-error": "^3.2.0", "utp-native": "^2.5.3", "uuid": "^8.3.0" }, @@ -108,35 +114,37 @@ "@babel/preset-env": "^7.13.10", "@types/cross-spawn": "^6.0.2", "@types/google-protobuf": "^3.7.4", - "@types/jest": "^26.0.20", + "@types/jest": "^27.0.2", "@types/nexpect": "^0.4.31", - "@types/node": "^14.14.35", - "@types/node-forge": "^0.9.7", + "@types/node": "^16.11.7", + "@types/node-forge": "^0.10.4", "@types/pako": "^1.0.2", "@types/prompts": "^2.0.13", "@types/readable-stream": "^2.3.11", "@types/uuid": "^8.3.0", - "@typescript-eslint/eslint-plugin": "^5.4.0", - "@typescript-eslint/parser": "^5.4.0", - "babel-jest": "^26.6.3", - "eslint": "^7.17.0", - "eslint-config-prettier": "^7.1.0", - "eslint-plugin-import": "^2.25.3", - "eslint-plugin-prettier": "^3.3.1", + "@typescript-eslint/eslint-plugin": "^5.23.0", + "@typescript-eslint/parser": "^5.23.0", + "babel-jest": "^27.0.0", + "eslint": "^8.15.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-prettier": "^4.0.0", "grpc_tools_node_protoc_ts": "^5.1.3", - "jest": "^26.6.3", + "jest": "^27.2.5", "jest-mock-process": "^1.4.1", "jest-mock-props": "^1.9.0", "mocked-env": "^1.3.5", "nexpect": "^0.6.0", - "node-gyp-build": "4.2.3", - "pkg": "5.3.0", - "prettier": "^2.2.1", - "ts-jest": "^26.4.4", - "ts-node": "^10.4.0", + "node-gyp-build": "^4.4.0", + "pkg": "5.6.0", + "prettier": "^2.6.2", + "shelljs": "^0.8.5", + "shx": "^0.3.4", + "ts-jest": "^27.0.5", + "ts-node": "10.7.0", "tsconfig-paths": "^3.9.0", - "typedoc": "^0.21.5", - "typescript": "^4.1.3", + "typedoc": "^0.22.15", + "typescript": "^4.5.2", "typescript-cached-transpile": "0.0.6" } } diff --git a/pkgs.nix b/pkgs.nix index 4b5b0c11b..6fe22a5b7 100644 --- a/pkgs.nix +++ b/pkgs.nix @@ -1,4 +1,4 @@ import ( - let rev = "53caacaf56640d04180775aee016d2f16d6f083c"; in - fetchTarball "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz" + let rev = "a5774e76bb8c3145eac524be62375c937143b80c"; in + builtins.fetchTarball "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz" ) diff --git a/release.nix b/release.nix index c806c1fc5..2544eb403 100644 --- a/release.nix +++ b/release.nix @@ -8,21 +8,16 @@ let name = "${utils.basename}-${version}-linux-${arch}"; version = utils.node2nixDev.version; src = "${utils.node2nixDev}/lib/node_modules/${utils.node2nixDev.packageName}"; - buildInputs = [ - utils.pkg - ]; + nativeBuildInputs = [ nodejs ]; PKG_CACHE_PATH = utils.pkgCachePath; PKG_IGNORE_TAG = 1; - # ensure that native modules are built from source - npm_config_build_from_source = "true"; buildPhase = '' - cp ${./package.json} package.json - pkg . \ - --targets linux-${arch} \ - --no-bytecode \ - --public \ - --public-packages "*" \ - --output out + npm run pkg -- \ + --output=out \ + --bin=polykey \ + --node-version=${utils.nodeVersion} \ + --platform=linux \ + --arch=${arch} ''; installPhase = '' cp out $out @@ -31,24 +26,19 @@ let }; buildExe = arch: stdenv.mkDerivation rec { - name = "${utils.basename}-${version}-win32-${arch}.exe"; + name = "${utils.basename}-${version}-win-${arch}.exe"; version = utils.node2nixDev.version; src = "${utils.node2nixDev}/lib/node_modules/${utils.node2nixDev.packageName}"; - buildInputs = [ - utils.pkg - ]; + nativeBuildInputs = [ nodejs ]; PKG_CACHE_PATH = utils.pkgCachePath; PKG_IGNORE_TAG = 1; - # ensure that native modules are built from source - npm_config_build_from_source = "true"; buildPhase = '' - cp ${./package.json} package.json - pkg . \ - --targets win-${arch} \ - --no-bytecode \ - --public \ - --public-packages "*" \ - --output out.exe + npm run pkg -- \ + --output=out.exe \ + --bin=polykey \ + --node-version=${utils.nodeVersion} \ + --platform=win32 \ + --arch=${arch} ''; installPhase = '' cp out.exe $out @@ -60,44 +50,34 @@ let name = "${utils.basename}-${version}-macos-${arch}"; version = utils.node2nixDev.version; src = "${utils.node2nixDev}/lib/node_modules/${utils.node2nixDev.packageName}"; - buildInputs = [ - utils.pkg - ]; + nativeBuildInputs = [ nodejs ]; PKG_CACHE_PATH = utils.pkgCachePath; PKG_IGNORE_TAG = 1; - # ensure that native modules are built from source - npm_config_build_from_source = "true"; buildPhase = '' - cp ${./package.json} package.json - pkg . \ - --targets macos-${arch} \ - --no-bytecode \ - --public \ - --public-packages "*" \ - --output out + npm run pkg -- \ + --output=out \ + --bin=polykey \ + --node-version=${utils.nodeVersion} \ + --platform=darwin \ + --arch=${arch} ''; installPhase = '' cp out $out ''; dontFixup = true; }; - # allow resolution of localhost - nsswitch = writeTextDir "etc/nsswitch.conf" - '' - hosts: files dns myhostname - ''; in rec { application = callPackage ./default.nix {}; docker = dockerTools.buildImage { name = application.name; - contents = [ application cacert nsswitch ]; + contents = [ application ]; keepContentsDirlinks = true; extraCommands = '' mkdir -m 1777 tmp ''; config = { - Entrypoint = [ "/bin/polykey" ]; + Cmd = [ "/bin/polykey" ]; }; }; package = { @@ -115,6 +95,9 @@ in x64 = { macho = buildMacho "x64"; }; + arm64 = { + macho = buildMacho "arm64"; + }; }; }; } diff --git a/scripts/pkg.js b/scripts/pkg.js new file mode 100755 index 000000000..d5e55c10b --- /dev/null +++ b/scripts/pkg.js @@ -0,0 +1,174 @@ +#!/usr/bin/env node + +const os = require('os'); +const fs = require('fs'); +const path = require('path'); +const process = require('process'); +const crypto = require('crypto'); +const child_process = require('child_process'); +const packageJSON = require('../package.json'); + +/** + * Supported platforms + * Maps os.platform() to pkg platform + */ +const platforms = { + 'linux': 'linux', + 'win32': 'win', + 'darwin': 'macos', +}; + +/** + * Supported architectures + * Maps os.arch() to pkg arch + */ +const archs = { + 'x64': 'x64', + 'arm64': 'arm64', +}; + +function randomString(l) { + return crypto + .randomBytes(l) + .toString('base64') + .replace(/\//, '_'); +} + +async function find(dirPath, pattern) { + const found = []; + let entries; + try { + entries = await fs.promises.readdir(dirPath); + } catch (e) { + if (e.code === 'ENOENT') { + return found ; + } + throw e; + } + for (const entry of entries) { + const entryPath = path.join(dirPath, entry); + const stat = await fs.promises.lstat(entryPath); + if (stat.isDirectory()) { + found.push(...(await find(entryPath, pattern))); + } else if (pattern.test(entryPath)) { + found.push(entryPath); + } + } + return found; +}; + +async function main(argv = process.argv) { + argv = argv.slice(2); + let outPath; + let binTarget; + let nodeVersion = process.versions.node.match(/\d+/)[0]; + let platform = os.platform(); + let arch = os.arch(); + const restArgs = []; + while (argv.length > 0) { + const option = argv.shift(); + let match; + if (match = option.match(/--output(?:=(.+)|$)/)) { + outPath = match[1] ?? argv.shift(); + } else if (match = option.match(/--bin(?:=(.+)|$)/)) { + binTarget = match[1] ?? argv.shift(); + } else if (match = option.match(/--node-version(?:=(.+)|$)/)) { + nodeVersion = match[1] ?? argv.shift(); + } else if (match = option.match(/--platform(?:=(.+)|$)/)) { + platform = match[1] ?? argv.shift(); + } else if (match = option.match(/--arch(?:=(.+)|$)/)) { + arch = match[1] ?? argv.shift(); + } else { + restArgs.push(option); + } + } + let entryPoint; + if (binTarget == null) { + entryPoint = Object.values(packageJSON.bin ?? {})[0]; + } else { + entryPoint = packageJSON.bin[binTarget]; + } + if (entryPoint == null) { + throw new Error('Bin executable is required'); + } + if (typeof outPath !== 'string') { + throw new Error('Output path is required'); + } + if (entryPoint == null) { + throw new Error(`Unknown bin target: ${binTarget}`); + } + if (isNaN(parseInt(nodeVersion))) { + throw new Error(`Unsupported node version: ${nodeVersion}`); + } + if (!(platform in platforms)) { + throw new Error(`Unsupported platform: ${platform}`); + } + if (!(arch in archs)) { + throw new Error(`Unsupported architecture: ${arch}`); + } + // Monkey patch the os.platform and os.arch for node-gyp-build + os.platform = () => platform; + os.arch = () => arch; + const nodeGypBuild = require('node-gyp-build'); + const pkgConfig = packageJSON.pkg ?? {}; + pkgConfig.assets = pkgConfig.assets ?? {}; + const npmLsOut = child_process.execFileSync( + 'npm', + ['ls', '--all', '--prod', '--parseable'], + { + windowsHide: true, + encoding: 'utf-8' + } + ); + const nodePackages = npmLsOut.trim().split('\n'); + const projectRoot = path.join(__dirname, '..'); + for (const nodePackage of nodePackages) { + // If `build` or `prebuilds` directory exists with a `.node` file + // then we expect to find the appropriate native addon + // The `node-gyp-build` will look in these 2 directories + const buildPath = path.join(nodePackage, 'build'); + const prebuildsPath = path.join(nodePackage, 'prebuilds'); + const buildFinds = await find(buildPath, /.node$/); + const prebuildsFinds = await find(prebuildsPath, /.node$/); + if (buildFinds.length > 0 || prebuildsFinds.length > 0) { + let nativeAddonPath = nodeGypBuild.path(nodePackage); + // Must use relative paths + // so that assets are added relative to the project + nativeAddonPath = path.relative(projectRoot, nativeAddonPath); + pkgConfig.assets.push(nativeAddonPath); + } + } + console.error('Configured pkg with:'); + console.error(pkgConfig); + // The pkg config must be in the same directory as the `package.json` + // otherwise the relative paths won't work + const pkgConfigPath = path.join(projectRoot, 'pkg.json'); + await fs.promises.writeFile(pkgConfigPath, JSON.stringify(pkgConfig)); + const pkgPlatform = platforms[platform]; + const pkgArch = archs[arch]; + const pkgArgs = [ + entryPoint, + `--config=${pkgConfigPath}`, + `--targets=node${nodeVersion}-${pkgPlatform}-${pkgArch}`, + '--no-bytecode', + '--no-native-build', + '--public', + '--public-packages=\'*\'', + `--output=${outPath}`, + ...restArgs + ]; + console.error('Running pkg:') + console.error(['pkg', ...pkgArgs].join(' ')); + child_process.execFileSync( + 'pkg', + pkgArgs, + { + stdio: ['inherit', 'inherit', 'inherit'], + windowsHide: true, + encoding: 'utf-8' + } + ); + await fs.promises.rm(pkgConfigPath); +} + +void main(); diff --git a/scripts/test-pipelines.sh b/scripts/test-pipelines.sh index 323850fdd..b82ab5746 100755 --- a/scripts/test-pipelines.sh +++ b/scripts/test-pipelines.sh @@ -5,7 +5,19 @@ shopt -s nullglob # Quote the heredoc to prevent shell expansion cat << "EOF" +workflow: + rules: + # Disable merge request pipelines + - if: $CI_MERGE_REQUEST_ID + when: never + - when: always + +default: + interruptible: true + variables: + GH_PROJECT_PATH: "MatrixAI/${CI_PROJECT_NAME}" + GH_PROJECT_URL: "https://${GITHUB_TOKEN}@github.com/${GH_PROJECT_PATH}.git" GIT_SUBMODULE_STRATEGY: "recursive" # Cache .npm NPM_CONFIG_CACHE: "./tmp/npm" @@ -16,7 +28,7 @@ variables: TS_CACHED_TRANSPILE_CACHE: "${CI_PROJECT_DIR}/tmp/ts-node-cache" TS_CACHED_TRANSPILE_PORTABLE: "true" -# Cached directories shared between jobs & pipelines per-branch +# Cached directories shared between jobs & pipelines per-branch per-runner cache: key: $CI_COMMIT_REF_SLUG paths: @@ -24,24 +36,12 @@ cache: - ./tmp/ts-node-cache/ # `jest` cache is configured in jest.config.js - ./tmp/jest/ + +image: registry.gitlab.com/matrixai/engineering/maintenance/gitlab-runner EOF printf "\n" -# # SPECIAL CASE -# cat << EOF -# test binagent: -# image: registry.gitlab.com/matrixai/engineering/maintenance/gitlab-runner -# stage: test -# interruptible: true -# script: -# - > -# nix-shell -I nixpkgs=./pkgs.nix --packages nodejs --run ' -# npm ci; -# npm test -- ./tests/bin/agent; -# ' -# EOF - # Each test directory has its own job for test_dir in tests/**/*/; do test_files=("$test_dir"*.test.ts) @@ -53,16 +53,20 @@ for test_dir in tests/**/*/; do # Remove `tests/` prefix test_dir="${test_dir#*/}" cat << EOF -test $test_dir: - image: registry.gitlab.com/matrixai/engineering/maintenance/gitlab-runner +check:test $test_dir: stage: test - interruptible: true + needs: [] script: - > - nix-shell -I nixpkgs=./pkgs.nix --packages nodejs --run ' - npm ci; - npm test -- ${test_files[@]}; + nix-shell --run ' + npm run build --verbose; + npm test -- --ci --runInBand ${test_files[@]}; ' + artifacts: + when: always + reports: + junit: + - ./tmp/junit/junit.xml EOF printf "\n" done @@ -70,14 +74,18 @@ done # All top-level test files are accumulated into 1 job test_files=(tests/*.test.ts) cat << EOF -test index: - image: registry.gitlab.com/matrixai/engineering/maintenance/gitlab-runner +check:test index: stage: test - interruptible: true + needs: [] script: - > - nix-shell -I nixpkgs=./pkgs.nix --packages nodejs --run ' - npm ci; - npm test -- ${test_files[@]}; + nix-shell --run ' + npm run build --verbose; + npm test -- --ci --runInBand ${test_files[@]}; ' + artifacts: + when: always + reports: + junit: + - ./tmp/junit/junit.xml EOF diff --git a/shell.nix b/shell.nix index 318d807aa..961a13c8a 100644 --- a/shell.nix +++ b/shell.nix @@ -4,13 +4,12 @@ with pkgs; let utils = callPackage ./utils.nix {}; in - pkgs.mkShell { + mkShell { nativeBuildInputs = [ nodejs - nodePackages.node2nix + utils.node2nix grpc-tools grpcurl - utils.pkg ]; PKG_CACHE_PATH = utils.pkgCachePath; PKG_IGNORE_TAG = 1; @@ -21,21 +20,15 @@ in set +o allexport set -v - # Enables npm link to work - export npm_config_prefix=~/.npm + mkdir --parents "$(pwd)/tmp" + # Built executables and NPM executables export PATH="$(pwd)/dist/bin:$(npm bin):$PATH" - # pkg is installed in package.json - # this ensures that in nix-shell we are using the nix packaged versions - export PATH="${lib.makeBinPath - [ - utils.pkg - ] - }:$PATH" + # Enables npm link to work + export npm_config_prefix=~/.npm - npm install - mkdir --parents "$(pwd)/tmp" + npm install --ignore-scripts set +v ''; diff --git a/src/ErrorPolykey.ts b/src/ErrorPolykey.ts index 414b95732..b4a1d046f 100644 --- a/src/ErrorPolykey.ts +++ b/src/ErrorPolykey.ts @@ -1,24 +1,43 @@ -import type { POJO } from './types'; -import { CustomError } from 'ts-custom-error'; +import type { Class } from '@matrixai/errors'; +import { AbstractError } from '@matrixai/errors'; import sysexits from './utils/sysexits'; -class ErrorPolykey extends CustomError { - data: POJO; - description: string = 'Polykey error'; +class ErrorPolykey extends AbstractError { + static description: string = 'Polykey error'; exitCode: number = sysexits.GENERAL; - constructor(message: string = '', data: POJO = {}) { - super(message); - this.data = data; - } - toJSON(): string { - return JSON.stringify({ - name: this.name, - description: this.description, - message: this.message, - exitCode: this.exitCode, - data: this.data, - stack: this.stack, + + public static fromJSON>( + this: T, + json: any, + ): InstanceType { + if ( + typeof json !== 'object' || + json.type !== this.name || + typeof json.data !== 'object' || + typeof json.data.message !== 'string' || + isNaN(Date.parse(json.data.timestamp)) || + typeof json.data.description !== 'string' || + typeof json.data.data !== 'object' || + typeof json.data.exitCode !== 'number' || + ('stack' in json.data && typeof json.data.stack !== 'string') + ) { + throw new TypeError(`Cannot decode JSON to ${this.name}`); + } + const e = new this(json.data.message, { + timestamp: new Date(json.data.timestamp), + data: json.data.data, + cause: json.data.cause, }); + e.exitCode = json.data.exitCode; + e.stack = json.data.stack; + return e; + } + + public toJSON(): any { + const json = super.toJSON(); + json.data.description = this.description; + json.data.exitCode = this.exitCode; + return json; } } diff --git a/src/PolykeyAgent.ts b/src/PolykeyAgent.ts index ba9cafc37..3cd247700 100644 --- a/src/PolykeyAgent.ts +++ b/src/PolykeyAgent.ts @@ -1,6 +1,6 @@ import type { FileSystem } from './types'; import type { PolykeyWorkerManagerInterface } from './workers/types'; -import type { Host, Port } from './network/types'; +import type { ConnectionData, Host, Port } from './network/types'; import type { SeedNodes } from './nodes/types'; import type { KeyManagerChangeData } from './keys/types'; import path from 'path'; @@ -8,27 +8,33 @@ import process from 'process'; import Logger from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { CreateDestroyStartStop } from '@matrixai/async-init/dist/CreateDestroyStartStop'; -import { KeyManager, utils as keysUtils } from './keys'; -import { Status } from './status'; -import { Schema } from './schema'; -import { VaultManager } from './vaults'; -import { ACL } from './acl'; -import { NodeConnectionManager, NodeGraph, NodeManager } from './nodes'; -import * as nodesUtils from './nodes/utils'; -import { NotificationsManager } from './notifications'; -import { GestaltGraph } from './gestalts'; -import { Sigchain } from './sigchain'; -import { Discovery } from './discovery'; -import { SessionManager } from './sessions'; -import { GRPCServer } from './grpc'; -import { IdentitiesManager, providers } from './identities'; +import Queue from './nodes/Queue'; +import * as networkUtils from './network/utils'; +import KeyManager from './keys/KeyManager'; +import Status from './status/Status'; +import Schema from './schema/Schema'; +import VaultManager from './vaults/VaultManager'; +import ACL from './acl/ACL'; +import NodeManager from './nodes/NodeManager'; +import NodeGraph from './nodes/NodeGraph'; +import NodeConnectionManager from './nodes/NodeConnectionManager'; +import NotificationsManager from './notifications/NotificationsManager'; +import GestaltGraph from './gestalts/GestaltGraph'; +import Sigchain from './sigchain/Sigchain'; +import Discovery from './discovery/Discovery'; +import SessionManager from './sessions/SessionManager'; +import GRPCServer from './grpc/GRPCServer'; +import IdentitiesManager from './identities/IdentitiesManager'; +import { providers } from './identities'; import Proxy from './network/Proxy'; import { EventBus, captureRejectionSymbol } from './events'; -import { createAgentService, AgentServiceService } from './agent'; -import { createClientService, ClientServiceService } from './client'; +import createAgentService, { AgentServiceService } from './agent/service'; +import createClientService, { ClientServiceService } from './client/service'; import config from './config'; -import * as utils from './utils'; import * as errors from './errors'; +import * as utils from './utils'; +import * as keysUtils from './keys/utils'; +import * as nodesUtils from './nodes/utils'; type NetworkConfig = { forwardHost?: Host; @@ -55,8 +61,10 @@ class PolykeyAgent { */ public static readonly eventSymbols = { [KeyManager.name]: Symbol(KeyManager.name), + [Proxy.name]: Symbol(Proxy.name), } as { readonly KeyManager: unique symbol; + readonly Proxy: unique symbol; }; public static async createPolykeyAgent({ @@ -80,6 +88,7 @@ class PolykeyAgent { gestaltGraph, proxy, nodeGraph, + queue, nodeConnectionManager, nodeManager, discovery, @@ -125,6 +134,7 @@ class PolykeyAgent { gestaltGraph?: GestaltGraph; proxy?: Proxy; nodeGraph?: NodeGraph; + queue?: Queue; nodeConnectionManager?: NodeConnectionManager; nodeManager?: NodeManager; discovery?: Discovery; @@ -262,6 +272,8 @@ class PolykeyAgent { proxy ?? new Proxy({ ...proxyConfig_, + connectionEstablishedCallback: (data) => + events.emitAsync(PolykeyAgent.eventSymbols.Proxy, data), logger: logger.getChild(Proxy.name), }); nodeGraph = @@ -272,12 +284,18 @@ class PolykeyAgent { keyManager, logger: logger.getChild(NodeGraph.name), })); + queue = + queue ?? + new Queue({ + logger: logger.getChild(Queue.name), + }); nodeConnectionManager = nodeConnectionManager ?? new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, seedNodes, ...nodeConnectionManagerConfig_, logger: logger.getChild(NodeConnectionManager.name), @@ -290,8 +308,10 @@ class PolykeyAgent { keyManager, nodeGraph, nodeConnectionManager, + queue, logger: logger.getChild(NodeManager.name), }); + await nodeManager.start(); discovery = discovery ?? (await Discovery.createDiscovery({ @@ -375,6 +395,7 @@ class PolykeyAgent { gestaltGraph, proxy, nodeGraph, + queue, nodeConnectionManager, nodeManager, discovery, @@ -407,6 +428,7 @@ class PolykeyAgent { public readonly gestaltGraph: GestaltGraph; public readonly proxy: Proxy; public readonly nodeGraph: NodeGraph; + public readonly queue: Queue; public readonly nodeConnectionManager: NodeConnectionManager; public readonly nodeManager: NodeManager; public readonly discovery: Discovery; @@ -417,8 +439,7 @@ class PolykeyAgent { public readonly grpcServerClient: GRPCServer; public readonly events: EventBus; public readonly fs: FileSystem; - - protected logger: Logger; + public readonly logger: Logger; constructor({ nodePath, @@ -432,6 +453,7 @@ class PolykeyAgent { gestaltGraph, proxy, nodeGraph, + queue, nodeConnectionManager, nodeManager, discovery, @@ -455,6 +477,7 @@ class PolykeyAgent { gestaltGraph: GestaltGraph; proxy: Proxy; nodeGraph: NodeGraph; + queue: Queue; nodeConnectionManager: NodeConnectionManager; nodeManager: NodeManager; discovery: Discovery; @@ -480,6 +503,7 @@ class PolykeyAgent { this.proxy = proxy; this.discovery = discovery; this.nodeGraph = nodeGraph; + this.queue = queue; this.nodeConnectionManager = nodeConnectionManager; this.nodeManager = nodeManager; this.vaultManager = vaultManager; @@ -527,7 +551,7 @@ class PolykeyAgent { await this.status.updateStatusLive({ nodeId: data.nodeId, }); - await this.nodeManager.refreshBuckets(); + await this.nodeManager.resetBuckets(); const tlsConfig = { keyPrivatePem: keysUtils.privateKeyToPem( data.rootKeyPair.privateKey, @@ -539,6 +563,31 @@ class PolykeyAgent { this.logger.info(`${KeyManager.name} change propagated`); }, ); + this.events.on( + PolykeyAgent.eventSymbols.Proxy, + async (data: ConnectionData) => { + if (data.type === 'reverse') { + const address = networkUtils.buildAddress( + data.remoteHost, + data.remotePort, + ); + const nodeIdEncoded = nodesUtils.encodeNodeId(data.remoteNodeId); + this.logger.info( + `Reverse connection adding ${nodeIdEncoded}:${address} to ${NodeGraph.name}`, + ); + // Reverse connection was established and authenticated, + // add it to the node graph + await this.nodeManager.setNode( + data.remoteNodeId, + { + host: data.remoteHost, + port: data.remotePort, + }, + false, + ); + } + }, + ); const networkConfig_ = { ...config.defaults.networkConfig, ...utils.filterEmptyObject(networkConfig), @@ -546,6 +595,7 @@ class PolykeyAgent { await this.status.start({ pid: process.pid }); await this.schema.start({ fresh }); const agentService = createAgentService({ + db: this.db, keyManager: this.keyManager, vaultManager: this.vaultManager, nodeManager: this.nodeManager, @@ -556,9 +606,11 @@ class PolykeyAgent { acl: this.acl, gestaltGraph: this.gestaltGraph, proxy: this.proxy, + logger: this.logger.getChild('GRPCClientAgentService'), }); const clientService = createClientService({ pkAgent: this, + db: this.db, discovery: this.discovery, gestaltGraph: this.gestaltGraph, identitiesManager: this.identitiesManager, @@ -575,6 +627,7 @@ class PolykeyAgent { grpcServerAgent: this.grpcServerAgent, proxy: this.proxy, fs: this.fs, + logger: this.logger.getChild('GRPCClientClientService'), }); // Starting modules await this.keyManager.start({ @@ -613,9 +666,11 @@ class PolykeyAgent { proxyPort: networkConfig_.proxyPort, tlsConfig, }); - await this.nodeConnectionManager.start(); + await this.queue.start(); + await this.nodeManager.start(); + await this.nodeConnectionManager.start({ nodeManager: this.nodeManager }); await this.nodeGraph.start({ fresh }); - await this.nodeConnectionManager.syncNodeGraph(); + await this.nodeConnectionManager.syncNodeGraph(false); await this.discovery.start({ fresh }); await this.vaultManager.start({ fresh }); await this.notificationsManager.start({ fresh }); @@ -635,11 +690,16 @@ class PolykeyAgent { this.logger.info(`Started ${this.constructor.name}`); } catch (e) { this.logger.warn(`Failed Starting ${this.constructor.name}`); + this.events.removeAllListeners(); await this.status?.beginStop({ pid: process.pid }); await this.sessionManager?.stop(); await this.notificationsManager?.stop(); await this.vaultManager?.stop(); await this.discovery?.stop(); + await this.queue?.stop(); + await this.nodeGraph?.stop(); + await this.nodeConnectionManager?.stop(); + await this.nodeManager?.stop(); await this.proxy?.stop(); await this.grpcServerAgent?.stop(); await this.grpcServerClient?.stop(); @@ -651,7 +711,6 @@ class PolykeyAgent { await this.keyManager?.stop(); await this.schema?.stop(); await this.status?.stop({}); - this.events.removeAllListeners(); throw e; } } @@ -661,6 +720,7 @@ class PolykeyAgent { */ public async stop() { this.logger.info(`Stopping ${this.constructor.name}`); + this.events.removeAllListeners(); await this.status.beginStop({ pid: process.pid }); await this.sessionManager.stop(); await this.notificationsManager.stop(); @@ -668,6 +728,8 @@ class PolykeyAgent { await this.discovery.stop(); await this.nodeConnectionManager.stop(); await this.nodeGraph.stop(); + await this.nodeManager.stop(); + await this.queue.stop(); await this.proxy.stop(); await this.grpcServerAgent.stop(); await this.grpcServerClient.stop(); @@ -679,7 +741,6 @@ class PolykeyAgent { await this.keyManager.stop(); await this.schema.stop(); await this.status.stop({}); - this.events.removeAllListeners(); this.logger.info(`Stopped ${this.constructor.name}`); } diff --git a/src/PolykeyClient.ts b/src/PolykeyClient.ts index b124feefa..bea2b830b 100644 --- a/src/PolykeyClient.ts +++ b/src/PolykeyClient.ts @@ -1,4 +1,4 @@ -import type { FileSystem } from './types'; +import type { FileSystem, Timer } from './types'; import type { NodeId } from './nodes/types'; import type { Host, Port } from './network/types'; @@ -29,7 +29,7 @@ class PolykeyClient { nodePath = config.defaults.nodePath, session, grpcClient, - timeout, + timer, fs = require('fs'), logger = new Logger(this.name), fresh = false, @@ -38,7 +38,7 @@ class PolykeyClient { host: Host; port: Port; nodePath?: string; - timeout?: number; + timer?: Timer; session?: Session; grpcClient?: GRPCClientClient; fs?: FileSystem; @@ -66,7 +66,7 @@ class PolykeyClient { port, tlsConfig: { keyPrivatePem: undefined, certChainPem: undefined }, session, - timeout, + timer, logger: logger.getChild(GRPCClientClient.name), })); const pkClient = new PolykeyClient({ diff --git a/src/acl/ACL.ts b/src/acl/ACL.ts index 358663d51..ac83ade13 100644 --- a/src/acl/ACL.ts +++ b/src/acl/ACL.ts @@ -1,21 +1,22 @@ +import type { DB, DBTransaction, LevelPath } from '@matrixai/db'; import type { PermissionId, PermissionIdString, Permission, VaultActions, } from './types'; -import type { DB, DBLevel, DBOp } from '@matrixai/db'; import type { NodeId } from '../nodes/types'; import type { GestaltAction } from '../gestalts/types'; import type { VaultAction, VaultId } from '../vaults/types'; import type { Ref } from '../types'; -import { Mutex } from 'async-mutex'; + import Logger from '@matrixai/logger'; import { IdInternal } from '@matrixai/id'; import { CreateDestroyStartStop, ready, } from '@matrixai/async-init/dist/CreateDestroyStartStop'; +import { withF } from '@matrixai/resources'; import * as aclUtils from './utils'; import * as aclErrors from './errors'; @@ -43,15 +44,23 @@ class ACL { protected logger: Logger; protected db: DB; - protected aclDbDomain: string = this.constructor.name; - protected aclPermsDbDomain: Array = [this.aclDbDomain, 'perms']; - protected aclNodesDbDomain: Array = [this.aclDbDomain, 'nodes']; - protected aclVaultsDbDomain: Array = [this.aclDbDomain, 'vaults']; - protected aclDb: DBLevel; - protected aclPermsDb: DBLevel; - protected aclNodesDb: DBLevel; - protected aclVaultsDb: DBLevel; - protected lock: Mutex = new Mutex(); + + protected aclDbPath: LevelPath = [this.constructor.name]; + /** + * Perms stores PermissionId -> Ref + */ + protected aclPermsDbPath: LevelPath = [this.constructor.name, 'perms']; + /** + * Nodes stores NodeId -> PermissionId + */ + protected aclNodesDbPath: LevelPath = [this.constructor.name, 'nodes']; + /** + * Vaults stores VaultIdString -> Record + * note that the NodeId in each record must be in their own unique gestalt + * the NodeId in each record may be missing if it had been previously deleted + */ + protected aclVaultsDbPath: LevelPath = [this.constructor.name, 'vaults']; + protected generatePermId: () => PermissionId; constructor({ db, logger }: { db: DB; logger: Logger }) { @@ -59,32 +68,15 @@ class ACL { this.db = db; } - get locked(): boolean { - return this.lock.isLocked(); - } - public async start({ fresh = false, }: { fresh?: boolean; } = {}): Promise { this.logger.info(`Starting ${this.constructor.name}`); - const aclDb = await this.db.level(this.aclDbDomain); - // Perms stores PermissionId -> Ref - const aclPermsDb = await this.db.level(this.aclPermsDbDomain[1], aclDb); - // Nodes stores NodeId -> PermissionId - const aclNodesDb = await this.db.level(this.aclNodesDbDomain[1], aclDb); - // Vaults stores VaultIdString -> Record - // note that the NodeId in each array must be in their own unique gestalt - // the NodeId in each array may be missing if it had been previously deleted - const aclVaultsDb = await this.db.level(this.aclVaultsDbDomain[1], aclDb); if (fresh) { - await aclDb.clear(); + await this.db.clear(this.aclDbPath); } - this.aclDb = aclDb; - this.aclPermsDb = aclPermsDb; - this.aclNodesDb = aclNodesDb; - this.aclVaultsDb = aclVaultsDb; this.generatePermId = aclUtils.createPermIdGenerator(); this.logger.info(`Started ${this.constructor.name}`); } @@ -96,160 +88,133 @@ class ACL { public async destroy() { this.logger.info(`Destroying ${this.constructor.name}`); - const aclDb = await this.db.level(this.aclDbDomain); - await aclDb.clear(); + await this.db.clear(this.aclDbPath); this.logger.info(`Destroyed ${this.constructor.name}`); } - /** - * Run several operations within the same lock - * This does not ensure atomicity of the underlying database - * Database atomicity still depends on the underlying operation - */ - @ready(new aclErrors.ErrorACLNotRunning()) - public async transaction(f: (acl: ACL) => Promise): Promise { - const release = await this.lock.acquire(); - try { - return await f(this); - } finally { - release(); - } - } - - /** - * Transaction wrapper that will not lock if the operation was executed - * within a transaction context - */ @ready(new aclErrors.ErrorACLNotRunning()) - public async _transaction(f: () => Promise): Promise { - if (this.lock.isLocked()) { - return await f(); - } else { - return await this.transaction(f); - } + public async withTransactionF( + f: (tran: DBTransaction) => Promise, + ): Promise { + return withF([this.db.transaction()], ([tran]) => f(tran)); } @ready(new aclErrors.ErrorACLNotRunning()) public async sameNodePerm( nodeId1: NodeId, nodeId2: NodeId, + tran?: DBTransaction, ): Promise { - return await this._transaction(async () => { - const permId1 = await this.db.get( - this.aclNodesDbDomain, - nodeId1.toBuffer(), - true, + if (tran == null) { + return this.withTransactionF(async (tran) => + this.sameNodePerm(nodeId1, nodeId2, tran), ); - const permId2 = await this.db.get( - this.aclNodesDbDomain, - nodeId2.toBuffer(), - true, + } + const permId1 = await tran.get( + [...this.aclNodesDbPath, nodeId1.toBuffer()], + true, + ); + const permId2 = await tran.get( + [...this.aclNodesDbPath, nodeId2.toBuffer()], + true, + ); + if (permId1 != null && permId2 != null) { + return IdInternal.fromBuffer(permId1).equals( + IdInternal.fromBuffer(permId2), ); - if (permId1 != null && permId2 != null && permId1 === permId2) { - return true; - } - return false; - }); + } + return false; } @ready(new aclErrors.ErrorACLNotRunning()) - public async getNodePerms(): Promise>> { - return await this._transaction(async () => { - const permIds: Record< - PermissionIdString, - Record - > = {}; - for await (const o of this.aclNodesDb.createReadStream()) { - const nodeId = IdInternal.fromBuffer((o as any).key); - const data = (o as any).value as Buffer; - const permId = IdInternal.fromBuffer( - await this.db.deserializeDecrypt(data, true), - ); - let nodePerm: Record; - if (permId in permIds) { - nodePerm = permIds[permId]; - // Get the first existing perm object - let perm: Permission; - for (const nodeId_ in nodePerm) { - perm = nodePerm[nodeId_]; - break; - } - // All perm objects are shared - nodePerm[nodeId] = perm!; - } else { - const permRef = (await this.db.get( - this.aclPermsDbDomain, - permId.toBuffer(), - )) as Ref; - nodePerm = { [nodeId]: permRef.object }; - permIds[permId] = nodePerm; + public async getNodePerms( + tran?: DBTransaction, + ): Promise>> { + if (tran == null) { + return this.withTransactionF(async (tran) => this.getNodePerms(tran)); + } + const permIds: Record> = {}; + for await (const [keyPath, value] of tran.iterator(undefined, [ + ...this.aclNodesDbPath, + ])) { + const key = keyPath[0] as Buffer; + const nodeId = IdInternal.fromBuffer(key); + const permId = IdInternal.fromBuffer(value); + let nodePerm: Record; + if (permId in permIds) { + nodePerm = permIds[permId]; + // Get the first existing perm object + let perm: Permission; + for (const nodeId_ in nodePerm) { + perm = nodePerm[nodeId_]; + break; } + // All perm objects are shared + nodePerm[nodeId] = perm!; + } else { + const permRef = (await tran.get( + [...this.aclPermsDbPath, permId.toBuffer()], + false, + )) as Ref; + nodePerm = { [nodeId]: permRef.object }; + permIds[permId] = nodePerm; } - const nodePerms_: Array> = []; - for (const permId in permIds) { - nodePerms_.push(permIds[permId]); - } - return nodePerms_; - }); + } + const nodePerms_: Array> = []; + for (const permId in permIds) { + nodePerms_.push(permIds[permId]); + } + return nodePerms_; } @ready(new aclErrors.ErrorACLNotRunning()) - public async getVaultPerms(): Promise< - Record> - > { - return await this._transaction(async () => { - const vaultPerms: Record> = {}; - const ops: Array = []; - for await (const o of this.aclVaultsDb.createReadStream()) { - const vaultIdBuffer = (o as any).key as Buffer; - const vaultId = IdInternal.fromBuffer(vaultIdBuffer); - const data = (o as any).value as Buffer; - const nodeIds = await this.db.deserializeDecrypt>( - data, - false, + public async getVaultPerms( + tran?: DBTransaction, + ): Promise>> { + if (tran == null) { + return this.withTransactionF(async (tran) => this.getVaultPerms(tran)); + } + const vaultPerms: Record> = {}; + for await (const [keyPath, nodeIds] of tran.iterator>( + { valueAsBuffer: false }, + [...this.aclVaultsDbPath], + )) { + const key = keyPath[0] as Buffer; + const vaultId = IdInternal.fromBuffer(key); + const nodePerm: Record = {}; + const nodeIdsGc: Set = new Set(); + for (const nodeIdString in nodeIds) { + const nodeId: NodeId = IdInternal.fromString(nodeIdString); + const permId = await tran.get( + [...this.aclNodesDbPath, nodeId.toBuffer()], + true, ); - const nodePerm: Record = {}; - const nodeIdsGc: Set = new Set(); - for (const nodeIdString in nodeIds) { - const nodeId: NodeId = IdInternal.fromString(nodeIdString); - const permId = await this.db.get( - this.aclNodesDbDomain, - nodeId.toBuffer(), - true, - ); - if (permId == null) { - // Invalid node id - nodeIdsGc.add(nodeId); - continue; - } - const permRef = (await this.db.get( - this.aclPermsDbDomain, - permId, - )) as Ref; - if (!(vaultId in permRef.object.vaults)) { - // Vault id is missing from the perm - nodeIdsGc.add(nodeId); - continue; - } - nodePerm[nodeId] = permRef.object; + if (permId == null) { + // Invalid node id + nodeIdsGc.add(nodeId); + continue; + } + const permRef = (await tran.get( + [...this.aclPermsDbPath, permId], + false, + )) as Ref; + if (!(vaultId in permRef.object.vaults)) { + // Vault id is missing from the perm + nodeIdsGc.add(nodeId); + continue; } - if (nodeIdsGc.size > 0) { - // Remove invalid node ids - for (const nodeId of nodeIdsGc) { - delete nodeIds[nodeId]; - } - ops.push({ - type: 'put', - domain: this.aclVaultsDbDomain, - key: vaultId.toBuffer(), - value: nodeIds, - }); + nodePerm[nodeId] = permRef.object; + } + if (nodeIdsGc.size > 0) { + // Remove invalid node ids + for (const nodeId of nodeIdsGc) { + delete nodeIds[nodeId]; } - vaultPerms[vaultId] = nodePerm; + await tran.put([...this.aclVaultsDbPath, vaultId.toBuffer()], nodeIds); } - await this.db.batch(ops); - return vaultPerms; - }); + vaultPerms[vaultId] = nodePerm; + } + return vaultPerms; } /** @@ -257,22 +222,27 @@ class ACL { * Any node id is acceptable */ @ready(new aclErrors.ErrorACLNotRunning()) - public async getNodePerm(nodeId: NodeId): Promise { - return await this._transaction(async () => { - const permId = await this.db.get( - this.aclNodesDbDomain, - nodeId.toBuffer(), - true, + public async getNodePerm( + nodeId: NodeId, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.withTransactionF(async (tran) => + this.getNodePerm(nodeId, tran), ); - if (permId == null) { - return; - } - const perm = (await this.db.get( - this.aclPermsDbDomain, - permId, - )) as Ref; - return perm.object; - }); + } + const permId = await tran.get( + [...this.aclNodesDbPath, nodeId.toBuffer()], + true, + ); + if (permId == null) { + return; + } + const perm = (await tran.get( + [...this.aclPermsDbPath, permId], + false, + )) as Ref; + return perm.object; } /** @@ -283,127 +253,128 @@ class ACL { @ready(new aclErrors.ErrorACLNotRunning()) public async getVaultPerm( vaultId: VaultId, + tran?: DBTransaction, ): Promise> { - return await this._transaction(async () => { - const nodeIds = await this.db.get>( - this.aclVaultsDbDomain, - vaultId.toBuffer(), + if (tran == null) { + return this.withTransactionF(async (tran) => + this.getVaultPerm(vaultId, tran), ); - if (nodeIds == null) { - return {}; + } + const nodeIds = await tran.get>( + [...this.aclVaultsDbPath, vaultId.toBuffer()], + false, + ); + if (nodeIds == null) { + return {}; + } + const perms: Record = {}; + const nodeIdsGc: Set = new Set(); + for (const nodeIdString in nodeIds) { + const nodeId: NodeId = IdInternal.fromString(nodeIdString); + const permId = await tran.get( + [...this.aclNodesDbPath, nodeId.toBuffer()], + true, + ); + if (permId == null) { + // Invalid node id + nodeIdsGc.add(nodeId); + continue; } - const perms: Record = {}; - const nodeIdsGc: Set = new Set(); - for (const nodeIdString in nodeIds) { - const nodeId: NodeId = IdInternal.fromString(nodeIdString); - const permId = await this.db.get( - this.aclNodesDbDomain, - nodeId.toBuffer(), - true, - ); - if (permId == null) { - // Invalid node id - nodeIdsGc.add(nodeId); - continue; - } - const permRef = (await this.db.get( - this.aclPermsDbDomain, - permId, - )) as Ref; - if (!(vaultId in permRef.object.vaults)) { - // Vault id is missing from the perm - nodeIdsGc.add(nodeId); - continue; - } - perms[nodeId] = permRef.object; + const permRef = (await tran.get( + [...this.aclPermsDbPath, permId], + false, + )) as Ref; + if (!(vaultId in permRef.object.vaults)) { + // Vault id is missing from the perm + nodeIdsGc.add(nodeId); + continue; } - if (nodeIdsGc.size > 0) { - // Remove invalid node ids - for (const nodeId of nodeIdsGc) { - delete nodeIds[nodeId]; - } - await this.db.put(this.aclVaultsDbDomain, vaultId.toBuffer(), nodeIds); + perms[nodeId] = permRef.object; + } + if (nodeIdsGc.size > 0) { + // Remove invalid node ids + for (const nodeId of nodeIdsGc) { + delete nodeIds[nodeId]; } - return perms; - }); + await tran.put( + [...this.aclVaultsDbPath, vaultId.toBuffer()], + nodeIds, + false, + ); + } + return perms; } @ready(new aclErrors.ErrorACLNotRunning()) public async setNodeAction( nodeId: NodeId, action: GestaltAction, + tran?: DBTransaction, ): Promise { - return await this._transaction(async () => { - const permId = await this.db.get( - this.aclNodesDbDomain, - nodeId.toBuffer(), - true, + if (tran == null) { + return this.withTransactionF(async (tran) => + this.setNodeAction(nodeId, action, tran), ); - const ops: Array = []; - if (permId == null) { - const permId = await this.generatePermId(); - const permRef = { - count: 1, - object: { - gestalt: { - [action]: null, - }, - vaults: {}, - }, - }; - ops.push( - { - type: 'put', - domain: this.aclPermsDbDomain, - key: permId.toBuffer(), - value: permRef, - }, - { - type: 'put', - domain: this.aclNodesDbDomain, - key: nodeId.toBuffer(), - value: permId.toBuffer(), - raw: true, + } + const permId = await tran.get( + [...this.aclNodesDbPath, nodeId.toBuffer()], + true, + ); + if (permId == null) { + const permId = this.generatePermId(); + const permRef = { + count: 1, + object: { + gestalt: { + [action]: null, }, - ); - } else { - const permRef = (await this.db.get( - this.aclPermsDbDomain, - permId, - )) as Ref; - permRef.object.gestalt[action] = null; - ops.push({ - type: 'put', - domain: this.aclPermsDbDomain, - key: permId, - value: permRef, - }); - } - await this.db.batch(ops); - }); + vaults: {}, + }, + }; + await tran.put( + [...this.aclPermsDbPath, permId.toBuffer()], + permRef, + false, + ); + await tran.put( + [...this.aclNodesDbPath, nodeId.toBuffer()], + permId.toBuffer(), + true, + ); + } else { + const permRef = (await tran.get( + [...this.aclPermsDbPath, permId], + false, + )) as Ref; + permRef.object.gestalt[action] = null; + await tran.put([...this.aclPermsDbPath, permId], permRef, false); + } } @ready(new aclErrors.ErrorACLNotRunning()) public async unsetNodeAction( nodeId: NodeId, action: GestaltAction, + tran?: DBTransaction, ): Promise { - return await this._transaction(async () => { - const permId = await this.db.get( - this.aclNodesDbDomain, - nodeId.toBuffer(), - true, + if (tran == null) { + return this.withTransactionF(async (tran) => + this.unsetNodeAction(nodeId, action, tran), ); - if (permId == null) { - return; - } - const permRef = (await this.db.get( - this.aclPermsDbDomain, - permId, - )) as Ref; - delete permRef.object.gestalt[action]; - await this.db.put(this.aclPermsDbDomain, permId, permRef); - }); + } + const permId = await tran.get( + [...this.aclNodesDbPath, nodeId.toBuffer()], + true, + ); + if (permId == null) { + return; + } + const permRef = (await tran.get( + [...this.aclPermsDbPath, permId], + false, + )) as Ref; + delete permRef.object.gestalt[action]; + await tran.put([...this.aclPermsDbPath, permId], permRef, false); } @ready(new aclErrors.ErrorACLNotRunning()) @@ -411,55 +382,43 @@ class ACL { vaultId: VaultId, nodeId: NodeId, action: VaultAction, + tran?: DBTransaction, ): Promise { - return await this._transaction(async () => { - const nodeIds = - (await this.db.get>( - this.aclVaultsDbDomain, - vaultId.toBuffer(), - )) ?? {}; - const permId = await this.db.get( - this.aclNodesDbDomain, - nodeId.toBuffer(), - true, + if (tran == null) { + return this.withTransactionF(async (tran) => + this.setVaultAction(vaultId, nodeId, action, tran), ); - if (permId == null) { - throw new aclErrors.ErrorACLNodeIdMissing(); - } - nodeIds[nodeId] = null; - const permRef = (await this.db.get( - this.aclPermsDbDomain, - permId, - )) as Ref; - let actions: VaultActions | undefined = permRef.object.vaults[vaultId]; - if (actions == null) { - actions = {}; - permRef.object.vaults[vaultId] = actions; - } - actions[action] = null; - const ops: Array = [ - { - type: 'put', - domain: this.aclPermsDbDomain, - key: permId, - value: permRef, - }, - { - type: 'put', - domain: this.aclNodesDbDomain, - key: nodeId.toBuffer(), - value: permId, - raw: true, - }, - { - type: 'put', - domain: this.aclVaultsDbDomain, - key: vaultId.toBuffer(), - value: nodeIds, - }, - ]; - await this.db.batch(ops); - }); + } + const nodeIds = + (await tran.get>( + [...this.aclVaultsDbPath, vaultId.toBuffer()], + false, + )) ?? {}; + const permId = await tran.get( + [...this.aclNodesDbPath, nodeId.toBuffer()], + true, + ); + if (permId == null) { + throw new aclErrors.ErrorACLNodeIdMissing(); + } + nodeIds[nodeId] = null; + const permRef = (await tran.get( + [...this.aclPermsDbPath, permId], + false, + )) as Ref; + let actions: VaultActions | undefined = permRef.object.vaults[vaultId]; + if (actions == null) { + actions = {}; + permRef.object.vaults[vaultId] = actions; + } + actions[action] = null; + await tran.put([...this.aclPermsDbPath, permId], permRef, false); + await tran.put([...this.aclNodesDbPath, nodeId.toBuffer()], permId, true); + await tran.put( + [...this.aclVaultsDbPath, vaultId.toBuffer()], + nodeIds, + false, + ); } @ready(new aclErrors.ErrorACLNotRunning()) @@ -467,34 +426,37 @@ class ACL { vaultId: VaultId, nodeId: NodeId, action: VaultAction, + tran?: DBTransaction, ): Promise { - await this._transaction(async () => { - const nodeIds = await this.db.get>( - this.aclVaultsDbDomain, - vaultId.toBuffer(), - ); - if (nodeIds == null || !(nodeId in nodeIds)) { - return; - } - const permId = await this.db.get( - this.aclNodesDbDomain, - nodeId.toBuffer(), - true, + if (tran == null) { + return this.withTransactionF(async (tran) => + this.unsetVaultAction(vaultId, nodeId, action, tran), ); - if (permId == null) { - return; - } - const permRef = (await this.db.get( - this.aclPermsDbDomain, - permId, - )) as Ref; - const actions: VaultActions | undefined = permRef.object.vaults[vaultId]; - if (actions == null) { - return; - } - delete actions[action]; - await this.db.put(this.aclPermsDbDomain, permId, permRef); - }); + } + const nodeIds = await tran.get>( + [...this.aclVaultsDbPath, vaultId.toBuffer()], + false, + ); + if (nodeIds == null || !(nodeId in nodeIds)) { + return; + } + const permId = await tran.get( + [...this.aclNodesDbPath, nodeId.toBuffer()], + true, + ); + if (permId == null) { + return; + } + const permRef = (await tran.get( + [...this.aclPermsDbPath, permId], + false, + )) as Ref; + const actions: VaultActions | undefined = permRef.object.vaults[vaultId]; + if (actions == null) { + return; + } + delete actions[action]; + await tran.put([...this.aclPermsDbPath, permId], permRef, false); } /** @@ -506,24 +468,17 @@ class ACL { public async setNodesPerm( nodeIds: Array, perm: Permission, + tran?: DBTransaction, ): Promise { - await this._transaction(async () => { - const ops = await this.setNodesPermOps(nodeIds, perm); - await this.db.batch(ops); - }); - } - - @ready(new aclErrors.ErrorACLNotRunning()) - public async setNodesPermOps( - nodeIds: Array, - perm: Permission, - ): Promise> { - const ops: Array = []; + if (tran == null) { + return this.withTransactionF(async (tran) => + this.setNodesPerm(nodeIds, perm, tran), + ); + } const permIdCounts: Record = {}; for (const nodeId of nodeIds) { - const permIdBuffer = await this.db.get( - this.aclNodesDbDomain, - nodeId.toBuffer(), + const permIdBuffer = await tran.get( + [...this.aclNodesDbPath, nodeId.toBuffer()], true, ); if (permIdBuffer == null) { @@ -534,196 +489,146 @@ class ACL { } for (const permIdString in permIdCounts) { const permId = IdInternal.fromString(permIdString); - const permRef = (await this.db.get( - this.aclPermsDbDomain, - permId.toBuffer(), + const permRef = (await tran.get( + [...this.aclPermsDbPath, permId.toBuffer()], + false, )) as Ref; permRef.count = permRef.count - permIdCounts[permId]; if (permRef.count === 0) { - ops.push({ - type: 'del', - domain: this.aclPermsDbDomain, - key: permId.toBuffer(), - }); + await tran.del([...this.aclPermsDbPath, permId.toBuffer()]); } else { - ops.push({ - type: 'put', - domain: this.aclPermsDbDomain, - key: permId.toBuffer(), - value: permRef, - }); + await tran.put( + [...this.aclPermsDbPath, permId.toBuffer()], + permRef, + false, + ); } } - const permId = await this.generatePermId(); + const permId = this.generatePermId(); const permRef = { count: nodeIds.length, object: perm, }; - ops.push({ - domain: this.aclPermsDbDomain, - type: 'put', - key: permId.toBuffer(), - value: permRef, - }); + await tran.put([...this.aclPermsDbPath, permId.toBuffer()], permRef, false); for (const nodeId of nodeIds) { - ops.push({ - domain: this.aclNodesDbDomain, - type: 'put', - key: nodeId.toBuffer(), - value: permId.toBuffer(), - raw: true, - }); - } - return ops; - } - - @ready(new aclErrors.ErrorACLNotRunning()) - public async setNodePerm(nodeId: NodeId, perm: Permission): Promise { - await this._transaction(async () => { - const ops = await this.setNodePermOps(nodeId, perm); - await this.db.batch(ops); - }); + await tran.put( + [...this.aclNodesDbPath, nodeId.toBuffer()], + permId.toBuffer(), + true, + ); + } } @ready(new aclErrors.ErrorACLNotRunning()) - public async setNodePermOps( + public async setNodePerm( nodeId: NodeId, perm: Permission, - ): Promise> { - const permId = await this.db.get( - this.aclNodesDbDomain, - nodeId.toBuffer(), + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.withTransactionF(async (tran) => + this.setNodePerm(nodeId, perm, tran), + ); + } + const permId = await tran.get( + [...this.aclNodesDbPath, nodeId.toBuffer()], true, ); - const ops: Array = []; if (permId == null) { - const permId = await this.generatePermId(); + const permId = this.generatePermId(); const permRef = { count: 1, object: perm, }; - ops.push( - { - type: 'put', - domain: this.aclPermsDbDomain, - key: permId.toBuffer(), - value: permRef, - }, - { - type: 'put', - domain: this.aclNodesDbDomain, - key: nodeId.toBuffer(), - value: permId.toBuffer(), - raw: true, - }, + await tran.put( + [...this.aclPermsDbPath, permId.toBuffer()], + permRef, + false, + ); + await tran.put( + [...this.aclNodesDbPath, nodeId.toBuffer()], + permId.toBuffer(), + true, ); } else { // The entire gestalt's perm gets replaced, therefore the count stays the same - const permRef = (await this.db.get( - this.aclPermsDbDomain, - permId, + const permRef = (await tran.get( + [...this.aclPermsDbPath, permId], + false, )) as Ref; permRef.object = perm; - ops.push({ - type: 'put', - domain: this.aclPermsDbDomain, - key: permId, - value: permRef, - }); - } - return ops; - } - - @ready(new aclErrors.ErrorACLNotRunning()) - public async unsetNodePerm(nodeId: NodeId): Promise { - await this._transaction(async () => { - const ops = await this.unsetNodePermOps(nodeId); - await this.db.batch(ops); - }); + await tran.put([...this.aclPermsDbPath, permId], permRef, false); + } } @ready(new aclErrors.ErrorACLNotRunning()) - public async unsetNodePermOps(nodeId: NodeId): Promise> { - const permId = await this.db.get( - this.aclNodesDbDomain, - nodeId.toBuffer(), + public async unsetNodePerm( + nodeId: NodeId, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.withTransactionF(async (tran) => + this.unsetNodePerm(nodeId, tran), + ); + } + const permId = await tran.get( + [...this.aclNodesDbPath, nodeId.toBuffer()], true, ); if (permId == null) { - return []; + return; } - const ops: Array = []; - const permRef = (await this.db.get( - this.aclPermsDbDomain, - permId, + const permRef = (await tran.get( + [...this.aclPermsDbPath, permId], + false, )) as Ref; const count = --permRef.count; if (count === 0) { - ops.push({ - type: 'del', - domain: this.aclPermsDbDomain, - key: permId, - }); + await tran.del([...this.aclPermsDbPath, permId]); } else { - ops.push({ - type: 'put', - domain: this.aclPermsDbDomain, - key: permId, - value: permRef, - }); - } - ops.push({ - type: 'del', - domain: this.aclNodesDbDomain, - key: nodeId.toBuffer(), - }); + await tran.put([...this.aclPermsDbPath, permId], permRef, false); + } + await tran.del([...this.aclNodesDbPath, nodeId.toBuffer()]); // We do not remove the node id from the vaults // they can be removed later upon inspection - return ops; } @ready(new aclErrors.ErrorACLNotRunning()) - public async unsetVaultPerms(vaultId: VaultId): Promise { - await this._transaction(async () => { - const nodeIds = await this.db.get>( - this.aclVaultsDbDomain, - vaultId.toBuffer(), + public async unsetVaultPerms( + vaultId: VaultId, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.withTransactionF(async (tran) => + this.unsetVaultPerms(vaultId, tran), ); - if (nodeIds == null) { - return; - } - const ops: Array = []; - for (const nodeIdString in nodeIds) { - const nodeId: NodeId = IdInternal.fromString(nodeIdString); - const permId = await this.db.get( - this.aclNodesDbDomain, - nodeId.toBuffer(), - true, - ); - // Skip if the nodeId doesn't exist - // this means that it previously been removed - if (permId == null) { - continue; - } - const perm = (await this.db.get( - this.aclPermsDbDomain, - permId, - )) as Ref; - delete perm.object.vaults[vaultId]; - ops.push({ - type: 'put', - domain: this.aclPermsDbDomain, - key: permId, - value: perm, - }); + } + const nodeIds = await tran.get>( + [...this.aclVaultsDbPath, vaultId.toBuffer()], + false, + ); + if (nodeIds == null) { + return; + } + for (const nodeIdString in nodeIds) { + const nodeId: NodeId = IdInternal.fromString(nodeIdString); + const permId = await tran.get( + [...this.aclNodesDbPath, nodeId.toBuffer()], + true, + ); + // Skip if the nodeId doesn't exist + // this means that it previously been removed + if (permId == null) { + continue; } - ops.push({ - type: 'del', - domain: this.aclVaultsDbDomain, - key: vaultId.toBuffer(), - }); - await this.db.batch(ops); - }); + const perm = (await tran.get( + [...this.aclPermsDbPath, permId], + false, + )) as Ref; + delete perm.object.vaults[vaultId]; + await tran.put([...this.aclPermsDbPath, permId], perm, false); + } + await tran.del([...this.aclVaultsDbPath, vaultId.toBuffer()]); } @ready(new aclErrors.ErrorACLNotRunning()) @@ -731,40 +636,31 @@ class ACL { nodeId: NodeId, nodeIdsJoin: Array, perm?: Permission, + tran?: DBTransaction, ): Promise { - await this._transaction(async () => { - const ops = await this.joinNodePermOps(nodeId, nodeIdsJoin, perm); - await this.db.batch(ops); - }); - } - - @ready(new aclErrors.ErrorACLNotRunning()) - public async joinNodePermOps( - nodeId: NodeId, - nodeIdsJoin: Array, - perm?: Permission, - ): Promise> { - const permId = await this.db.get( - this.aclNodesDbDomain, - nodeId.toBuffer(), + if (tran == null) { + return this.withTransactionF(async (tran) => + this.joinNodePerm(nodeId, nodeIdsJoin, perm, tran), + ); + } + const permId = await tran.get( + [...this.aclNodesDbPath, nodeId.toBuffer()], true, ); if (permId == null) { throw new aclErrors.ErrorACLNodeIdMissing(); } - const ops: Array = []; - const permRef = (await this.db.get( - this.aclPermsDbDomain, - permId, + const permRef = (await tran.get( + [...this.aclPermsDbPath, permId], + false, )) as Ref; // Optionally replace the permission record for the target if (perm != null) { permRef.object = perm; } for (const nodeIdJoin of nodeIdsJoin) { - const permIdJoin = await this.db.get( - this.aclNodesDbDomain, - nodeIdJoin.toBuffer(), + const permIdJoin = await tran.get( + [...this.aclNodesDbPath, nodeIdJoin.toBuffer()], true, ); if (permIdJoin === permId) { @@ -772,73 +668,49 @@ class ACL { } ++permRef.count; if (permIdJoin != null) { - const permJoin = (await this.db.get( - this.aclPermsDbDomain, - permIdJoin, + const permJoin = (await tran.get( + [...this.aclPermsDbPath, permIdJoin], + false, )) as Ref; --permJoin.count; if (permJoin.count === 0) { - ops.push({ - type: 'del', - domain: this.aclPermsDbDomain, - key: permIdJoin, - }); + await tran.del([...this.aclPermsDbPath, permIdJoin]); } else { - ops.push({ - type: 'put', - domain: this.aclPermsDbDomain, - key: permIdJoin, - value: permJoin, - }); + await tran.put([...this.aclPermsDbPath, permIdJoin], permJoin, false); } } - ops.push({ - type: 'put', - domain: this.aclNodesDbDomain, - key: nodeIdJoin.toBuffer(), - value: permId, - raw: true, - }); - } - ops.push({ - type: 'put', - domain: this.aclPermsDbDomain, - key: permId, - value: permRef, - }); - return ops; + await tran.put( + [...this.aclNodesDbPath, nodeIdJoin.toBuffer()], + permId, + true, + ); + } + await tran.put([...this.aclPermsDbPath, permId], permRef, false); } @ready(new aclErrors.ErrorACLNotRunning()) public async joinVaultPerms( vaultId: VaultId, vaultIdsJoin: Array, + tran?: DBTransaction, ): Promise { - await this._transaction(async () => { - const ops = await this.joinVaultPermsOps(vaultId, vaultIdsJoin); - await this.db.batch(ops); - }); - } - - @ready(new aclErrors.ErrorACLNotRunning()) - private async joinVaultPermsOps( - vaultId: VaultId, - vaultIdsJoin: Array, - ): Promise> { - const nodeIds = await this.db.get>( - this.aclVaultsDbDomain, - vaultId.toBuffer(), + if (tran == null) { + return this.withTransactionF(async (tran) => + this.joinVaultPerms(vaultId, vaultIdsJoin, tran), + ); + } + const nodeIds = await tran.get>( + [...this.aclVaultsDbPath, vaultId.toBuffer()], + false, ); if (nodeIds == null) { throw new aclErrors.ErrorACLVaultIdMissing(); } - const ops: Array = []; const nodeIdsGc: Set = new Set(); for (const nodeIdString in nodeIds) { const nodeId: NodeId = IdInternal.fromString(nodeIdString); - const permId = await this.db.get( - this.aclNodesDbDomain, - nodeId.toBuffer(), + const permId = await tran.get( + [...this.aclNodesDbPath, nodeId.toBuffer()], true, ); if (permId == null) { @@ -846,9 +718,9 @@ class ACL { nodeIdsGc.add(nodeId); continue; } - const permRef = (await this.db.get( - this.aclPermsDbDomain, - permId, + const permRef = (await tran.get( + [...this.aclPermsDbPath, permId], + false, )) as Ref; if (!(vaultId in permRef.object.vaults)) { // Vault id is missing from the perm @@ -860,34 +732,26 @@ class ACL { for (const vaultIdJoin of vaultIdsJoin) { permRef.object.vaults[vaultIdJoin] = vaultActions; } - ops.push({ - type: 'put', - domain: this.aclPermsDbDomain, - key: permId, - value: permRef, - }); + await tran.put([...this.aclPermsDbPath, permId], permRef, false); } for (const vaultIdJoin of vaultIdsJoin) { - ops.push({ - type: 'put', - domain: this.aclVaultsDbDomain, - key: vaultIdJoin.toBuffer(), - value: nodeIds, - }); + await tran.put( + [...this.aclVaultsDbPath, vaultIdJoin.toBuffer()], + nodeIds, + false, + ); } if (nodeIdsGc.size > 0) { // Remove invalid node ids for (const nodeId of nodeIdsGc) { delete nodeIds[nodeId]; } - ops.push({ - type: 'put', - domain: this.aclVaultsDbDomain, - key: vaultId.toBuffer(), - value: nodeIds, - }); - } - return ops; + await tran.put( + [...this.aclVaultsDbPath, vaultId.toBuffer()], + nodeIds, + false, + ); + } } } diff --git a/src/acl/errors.ts b/src/acl/errors.ts index f22044397..508513759 100644 --- a/src/acl/errors.ts +++ b/src/acl/errors.ts @@ -1,18 +1,36 @@ -import { ErrorPolykey } from '../errors'; - -class ErrorACL extends ErrorPolykey {} - -class ErrorACLRunning extends ErrorACL {} - -class ErrorACLNotRunning extends ErrorACL {} - -class ErrorACLDestroyed extends ErrorACL {} - -class ErrorACLNodeIdMissing extends ErrorACL {} - -class ErrorACLVaultIdMissing extends ErrorACL {} - -class ErrorACLNodeIdExists extends ErrorACL {} +import { ErrorPolykey, sysexits } from '../errors'; + +class ErrorACL extends ErrorPolykey {} + +class ErrorACLRunning extends ErrorACL { + static description = 'ACL is running'; + exitCode = sysexits.USAGE; +} + +class ErrorACLNotRunning extends ErrorACL { + static description = 'ACL is not running'; + exitCode = sysexits.USAGE; +} + +class ErrorACLDestroyed extends ErrorACL { + static description = 'ACL is destroyed'; + exitCode = sysexits.USAGE; +} + +class ErrorACLNodeIdMissing extends ErrorACL { + static description = 'Could not find NodeId'; + exitCode = sysexits.NOUSER; +} + +class ErrorACLVaultIdMissing extends ErrorACL { + static description = 'Could not find VaultId'; + exitCode = sysexits.DATAERR; +} + +class ErrorACLNodeIdExists extends ErrorACL { + static description = 'NodeId already exists'; + exitCode = sysexits.DATAERR; +} export { ErrorACL, diff --git a/src/agent/GRPCClientAgent.ts b/src/agent/GRPCClientAgent.ts index 4190f66b6..db94979db 100644 --- a/src/agent/GRPCClientAgent.ts +++ b/src/agent/GRPCClientAgent.ts @@ -10,6 +10,7 @@ import type * as utilsPB from '../proto/js/polykey/v1/utils/utils_pb'; import type * as vaultsPB from '../proto/js/polykey/v1/vaults/vaults_pb'; import type * as nodesPB from '../proto/js/polykey/v1/nodes/nodes_pb'; import type * as notificationsPB from '../proto/js/polykey/v1/notifications/notifications_pb'; +import type { Timer } from '../types'; import Logger from '@matrixai/logger'; import { CreateDestroy, ready } from '@matrixai/async-init/dist/CreateDestroy'; import * as agentErrors from './errors'; @@ -32,7 +33,7 @@ class GRPCClientAgent extends GRPCClient { port, tlsConfig, proxyConfig, - timeout = Infinity, + timer, destroyCallback = async () => {}, logger = new Logger(this.name), }: { @@ -41,7 +42,7 @@ class GRPCClientAgent extends GRPCClient { port: Port; tlsConfig?: Partial; proxyConfig?: ProxyConfig; - timeout?: number; + timer?: Timer; destroyCallback?: () => Promise; logger?: Logger; }): Promise { @@ -53,7 +54,7 @@ class GRPCClientAgent extends GRPCClient { port, tlsConfig, proxyConfig, - timeout, + timer, logger, }); const grpcClientAgent = new GRPCClientAgent({ @@ -79,6 +80,12 @@ class GRPCClientAgent extends GRPCClient { public echo(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.echo.name, + }, this.client.echo, )(...args); } @@ -92,6 +99,12 @@ class GRPCClientAgent extends GRPCClient { > { return grpcUtils.promisifyReadableStreamCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsGitInfoGet.name, + }, this.client.vaultsGitInfoGet, )(...args); } @@ -106,6 +119,12 @@ class GRPCClientAgent extends GRPCClient { > { return grpcUtils.promisifyDuplexStreamCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsGitPackGet.name, + }, this.client.vaultsGitPackGet, )(...args); } @@ -119,6 +138,12 @@ class GRPCClientAgent extends GRPCClient { > { return grpcUtils.promisifyReadableStreamCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsScan.name, + }, this.client.vaultsScan, )(...args); } @@ -127,6 +152,12 @@ class GRPCClientAgent extends GRPCClient { public nodesClosestLocalNodesGet(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.nodesClosestLocalNodesGet.name, + }, this.client.nodesClosestLocalNodesGet, )(...args); } @@ -135,6 +166,12 @@ class GRPCClientAgent extends GRPCClient { public nodesClaimsGet(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.nodesClaimsGet.name, + }, this.client.nodesClaimsGet, )(...args); } @@ -143,6 +180,12 @@ class GRPCClientAgent extends GRPCClient { public nodesChainDataGet(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.nodesChainDataGet.name, + }, this.client.nodesChainDataGet, )(...args); } @@ -151,6 +194,12 @@ class GRPCClientAgent extends GRPCClient { public nodesHolePunchMessageSend(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.nodesHolePunchMessageSend.name, + }, this.client.nodesHolePunchMessageSend, )(...args); } @@ -159,6 +208,12 @@ class GRPCClientAgent extends GRPCClient { public notificationsSend(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.notificationsSend.name, + }, this.client.notificationsSend, )(...args); } @@ -176,6 +231,12 @@ class GRPCClientAgent extends GRPCClient { nodesPB.CrossSign >( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.nodesCrossSignClaim.name, + }, this.client.nodesCrossSignClaim, )(...args); } diff --git a/src/agent/errors.ts b/src/agent/errors.ts index e4db4293c..b0460055c 100644 --- a/src/agent/errors.ts +++ b/src/agent/errors.ts @@ -1,15 +1,24 @@ import { ErrorPolykey, sysexits } from '../errors'; -class ErrorAgent extends ErrorPolykey {} +class ErrorAgent extends ErrorPolykey {} -class ErrorAgentRunning extends ErrorPolykey {} +class ErrorAgentRunning extends ErrorPolykey { + static description = 'Agent Client is running'; + exitCode = sysexits.USAGE; +} -class ErrorAgentClientNotStarted extends ErrorAgent {} +class ErrorAgentClientNotStarted extends ErrorAgent { + static description = 'Agent Client is not started'; + exitCode = sysexits.USAGE; +} -class ErrorAgentClientDestroyed extends ErrorAgent {} +class ErrorAgentClientDestroyed extends ErrorAgent { + static description = 'Agent Client is destroyed'; + exitCode = sysexits.USAGE; +} -class ErrorConnectionInfoMissing extends ErrorAgent { - description = 'Vault already exists'; +class ErrorConnectionInfoMissing extends ErrorAgent { + static description = 'Vault already exists'; exitCode = sysexits.UNAVAILABLE; } diff --git a/src/agent/service/index.ts b/src/agent/service/index.ts index aa96bfd2e..2e51b699e 100644 --- a/src/agent/service/index.ts +++ b/src/agent/service/index.ts @@ -1,16 +1,16 @@ -import type { KeyManager } from '../../keys'; -import type { VaultManager } from '../../vaults'; -import type { - NodeGraph, - NodeManager, - NodeConnectionManager, -} from '../../nodes'; -import type { NotificationsManager } from '../../notifications'; -import type { Sigchain } from '../../sigchain'; -import type { ACL } from '../../acl'; -import type { GestaltGraph } from '../../gestalts'; +import type { DB } from '@matrixai/db'; +import type KeyManager from '../../keys/KeyManager'; +import type VaultManager from '../../vaults/VaultManager'; +import type NodeGraph from '../../nodes/NodeGraph'; +import type NodeManager from '../../nodes/NodeManager'; +import type NodeConnectionManager from '../../nodes/NodeConnectionManager'; +import type NotificationsManager from '../../notifications/NotificationsManager'; +import type Sigchain from '../../sigchain/Sigchain'; +import type ACL from '../../acl/ACL'; +import type GestaltGraph from '../../gestalts/GestaltGraph'; import type { IAgentServiceServer } from '../../proto/js/polykey/v1/agent_service_grpc_pb'; import type Proxy from '../../network/Proxy'; +import Logger from '@matrixai/logger'; import echo from './echo'; import nodesChainDataGet from './nodesChainDataGet'; import nodesClaimsGet from './nodesClaimsGet'; @@ -24,7 +24,13 @@ import vaultsScan from './vaultsScan'; import { AgentServiceService } from '../../proto/js/polykey/v1/agent_service_grpc_pb'; import * as agentUtils from '../utils'; -function createService(container: { +function createService({ + proxy, + db, + logger = new Logger('GRPCClientAgentService'), + ...containerRest +}: { + db: DB; keyManager: KeyManager; vaultManager: VaultManager; nodeConnectionManager: NodeConnectionManager; @@ -35,23 +41,26 @@ function createService(container: { acl: ACL; gestaltGraph: GestaltGraph; proxy: Proxy; + logger?: Logger; }): IAgentServiceServer { - const connectionInfoGet = agentUtils.connectionInfoGetter(container.proxy); - const container_ = { - ...container, + const connectionInfoGet = agentUtils.connectionInfoGetter(proxy); + const container = { + ...containerRest, + db, + logger, connectionInfoGet: connectionInfoGet, }; const service: IAgentServiceServer = { - echo: echo(container_), - nodesChainDataGet: nodesChainDataGet(container_), - nodesClaimsGet: nodesClaimsGet(container_), - nodesClosestLocalNodesGet: nodesClosestLocalNodesGet(container_), - nodesCrossSignClaim: nodesCrossSignClaim(container_), - nodesHolePunchMessageSend: nodesHolePunchMessageSend(container_), - notificationsSend: notificationsSend(container_), - vaultsGitInfoGet: vaultsGitInfoGet(container_), - vaultsGitPackGet: vaultsGitPackGet(container_), - vaultsScan: vaultsScan(container_), + echo: echo(container), + nodesChainDataGet: nodesChainDataGet(container), + nodesClaimsGet: nodesClaimsGet(container), + nodesClosestLocalNodesGet: nodesClosestLocalNodesGet(container), + nodesCrossSignClaim: nodesCrossSignClaim(container), + nodesHolePunchMessageSend: nodesHolePunchMessageSend(container), + notificationsSend: notificationsSend(container), + vaultsGitInfoGet: vaultsGitInfoGet(container), + vaultsGitPackGet: vaultsGitPackGet(container), + vaultsScan: vaultsScan(container), }; return service; } diff --git a/src/agent/service/nodesChainDataGet.ts b/src/agent/service/nodesChainDataGet.ts index 3ed37b99f..10175c706 100644 --- a/src/agent/service/nodesChainDataGet.ts +++ b/src/agent/service/nodesChainDataGet.ts @@ -1,21 +1,34 @@ import type * as grpc from '@grpc/grpc-js'; -import type { Sigchain } from '../../sigchain'; +import type { DB } from '@matrixai/db'; +import type Sigchain from '../../sigchain/Sigchain'; import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; import type { ClaimIdEncoded } from '../../claims/types'; -import { utils as grpcUtils } from '../../grpc'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; import * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; +import * as agentUtils from '../utils'; /** * Retrieves the ChainDataEncoded of this node. */ -function nodesChainDataGet({ sigchain }: { sigchain: Sigchain }) { +function nodesChainDataGet({ + sigchain, + db, + logger, +}: { + sigchain: Sigchain; + db: DB; + logger: Logger; +}) { return async ( call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { try { const response = new nodesPB.ChainData(); - const chainData = await sigchain.getChainData(); + const chainData = await db.withTransactionF(async (tran) => + sigchain.getChainData(tran), + ); // Iterate through each claim in the chain, and serialize for transport let claimIdEncoded: ClaimIdEncoded; for (claimIdEncoded in chainData) { @@ -37,7 +50,9 @@ function nodesChainDataGet({ sigchain }: { sigchain: Sigchain }) { callback(null, response); return; } catch (e) { - callback(grpcUtils.fromError(e)); + callback(grpcUtils.fromError(e, true)); + !agentUtils.isAgentClientError(e) && + logger.error(`${nodesChainDataGet.name}:${e}`); return; } }; diff --git a/src/agent/service/nodesClosestLocalNodesGet.ts b/src/agent/service/nodesClosestLocalNodesGet.ts index 559337c9d..4c987667d 100644 --- a/src/agent/service/nodesClosestLocalNodesGet.ts +++ b/src/agent/service/nodesClosestLocalNodesGet.ts @@ -1,20 +1,28 @@ import type * as grpc from '@grpc/grpc-js'; -import type { NodeConnectionManager } from '../../nodes'; +import type { NodeGraph } from '../../nodes'; +import type { DB } from '@matrixai/db'; import type { NodeId } from '../../nodes/types'; -import { utils as grpcUtils } from '../../grpc'; -import { utils as nodesUtils } from '../../nodes'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; +import * as nodesUtils from '../../nodes/utils'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; import { matchSync } from '../../utils'; import * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; +import * as agentUtils from '../utils'; /** * Retrieves the local nodes (i.e. from the current node) that are closest * to some provided node ID. */ function nodesClosestLocalNodesGet({ - nodeConnectionManager, + nodeGraph, + db, + logger, }: { - nodeConnectionManager: NodeConnectionManager; + nodeGraph: NodeGraph; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -38,22 +46,25 @@ function nodesClosestLocalNodesGet({ }, ); // Get all local nodes that are closest to the target node from the request - const closestNodes = await nodeConnectionManager.getClosestLocalNodes( - nodeId, + const closestNodes = await db.withTransactionF( + async (tran) => + await nodeGraph.getClosestNodes(nodeId, undefined, tran), ); - for (const node of closestNodes) { + for (const [nodeId, nodeData] of closestNodes) { const addressMessage = new nodesPB.Address(); - addressMessage.setHost(node.address.host); - addressMessage.setPort(node.address.port); + addressMessage.setHost(nodeData.address.host); + addressMessage.setPort(nodeData.address.port); // Add the node to the response's map (mapping of node ID -> node address) response .getNodeTableMap() - .set(nodesUtils.encodeNodeId(node.id), addressMessage); + .set(nodesUtils.encodeNodeId(nodeId), addressMessage); } callback(null, response); return; } catch (e) { - callback(grpcUtils.fromError(e)); + callback(grpcUtils.fromError(e, true)); + !agentUtils.isAgentClientError(e) && + logger.error(`${nodesClosestLocalNodesGet.name}:${e}`); return; } }; diff --git a/src/agent/service/nodesCrossSignClaim.ts b/src/agent/service/nodesCrossSignClaim.ts index 907494512..2c9793ba0 100644 --- a/src/agent/service/nodesCrossSignClaim.ts +++ b/src/agent/service/nodesCrossSignClaim.ts @@ -1,33 +1,45 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { ClaimEncoded, ClaimIntermediary } from '../../claims/types'; -import type { NodeManager } from '../../nodes'; +import type NodeManager from '../../nodes/NodeManager'; import type { NodeId } from '../../nodes/types'; -import type { Sigchain } from '../../sigchain'; -import type { KeyManager } from '../../keys'; +import type Sigchain from '../../sigchain/Sigchain'; +import type KeyManager from '../../keys/KeyManager'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import { utils as grpcUtils } from '../../grpc'; -import { utils as claimsUtils, errors as claimsErrors } from '../../claims'; -import { utils as nodesUtils } from '../../nodes'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; +import * as claimsUtils from '../../claims/utils'; +import * as claimsErrors from '../../claims/errors'; +import * as nodesUtils from '../../nodes/utils'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; import { matchSync } from '../../utils'; +import * as agentUtils from '../utils'; function nodesCrossSignClaim({ + db, keyManager, nodeManager, sigchain, + logger, }: { + db: DB; keyManager: KeyManager; nodeManager: NodeManager; sigchain: Sigchain; + logger: Logger; }) { return async ( call: grpc.ServerDuplexStream, ) => { - // TODO: Move all "await genClaims.throw" to a final catch(). Wrap this - // entire thing in a try block. And re-throw whatever error is caught - const genClaims = grpcUtils.generatorDuplex(call); + const nodeId = keyManager.getNodeId(); + const genClaims = grpcUtils.generatorDuplex( + call, + { nodeId, command: nodesCrossSignClaim.name }, + true, + ); try { - await sigchain.transaction(async (sigchain) => { + await db.withTransactionF(async (tran) => { const readStatus = await genClaims.read(); // If nothing to read, end and destroy if (readStatus.done) { @@ -42,7 +54,6 @@ function nodesCrossSignClaim({ if (!intermediarySignature) { throw new claimsErrors.ErrorUndefinedSignature(); } - // 3. X --> responds with double signing the Y signed claim, and also --> Y // bundles it with its own signed claim (intermediate) // Reconstruct the claim to verify its signature @@ -99,11 +110,14 @@ function nodesCrossSignClaim({ signeeNodeId: nodesUtils.encodeNodeId(keyManager.getNodeId()), }); // Then create your own intermediary node claim (from X -> Y) - const singlySignedClaim = await sigchain.createIntermediaryClaim({ - type: 'node', - node1: nodesUtils.encodeNodeId(keyManager.getNodeId()), - node2: payloadData.node1, - }); + const singlySignedClaim = await sigchain.createIntermediaryClaim( + { + type: 'node', + node1: nodesUtils.encodeNodeId(keyManager.getNodeId()), + node2: payloadData.node1, + }, + tran, + ); // Should never be reached, but just for type safety if (!doublySignedClaim.payload || !singlySignedClaim.payload) { throw new claimsErrors.ErrorClaimsUndefinedClaimPayload(); @@ -149,20 +163,23 @@ function nodesCrossSignClaim({ senderPublicKey, )); if (!verifiedDoubly) { - await genClaims.throw( - new claimsErrors.ErrorDoublySignedClaimVerificationFailed(), - ); + throw new claimsErrors.ErrorDoublySignedClaimVerificationFailed(); } // If verified, then we can safely add to our sigchain - await sigchain.addExistingClaim(constructedDoublySignedClaim); + await sigchain.addExistingClaim(constructedDoublySignedClaim, tran); // Close the stream await genClaims.next(null); return; }); } catch (e) { await genClaims.throw(e); - // TODO: Handle the exception on this server - throw e? - // throw e; + !agentUtils.isAgentClientError(e, [ + claimsErrors.ErrorEmptyStream, + claimsErrors.ErrorUndefinedSinglySignedClaim, + claimsErrors.ErrorUndefinedSignature, + claimsErrors.ErrorNodesClaimType, + claimsErrors.ErrorUndefinedDoublySignedClaim, + ]) && logger.error(`${nodesCrossSignClaim.name}:${e}`); return; } }; diff --git a/src/agent/service/nodesHolePunchMessageSend.ts b/src/agent/service/nodesHolePunchMessageSend.ts index d524e9f24..c610d7428 100644 --- a/src/agent/service/nodesHolePunchMessageSend.ts +++ b/src/agent/service/nodesHolePunchMessageSend.ts @@ -1,22 +1,31 @@ import type * as grpc from '@grpc/grpc-js'; -import type { NodeManager, NodeConnectionManager } from '../../nodes'; +import type { DB } from '@matrixai/db'; +import type NodeManager from '../../nodes/NodeManager'; +import type NodeConnectionManager from '../../nodes/NodeConnectionManager'; import type KeyManager from '../../keys/KeyManager'; import type { NodeId } from '../../nodes/types'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; +import type Logger from '@matrixai/logger'; import * as networkUtils from '../../network/utils'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; +import * as grpcUtils from '../../grpc/utils'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as agentUtils from '../utils'; function nodesHolePunchMessageSend({ keyManager, nodeManager, nodeConnectionManager, + db, + logger, }: { keyManager: KeyManager; nodeManager: NodeManager; nodeConnectionManager: NodeConnectionManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -49,20 +58,24 @@ function nodesHolePunchMessageSend({ // Firstly, check if this node is the desired node // If so, then we want to make this node start sending hole punching packets // back to the source node. - if (keyManager.getNodeId().equals(targetId)) { - const [host, port] = networkUtils.parseAddress( - call.request.getProxyAddress(), - ); - await nodeConnectionManager.holePunchReverse(host, port); - // Otherwise, find if node in table - // If so, ask the nodeManager to relay to the node - } else if (await nodeManager.knowsNode(sourceId)) { - await nodeConnectionManager.relayHolePunchMessage(call.request); - } + await db.withTransactionF(async (tran) => { + if (keyManager.getNodeId().equals(targetId)) { + const [host, port] = networkUtils.parseAddress( + call.request.getProxyAddress(), + ); + await nodeConnectionManager.holePunchReverse(host, port); + // Otherwise, find if node in table + // If so, ask the nodeManager to relay to the node + } else if (await nodeManager.knowsNode(sourceId, tran)) { + await nodeConnectionManager.relayHolePunchMessage(call.request); + } + }); callback(null, response); return; } catch (e) { - callback(grpcUtils.fromError(e)); + callback(grpcUtils.fromError(e, true)); + !agentUtils.isAgentClientError(e) && + logger.error(`${nodesHolePunchMessageSend.name}:${e}`); return; } }; diff --git a/src/agent/service/notificationsSend.ts b/src/agent/service/notificationsSend.ts index cf2b589ea..cd1b43c76 100644 --- a/src/agent/service/notificationsSend.ts +++ b/src/agent/service/notificationsSend.ts @@ -1,14 +1,22 @@ import type * as grpc from '@grpc/grpc-js'; -import type { NotificationsManager } from '../../notifications'; +import type NotificationsManager from '../../notifications/NotificationsManager'; import type * as notificationsPB from '../../proto/js/polykey/v1/notifications/notifications_pb'; -import { utils as grpcUtils } from '../../grpc'; -import { utils as notificationsUtils } from '../../notifications'; +import type Logger from '@matrixai/logger'; +import type { DB } from '@matrixai/db'; +import * as grpcUtils from '../../grpc/utils'; +import * as notificationsUtils from '../../notifications/utils'; +import * as notificationsErrors from '../../notifications/errors'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as agentUtils from '../utils'; function notificationsSend({ notificationsManager, + db, + logger, }: { notificationsManager: NotificationsManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall< @@ -18,14 +26,22 @@ function notificationsSend({ callback: grpc.sendUnaryData, ): Promise => { try { - const response = new utilsPB.EmptyMessage(); const jwt = call.request.getContent(); const notification = await notificationsUtils.verifyAndDecodeNotif(jwt); - await notificationsManager.receiveNotification(notification); + await db.withTransactionF(async (tran) => { + await notificationsManager.receiveNotification(notification, tran); + }); + const response = new utilsPB.EmptyMessage(); callback(null, response); return; } catch (e) { - callback(grpcUtils.fromError(e)); + callback(grpcUtils.fromError(e, true)); + !agentUtils.isAgentClientError(e, [ + notificationsErrors.ErrorNotificationsInvalidType, + notificationsErrors.ErrorNotificationsValidationFailed, + notificationsErrors.ErrorNotificationsParse, + notificationsErrors.ErrorNotificationsPermissionsNotFound, + ]) && logger.error(`${notificationsSend.name}:${e}`); return; } }; diff --git a/src/agent/service/vaultsGitInfoGet.ts b/src/agent/service/vaultsGitInfoGet.ts index 72f01a74c..0fb18c96a 100644 --- a/src/agent/service/vaultsGitInfoGet.ts +++ b/src/agent/service/vaultsGitInfoGet.ts @@ -1,93 +1,119 @@ -import type { VaultName } from '../../vaults/types'; +import type { DB } from '@matrixai/db'; +import type { VaultName, VaultAction } from '../../vaults/types'; import type VaultManager from '../../vaults/VaultManager'; import type ACL from '../../acl/ACL'; import type { ConnectionInfoGet } from '../../agent/types'; +import type Logger from '@matrixai/logger'; import * as grpc from '@grpc/grpc-js'; import * as grpcUtils from '../../grpc/utils'; import * as vaultsUtils from '../../vaults/utils'; import * as vaultsErrors from '../../vaults/errors'; import * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; +import { validateSync } from '../../validation'; import * as validationUtils from '../../validation/utils'; import * as nodesUtils from '../../nodes/utils'; +import { matchSync } from '../../utils'; import * as agentErrors from '../errors'; +import * as agentUtils from '../utils'; function vaultsGitInfoGet({ vaultManager, acl, + db, + logger, connectionInfoGet, }: { vaultManager: VaultManager; acl: ACL; + db: DB; + logger: Logger; connectionInfoGet: ConnectionInfoGet; }) { return async ( call: grpc.ServerWritableStream, ): Promise => { - const genWritable = grpcUtils.generatorWritable(call); - const request = call.request; - const vaultMessage = request.getVault(); - if (vaultMessage == null) { - await genWritable.throw({ code: grpc.status.NOT_FOUND }); + const genWritable = grpcUtils.generatorWritable(call, true); + try { + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + call.request.getVault()?.getNameOrId() as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? + vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const { + actionType, + }: { + actionType: VaultAction; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['actionType'], () => validationUtils.parseVaultAction(value)], + () => value, + ); + }, + { + actionType: call.request.getAction(), + }, + ); + const vaultName = (await vaultManager.getVaultMeta(vaultId, tran)) + ?.vaultName; + if (vaultName == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + // Getting the NodeId from the ReverseProxy connection info + const connectionInfo = connectionInfoGet(call); + // If this is getting run the connection exists + // It SHOULD exist here + if (connectionInfo == null) { + throw new agentErrors.ErrorConnectionInfoMissing(); + } + const nodeId = connectionInfo.remoteNodeId; + const nodeIdEncoded = nodesUtils.encodeNodeId(nodeId); + const permissions = await acl.getNodePerm(nodeId, tran); + if (permissions == null) { + throw new vaultsErrors.ErrorVaultsPermissionDenied( + `No permissions found for ${nodeIdEncoded}`, + ); + } + const vaultPerms = permissions.vaults[vaultId]; + if (vaultPerms?.[actionType] !== null) { + throw new vaultsErrors.ErrorVaultsPermissionDenied( + `${nodeIdEncoded} does not have permission to ${actionType} from vault ${vaultsUtils.encodeVaultId( + vaultId, + )}`, + ); + } + const meta = new grpc.Metadata(); + meta.set('vaultName', vaultName); + meta.set('vaultId', vaultsUtils.encodeVaultId(vaultId)); + genWritable.stream.sendMetadata(meta); + const response = new vaultsPB.PackChunk(); + const responseGen = vaultManager.handleInfoRequest(vaultId, tran); + for await (const byte of responseGen) { + if (byte !== null) { + response.setChunk(byte); + await genWritable.next(response); + } else { + await genWritable.next(null); + } + } + }); + await genWritable.next(null); return; - } - let vaultName; - const vaultNameOrId = vaultMessage.getNameOrId(); - let vaultId = await vaultManager.getVaultId(vaultNameOrId as VaultName); - vaultName = vaultNameOrId; - if (vaultId == null) { - try { - vaultId = validationUtils.parseVaultId(vaultNameOrId); - vaultName = (await vaultManager.getVaultMeta(vaultId))?.vaultName; - } catch (err) { - await genWritable.throw(new vaultsErrors.ErrorVaultsVaultUndefined()); - return; - } - } - // Getting the NodeId from the ReverseProxy connection info - const connectionInfo = connectionInfoGet(call); - // If this is getting run the connection exists - // It SHOULD exist here - if (connectionInfo == null) { - throw new agentErrors.ErrorConnectionInfoMissing(); - } - const nodeId = connectionInfo.remoteNodeId; - const nodeIdEncoded = nodesUtils.encodeNodeId(nodeId); - const actionType = validationUtils.parseVaultAction(request.getAction()); - const permissions = await acl.getNodePerm(nodeId); - if (permissions == null) { - await genWritable.throw( - new vaultsErrors.ErrorVaultsPermissionDenied( - `No permissions found for ${nodeIdEncoded}`, - ), - ); + } catch (e) { + await genWritable.throw(e); + !agentUtils.isAgentClientError(e, [ + vaultsErrors.ErrorVaultsVaultUndefined, + agentErrors.ErrorConnectionInfoMissing, + vaultsErrors.ErrorVaultsPermissionDenied, + ]) && logger.error(`${vaultsGitInfoGet.name}:${e}`); return; } - const vaultPerms = permissions.vaults[vaultId]; - if (vaultPerms?.[actionType] !== null) { - await genWritable.throw( - new vaultsErrors.ErrorVaultsPermissionDenied( - `${nodeIdEncoded} does not have permission to ${actionType} from vault ${vaultsUtils.encodeVaultId( - vaultId, - )}`, - ), - ); - return; - } - const meta = new grpc.Metadata(); - meta.set('vaultName', vaultName); - meta.set('vaultId', vaultsUtils.encodeVaultId(vaultId)); - genWritable.stream.sendMetadata(meta); - const response = new vaultsPB.PackChunk(); - const responseGen = vaultManager.handleInfoRequest(vaultId); - for await (const byte of responseGen) { - if (byte !== null) { - response.setChunk(byte); - await genWritable.next(response); - } else { - await genWritable.next(null); - } - } - await genWritable.next(null); }; } diff --git a/src/agent/service/vaultsGitPackGet.ts b/src/agent/service/vaultsGitPackGet.ts index 5528ade31..f8aa5dc3d 100644 --- a/src/agent/service/vaultsGitPackGet.ts +++ b/src/agent/service/vaultsGitPackGet.ts @@ -1,100 +1,132 @@ import type * as grpc from '@grpc/grpc-js'; -import type { VaultName } from '../../vaults/types'; +import type { DB } from '@matrixai/db'; +import type { VaultName, VaultAction } from '../../vaults/types'; import type VaultManager from '../../vaults/VaultManager'; import type { ConnectionInfoGet } from '../../agent/types'; import type ACL from '../../acl/ACL'; +import type KeyManager from '../../keys/KeyManager'; +import type Logger from '@matrixai/logger'; import * as nodesUtils from '../../nodes/utils'; -import * as grpcErrors from '../../grpc/errors'; import * as grpcUtils from '../../grpc/utils'; import * as vaultsErrors from '../../vaults/errors'; import * as vaultsUtils from '../../vaults/utils'; import * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; +import { validateSync } from '../../validation'; import * as validationUtils from '../../validation/utils'; +import { matchSync } from '../../utils'; import * as agentErrors from '../errors'; +import * as agentUtils from '../utils'; function vaultsGitPackGet({ vaultManager, acl, + db, + keyManager, + logger, connectionInfoGet, }: { vaultManager: VaultManager; acl: ACL; + db: DB; + keyManager: KeyManager; + logger: Logger; connectionInfoGet: ConnectionInfoGet; }) { return async ( call: grpc.ServerDuplexStream, - ) => { - const genDuplex = grpcUtils.generatorDuplex(call); - const clientBodyBuffers: Uint8Array[] = []; - const clientRequest = (await genDuplex.read()).value; - clientBodyBuffers.push(clientRequest!.getChunk_asU8()); - const body = Buffer.concat(clientBodyBuffers); - const meta = call.metadata; - // Getting the NodeId from the ReverseProxy connection info - const connectionInfo = connectionInfoGet(call); - // If this is getting run the connection exists - // It SHOULD exist here - if (connectionInfo == null) { - throw new agentErrors.ErrorConnectionInfoMissing(); - } - const nodeId = connectionInfo.remoteNodeId; - const nodeIdEncoded = nodesUtils.encodeNodeId(nodeId); - // Getting vaultId - const vaultNameOrId = meta.get('vaultNameOrId').pop()!.toString(); - if (vaultNameOrId == null) { - throw new grpcErrors.ErrorGRPC('vault-name not in metadata'); - } - let vaultId = await vaultManager.getVaultId(vaultNameOrId as VaultName); - vaultId = vaultId ?? vaultsUtils.decodeVaultId(vaultNameOrId); - if (vaultId == null) { - await genDuplex.throw( - // Throwing permission error to hide information about vaults existence - new vaultsErrors.ErrorVaultsPermissionDenied( - `No permissions found for ${nodeIdEncoded}`, - ), - ); - return; - } - // Checking permissions - const permissions = await acl.getNodePerm(nodeId); - const vaultPerms = permissions?.vaults[vaultId]; - const actionType = validationUtils.parseVaultAction( - meta.get('vaultAction').pop(), - ); - if (vaultPerms?.[actionType] !== null) { - await genDuplex.throw( - new vaultsErrors.ErrorVaultsPermissionDenied( - `${nodeIdEncoded} does not have permission to ${actionType} from vault ${vaultsUtils.encodeVaultId( - vaultId, - )}`, - ), - ); - return; - } - const response = new vaultsPB.PackChunk(); - const [sideBand, progressStream] = await vaultManager.handlePackRequest( - vaultId, - Buffer.from(body), + ): Promise => { + const nodeId = keyManager.getNodeId(); + const genDuplex = grpcUtils.generatorDuplex( + call, + { nodeId, command: vaultsGitPackGet.name }, + true, ); - response.setChunk(Buffer.from('0008NAK\n')); - await genDuplex.write(response); - const responseBuffers: Uint8Array[] = []; - await new Promise((resolve, reject) => { - sideBand.on('data', async (data: Uint8Array) => { - responseBuffers.push(data); - }); - sideBand.on('end', async () => { - response.setChunk(Buffer.concat(responseBuffers)); + try { + const clientBodyBuffers: Uint8Array[] = []; + const clientRequest = (await genDuplex.read()).value; + clientBodyBuffers.push(clientRequest!.getChunk_asU8()); + const body = Buffer.concat(clientBodyBuffers); + const meta = call.metadata; + // Getting the NodeId from the ReverseProxy connection info + const connectionInfo = connectionInfoGet(call); + // If this is getting run the connection exists + // It SHOULD exist here + if (connectionInfo == null) { + throw new agentErrors.ErrorConnectionInfoMissing(); + } + const nodeId = connectionInfo.remoteNodeId; + const nodeIdEncoded = nodesUtils.encodeNodeId(nodeId); + const nameOrId = meta.get('vaultNameOrId').pop()!.toString(); + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + nameOrId as VaultName, + tran, + ); + const vaultId = vaultIdFromName ?? vaultsUtils.decodeVaultId(nameOrId); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const { + actionType, + }: { + actionType: VaultAction; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['actionType'], () => validationUtils.parseVaultAction(value)], + () => value, + ); + }, + { + actionType: meta.get('vaultAction').pop()!.toString(), + }, + ); + // Checking permissions + const permissions = await acl.getNodePerm(nodeId, tran); + const vaultPerms = permissions?.vaults[vaultId]; + if (vaultPerms?.[actionType] !== null) { + throw new vaultsErrors.ErrorVaultsPermissionDenied( + `${nodeIdEncoded} does not have permission to ${actionType} from vault ${vaultsUtils.encodeVaultId( + vaultId, + )}`, + ); + } + const response = new vaultsPB.PackChunk(); + const [sideBand, progressStream] = await vaultManager.handlePackRequest( + vaultId, + Buffer.from(body), + tran, + ); + response.setChunk(Buffer.from('0008NAK\n')); await genDuplex.write(response); - resolve(); - }); - sideBand.on('error', (err) => { - reject(err); + const responseBuffers: Uint8Array[] = []; + await new Promise((resolve, reject) => { + sideBand.on('data', async (data: Uint8Array) => { + responseBuffers.push(data); + }); + sideBand.on('end', async () => { + response.setChunk(Buffer.concat(responseBuffers)); + await genDuplex.write(response); + resolve(); + }); + sideBand.on('error', (err) => { + reject(err); + }); + progressStream.write(Buffer.from('0014progress is at 50%\n')); + progressStream.end(); + }); }); - progressStream.write(Buffer.from('0014progress is at 50%\n')); - progressStream.end(); - }); - await genDuplex.next(null); + await genDuplex.next(null); + return; + } catch (e) { + await genDuplex.throw(e); + !agentUtils.isAgentClientError(e, [ + agentErrors.ErrorConnectionInfoMissing, + vaultsErrors.ErrorVaultsPermissionDenied, + vaultsErrors.ErrorVaultsVaultUndefined, + ]) && logger.error(`${vaultsGitPackGet.name}:${e}`); + return; + } }; } diff --git a/src/agent/service/vaultsScan.ts b/src/agent/service/vaultsScan.ts index 6dfd028e4..f82719108 100644 --- a/src/agent/service/vaultsScan.ts +++ b/src/agent/service/vaultsScan.ts @@ -1,23 +1,31 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type VaultManager from '../../vaults/VaultManager'; import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; import type { ConnectionInfoGet } from '../../agent/types'; +import type Logger from '@matrixai/logger'; import * as agentErrors from '../../agent/errors'; +import * as vaultsErrors from '../../vaults/errors'; import * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; import * as vaultsUtils from '../../vaults/utils'; import * as grpcUtils from '../../grpc/utils'; +import * as agentUtils from '../utils'; function vaultsScan({ vaultManager, + logger, connectionInfoGet, + db, }: { vaultManager: VaultManager; + logger: Logger; connectionInfoGet: ConnectionInfoGet; + db: DB; }) { return async ( call: grpc.ServerWritableStream, ): Promise => { - const genWritable = grpcUtils.generatorWritable(call); + const genWritable = grpcUtils.generatorWritable(call, true); const listMessage = new vaultsPB.List(); // Getting the NodeId from the ReverseProxy connection info const connectionInfo = connectionInfoGet(call); @@ -28,20 +36,28 @@ function vaultsScan({ } const nodeId = connectionInfo.remoteNodeId; try { - const listResponse = vaultManager.handleScanVaults(nodeId); - for await (const { - vaultId, - vaultName, - vaultPermissions, - } of listResponse) { - listMessage.setVaultId(vaultsUtils.encodeVaultId(vaultId)); - listMessage.setVaultName(vaultName); - listMessage.setVaultPermissionsList(vaultPermissions); - await genWritable.next(listMessage); - } + await db.withTransactionF(async (tran) => { + const listResponse = vaultManager.handleScanVaults(nodeId, tran); + for await (const { + vaultId, + vaultName, + vaultPermissions, + } of listResponse) { + listMessage.setVaultId(vaultsUtils.encodeVaultId(vaultId)); + listMessage.setVaultName(vaultName); + listMessage.setVaultPermissionsList(vaultPermissions); + await genWritable.next(listMessage); + } + }); await genWritable.next(null); + return; } catch (e) { await genWritable.throw(e); + !agentUtils.isAgentClientError(e, [ + agentErrors.ErrorConnectionInfoMissing, + vaultsErrors.ErrorVaultsPermissionDenied, + ]) && logger.error(`${vaultsScan.name}:${e}`); + return; } }; } diff --git a/src/agent/types.ts b/src/agent/types.ts index ced17bbf1..b6d0e0259 100644 --- a/src/agent/types.ts +++ b/src/agent/types.ts @@ -1,8 +1,14 @@ -import type { ConnectionInfo } from 'network/types'; import type { ServerSurfaceCall } from '@grpc/grpc-js/build/src/server-call'; +import type { Class } from '@matrixai/errors'; +import type { ConnectionInfo } from '../network/types'; +import type ErrorPolykey from '../ErrorPolykey'; type ConnectionInfoGet = ( call: ServerSurfaceCall, ) => ConnectionInfo | undefined; -export type { ConnectionInfoGet }; +type AgentClientErrors = Array< + Class> | Array>> +>; + +export type { ConnectionInfoGet, AgentClientErrors }; diff --git a/src/agent/utils.ts b/src/agent/utils.ts index f38e540d5..48f9ff59f 100644 --- a/src/agent/utils.ts +++ b/src/agent/utils.ts @@ -1,7 +1,17 @@ -import type { Host, Port } from 'network/types'; -import type Proxy from 'network/Proxy'; -import type { ConnectionInfoGet } from './types'; import type { ServerSurfaceCall } from '@grpc/grpc-js/build/src/server-call'; +import type ErrorPolykey from '../ErrorPolykey'; +import type { Host, Port } from '../network/types'; +import type Proxy from '../network/Proxy'; +import type { ConnectionInfoGet, AgentClientErrors } from './types'; +import * as validationErrors from '../validation/errors'; + +/** + * Array of errors that are always considered to be "client errors" + * (4xx errors in HTTP) in the context of the agent service + */ +const defaultClientErrors: AgentClientErrors = [ + validationErrors.ErrorValidation, +]; function connectionInfoGetter(proxy: Proxy): ConnectionInfoGet { return (call: ServerSurfaceCall) => { @@ -15,4 +25,61 @@ function connectionInfoGetter(proxy: Proxy): ConnectionInfoGet { }; } -export { connectionInfoGetter }; +/** + * Checks whether an error is a "client error" (4xx errors in HTTP) + * Used by the service handlers since client errors should not be + * reported on the server side + * Additional errors that are considered to be client errors in the + * context of a given handler can be supplied in the `extraClientErrors` + * argument + */ +function isAgentClientError( + thrownError: ErrorPolykey, + extraClientErrors?: AgentClientErrors, +): boolean { + for (const error of defaultClientErrors) { + if (Array.isArray(error)) { + let e = thrownError; + let matches = true; + for (const eType of error) { + if (e == null) { + matches = false; + break; + } + if (!(e instanceof eType)) { + matches = false; + break; + } + e = e.cause; + } + if (matches) return true; + } else if (thrownError instanceof error) { + return true; + } + } + if (extraClientErrors) { + for (const error of extraClientErrors) { + if (Array.isArray(error)) { + let e = thrownError; + let matches = true; + for (const eType of error) { + if (e == null) { + matches = false; + break; + } + if (!(e instanceof eType)) { + matches = false; + break; + } + e = e.cause; + } + if (matches) return true; + } else if (thrownError instanceof error) { + return true; + } + } + } + return false; +} + +export { connectionInfoGetter, isAgentClientError }; diff --git a/src/bin/CommandPolykey.ts b/src/bin/CommandPolykey.ts index b9534fee0..436dfdbdd 100644 --- a/src/bin/CommandPolykey.ts +++ b/src/bin/CommandPolykey.ts @@ -61,6 +61,8 @@ class CommandPolykey extends commander.Command { public action(fn: (...args: any[]) => void | Promise): this { return super.action(async (...args: any[]) => { const opts = this.opts(); + // Set the format for error logging for the exit handlers + this.exitHandlers.errFormat = opts.format === 'json' ? 'json' : 'error'; // Set the logger according to the verbosity this.logger.setLevel(binUtils.verboseToLogLevel(opts.verbose)); // Set the global upstream GRPC logger diff --git a/src/bin/agent/CommandStart.ts b/src/bin/agent/CommandStart.ts index d99a60369..6ccc4e9c0 100644 --- a/src/bin/agent/CommandStart.ts +++ b/src/bin/agent/CommandStart.ts @@ -175,14 +175,17 @@ class CommandStart extends CommandPolykey { new binErrors.ErrorCLIPolykeyAgentProcess( 'Agent process closed during fork', { - code, - signal, + data: { + code, + signal, + }, }, ), ); }); const messageIn: AgentChildProcessInput = { logLevel: this.logger.getEffectiveLevel(), + format: options.format, workers: options.workers, agentConfig, }; diff --git a/src/bin/errors.ts b/src/bin/errors.ts index e8fa532a4..95951d260 100644 --- a/src/bin/errors.ts +++ b/src/bin/errors.ts @@ -1,56 +1,56 @@ import ErrorPolykey from '../ErrorPolykey'; import sysexits from '../utils/sysexits'; -class ErrorCLI extends ErrorPolykey {} +class ErrorCLI extends ErrorPolykey {} -class ErrorCLINodePath extends ErrorCLI { - description = 'Cannot derive default node path from unknown platform'; +class ErrorCLINodePath extends ErrorCLI { + static description = 'Cannot derive default node path from unknown platform'; exitCode = sysexits.USAGE; } -class ErrorCLIClientOptions extends ErrorCLI { - description = 'Missing required client options'; +class ErrorCLIClientOptions extends ErrorCLI { + static description = 'Missing required client options'; exitCode = sysexits.USAGE; } -class ErrorCLIPasswordMissing extends ErrorCLI { - description = +class ErrorCLIPasswordMissing extends ErrorCLI { + static description = 'Password is necessary, provide it via --password-file, PK_PASSWORD or when prompted'; exitCode = sysexits.USAGE; } -class ErrorCLIPasswordFileRead extends ErrorCLI { - description = 'Failed to read password file'; +class ErrorCLIPasswordFileRead extends ErrorCLI { + static description = 'Failed to read password file'; exitCode = sysexits.NOINPUT; } -class ErrorCLIRecoveryCodeFileRead extends ErrorCLI { - description = 'Failed to read recovery code file'; +class ErrorCLIRecoveryCodeFileRead extends ErrorCLI { + static description = 'Failed to read recovery code file'; exitCode = sysexits.NOINPUT; } -class ErrorCLIFileRead extends ErrorCLI { - description = 'Failed to read file'; +class ErrorCLIFileRead extends ErrorCLI { + static description = 'Failed to read file'; exitCode = sysexits.NOINPUT; } -class ErrorCLIPolykeyAgentStatus extends ErrorCLI { - description = 'PolykeyAgent agent status'; +class ErrorCLIPolykeyAgentStatus extends ErrorCLI { + static description = 'PolykeyAgent agent status'; exitCode = sysexits.TEMPFAIL; } -class ErrorCLIPolykeyAgentProcess extends ErrorCLI { - description = 'PolykeyAgent process could not be started'; +class ErrorCLIPolykeyAgentProcess extends ErrorCLI { + static description = 'PolykeyAgent process could not be started'; exitCode = sysexits.OSERR; } -class ErrorNodeFindFailed extends ErrorCLI { - description = 'Failed to find the node in the DHT'; +class ErrorNodeFindFailed extends ErrorCLI { + static description = 'Failed to find the node in the DHT'; exitCode = 1; } -class ErrorNodePingFailed extends ErrorCLI { - description = 'Node was not online or not found.'; +class ErrorNodePingFailed extends ErrorCLI { + static description = 'Node was not online or not found.'; exitCode = 1; } diff --git a/src/bin/keys/CommandDecrypt.ts b/src/bin/keys/CommandDecrypt.ts index 137757bbb..aeb4a5191 100644 --- a/src/bin/keys/CommandDecrypt.ts +++ b/src/bin/keys/CommandDecrypt.ts @@ -52,10 +52,13 @@ class CommandDecrypt extends CommandPolykey { }); } catch (e) { throw new binErrors.ErrorCLIFileRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } cryptoMessage.setData(cipherText); diff --git a/src/bin/keys/CommandEncrypt.ts b/src/bin/keys/CommandEncrypt.ts index dac9d52eb..2edef5b08 100644 --- a/src/bin/keys/CommandEncrypt.ts +++ b/src/bin/keys/CommandEncrypt.ts @@ -52,10 +52,13 @@ class CommandEncypt extends CommandPolykey { }); } catch (e) { throw new binErrors.ErrorCLIFileRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } cryptoMessage.setData(plainText); diff --git a/src/bin/keys/CommandSign.ts b/src/bin/keys/CommandSign.ts index 4d94d2a24..4d31dee5f 100644 --- a/src/bin/keys/CommandSign.ts +++ b/src/bin/keys/CommandSign.ts @@ -52,10 +52,13 @@ class CommandSign extends CommandPolykey { }); } catch (e) { throw new binErrors.ErrorCLIFileRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } cryptoMessage.setData(data); diff --git a/src/bin/keys/CommandVerify.ts b/src/bin/keys/CommandVerify.ts index 42dabed70..7c0a5de49 100644 --- a/src/bin/keys/CommandVerify.ts +++ b/src/bin/keys/CommandVerify.ts @@ -60,10 +60,13 @@ class CommandVerify extends CommandPolykey { }); } catch (e) { throw new binErrors.ErrorCLIFileRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } cryptoMessage.setData(data); diff --git a/src/bin/nodes/CommandAdd.ts b/src/bin/nodes/CommandAdd.ts index fdf49f48e..49ea3105a 100644 --- a/src/bin/nodes/CommandAdd.ts +++ b/src/bin/nodes/CommandAdd.ts @@ -18,6 +18,8 @@ class CommandAdd extends CommandPolykey { this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); + this.addOption(binOptions.forceNodeAdd); + this.addOption(binOptions.noPing); this.action(async (nodeId: NodeId, host: Host, port: Port, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); const nodesUtils = await import('../../nodes/utils'); @@ -46,13 +48,15 @@ class CommandAdd extends CommandPolykey { port: clientOptions.clientPort, logger: this.logger.getChild(PolykeyClient.name), }); - const nodeAddressMessage = new nodesPB.NodeAddress(); - nodeAddressMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); - nodeAddressMessage.setAddress( + const nodeAddMessage = new nodesPB.NodeAdd(); + nodeAddMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); + nodeAddMessage.setAddress( new nodesPB.Address().setHost(host).setPort(port), ); + nodeAddMessage.setForce(options.force); + nodeAddMessage.setPing(options.ping); await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.nodesAdd(nodeAddressMessage, auth), + (auth) => pkClient.grpcClient.nodesAdd(nodeAddMessage, auth), meta, ); } finally { diff --git a/src/bin/nodes/CommandFind.ts b/src/bin/nodes/CommandFind.ts index 5788c2c8a..32169a968 100644 --- a/src/bin/nodes/CommandFind.ts +++ b/src/bin/nodes/CommandFind.ts @@ -71,7 +71,9 @@ class CommandFind extends CommandPolykey { result.port as Port, )}`; } catch (err) { - if (!(err instanceof nodesErrors.ErrorNodeGraphNodeIdNotFound)) { + if ( + !(err.cause instanceof nodesErrors.ErrorNodeGraphNodeIdNotFound) + ) { throw err; } // Else failed to find the node. diff --git a/src/bin/nodes/CommandGetAll.ts b/src/bin/nodes/CommandGetAll.ts new file mode 100644 index 000000000..243991fc9 --- /dev/null +++ b/src/bin/nodes/CommandGetAll.ts @@ -0,0 +1,77 @@ +import type PolykeyClient from '../../PolykeyClient'; +import type nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; +import CommandPolykey from '../CommandPolykey'; +import * as binUtils from '../utils'; +import * as binOptions from '../utils/options'; +import * as binProcessors from '../utils/processors'; + +class CommandGetAll extends CommandPolykey { + constructor(...args: ConstructorParameters) { + super(...args); + this.name('getall'); + this.description('Get all Nodes from Node Graph'); + this.addOption(binOptions.nodeId); + this.addOption(binOptions.clientHost); + this.addOption(binOptions.clientPort); + this.action(async (options) => { + const { default: PolykeyClient } = await import('../../PolykeyClient'); + const utilsPB = await import('../../proto/js/polykey/v1/utils/utils_pb'); + + const clientOptions = await binProcessors.processClientOptions( + options.nodePath, + options.nodeId, + options.clientHost, + options.clientPort, + this.fs, + this.logger.getChild(binProcessors.processClientOptions.name), + ); + const meta = await binProcessors.processAuthentication( + options.passwordFile, + this.fs, + ); + let pkClient: PolykeyClient; + this.exitHandlers.handlers.push(async () => { + if (pkClient != null) await pkClient.stop(); + }); + let result: nodesPB.NodeBuckets; + try { + pkClient = await PolykeyClient.createPolykeyClient({ + nodePath: options.nodePath, + nodeId: clientOptions.nodeId, + host: clientOptions.clientHost, + port: clientOptions.clientPort, + logger: this.logger.getChild(PolykeyClient.name), + }); + const emptyMessage = new utilsPB.EmptyMessage(); + result = await binUtils.retryAuthentication( + (auth) => pkClient.grpcClient.nodesGetAll(emptyMessage, auth), + meta, + ); + let output: any = {}; + for (const [bucketIndex, bucket] of result.getBucketsMap().entries()) { + output[bucketIndex] = {}; + for (const [encodedId, address] of bucket + .getNodeTableMap() + .entries()) { + output[bucketIndex][encodedId] = {}; + output[bucketIndex][encodedId].host = address.getHost(); + output[bucketIndex][encodedId].port = address.getPort(); + } + } + if (options.format === 'human') { + output = [result.getBucketsMap().getEntryList()]; + } + process.stdout.write( + binUtils.outputFormatter({ + type: options.format === 'json' ? 'json' : 'list', + data: output, + }), + ); + } finally { + if (pkClient! != null) await pkClient.stop(); + } + }); + } +} + +export default CommandGetAll; diff --git a/src/bin/nodes/CommandNodes.ts b/src/bin/nodes/CommandNodes.ts index 6827d01f3..0866a088f 100644 --- a/src/bin/nodes/CommandNodes.ts +++ b/src/bin/nodes/CommandNodes.ts @@ -2,6 +2,7 @@ import CommandAdd from './CommandAdd'; import CommandClaim from './CommandClaim'; import CommandFind from './CommandFind'; import CommandPing from './CommandPing'; +import CommandGetAll from './CommandGetAll'; import CommandPolykey from '../CommandPolykey'; class CommandNodes extends CommandPolykey { @@ -13,6 +14,7 @@ class CommandNodes extends CommandPolykey { this.addCommand(new CommandClaim(...args)); this.addCommand(new CommandFind(...args)); this.addCommand(new CommandPing(...args)); + this.addCommand(new CommandGetAll(...args)); } } diff --git a/src/bin/nodes/CommandPing.ts b/src/bin/nodes/CommandPing.ts index b22e0d19d..a15779c55 100644 --- a/src/bin/nodes/CommandPing.ts +++ b/src/bin/nodes/CommandPing.ts @@ -55,11 +55,12 @@ class CommandPing extends CommandPolykey { meta, ); } catch (err) { - if (err instanceof nodesErrors.ErrorNodeGraphNodeIdNotFound) { + if (err.cause instanceof nodesErrors.ErrorNodeGraphNodeIdNotFound) { error = new binErrors.ErrorNodePingFailed( `Failed to resolve node ID ${nodesUtils.encodeNodeId( nodeId, )} to an address.`, + { cause: err }, ); } else { throw err; diff --git a/src/bin/polykey-agent.ts b/src/bin/polykey-agent.ts old mode 100644 new mode 100755 index d92dce2d5..b9476b514 --- a/src/bin/polykey-agent.ts +++ b/src/bin/polykey-agent.ts @@ -37,13 +37,15 @@ const logger = new Logger('polykey', undefined, [new StreamHandler()]); */ async function main(_argv = process.argv): Promise { const exitHandlers = new binUtils.ExitHandlers(); - const processSend = promisify(process.send!.bind(process)); + const processSend = promisify(process.send!.bind(process)); const { p: messageInP, resolveP: resolveMessageInP } = promise(); - process.once('message', (data) => { + process.once('message', (data: AgentChildProcessInput) => { resolveMessageInP(data); }); const messageIn = await messageInP; + const errFormat = messageIn.format === 'json' ? 'json' : 'error'; + exitHandlers.errFormat = errFormat; logger.setLevel(messageIn.logLevel); // Set the global upstream GRPC logger grpcSetLogger(logger.getChild('grpc')); @@ -71,10 +73,8 @@ async function main(_argv = process.argv): Promise { if (e instanceof ErrorPolykey) { process.stderr.write( binUtils.outputFormatter({ - type: 'error', - name: e.name, - description: e.description, - message: e.message, + type: errFormat, + data: e, }), ); process.exitCode = e.exitCode; @@ -82,9 +82,8 @@ async function main(_argv = process.argv): Promise { // Unknown error, this should not happen process.stderr.write( binUtils.outputFormatter({ - type: 'error', - name: e.name, - description: e.message, + type: errFormat, + data: e, }), ); process.exitCode = 255; @@ -107,9 +106,8 @@ async function main(_argv = process.argv): Promise { // There's no point attempting to propagate the error to the parent process.stderr.write( binUtils.outputFormatter({ - type: 'error', - name: e.name, - description: e.message, + type: errFormat, + data: e, }), ); process.exitCode = 255; @@ -137,9 +135,8 @@ async function main(_argv = process.argv): Promise { // There's no point attempting to propagate the error to the parent process.stderr.write( binUtils.outputFormatter({ - type: 'error', - name: e.name, - description: e.message, + type: errFormat, + data: e, }), ); process.exitCode = 255; diff --git a/src/bin/polykey.ts b/src/bin/polykey.ts old mode 100644 new mode 100755 index ac40d5373..bb4d49f8a --- a/src/bin/polykey.ts +++ b/src/bin/polykey.ts @@ -55,6 +55,7 @@ async function main(argv = process.argv): Promise { // Successful execution (even if the command was non-terminating) process.exitCode = 0; } catch (e) { + const errFormat = rootCommand.opts().format === 'json' ? 'json' : 'error'; if (e instanceof commander.CommanderError) { // Commander writes help and error messages on stderr automatically if ( @@ -78,10 +79,8 @@ async function main(argv = process.argv): Promise { } else if (e instanceof ErrorPolykey) { process.stderr.write( binUtils.outputFormatter({ - type: 'error', - name: e.name, - description: e.description, - message: e.message, + type: errFormat, + data: e, }), ); process.exitCode = e.exitCode; @@ -89,9 +88,8 @@ async function main(argv = process.argv): Promise { // Unknown error, this should not happen process.stderr.write( binUtils.outputFormatter({ - type: 'error', - name: e.name, - description: e.message, + type: errFormat, + data: e, }), ); process.exitCode = 255; diff --git a/src/bin/secrets/CommandCreate.ts b/src/bin/secrets/CommandCreate.ts index b0d9a7d0d..26f22cbbe 100644 --- a/src/bin/secrets/CommandCreate.ts +++ b/src/bin/secrets/CommandCreate.ts @@ -65,10 +65,13 @@ class CommandCreate extends CommandPolykey { content = await this.fs.promises.readFile(directoryPath); } catch (e) { throw new binErrors.ErrorCLIFileRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } secretMessage.setSecretContent(content); diff --git a/src/bin/secrets/CommandEdit.ts b/src/bin/secrets/CommandEdit.ts index f3d005810..f86b37499 100644 --- a/src/bin/secrets/CommandEdit.ts +++ b/src/bin/secrets/CommandEdit.ts @@ -74,10 +74,13 @@ class CommandEdit extends CommandPolykey { content = await this.fs.promises.readFile(tmpFile); } catch (e) { throw new binErrors.ErrorCLIFileRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } secretMessage.setVault(vaultMessage); diff --git a/src/bin/secrets/CommandUpdate.ts b/src/bin/secrets/CommandUpdate.ts index 941006aed..c7b9e696d 100644 --- a/src/bin/secrets/CommandUpdate.ts +++ b/src/bin/secrets/CommandUpdate.ts @@ -65,10 +65,13 @@ class CommandUpdate extends CommandPolykey { content = await this.fs.promises.readFile(directoryPath); } catch (e) { throw new binErrors.ErrorCLIFileRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } secretMessage.setSecretContent(content); diff --git a/src/bin/types.ts b/src/bin/types.ts index ef2451661..0517f08f1 100644 --- a/src/bin/types.ts +++ b/src/bin/types.ts @@ -17,6 +17,7 @@ type AgentStatusLiveData = Omit & { */ type AgentChildProcessInput = { logLevel: LogLevel; + format: 'human' | 'json'; workers?: number; agentConfig: { password: string; diff --git a/src/bin/utils/ExitHandlers.ts b/src/bin/utils/ExitHandlers.ts index 84b981d80..2fdd74f03 100644 --- a/src/bin/utils/ExitHandlers.ts +++ b/src/bin/utils/ExitHandlers.ts @@ -9,6 +9,7 @@ class ExitHandlers { */ public handlers: Array<(signal?: NodeJS.Signals) => Promise>; protected _exiting: boolean = false; + protected _errFormat: 'json' | 'error'; /** * Handles synchronous and asynchronous exceptions * This prints out appropriate error message on STDERR @@ -23,10 +24,8 @@ class ExitHandlers { if (e instanceof ErrorPolykey) { process.stderr.write( binUtils.outputFormatter({ - type: 'error', - name: e.name, - description: e.description, - message: e.message, + type: this._errFormat, + data: e, }), ); process.exitCode = e.exitCode; @@ -34,9 +33,8 @@ class ExitHandlers { // Unknown error, this should not happen process.stderr.write( binUtils.outputFormatter({ - type: 'error', - name: e.name, - description: e.message, + type: this._errFormat, + data: e, }), ); process.exitCode = 255; @@ -65,19 +63,16 @@ class ExitHandlers { if (e instanceof ErrorPolykey) { process.stderr.write( binUtils.outputFormatter({ - type: 'error', - name: e.name, - description: e.description, - message: e.message, + type: this._errFormat, + data: e, }), ); } else { // Unknown error, this should not happen process.stderr.write( binUtils.outputFormatter({ - type: 'error', - name: e.name, - description: e.message, + type: this._errFormat, + data: e, }), ); } @@ -103,6 +98,10 @@ class ExitHandlers { return this._exiting; } + set errFormat(errFormat: 'json' | 'error') { + this._errFormat = errFormat; + } + public install() { process.on('SIGINT', this.signalHandler); process.on('SIGTERM', this.signalHandler); diff --git a/src/bin/utils/options.ts b/src/bin/utils/options.ts index bed18d65a..f2da17b8c 100644 --- a/src/bin/utils/options.ts +++ b/src/bin/utils/options.ts @@ -154,6 +154,15 @@ const pullVault = new commander.Option( 'Name or Id of the vault to pull from', ); +const forceNodeAdd = new commander.Option( + '--force', + 'Force adding node to nodeGraph', +).default(false); + +const noPing = new commander.Option('--no-ping', 'Skip ping step').default( + true, +); + export { nodePath, format, @@ -176,4 +185,6 @@ export { network, workers, pullVault, + forceNodeAdd, + noPing, }; diff --git a/src/bin/utils/processors.ts b/src/bin/utils/processors.ts index 509134fc0..df43437d0 100644 --- a/src/bin/utils/processors.ts +++ b/src/bin/utils/processors.ts @@ -89,10 +89,13 @@ async function processPassword( password = (await fs.promises.readFile(passwordFile, 'utf-8')).trim(); } catch (e) { throw new binErrors.ErrorCLIPasswordFileRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } } else if (typeof process.env['PK_PASSWORD'] === 'string') { @@ -131,10 +134,13 @@ async function processNewPassword( ).trim(); } catch (e) { throw new binErrors.ErrorCLIPasswordFileRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } } else if (!existing && typeof process.env['PK_PASSWORD'] === 'string') { @@ -167,10 +173,13 @@ async function processRecoveryCode( ).trim(); } catch (e) { throw new binErrors.ErrorCLIRecoveryCodeFileRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } } else if (typeof process.env['PK_RECOVERY_CODE'] === 'string') { @@ -372,10 +381,13 @@ async function processAuthentication( password = (await fs.promises.readFile(passwordFile, 'utf-8')).trim(); } catch (e) { throw new binErrors.ErrorCLIPasswordFileRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } meta = clientUtils.encodeAuthFromPassword(password); diff --git a/src/bin/utils/utils.ts b/src/bin/utils/utils.ts index c902337e8..1345afb04 100644 --- a/src/bin/utils/utils.ts +++ b/src/bin/utils/utils.ts @@ -2,10 +2,15 @@ import type { POJO } from '../../types'; import process from 'process'; import { LogLevel } from '@matrixai/logger'; import * as grpc from '@grpc/grpc-js'; +import { AbstractError } from '@matrixai/errors'; import * as binProcessors from './processors'; +import ErrorPolykey from '../../ErrorPolykey'; import * as binErrors from '../errors'; import * as clientUtils from '../../client/utils'; import * as clientErrors from '../../client/errors'; +import * as grpcErrors from '../../grpc/errors'; +import * as nodesUtils from '../../nodes/utils'; +import * as utils from '../../utils'; /** * Convert verbosity to LogLevel @@ -39,9 +44,7 @@ type OutputObject = } | { type: 'error'; - name: string; - description: string; - message?: string; + data: Error; }; function outputFormatter(msg: OutputObject): string { @@ -91,14 +94,72 @@ function outputFormatter(msg: OutputObject): string { output += `${key}\t${value}\n`; } } else if (msg.type === 'json') { + if (msg.data instanceof Error && !(msg.data instanceof AbstractError)) { + msg.data = { + type: msg.data.name, + data: { message: msg.data.message, stack: msg.data.stack }, + }; + } output = JSON.stringify(msg.data); output += '\n'; } else if (msg.type === 'error') { - output += `${msg.name}: ${msg.description}`; - if (msg.message) { - output += ` - ${msg.message}`; + let currError = msg.data; + let indent = ' '; + while (currError != null) { + if (currError instanceof grpcErrors.ErrorPolykeyRemote) { + output += `${currError.name}: ${currError.description}`; + if (currError.message && currError.message !== '') { + output += ` - ${currError.message}`; + } + output += '\n'; + output += `${indent}command\t${currError.metadata.command}\n`; + output += `${indent}nodeId\t${nodesUtils.encodeNodeId( + currError.metadata.nodeId, + )}\n`; + output += `${indent}host\t${currError.metadata.host}\n`; + output += `${indent}port\t${currError.metadata.port}\n`; + output += `${indent}timestamp\t${currError.timestamp}\n`; + output += `${indent}cause: `; + currError = currError.cause; + } else if (currError instanceof ErrorPolykey) { + output += `${currError.name}: ${currError.description}`; + if (currError.message && currError.message !== '') { + output += ` - ${currError.message}`; + } + output += '\n'; + output += `${indent}exitCode\t${currError.exitCode}\n`; + output += `${indent}timestamp\t${currError.timestamp}\n`; + if (currError.data && !utils.isEmptyObject(currError.data)) { + output += `${indent}data\t${JSON.stringify(currError.data)}\n`; + } + if (currError.cause) { + output += `${indent}cause: `; + if (currError.cause instanceof ErrorPolykey) { + currError = currError.cause; + } else if (currError.cause instanceof Error) { + output += `${currError.cause.name}`; + if (currError.cause.message && currError.cause.message !== '') { + output += `: ${currError.cause.message}`; + } + output += '\n'; + break; + } else { + output += `${JSON.stringify(currError.cause)}\n`; + break; + } + } else { + break; + } + } else { + output += `${currError.name}`; + if (currError.message && currError.message !== '') { + output += `: ${currError.message}`; + } + output += '\n'; + break; + } + indent = indent + ' '; } - output += '\n'; } return output; } @@ -122,9 +183,10 @@ async function retryAuthentication( throw e; } // If it is exception is not missing or denied, then throw the exception + const [cause] = remoteErrorCause(e); if ( - !(e instanceof clientErrors.ErrorClientAuthMissing) && - !(e instanceof clientErrors.ErrorClientAuthDenied) + !(cause instanceof clientErrors.ErrorClientAuthMissing) && + !(cause instanceof clientErrors.ErrorClientAuthDenied) ) { throw e; } @@ -141,14 +203,30 @@ async function retryAuthentication( try { return await f(meta); } catch (e) { + const [cause] = remoteErrorCause(e); // The auth cannot be missing, so when it is denied do we retry - if (!(e instanceof clientErrors.ErrorClientAuthDenied)) { + if (!(cause instanceof clientErrors.ErrorClientAuthDenied)) { throw e; } } } } -export { verboseToLogLevel, outputFormatter, retryAuthentication }; +function remoteErrorCause(e: any): [any, number] { + let errorCause = e; + let depth = 0; + while (errorCause instanceof grpcErrors.ErrorPolykeyRemote) { + errorCause = errorCause.cause; + depth++; + } + return [errorCause, depth]; +} + +export { + verboseToLogLevel, + outputFormatter, + retryAuthentication, + remoteErrorCause, +}; export type { OutputObject }; diff --git a/src/bootstrap/errors.ts b/src/bootstrap/errors.ts index 1e24566a2..c2e25289c 100644 --- a/src/bootstrap/errors.ts +++ b/src/bootstrap/errors.ts @@ -1,9 +1,9 @@ import { ErrorPolykey, sysexits } from '../errors'; -class ErrorBootstrap extends ErrorPolykey {} +class ErrorBootstrap extends ErrorPolykey {} -class ErrorBootstrapExistingState extends ErrorBootstrap { - description = 'Node path is occupied with existing state'; +class ErrorBootstrapExistingState extends ErrorBootstrap { + static description = 'Node path is occupied with existing state'; exitCode = sysexits.USAGE; } diff --git a/src/bootstrap/utils.ts b/src/bootstrap/utils.ts index 422709b01..60844fc19 100644 --- a/src/bootstrap/utils.ts +++ b/src/bootstrap/utils.ts @@ -4,6 +4,7 @@ import path from 'path'; import Logger from '@matrixai/logger'; import { DB } from '@matrixai/db'; import * as bootstrapErrors from './errors'; +import Queue from '../nodes/Queue'; import { IdentitiesManager } from '../identities'; import { SessionManager } from '../sessions'; import { Status } from '../status'; @@ -141,10 +142,12 @@ async function bootstrapState({ keyManager, logger: logger.getChild(NodeGraph.name), }); + const queue = new Queue({ logger }); const nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, logger: logger.getChild(NodeConnectionManager.name), }); const nodeManager = new NodeManager({ @@ -153,6 +156,7 @@ async function bootstrapState({ nodeGraph, nodeConnectionManager, sigchain, + queue, logger: logger.getChild(NodeManager.name), }); const notificationsManager = diff --git a/src/claims/errors.ts b/src/claims/errors.ts index 769911597..95b03c74a 100644 --- a/src/claims/errors.ts +++ b/src/claims/errors.ts @@ -1,54 +1,99 @@ -import { ErrorPolykey } from '../errors'; +import { ErrorPolykey, sysexits } from '../errors'; -class ErrorClaims extends ErrorPolykey {} +class ErrorClaims extends ErrorPolykey {} -class ErrorClaimsUndefinedCanonicalizedClaim extends ErrorClaims {} +class ErrorClaimsUndefinedCanonicalizedClaim extends ErrorClaims { + static description = 'Could not canonicalize claim'; + exitCode = sysexits.UNKNOWN; +} -class ErrorClaimsUndefinedClaimPayload extends ErrorClaims {} +class ErrorClaimsUndefinedClaimPayload extends ErrorClaims { + static description = 'Missing claim payload'; + exitCode = sysexits.UNKNOWN; +} -class ErrorClaimsUndefinedSignatureHeader extends ErrorClaims {} +class ErrorClaimsUndefinedSignatureHeader extends ErrorClaims { + static description = 'Missing signature header'; + exitCode = sysexits.UNKNOWN; +} /** * Exceptions arising in cross-signing process (GRPC) */ -class ErrorCrossSign extends ErrorClaims {} +class ErrorCrossSign extends ErrorClaims {} -class ErrorEmptyStream extends ErrorCrossSign {} +class ErrorEmptyStream extends ErrorCrossSign { + static description = 'Unexpected end of stream'; + exitCode = sysexits.IOERR; +} -class ErrorUndefinedSinglySignedClaim extends ErrorCrossSign { - description: string = 'An expected singly signed claim was not received'; +class ErrorUndefinedSinglySignedClaim extends ErrorCrossSign { + static description: string = + 'An expected singly signed claim was not received'; + exitCode = sysexits.USAGE; } -class ErrorUndefinedDoublySignedClaim extends ErrorCrossSign { - description: string = 'An expected doubly signed claim was not received'; +class ErrorUndefinedDoublySignedClaim extends ErrorCrossSign { + static description: string = + 'An expected doubly signed claim was not received'; + exitCode = sysexits.USAGE; } -class ErrorUndefinedSignature extends ErrorCrossSign { - description: string = 'A received claim does not have an expected signature'; +class ErrorUndefinedSignature extends ErrorCrossSign { + static description: string = + 'A received claim does not have an expected signature'; + exitCode = sysexits.CONFIG; } -class ErrorSinglySignedClaimVerificationFailed extends ErrorCrossSign {} +class ErrorSinglySignedClaimVerificationFailed extends ErrorCrossSign { + static description = 'Unable to verify intermediary claim'; + exitCode = sysexits.CONFIG; +} -class ErrorDoublySignedClaimVerificationFailed extends ErrorCrossSign {} +class ErrorDoublySignedClaimVerificationFailed extends ErrorCrossSign { + static description = 'Unable to verify claim'; + exitCode = sysexits.CONFIG; +} /** * Exceptions arising during schema validation */ -class ErrorSchemaValidate extends ErrorClaims {} +class ErrorSchemaValidate extends ErrorClaims {} -class ErrorClaimValidationFailed extends ErrorSchemaValidate {} +class ErrorClaimValidationFailed extends ErrorSchemaValidate { + static description = 'Claim data does not match schema'; + exitCode = sysexits.CONFIG; +} -class ErrorNodesClaimType extends ErrorSchemaValidate {} +class ErrorNodesClaimType extends ErrorSchemaValidate { + static description = 'Invalid claim type'; + exitCode = sysexits.CONFIG; +} -class ErrorIdentitiesClaimType extends ErrorSchemaValidate {} +class ErrorIdentitiesClaimType extends ErrorSchemaValidate { + static description = 'Invalid claim type'; + exitCode = sysexits.CONFIG; +} -class ErrorSinglySignedClaimNumSignatures extends ErrorSchemaValidate {} +class ErrorSinglySignedClaimNumSignatures extends ErrorSchemaValidate { + static description = 'Claim is not signed or has more than one signature'; + exitCode = sysexits.CONFIG; +} -class ErrorDoublySignedClaimNumSignatures extends ErrorSchemaValidate {} +class ErrorDoublySignedClaimNumSignatures extends ErrorSchemaValidate { + static description = 'Claim is not signed or does not have two signatures'; + exitCode = sysexits.CONFIG; +} -class ErrorSinglySignedClaimValidationFailed extends ErrorSchemaValidate {} +class ErrorSinglySignedClaimValidationFailed extends ErrorSchemaValidate { + static description = 'Claim data does not match schema'; + exitCode = sysexits.CONFIG; +} -class ErrorDoublySignedClaimValidationFailed extends ErrorSchemaValidate {} +class ErrorDoublySignedClaimValidationFailed extends ErrorSchemaValidate { + static description = 'Claim data does not match schema'; + exitCode = sysexits.CONFIG; +} export { ErrorClaims, diff --git a/src/claims/utils.ts b/src/claims/utils.ts index faee8ea4b..ea5ecf15d 100644 --- a/src/claims/utils.ts +++ b/src/claims/utils.ts @@ -62,7 +62,7 @@ async function createClaim({ const byteEncoder = new TextEncoder(); const claim = new GeneralSign(byteEncoder.encode(canonicalizedPayload)); claim - .addSignature(await createPrivateKey(privateKey)) + .addSignature(createPrivateKey(privateKey)) .setProtectedHeader({ alg: alg, kid: kid }); const signedClaim = await claim.sign(); return signedClaim as ClaimEncoded; @@ -83,14 +83,14 @@ async function signExistingClaim({ kid: NodeIdEncoded; alg?: string; }): Promise { - const decodedClaim = await decodeClaim(claim); + const decodedClaim = decodeClaim(claim); // Reconstruct the claim with our own signature // Make the payload contents deterministic const canonicalizedPayload = canonicalize(decodedClaim.payload); const byteEncoder = new TextEncoder(); const newClaim = new GeneralSign(byteEncoder.encode(canonicalizedPayload)); newClaim - .addSignature(await createPrivateKey(privateKey)) + .addSignature(createPrivateKey(privateKey)) .setProtectedHeader({ alg: alg, kid: kid }); const signedClaim = await newClaim.sign(); // Add our signature to the existing claim diff --git a/src/client/GRPCClientClient.ts b/src/client/GRPCClientClient.ts index 3b07305ea..2a0a4626f 100644 --- a/src/client/GRPCClientClient.ts +++ b/src/client/GRPCClientClient.ts @@ -3,7 +3,7 @@ import type { ClientReadableStream } from '@grpc/grpc-js/build/src/call'; import type { AsyncGeneratorReadableStreamClient } from '../grpc/types'; import type { Session } from '../sessions'; import type { NodeId } from '../nodes/types'; -import type { Host, Port, TLSConfig, ProxyConfig } from '../network/types'; +import type { Host, Port, ProxyConfig, TLSConfig } from '../network/types'; import type * as utilsPB from '../proto/js/polykey/v1/utils/utils_pb'; import type * as agentPB from '../proto/js/polykey/v1/agent/agent_pb'; import type * as vaultsPB from '../proto/js/polykey/v1/vaults/vaults_pb'; @@ -14,6 +14,7 @@ import type * as identitiesPB from '../proto/js/polykey/v1/identities/identities import type * as keysPB from '../proto/js/polykey/v1/keys/keys_pb'; import type * as permissionsPB from '../proto/js/polykey/v1/permissions/permissions_pb'; import type * as secretsPB from '../proto/js/polykey/v1/secrets/secrets_pb'; +import type { Timer } from '../types'; import { CreateDestroy, ready } from '@matrixai/async-init/dist/CreateDestroy'; import Logger from '@matrixai/logger'; import * as clientErrors from './errors'; @@ -38,7 +39,7 @@ class GRPCClientClient extends GRPCClient { tlsConfig, proxyConfig, session, - timeout = Infinity, + timer, destroyCallback = async () => {}, logger = new Logger(this.name), }: { @@ -48,7 +49,7 @@ class GRPCClientClient extends GRPCClient { tlsConfig?: Partial; proxyConfig?: ProxyConfig; session?: Session; - timeout?: number; + timer?: Timer; destroyCallback?: () => Promise; logger?: Logger; }): Promise { @@ -64,11 +65,11 @@ class GRPCClientClient extends GRPCClient { port, tlsConfig, proxyConfig, - timeout, + timer, interceptors, logger, }); - const grpcClientClient = new GRPCClientClient({ + return new GRPCClientClient({ client, nodeId, host, @@ -80,7 +81,6 @@ class GRPCClientClient extends GRPCClient { destroyCallback, logger, }); - return grpcClientClient; } public async destroy() { @@ -91,6 +91,12 @@ class GRPCClientClient extends GRPCClient { public agentStatus(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.agentStatus.name, + }, this.client.agentStatus, )(...args); } @@ -99,6 +105,12 @@ class GRPCClientClient extends GRPCClient { public agentStop(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.agentStop.name, + }, this.client.agentStop, )(...args); } @@ -107,6 +119,12 @@ class GRPCClientClient extends GRPCClient { public agentUnlock(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.agentUnlock.name, + }, this.client.agentUnlock, )(...args); } @@ -115,6 +133,12 @@ class GRPCClientClient extends GRPCClient { public agentLockAll(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.agentLockAll.name, + }, this.client.agentLockAll, )(...args); } @@ -128,6 +152,12 @@ class GRPCClientClient extends GRPCClient { > { return grpcUtils.promisifyReadableStreamCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsList.name, + }, this.client.vaultsList, )(...args); } @@ -136,6 +166,12 @@ class GRPCClientClient extends GRPCClient { public vaultsCreate(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsCreate.name, + }, this.client.vaultsCreate, )(...args); } @@ -144,6 +180,12 @@ class GRPCClientClient extends GRPCClient { public vaultsRename(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsRename.name, + }, this.client.vaultsRename, )(...args); } @@ -152,6 +194,12 @@ class GRPCClientClient extends GRPCClient { public vaultsDelete(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsDelete.name, + }, this.client.vaultsDelete, )(...args); } @@ -160,6 +208,12 @@ class GRPCClientClient extends GRPCClient { public vaultsClone(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsClone.name, + }, this.client.vaultsClone, )(...args); } @@ -168,6 +222,12 @@ class GRPCClientClient extends GRPCClient { public vaultsPull(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsPull.name, + }, this.client.vaultsPull, )(...args); } @@ -181,6 +241,12 @@ class GRPCClientClient extends GRPCClient { > { return grpcUtils.promisifyReadableStreamCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsScan.name, + }, this.client.vaultsScan, )(...args); } @@ -189,6 +255,12 @@ class GRPCClientClient extends GRPCClient { public vaultsPermissionGet(...args) { return grpcUtils.promisifyReadableStreamCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsPermissionGet.name, + }, this.client.vaultsPermissionGet, )(...args); } @@ -197,6 +269,12 @@ class GRPCClientClient extends GRPCClient { public vaultsPermissionSet(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsPermissionSet.name, + }, this.client.vaultsPermissionSet, )(...args); } @@ -205,6 +283,12 @@ class GRPCClientClient extends GRPCClient { public vaultsPermissionUnset(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsPermissionUnset.name, + }, this.client.vaultsPermissionUnset, )(...args); } @@ -218,14 +302,26 @@ class GRPCClientClient extends GRPCClient { > { return grpcUtils.promisifyReadableStreamCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsSecretsList.name, + }, this.client.vaultsSecretsList, )(...args); } @ready(new clientErrors.ErrorClientClientDestroyed()) public vaultsSecretsMkdir(...args) { - return grpcUtils.promisifyUnaryCall( + return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsSecretsMkdir.name, + }, this.client.vaultsSecretsMkdir, )(...args); } @@ -234,6 +330,12 @@ class GRPCClientClient extends GRPCClient { public vaultsSecretsDelete(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsSecretsDelete.name, + }, this.client.vaultsSecretsDelete, )(...args); } @@ -242,6 +344,12 @@ class GRPCClientClient extends GRPCClient { public vaultsSecretsEdit(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsSecretsEdit.name, + }, this.client.vaultsSecretsEdit, )(...args); } @@ -250,6 +358,12 @@ class GRPCClientClient extends GRPCClient { public vaultsSecretsGet(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsSecretsGet.name, + }, this.client.vaultsSecretsGet, )(...args); } @@ -258,6 +372,12 @@ class GRPCClientClient extends GRPCClient { public vaultsSecretsStat(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsSecretsStat.name, + }, this.client.vaultsSecretsStat, )(...args); } @@ -266,6 +386,12 @@ class GRPCClientClient extends GRPCClient { public vaultsSecretsRename(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsSecretsRename.name, + }, this.client.vaultsSecretsRename, )(...args); } @@ -274,6 +400,12 @@ class GRPCClientClient extends GRPCClient { public vaultsSecretsNew(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsSecretsNew.name, + }, this.client.vaultsSecretsNew, )(...args); } @@ -282,6 +414,12 @@ class GRPCClientClient extends GRPCClient { public vaultsSecretsNewDir(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsSecretsNewDir.name, + }, this.client.vaultsSecretsNewDir, )(...args); } @@ -290,6 +428,12 @@ class GRPCClientClient extends GRPCClient { public vaultsVersion(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsVersion.name, + }, this.client.vaultsVersion, )(...args); } @@ -303,6 +447,12 @@ class GRPCClientClient extends GRPCClient { > { return grpcUtils.promisifyReadableStreamCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.vaultsLog.name, + }, this.client.vaultsLog, )(...args); } @@ -311,6 +461,12 @@ class GRPCClientClient extends GRPCClient { public keysKeyPairRoot(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.keysKeyPairRoot.name, + }, this.client.keysKeyPairRoot, )(...args); } @@ -319,6 +475,12 @@ class GRPCClientClient extends GRPCClient { public keysKeyPairReset(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.keysKeyPairReset.name, + }, this.client.keysKeyPairReset, )(...args); } @@ -327,6 +489,12 @@ class GRPCClientClient extends GRPCClient { public keysKeyPairRenew(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.keysKeyPairRenew.name, + }, this.client.keysKeyPairRenew, )(...args); } @@ -335,6 +503,12 @@ class GRPCClientClient extends GRPCClient { public keysEncrypt(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.keysEncrypt.name, + }, this.client.keysEncrypt, )(...args); } @@ -343,6 +517,12 @@ class GRPCClientClient extends GRPCClient { public keysDecrypt(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.keysDecrypt.name, + }, this.client.keysDecrypt, )(...args); } @@ -351,6 +531,12 @@ class GRPCClientClient extends GRPCClient { public keysSign(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.keysSign.name, + }, this.client.keysSign, )(...args); } @@ -359,6 +545,12 @@ class GRPCClientClient extends GRPCClient { public keysVerify(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.keysVerify.name, + }, this.client.keysVerify, )(...args); } @@ -367,6 +559,12 @@ class GRPCClientClient extends GRPCClient { public keysPasswordChange(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.keysPasswordChange.name, + }, this.client.keysPasswordChange, )(...args); } @@ -375,6 +573,12 @@ class GRPCClientClient extends GRPCClient { public keysCertsGet(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.keysCertsGet.name, + }, this.client.keysCertsGet, )(...args); } @@ -388,6 +592,12 @@ class GRPCClientClient extends GRPCClient { > { return grpcUtils.promisifyReadableStreamCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.keysCertsGet.name, + }, this.client.keysCertsChainGet, )(...args); } @@ -401,6 +611,12 @@ class GRPCClientClient extends GRPCClient { > { return grpcUtils.promisifyReadableStreamCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.gestaltsGestaltList.name, + }, this.client.gestaltsGestaltList, )(...args); } @@ -409,6 +625,12 @@ class GRPCClientClient extends GRPCClient { public gestaltsGestaltGetByIdentity(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.gestaltsGestaltGetByIdentity.name, + }, this.client.gestaltsGestaltGetByIdentity, )(...args); } @@ -417,6 +639,12 @@ class GRPCClientClient extends GRPCClient { public gestaltsGestaltGetByNode(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.gestaltsGestaltGetByNode.name, + }, this.client.gestaltsGestaltGetByNode, )(...args); } @@ -425,6 +653,12 @@ class GRPCClientClient extends GRPCClient { public gestaltsDiscoveryByNode(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.gestaltsDiscoveryByNode.name, + }, this.client.gestaltsDiscoveryByNode, )(...args); } @@ -433,6 +667,12 @@ class GRPCClientClient extends GRPCClient { public gestaltsDiscoveryByIdentity(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.gestaltsDiscoveryByIdentity.name, + }, this.client.gestaltsDiscoveryByIdentity, )(...args); } @@ -441,6 +681,12 @@ class GRPCClientClient extends GRPCClient { public gestaltsActionsGetByNode(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.gestaltsActionsGetByNode.name, + }, this.client.gestaltsActionsGetByNode, )(...args); } @@ -449,6 +695,12 @@ class GRPCClientClient extends GRPCClient { public gestaltsActionsGetByIdentity(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.gestaltsActionsGetByIdentity.name, + }, this.client.gestaltsActionsGetByIdentity, )(...args); } @@ -457,6 +709,12 @@ class GRPCClientClient extends GRPCClient { public gestaltsActionsSetByNode(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.gestaltsActionsSetByNode.name, + }, this.client.gestaltsActionsSetByNode, )(...args); } @@ -465,6 +723,12 @@ class GRPCClientClient extends GRPCClient { public gestaltsActionsSetByIdentity(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.gestaltsActionsSetByIdentity.name, + }, this.client.gestaltsActionsSetByIdentity, )(...args); } @@ -473,6 +737,12 @@ class GRPCClientClient extends GRPCClient { public gestaltsActionsUnsetByNode(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.gestaltsActionsUnsetByNode.name, + }, this.client.gestaltsActionsUnsetByNode, )(...args); } @@ -481,6 +751,12 @@ class GRPCClientClient extends GRPCClient { public gestaltsActionsUnsetByIdentity(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.gestaltsActionsUnsetByIdentity.name, + }, this.client.gestaltsActionsUnsetByIdentity, )(...args); } @@ -489,6 +765,12 @@ class GRPCClientClient extends GRPCClient { public gestaltsGestaltTrustByNode(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.gestaltsGestaltTrustByNode.name, + }, this.client.gestaltsGestaltTrustByNode, )(...args); } @@ -497,6 +779,12 @@ class GRPCClientClient extends GRPCClient { public gestaltsGestaltTrustByIdentity(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.gestaltsGestaltTrustByIdentity.name, + }, this.client.gestaltsGestaltTrustByIdentity, )(...args); } @@ -505,6 +793,12 @@ class GRPCClientClient extends GRPCClient { public identitiesTokenPut(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.identitiesTokenPut.name, + }, this.client.identitiesTokenPut, )(...args); } @@ -513,6 +807,12 @@ class GRPCClientClient extends GRPCClient { public identitiesTokenGet(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.identitiesTokenGet.name, + }, this.client.identitiesTokenGet, )(...args); } @@ -521,6 +821,12 @@ class GRPCClientClient extends GRPCClient { public identitiesTokenDelete(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.identitiesTokenDelete.name, + }, this.client.identitiesTokenDelete, )(...args); } @@ -529,6 +835,12 @@ class GRPCClientClient extends GRPCClient { public identitiesProvidersList(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.identitiesProvidersList.name, + }, this.client.identitiesProvidersList, )(...args); } @@ -537,6 +849,12 @@ class GRPCClientClient extends GRPCClient { public nodesAdd(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.nodesAdd.name, + }, this.client.nodesAdd, )(...args); } @@ -545,6 +863,12 @@ class GRPCClientClient extends GRPCClient { public nodesPing(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.nodesPing.name, + }, this.client.nodesPing, )(...args); } @@ -553,6 +877,12 @@ class GRPCClientClient extends GRPCClient { public nodesClaim(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.nodesClaim.name, + }, this.client.nodesClaim, )(...args); } @@ -561,14 +891,40 @@ class GRPCClientClient extends GRPCClient { public nodesFind(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.nodesFind.name, + }, this.client.nodesFind, )(...args); } + @ready(new clientErrors.ErrorClientClientDestroyed()) + public nodesGetAll(...args) { + return grpcUtils.promisifyUnaryCall( + this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.nodesGetAll.name, + }, + this.client.nodesGetAll, + )(...args); + } + @ready(new clientErrors.ErrorClientClientDestroyed()) public identitiesAuthenticate(...args) { return grpcUtils.promisifyReadableStreamCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.identitiesAuthenticate.name, + }, this.client.identitiesAuthenticate, )(...args); } @@ -577,6 +933,12 @@ class GRPCClientClient extends GRPCClient { public identitiesInfoConnectedGet(...args) { return grpcUtils.promisifyReadableStreamCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.identitiesInfoConnectedGet.name, + }, this.client.identitiesInfoConnectedGet, )(...args); } @@ -585,6 +947,12 @@ class GRPCClientClient extends GRPCClient { public identitiesInfoGet(...args) { return grpcUtils.promisifyReadableStreamCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.identitiesInfoGet.name, + }, this.client.identitiesInfoGet, )(...args); } @@ -593,6 +961,12 @@ class GRPCClientClient extends GRPCClient { public identitiesClaim(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.identitiesClaim.name, + }, this.client.identitiesClaim, )(...args); } @@ -601,6 +975,12 @@ class GRPCClientClient extends GRPCClient { public identitiesAuthenticatedGet(...args) { return grpcUtils.promisifyReadableStreamCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.identitiesAuthenticatedGet.name, + }, this.client.identitiesAuthenticatedGet, )(...args); } @@ -609,6 +989,12 @@ class GRPCClientClient extends GRPCClient { public notificationsSend(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.notificationsSend.name, + }, this.client.notificationsSend, )(...args); } @@ -617,6 +1003,12 @@ class GRPCClientClient extends GRPCClient { public notificationsRead(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.notificationsRead.name, + }, this.client.notificationsRead, )(...args); } @@ -625,6 +1017,12 @@ class GRPCClientClient extends GRPCClient { public notificationsClear(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.notificationsClear.name, + }, this.client.notificationsClear, )(...args); } diff --git a/src/client/errors.ts b/src/client/errors.ts index 246ddb084..13baa73b0 100644 --- a/src/client/errors.ts +++ b/src/client/errors.ts @@ -1,24 +1,24 @@ import { ErrorPolykey, sysexits } from '../errors'; -class ErrorClient extends ErrorPolykey {} +class ErrorClient extends ErrorPolykey {} -class ErrorClientClientDestroyed extends ErrorClient { - description = 'GRPCClientClient has been destroyed'; +class ErrorClientClientDestroyed extends ErrorClient { + static description = 'GRPCClientClient has been destroyed'; exitCode = sysexits.USAGE; } -class ErrorClientAuthMissing extends ErrorClient { - description = 'Authorisation metadata is required but missing'; +class ErrorClientAuthMissing extends ErrorClient { + static description = 'Authorisation metadata is required but missing'; exitCode = sysexits.NOPERM; } -class ErrorClientAuthFormat extends ErrorClient { - description = 'Authorisation metadata has invalid format'; +class ErrorClientAuthFormat extends ErrorClient { + static description = 'Authorisation metadata has invalid format'; exitCode = sysexits.USAGE; } -class ErrorClientAuthDenied extends ErrorClient { - description = 'Authorisation metadata is incorrect or expired'; +class ErrorClientAuthDenied extends ErrorClient { + static description = 'Authorisation metadata is incorrect or expired'; exitCode = sysexits.NOPERM; } diff --git a/src/client/service/agentLockAll.ts b/src/client/service/agentLockAll.ts index dd80ebf62..2c2c7505e 100644 --- a/src/client/service/agentLockAll.ts +++ b/src/client/service/agentLockAll.ts @@ -1,15 +1,22 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type { SessionManager } from '../../sessions'; +import type Logger from '@matrixai/logger'; import * as grpcUtils from '../../grpc/utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function agentLockAll({ - sessionManager, authenticate, + sessionManager, + db, + logger, }: { - sessionManager: SessionManager; authenticate: Authenticate; + sessionManager: SessionManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -19,11 +26,15 @@ function agentLockAll({ const response = new utilsPB.EmptyMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - await sessionManager.resetKey(); + await db.withTransactionF( + async (tran) => await sessionManager.resetKey(tran), + ); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${agentLockAll.name}:${e}`); return; } }; diff --git a/src/client/service/agentStatus.ts b/src/client/service/agentStatus.ts index e90042ebd..3ebb00b5d 100644 --- a/src/client/service/agentStatus.ts +++ b/src/client/service/agentStatus.ts @@ -4,10 +4,12 @@ import type KeyManager from '../../keys/KeyManager'; import type GRPCServer from '../../grpc/GRPCServer'; import type Proxy from '../../network/Proxy'; import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import type Logger from '@matrixai/logger'; import process from 'process'; import * as grpcUtils from '../../grpc/utils'; import * as nodesUtils from '../../nodes/utils'; import * as agentPB from '../../proto/js/polykey/v1/agent/agent_pb'; +import * as clientUtils from '../utils'; function agentStatus({ authenticate, @@ -15,12 +17,14 @@ function agentStatus({ grpcServerClient, grpcServerAgent, proxy, + logger, }: { authenticate: Authenticate; keyManager: KeyManager; grpcServerClient: GRPCServer; grpcServerAgent: GRPCServer; proxy: Proxy; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -46,6 +50,8 @@ function agentStatus({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${agentStatus.name}:${e}`); return; } }; diff --git a/src/client/service/agentStop.ts b/src/client/service/agentStop.ts index 6f0c83bdc..2332c732e 100644 --- a/src/client/service/agentStop.ts +++ b/src/client/service/agentStop.ts @@ -1,16 +1,20 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; import type PolykeyAgent from '../../PolykeyAgent'; +import type Logger from '@matrixai/logger'; import { status, running } from '@matrixai/async-init'; import * as grpcUtils from '../../grpc/utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function agentStop({ authenticate, pkAgent, + logger, }: { authenticate: Authenticate; pkAgent: PolykeyAgent; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -29,6 +33,8 @@ function agentStop({ callback(null, response); } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${agentStop.name}:${e}`); return; } // Stop is called after GRPC resources are cleared diff --git a/src/client/service/agentUnlock.ts b/src/client/service/agentUnlock.ts index a3ff33ddd..991a51c9f 100644 --- a/src/client/service/agentUnlock.ts +++ b/src/client/service/agentUnlock.ts @@ -1,9 +1,17 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; +import type Logger from '@matrixai/logger'; import * as grpcUtils from '../../grpc/utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; -function agentUnlock({ authenticate }: { authenticate: Authenticate }) { +function agentUnlock({ + authenticate, + logger, +}: { + authenticate: Authenticate; + logger: Logger; +}) { return async ( call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, @@ -16,6 +24,8 @@ function agentUnlock({ authenticate }: { authenticate: Authenticate }) { return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${agentUnlock.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsActionsGetByIdentity.ts b/src/client/service/gestaltsActionsGetByIdentity.ts index c4df02f2c..3375ed15d 100644 --- a/src/client/service/gestaltsActionsGetByIdentity.ts +++ b/src/client/service/gestaltsActionsGetByIdentity.ts @@ -1,19 +1,27 @@ +import type Logger from '@matrixai/logger'; import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type { GestaltGraph } from '../../gestalts'; +import type GestaltGraph from '../../gestalts/GestaltGraph'; import type { IdentityId, ProviderId } from '../../identities/types'; import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; -import { matchSync } from '../../utils'; +import { matchSync } from '../../utils/matchers'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; +import * as grpcUtils from '../../grpc/utils'; import * as permissionsPB from '../../proto/js/polykey/v1/permissions/permissions_pb'; +import * as clientUtils from '../utils'; function gestaltsActionsGetByIdentity({ authenticate, gestaltGraph, + db, + logger, }: { authenticate: Authenticate; gestaltGraph: GestaltGraph; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -39,9 +47,9 @@ function gestaltsActionsGetByIdentity({ identityId: call.request.getIdentityId(), }, ); - const result = await gestaltGraph.getGestaltActionsByIdentity( - providerId, - identityId, + + const result = await db.withTransactionF(async (tran) => + gestaltGraph.getGestaltActionsByIdentity(providerId, identityId, tran), ); if (result == null) { // Node doesn't exist, so no permissions @@ -55,6 +63,8 @@ function gestaltsActionsGetByIdentity({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${gestaltsActionsGetByIdentity.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsActionsGetByNode.ts b/src/client/service/gestaltsActionsGetByNode.ts index f4bcd4d5a..ea0e4298d 100644 --- a/src/client/service/gestaltsActionsGetByNode.ts +++ b/src/client/service/gestaltsActionsGetByNode.ts @@ -1,19 +1,27 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type { NodeId } from '../../nodes/types'; -import type { GestaltGraph } from '../../gestalts'; +import type GestaltGraph from '../../gestalts/GestaltGraph'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; import { matchSync } from '../../utils'; import * as permissionsPB from '../../proto/js/polykey/v1/permissions/permissions_pb'; +import * as clientUtils from '../utils'; function gestaltsActionsGetByNode({ authenticate, gestaltGraph, + db, + logger, }: { authenticate: Authenticate; gestaltGraph: GestaltGraph; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -34,7 +42,9 @@ function gestaltsActionsGetByNode({ nodeId: call.request.getNodeId(), }, ); - const result = await gestaltGraph.getGestaltActionsByNode(nodeId); + const result = await db.withTransactionF(async (tran) => + gestaltGraph.getGestaltActionsByNode(nodeId, tran), + ); if (result == null) { // Node doesn't exist, so no permissions response.setActionList([]); @@ -47,6 +57,8 @@ function gestaltsActionsGetByNode({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${gestaltsActionsGetByNode.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsActionsSetByIdentity.ts b/src/client/service/gestaltsActionsSetByIdentity.ts index 0a7637876..b60d3aa84 100644 --- a/src/client/service/gestaltsActionsSetByIdentity.ts +++ b/src/client/service/gestaltsActionsSetByIdentity.ts @@ -1,20 +1,29 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type { GestaltGraph } from '../../gestalts'; +import type GestaltGraph from '../../gestalts/GestaltGraph'; import type { GestaltAction } from '../../gestalts/types'; import type { IdentityId, ProviderId } from '../../identities/types'; import type * as permissionsPB from '../../proto/js/polykey/v1/permissions/permissions_pb'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; +import * as gestaltsErrors from '../../gestalts/errors'; import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function gestaltsActionsSetByIdentity({ authenticate, gestaltGraph, + db, + logger, }: { authenticate: Authenticate; gestaltGraph: GestaltGraph; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -47,15 +56,22 @@ function gestaltsActionsSetByIdentity({ identityId: call.request.getIdentity()?.getIdentityId(), }, ); - await gestaltGraph.setGestaltActionByIdentity( - providerId, - identityId, - action, + await db.withTransactionF(async (tran) => + gestaltGraph.setGestaltActionByIdentity( + providerId, + identityId, + action, + tran, + ), ); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, + gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, + ]) && logger.error(`${gestaltsActionsSetByIdentity.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsActionsSetByNode.ts b/src/client/service/gestaltsActionsSetByNode.ts index f7e2e22fc..187c634a7 100644 --- a/src/client/service/gestaltsActionsSetByNode.ts +++ b/src/client/service/gestaltsActionsSetByNode.ts @@ -1,20 +1,29 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type { GestaltGraph } from '../../gestalts'; +import type GestaltGraph from '../../gestalts/GestaltGraph'; import type { GestaltAction } from '../../gestalts/types'; import type { NodeId } from '../../nodes/types'; import type * as permissionsPB from '../../proto/js/polykey/v1/permissions/permissions_pb'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; +import * as gestaltsErrors from '../../gestalts/errors'; import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function gestaltsActionsSetByNode({ authenticate, gestaltGraph, + db, + logger, }: { authenticate: Authenticate; gestaltGraph: GestaltGraph; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -38,11 +47,16 @@ function gestaltsActionsSetByNode({ action: call.request.getAction(), }, ); - await gestaltGraph.setGestaltActionByNode(nodeId, action); + await db.withTransactionF(async (tran) => + gestaltGraph.setGestaltActionByNode(nodeId, action, tran), + ); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, + ]) && logger.error(`${gestaltsActionsSetByNode.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsActionsUnsetByIdentity.ts b/src/client/service/gestaltsActionsUnsetByIdentity.ts index a247babd8..b2467bee5 100644 --- a/src/client/service/gestaltsActionsUnsetByIdentity.ts +++ b/src/client/service/gestaltsActionsUnsetByIdentity.ts @@ -1,20 +1,29 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type { GestaltGraph } from '../../gestalts'; +import type GestaltGraph from '../../gestalts/GestaltGraph'; import type { GestaltAction } from '../../gestalts/types'; import type { IdentityId, ProviderId } from '../../identities/types'; import type * as permissionsPB from '../../proto/js/polykey/v1/permissions/permissions_pb'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; +import * as gestaltsErrors from '../../gestalts/errors'; import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function gestaltsActionsUnsetByIdentity({ authenticate, gestaltGraph, + db, + logger, }: { authenticate: Authenticate; gestaltGraph: GestaltGraph; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -47,15 +56,22 @@ function gestaltsActionsUnsetByIdentity({ identityId: call.request.getIdentity()?.getIdentityId(), }, ); - await gestaltGraph.unsetGestaltActionByIdentity( - providerId, - identityId, - action, + await db.withTransactionF(async (tran) => + gestaltGraph.unsetGestaltActionByIdentity( + providerId, + identityId, + action, + tran, + ), ); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, + gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, + ]) && logger.error(`${gestaltsActionsUnsetByIdentity.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsActionsUnsetByNode.ts b/src/client/service/gestaltsActionsUnsetByNode.ts index 0add07203..bc39dc569 100644 --- a/src/client/service/gestaltsActionsUnsetByNode.ts +++ b/src/client/service/gestaltsActionsUnsetByNode.ts @@ -1,20 +1,29 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type { GestaltGraph } from '../../gestalts'; +import type GestaltGraph from '../../gestalts/GestaltGraph'; import type { GestaltAction } from '../../gestalts/types'; import type { NodeId } from '../../nodes/types'; import type * as permissionsPB from '../../proto/js/polykey/v1/permissions/permissions_pb'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; +import * as gestaltsErrors from '../../gestalts/errors'; import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function gestaltsActionsUnsetByNode({ authenticate, gestaltGraph, + db, + logger, }: { authenticate: Authenticate; gestaltGraph: GestaltGraph; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -38,11 +47,16 @@ function gestaltsActionsUnsetByNode({ action: call.request.getAction(), }, ); - await gestaltGraph.unsetGestaltActionByNode(nodeId, action); + await db.withTransactionF(async (tran) => + gestaltGraph.unsetGestaltActionByNode(nodeId, action, tran), + ); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, + ]) && logger.error(`${gestaltsActionsUnsetByNode.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsDiscoveryByIdentity.ts b/src/client/service/gestaltsDiscoveryByIdentity.ts index 4ebeae0ce..08f2df64e 100644 --- a/src/client/service/gestaltsDiscoveryByIdentity.ts +++ b/src/client/service/gestaltsDiscoveryByIdentity.ts @@ -1,20 +1,24 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { Discovery } from '../../discovery'; +import type Discovery from '../../discovery/Discovery'; import type { IdentityId, ProviderId } from '../../identities/types'; import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; +import type Logger from '@matrixai/logger'; import { validateSync } from '../../validation'; import { matchSync } from '../../utils'; import * as grpcUtils from '../../grpc/utils'; import * as validationUtils from '../../validation/utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function gestaltsDiscoveryByIdentity({ authenticate, discovery, + logger, }: { authenticate: Authenticate; discovery: Discovery; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -48,6 +52,8 @@ function gestaltsDiscoveryByIdentity({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${gestaltsDiscoveryByIdentity.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsDiscoveryByNode.ts b/src/client/service/gestaltsDiscoveryByNode.ts index c8f900141..f6ed454b0 100644 --- a/src/client/service/gestaltsDiscoveryByNode.ts +++ b/src/client/service/gestaltsDiscoveryByNode.ts @@ -1,20 +1,24 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { Discovery } from '../../discovery'; +import type Discovery from '../../discovery/Discovery'; import type { NodeId } from '../../nodes/types'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; +import type Logger from '@matrixai/logger'; import { validateSync } from '../../validation'; import { matchSync } from '../../utils'; import * as grpcUtils from '../../grpc/utils'; import * as validationUtils from '../../validation/utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function gestaltsDiscoveryByNode({ authenticate, discovery, + logger, }: { authenticate: Authenticate; discovery: Discovery; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -44,6 +48,8 @@ function gestaltsDiscoveryByNode({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${gestaltsDiscoveryByNode.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsGestaltGetByIdentity.ts b/src/client/service/gestaltsGestaltGetByIdentity.ts index 357761c11..8768ad136 100644 --- a/src/client/service/gestaltsGestaltGetByIdentity.ts +++ b/src/client/service/gestaltsGestaltGetByIdentity.ts @@ -1,19 +1,27 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type { GestaltGraph } from '../../gestalts'; +import type GestaltGraph from '../../gestalts/GestaltGraph'; import type { IdentityId, ProviderId } from '../../identities/types'; import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; import { matchSync } from '../../utils'; import * as gestaltsPB from '../../proto/js/polykey/v1/gestalts/gestalts_pb'; +import * as clientUtils from '../utils'; function gestaltsGestaltGetByIdentity({ authenticate, gestaltGraph, + db, + logger, }: { authenticate: Authenticate; gestaltGraph: GestaltGraph; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -42,9 +50,8 @@ function gestaltsGestaltGetByIdentity({ identityId: call.request.getIdentityId(), }, ); - const gestalt = await gestaltGraph.getGestaltByIdentity( - providerId, - identityId, + const gestalt = await db.withTransactionF(async (tran) => + gestaltGraph.getGestaltByIdentity(providerId, identityId, tran), ); if (gestalt != null) { response.setGestaltGraph(JSON.stringify(gestalt)); @@ -53,6 +60,8 @@ function gestaltsGestaltGetByIdentity({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${gestaltsGestaltGetByIdentity.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsGestaltGetByNode.ts b/src/client/service/gestaltsGestaltGetByNode.ts index 424ad0f65..207859fb5 100644 --- a/src/client/service/gestaltsGestaltGetByNode.ts +++ b/src/client/service/gestaltsGestaltGetByNode.ts @@ -1,19 +1,27 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type { NodeId } from '../../nodes/types'; -import type { GestaltGraph } from '../../gestalts'; +import type GestaltGraph from '../../gestalts/GestaltGraph'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; import { matchSync } from '../../utils'; import * as gestaltsPB from '../../proto/js/polykey/v1/gestalts/gestalts_pb'; +import * as clientUtils from '../utils'; function gestaltsGestaltGetByNode({ authenticate, gestaltGraph, + db, + logger, }: { authenticate: Authenticate; gestaltGraph: GestaltGraph; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -38,7 +46,9 @@ function gestaltsGestaltGetByNode({ nodeId: call.request.getNodeId(), }, ); - const gestalt = await gestaltGraph.getGestaltByNode(nodeId); + const gestalt = await db.withTransactionF(async (tran) => + gestaltGraph.getGestaltByNode(nodeId, tran), + ); if (gestalt != null) { response.setGestaltGraph(JSON.stringify(gestalt)); } @@ -46,6 +56,8 @@ function gestaltsGestaltGetByNode({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${gestaltsGestaltGetByNode.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsGestaltList.ts b/src/client/service/gestaltsGestaltList.ts index 458b5cf32..d07fb9f32 100644 --- a/src/client/service/gestaltsGestaltList.ts +++ b/src/client/service/gestaltsGestaltList.ts @@ -1,27 +1,36 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type { GestaltGraph } from '../../gestalts'; +import type GestaltGraph from '../../gestalts/GestaltGraph'; import type { Gestalt } from '../../gestalts/types'; import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import { utils as grpcUtils } from '../../grpc'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; import * as gestaltsPB from '../../proto/js/polykey/v1/gestalts/gestalts_pb'; +import * as clientUtils from '../utils'; function gestaltsGestaltList({ authenticate, gestaltGraph, + db, + logger, }: { authenticate: Authenticate; gestaltGraph: GestaltGraph; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerWritableStream, ): Promise => { - const genWritable = grpcUtils.generatorWritable(call); + const genWritable = grpcUtils.generatorWritable(call, false); let gestaltMessage: gestaltsPB.Gestalt; try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const certs: Array = await gestaltGraph.getGestalts(); + const certs: Array = await db.withTransactionF(async (tran) => + gestaltGraph.getGestalts(tran), + ); for (const cert of certs) { gestaltMessage = new gestaltsPB.Gestalt(); gestaltMessage.setName(JSON.stringify(cert)); @@ -31,6 +40,8 @@ function gestaltsGestaltList({ return; } catch (e) { await genWritable.throw(e); + !clientUtils.isClientClientError(e) && + logger.error(`${gestaltsGestaltList.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsGestaltTrustByIdentity.ts b/src/client/service/gestaltsGestaltTrustByIdentity.ts index 754364f79..06a9eb6c4 100644 --- a/src/client/service/gestaltsGestaltTrustByIdentity.ts +++ b/src/client/service/gestaltsGestaltTrustByIdentity.ts @@ -1,23 +1,31 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type { GestaltGraph } from '../../gestalts'; +import type GestaltGraph from '../../gestalts/GestaltGraph'; import type { IdentityId, ProviderId } from '../../identities/types'; -import type { Discovery } from '../../discovery'; +import type Discovery from '../../discovery/Discovery'; import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; +import type Logger from '@matrixai/logger'; import { validateSync } from '../../validation'; import { matchSync } from '../../utils'; import * as grpcUtils from '../../grpc/utils'; +import * as gestaltsErrors from '../../gestalts/errors'; import * as validationUtils from '../../validation/utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function gestaltsGestaltTrustByIdentity({ authenticate, gestaltGraph, discovery, + db, + logger, }: { authenticate: Authenticate; gestaltGraph: GestaltGraph; discovery: Discovery; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -47,29 +55,39 @@ function gestaltsGestaltTrustByIdentity({ }, ); // Set the identity in the gestalt graph if not already - if ( - (await gestaltGraph.getGestaltByIdentity(providerId, identityId)) == - null - ) { - // Queue the new identity for discovery - // This will only add the identity to the GG if it is connected to a - // node (required to set permissions for it) - await discovery.queueDiscoveryByIdentity(providerId, identityId); - } - // We can currently only set permissions for identities that are - // connected to at least one node. If these conditions are not met, this - // will throw an error. Since discovery can take time, you may need to - // reattempt this command if it fails on the first attempt and you expect - // there to be a linked node for the identity. - await gestaltGraph.setGestaltActionByIdentity( - providerId, - identityId, - 'notify', - ); + await db.withTransactionF(async (tran) => { + if ( + (await gestaltGraph.getGestaltByIdentity( + providerId, + identityId, + tran, + )) == null + ) { + // Queue the new identity for discovery + // This will only add the identity to the GG if it is connected to a + // node (required to set permissions for it) + await discovery.queueDiscoveryByIdentity(providerId, identityId); + } + // We can currently only set permissions for identities that are + // connected to at least one node. If these conditions are not met, this + // will throw an error. Since discovery can take time, you may need to + // reattempt this command if it fails on the first attempt and you expect + // there to be a linked node for the identity. + await gestaltGraph.setGestaltActionByIdentity( + providerId, + identityId, + 'notify', + tran, + ); + }); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, + gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, + ]) && logger.error(`${gestaltsGestaltTrustByIdentity.name}:${e}`); return; } }; diff --git a/src/client/service/gestaltsGestaltTrustByNode.ts b/src/client/service/gestaltsGestaltTrustByNode.ts index b8f03fb87..4e01de903 100644 --- a/src/client/service/gestaltsGestaltTrustByNode.ts +++ b/src/client/service/gestaltsGestaltTrustByNode.ts @@ -1,24 +1,32 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type { GestaltGraph } from '../../gestalts'; -import type { Discovery } from '../../discovery'; +import type GestaltGraph from '../../gestalts/GestaltGraph'; +import type Discovery from '../../discovery/Discovery'; import type { NodeId } from '../../nodes/types'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; +import type Logger from '@matrixai/logger'; import { validateSync } from '../../validation'; import { matchSync } from '../../utils'; import * as grpcUtils from '../../grpc/utils'; +import * as gestaltsErrors from '../../gestalts/errors'; import * as validationUtils from '../../validation/utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; import * as nodesUtils from '../../nodes/utils'; +import * as clientUtils from '../utils'; function gestaltsGestaltTrustByNode({ authenticate, gestaltGraph, discovery, + db, + logger, }: { authenticate: Authenticate; gestaltGraph: GestaltGraph; discovery: Discovery; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -43,21 +51,29 @@ function gestaltsGestaltTrustByNode({ nodeId: call.request.getNodeId(), }, ); - // Set the node in the gestalt graph if not already - if ((await gestaltGraph.getGestaltByNode(nodeId)) == null) { - await gestaltGraph.setNode({ - id: nodesUtils.encodeNodeId(nodeId), - chain: {}, - }); - // Queue the new node for discovery - await discovery.queueDiscoveryByNode(nodeId); - } - // Set notify permission - await gestaltGraph.setGestaltActionByNode(nodeId, 'notify'); + await db.withTransactionF(async (tran) => { + // Set the node in the gestalt graph if not already + if ((await gestaltGraph.getGestaltByNode(nodeId, tran)) == null) { + await gestaltGraph.setNode( + { + id: nodesUtils.encodeNodeId(nodeId), + chain: {}, + }, + tran, + ); + // Queue the new node for discovery + await discovery.queueDiscoveryByNode(nodeId); + } + // Set notify permission + await gestaltGraph.setGestaltActionByNode(nodeId, 'notify', tran); + }); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, + ]) && logger.error(`${gestaltsGestaltTrustByNode.name}:${e}`); return; } }; diff --git a/src/client/service/identitiesAuthenticate.ts b/src/client/service/identitiesAuthenticate.ts index 24ccf2f7e..0950abddd 100644 --- a/src/client/service/identitiesAuthenticate.ts +++ b/src/client/service/identitiesAuthenticate.ts @@ -1,19 +1,24 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { IdentitiesManager } from '../../identities'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; import type { ProviderId } from '../../identities/types'; -import { utils as grpcUtils } from '../../grpc'; -import { errors as identitiesErrors } from '../../identities'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; +import * as identitiesErrors from '../../identities/errors'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; import { matchSync, never } from '../../utils'; import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; +import * as clientUtils from '../utils'; function identitiesAuthenticate({ - identitiesManager, authenticate, + identitiesManager, + logger, }: { - identitiesManager: IdentitiesManager; authenticate: Authenticate; + identitiesManager: IdentitiesManager; + logger: Logger; }) { return async ( call: grpc.ServerWritableStream< @@ -21,7 +26,7 @@ function identitiesAuthenticate({ identitiesPB.AuthenticationProcess >, ): Promise => { - const genWritable = grpcUtils.generatorWritable(call); + const genWritable = grpcUtils.generatorWritable(call, false); try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); @@ -70,6 +75,9 @@ function identitiesAuthenticate({ return; } catch (e) { await genWritable.throw(e); + !clientUtils.isClientClientError(e, [ + identitiesErrors.ErrorProviderMissing, + ]) && logger.error(`${identitiesAuthenticate.name}:${e}`); return; } }; diff --git a/src/client/service/identitiesAuthenticatedGet.ts b/src/client/service/identitiesAuthenticatedGet.ts index 1fd4bb9da..106a1f53a 100644 --- a/src/client/service/identitiesAuthenticatedGet.ts +++ b/src/client/service/identitiesAuthenticatedGet.ts @@ -1,19 +1,23 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { IdentitiesManager } from '../../identities'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; import type { ProviderId } from '../../identities/types'; +import type Logger from '@matrixai/logger'; import { validateSync } from '../../validation'; import { matchSync } from '../../utils'; import * as grpcUtils from '../../grpc/utils'; import * as validationUtils from '../../validation/utils'; import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; +import * as clientUtils from '../utils'; function identitiesAuthenticatedGet({ - identitiesManager, authenticate, + identitiesManager, + logger, }: { - identitiesManager: IdentitiesManager; authenticate: Authenticate; + identitiesManager: IdentitiesManager; + logger: Logger; }) { return async ( call: grpc.ServerWritableStream< @@ -21,7 +25,7 @@ function identitiesAuthenticatedGet({ identitiesPB.Provider >, ): Promise => { - const genWritable = grpcUtils.generatorWritable(call); + const genWritable = grpcUtils.generatorWritable(call, false); try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); @@ -60,6 +64,8 @@ function identitiesAuthenticatedGet({ return; } catch (e) { await genWritable.throw(e); + !clientUtils.isClientClientError(e) && + logger.error(`${identitiesAuthenticatedGet.name}:${e}`); return; } }; diff --git a/src/client/service/identitiesClaim.ts b/src/client/service/identitiesClaim.ts index 0964ecf78..6677c77d4 100644 --- a/src/client/service/identitiesClaim.ts +++ b/src/client/service/identitiesClaim.ts @@ -1,30 +1,38 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type KeyManager from '../../keys/KeyManager'; -import type { Sigchain } from '../../sigchain'; -import type { IdentitiesManager } from '../../identities'; +import type Sigchain from '../../sigchain/Sigchain'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; import type { IdentityId, ProviderId } from '../../identities/types'; -import { utils as grpcUtils } from '../../grpc'; -import { utils as claimsUtils } from '../../claims'; -import { utils as nodesUtils } from '../../nodes'; -import { errors as identitiesErrors } from '../../identities'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; +import * as claimsUtils from '../../claims/utils'; +import * as nodesUtils from '../../nodes/utils'; +import * as identitiesErrors from '../../identities/errors'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; import { matchSync } from '../../utils'; import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; +import * as clientUtils from '../utils'; /** * Augments the keynode with a new identity. */ function identitiesClaim({ + authenticate, identitiesManager, sigchain, keyManager, - authenticate, + db, + logger, }: { + authenticate: Authenticate; identitiesManager: IdentitiesManager; sigchain: Sigchain; keyManager: KeyManager; - authenticate: Authenticate; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -63,12 +71,17 @@ function identitiesClaim({ throw new identitiesErrors.ErrorProviderUnauthenticated(); } // Create identity claim on our node - const [, claim] = await sigchain.addClaim({ - type: 'identity', - node: nodesUtils.encodeNodeId(keyManager.getNodeId()), - provider: providerId, - identity: identityId, - }); + const [, claim] = await db.withTransactionF(async (tran) => + sigchain.addClaim( + { + type: 'identity', + node: nodesUtils.encodeNodeId(keyManager.getNodeId()), + provider: providerId, + identity: identityId, + }, + tran, + ), + ); // Publish claim on identity const claimDecoded = claimsUtils.decodeClaim(claim); const claimData = await provider.publishClaim(identityId, claimDecoded); @@ -80,6 +93,10 @@ function identitiesClaim({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + identitiesErrors.ErrorProviderMissing, + identitiesErrors.ErrorProviderUnauthenticated, + ]) && logger.error(`${identitiesClaim.name}:${e}`); return; } }; diff --git a/src/client/service/identitiesInfoConnectedGet.ts b/src/client/service/identitiesInfoConnectedGet.ts index 399600900..f8f906807 100644 --- a/src/client/service/identitiesInfoConnectedGet.ts +++ b/src/client/service/identitiesInfoConnectedGet.ts @@ -1,24 +1,28 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { IdentitiesManager } from '../../identities'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; import type { IdentityData, IdentityId, ProviderId, } from '../../identities/types'; +import type Logger from '@matrixai/logger'; import { validateSync } from '../../validation'; import { matchSync } from '../../utils'; import * as grpcUtils from '../../grpc/utils'; import * as validationUtils from '../../validation/utils'; import * as identitiesErrors from '../../identities/errors'; import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; +import * as clientUtils from '../utils'; function identitiesInfoConnectedGet({ - identitiesManager, authenticate, + identitiesManager, + logger, }: { - identitiesManager: IdentitiesManager; authenticate: Authenticate; + identitiesManager: IdentitiesManager; + logger: Logger; }) { return async ( call: grpc.ServerWritableStream< @@ -26,7 +30,7 @@ function identitiesInfoConnectedGet({ identitiesPB.Info >, ): Promise => { - const genWritable = grpcUtils.generatorWritable(call); + const genWritable = grpcUtils.generatorWritable(call, false); try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); @@ -116,6 +120,11 @@ function identitiesInfoConnectedGet({ return; } catch (e) { await genWritable.throw(e); + !clientUtils.isClientClientError(e, [ + identitiesErrors.ErrorProviderMissing, + identitiesErrors.ErrorProviderUnauthenticated, + identitiesErrors.ErrorProviderUnimplemented, + ]) && logger.error(`${identitiesInfoConnectedGet.name}:${e}`); return; } }; diff --git a/src/client/service/identitiesInfoGet.ts b/src/client/service/identitiesInfoGet.ts index bbf159f55..3fa2bdbc1 100644 --- a/src/client/service/identitiesInfoGet.ts +++ b/src/client/service/identitiesInfoGet.ts @@ -1,11 +1,12 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { IdentitiesManager } from '../../identities'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; import type { IdentityData, IdentityId, ProviderId, } from '../../identities/types'; +import type Logger from '@matrixai/logger'; import { validateSync } from '../../validation'; import { matchSync } from '../../utils'; import * as grpcUtils from '../../grpc/utils'; @@ -13,13 +14,16 @@ import * as validationUtils from '../../validation/utils'; import * as identitiesUtils from '../../identities/utils'; import * as identitiesErrors from '../../identities/errors'; import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; +import * as clientUtils from '../utils'; function identitiesInfoGet({ - identitiesManager, authenticate, + identitiesManager, + logger, }: { - identitiesManager: IdentitiesManager; authenticate: Authenticate; + identitiesManager: IdentitiesManager; + logger: Logger; }) { return async ( call: grpc.ServerWritableStream< @@ -27,7 +31,7 @@ function identitiesInfoGet({ identitiesPB.Info >, ): Promise => { - const genWritable = grpcUtils.generatorWritable(call); + const genWritable = grpcUtils.generatorWritable(call, false); try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); @@ -109,6 +113,10 @@ function identitiesInfoGet({ return; } catch (e) { await genWritable.throw(e); + !clientUtils.isClientClientError(e, [ + identitiesErrors.ErrorProviderMissing, + identitiesErrors.ErrorProviderUnauthenticated, + ]) && logger.error(`${identitiesInfoGet.name}:${e}`); return; } }; diff --git a/src/client/service/identitiesProvidersList.ts b/src/client/service/identitiesProvidersList.ts index a7ce51051..17ae7bf31 100644 --- a/src/client/service/identitiesProvidersList.ts +++ b/src/client/service/identitiesProvidersList.ts @@ -1,16 +1,20 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { IdentitiesManager } from '../../identities'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import { utils as grpcUtils } from '../../grpc'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; +import * as clientUtils from '../utils'; function identitiesProvidersList({ - identitiesManager, authenticate, + identitiesManager, + logger, }: { - identitiesManager: IdentitiesManager; authenticate: Authenticate; + identitiesManager: IdentitiesManager; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -26,6 +30,8 @@ function identitiesProvidersList({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${identitiesProvidersList.name}:${e}`); return; } }; diff --git a/src/client/service/identitiesTokenDelete.ts b/src/client/service/identitiesTokenDelete.ts index 835fee428..2b4a78b9b 100644 --- a/src/client/service/identitiesTokenDelete.ts +++ b/src/client/service/identitiesTokenDelete.ts @@ -1,19 +1,27 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type { IdentitiesManager } from '../../identities'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; import type { IdentityId, ProviderId } from '../../identities/types'; import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function identitiesTokenDelete({ - identitiesManager, authenticate, + identitiesManager, + db, + logger, }: { - identitiesManager: IdentitiesManager; authenticate: Authenticate; + identitiesManager: IdentitiesManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -42,11 +50,15 @@ function identitiesTokenDelete({ identityId: call.request.getIdentityId(), }, ); - await identitiesManager.delToken(providerId, identityId); + await db.withTransactionF(async (tran) => + identitiesManager.delToken(providerId, identityId, tran), + ); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${identitiesTokenDelete.name}:${e}`); return; } }; diff --git a/src/client/service/identitiesTokenGet.ts b/src/client/service/identitiesTokenGet.ts index 102ae3a07..c829da281 100644 --- a/src/client/service/identitiesTokenGet.ts +++ b/src/client/service/identitiesTokenGet.ts @@ -1,18 +1,26 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type { IdentitiesManager } from '../../identities'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; import type { IdentityId, ProviderId } from '../../identities/types'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; import { matchSync } from '../../utils'; import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; +import * as clientUtils from '../utils'; function identitiesTokenGet({ - identitiesManager, authenticate, + identitiesManager, + db, + logger, }: { - identitiesManager: IdentitiesManager; authenticate: Authenticate; + identitiesManager: IdentitiesManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -41,12 +49,16 @@ function identitiesTokenGet({ identityId: call.request.getIdentityId(), }, ); - const tokens = await identitiesManager.getToken(providerId, identityId); + const tokens = await db.withTransactionF(async (tran) => + identitiesManager.getToken(providerId, identityId, tran), + ); response.setToken(JSON.stringify(tokens)); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${identitiesTokenGet.name}:${e}`); return; } }; diff --git a/src/client/service/identitiesTokenPut.ts b/src/client/service/identitiesTokenPut.ts index 1a371cb95..b7ae0139f 100644 --- a/src/client/service/identitiesTokenPut.ts +++ b/src/client/service/identitiesTokenPut.ts @@ -1,19 +1,27 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type { IdentitiesManager } from '../../identities'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; import type { IdentityId, ProviderId, TokenData } from '../../identities/types'; import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function identitiesTokenPut({ - identitiesManager, authenticate, + identitiesManager, + db, + logger, }: { - identitiesManager: IdentitiesManager; authenticate: Authenticate; + identitiesManager: IdentitiesManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall< @@ -45,13 +53,22 @@ function identitiesTokenPut({ identityId: call.request.getProvider()?.getIdentityId(), }, ); - await identitiesManager.putToken(providerId, identityId, { - accessToken: call.request.getToken(), - } as TokenData); + await db.withTransactionF(async (tran) => + identitiesManager.putToken( + providerId, + identityId, + { + accessToken: call.request.getToken(), + } as TokenData, + tran, + ), + ); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${identitiesTokenPut.name}:${e}`); return; } }; diff --git a/src/client/service/index.ts b/src/client/service/index.ts index 494c5088c..68f98ac8c 100644 --- a/src/client/service/index.ts +++ b/src/client/service/index.ts @@ -1,5 +1,6 @@ +import type { DB } from '@matrixai/db'; import type PolykeyAgent from '../../PolykeyAgent'; -import type { KeyManager } from '../../keys'; +import type KeyManager from '../../keys/KeyManager'; import type { VaultManager } from '../../vaults'; import type { NodeManager, @@ -9,7 +10,7 @@ import type { import type { IdentitiesManager } from '../../identities'; import type { GestaltGraph } from '../../gestalts'; import type { SessionManager } from '../../sessions'; -import type { NotificationsManager } from '../../notifications'; +import type NotificationsManager from '../../notifications/NotificationsManager'; import type { Discovery } from '../../discovery'; import type { Sigchain } from '../../sigchain'; import type { GRPCServer } from '../../grpc'; @@ -58,6 +59,7 @@ import nodesAdd from './nodesAdd'; import nodesClaim from './nodesClaim'; import nodesFind from './nodesFind'; import nodesPing from './nodesPing'; +import nodesGetAll from './nodesGetAll'; import notificationsClear from './notificationsClear'; import notificationsRead from './notificationsRead'; import notificationsSend from './notificationsSend'; @@ -88,11 +90,13 @@ import { ClientServiceService } from '../../proto/js/polykey/v1/client_service_g function createService({ keyManager, sessionManager, - logger = new Logger(createService.name), + db, + logger = new Logger('GRPCClientClientService'), fs = require('fs'), ...containerRest }: { pkAgent: PolykeyAgent; + db: DB; keyManager: KeyManager; vaultManager: VaultManager; nodeGraph: NodeGraph; @@ -116,6 +120,7 @@ function createService({ ...containerRest, keyManager, sessionManager, + db, logger, fs, authenticate, @@ -161,6 +166,7 @@ function createService({ nodesClaim: nodesClaim(container), nodesFind: nodesFind(container), nodesPing: nodesPing(container), + nodesGetAll: nodesGetAll(container), notificationsClear: notificationsClear(container), notificationsRead: notificationsRead(container), notificationsSend: notificationsSend(container), diff --git a/src/client/service/keysCertsChainGet.ts b/src/client/service/keysCertsChainGet.ts index 8355474fd..fc074a239 100644 --- a/src/client/service/keysCertsChainGet.ts +++ b/src/client/service/keysCertsChainGet.ts @@ -1,21 +1,25 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { KeyManager } from '../../keys'; +import type KeyManager from '../../keys/KeyManager'; import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import { utils as grpcUtils } from '../../grpc'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; import * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; +import * as clientUtils from '../utils'; function keysCertsChainGet({ - keyManager, authenticate, + keyManager, + logger, }: { - keyManager: KeyManager; authenticate: Authenticate; + keyManager: KeyManager; + logger: Logger; }) { return async ( call: grpc.ServerWritableStream, ): Promise => { - const genWritable = grpcUtils.generatorWritable(call); + const genWritable = grpcUtils.generatorWritable(call, false); try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); @@ -30,6 +34,8 @@ function keysCertsChainGet({ return; } catch (e) { await genWritable.throw(e); + !clientUtils.isClientClientError(e) && + logger.error(`${keysCertsChainGet.name}:${e}`); return; } }; diff --git a/src/client/service/keysCertsGet.ts b/src/client/service/keysCertsGet.ts index 198a67884..ff5e3d525 100644 --- a/src/client/service/keysCertsGet.ts +++ b/src/client/service/keysCertsGet.ts @@ -1,16 +1,20 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { KeyManager } from '../../keys'; +import type KeyManager from '../../keys/KeyManager'; import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import { utils as grpcUtils } from '../../grpc'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; import * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; +import * as clientUtils from '../utils'; function keysCertsGet({ - keyManager, authenticate, + keyManager, + logger, }: { - keyManager: KeyManager; authenticate: Authenticate; + keyManager: KeyManager; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -26,6 +30,8 @@ function keysCertsGet({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${keysCertsGet.name}:${e}`); return; } }; diff --git a/src/client/service/keysDecrypt.ts b/src/client/service/keysDecrypt.ts index 0aa84d004..c155c0d8d 100644 --- a/src/client/service/keysDecrypt.ts +++ b/src/client/service/keysDecrypt.ts @@ -1,15 +1,19 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { KeyManager } from '../../keys'; -import { utils as grpcUtils } from '../../grpc'; +import type KeyManager from '../../keys/KeyManager'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; import * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; +import * as clientUtils from '../utils'; function keysDecrypt({ - keyManager, authenticate, + keyManager, + logger, }: { - keyManager: KeyManager; authenticate: Authenticate; + keyManager: KeyManager; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -27,6 +31,8 @@ function keysDecrypt({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${keysDecrypt.name}:${e}`); return; } }; diff --git a/src/client/service/keysEncrypt.ts b/src/client/service/keysEncrypt.ts index 8c048e94c..71a5a84ff 100644 --- a/src/client/service/keysEncrypt.ts +++ b/src/client/service/keysEncrypt.ts @@ -1,15 +1,19 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { KeyManager } from '../../keys'; -import { utils as grpcUtils } from '../../grpc'; +import type KeyManager from '../../keys/KeyManager'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; import * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; +import * as clientUtils from '../utils'; function keysEncrypt({ - keyManager, authenticate, + keyManager, + logger, }: { - keyManager: KeyManager; authenticate: Authenticate; + keyManager: KeyManager; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -27,6 +31,8 @@ function keysEncrypt({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${keysEncrypt.name}:${e}`); return; } }; diff --git a/src/client/service/keysKeyPairRenew.ts b/src/client/service/keysKeyPairRenew.ts index ffcd9b5b4..a44df0628 100644 --- a/src/client/service/keysKeyPairRenew.ts +++ b/src/client/service/keysKeyPairRenew.ts @@ -1,16 +1,20 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { KeyManager } from '../../keys'; +import type KeyManager from '../../keys/KeyManager'; import type * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; -import { utils as grpcUtils } from '../../grpc'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function keysKeyPairRenew({ - keyManager, authenticate, + keyManager, + logger, }: { - keyManager: KeyManager; authenticate: Authenticate; + keyManager: KeyManager; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -27,6 +31,8 @@ function keysKeyPairRenew({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${keysKeyPairRenew.name}:${e}`); return; } }; diff --git a/src/client/service/keysKeyPairReset.ts b/src/client/service/keysKeyPairReset.ts index a5fee6145..a1ba20365 100644 --- a/src/client/service/keysKeyPairReset.ts +++ b/src/client/service/keysKeyPairReset.ts @@ -1,16 +1,20 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { KeyManager } from '../../keys'; +import type KeyManager from '../../keys/KeyManager'; import type * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; -import { utils as grpcUtils } from '../../grpc'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function keysKeyPairReset({ - keyManager, authenticate, + keyManager, + logger, }: { - keyManager: KeyManager; authenticate: Authenticate; + keyManager: KeyManager; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -27,6 +31,8 @@ function keysKeyPairReset({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${keysKeyPairReset.name}:${e}`); return; } }; diff --git a/src/client/service/keysKeyPairRoot.ts b/src/client/service/keysKeyPairRoot.ts index 4a5858347..2b1cdce82 100644 --- a/src/client/service/keysKeyPairRoot.ts +++ b/src/client/service/keysKeyPairRoot.ts @@ -1,16 +1,20 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { KeyManager } from '../../keys'; +import type KeyManager from '../../keys/KeyManager'; import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import { utils as grpcUtils } from '../../grpc'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; import * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; +import * as clientUtils from '../utils'; function keysKeyPairRoot({ - keyManager, authenticate, + keyManager, + logger, }: { - keyManager: KeyManager; authenticate: Authenticate; + keyManager: KeyManager; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -27,6 +31,8 @@ function keysKeyPairRoot({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${keysKeyPairRoot.name}:${e}`); return; } }; diff --git a/src/client/service/keysPasswordChange.ts b/src/client/service/keysPasswordChange.ts index 2d5074abf..a7f4ed029 100644 --- a/src/client/service/keysPasswordChange.ts +++ b/src/client/service/keysPasswordChange.ts @@ -1,16 +1,20 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { KeyManager } from '../../keys'; +import type KeyManager from '../../keys/KeyManager'; import type * as sessionsPB from '../../proto/js/polykey/v1/sessions/sessions_pb'; -import { utils as grpcUtils } from '../../grpc'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function keysPasswordChange({ - keyManager, authenticate, + keyManager, + logger, }: { - keyManager: KeyManager; authenticate: Authenticate; + keyManager: KeyManager; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -25,6 +29,8 @@ function keysPasswordChange({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${keysPasswordChange.name}:${e}`); return; } }; diff --git a/src/client/service/keysSign.ts b/src/client/service/keysSign.ts index 5f2eee691..126d0c149 100644 --- a/src/client/service/keysSign.ts +++ b/src/client/service/keysSign.ts @@ -1,15 +1,19 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { KeyManager } from '../../keys'; -import { utils as grpcUtils } from '../../grpc'; +import type KeyManager from '../../keys/KeyManager'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; import * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; +import * as clientUtils from '../utils'; function keysSign({ - keyManager, authenticate, + keyManager, + logger, }: { - keyManager: KeyManager; authenticate: Authenticate; + keyManager: KeyManager; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -28,6 +32,8 @@ function keysSign({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${keysSign.name}:${e}`); return; } }; diff --git a/src/client/service/keysVerify.ts b/src/client/service/keysVerify.ts index 1090642c5..d2c82bfba 100644 --- a/src/client/service/keysVerify.ts +++ b/src/client/service/keysVerify.ts @@ -1,16 +1,20 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { KeyManager } from '../../keys'; +import type KeyManager from '../../keys/KeyManager'; import type * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; -import { utils as grpcUtils } from '../../grpc'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function keysVerify({ - keyManager, authenticate, + keyManager, + logger, }: { - keyManager: KeyManager; authenticate: Authenticate; + keyManager: KeyManager; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -29,6 +33,8 @@ function keysVerify({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${keysVerify.name}:${e}`); return; } }; diff --git a/src/client/service/nodesAdd.ts b/src/client/service/nodesAdd.ts index 57924cd5e..92de5581d 100644 --- a/src/client/service/nodesAdd.ts +++ b/src/client/service/nodesAdd.ts @@ -1,33 +1,43 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type { NodeManager } from '../../nodes'; +import type NodeManager from '../../nodes/NodeManager'; import type { NodeId, NodeAddress } from '../../nodes/types'; import type { Host, Hostname, Port } from '../../network/types'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as nodeErrors from '../../nodes/errors'; +import * as grpcUtils from '../../grpc/utils'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; /** - * Adds a node ID -> node address mapping into the buckets database. + * Adds a node ID -> node address mapping into the buckets' database. * This is an unrestricted add: no validity checks are made for the correctness * of the passed ID or host/port. */ function nodesAdd({ - nodeManager, authenticate, + nodeManager, + db, + logger, }: { - nodeManager: NodeManager; authenticate: Authenticate; + nodeManager: NodeManager; + db: DB; + logger: Logger; }) { return async ( - call: grpc.ServerUnaryCall, + call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { try { const response = new utilsPB.EmptyMessage(); const metadata = await authenticate(call.metadata); + const request = call.request; call.sendMetadata(metadata); const { nodeId, @@ -47,19 +57,40 @@ function nodesAdd({ ); }, { - nodeId: call.request.getNodeId(), - host: call.request.getAddress()?.getHost(), - port: call.request.getAddress()?.getPort(), + nodeId: request.getNodeId(), + host: request.getAddress()?.getHost(), + port: request.getAddress()?.getPort(), }, ); - await nodeManager.setNode(nodeId, { - host, - port, - } as NodeAddress); + // Pinging to authenticate the node + if ( + request.getPing() && + !(await nodeManager.pingNode(nodeId, { host, port })) + ) { + throw new nodeErrors.ErrorNodePingFailed( + 'Failed to authenticate target node', + ); + } + + await db.withTransactionF(async (tran) => + nodeManager.setNode( + nodeId, + { + host, + port, + } as NodeAddress, + true, + request.getForce(), + undefined, + tran, + ), + ); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${nodesAdd.name}:${e}`); return; } }; diff --git a/src/client/service/nodesClaim.ts b/src/client/service/nodesClaim.ts index 280dedb24..991ed9548 100644 --- a/src/client/service/nodesClaim.ts +++ b/src/client/service/nodesClaim.ts @@ -1,13 +1,18 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type { NodeManager } from '../../nodes'; +import type NodeManager from '../../nodes/NodeManager'; import type { NodeId } from '../../nodes/types'; -import type { NotificationsManager } from '../../notifications'; +import type NotificationsManager from '../../notifications/NotificationsManager'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; +import * as nodesErrors from '../../nodes/errors'; import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; /** * Checks whether there is an existing Gestalt Invitation from the other node. @@ -15,13 +20,17 @@ import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; * other node and host node. */ function nodesClaim({ + authenticate, nodeManager, notificationsManager, - authenticate, + db, + logger, }: { + authenticate: Authenticate; nodeManager: NodeManager; notificationsManager: NotificationsManager; - authenticate: Authenticate; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -46,25 +55,31 @@ function nodesClaim({ nodeId: call.request.getNodeId(), }, ); - const gestaltInvite = await notificationsManager.findGestaltInvite( - nodeId, - ); - // Check first whether there is an existing gestalt invite from the remote node - // or if we want to force an invitation rather than a claim - if (gestaltInvite === undefined || call.request.getForceInvite()) { - await notificationsManager.sendNotification(nodeId, { - type: 'GestaltInvite', - }); - response.setSuccess(false); - } else { - // There is an existing invitation, and we want to claim the node - await nodeManager.claimNode(nodeId); - response.setSuccess(true); - } + await db.withTransactionF(async (tran) => { + const gestaltInvite = await notificationsManager.findGestaltInvite( + nodeId, + tran, + ); + // Check first whether there is an existing gestalt invite from the remote node + // or if we want to force an invitation rather than a claim + if (gestaltInvite === undefined || call.request.getForceInvite()) { + await notificationsManager.sendNotification(nodeId, { + type: 'GestaltInvite', + }); + response.setSuccess(false); + } else { + // There is an existing invitation, and we want to claim the node + await nodeManager.claimNode(nodeId, tran); + response.setSuccess(true); + } + }); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + nodesErrors.ErrorNodeGraphNodeIdNotFound, + ]) && logger.error(`${nodesClaim.name}:${e}`); return; } }; diff --git a/src/client/service/nodesFind.ts b/src/client/service/nodesFind.ts index 7982fd9ad..7b02f9347 100644 --- a/src/client/service/nodesFind.ts +++ b/src/client/service/nodesFind.ts @@ -1,12 +1,16 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { NodeConnectionManager } from '../../nodes'; +import type NodeConnectionManager from '../../nodes/NodeConnectionManager'; import type { NodeId } from '../../nodes/types'; -import { utils as nodesUtils } from '../../nodes'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as nodesUtils from '../../nodes/utils'; +import * as nodesErrors from '../../nodes/errors'; +import * as grpcUtils from '../../grpc/utils'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; import { matchSync } from '../../utils'; import * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; +import * as clientUtils from '../utils'; /** * Attempts to get the node address of a provided node ID (by contacting @@ -14,11 +18,13 @@ import * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; * @throws ErrorNodeGraphNodeNotFound if node address cannot be found */ function nodesFind({ - nodeConnectionManager, authenticate, + nodeConnectionManager, + logger, }: { - nodeConnectionManager: NodeConnectionManager; authenticate: Authenticate; + nodeConnectionManager: NodeConnectionManager; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -44,6 +50,7 @@ function nodesFind({ }, ); const address = await nodeConnectionManager.findNode(nodeId); + if (address == null) throw new nodesErrors.ErrorNodeGraphNodeIdNotFound(); response .setNodeId(nodesUtils.encodeNodeId(nodeId)) .setAddress( @@ -53,6 +60,9 @@ function nodesFind({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + nodesErrors.ErrorNodeGraphNodeIdNotFound, + ]) && logger.error(`${nodesFind.name}:${e}`); return; } }; diff --git a/src/client/service/nodesGetAll.ts b/src/client/service/nodesGetAll.ts new file mode 100644 index 000000000..ad6ab9b6e --- /dev/null +++ b/src/client/service/nodesGetAll.ts @@ -0,0 +1,78 @@ +import type * as grpc from '@grpc/grpc-js'; +import type Logger from '@matrixai/logger'; +import type { Authenticate } from '../types'; +import type KeyManager from '../../keys/KeyManager'; +import type { NodeId } from '../../nodes/types'; +import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import type NodeGraph from '../../nodes/NodeGraph'; +import { IdInternal } from '@matrixai/id'; +import * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; +import * as nodesUtils from '../../nodes/utils'; +import * as nodesErrors from '../../nodes/errors'; +import * as grpcUtils from '../../grpc/utils'; +import * as clientUtils from '../utils'; + +/** + * Retrieves all nodes from all buckets in the NodeGraph. + */ +function nodesGetAll({ + authenticate, + nodeGraph, + keyManager, + logger, +}: { + authenticate: Authenticate; + nodeGraph: NodeGraph; + keyManager: KeyManager; + logger: Logger; +}) { + return async ( + call: grpc.ServerUnaryCall, + callback: grpc.sendUnaryData, + ): Promise => { + try { + const response = new nodesPB.NodeBuckets(); + const metadata = await authenticate(call.metadata); + call.sendMetadata(metadata); + const buckets = nodeGraph.getBuckets(); + for await (const b of buckets) { + let index; + for (const id of Object.keys(b)) { + const encodedId = nodesUtils.encodeNodeId( + IdInternal.fromString(id), + ); + const address = new nodesPB.Address() + .setHost(b[id].address.host) + .setPort(b[id].address.port); + // For every node in every bucket, add it to our message + if (!index) { + index = nodesUtils.bucketIndex( + keyManager.getNodeId(), + IdInternal.fromString(id), + ); + } + // Need to either add node to an existing bucket, or create a new + // bucket (if it doesn't exist) + const bucket = response.getBucketsMap().get(index); + if (bucket) { + bucket.getNodeTableMap().set(encodedId, address); + } else { + const newBucket = new nodesPB.NodeTable(); + newBucket.getNodeTableMap().set(encodedId, address); + response.getBucketsMap().set(index, newBucket); + } + } + } + callback(null, response); + return; + } catch (e) { + callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + nodesErrors.ErrorNodeGraphNodeIdNotFound, + ]) && logger.error(`${nodesGetAll.name}:${e}`); + return; + } + }; +} + +export default nodesGetAll; diff --git a/src/client/service/nodesPing.ts b/src/client/service/nodesPing.ts index 7dfd89939..eaf69983c 100644 --- a/src/client/service/nodesPing.ts +++ b/src/client/service/nodesPing.ts @@ -1,22 +1,28 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { NodeManager } from '../../nodes'; +import type NodeManager from '../../nodes/NodeManager'; import type { NodeId } from '../../nodes/types'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; +import * as nodesErrors from '../../nodes/errors'; import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; /** * Checks if a remote node is online. */ function nodesPing({ - nodeManager, authenticate, + nodeManager, + logger, }: { - nodeManager: NodeManager; authenticate: Authenticate; + nodeManager: NodeManager; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -47,6 +53,9 @@ function nodesPing({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + nodesErrors.ErrorNodeGraphNodeIdNotFound, + ]) && logger.error(`${nodesPing.name}:${e}`); return; } }; diff --git a/src/client/service/notificationsClear.ts b/src/client/service/notificationsClear.ts index 322f64cf6..ebcea2af0 100644 --- a/src/client/service/notificationsClear.ts +++ b/src/client/service/notificationsClear.ts @@ -1,15 +1,22 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type { NotificationsManager } from '../../notifications'; -import { utils as grpcUtils } from '../../grpc'; +import type NotificationsManager from '../../notifications/NotificationsManager'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function notificationsClear({ - notificationsManager, authenticate, + notificationsManager, + db, + logger, }: { - notificationsManager: NotificationsManager; authenticate: Authenticate; + notificationsManager: NotificationsManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -19,11 +26,15 @@ function notificationsClear({ const response = new utilsPB.EmptyMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - await notificationsManager.clearNotifications(); + await db.withTransactionF(async (tran) => + notificationsManager.clearNotifications(tran), + ); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${notificationsClear.name}:${e}`); return; } }; diff --git a/src/client/service/notificationsRead.ts b/src/client/service/notificationsRead.ts index e890ce6c0..f706b5bd2 100644 --- a/src/client/service/notificationsRead.ts +++ b/src/client/service/notificationsRead.ts @@ -1,15 +1,22 @@ import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type { NotificationsManager } from '../../notifications'; -import { utils as grpcUtils } from '../../grpc'; +import type NotificationsManager from '../../notifications/NotificationsManager'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; import * as notificationsPB from '../../proto/js/polykey/v1/notifications/notifications_pb'; +import * as clientUtils from '../utils'; function notificationsRead({ - notificationsManager, authenticate, + notificationsManager, + db, + logger, }: { - notificationsManager: NotificationsManager; authenticate: Authenticate; + notificationsManager: NotificationsManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -28,11 +35,14 @@ function notificationsRead({ } else { number = parseInt(numberField); } - const notifications = await notificationsManager.readNotifications({ - unread, - number, - order, - }); + const notifications = await db.withTransactionF(async (tran) => + notificationsManager.readNotifications({ + unread, + number, + order, + tran, + }), + ); const notifMessages: Array = []; for (const notif of notifications) { const notificationsMessage = new notificationsPB.Notification(); @@ -65,6 +75,8 @@ function notificationsRead({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e) && + logger.error(`${notificationsRead.name}:${e}`); return; } }; diff --git a/src/client/service/notificationsSend.ts b/src/client/service/notificationsSend.ts index ed3b26e05..118d5edbf 100644 --- a/src/client/service/notificationsSend.ts +++ b/src/client/service/notificationsSend.ts @@ -1,20 +1,26 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; -import type { NotificationsManager } from '../../notifications'; +import type NotificationsManager from '../../notifications/NotificationsManager'; import type { NodeId } from '../../nodes/types'; import type * as notificationsPB from '../../proto/js/polykey/v1/notifications/notifications_pb'; -import { utils as grpcUtils } from '../../grpc'; -import { utils as notificationsUtils } from '../../notifications'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type Logger from '@matrixai/logger'; +import * as grpcUtils from '../../grpc/utils'; +import * as notificationsUtils from '../../notifications/utils'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; +import * as nodesErrors from '../../nodes/errors'; import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function notificationsSend({ - notificationsManager, authenticate, + notificationsManager, + logger, }: { - notificationsManager: NotificationsManager; authenticate: Authenticate; + notificationsManager: NotificationsManager; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -50,6 +56,9 @@ function notificationsSend({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + nodesErrors.ErrorNodeGraphNodeIdNotFound, + ]) && logger.error(`${notificationsSend.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsClone.ts b/src/client/service/vaultsClone.ts index a35d70e7f..14b799837 100644 --- a/src/client/service/vaultsClone.ts +++ b/src/client/service/vaultsClone.ts @@ -1,18 +1,31 @@ +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type VaultManager from '../../vaults/VaultManager'; +import type { NodeId } from '../../nodes/types'; import type * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import * as grpc from '@grpc/grpc-js'; +import type Logger from '@matrixai/logger'; +import type * as grpc from '@grpc/grpc-js'; import * as grpcUtils from '../../grpc/utils'; +import * as grpcErrors from '../../grpc/errors'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as nodesErrors from '../../nodes/errors'; +import { validateSync } from '../../validation'; import * as validationUtils from '../../validation/utils'; import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import { matchSync } from '../../utils'; +import * as clientUtils from '../utils'; function vaultsClone({ authenticate, vaultManager, + db, + logger, }: { authenticate: Authenticate; vaultManager: VaultManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -22,30 +35,40 @@ function vaultsClone({ const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - - const vaultMessage = call.request.getVault(); - if (vaultMessage == null) { - callback({ code: grpc.status.NOT_FOUND }, null); - return; - } - const nodeMessage = call.request.getNode(); - if (nodeMessage == null) { - callback({ code: grpc.status.NOT_FOUND }, null); - return; - } + // Node id + const { + nodeId, + }: { + nodeId: NodeId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: call.request.getNode()?.getNodeId(), + }, + ); // Vault id let vaultId; - const vaultNameOrId = vaultMessage.getNameOrId(); + const vaultNameOrId = call.request.getVault()?.getNameOrId(); vaultId = vaultsUtils.decodeVaultId(vaultNameOrId); vaultId = vaultId ?? vaultNameOrId; - // Node id - const nodeId = validationUtils.parseNodeId(nodeMessage.getNodeId()); - await vaultManager.cloneVault(nodeId, vaultId); + await db.withTransactionF(async (tran) => { + await vaultManager.cloneVault(nodeId, vaultId, tran); + }); response.setSuccess(true); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + nodesErrors.ErrorNodeGraphNodeIdNotFound, + vaultsErrors.ErrorVaultsNameConflict, + [grpcErrors.ErrorPolykeyRemote, vaultsErrors.ErrorVaultsVaultUndefined], + ]) && logger.error(`${vaultsClone.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsCreate.ts b/src/client/service/vaultsCreate.ts index 363e4a200..df7c6cfac 100644 --- a/src/client/service/vaultsCreate.ts +++ b/src/client/service/vaultsCreate.ts @@ -2,17 +2,25 @@ import type { Authenticate } from '../types'; import type { VaultId, VaultName } from '../../vaults/types'; import type VaultManager from '../../vaults/VaultManager'; import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import type Logger from '@matrixai/logger'; import * as grpcUtils from '../../grpc/utils'; import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; import * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; +import * as clientUtils from '../utils'; function vaultsCreate({ - vaultManager, authenticate, + vaultManager, + db, + logger, }: { - vaultManager: VaultManager; authenticate: Authenticate; + vaultManager: VaultManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -23,14 +31,17 @@ function vaultsCreate({ try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - vaultId = await vaultManager.createVault( - call.request.getNameOrId() as VaultName, + vaultId = await db.withTransactionF(async (tran) => + vaultManager.createVault(call.request.getNameOrId() as VaultName, tran), ); response.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + vaultsErrors.ErrorVaultsVaultDefined, + ]) && logger.error(`${vaultsCreate.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsDelete.ts b/src/client/service/vaultsDelete.ts index d2f029c4a..8e04bf0ab 100644 --- a/src/client/service/vaultsDelete.ts +++ b/src/client/service/vaultsDelete.ts @@ -2,36 +2,55 @@ import type { Authenticate } from '../types'; import type { VaultName } from '../../vaults/types'; import type VaultManager from '../../vaults/VaultManager'; import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; +import type Logger from '@matrixai/logger'; import * as grpcUtils from '../../grpc/utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as validationUtils from '../../validation/utils'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import * as clientUtils from '../utils'; function vaultsDelete({ - vaultManager, authenticate, + vaultManager, + db, + logger, }: { - vaultManager: VaultManager; authenticate: Authenticate; + vaultManager: VaultManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const vaultMessage = call.request; try { const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const nameOrId = vaultMessage.getNameOrId(); - let vaultId = await vaultManager.getVaultId(nameOrId as VaultName); - vaultId = vaultId ?? validationUtils.parseVaultId(nameOrId); - await vaultManager.destroyVault(vaultId); + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + call.request.getNameOrId() as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? + vaultsUtils.decodeVaultId(call.request.getNameOrId()); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + await vaultManager.destroyVault(vaultId, tran); + }); response.setSuccess(true); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + vaultsErrors.ErrorVaultsVaultUndefined, + ]) && logger.error(`${vaultsDelete.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsList.ts b/src/client/service/vaultsList.ts index d81902976..c7d3da737 100644 --- a/src/client/service/vaultsList.ts +++ b/src/client/service/vaultsList.ts @@ -1,29 +1,35 @@ import type { Authenticate } from '../types'; import type VaultManager from '../../vaults/VaultManager'; import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import type Logger from '@matrixai/logger'; import * as grpcUtils from '../../grpc/utils'; import * as vaultsUtils from '../../vaults/utils'; import * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; +import * as clientUtils from '../utils'; function vaultsList({ - vaultManager, authenticate, + vaultManager, + db, + logger, }: { - vaultManager: VaultManager; authenticate: Authenticate; + vaultManager: VaultManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerWritableStream, ): Promise => { - // Call.on('error', (e) => console.error(e)); - // call.on('close', () => console.log('Got close')); - // call.on('finish', () => console.log('Got finish')); - const genWritable = grpcUtils.generatorWritable(call); + const genWritable = grpcUtils.generatorWritable(call, false); try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const vaults = await vaultManager.listVaults(); + const vaults = await db.withTransactionF(async (tran) => + vaultManager.listVaults(tran), + ); for await (const [vaultName, vaultId] of vaults) { const vaultListMessage = new vaultsPB.List(); vaultListMessage.setVaultName(vaultName); @@ -34,6 +40,8 @@ function vaultsList({ return; } catch (e) { await genWritable.throw(e); + !clientUtils.isClientClientError(e) && + logger.error(`${vaultsList.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsLog.ts b/src/client/service/vaultsLog.ts index 99056911a..c028006dc 100644 --- a/src/client/service/vaultsLog.ts +++ b/src/client/service/vaultsLog.ts @@ -1,42 +1,56 @@ +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type { VaultName } from '../../vaults/types'; import type VaultManager from '../../vaults/VaultManager'; -import * as grpc from '@grpc/grpc-js'; +import type Logger from '@matrixai/logger'; +import type * as grpc from '@grpc/grpc-js'; import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb'; import * as grpcUtils from '../../grpc/utils'; import * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import * as validationUtils from '../../validation/utils'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import * as clientUtils from '../utils'; function vaultsLog({ - vaultManager, authenticate, + vaultManager, + db, + logger, }: { - vaultManager: VaultManager; authenticate: Authenticate; + vaultManager: VaultManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerWritableStream, ): Promise => { - const genWritable = grpcUtils.generatorWritable(call); + const genWritable = grpcUtils.generatorWritable(call, false); try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - // Getting the vault - const vaultsLogMessage = call.request; - const vaultMessage = vaultsLogMessage.getVault(); - if (vaultMessage == null) { - await genWritable.throw({ code: grpc.status.NOT_FOUND }); - return; - } - const nameOrId = vaultMessage.getNameOrId(); - let vaultId = await vaultManager.getVaultId(nameOrId as VaultName); - vaultId = vaultId ?? validationUtils.parseVaultId(nameOrId); - // Getting the log - const depth = vaultsLogMessage.getLogDepth(); - let commitId: string | undefined = vaultsLogMessage.getCommitId(); - commitId = commitId ? commitId : undefined; - const log = await vaultManager.withVaults([vaultId], async (vault) => { - return await vault.log(commitId, depth); + const log = await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + call.request.getVault()?.getNameOrId() as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? + vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + // Getting the log + const depth = call.request.getLogDepth(); + let commitId: string | undefined = call.request.getCommitId(); + commitId = commitId ? commitId : undefined; + return await vaultManager.withVaults( + [vaultId], + async (vault) => { + return await vault.log(commitId, depth); + }, + tran, + ); }); const vaultsLogEntryMessage = new vaultsPB.LogEntry(); for (const entry of log) { @@ -52,6 +66,10 @@ function vaultsLog({ return; } catch (e) { await genWritable.throw(e); + !clientUtils.isClientClientError(e, [ + vaultsErrors.ErrorVaultsVaultUndefined, + vaultsErrors.ErrorVaultReferenceInvalid, + ]) && logger.error(`${vaultsLog.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsPermissionGet.ts b/src/client/service/vaultsPermissionGet.ts index 23780000e..e89d9b500 100644 --- a/src/client/service/vaultsPermissionGet.ts +++ b/src/client/service/vaultsPermissionGet.ts @@ -1,51 +1,65 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type VaultManager from '../../vaults/VaultManager'; -import type { VaultName } from '../../vaults/types'; -import type * as grpc from '@grpc/grpc-js'; -import type { VaultActions } from '../../vaults/types'; +import type { VaultName, VaultActions } from '../../vaults/types'; import type ACL from '../../acl/ACL'; import type { NodeId, NodeIdEncoded } from 'nodes/types'; +import type Logger from '@matrixai/logger'; import { IdInternal } from '@matrixai/id'; import * as grpcUtils from '../../grpc/utils'; import * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; import * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import * as validationUtils from '../../validation/utils'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; import * as nodesUtils from '../../nodes/utils'; +import * as clientUtils from '../utils'; function vaultsPermissionGet({ authenticate, vaultManager, acl, + db, + logger, }: { authenticate: Authenticate; vaultManager: VaultManager; acl: ACL; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerWritableStream, ): Promise => { - const genWritable = grpcUtils.generatorWritable(call); + const genWritable = grpcUtils.generatorWritable(call, false); try { - const vaultMessage = call.request; const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); // Getting vaultId - const nameOrId = vaultMessage.getNameOrId(); - let vaultId = await vaultManager.getVaultId(nameOrId as VaultName); - vaultId = vaultId ?? validationUtils.parseVaultId(nameOrId); - - // Getting permissions - const rawPermissions = await acl.getVaultPerm(vaultId); + const [rawPermissions, vaultId] = await db.withTransactionF( + async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + call.request.getNameOrId() as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? + vaultsUtils.decodeVaultId(call.request.getNameOrId()); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + // Getting permissions + return [await acl.getVaultPerm(vaultId, tran), vaultId]; + }, + ); const permissionList: Record = {}; // Getting the relevant information for (const nodeId in rawPermissions) { permissionList[nodeId] = rawPermissions[nodeId].vaults[vaultId]; } - const vaultPermissionsMessage = new vaultsPB.Permissions(); - vaultPermissionsMessage.setVault(vaultMessage); + vaultPermissionsMessage.setVault(call.request); const nodeMessage = new nodesPB.Node(); - // Constructing the message for (const nodeIdString in permissionList) { const nodeId = IdInternal.fromString(nodeIdString); @@ -57,8 +71,11 @@ function vaultsPermissionGet({ } await genWritable.next(null); return; - } catch (err) { - await genWritable.throw(err); + } catch (e) { + await genWritable.throw(e); + !clientUtils.isClientClientError(e, [ + vaultsErrors.ErrorVaultsVaultUndefined, + ]) && logger.error(`${vaultsPermissionGet.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsPermissionSet.ts b/src/client/service/vaultsPermissionSet.ts index 6b4768ee8..37b30c29a 100644 --- a/src/client/service/vaultsPermissionSet.ts +++ b/src/client/service/vaultsPermissionSet.ts @@ -1,30 +1,41 @@ +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type { VaultName } from '../../vaults/types'; +import type { NodeId } from '../../nodes/types'; +import type { VaultName, VaultAction, VaultActions } from '../../vaults/types'; import type VaultManager from '../../vaults/VaultManager'; import type GestaltGraph from '../../gestalts/GestaltGraph'; import type ACL from '../../acl/ACL'; import type NotificationsManager from '../../notifications/NotificationsManager'; -import type { VaultActions } from '../../vaults/types'; import type * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import * as grpc from '@grpc/grpc-js'; +import type Logger from '@matrixai/logger'; +import type * as grpc from '@grpc/grpc-js'; import * as vaultsUtils from '../../vaults/utils'; import * as vaultsErrors from '../../vaults/errors'; +import { validateSync } from '../../validation'; import * as validationUtils from '../../validation/utils'; import * as grpcUtils from '../../grpc/utils'; +import * as aclErrors from '../../acl/errors'; +import * as nodesErrors from '../../nodes/errors'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import { matchSync } from '../../utils'; +import * as clientUtils from '../utils'; function vaultsPermissionSet({ - vaultManager, authenticate, + vaultManager, gestaltGraph, acl, notificationsManager, + db, + logger, }: { - vaultManager: VaultManager; authenticate: Authenticate; + vaultManager: VaultManager; gestaltGraph: GestaltGraph; acl: ACL; notificationsManager: NotificationsManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -34,39 +45,53 @@ function vaultsPermissionSet({ // Checking session token const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const vaultsPermissionsMessage = call.request; - const vaultMessage = vaultsPermissionsMessage.getVault(); - const nodeMessage = vaultsPermissionsMessage.getNode(); - if (vaultMessage == null || nodeMessage == null) { - callback({ code: grpc.status.NOT_FOUND }, null); - return; - } - // Parsing VaultId - const nameOrId = vaultMessage.getNameOrId(); - let vaultId = await vaultManager.getVaultId(nameOrId as VaultName); - vaultId = vaultId ?? validationUtils.parseVaultId(nameOrId); - // Parsing NodeId - const nodeId = validationUtils.parseNodeId(nodeMessage.getNodeId()); - // Parsing actions - const actions = vaultsPermissionsMessage - .getVaultPermissionsList() - .map((vaultAction) => validationUtils.parseVaultAction(vaultAction)); - // Checking if vault exists - const vaultMeta = await vaultManager.getVaultMeta(vaultId); - if (!vaultMeta) throw new vaultsErrors.ErrorVaultsVaultUndefined(); - // Setting permissions - const actionsSet: VaultActions = {}; - await gestaltGraph.setGestaltActionByNode(nodeId, 'scan'); - for (const action of actions) { - await acl.setVaultAction(vaultId, nodeId, action); - actionsSet[action] = null; - } - // Sending notification - await notificationsManager.sendNotification(nodeId, { - type: 'VaultShare', - vaultId: vaultsUtils.encodeVaultId(vaultId), - vaultName: vaultMeta.vaultName, - actions: actionsSet, + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + call.request.getVault()?.getNameOrId() as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? + vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const { + nodeId, + actions, + }: { + nodeId: NodeId; + actions: Array; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + [['actions'], () => value.map(validationUtils.parseVaultAction)], + () => value, + ); + }, + { + nodeId: call.request.getNode()?.getNodeId(), + actions: call.request.getVaultPermissionsList(), + }, + ); + // Checking if vault exists + const vaultMeta = await vaultManager.getVaultMeta(vaultId, tran); + if (!vaultMeta) throw new vaultsErrors.ErrorVaultsVaultUndefined(); + // Setting permissions + const actionsSet: VaultActions = {}; + await gestaltGraph.setGestaltActionByNode(nodeId, 'scan', tran); + for (const action of actions) { + await acl.setVaultAction(vaultId, nodeId, action, tran); + actionsSet[action] = null; + } + // Sending notification + await notificationsManager.sendNotification(nodeId, { + type: 'VaultShare', + vaultId: vaultsUtils.encodeVaultId(vaultId), + vaultName: vaultMeta.vaultName, + actions: actionsSet, + }); }); // Formatting response const response = new utilsPB.StatusMessage().setSuccess(true); @@ -74,6 +99,11 @@ function vaultsPermissionSet({ return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + vaultsErrors.ErrorVaultsVaultUndefined, + aclErrors.ErrorACLNodeIdMissing, + nodesErrors.ErrorNodeGraphNodeIdNotFound, + ]) && logger.error(`${vaultsPermissionSet.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsPermissionUnset.ts b/src/client/service/vaultsPermissionUnset.ts index d16d81d98..5aa386d6b 100644 --- a/src/client/service/vaultsPermissionUnset.ts +++ b/src/client/service/vaultsPermissionUnset.ts @@ -1,25 +1,37 @@ +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type { VaultName } from '../../vaults/types'; +import type { NodeId } from '../../nodes/types'; +import type { VaultName, VaultAction } from '../../vaults/types'; import type VaultManager from '../../vaults/VaultManager'; import type GestaltGraph from '../../gestalts/GestaltGraph'; import type ACL from '../../acl/ACL'; import type * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import * as grpc from '@grpc/grpc-js'; +import type Logger from '@matrixai/logger'; +import type * as grpc from '@grpc/grpc-js'; +import * as vaultsUtils from '../../vaults/utils'; import * as vaultsErrors from '../../vaults/errors'; +import { validateSync } from '../../validation'; import * as validationUtils from '../../validation/utils'; import * as grpcUtils from '../../grpc/utils'; +import * as gestaltsErrors from '../../gestalts/errors'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import { matchSync } from '../../utils'; +import * as clientUtils from '../utils'; function vaultsPermissionUnset({ - vaultManager, authenticate, + vaultManager, gestaltGraph, acl, + db, + logger, }: { - vaultManager: VaultManager; authenticate: Authenticate; + vaultManager: VaultManager; gestaltGraph: GestaltGraph; acl: ACL; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -29,50 +41,68 @@ function vaultsPermissionUnset({ // Checking session token const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const vaultsPermissionsMessage = call.request; - const vaultMessage = vaultsPermissionsMessage.getVault(); - const nodeMessage = vaultsPermissionsMessage.getNode(); - if (vaultMessage == null || nodeMessage == null) { - callback({ code: grpc.status.NOT_FOUND }, null); - return; - } - // Parsing VaultId - const nameOrId = vaultMessage.getNameOrId(); - let vaultId = await vaultManager.getVaultId(nameOrId as VaultName); - vaultId = vaultId ?? validationUtils.parseVaultId(nameOrId); - // Parsing NodeId - const nodeId = validationUtils.parseNodeId(nodeMessage.getNodeId()); - // Parsing actions - const actions = vaultsPermissionsMessage - .getVaultPermissionsList() - .map((vaultAction) => validationUtils.parseVaultAction(vaultAction)); - // Checking if vault exists - const vaultMeta = await vaultManager.getVaultMeta(vaultId); - if (!vaultMeta) throw new vaultsErrors.ErrorVaultsVaultUndefined(); - // Unsetting permissions - await gestaltGraph.setGestaltActionByNode(nodeId, 'scan'); - for (const action of actions) { - await acl.unsetVaultAction(vaultId, nodeId, action); - } - // We need to check if there are still shared vaults - const nodePermissions = await acl.getNodePerm(nodeId); - // Remove scan permissions if no more shared vaults - if (nodePermissions != null) { - // Counting total number of permissions - const totalPermissions = Object.keys(nodePermissions.vaults) - .map((key) => Object.keys(nodePermissions.vaults[key]).length) - .reduce((prev, current) => current + prev); - // If no permissions are left then we remove the scan permission - if (totalPermissions === 0) { - await gestaltGraph.unsetGestaltActionByNode(nodeId, 'scan'); + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + call.request.getVault()?.getNameOrId() as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? + vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const { + nodeId, + actions, + }: { + nodeId: NodeId; + actions: Array; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + [['actions'], () => value.map(validationUtils.parseVaultAction)], + () => value, + ); + }, + { + nodeId: call.request.getNode()?.getNodeId(), + actions: call.request.getVaultPermissionsList(), + }, + ); + // Checking if vault exists + const vaultMeta = await vaultManager.getVaultMeta(vaultId, tran); + if (!vaultMeta) throw new vaultsErrors.ErrorVaultsVaultUndefined(); + // Unsetting permissions + await gestaltGraph.setGestaltActionByNode(nodeId, 'scan', tran); + for (const action of actions) { + await acl.unsetVaultAction(vaultId, nodeId, action, tran); + } + // We need to check if there are still shared vaults + const nodePermissions = await acl.getNodePerm(nodeId, tran); + // Remove scan permissions if no more shared vaults + if (nodePermissions != null) { + // Counting total number of permissions + const totalPermissions = Object.keys(nodePermissions.vaults) + .map((key) => Object.keys(nodePermissions.vaults[key]).length) + .reduce((prev, current) => current + prev); + // If no permissions are left then we remove the scan permission + if (totalPermissions === 0) { + await gestaltGraph.unsetGestaltActionByNode(nodeId, 'scan', tran); + } } - } + }); // Formatting response const response = new utilsPB.StatusMessage().setSuccess(true); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + vaultsErrors.ErrorVaultsVaultUndefined, + gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, + ]) && logger.error(`${vaultsPermissionUnset.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsPull.ts b/src/client/service/vaultsPull.ts index 8c18e1a29..ea83519a4 100644 --- a/src/client/service/vaultsPull.ts +++ b/src/client/service/vaultsPull.ts @@ -1,19 +1,32 @@ +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type VaultManager from '../../vaults/VaultManager'; import type { VaultName } from '../../vaults/types'; +import type { NodeId } from '../../nodes/types'; import type * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import * as grpc from '@grpc/grpc-js'; +import type Logger from '@matrixai/logger'; +import type * as grpc from '@grpc/grpc-js'; import * as grpcUtils from '../../grpc/utils'; +import * as grpcErrors from '../../grpc/errors'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import { validateSync } from '../../validation'; import * as validationUtils from '../../validation/utils'; import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import * as nodesErrors from '../../nodes/errors'; +import { matchSync } from '../../utils'; +import * as clientUtils from '../utils'; function vaultsPull({ authenticate, vaultManager, + db, + logger, }: { authenticate: Authenticate; vaultManager: VaultManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -23,41 +36,61 @@ function vaultsPull({ const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - - const vaultMessage = call.request.getVault(); - if (vaultMessage == null) { - callback({ code: grpc.status.NOT_FOUND }, null); - return; - } - const nameOrId = vaultMessage.getNameOrId(); - let vaultId = await vaultManager.getVaultId(nameOrId as VaultName); - vaultId = vaultId ?? validationUtils.parseVaultId(nameOrId); - let nodeId; - const nodeMessage = call.request.getNode(); - if (nodeMessage == null) { - nodeId = null; - } else { - nodeId = validationUtils.parseNodeId(nodeMessage.getNodeId()); - } - let pullVault; + let pullVaultId; const pullVaultMessage = call.request.getPullVault(); if (pullVaultMessage == null) { - pullVault = null; + pullVaultId = null; } else { - pullVault = vaultsUtils.decodeVaultId(pullVaultMessage.getNameOrId()); - pullVault = pullVault ?? pullVaultMessage.getNameOrId(); - if (pullVault == null) pullVault = pullVaultMessage.getNameOrId(); + pullVaultId = vaultsUtils.decodeVaultId(pullVaultMessage.getNameOrId()); + pullVaultId = pullVaultId ?? pullVaultMessage.getNameOrId(); + if (pullVaultId == null) pullVaultId = pullVaultMessage.getNameOrId(); } - await vaultManager.pullVault({ - vaultId, - pullNodeId: nodeId, - pullVaultNameOrId: pullVault, + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + call.request.getVault()?.getNameOrId() as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? + vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const { + nodeId, + }: { + nodeId: NodeId | undefined; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [ + ['nodeId'], + () => (value ? validationUtils.parseNodeId(value) : undefined), + ], + () => value, + ); + }, + { + nodeId: call.request.getNode()?.getNodeId(), + }, + ); + await vaultManager.pullVault({ + vaultId, + pullNodeId: nodeId, + pullVaultNameOrId: pullVaultId, + tran, + }); }); response.setSuccess(true); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + vaultsErrors.ErrorVaultsVaultUndefined, + nodesErrors.ErrorNodeGraphNodeIdNotFound, + [grpcErrors.ErrorPolykeyRemote, vaultsErrors.ErrorVaultsVaultUndefined], + ]) && logger.error(`${vaultsPull.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsRename.ts b/src/client/service/vaultsRename.ts index 506162989..21bcd7626 100644 --- a/src/client/service/vaultsRename.ts +++ b/src/client/service/vaultsRename.ts @@ -1,18 +1,25 @@ +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type { VaultName } from '../../vaults/types'; import type VaultManager from '../../vaults/VaultManager'; -import * as grpc from '@grpc/grpc-js'; -import * as validationUtils from '../../validation/utils'; +import type Logger from '@matrixai/logger'; +import type * as grpc from '@grpc/grpc-js'; import * as grpcUtils from '../../grpc/utils'; import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; import * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; +import * as clientUtils from '../utils'; function vaultsRename({ - vaultManager, authenticate, + vaultManager, + db, + logger, }: { - vaultManager: VaultManager; authenticate: Authenticate; + vaultManager: VaultManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -22,21 +29,29 @@ function vaultsRename({ const response = new vaultsPB.Vault(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const vaultMessage = call.request.getVault(); - if (vaultMessage == null) { - callback({ code: grpc.status.NOT_FOUND }, null); - return; - } - const nameOrId = vaultMessage.getNameOrId(); - let vaultId = await vaultManager.getVaultId(nameOrId as VaultName); - vaultId = vaultId ?? validationUtils.parseVaultId(nameOrId); - const newName = call.request.getNewName() as VaultName; - await vaultManager.renameVault(vaultId, newName); - response.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + call.request.getVault()?.getNameOrId() as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? + vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const newName = call.request.getNewName() as VaultName; + await vaultManager.renameVault(vaultId, newName, tran); + response.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); + }); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + vaultsErrors.ErrorVaultsVaultUndefined, + vaultsErrors.ErrorVaultsVaultDefined, + ]) && logger.error(`${vaultsRename.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsScan.ts b/src/client/service/vaultsScan.ts index 3d8d73a7e..248a3c5d8 100644 --- a/src/client/service/vaultsScan.ts +++ b/src/client/service/vaultsScan.ts @@ -3,23 +3,28 @@ import type { NodeId } from '../../nodes/types'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; import type * as grpc from '@grpc/grpc-js'; import type VaultManager from '../../vaults/VaultManager'; +import type Logger from '@matrixai/logger'; import * as grpcUtils from '../../grpc/utils'; import { validateSync } from '../../validation'; import * as validationUtils from '../../validation/utils'; -import { matchSync } from '../../utils'; import * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; +import * as nodesErrors from '../../nodes/errors'; +import { matchSync } from '../../utils'; +import * as clientUtils from '../utils'; function vaultsScan({ - vaultManager, authenticate, + vaultManager, + logger, }: { - vaultManager: VaultManager; authenticate: Authenticate; + vaultManager: VaultManager; + logger: Logger; }) { return async ( call: grpc.ServerWritableStream, ): Promise => { - const genWritable = grpcUtils.generatorWritable(call); + const genWritable = grpcUtils.generatorWritable(call, false); try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); @@ -53,6 +58,9 @@ function vaultsScan({ return; } catch (e) { await genWritable.throw(e); + !clientUtils.isClientClientError(e, [ + nodesErrors.ErrorNodeGraphNodeIdNotFound, + ]) && logger.error(`${vaultsScan.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsSecretsDelete.ts b/src/client/service/vaultsSecretsDelete.ts index 07a56a92d..c35ba8ce8 100644 --- a/src/client/service/vaultsSecretsDelete.ts +++ b/src/client/service/vaultsSecretsDelete.ts @@ -1,19 +1,27 @@ +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type { VaultName } from '../../vaults/types'; import type VaultManager from '../../vaults/VaultManager'; import type * as secretsPB from '../../proto/js/polykey/v1/secrets/secrets_pb'; -import * as grpc from '@grpc/grpc-js'; -import * as validationUtils from '../../validation/utils'; +import type Logger from '@matrixai/logger'; +import type * as grpc from '@grpc/grpc-js'; +import * as vaultsUtils from '../../vaults/utils'; import * as grpcUtils from '../../grpc/utils'; +import * as vaultsErrors from '../../vaults/errors'; import * as vaultOps from '../../vaults/VaultOps'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function vaultsSecretsDelete({ - vaultManager, authenticate, + vaultManager, + db, + logger, }: { - vaultManager: VaultManager; authenticate: Authenticate; + vaultManager: VaultManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -23,23 +31,35 @@ function vaultsSecretsDelete({ const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const vaultMessage = call.request.getVault(); - if (vaultMessage == null) { - callback({ code: grpc.status.NOT_FOUND }, null); - return; - } - const nameOrId = vaultMessage.getNameOrId(); - let vaultId = await vaultManager.getVaultId(nameOrId as VaultName); - vaultId = vaultId ?? validationUtils.parseVaultId(nameOrId); - const secretName = call.request.getSecretName(); - await vaultManager.withVaults([vaultId], async (vault) => { - await vaultOps.deleteSecret(vault, secretName); + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + call.request.getVault()?.getNameOrId() as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? + vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const secretName = call.request.getSecretName(); + await vaultManager.withVaults( + [vaultId], + async (vault) => { + await vaultOps.deleteSecret(vault, secretName); + }, + tran, + ); }); response.setSuccess(true); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + vaultsErrors.ErrorVaultsVaultUndefined, + vaultsErrors.ErrorSecretsSecretUndefined, + ]) && logger.error(`${vaultsSecretsDelete.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsSecretsEdit.ts b/src/client/service/vaultsSecretsEdit.ts index 8f45362b2..d114e56d2 100644 --- a/src/client/service/vaultsSecretsEdit.ts +++ b/src/client/service/vaultsSecretsEdit.ts @@ -1,19 +1,27 @@ +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type { VaultName } from '../../vaults/types'; import type VaultManager from '../../vaults/VaultManager'; import type * as secretsPB from '../../proto/js/polykey/v1/secrets/secrets_pb'; -import * as grpc from '@grpc/grpc-js'; -import * as validationUtils from '../../validation/utils'; +import type Logger from '@matrixai/logger'; +import type * as grpc from '@grpc/grpc-js'; +import * as vaultsUtils from '../../vaults/utils'; import * as grpcUtils from '../../grpc/utils'; +import * as vaultsErrors from '../../vaults/errors'; import * as vaultOps from '../../vaults/VaultOps'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function vaultsSecretsEdit({ - vaultManager, authenticate, + vaultManager, + db, + logger, }: { - vaultManager: VaultManager; authenticate: Authenticate; + vaultManager: VaultManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -23,29 +31,37 @@ function vaultsSecretsEdit({ const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const secretMessage = call.request; - if (secretMessage == null) { - callback({ code: grpc.status.NOT_FOUND }, null); - return; - } - const vaultMessage = secretMessage.getVault(); - if (vaultMessage == null) { - callback({ code: grpc.status.NOT_FOUND }, null); - return; - } - const nameOrId = vaultMessage.getNameOrId(); - let vaultId = await vaultManager.getVaultId(nameOrId as VaultName); - vaultId = vaultId ?? validationUtils.parseVaultId(nameOrId); - const secretName = secretMessage.getSecretName(); - const secretContent = Buffer.from(secretMessage.getSecretContent()); - await vaultManager.withVaults([vaultId], async (vault) => { - await vaultOps.updateSecret(vault, secretName, secretContent); + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + call.request.getVault()?.getNameOrId() as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? + vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const secretName = call.request.getSecretName(); + const secretContent = Buffer.from(call.request.getSecretContent()); + await vaultManager.withVaults( + [vaultId], + async (vault) => { + await vaultOps.updateSecret(vault, secretName, secretContent); + }, + tran, + ); }); response.setSuccess(true); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + vaultsErrors.ErrorVaultsVaultUndefined, + vaultsErrors.ErrorSecretsSecretUndefined, + vaultsErrors.ErrorVaultRemoteDefined, + ]) && logger.error(`${vaultsSecretsEdit.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsSecretsGet.ts b/src/client/service/vaultsSecretsGet.ts index fa836e1b0..61eafbf16 100644 --- a/src/client/service/vaultsSecretsGet.ts +++ b/src/client/service/vaultsSecretsGet.ts @@ -1,19 +1,27 @@ +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type { VaultName } from '../../vaults/types'; import type VaultManager from '../../vaults/VaultManager'; import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as grpc from '@grpc/grpc-js'; -import * as validationUtils from '../../validation/utils'; +import type Logger from '@matrixai/logger'; +import type * as grpc from '@grpc/grpc-js'; +import * as vaultsUtils from '../../vaults/utils'; import * as grpcUtils from '../../grpc/utils'; +import * as vaultsErrors from '../../vaults/errors'; import * as vaultOps from '../../vaults/VaultOps'; import * as secretsPB from '../../proto/js/polykey/v1/secrets/secrets_pb'; +import * as clientUtils from '../utils'; function vaultsSecretsGet({ - vaultManager, authenticate, + vaultManager, + db, + logger, }: { - vaultManager: VaultManager; authenticate: Authenticate; + vaultManager: VaultManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -23,26 +31,35 @@ function vaultsSecretsGet({ const response = new secretsPB.Secret(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const vaultMessage = call.request.getVault(); - if (vaultMessage == null) { - callback({ code: grpc.status.NOT_FOUND }, null); - return; - } - const nameOrId = vaultMessage.getNameOrId(); - let vaultId = await vaultManager.getVaultId(nameOrId as VaultName); - vaultId = vaultId ?? validationUtils.parseVaultId(nameOrId); - const secretName = call.request.getSecretName(); - const secretContent = await vaultManager.withVaults( - [vaultId], - async (vault) => { - return await vaultOps.getSecret(vault, secretName); - }, - ); - response.setSecretContent(secretContent); + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + call.request.getVault()?.getNameOrId() as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? + vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const secretName = call.request.getSecretName(); + const secretContent = await vaultManager.withVaults( + [vaultId], + async (vault) => { + return await vaultOps.getSecret(vault, secretName); + }, + tran, + ); + response.setSecretContent(secretContent); + }); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + vaultsErrors.ErrorVaultsVaultUndefined, + vaultsErrors.ErrorSecretsSecretUndefined, + ]) && logger.error(`${vaultsSecretsGet.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsSecretsList.ts b/src/client/service/vaultsSecretsList.ts index db2a1cc36..c55aa59d0 100644 --- a/src/client/service/vaultsSecretsList.ts +++ b/src/client/service/vaultsSecretsList.ts @@ -2,36 +2,53 @@ import type { Authenticate } from '../types'; import type { VaultName } from '../../vaults/types'; import type VaultManager from '../../vaults/VaultManager'; import type * as grpc from '@grpc/grpc-js'; +import type { DB } from '@matrixai/db'; import type * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import * as validationUtils from '../../validation/utils'; +import type Logger from '@matrixai/logger'; +import * as vaultsUtils from '../../vaults/utils'; import * as grpcUtils from '../../grpc/utils'; +import * as vaultsErrors from '../../vaults/errors'; import * as vaultOps from '../../vaults/VaultOps'; import * as secretsPB from '../../proto/js/polykey/v1/secrets/secrets_pb'; +import * as clientUtils from '../utils'; function vaultsSecretsList({ - vaultManager, authenticate, + vaultManager, + db, + logger, }: { - vaultManager: VaultManager; authenticate: Authenticate; + vaultManager: VaultManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerWritableStream, ): Promise => { - const genWritable = grpcUtils.generatorWritable(call); + const genWritable = grpcUtils.generatorWritable(call, false); try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const vaultMessage = call.request; - const nameOrId = vaultMessage.getNameOrId(); - let vaultId = await vaultManager.getVaultId(nameOrId as VaultName); - vaultId = vaultId ?? validationUtils.parseVaultId(nameOrId); - const secrets = await vaultManager.withVaults( - [vaultId], - async (vault) => { - return await vaultOps.listSecrets(vault); - }, - ); + const secrets = await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + call.request.getNameOrId() as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? + vaultsUtils.decodeVaultId(call.request.getNameOrId()); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + return await vaultManager.withVaults( + [vaultId], + async (vault) => { + return await vaultOps.listSecrets(vault); + }, + tran, + ); + }); let secretMessage: secretsPB.Secret; for (const secret of secrets) { secretMessage = new secretsPB.Secret(); @@ -42,6 +59,9 @@ function vaultsSecretsList({ return; } catch (e) { await genWritable.throw(e); + !clientUtils.isClientClientError(e, [ + vaultsErrors.ErrorVaultsVaultUndefined, + ]) && logger.error(`${vaultsSecretsList.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsSecretsMkdir.ts b/src/client/service/vaultsSecretsMkdir.ts index fca32d4f9..a9a545704 100644 --- a/src/client/service/vaultsSecretsMkdir.ts +++ b/src/client/service/vaultsSecretsMkdir.ts @@ -1,47 +1,66 @@ +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type { VaultName } from '../../vaults/types'; import type VaultManager from '../../vaults/VaultManager'; import type * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import * as grpc from '@grpc/grpc-js'; -import * as validationUtils from '../../validation/utils'; +import type Logger from '@matrixai/logger'; +import type * as grpc from '@grpc/grpc-js'; +import * as vaultsUtils from '../../vaults/utils'; import * as grpcUtils from '../../grpc/utils'; +import * as vaultsErrors from '../../vaults/errors'; import * as vaultOps from '../../vaults/VaultOps'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function vaultsSecretsMkdir({ - vaultManager, authenticate, + vaultManager, + db, + logger, }: { - vaultManager: VaultManager; authenticate: Authenticate; + vaultManager: VaultManager; + db: DB; + logger: Logger; }) { return async ( - call: grpc.ServerUnaryCall, + call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { try { const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const vaultMkdirMessge = call.request; - const vaultMessage = vaultMkdirMessge.getVault(); - if (vaultMessage == null) { - callback({ code: grpc.status.NOT_FOUND }, null); - return; - } - const nameOrId = vaultMessage.getNameOrId(); - let vaultId = await vaultManager.getVaultId(nameOrId as VaultName); - vaultId = vaultId ?? validationUtils.parseVaultId(nameOrId); - await vaultManager.withVaults([vaultId], async (vault) => { - await vaultOps.mkdir(vault, vaultMkdirMessge.getDirName(), { - recursive: vaultMkdirMessge.getRecursive(), - }); + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + call.request.getVault()?.getNameOrId() as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? + vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + await vaultManager.withVaults( + [vaultId], + async (vault) => { + await vaultOps.mkdir(vault, call.request.getDirName(), { + recursive: call.request.getRecursive(), + }); + }, + tran, + ); }); response.setSuccess(true); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + vaultsErrors.ErrorVaultsVaultUndefined, + vaultsErrors.ErrorVaultsRecursive, + ]) && logger.error(`${vaultsSecretsMkdir.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsSecretsNew.ts b/src/client/service/vaultsSecretsNew.ts index 3c22baa7a..2414e6bf6 100644 --- a/src/client/service/vaultsSecretsNew.ts +++ b/src/client/service/vaultsSecretsNew.ts @@ -1,19 +1,27 @@ +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type { VaultName } from '../../vaults/types'; import type VaultManager from '../../vaults/VaultManager'; import type * as secretsPB from '../../proto/js/polykey/v1/secrets/secrets_pb'; -import * as grpc from '@grpc/grpc-js'; -import * as validationUtils from '../../validation/utils'; +import type Logger from '@matrixai/logger'; +import type * as grpc from '@grpc/grpc-js'; +import * as vaultsUtils from '../../vaults/utils'; import * as grpcUtils from '../../grpc/utils'; +import * as vaultsErrors from '../../vaults/errors'; import * as vaultOps from '../../vaults/VaultOps'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function vaultsSecretsNew({ - vaultManager, authenticate, + vaultManager, + db, + logger, }: { - vaultManager: VaultManager; authenticate: Authenticate; + vaultManager: VaultManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -23,24 +31,36 @@ function vaultsSecretsNew({ const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const vaultMessage = call.request.getVault(); - if (vaultMessage == null) { - callback({ code: grpc.status.NOT_FOUND }, null); - return; - } - const nameOrId = vaultMessage.getNameOrId(); - let vaultId = await vaultManager.getVaultId(nameOrId as VaultName); - vaultId = vaultId ?? validationUtils.parseVaultId(nameOrId); - const secret = call.request.getSecretName(); - const content = Buffer.from(call.request.getSecretContent()); - await vaultManager.withVaults([vaultId], async (vault) => { - await vaultOps.addSecret(vault, secret, content); + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + call.request.getVault()?.getNameOrId() as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? + vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const secret = call.request.getSecretName(); + const content = Buffer.from(call.request.getSecretContent()); + await vaultManager.withVaults( + [vaultId], + async (vault) => { + await vaultOps.addSecret(vault, secret, content); + }, + tran, + ); }); response.setSuccess(true); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + vaultsErrors.ErrorVaultsVaultUndefined, + vaultsErrors.ErrorSecretsSecretUndefined, + ]) && logger.error(`${vaultsSecretsNew.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsSecretsNewDir.ts b/src/client/service/vaultsSecretsNewDir.ts index 31a075e01..3ed67ce68 100644 --- a/src/client/service/vaultsSecretsNewDir.ts +++ b/src/client/service/vaultsSecretsNewDir.ts @@ -1,22 +1,30 @@ +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type { VaultName } from '../../vaults/types'; import type VaultManager from '../../vaults/VaultManager'; import type { FileSystem } from '../../types'; import type * as secretsPB from '../../proto/js/polykey/v1/secrets/secrets_pb'; -import * as grpc from '@grpc/grpc-js'; -import * as validationUtils from '../../validation/utils'; +import type Logger from '@matrixai/logger'; +import type * as grpc from '@grpc/grpc-js'; +import * as vaultsUtils from '../../vaults/utils'; import * as grpcUtils from '../../grpc/utils'; +import * as vaultsErrors from '../../vaults/errors'; import * as vaultOps from '../../vaults/VaultOps'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function vaultsSecretsNewDir({ - vaultManager, authenticate, + vaultManager, fs, + db, + logger, }: { - vaultManager: VaultManager; authenticate: Authenticate; + vaultManager: VaultManager; fs: FileSystem; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -26,23 +34,34 @@ function vaultsSecretsNewDir({ const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const vaultMessage = call.request.getVault(); - if (vaultMessage == null) { - callback({ code: grpc.status.NOT_FOUND }, null); - return; - } - const nameOrId = vaultMessage.getNameOrId(); - let vaultId = await vaultManager.getVaultId(nameOrId as VaultName); - vaultId = vaultId ?? validationUtils.parseVaultId(nameOrId); - const secretsPath = call.request.getSecretDirectory(); - await vaultManager.withVaults([vaultId], async (vault) => { - await vaultOps.addSecretDirectory(vault, secretsPath, fs); + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + call.request.getVault()?.getNameOrId() as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? + vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const secretsPath = call.request.getSecretDirectory(); + await vaultManager.withVaults( + [vaultId], + async (vault) => { + await vaultOps.addSecretDirectory(vault, secretsPath, fs); + }, + tran, + ); }); response.setSuccess(true); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + vaultsErrors.ErrorVaultsVaultUndefined, + ]) && logger.error(`${vaultsSecretsNewDir.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsSecretsRename.ts b/src/client/service/vaultsSecretsRename.ts index 7de527519..d65a87f02 100644 --- a/src/client/service/vaultsSecretsRename.ts +++ b/src/client/service/vaultsSecretsRename.ts @@ -1,19 +1,27 @@ +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type { VaultName } from '../../vaults/types'; import type VaultManager from '../../vaults/VaultManager'; import type * as secretsPB from '../../proto/js/polykey/v1/secrets/secrets_pb'; -import * as grpc from '@grpc/grpc-js'; -import * as validationUtils from '../../validation/utils'; +import type Logger from '@matrixai/logger'; +import type * as grpc from '@grpc/grpc-js'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; import * as grpcUtils from '../../grpc/utils'; import * as vaultOps from '../../vaults/VaultOps'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '../utils'; function vaultsSecretsRename({ - vaultManager, authenticate, + vaultManager, + db, + logger, }: { - vaultManager: VaultManager; authenticate: Authenticate; + vaultManager: VaultManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -23,29 +31,41 @@ function vaultsSecretsRename({ const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const secretMessage = call.request.getOldSecret(); - if (!secretMessage) { - callback({ code: grpc.status.NOT_FOUND }, null); - return; - } - const vaultMessage = secretMessage.getVault(); - if (vaultMessage == null) { - callback({ code: grpc.status.NOT_FOUND }, null); - return; - } - const nameOrId = vaultMessage.getNameOrId(); - let vaultId = await vaultManager.getVaultId(nameOrId as VaultName); - vaultId = vaultId ?? validationUtils.parseVaultId(nameOrId); - const oldSecret = secretMessage.getSecretName(); - const newSecret = call.request.getNewName(); - await vaultManager.withVaults([vaultId], async (vault) => { - await vaultOps.renameSecret(vault, oldSecret, newSecret); + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + call.request.getOldSecret()?.getVault()?.getNameOrId() as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? + vaultsUtils.decodeVaultId( + call.request.getOldSecret()?.getVault()?.getNameOrId(), + ); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const oldSecret = call.request.getOldSecret()?.getSecretName(); + if (oldSecret == null) { + throw new vaultsErrors.ErrorSecretsSecretUndefined(); + } + const newSecret = call.request.getNewName(); + await vaultManager.withVaults( + [vaultId], + async (vault) => { + await vaultOps.renameSecret(vault, oldSecret, newSecret); + }, + tran, + ); }); response.setSuccess(true); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + vaultsErrors.ErrorVaultsVaultUndefined, + vaultsErrors.ErrorSecretsSecretUndefined, + ]) && logger.error(`${vaultsSecretsRename.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsSecretsStat.ts b/src/client/service/vaultsSecretsStat.ts index e657d4009..9d4477443 100644 --- a/src/client/service/vaultsSecretsStat.ts +++ b/src/client/service/vaultsSecretsStat.ts @@ -1,18 +1,26 @@ +import type { DB } from '@matrixai/db'; import type VaultManager from '../../vaults/VaultManager'; import type { VaultName } from '../../vaults/types'; import type { Authenticate } from '../types'; -import * as grpc from '@grpc/grpc-js'; -import * as validationUtils from '../../validation/utils'; +import type Logger from '@matrixai/logger'; +import type * as grpc from '@grpc/grpc-js'; import * as grpcUtils from '../../grpc/utils'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; import * as vaultOps from '../../vaults/VaultOps'; import * as secretsPB from '../../proto/js/polykey/v1/secrets/secrets_pb'; +import * as clientUtils from '../utils'; function vaultsSecretsStat({ authenticate, vaultManager, + db, + logger, }: { authenticate: Authenticate; vaultManager: VaultManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -22,23 +30,35 @@ function vaultsSecretsStat({ const response = new secretsPB.Stat(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const vaultMessage = call.request.getVault(); - if (vaultMessage == null) { - callback({ code: grpc.status.NOT_FOUND }, null); - return; - } - const nameOrId = vaultMessage.getNameOrId(); - let vaultId = await vaultManager.getVaultId(nameOrId as VaultName); - vaultId = vaultId ?? validationUtils.parseVaultId(nameOrId); - const secretName = call.request.getSecretName(); - const stat = await vaultManager.withVaults([vaultId], async (vault) => { - return await vaultOps.statSecret(vault, secretName); + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + call.request.getVault()?.getNameOrId() as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? + vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const secretName = call.request.getSecretName(); + const stat = await vaultManager.withVaults( + [vaultId], + async (vault) => { + return await vaultOps.statSecret(vault, secretName); + }, + tran, + ); + response.setJson(JSON.stringify(stat)); }); - response.setJson(JSON.stringify(stat)); callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + vaultsErrors.ErrorVaultsVaultUndefined, + vaultsErrors.ErrorSecretsSecretUndefined, + ]) && logger.error(`${vaultsSecretsStat.name}:${e}`); return; } }; diff --git a/src/client/service/vaultsVersion.ts b/src/client/service/vaultsVersion.ts index 4338966da..df1dc3300 100644 --- a/src/client/service/vaultsVersion.ts +++ b/src/client/service/vaultsVersion.ts @@ -1,17 +1,25 @@ +import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type { VaultName } from '../../vaults/types'; import type VaultManager from '../../vaults/VaultManager'; -import * as grpc from '@grpc/grpc-js'; -import * as validationUtils from '../../validation/utils'; +import type Logger from '@matrixai/logger'; +import type * as grpc from '@grpc/grpc-js'; +import * as vaultsUtils from '../../vaults/utils'; import * as grpcUtils from '../../grpc/utils'; +import * as vaultsErrors from '../../vaults/errors'; import * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; +import * as clientUtils from '../utils'; function vaultsVersion({ - vaultManager, authenticate, + vaultManager, + db, + logger, }: { - vaultManager: VaultManager; authenticate: Authenticate; + vaultManager: VaultManager; + db: DB; + logger: Logger; }) { return async ( call: grpc.ServerUnaryCall, @@ -22,36 +30,45 @@ function vaultsVersion({ // Checking session token const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const vaultsVersionMessage = call.request; - // Getting vault ID - const vaultMessage = vaultsVersionMessage.getVault(); - if (vaultMessage == null) { - callback({ code: grpc.status.NOT_FOUND }, null); - return; - } - const nameOrId = vaultMessage.getNameOrId(); - let vaultId = await vaultManager.getVaultId(nameOrId as VaultName); - vaultId = vaultId ?? validationUtils.parseVaultId(nameOrId); - // Doing the deed - const versionId = vaultsVersionMessage.getVersionId(); - const [latestOid, currentVersionId] = await vaultManager.withVaults( - [vaultId], - async (vault) => { - const latestOid = (await vault.log())[0].commitId; - await vault.version(versionId); - const currentVersionId = (await vault.log(versionId, 0))[0]?.commitId; - return [latestOid, currentVersionId]; - }, - ); - // Checking if latest version ID - const isLatestVersion = latestOid === currentVersionId; - // Creating message - response.setIsLatestVersion(isLatestVersion); + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + call.request.getVault()?.getNameOrId() as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? + vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + // Doing the deed + const versionId = call.request.getVersionId(); + const [latestOid, currentVersionId] = await vaultManager.withVaults( + [vaultId], + async (vault) => { + const latestOid = (await vault.log())[0].commitId; + await vault.version(versionId); + const currentVersionId = (await vault.log(versionId, 0))[0] + ?.commitId; + return [latestOid, currentVersionId]; + }, + tran, + ); + // Checking if latest version ID + const isLatestVersion = latestOid === currentVersionId; + // Creating message + response.setIsLatestVersion(isLatestVersion); + }); // Sending message callback(null, response); return; } catch (e) { callback(grpcUtils.fromError(e)); + !clientUtils.isClientClientError(e, [ + vaultsErrors.ErrorVaultsVaultUndefined, + vaultsErrors.ErrorVaultReferenceInvalid, + vaultsErrors.ErrorVaultReferenceMissing, + ]) && logger.error(`${vaultsVersion.name}:${e}`); return; } }; diff --git a/src/client/types.ts b/src/client/types.ts index dc642800f..d06cdb11e 100644 --- a/src/client/types.ts +++ b/src/client/types.ts @@ -1,8 +1,14 @@ import type * as grpc from '@grpc/grpc-js'; +import type { Class } from '@matrixai/errors'; +import type ErrorPolykey from '../ErrorPolykey'; type Authenticate = ( metadataClient: grpc.Metadata, metadataServer?: grpc.Metadata, ) => Promise; -export type { Authenticate }; +type ClientClientErrors = Array< + Class> | Array>> +>; + +export type { Authenticate, ClientClientErrors }; diff --git a/src/client/utils/utils.ts b/src/client/utils/utils.ts index 3e2021cf9..21bfaab20 100644 --- a/src/client/utils/utils.ts +++ b/src/client/utils/utils.ts @@ -3,14 +3,28 @@ import type { Interceptor, InterceptorOptions, } from '@grpc/grpc-js/build/src/client-interceptors'; -import type { KeyManager } from '../../keys'; -import type { Session, SessionManager } from '../../sessions'; +import type ErrorPolykey from '../../ErrorPolykey'; +import type KeyManager from '../../keys/KeyManager'; +import type Session from '../../sessions/Session'; +import type SessionManager from '../../sessions/SessionManager'; import type { SessionToken } from '../../sessions/types'; -import type { Authenticate } from '../types'; -import * as grpc from '@grpc/grpc-js'; +import type { Authenticate, ClientClientErrors } from '../types'; import * as base64 from 'multiformats/bases/base64'; +import * as grpc from '@grpc/grpc-js'; +import * as validationErrors from '../../validation/errors'; import * as clientErrors from '../errors'; +/** + * Array of errors that are always considered to be "client errors" + * (4xx errors in HTTP) in the context of the client service + */ +const defaultClientErrors: ClientClientErrors = [ + validationErrors.ErrorValidation, + clientErrors.ErrorClientAuthMissing, + clientErrors.ErrorClientAuthFormat, + clientErrors.ErrorClientAuthDenied, +]; + /** * Session interceptor middleware for authenticatio * Session token is read at the beginning of every call @@ -129,7 +143,64 @@ function decodeAuthToSession( if (auth == null || !auth.startsWith('Bearer ')) { return; } - return auth.substr(7) as SessionToken; + return auth.substring(7) as SessionToken; +} + +/** + * Checks whether an error is a "client error" (4xx errors in HTTP) + * Used by the service handlers since client errors should not be + * reported on the server side + * Additional errors that are considered to be client errors in the + * context of a given handler can be supplied in the `extraClientErrors` + * argument + */ +function isClientClientError( + thrownError: ErrorPolykey, + extraClientErrors?: ClientClientErrors, +): boolean { + for (const error of defaultClientErrors) { + if (Array.isArray(error)) { + let e = thrownError; + let matches = true; + for (const eType of error) { + if (e == null) { + matches = false; + break; + } + if (!(e instanceof eType)) { + matches = false; + break; + } + e = e.cause; + } + if (matches) return true; + } else if (thrownError instanceof error) { + return true; + } + } + if (extraClientErrors) { + for (const error of extraClientErrors) { + if (Array.isArray(error)) { + let e = thrownError; + let matches = true; + for (const eType of error) { + if (e == null) { + matches = false; + break; + } + if (!(e instanceof eType)) { + matches = false; + break; + } + e = e.cause; + } + if (matches) return true; + } else if (thrownError instanceof error) { + return true; + } + } + } + return false; } export { @@ -138,4 +209,5 @@ export { encodeAuthFromPassword, encodeAuthFromSession, decodeAuthToSession, + isClientClientError, }; diff --git a/src/discovery/Discovery.ts b/src/discovery/Discovery.ts index 900b6b63f..3e4f9d7d0 100644 --- a/src/discovery/Discovery.ts +++ b/src/discovery/Discovery.ts @@ -1,5 +1,4 @@ -import type { MutexInterface } from 'async-mutex'; -import type { DB, DBLevel } from '@matrixai/db'; +import type { DB, LevelPath } from '@matrixai/db'; import type { DiscoveryQueueId, DiscoveryQueueIdGenerator } from './types'; import type { NodeId, NodeInfo } from '../nodes/types'; import type NodeManager from '../nodes/NodeManager'; @@ -14,12 +13,10 @@ import type { IdentityClaimId, IdentityClaims, } from '../identities/types'; -import type { Sigchain } from '../sigchain'; -import type { KeyManager } from '../keys'; +import type Sigchain from '../sigchain/Sigchain'; +import type KeyManager from '../keys/KeyManager'; import type { ClaimIdEncoded, Claim, ClaimLinkIdentity } from '../claims/types'; import type { ChainData } from '../sigchain/types'; -import type { ResourceAcquire } from '../utils'; -import { Mutex } from 'async-mutex'; import Logger from '@matrixai/logger'; import { CreateDestroyStartStop, @@ -27,14 +24,16 @@ import { status, } from '@matrixai/async-init/dist/CreateDestroyStartStop'; import { IdInternal } from '@matrixai/id'; +import { Lock } from '@matrixai/async-locks'; import * as idUtils from '@matrixai/id/dist/utils'; +import * as resources from '@matrixai/resources'; import * as discoveryUtils from './utils'; import * as discoveryErrors from './errors'; import * as nodesErrors from '../nodes/errors'; -import * as utils from '../utils'; import * as gestaltsUtils from '../gestalts/utils'; import * as claimsUtils from '../claims/utils'; import * as nodesUtils from '../nodes/utils'; +import { never, promise } from '../utils'; interface Discovery extends CreateDestroyStartStop {} @CreateDestroyStartStop( @@ -83,20 +82,15 @@ class Discovery { protected gestaltGraph: GestaltGraph; protected identitiesManager: IdentitiesManager; protected nodeManager: NodeManager; - protected discoveryDbDomain: string = this.constructor.name; - protected discoveryQueueDbDomain: Array = [ - this.discoveryDbDomain, - 'queue', - ]; - protected discoveryDb: DBLevel; - protected discoveryQueueDb: DBLevel; - protected lock: Mutex = new Mutex(); + + protected discoveryDbPath: LevelPath = [this.constructor.name]; + protected discoveryQueueDbPath: LevelPath = [this.constructor.name, 'queue']; protected discoveryQueueIdGenerator: DiscoveryQueueIdGenerator; protected visitedVertices = new Set(); - protected discoveryQueue: AsyncGenerator; protected discoveryProcess: Promise; - protected queuePlug: Mutex = new Mutex(); - protected queuePlugRelease: MutexInterface.Releaser | undefined; + protected queuePlug = promise(); + protected queueDrained = promise(); + protected lock: Lock = new Lock(); public constructor({ keyManager, @@ -130,60 +124,44 @@ class Discovery { fresh?: boolean; } = {}): Promise { this.logger.info(`Starting ${this.constructor.name}`); - const discoveryDb = await this.db.level(this.discoveryDbDomain); - // Queue stores DiscoveryQueueId -> GestaltKey - const discoveryQueueDb = await this.db.level( - this.discoveryQueueDbDomain[1], - discoveryDb, - ); if (fresh) { - await discoveryDb.clear(); + await this.db.clear(this.discoveryDbPath); } - this.discoveryDb = discoveryDb; - this.discoveryQueueDb = discoveryQueueDb; // Getting latest ID and creating ID generator let latestId: DiscoveryQueueId | undefined; - const keyStream = this.discoveryQueueDb.createKeyStream({ - limit: 1, - reverse: true, - }); - for await (const o of keyStream) { - latestId = IdInternal.fromBuffer(o); + const keyIterator = this.db.iterator( + { limit: 1, reverse: true, values: false }, + this.discoveryQueueDbPath, + ); + for await (const [keyPath] of keyIterator) { + const key = keyPath[0] as Buffer; + latestId = IdInternal.fromBuffer(key); } this.discoveryQueueIdGenerator = discoveryUtils.createDiscoveryQueueIdGenerator(latestId); - this.discoveryQueue = this.setupDiscoveryQueue(); - this.discoveryProcess = this.runDiscoveryQueue(); + // Starting the queue + this.discoveryProcess = this.setupDiscoveryQueue(); this.logger.info(`Started ${this.constructor.name}`); } public async stop(): Promise { this.logger.info(`Stopping ${this.constructor.name}`); - if (this.queuePlugRelease != null) { - this.queuePlugRelease(); - } - await this.discoveryQueue.return(); + this.queuePlug.resolveP(); await this.discoveryProcess; this.logger.info(`Stopped ${this.constructor.name}`); } - public async destroy() { + public async destroy(): Promise { this.logger.info(`Destroying ${this.constructor.name}`); - const discoveryDb = await this.db.level(this.discoveryDbDomain); - await discoveryDb.clear(); + await this.db.clear(this.discoveryDbPath); this.logger.info(`Destroyed ${this.constructor.name}`); } - public transaction: ResourceAcquire = async () => { - const release = await this.lock.acquire(); - return [async () => release(), this]; - }; - /** * Queues a node for discovery. Internally calls `pushKeyToDiscoveryQueue`. */ @ready(new discoveryErrors.ErrorDiscoveryNotRunning()) - public async queueDiscoveryByNode(nodeId: NodeId) { + public async queueDiscoveryByNode(nodeId: NodeId): Promise { const nodeKey = gestaltsUtils.keyFromNode(nodeId); await this.pushKeyToDiscoveryQueue(nodeKey); } @@ -196,87 +174,196 @@ class Discovery { public async queueDiscoveryByIdentity( providerId: ProviderId, identityId: IdentityId, - ) { + ): Promise { const identityKey = gestaltsUtils.keyFromIdentity(providerId, identityId); await this.pushKeyToDiscoveryQueue(identityKey); } /** - * Generator for the logic of iterating through the Discovery Queue. + * Async function for processing the Discovery Queue */ - public async *setupDiscoveryQueue(): AsyncGenerator { + public async setupDiscoveryQueue(): Promise { + this.logger.debug('Setting up DiscoveryQueue'); while (true) { - if (!(await this.queueIsEmpty())) { - for await (const o of this.discoveryQueueDb.createReadStream()) { - const vertexId = IdInternal.fromBuffer(o.key) as DiscoveryQueueId; - const data = o.value as Buffer; - const vertex = await this.db.deserializeDecrypt( - data, - false, - ); - const vertexGId = gestaltsUtils.ungestaltKey(vertex); - if (vertexGId.type === 'node') { - // The sigchain data of the vertex (containing all cryptolinks) - let vertexChainData: ChainData = {}; - // If the vertex we've found is our own node, we simply get our own chain - const nodeId = nodesUtils.decodeNodeId(vertexGId.nodeId)!; - if (nodeId.equals(this.keyManager.getNodeId())) { - const vertexChainDataEncoded = await this.sigchain.getChainData(); - // Decode all our claims - no need to verify (on our own sigchain) - for (const c in vertexChainDataEncoded) { - const claimId = c as ClaimIdEncoded; - vertexChainData[claimId] = claimsUtils.decodeClaim( - vertexChainDataEncoded[claimId], - ); + // Checking and waiting for items to process + if (await this.queueIsEmpty()) { + if (!(this[status] === 'stopping')) { + this.queuePlug = promise(); + this.queueDrained.resolveP(); + } + this.logger.debug('DiscoveryQueue is pausing'); + await this.queuePlug.p; + this.queueDrained = promise(); + } + if (this[status] === 'stopping') { + this.logger.debug('DiscoveryQueue is ending'); + break; + } + + // Processing queue + this.logger.debug('DiscoveryQueue is processing'); + for await (const [keyPath, vertex] of this.db.iterator( + { valueAsBuffer: false }, + this.discoveryQueueDbPath, + )) { + const key = keyPath[0] as Buffer; + const vertexId = IdInternal.fromBuffer(key); + this.logger.debug(`Processing vertex: ${vertex}`); + const vertexGId = gestaltsUtils.ungestaltKey(vertex); + switch (vertexGId.type) { + case 'node': + { + // The sigChain data of the vertex (containing all cryptolinks) + let vertexChainData: ChainData = {}; + // If the vertex we've found is our own node, we simply get our own chain + const nodeId = nodesUtils.decodeNodeId(vertexGId.nodeId)!; + if (nodeId.equals(this.keyManager.getNodeId())) { + const vertexChainDataEncoded = + await this.sigchain.getChainData(); + // Decode all our claims - no need to verify (on our own sigChain) + for (const c in vertexChainDataEncoded) { + const claimId = c as ClaimIdEncoded; + vertexChainData[claimId] = claimsUtils.decodeClaim( + vertexChainDataEncoded[claimId], + ); + } + // Otherwise, request the verified chain data from the node + } else { + try { + vertexChainData = await this.nodeManager.requestChainData( + nodeId, + ); + } catch (e) { + this.visitedVertices.add(vertex); + await this.removeKeyFromDiscoveryQueue(vertexId); + this.logger.error( + `Failed to discover ${vertexGId.nodeId} - ${e.toString()}`, + ); + continue; + } } - // Otherwise, request the verified chain data from the node - } else { - try { - vertexChainData = await this.nodeManager.requestChainData( - nodeId, - ); - } catch (e) { - this.visitedVertices.add(vertex); - await this.removeKeyFromDiscoveryQueue(vertexId); - this.logger.error( - `Failed to discover ${vertexGId.nodeId} - ${e.toString()}`, - ); - yield; - continue; + // TODO: for now, the chain data is treated as a 'disjoint' set of + // cryptolink claims from a node to another node/identity + // That is, we have no notion of revocations, or multiple claims to the + // same node/identity. Thus, we simply iterate over this chain of + // cryptolinks. + // Now have the NodeInfo of this vertex + const vertexNodeInfo: NodeInfo = { + id: nodesUtils.encodeNodeId(nodeId), + chain: vertexChainData, + }; + // Iterate over each of the claims in the chain (already verified) + // TODO: because we're iterating over keys in a record, I don't believe + // that this will iterate in lexicographical order of keys. For now, + // this doesn't matter though (because of the previous comment). + for (const claimId in vertexChainData) { + const claim: Claim = vertexChainData[claimId as ClaimIdEncoded]; + // If the claim is to a node + if (claim.payload.data.type === 'node') { + // Get the chain data of the linked node + // Could be node1 or node2 in the claim so get the one that's + // not equal to nodeId from above + const node1Id = nodesUtils.decodeNodeId( + claim.payload.data.node1, + )!; + const node2Id = nodesUtils.decodeNodeId( + claim.payload.data.node2, + )!; + const linkedVertexNodeId = node1Id.equals(nodeId) + ? node2Id + : node1Id; + const linkedVertexGK = + gestaltsUtils.keyFromNode(linkedVertexNodeId); + let linkedVertexChainData: ChainData; + try { + linkedVertexChainData = + await this.nodeManager.requestChainData( + linkedVertexNodeId, + ); + } catch (e) { + if ( + e instanceof nodesErrors.ErrorNodeConnectionDestroyed || + e instanceof nodesErrors.ErrorNodeConnectionTimeout + ) { + if (!this.visitedVertices.has(linkedVertexGK)) { + await this.pushKeyToDiscoveryQueue(linkedVertexGK); + } + this.logger.error( + `Failed to discover ${nodesUtils.encodeNodeId( + linkedVertexNodeId, + )} - ${e.toString()}`, + ); + continue; + } else { + throw e; + } + } + // With this verified chain, we can link + const linkedVertexNodeInfo: NodeInfo = { + id: nodesUtils.encodeNodeId(linkedVertexNodeId), + chain: linkedVertexChainData, + }; + await this.gestaltGraph.linkNodeAndNode( + vertexNodeInfo, + linkedVertexNodeInfo, + ); + // Add this vertex to the queue if it hasn't already been visited + if (!this.visitedVertices.has(linkedVertexGK)) { + await this.pushKeyToDiscoveryQueue(linkedVertexGK); + } + } + // Else the claim is to an identity + if (claim.payload.data.type === 'identity') { + // Attempt to get the identity info on the identity provider + const identityInfo = await this.getIdentityInfo( + claim.payload.data.provider, + claim.payload.data.identity, + ); + // If we can't get identity info, simply skip this claim + if (identityInfo == null) { + continue; + } + // Link the node to the found identity info + await this.gestaltGraph.linkNodeAndIdentity( + vertexNodeInfo, + identityInfo, + ); + // Add this identity vertex to the queue if it is not present + const linkedIdentityGK = gestaltsUtils.keyFromIdentity( + claim.payload.data.provider, + claim.payload.data.identity, + ); + if (!this.visitedVertices.has(linkedIdentityGK)) { + await this.pushKeyToDiscoveryQueue(linkedIdentityGK); + } + } } } - // TODO: for now, the chain data is treated as a 'disjoint' set of - // cryptolink claims from a node to another node/identity - // That is, we have no notion of revocations, or multiple claims to the - // same node/identity. Thus, we simply iterate over this chain of - // cryptolinks. - // Now have the NodeInfo of this vertex - const vertexNodeInfo: NodeInfo = { - id: nodesUtils.encodeNodeId(nodeId), - chain: vertexChainData, - }; - // Iterate over each of the claims in the chain (already verified) - // TODO: because we're iterating over keys in a record, I don't believe - // that this will iterate in lexicographical order of keys. For now, - // this doesn't matter though (because of the previous comment). - for (const claimId in vertexChainData) { - const claim: Claim = vertexChainData[claimId as ClaimIdEncoded]; - // If the claim is to a node - if (claim.payload.data.type === 'node') { - // Get the chain data of the linked node - // Could be node1 or node2 in the claim so get the one that's - // not equal to nodeId from above - const node1Id = nodesUtils.decodeNodeId( - claim.payload.data.node1, - )!; - const node2Id = nodesUtils.decodeNodeId( - claim.payload.data.node2, - )!; - const linkedVertexNodeId = node1Id.equals(nodeId) - ? node2Id - : node1Id; + break; + case 'identity': + { + // If the next vertex is an identity, perform a social discovery + // Firstly get the identity info of this identity + const vertexIdentityInfo = await this.getIdentityInfo( + vertexGId.providerId, + vertexGId.identityId, + ); + // If we don't have identity info, simply skip this vertex + if (vertexIdentityInfo == null) { + continue; + } + // Link the identity with each node from its claims on the provider + // Iterate over each of the claims + for (const id in vertexIdentityInfo.claims) { + const identityClaimId = id as IdentityClaimId; + const claim = vertexIdentityInfo.claims[identityClaimId]; + // Claims on an identity provider will always be node -> identity + // So just cast payload data as such + const data = claim.payload.data as ClaimLinkIdentity; + const linkedVertexNodeId = nodesUtils.decodeNodeId(data.node)!; const linkedVertexGK = gestaltsUtils.keyFromNode(linkedVertexNodeId); + // Get the chain data of this claimed node (so that we can link in GG) let linkedVertexChainData: ChainData; try { linkedVertexChainData = @@ -290,11 +377,8 @@ class Discovery { await this.pushKeyToDiscoveryQueue(linkedVertexGK); } this.logger.error( - `Failed to discover ${nodesUtils.encodeNodeId( - linkedVertexNodeId, - )} - ${e.toString()}`, + `Failed to discover ${data.node} - ${e.toString()}`, ); - yield; continue; } else { throw e; @@ -305,126 +389,31 @@ class Discovery { id: nodesUtils.encodeNodeId(linkedVertexNodeId), chain: linkedVertexChainData, }; - await this.gestaltGraph.linkNodeAndNode( - vertexNodeInfo, + await this.gestaltGraph.linkNodeAndIdentity( linkedVertexNodeInfo, + vertexIdentityInfo, ); - // Add this vertex to the queue if it hasn't already been visited + // Add this vertex to the queue if it is not present if (!this.visitedVertices.has(linkedVertexGK)) { await this.pushKeyToDiscoveryQueue(linkedVertexGK); } } - // Else the claim is to an identity - if (claim.payload.data.type === 'identity') { - // Attempt to get the identity info on the identity provider - const identityInfo = await this.getIdentityInfo( - claim.payload.data.provider, - claim.payload.data.identity, - ); - // If we can't get identity info, simply skip this claim - if (identityInfo == null) { - continue; - } - // Link the node to the found identity info - await this.gestaltGraph.linkNodeAndIdentity( - vertexNodeInfo, - identityInfo, - ); - // Add this identity vertex to the queue if it is not present - const linkedIdentityGK = gestaltsUtils.keyFromIdentity( - claim.payload.data.provider, - claim.payload.data.identity, - ); - if (!this.visitedVertices.has(linkedIdentityGK)) { - await this.pushKeyToDiscoveryQueue(linkedIdentityGK); - } - } - } - } else if (vertexGId.type === 'identity') { - // If the next vertex is an identity, perform a social discovery - // Firstly get the identity info of this identity - const vertexIdentityInfo = await this.getIdentityInfo( - vertexGId.providerId, - vertexGId.identityId, - ); - // If we don't have identity info, simply skip this vertex - if (vertexIdentityInfo == null) { - continue; - } - // Link the identity with each node from its claims on the provider - // Iterate over each of the claims - for (const id in vertexIdentityInfo.claims) { - const identityClaimId = id as IdentityClaimId; - const claim = vertexIdentityInfo.claims[identityClaimId]; - // Claims on an identity provider will always be node -> identity - // So just cast payload data as such - const data = claim.payload.data as ClaimLinkIdentity; - const linkedVertexNodeId = nodesUtils.decodeNodeId(data.node)!; - const linkedVertexGK = - gestaltsUtils.keyFromNode(linkedVertexNodeId); - // Get the chain data of this claimed node (so that we can link in GG) - let linkedVertexChainData: ChainData; - try { - linkedVertexChainData = await this.nodeManager.requestChainData( - linkedVertexNodeId, - ); - } catch (e) { - if ( - e instanceof nodesErrors.ErrorNodeConnectionDestroyed || - e instanceof nodesErrors.ErrorNodeConnectionTimeout - ) { - if (!this.visitedVertices.has(linkedVertexGK)) { - await this.pushKeyToDiscoveryQueue(linkedVertexGK); - } - yield; - this.logger.error( - `Failed to discover ${data.node} - ${e.toString()}`, - ); - continue; - } else { - throw e; - } - } - // With this verified chain, we can link - const linkedVertexNodeInfo: NodeInfo = { - id: nodesUtils.encodeNodeId(linkedVertexNodeId), - chain: linkedVertexChainData, - }; - await this.gestaltGraph.linkNodeAndIdentity( - linkedVertexNodeInfo, - vertexIdentityInfo, - ); - // Add this vertex to the queue if it is not present - if (!this.visitedVertices.has(linkedVertexGK)) { - await this.pushKeyToDiscoveryQueue(linkedVertexGK); - } } - } - this.visitedVertices.add(vertex); - await this.removeKeyFromDiscoveryQueue(vertexId); - yield; - } - } else { - if (!(this[status] === 'stopping')) { - this.queuePlugRelease = await this.queuePlug.acquire(); + break; + default: + never(); } - await this.queuePlug.waitForUnlock(); - } - if (this[status] === 'stopping') { - break; + this.visitedVertices.add(vertex); + await this.removeKeyFromDiscoveryQueue(vertexId); } } } /** - * Used for iterating over the discovery queue. This method should run - * continuously whenever the Discovery module is started and should be exited - * only during stopping. + * Will resolve once the queue has drained */ - protected async runDiscoveryQueue() { - for await (const _ of this.discoveryQueue) { - // Empty - } + public async waitForDrained(): Promise { + await this.queueDrained.p; } /** @@ -432,18 +421,17 @@ class Discovery { * transaction lock to ensure consistency. */ protected async queueIsEmpty(): Promise { - return await utils.withF([this.transaction], async () => { + return await this.lock.withF(async () => { let nextDiscoveryQueueId: DiscoveryQueueId | undefined; - const keyStream = this.discoveryQueueDb.createKeyStream({ - limit: 1, - }); - for await (const o of keyStream) { - nextDiscoveryQueueId = IdInternal.fromBuffer(o); - } - if (nextDiscoveryQueueId == null) { - return true; + const keyIterator = this.db.iterator( + { limit: 1, values: false }, + this.discoveryQueueDbPath, + ); + for await (const [keyPath] of keyIterator) { + const key = keyPath[0] as Buffer; + nextDiscoveryQueueId = IdInternal.fromBuffer(key); } - return false; + return nextDiscoveryQueueId == null; }); } @@ -452,25 +440,29 @@ class Discovery { * the queue if it was previously locked (due to being empty) * Will only add the Key if it does not already exist in the queue */ - protected async pushKeyToDiscoveryQueue(gk: GestaltKey) { - await utils.withF([this.transaction], async () => { - const valueStream = this.discoveryQueueDb.createValueStream({}); - for await (const key of valueStream) { - if (key === gk) { - return; + protected async pushKeyToDiscoveryQueue( + gestaltKey: GestaltKey, + ): Promise { + await resources.withF( + [this.db.transaction(), this.lock.lock()], + async ([tran]) => { + const valueIterator = tran.iterator( + { valueAsBuffer: false }, + this.discoveryQueueDbPath, + ); + for await (const [, value] of valueIterator) { + if (value === gestaltKey) { + return; + } } - } - const discoveryQueueId = this.discoveryQueueIdGenerator(); - await this.db.put( - this.discoveryQueueDbDomain, - idUtils.toBuffer(discoveryQueueId), - gk, - ); - }); - if (this.queuePlugRelease != null) { - this.queuePlugRelease(); - this.queuePlugRelease = undefined; - } + const discoveryQueueId = this.discoveryQueueIdGenerator(); + await tran.put( + [...this.discoveryQueueDbPath, idUtils.toBuffer(discoveryQueueId)], + gestaltKey, + ); + }, + ); + this.queuePlug.resolveP(); } /** @@ -478,9 +470,14 @@ class Discovery { * only be done after a Key has been discovered in order to remove it from * the beginning of the queue. */ - protected async removeKeyFromDiscoveryQueue(keyId: DiscoveryQueueId) { - await utils.withF([this.transaction], async () => { - await this.db.del(this.discoveryQueueDbDomain, idUtils.toBuffer(keyId)); + protected async removeKeyFromDiscoveryQueue( + keyId: DiscoveryQueueId, + ): Promise { + await this.lock.withF(async () => { + await this.db.del([ + ...this.discoveryQueueDbPath, + idUtils.toBuffer(keyId), + ]); }); } diff --git a/src/discovery/errors.ts b/src/discovery/errors.ts index cadf81048..ca83bfd18 100644 --- a/src/discovery/errors.ts +++ b/src/discovery/errors.ts @@ -1,12 +1,21 @@ -import { ErrorPolykey } from '../errors'; +import { ErrorPolykey, sysexits } from '../errors'; -class ErrorDiscovery extends ErrorPolykey {} +class ErrorDiscovery extends ErrorPolykey {} -class ErrorDiscoveryRunning extends ErrorDiscovery {} +class ErrorDiscoveryRunning extends ErrorDiscovery { + static description = 'Discovery is running'; + exitCode = sysexits.USAGE; +} -class ErrorDiscoveryDestroyed extends ErrorDiscovery {} +class ErrorDiscoveryDestroyed extends ErrorDiscovery { + static description = 'Discovery is destroyed'; + exitCode = sysexits.USAGE; +} -class ErrorDiscoveryNotRunning extends ErrorDiscovery {} +class ErrorDiscoveryNotRunning extends ErrorDiscovery { + static description = 'Discovery is not running'; + exitCode = sysexits.USAGE; +} export { ErrorDiscovery, diff --git a/src/errors.ts b/src/errors.ts index 1d224575e..3f6aba171 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,31 +1,55 @@ import ErrorPolykey from './ErrorPolykey'; import sysexits from './utils/sysexits'; -class ErrorPolykeyUnimplemented extends ErrorPolykey { - description = 'This is an unimplemented functionality'; +class ErrorPolykeyUnimplemented extends ErrorPolykey { + static description = 'This is an unimplemented functionality'; exitCode = sysexits.UNAVAILABLE; } -class ErrorPolykeyAgentRunning extends ErrorPolykey {} +class ErrorPolykeyUnknown extends ErrorPolykey { + static description = 'Unable to deserialise to known error'; + exitCode = sysexits.PROTOCOL; +} + +class ErrorPolykeyAgentRunning extends ErrorPolykey { + static description = 'PolykeyAgent is running'; + exitCode = sysexits.USAGE; +} -class ErrorPolykeyAgentNotRunning extends ErrorPolykey {} +class ErrorPolykeyAgentNotRunning extends ErrorPolykey { + static description = 'PolykeyAgent is not running'; + exitCode = sysexits.USAGE; +} -class ErrorPolykeyAgentDestroyed extends ErrorPolykey {} +class ErrorPolykeyAgentDestroyed extends ErrorPolykey { + static description = 'PolykeyAgent is destroyed'; + exitCode = sysexits.USAGE; +} -class ErrorPolykeyClientRunning extends ErrorPolykey {} +class ErrorPolykeyClientRunning extends ErrorPolykey { + static description = 'PolykeyClient is running'; + exitCode = sysexits.USAGE; +} -class ErrorPolykeyClientNotRunning extends ErrorPolykey {} +class ErrorPolykeyClientNotRunning extends ErrorPolykey { + static description = 'PolykeyClient is not running'; + exitCode = sysexits.USAGE; +} -class ErrorPolykeyClientDestroyed extends ErrorPolykey {} +class ErrorPolykeyClientDestroyed extends ErrorPolykey { + static description = 'PolykeyClient is destroyed'; + exitCode = sysexits.USAGE; +} -class ErrorInvalidId extends ErrorPolykey {} +class ErrorInvalidId extends ErrorPolykey {} -class ErrorInvalidConfigEnvironment extends ErrorPolykey {} +class ErrorInvalidConfigEnvironment extends ErrorPolykey {} export { sysexits, ErrorPolykey, ErrorPolykeyUnimplemented, + ErrorPolykeyUnknown, ErrorPolykeyAgentRunning, ErrorPolykeyAgentNotRunning, ErrorPolykeyAgentDestroyed, diff --git a/src/gestalts/GestaltGraph.ts b/src/gestalts/GestaltGraph.ts index 4e9036d7a..b746700d9 100644 --- a/src/gestalts/GestaltGraph.ts +++ b/src/gestalts/GestaltGraph.ts @@ -1,4 +1,4 @@ -import type { DB, DBLevel, DBOp } from '@matrixai/db'; +import type { DB, DBTransaction, KeyPath, LevelPath } from '@matrixai/db'; import type { Gestalt, GestaltAction, @@ -10,14 +10,14 @@ import type { } from './types'; import type { NodeId, NodeInfo } from '../nodes/types'; import type { IdentityId, IdentityInfo, ProviderId } from '../identities/types'; -import type { ACL } from '../acl'; import type { Permission } from '../acl/types'; -import { Mutex } from 'async-mutex'; +import type ACL from '../acl/ACL'; import Logger from '@matrixai/logger'; import { CreateDestroyStartStop, ready, } from '@matrixai/async-init/dist/CreateDestroyStartStop'; +import { withF } from '@matrixai/resources'; import * as gestaltsUtils from './utils'; import * as gestaltsErrors from './errors'; import * as aclUtils from '../acl/utils'; @@ -30,22 +30,6 @@ interface GestaltGraph extends CreateDestroyStartStop {} new gestaltsErrors.ErrorGestaltsGraphDestroyed(), ) class GestaltGraph { - protected logger: Logger; - protected db: DB; - protected acl: ACL; - protected graphDbDomain: string = this.constructor.name; - protected graphMatrixDbDomain: Array = [this.graphDbDomain, 'matrix']; - protected graphNodesDbDomain: Array = [this.graphDbDomain, 'nodes']; - protected graphIdentitiesDbDomain: Array = [ - this.graphDbDomain, - 'identities', - ]; - protected graphDb: DBLevel; - protected graphMatrixDb: DBLevel; - protected graphNodesDb: DBLevel; - protected graphIdentitiesDb: DBLevel; - protected lock: Mutex = new Mutex(); - static async createGestaltGraph({ db, acl, @@ -64,38 +48,34 @@ class GestaltGraph { return gestaltGraph; } + protected logger: Logger; + protected db: DB; + protected acl: ACL; + protected gestaltGraphDbPath: LevelPath = [this.constructor.name]; + protected gestaltGraphMatrixDbPath: LevelPath = [ + this.constructor.name, + 'matrix', + ]; + protected gestaltGraphNodesDbPath: LevelPath = [ + this.constructor.name, + 'nodes', + ]; + protected gestaltGraphIdentitiesDbPath: LevelPath = [ + this.constructor.name, + 'identities', + ]; + constructor({ db, acl, logger }: { db: DB; acl: ACL; logger: Logger }) { this.logger = logger; this.db = db; this.acl = acl; } - get locked(): boolean { - return this.lock.isLocked(); - } - public async start({ fresh = false }: { fresh?: boolean } = {}) { this.logger.info(`Starting ${this.constructor.name}`); - const graphDb = await this.db.level(this.graphDbDomain); - const graphMatrixDb = await this.db.level( - this.graphMatrixDbDomain[1], - graphDb, - ); - const graphNodesDb = await this.db.level( - this.graphNodesDbDomain[1], - graphDb, - ); - const graphIdentitiesDb = await this.db.level( - this.graphIdentitiesDbDomain[1], - graphDb, - ); if (fresh) { - await graphDb.clear(); + await this.db.clear(this.gestaltGraphDbPath); } - this.graphDb = graphDb; - this.graphMatrixDb = graphMatrixDb; - this.graphNodesDb = graphNodesDb; - this.graphIdentitiesDb = graphIdentitiesDb; this.logger.info(`Started ${this.constructor.name}`); } @@ -106,200 +86,173 @@ class GestaltGraph { async destroy() { this.logger.info(`Destroying ${this.constructor.name}`); - const graphDb = await this.db.level(this.graphDbDomain); - await graphDb.clear(); + await this.db.clear(this.gestaltGraphDbPath); this.logger.info(`Destroyed ${this.constructor.name}`); } - /** - * Run several operations within the same lock - * This does not ensure atomicity of the underlying database - * Database atomicity still depends on the underlying operation - */ - public async transaction( - f: (gestaltGraph: GestaltGraph) => Promise, + @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) + public async withTransactionF( + f: (tran: DBTransaction) => Promise, ): Promise { - const release = await this.lock.acquire(); - try { - return await f(this); - } finally { - release(); - } - } - - /** - * Transaction wrapper that will not lock if the operation was executed - * within a transaction context - */ - public async _transaction(f: () => Promise): Promise { - if (this.lock.isLocked()) { - return await f(); - } else { - return await this.transaction(f); - } + return withF([this.db.transaction()], ([tran]) => f(tran)); } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) - public async getGestalts(): Promise> { - return await this._transaction(async () => { - const unvisited: Map = new Map(); - for await (const o of this.graphMatrixDb.createReadStream()) { - const gK = (o as any).key.toString() as GestaltKey; - const data = (o as any).value as Buffer; - const gKs = await this.db.deserializeDecrypt( - data, - false, - ); - unvisited.set(gK, gKs); - } - const gestalts: Array = []; - let gestalt: Gestalt; - for (const gKSet of unvisited) { - gestalt = { - matrix: {}, - nodes: {}, - identities: {}, - }; - const gK = gKSet[0]; - const queue = [gK]; - while (true) { - const vertex = queue.shift(); - if (vertex == null) { - gestalts.push(gestalt); - break; - } - const gId = gestaltsUtils.ungestaltKey(vertex); - const vertexKeys = unvisited.get(vertex); - if (vertexKeys == null) { - // This should not happen - break; - } - gestalt.matrix[vertex] = vertexKeys; - if (gId.type === 'node') { - const nodeInfo = await this.db.get( - this.graphNodesDbDomain, - vertex as GestaltNodeKey, - ); - gestalt.nodes[vertex] = nodeInfo!; - } else if (gId.type === 'identity') { - const identityInfo = await this.db.get( - this.graphIdentitiesDbDomain, - vertex as GestaltIdentityKey, - ); - gestalt.identities[vertex] = identityInfo!; - } - unvisited.delete(vertex); - const neighbours: Array = Object.keys(vertexKeys).filter( - (k: GestaltKey) => unvisited.has(k), - ) as Array; - queue.push(...neighbours); + public async getGestalts(tran?: DBTransaction): Promise> { + if (tran == null) { + return this.withTransactionF(async (tran) => this.getGestalts(tran)); + } + const unvisited: Map = new Map(); + for await (const [k, gKs] of tran.iterator( + { valueAsBuffer: false }, + [...this.gestaltGraphMatrixDbPath], + )) { + const gK = k.toString() as GestaltKey; + unvisited.set(gK, gKs); + } + const gestalts: Array = []; + let gestalt: Gestalt; + for (const gKSet of unvisited) { + gestalt = { + matrix: {}, + nodes: {}, + identities: {}, + }; + const gK = gKSet[0]; + const queue = [gK]; + while (true) { + const vertex = queue.shift(); + if (vertex == null) { + gestalts.push(gestalt); + break; + } + const gId = gestaltsUtils.ungestaltKey(vertex); + const vertexKeys = unvisited.get(vertex); + if (vertexKeys == null) { + // This should not happen + break; } + gestalt.matrix[vertex] = vertexKeys; + if (gId.type === 'node') { + const vertexPath = [ + ...this.gestaltGraphNodesDbPath, + vertex as GestaltNodeKey, + ] as unknown as KeyPath; + const nodeInfo = await tran.get(vertexPath); + gestalt.nodes[vertex] = nodeInfo!; + } else if (gId.type === 'identity') { + const vertexPath = [ + ...this.gestaltGraphIdentitiesDbPath, + vertex as GestaltIdentityKey, + ] as unknown as KeyPath; + const identityInfo = await tran.get(vertexPath); + gestalt.identities[vertex] = identityInfo!; + } + unvisited.delete(vertex); + const neighbours: Array = Object.keys(vertexKeys).filter( + (k: GestaltKey) => unvisited.has(k), + ) as Array; + queue.push(...neighbours); } - return gestalts; - }); + } + return gestalts; } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) - public async getGestaltByNode(nodeId: NodeId): Promise { + public async getGestaltByNode( + nodeId: NodeId, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.withTransactionF(async (tran) => + this.getGestaltByNode(nodeId, tran), + ); + } const nodeKey = gestaltsUtils.keyFromNode(nodeId); - return this.getGestaltByKey(nodeKey); + return this.getGestaltByKey(nodeKey, tran); } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public async getGestaltByIdentity( providerId: ProviderId, identityId: IdentityId, + tran?: DBTransaction, ): Promise { + if (tran == null) { + return this.withTransactionF(async (tran) => + this.getGestaltByIdentity(providerId, identityId, tran), + ); + } const identityKey = gestaltsUtils.keyFromIdentity(providerId, identityId); - return this.getGestaltByKey(identityKey); + return this.getGestaltByKey(identityKey, tran); } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) - public async setIdentity(identityInfo: IdentityInfo): Promise { - return await this._transaction(async () => { - const ops = await this.setIdentityOps(identityInfo); - await this.db.batch(ops); - }); - } - - @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) - public async setIdentityOps( + public async setIdentity( identityInfo: IdentityInfo, - ): Promise> { + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.withTransactionF(async (tran) => + this.setIdentity(identityInfo, tran), + ); + } const identityKey = gestaltsUtils.keyFromIdentity( identityInfo.providerId, identityInfo.identityId, ); + const identityKeyPath = [ + ...this.gestaltGraphMatrixDbPath, + identityKey, + ] as unknown as KeyPath; const identityKeyKeys = - (await this.db.get( - this.graphMatrixDbDomain, - identityKey, - )) ?? {}; - const ops: Array = [ - { - type: 'put', - domain: this.graphMatrixDbDomain, - key: identityKey, - value: identityKeyKeys, - }, - { - type: 'put', - domain: this.graphIdentitiesDbDomain, - key: identityKey, - value: identityInfo, - }, - ]; - return ops; - } - - @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) - public async unsetIdentity(providerId: ProviderId, identityId: IdentityId) { - return await this._transaction(async () => { - return await this.acl._transaction(async () => { - const ops = await this.unsetIdentityOps(providerId, identityId); - await this.db.batch(ops); - }); - }); + (await tran.get(identityKeyPath)) ?? {}; + await tran.put(identityKeyPath, identityKeyKeys); + const identityInfoPath = [ + ...this.gestaltGraphIdentitiesDbPath, + identityKey, + ] as unknown as KeyPath; + await tran.put(identityInfoPath, identityInfo); } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) - public async unsetIdentityOps( + public async unsetIdentity( providerId: ProviderId, identityId: IdentityId, + tran?: DBTransaction, ) { + if (tran == null) { + return this.withTransactionF(async (tran) => + this.unsetIdentity(providerId, identityId, tran), + ); + } const identityKey = gestaltsUtils.keyFromIdentity(providerId, identityId); - const identityKeyKeys = await this.db.get( - this.graphMatrixDbDomain, + const identityKeyPath = [ + ...this.gestaltGraphMatrixDbPath, identityKey, - ); - const ops: Array = []; + ] as unknown as KeyPath; + const identityKeyKeys = await tran.get(identityKeyPath); if (identityKeyKeys == null) { - return ops; + return; } - ops.push({ - type: 'del', - domain: this.graphIdentitiesDbDomain, - key: identityKey, - }); + const identityPath = [ + ...this.gestaltGraphIdentitiesDbPath, + identityKey, + ] as unknown as KeyPath; + await tran.del(identityPath); for (const key of Object.keys(identityKeyKeys) as Array) { const gId = gestaltsUtils.ungestaltKey(key); if (gId.type === 'node') { - ops.push( - ...(await this.unlinkNodeAndIdentityOps( - nodesUtils.decodeNodeId(gId.nodeId)!, - providerId, - identityId, - )), + await this.unlinkNodeAndIdentity( + nodesUtils.decodeNodeId(gId.nodeId)!, + providerId, + identityId, + tran, ); } } // Ensure that an empty key set is still deleted - ops.push({ - type: 'del', - domain: this.graphMatrixDbDomain, - key: identityKey, - }); - return ops; + await tran.del(identityKeyPath); } /** @@ -309,53 +262,41 @@ class GestaltGraph { * to a new gestalt permission in the acl */ @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) - public async setNode(nodeInfo: NodeInfo): Promise { - return await this._transaction(async () => { - return await this.acl._transaction(async () => { - const ops = await this.setNodeOps(nodeInfo); - await this.db.batch(ops); - }); - }); - } - - @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) - public async setNodeOps(nodeInfo: NodeInfo): Promise> { + public async setNode( + nodeInfo: NodeInfo, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.withTransactionF(async (tran) => + this.setNode(nodeInfo, tran), + ); + } const nodeKey = gestaltsUtils.keyFromNode( nodesUtils.decodeNodeId(nodeInfo.id)!, ); - const ops: Array = []; - let nodeKeyKeys = await this.db.get( - this.graphMatrixDbDomain, + const nodeKeyPath = [ + ...this.gestaltGraphMatrixDbPath, nodeKey, - ); + ] as unknown as KeyPath; + let nodeKeyKeys = await tran.get(nodeKeyPath); if (nodeKeyKeys == null) { nodeKeyKeys = {}; // Sets the gestalt in the acl - ops.push( - ...(await this.acl.setNodePermOps( - nodesUtils.decodeNodeId(nodeInfo.id)!, - { - gestalt: {}, - vaults: {}, - }, - )), + await this.acl.setNodePerm( + nodesUtils.decodeNodeId(nodeInfo.id)!, + { + gestalt: {}, + vaults: {}, + }, + tran, ); } - ops.push( - { - type: 'put', - domain: this.graphMatrixDbDomain, - key: nodeKey, - value: nodeKeyKeys, - }, - { - type: 'put', - domain: this.graphNodesDbDomain, - key: nodeKey, - value: nodeInfo, - }, - ); - return ops; + await tran.put(nodeKeyPath, nodeKeyKeys); + const nodePath = [ + ...this.gestaltGraphNodesDbPath, + nodeKey, + ] as unknown as KeyPath; + await tran.put(nodePath, nodeInfo); } /** @@ -364,81 +305,61 @@ class GestaltGraph { * to the gestalt permission in the acl */ @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) - public async unsetNode(nodeId: NodeId): Promise { - return await this._transaction(async () => { - return await this.acl._transaction(async () => { - const ops = await this.unsetNodeOps(nodeId); - await this.db.batch(ops); - }); - }); - } - - @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) - public async unsetNodeOps(nodeId: NodeId): Promise> { + public async unsetNode(nodeId: NodeId, tran?: DBTransaction): Promise { + if (tran == null) { + return this.withTransactionF(async (tran) => + this.unsetNode(nodeId, tran), + ); + } const nodeKey = gestaltsUtils.keyFromNode(nodeId); - const nodeKeyKeys = await this.db.get( - this.graphMatrixDbDomain, + const nodeKeyPath = [ + ...this.gestaltGraphMatrixDbPath, nodeKey, - ); - const ops: Array = []; + ] as unknown as KeyPath; + const nodeKeyKeys = await tran.get(nodeKeyPath); if (nodeKeyKeys == null) { - return ops; + return; } - ops.push({ - type: 'del', - domain: this.graphNodesDbDomain, - key: nodeKey, - }); + const nodePath = [ + ...this.gestaltGraphNodesDbPath, + nodeKey, + ] as unknown as KeyPath; + await tran.del(nodePath); for (const key of Object.keys(nodeKeyKeys) as Array) { const gId = gestaltsUtils.ungestaltKey(key); if (gId.type === 'node') { - ops.push( - ...(await this.unlinkNodeAndNodeOps( - nodeId, - nodesUtils.decodeNodeId(gId.nodeId)!, - )), + await this.unlinkNodeAndNode( + nodeId, + nodesUtils.decodeNodeId(gId.nodeId)!, + tran, ); } else if (gId.type === 'identity') { - ops.push( - ...(await this.unlinkNodeAndIdentityOps( - nodeId, - gId.providerId, - gId.identityId, - )), + await this.unlinkNodeAndIdentity( + nodeId, + gId.providerId, + gId.identityId, + tran, ); } } // Ensure that an empty key set is still deleted - ops.push({ - type: 'del', - domain: this.graphMatrixDbDomain, - key: nodeKey, - }); + await tran.del(nodeKeyPath); // Unsets the gestalt in the acl // this must be done after all unlinking operations - ops.push(...(await this.acl.unsetNodePermOps(nodeId))); - return ops; + await this.acl.unsetNodePerm(nodeId, tran); } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public async linkNodeAndIdentity( nodeInfo: NodeInfo, identityInfo: IdentityInfo, + tran?: DBTransaction, ): Promise { - return await this._transaction(async () => { - return await this.acl._transaction(async () => { - const ops = await this.linkNodeAndIdentityOps(nodeInfo, identityInfo); - await this.db.batch(ops); - }); - }); - } - - @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) - public async linkNodeAndIdentityOps( - nodeInfo: NodeInfo, - identityInfo: IdentityInfo, - ): Promise> { - const ops: Array = []; + if (tran == null) { + return this.withTransactionF(async (tran) => + this.linkNodeAndIdentity(nodeInfo, identityInfo, tran), + ); + } const nodeKey = gestaltsUtils.keyFromNode( nodesUtils.decodeNodeId(nodeInfo.id)!, ); @@ -446,14 +367,16 @@ class GestaltGraph { identityInfo.providerId, identityInfo.identityId, ); - let nodeKeyKeys = await this.db.get( - this.graphMatrixDbDomain, + const nodeKeyPath = [ + ...this.gestaltGraphMatrixDbPath, nodeKey, - ); - let identityKeyKeys = await this.db.get( - this.graphMatrixDbDomain, + ] as unknown as KeyPath; + const identityKeyPath = [ + ...this.gestaltGraphMatrixDbPath, identityKey, - ); + ] as unknown as KeyPath; + let nodeKeyKeys = await tran.get(nodeKeyPath); + let identityKeyKeys = await tran.get(identityKeyPath); // If they are already connected we do nothing if ( nodeKeyKeys && @@ -461,7 +384,7 @@ class GestaltGraph { identityKeyKeys && nodeKey in identityKeyKeys ) { - return ops; + return; } let nodeNew = false; if (nodeKeyKeys == null) { @@ -490,14 +413,13 @@ class GestaltGraph { // else // join node gestalt's permission to the identity gestalt if (nodeNew && identityNew) { - ops.push( - ...(await this.acl.setNodePermOps( - nodesUtils.decodeNodeId(nodeInfo.id)!, - { - gestalt: {}, - vaults: {}, - }, - )), + await this.acl.setNodePerm( + nodesUtils.decodeNodeId(nodeInfo.id)!, + { + gestalt: {}, + vaults: {}, + }, + tran, ); } else if ( !nodeNew && @@ -507,6 +429,7 @@ class GestaltGraph { const [, identityNodeKeys] = await this.traverseGestalt( Object.keys(identityKeyKeys) as Array, [identityKey], + tran, ); const identityNodeIds = Array.from(identityNodeKeys, (key) => gestaltsUtils.nodeFromKey(key), @@ -514,32 +437,32 @@ class GestaltGraph { // These must exist const nodePerm = (await this.acl.getNodePerm( nodesUtils.decodeNodeId(nodeInfo.id)!, + tran, )) as Permission; const identityPerm = (await this.acl.getNodePerm( identityNodeIds[0], + tran, )) as Permission; // Union the perms together const permNew = aclUtils.permUnion(nodePerm, identityPerm); // Node perm is updated and identity perm is joined to node perm // this has to be done as 1 call to acl in order to combine ref count update // and the perm record update - ops.push( - ...(await this.acl.joinNodePermOps( - nodesUtils.decodeNodeId(nodeInfo.id)!, - identityNodeIds, - permNew, - )), + await this.acl.joinNodePerm( + nodesUtils.decodeNodeId(nodeInfo.id)!, + identityNodeIds, + permNew, + tran, ); } else if (nodeNew && !identityNew) { if (utils.isEmptyObject(identityKeyKeys)) { - ops.push( - ...(await this.acl.setNodePermOps( - nodesUtils.decodeNodeId(nodeInfo.id)!, - { - gestalt: {}, - vaults: {}, - }, - )), + await this.acl.setNodePerm( + nodesUtils.decodeNodeId(nodeInfo.id)!, + { + gestalt: {}, + vaults: {}, + }, + tran, ); } else { let identityNodeKey: GestaltNodeKey; @@ -548,75 +471,55 @@ class GestaltGraph { break; } const identityNodeId = gestaltsUtils.nodeFromKey(identityNodeKey!); - ops.push( - ...(await this.acl.joinNodePermOps(identityNodeId, [ - nodesUtils.decodeNodeId(nodeInfo.id)!, - ])), + await this.acl.joinNodePerm( + identityNodeId, + [nodesUtils.decodeNodeId(nodeInfo.id)!], + undefined, + tran, ); } } nodeKeyKeys[identityKey] = null; identityKeyKeys[nodeKey] = null; - ops.push( - { - type: 'put', - domain: this.graphMatrixDbDomain, - key: nodeKey, - value: nodeKeyKeys, - }, - { - type: 'put', - domain: this.graphMatrixDbDomain, - key: identityKey, - value: identityKeyKeys, - }, - { - type: 'put', - domain: this.graphNodesDbDomain, - key: nodeKey, - value: nodeInfo, - }, - { - type: 'put', - domain: this.graphIdentitiesDbDomain, - key: identityKey, - value: identityInfo, - }, - ); - return ops; + await tran.put(nodeKeyPath, nodeKeyKeys); + await tran.put(identityKeyPath, identityKeyKeys); + const nodePath = [ + ...this.gestaltGraphNodesDbPath, + nodeKey, + ] as unknown as KeyPath; + await tran.put(nodePath, nodeInfo); + const identityPath = [ + ...this.gestaltGraphIdentitiesDbPath, + identityKey, + ] as unknown as KeyPath; + await tran.put(identityPath, identityInfo); } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public async linkNodeAndNode( nodeInfo1: NodeInfo, nodeInfo2: NodeInfo, + tran?: DBTransaction, ): Promise { - return await this._transaction(async () => { - return await this.acl._transaction(async () => { - const ops = await this.linkNodeAndNodeOps(nodeInfo1, nodeInfo2); - await this.db.batch(ops); - }); - }); - } - - @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) - public async linkNodeAndNodeOps( - nodeInfo1: NodeInfo, - nodeInfo2: NodeInfo, - ): Promise> { - const ops: Array = []; + if (tran == null) { + return this.withTransactionF(async (tran) => + this.linkNodeAndNode(nodeInfo1, nodeInfo2, tran), + ); + } const nodeIdEncoded1 = nodesUtils.decodeNodeId(nodeInfo1.id)!; const nodeIdEncoded2 = nodesUtils.decodeNodeId(nodeInfo2.id)!; const nodeKey1 = gestaltsUtils.keyFromNode(nodeIdEncoded1); const nodeKey2 = gestaltsUtils.keyFromNode(nodeIdEncoded2); - let nodeKeyKeys1 = await this.db.get( - this.graphMatrixDbDomain, + const nodeKey1Path = [ + ...this.gestaltGraphMatrixDbPath, nodeKey1, - ); - let nodeKeyKeys2 = await this.db.get( - this.graphMatrixDbDomain, + ] as unknown as KeyPath; + const nodeKey2Path = [ + ...this.gestaltGraphMatrixDbPath, nodeKey2, - ); + ] as unknown as KeyPath; + let nodeKeyKeys1 = await tran.get(nodeKey1Path); + let nodeKeyKeys2 = await tran.get(nodeKey2Path); // If they are already connected we do nothing if ( nodeKeyKeys1 && @@ -624,7 +527,7 @@ class GestaltGraph { nodeKeyKeys2 && nodeKey1 in nodeKeyKeys2 ) { - return ops; + return; } let nodeNew1 = false; if (nodeKeyKeys1 == null) { @@ -647,16 +550,19 @@ class GestaltGraph { // if node 1 is new but node 2 exists // join node 1 gestalt's permission to the node 2 gestalt if (nodeNew1 && nodeNew2) { - ops.push( - ...(await this.acl.setNodesPermOps([nodeIdEncoded1, nodeIdEncoded2], { + await this.acl.setNodesPerm( + [nodeIdEncoded1, nodeIdEncoded2], + { gestalt: {}, vaults: {}, - })), + }, + tran, ); } else if (!nodeNew1 && !nodeNew2) { const [, nodeNodeKeys2] = await this.traverseGestalt( Object.keys(nodeKeyKeys2) as Array, [nodeKey2], + tran, ); const nodeNodeIds2 = Array.from(nodeNodeKeys2, (key) => gestaltsUtils.nodeFromKey(key), @@ -664,60 +570,47 @@ class GestaltGraph { // These must exist const nodePerm1 = (await this.acl.getNodePerm( nodeIdEncoded1, + tran, )) as Permission; const nodePerm2 = (await this.acl.getNodePerm( nodeIdEncoded2, + tran, )) as Permission; // Union the perms together const permNew = aclUtils.permUnion(nodePerm1, nodePerm2); // Node perm 1 is updated and node perm 2 is joined to node perm 2 // this has to be done as 1 call to acl in order to combine ref count update // and the perm record update - ops.push( - ...(await this.acl.joinNodePermOps( - nodeIdEncoded1, - nodeNodeIds2, - permNew, - )), - ); + await this.acl.joinNodePerm(nodeIdEncoded1, nodeNodeIds2, permNew, tran); } else if (nodeNew1 && !nodeNew2) { - ops.push( - ...(await this.acl.joinNodePermOps(nodeIdEncoded2, [nodeIdEncoded1])), + await this.acl.joinNodePerm( + nodeIdEncoded2, + [nodeIdEncoded1], + undefined, + tran, ); } else if (!nodeNew1 && nodeNew2) { - ops.push( - ...(await this.acl.joinNodePermOps(nodeIdEncoded1, [nodeIdEncoded2])), + await this.acl.joinNodePerm( + nodeIdEncoded1, + [nodeIdEncoded2], + undefined, + tran, ); } nodeKeyKeys1[nodeKey2] = null; nodeKeyKeys2[nodeKey1] = null; - ops.push( - { - type: 'put', - domain: this.graphMatrixDbDomain, - key: nodeKey1, - value: nodeKeyKeys1, - }, - { - type: 'put', - domain: this.graphMatrixDbDomain, - key: nodeKey2, - value: nodeKeyKeys2, - }, - { - type: 'put', - domain: this.graphNodesDbDomain, - key: nodeKey1, - value: nodeInfo1, - }, - { - type: 'put', - domain: this.graphNodesDbDomain, - key: nodeKey2, - value: nodeInfo2, - }, - ); - return ops; + await tran.put(nodeKey1Path, nodeKeyKeys1); + await tran.put(nodeKey2Path, nodeKeyKeys2); + const node1Path = [ + ...this.gestaltGraphNodesDbPath, + nodeKey1, + ] as unknown as KeyPath; + await tran.put(node1Path, nodeInfo1); + const node2Path = [ + ...this.gestaltGraphNodesDbPath, + nodeKey2, + ] as unknown as KeyPath; + await tran.put(node2Path, nodeInfo2); } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) @@ -725,56 +618,35 @@ class GestaltGraph { nodeId: NodeId, providerId: ProviderId, identityId: IdentityId, + tran?: DBTransaction, ): Promise { - return await this._transaction(async () => { - return await this.acl._transaction(async () => { - const ops = await this.unlinkNodeAndIdentityOps( - nodeId, - providerId, - identityId, - ); - await this.db.batch(ops); - }); - }); - } - - @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) - public async unlinkNodeAndIdentityOps( - nodeId: NodeId, - providerId: ProviderId, - identityId: IdentityId, - ): Promise> { + if (tran == null) { + return this.withTransactionF(async (tran) => + this.unlinkNodeAndIdentity(nodeId, providerId, identityId, tran), + ); + } const nodeKey = gestaltsUtils.keyFromNode(nodeId); const identityKey = gestaltsUtils.keyFromIdentity(providerId, identityId); - const nodeKeyKeys = await this.db.get( - this.graphMatrixDbDomain, + const nodeKeyPath = [ + ...this.gestaltGraphMatrixDbPath, nodeKey, - ); - const identityKeyKeys = await this.db.get( - this.graphMatrixDbDomain, + ] as unknown as KeyPath; + const identityKeyPath = [ + ...this.gestaltGraphMatrixDbPath, identityKey, - ); + ] as unknown as KeyPath; + const nodeKeyKeys = await tran.get(nodeKeyPath); + const identityKeyKeys = await tran.get(identityKeyPath); let unlinking = false; - const ops: Array = []; if (nodeKeyKeys && identityKey in nodeKeyKeys) { unlinking = true; delete nodeKeyKeys[identityKey]; - ops.push({ - type: 'put', - domain: this.graphMatrixDbDomain, - key: nodeKey, - value: nodeKeyKeys, - }); + await tran.put(nodeKeyPath, nodeKeyKeys); } if (identityKeyKeys && nodeKey in identityKeyKeys) { unlinking = true; delete identityKeyKeys[nodeKey]; - ops.push({ - type: 'put', - domain: this.graphMatrixDbDomain, - key: identityKey, - value: identityKeyKeys, - }); + await tran.put(identityKeyPath, identityKeyKeys); } if (nodeKeyKeys && identityKeyKeys && unlinking) { // Check if the gestalts have split @@ -783,69 +655,53 @@ class GestaltGraph { await this.traverseGestalt( Object.keys(nodeKeyKeys) as Array, [nodeKey], + tran, ); if (!gestaltIdentityKeys.has(identityKey)) { const nodeIds = Array.from(gestaltNodeKeys, (key) => gestaltsUtils.nodeFromKey(key), ); // It is assumed that an existing gestalt has a permission - const perm = (await this.acl.getNodePerm(nodeId)) as Permission; + const perm = (await this.acl.getNodePerm(nodeId, tran)) as Permission; // This remaps all existing nodes to a new permission - ops.push(...(await this.acl.setNodesPermOps(nodeIds, perm))); + await this.acl.setNodesPerm(nodeIds, perm, tran); } } - return ops; } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public async unlinkNodeAndNode( nodeId1: NodeId, nodeId2: NodeId, + tran?: DBTransaction, ): Promise { - return await this._transaction(async () => { - return await this.acl._transaction(async () => { - const ops = await this.unlinkNodeAndNodeOps(nodeId1, nodeId2); - await this.db.batch(ops); - }); - }); - } - - @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) - public async unlinkNodeAndNodeOps( - nodeId1: NodeId, - nodeId2: NodeId, - ): Promise> { + if (tran == null) { + return this.withTransactionF(async (tran) => + this.unlinkNodeAndNode(nodeId1, nodeId2, tran), + ); + } const nodeKey1 = gestaltsUtils.keyFromNode(nodeId1); const nodeKey2 = gestaltsUtils.keyFromNode(nodeId2); - const nodeKeyKeys1 = await this.db.get( - this.graphMatrixDbDomain, + const nodeKey1Path = [ + ...this.gestaltGraphMatrixDbPath, nodeKey1, - ); - const nodeKeyKeys2 = await this.db.get( - this.graphMatrixDbDomain, + ] as unknown as KeyPath; + const nodeKey2Path = [ + ...this.gestaltGraphMatrixDbPath, nodeKey2, - ); + ] as unknown as KeyPath; + const nodeKeyKeys1 = await tran.get(nodeKey1Path); + const nodeKeyKeys2 = await tran.get(nodeKey2Path); let unlinking = false; - const ops: Array = []; if (nodeKeyKeys1 && nodeKey2 in nodeKeyKeys1) { unlinking = true; delete nodeKeyKeys1[nodeKey2]; - ops.push({ - type: 'put', - domain: this.graphMatrixDbDomain, - key: nodeKey1, - value: nodeKeyKeys1, - }); + await tran.put(nodeKey1Path, nodeKeyKeys1); } if (nodeKeyKeys2 && nodeKey1 in nodeKeyKeys2) { unlinking = true; delete nodeKeyKeys2[nodeKey1]; - ops.push({ - type: 'put', - domain: this.graphMatrixDbDomain, - key: nodeKey2, - value: nodeKeyKeys2, - }); + await tran.put(nodeKey2Path, nodeKeyKeys2); } if (nodeKeyKeys1 && nodeKeyKeys2 && unlinking) { // Check if the gestalts have split @@ -853,99 +709,106 @@ class GestaltGraph { const [, gestaltNodeKeys] = await this.traverseGestalt( Object.keys(nodeKeyKeys1) as Array, [nodeKey1], + tran, ); if (!gestaltNodeKeys.has(nodeKey2)) { const nodeIds = Array.from(gestaltNodeKeys, (key) => gestaltsUtils.nodeFromKey(key), ); // It is assumed that an existing gestalt has a permission - const perm = (await this.acl.getNodePerm(nodeId1)) as Permission; + const perm = (await this.acl.getNodePerm(nodeId1, tran)) as Permission; // This remaps all existing nodes to a new permission - ops.push(...(await this.acl.setNodesPermOps(nodeIds, perm))); + await this.acl.setNodesPerm(nodeIds, perm, tran); } } - return ops; } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public async getGestaltActionsByNode( nodeId: NodeId, + tran?: DBTransaction, ): Promise { - return await this._transaction(async () => { - return await this.acl._transaction(async () => { - const nodeKey = gestaltsUtils.keyFromNode(nodeId); - if ( - (await this.db.get(this.graphNodesDbDomain, nodeKey)) == - null - ) { - return; - } - const perm = await this.acl.getNodePerm(nodeId); - if (perm == null) { - return; - } - return perm.gestalt; - }); - }); + if (tran == null) { + return this.withTransactionF(async (tran) => + this.getGestaltActionsByNode(nodeId, tran), + ); + } + const nodeKey = gestaltsUtils.keyFromNode(nodeId); + const nodeKeyPath = [ + ...this.gestaltGraphNodesDbPath, + nodeKey, + ] as unknown as KeyPath; + if ((await tran.get(nodeKeyPath)) == null) { + return; + } + const perm = await this.acl.getNodePerm(nodeId, tran); + if (perm == null) { + return; + } + return perm.gestalt; } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public async getGestaltActionsByIdentity( providerId: ProviderId, identityId: IdentityId, + tran?: DBTransaction, ): Promise { - return await this._transaction(async () => { - return await this.acl._transaction(async () => { - const identityKey = gestaltsUtils.keyFromIdentity( - providerId, - identityId, - ); - if ( - (await this.db.get( - this.graphIdentitiesDbDomain, - identityKey, - )) == null - ) { - return; - } - const gestaltKeySet = (await this.db.get( - this.graphMatrixDbDomain, - identityKey, - )) as GestaltKeySet; - let nodeId: NodeId | undefined; - for (const nodeKey in gestaltKeySet) { - nodeId = gestaltsUtils.nodeFromKey(nodeKey as GestaltNodeKey); - break; - } - if (nodeId == null) { - return; - } - const perm = await this.acl.getNodePerm(nodeId); - if (perm == null) { - return; - } - return perm.gestalt; - }); - }); + if (tran == null) { + return this.withTransactionF(async (tran) => + this.getGestaltActionsByIdentity(providerId, identityId, tran), + ); + } + const identityKey = gestaltsUtils.keyFromIdentity(providerId, identityId); + const identityKeyPath = [ + ...this.gestaltGraphIdentitiesDbPath, + identityKey, + ] as unknown as KeyPath; + if ((await tran.get(identityKeyPath)) == null) { + return; + } + const gestaltKeyPath = [ + ...this.gestaltGraphMatrixDbPath, + identityKey, + ] as unknown as KeyPath; + const gestaltKeySet = (await tran.get( + gestaltKeyPath, + )) as GestaltKeySet; + let nodeId: NodeId | undefined; + for (const nodeKey in gestaltKeySet) { + nodeId = gestaltsUtils.nodeFromKey(nodeKey as GestaltNodeKey); + break; + } + if (nodeId == null) { + return; + } + const perm = await this.acl.getNodePerm(nodeId, tran); + if (perm == null) { + return; + } + return perm.gestalt; } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public async setGestaltActionByNode( nodeId: NodeId, action: GestaltAction, + tran?: DBTransaction, ): Promise { - return await this._transaction(async () => { - return await this.acl._transaction(async () => { - const nodeKey = gestaltsUtils.keyFromNode(nodeId); - if ( - (await this.db.get(this.graphNodesDbDomain, nodeKey)) == - null - ) { - throw new gestaltsErrors.ErrorGestaltsGraphNodeIdMissing(); - } - await this.acl.setNodeAction(nodeId, action); - }); - }); + if (tran == null) { + return this.withTransactionF(async (tran) => + this.setGestaltActionByNode(nodeId, action, tran), + ); + } + const nodeKey = gestaltsUtils.keyFromNode(nodeId); + const nodeKeyPath = [ + ...this.gestaltGraphNodesDbPath, + nodeKey, + ] as unknown as KeyPath; + if ((await tran.get(nodeKeyPath)) == null) { + throw new gestaltsErrors.ErrorGestaltsGraphNodeIdMissing(); + } + await this.acl.setNodeAction(nodeId, action, tran); } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) @@ -953,56 +816,58 @@ class GestaltGraph { providerId: ProviderId, identityId: IdentityId, action: GestaltAction, + tran?: DBTransaction, ): Promise { - return await this._transaction(async () => { - return await this.acl._transaction(async () => { - const identityKey = gestaltsUtils.keyFromIdentity( - providerId, - identityId, - ); - if ( - (await this.db.get( - this.graphIdentitiesDbDomain, - identityKey, - )) == null - ) { - throw new gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing(); - } - const gestaltKeySet = (await this.db.get( - this.graphMatrixDbDomain, - identityKey, - )) as GestaltKeySet; - let nodeId: NodeId | undefined; - for (const nodeKey in gestaltKeySet) { - nodeId = gestaltsUtils.nodeFromKey(nodeKey as GestaltNodeKey); - break; - } - // If there are no linked nodes, this cannot proceed - if (nodeId == null) { - throw new gestaltsErrors.ErrorGestaltsGraphNodeIdMissing(); - } - await this.acl.setNodeAction(nodeId, action); - }); - }); + if (tran == null) { + return this.withTransactionF(async (tran) => + this.setGestaltActionByIdentity(providerId, identityId, action, tran), + ); + } + const identityKey = gestaltsUtils.keyFromIdentity(providerId, identityId); + const identityKeyPath = [ + ...this.gestaltGraphIdentitiesDbPath, + identityKey, + ] as unknown as KeyPath; + if ((await tran.get(identityKeyPath)) == null) { + throw new gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing(); + } + const gestaltKeyPath = [ + ...this.gestaltGraphMatrixDbPath, + identityKey, + ] as unknown as KeyPath; + const gestaltKeySet = (await tran.get(gestaltKeyPath)) as GestaltKeySet; + let nodeId: NodeId | undefined; + for (const nodeKey in gestaltKeySet) { + nodeId = gestaltsUtils.nodeFromKey(nodeKey as GestaltNodeKey); + break; + } + // If there are no linked nodes, this cannot proceed + if (nodeId == null) { + throw new gestaltsErrors.ErrorGestaltsGraphNodeIdMissing(); + } + await this.acl.setNodeAction(nodeId, action, tran); } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public async unsetGestaltActionByNode( nodeId: NodeId, action: GestaltAction, + tran?: DBTransaction, ): Promise { - return await this._transaction(async () => { - return await this.acl._transaction(async () => { - const nodeKey = gestaltsUtils.keyFromNode(nodeId); - if ( - (await this.db.get(this.graphNodesDbDomain, nodeKey)) == - null - ) { - throw new gestaltsErrors.ErrorGestaltsGraphNodeIdMissing(); - } - await this.acl.unsetNodeAction(nodeId, action); - }); - }); + if (tran == null) { + return this.withTransactionF(async (tran) => + this.unsetGestaltActionByNode(nodeId, action, tran), + ); + } + const nodeKey = gestaltsUtils.keyFromNode(nodeId); + const nodeKeyPath = [ + ...this.gestaltGraphNodesDbPath, + nodeKey, + ] as unknown as KeyPath; + if ((await tran.get(nodeKeyPath)) == null) { + throw new gestaltsErrors.ErrorGestaltsGraphNodeIdMissing(); + } + await this.acl.unsetNodeAction(nodeId, action, tran); } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) @@ -1010,92 +875,94 @@ class GestaltGraph { providerId: ProviderId, identityId: IdentityId, action: GestaltAction, + tran?: DBTransaction, ): Promise { - return await this._transaction(async () => { - return await this.acl._transaction(async () => { - const identityKey = gestaltsUtils.keyFromIdentity( - providerId, - identityId, - ); - if ( - (await this.db.get( - this.graphIdentitiesDbDomain, - identityKey, - )) == null - ) { - throw new gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing(); - } - const gestaltKeySet = (await this.db.get( - this.graphMatrixDbDomain, - identityKey, - )) as GestaltKeySet; - let nodeId: NodeId | undefined; - for (const nodeKey in gestaltKeySet) { - nodeId = gestaltsUtils.nodeFromKey(nodeKey as GestaltNodeKey); - break; - } - // If there are no linked nodes, this cannot proceed - if (nodeId == null) { - throw new gestaltsErrors.ErrorGestaltsGraphNodeIdMissing(); - } - await this.acl.unsetNodeAction(nodeId, action); - }); - }); + if (tran == null) { + return this.withTransactionF(async (tran) => + this.unsetGestaltActionByIdentity(providerId, identityId, action, tran), + ); + } + const identityKey = gestaltsUtils.keyFromIdentity(providerId, identityId); + const identityKeyPath = [ + ...this.gestaltGraphIdentitiesDbPath, + identityKey, + ] as unknown as KeyPath; + if ((await tran.get(identityKeyPath)) == null) { + throw new gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing(); + } + const gestaltKeyPath = [ + ...this.gestaltGraphMatrixDbPath, + identityKey, + ] as unknown as KeyPath; + const gestaltKeySet = (await tran.get(gestaltKeyPath)) as GestaltKeySet; + let nodeId: NodeId | undefined; + for (const nodeKey in gestaltKeySet) { + nodeId = gestaltsUtils.nodeFromKey(nodeKey as GestaltNodeKey); + break; + } + // If there are no linked nodes, this cannot proceed + if (nodeId == null) { + throw new gestaltsErrors.ErrorGestaltsGraphNodeIdMissing(); + } + await this.acl.unsetNodeAction(nodeId, action, tran); } protected async getGestaltByKey( gK: GestaltKey, + tran: DBTransaction, ): Promise { - return await this._transaction(async () => { - const gestalt: Gestalt = { - matrix: {}, - nodes: {}, - identities: {}, - }; - // We are not using traverseGestalt - // because this requires keeping track of the vertexKeys - const queue = [gK]; - const visited = new Set(); - while (true) { - const vertex = queue.shift(); - if (vertex == null) { - break; - } - const vertexKeys = await this.db.get( - this.graphMatrixDbDomain, - vertex, - ); - if (vertexKeys == null) { - return; - } - const gId = gestaltsUtils.ungestaltKey(vertex); - gestalt.matrix[vertex] = vertexKeys; - if (gId.type === 'node') { - const nodeInfo = await this.db.get( - this.graphNodesDbDomain, - vertex as GestaltNodeKey, - ); - gestalt.nodes[vertex] = nodeInfo!; - } else if (gId.type === 'identity') { - const identityInfo = await this.db.get( - this.graphIdentitiesDbDomain, - vertex as GestaltIdentityKey, - ); - gestalt.identities[vertex] = identityInfo!; - } - visited.add(vertex); - const neighbours: Array = Object.keys(vertexKeys).filter( - (k: GestaltKey) => !visited.has(k), - ) as Array; - queue.push(...neighbours); + const gestalt: Gestalt = { + matrix: {}, + nodes: {}, + identities: {}, + }; + // We are not using traverseGestalt + // because this requires keeping track of the vertexKeys + const queue = [gK]; + const visited = new Set(); + while (true) { + const vertex = queue.shift(); + if (vertex == null) { + break; } - return gestalt; - }); + const vertexPath = [ + ...this.gestaltGraphMatrixDbPath, + vertex, + ] as unknown as KeyPath; + const vertexKeys = await tran.get(vertexPath); + if (vertexKeys == null) { + return; + } + const gId = gestaltsUtils.ungestaltKey(vertex); + gestalt.matrix[vertex] = vertexKeys; + if (gId.type === 'node') { + const nodePath = [ + ...this.gestaltGraphNodesDbPath, + vertex as GestaltNodeKey, + ] as unknown as KeyPath; + const nodeInfo = await tran.get(nodePath); + gestalt.nodes[vertex] = nodeInfo!; + } else if (gId.type === 'identity') { + const identityPath = [ + ...this.gestaltGraphIdentitiesDbPath, + vertex as GestaltIdentityKey, + ] as unknown as KeyPath; + const identityInfo = await tran.get(identityPath); + gestalt.identities[vertex] = identityInfo!; + } + visited.add(vertex); + const neighbours: Array = Object.keys(vertexKeys).filter( + (k: GestaltKey) => !visited.has(k), + ) as Array; + queue.push(...neighbours); + } + return gestalt; } protected async traverseGestalt( queueStart: Array, visitedStart: Array = [], + tran: DBTransaction, ): Promise<[Set, Set, Set]> { const queue = [...queueStart]; const visited = new Set(visitedStart); @@ -1114,10 +981,11 @@ class GestaltGraph { if (vertex == null) { break; } - const vertexKeys = await this.db.get( - this.graphMatrixDbDomain, + const vertexPath = [ + ...this.gestaltGraphMatrixDbPath, vertex, - ); + ] as unknown as KeyPath; + const vertexKeys = await tran.get(vertexPath); if (vertexKeys == null) { break; } @@ -1135,11 +1003,6 @@ class GestaltGraph { } return [visited, visitedNodes, visitedIdentities]; } - - @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) - public async clearDB() { - await this.graphDb.clear(); - } } export default GestaltGraph; diff --git a/src/gestalts/errors.ts b/src/gestalts/errors.ts index b44eb0af6..ed11e46e1 100644 --- a/src/gestalts/errors.ts +++ b/src/gestalts/errors.ts @@ -1,16 +1,31 @@ -import { ErrorPolykey } from '../errors'; +import { ErrorPolykey, sysexits } from '../errors'; -class ErrorGestalts extends ErrorPolykey {} +class ErrorGestalts extends ErrorPolykey {} -class ErrorGestaltsGraphRunning extends ErrorGestalts {} +class ErrorGestaltsGraphRunning extends ErrorGestalts { + static description = 'GestaltGraph is running'; + exitCode = sysexits.USAGE; +} -class ErrorGestaltsGraphNotRunning extends ErrorGestalts {} +class ErrorGestaltsGraphNotRunning extends ErrorGestalts { + static description = 'GestaltGraph is not running'; + exitCode = sysexits.USAGE; +} -class ErrorGestaltsGraphDestroyed extends ErrorGestalts {} +class ErrorGestaltsGraphDestroyed extends ErrorGestalts { + static description = 'GestaltGraph is destroyed'; + exitCode = sysexits.USAGE; +} -class ErrorGestaltsGraphNodeIdMissing extends ErrorGestalts {} +class ErrorGestaltsGraphNodeIdMissing extends ErrorGestalts { + static description = 'Could not find NodeId'; + exitCode = sysexits.NOUSER; +} -class ErrorGestaltsGraphIdentityIdMissing extends ErrorGestalts {} +class ErrorGestaltsGraphIdentityIdMissing extends ErrorGestalts { + static description = 'Could not find IdentityId'; + exitCode = sysexits.NOUSER; +} export { ErrorGestalts, diff --git a/src/gestalts/utils.ts b/src/gestalts/utils.ts index 1f1df121f..2bba234e6 100644 --- a/src/gestalts/utils.ts +++ b/src/gestalts/utils.ts @@ -11,7 +11,7 @@ import type { NodeId } from '../nodes/types'; import type { IdentityId, ProviderId } from '../identities/types'; import canonicalize from 'canonicalize'; import { gestaltActions } from './types'; -import { utils as nodesUtils } from '../nodes'; +import * as nodesUtils from '../nodes/utils'; /** * Construct GestaltKey from GestaltId diff --git a/src/git/errors.ts b/src/git/errors.ts index 7995b7aeb..e2f265e32 100644 --- a/src/git/errors.ts +++ b/src/git/errors.ts @@ -1,18 +1,30 @@ -import { ErrorPolykey } from '../errors'; +import { ErrorPolykey, sysexits } from '../errors'; -class ErrorGit extends ErrorPolykey {} +class ErrorGit extends ErrorPolykey {} -class ErrorRepositoryUndefined extends ErrorGit {} +class ErrorRepositoryUndefined extends ErrorGit {} -class ErrorGitPermissionDenied extends ErrorGit {} +class ErrorGitPermissionDenied extends ErrorGit {} -class ErrorGitUndefinedRefs extends ErrorGit {} +class ErrorGitUndefinedRefs extends ErrorGit { + static description = 'Invalid ref'; + exitCode = sysexits.UNKNOWN; +} -class ErrorGitUndefinedType extends ErrorGit {} +class ErrorGitUndefinedType extends ErrorGit { + static description = 'Invalid data type'; + exitCode = sysexits.CONFIG; +} -class ErrorGitReadObject extends ErrorGit {} +class ErrorGitReadObject extends ErrorGit { + static description = 'Failed to read object'; + exitCode = sysexits.IOERR; +} -class ErrorGitUnimplementedMethod extends ErrorGit {} +class ErrorGitUnimplementedMethod extends ErrorGit { + static description = 'Invalid request'; + exitCode = sysexits.USAGE; +} export { ErrorGit, diff --git a/src/grpc/GRPCClient.ts b/src/grpc/GRPCClient.ts index b55d3a275..0434a1753 100644 --- a/src/grpc/GRPCClient.ts +++ b/src/grpc/GRPCClient.ts @@ -9,6 +9,7 @@ import type { import type { NodeId } from '../nodes/types'; import type { Certificate } from '../keys/types'; import type { Host, Port, TLSConfig, ProxyConfig } from '../network/types'; +import type { Timer } from '../types'; import http2 from 'http2'; import Logger from '@matrixai/logger'; import * as grpc from '@grpc/grpc-js'; @@ -44,7 +45,7 @@ abstract class GRPCClient { port, tlsConfig, proxyConfig, - timeout = Infinity, + timer, interceptors = [], logger = new Logger(this.name), }: { @@ -58,7 +59,7 @@ abstract class GRPCClient { port: Port; tlsConfig?: Partial; proxyConfig?: ProxyConfig; - timeout?: number; + timer?: Timer; interceptors?: Array; logger?: Logger; }): Promise<{ @@ -123,13 +124,21 @@ abstract class GRPCClient { } const waitForReady = promisify(client.waitForReady).bind(client); // Add the current unix time because grpc expects the milliseconds since unix epoch - timeout += Date.now(); try { - await waitForReady(timeout); + if (timer != null) { + await Promise.race([timer.timerP, waitForReady(Infinity)]); + // If the timer resolves first we throw a timeout error + if (timer?.timedOut === true) { + throw new grpcErrors.ErrorGRPCClientTimeout(); + } + } else { + // No timer given so we wait forever + await waitForReady(Infinity); + } } catch (e) { // If we fail here then we leak the client object... client.close(); - throw new grpcErrors.ErrorGRPCClientTimeout(); + throw new grpcErrors.ErrorGRPCClientTimeout(e.message, { cause: e }); } let serverCertChain: Array | undefined; if (channelCredentials._isSecure()) { @@ -152,7 +161,10 @@ abstract class GRPCClient { ); const e_ = new grpcErrors.ErrorGRPCClientVerification( `${e.name}: ${e.message}`, - e.data, + { + data: e.data, + cause: e, + }, ); session.destroy(e_, http2.constants.NGHTTP2_PROTOCOL_ERROR); } @@ -264,6 +276,14 @@ abstract class GRPCClient { ); } + /** + * Gets information about the client + * Useful for error reporting + */ + public getClientInfo() { + return { nodeId: this.nodeId, host: this.host, port: this.port }; + } + /** * Gets the leaf server certificate if the connection is encrypted * Don't use this when using network proxies diff --git a/src/grpc/GRPCServer.ts b/src/grpc/GRPCServer.ts index 7d6ab3d35..fb9218e3a 100644 --- a/src/grpc/GRPCServer.ts +++ b/src/grpc/GRPCServer.ts @@ -70,12 +70,13 @@ class GRPCServer { try { this.port = await bindAsync(address, serverCredentials); } catch (e) { - throw new grpcErrors.ErrorGRPCServerBind(e.message); + throw new grpcErrors.ErrorGRPCServerBind(e.message, { cause: e }); } if (serverCredentials._isSecure()) { // @ts-ignore hack for private property - const http2Servers = server.http2ServerList as Array; - for (const http2Server of http2Servers) { + const http2Servers = server.http2ServerList; + for (const http2ServerObjects of http2Servers) { + const http2Server = http2ServerObjects.server as Http2SecureServer; http2Server.on('session', (session: Http2Session) => { const socket = session.socket as TLSSocket; const address = networkUtils.buildAddress( @@ -101,7 +102,10 @@ class GRPCServer { ); const e_ = new grpcErrors.ErrorGRPCServerVerification( `${e.name}: ${e.message}`, - e.data, + { + data: e.data, + cause: e, + }, ); session.destroy(e_, http2.constants.NGHTTP2_PROTOCOL_ERROR); } else { @@ -140,7 +144,7 @@ class GRPCServer { ...(timer != null ? [timer.timerP] : []), ]); } catch (e) { - throw new grpcErrors.ErrorGRPCServerShutdown(e.message); + throw new grpcErrors.ErrorGRPCServerShutdown(e.message, { cause: e }); } finally { if (timer != null) timerStop(timer); } @@ -196,7 +200,8 @@ class GRPCServer { this.logger.info(`Updating ${this.constructor.name} TLS Config`); // @ts-ignore hack for private property const http2Servers = this.server.http2ServerList; - for (const http2Server of http2Servers as Array) { + for (const http2ServerObjects of http2Servers) { + const http2Server = http2ServerObjects.server as Http2SecureServer; http2Server.setSecureContext({ key: Buffer.from(tlsConfig.keyPrivatePem, 'ascii'), cert: Buffer.from(tlsConfig.certChainPem, 'ascii'), diff --git a/src/grpc/errors.ts b/src/grpc/errors.ts index ccac769e3..aed2281c2 100644 --- a/src/grpc/errors.ts +++ b/src/grpc/errors.ts @@ -1,32 +1,105 @@ -import { ErrorPolykey } from '../errors'; +import type { Class } from '@matrixai/errors'; +import type { ClientMetadata } from './types'; +import { ErrorPolykey, sysexits } from '../errors'; +import * as nodesUtils from '../nodes/utils'; -class ErrorGRPC extends ErrorPolykey {} +class ErrorGRPC extends ErrorPolykey {} -class ErrorGRPCClientTimeout extends ErrorGRPC { - description = 'Client connection timed out'; +class ErrorGRPCClientTimeout extends ErrorGRPC { + static description = 'Client connection timed out'; + exitCode = sysexits.UNAVAILABLE; } -class ErrorGRPCClientVerification extends ErrorGRPC { - description = 'Client could not verify server certificate'; +class ErrorGRPCClientVerification extends ErrorGRPC { + static description = 'Client could not verify server certificate'; + exitCode = sysexits.UNAVAILABLE; } -class ErrorGRPCClientChannelNotReady extends ErrorGRPC { - description = 'Client channel or subchannel is not ready'; +class ErrorGRPCClientChannelNotReady extends ErrorGRPC { + static description = 'Client channel or subchannel is not ready'; + exitCode = sysexits.UNAVAILABLE; } -class ErrorGRPCClientCall extends ErrorGRPC { - description = 'Generic call error'; +class ErrorGRPCClientCall extends ErrorGRPC { + static description = 'Generic call error'; + exitCode = sysexits.UNAVAILABLE; } -class ErrorGRPCServerNotRunning extends ErrorGRPC {} +class ErrorGRPCServerNotRunning extends ErrorGRPC { + static description = 'GRPC Server is not running'; + exitCode = sysexits.USAGE; +} + +class ErrorGRPCServerBind extends ErrorGRPC { + static description = 'Could not bind to server'; + exitCode = sysexits.UNAVAILABLE; +} + +class ErrorGRPCServerShutdown extends ErrorGRPC { + static description = 'Error during shutdown'; + exitCode = sysexits.UNAVAILABLE; +} -class ErrorGRPCServerBind extends ErrorGRPC {} +class ErrorGRPCServerNotSecured extends ErrorGRPC { + static description = 'Server is not secured'; + exitCode = sysexits.NOPERM; +} + +class ErrorGRPCServerVerification extends ErrorGRPC { + static description = 'Failed to verify server certificate'; + exitCode = sysexits.UNAVAILABLE; +} -class ErrorGRPCServerShutdown extends ErrorGRPC {} +class ErrorPolykeyRemote extends ErrorPolykey { + static description = 'Remote error from RPC call'; + exitCode = sysexits.UNAVAILABLE; + metadata: ClientMetadata; -class ErrorGRPCServerNotSecured extends ErrorGRPC {} + constructor(metadata: ClientMetadata, message?: string, options?) { + super(message, options); + this.metadata = metadata; + } -class ErrorGRPCServerVerification extends ErrorGRPC {} + public static fromJSON>( + this: T, + json: any, + ): InstanceType { + if ( + typeof json !== 'object' || + json.type !== this.name || + typeof json.data !== 'object' || + typeof json.data.message !== 'string' || + isNaN(Date.parse(json.data.timestamp)) || + typeof json.data.metadata !== 'object' || + typeof json.data.data !== 'object' || + typeof json.data.exitCode !== 'number' || + ('stack' in json.data && typeof json.data.stack !== 'string') + ) { + throw new TypeError(`Cannot decode JSON to ${this.name}`); + } + const parsedMetadata: ClientMetadata = { + ...json.data.metadata, + nodeId: nodesUtils.decodeNodeId(json.data.metadata.nodeId), + }; + const e = new this(parsedMetadata, json.data.message, { + timestamp: new Date(json.data.timestamp), + data: json.data.data, + cause: json.data.cause, + }); + e.exitCode = json.data.exitCode; + e.stack = json.data.stack; + return e; + } + + public toJSON(): any { + const json = super.toJSON(); + json.data.metadata = { + ...this.metadata, + nodeId: nodesUtils.encodeNodeId(this.metadata.nodeId), + }; + return json; + } +} export { ErrorGRPC, @@ -39,4 +112,5 @@ export { ErrorGRPCServerShutdown, ErrorGRPCServerNotSecured, ErrorGRPCServerVerification, + ErrorPolykeyRemote, }; diff --git a/src/grpc/types.ts b/src/grpc/types.ts index 41061c3dd..8f600c2ce 100644 --- a/src/grpc/types.ts +++ b/src/grpc/types.ts @@ -8,6 +8,9 @@ import type { UntypedServiceImplementation, Metadata, } from '@grpc/grpc-js'; +import type { NodeId } from '../nodes/types'; +import type { Host, Port } from '../network/types'; +import type { POJO } from '../types'; type Services = Array< [ @@ -100,6 +103,13 @@ interface AsyncGeneratorDuplexStreamClient< meta: Promise; } +type ClientMetadata = { + nodeId: NodeId; + host: Host; + port: Port; + command: string; +} & POJO; + export type { Services, PromiseUnaryCall, @@ -109,4 +119,5 @@ export type { AsyncGeneratorWritableStreamClient, AsyncGeneratorDuplexStream, AsyncGeneratorDuplexStreamClient, + ClientMetadata, }; diff --git a/src/grpc/utils/utils.ts b/src/grpc/utils/utils.ts index 7b28d4c38..f696f37a2 100644 --- a/src/grpc/utils/utils.ts +++ b/src/grpc/utils/utils.ts @@ -28,10 +28,15 @@ import type { AsyncGeneratorDuplexStreamClient, } from '../types'; import type { CertificatePemChain, PrivateKeyPem } from '../../keys/types'; +import type { POJO } from '../../types'; +import type { ClientMetadata } from '../types'; +import type { NodeId } from '../../nodes/types'; import { Buffer } from 'buffer'; +import { AbstractError } from '@matrixai/errors'; import * as grpc from '@grpc/grpc-js'; import * as grpcErrors from '../errors'; import * as errors from '../../errors'; +import * as networkUtils from '../../network/utils'; import { promisify, promise, never } from '../../utils/utils'; /** @@ -158,17 +163,19 @@ function getServerSession(call: ServerSurfaceCall): Http2Session { * Serializes Error instances into GRPC errors * Use this on the sending side to send exceptions * Do not send exceptions to clients you do not trust + * If sending to an agent (rather than a client), set sensitive to true to + * prevent sensitive information from being sent over the network */ -function fromError(error: Error): ServerStatusResponse { +function fromError( + error: Error, + sensitive: boolean = false, +): ServerStatusResponse { const metadata = new grpc.Metadata(); - // If the error is not ErrorPolykey, wrap it up so it can be serialised - // TODO: add additional metadata regarding the network location of the error - if (!(error instanceof errors.ErrorPolykey)) { - error = new errors.ErrorPolykey(error.message); + if (sensitive) { + metadata.set('error', JSON.stringify(error, sensitiveReplacer)); + } else { + metadata.set('error', JSON.stringify(error, replacer)); } - metadata.set('name', error.name); - metadata.set('message', error.message); - metadata.set('data', JSON.stringify((error as errors.ErrorPolykey).data)); return { metadata, }; @@ -178,10 +185,11 @@ function fromError(error: Error): ServerStatusResponse { * Deserialized GRPC errors into ErrorPolykey * Use this on the receiving side to receive exceptions */ -function toError(e: ServiceError): errors.ErrorPolykey { - const errorName = e.metadata.get('name')[0] as string; - const errorMessage = e.metadata.get('message')[0] as string; - const errorData = e.metadata.get('data')[0] as string; +function toError( + e: ServiceError, + metadata: ClientMetadata, +): errors.ErrorPolykey { + const errorData = e.metadata.get('error')[0] as string; // Grpc.status is an enum // this will iterate the enum values then enum keys // they will all be of string type @@ -190,19 +198,26 @@ function toError(e: ServiceError): errors.ErrorPolykey { // check if the error code matches a known grpc status // @ts-ignore grpc.status[key] is in fact a string if (isNaN(parseInt(key)) && e.code === grpc.status[key]) { - if ( - key === 'UNKNOWN' && - errorName != null && - errorMessage != null && - errorData != null && - errorName in errors - ) { - return new errors[errorName](errorMessage, JSON.parse(errorData)); + if (key === 'UNKNOWN' && errorData != null) { + const error: Error = JSON.parse(errorData, reviver); + const remoteError = new grpcErrors.ErrorPolykeyRemote( + metadata, + error.message, + { + cause: error, + }, + ); + if (error instanceof errors.ErrorPolykey) { + remoteError.exitCode = error.exitCode; + } + return remoteError; } else { return new grpcErrors.ErrorGRPCClientCall(e.message, { - code: e.code, - details: e.details, - metadata: e.metadata.getMap(), + data: { + code: e.code, + details: e.details, + metadata: e.metadata.getMap(), + }, }); } } @@ -210,6 +225,140 @@ function toError(e: ServiceError): errors.ErrorPolykey { never(); } +/** + * Replacer function for serialising errors over GRPC (used by `JSON.stringify` + * in `fromError`) + * Polykey errors are handled by their inbuilt `toJSON` method , so this only + * serialises other errors + */ +function replacer(key: string, value: any): any { + if (value instanceof AggregateError) { + // AggregateError has an `errors` property + return { + type: value.constructor.name, + data: { + errors: value.errors, + message: value.message, + stack: value.stack, + }, + }; + } else if (value instanceof Error) { + // If it's some other type of error then only serialise the message and + // stack (and the type of the error) + return { + type: value.name, + data: { + message: value.message, + stack: value.stack, + }, + }; + } else { + // If it's not an error then just leave as is + return value; + } +} + +/** + * The same as `replacer`, however this will additionally filter out any + * sensitive data that should not be sent over the network when sending to an + * agent (as opposed to a client) + */ +function sensitiveReplacer(key: string, value: any) { + if (key === 'stack') { + return; + } else { + return replacer(key, value); + } +} + +/** + * Error constructors for non-Polykey errors + * Allows these errors to be reconstructed from GRPC metadata + */ +const standardErrors = { + Error, + TypeError, + SyntaxError, + ReferenceError, + EvalError, + RangeError, + URIError, + AggregateError, + AbstractError, +}; + +/** + * Reviver function for deserialising errors sent over GRPC (used by + * `JSON.parse` in `toError`) + * The final result returned will always be an error - if the deserialised + * data is of an unknown type then this will be wrapped as an + * `ErrorPolykeyUnknown` + */ +function reviver(key: string, value: any): any { + // If the value is an error then reconstruct it + if ( + typeof value === 'object' && + typeof value.type === 'string' && + typeof value.data === 'object' + ) { + try { + let eClass = errors[value.type]; + if (eClass != null) return eClass.fromJSON(value); + eClass = standardErrors[value.type]; + if (eClass != null) { + let e; + switch (eClass) { + case AbstractError: + return eClass.fromJSON(); + case AggregateError: + if ( + !Array.isArray(value.data.errors) || + typeof value.data.message !== 'string' || + ('stack' in value.data && typeof value.data.stack !== 'string') + ) { + throw new TypeError(`cannot decode JSON to ${value.type}`); + } + e = new eClass(value.data.errors, value.data.message); + e.stack = value.data.stack; + break; + default: + if ( + typeof value.data.message !== 'string' || + ('stack' in value.data && typeof value.data.stack !== 'string') + ) { + throw new TypeError(`Cannot decode JSON to ${value.type}`); + } + e = new eClass(value.data.message); + e.stack = value.data.stack; + break; + } + return e; + } + } catch (e) { + // If `TypeError` which represents decoding failure + // then return value as-is + // Any other exception is a bug + if (!(e instanceof TypeError)) { + throw e; + } + } + // Other values are returned as-is + return value; + } else if (key === '') { + // Root key will be '' + // Reaching here means the root JSON value is not a valid exception + // Therefore ErrorPolykeyUnknown is only ever returned at the top-level + const error = new errors.ErrorPolykeyUnknown('Unknown error JSON', { + data: { + json: value, + }, + }); + return error; + } else { + return value; + } +} + /** * Converts GRPC unary call to promisified unary call * Used on the client side @@ -217,6 +366,7 @@ function toError(e: ServiceError): errors.ErrorPolykey { */ function promisifyUnaryCall( client: Client, + metadata: ClientMetadata, f: (...args: any[]) => ClientUnaryCall, ): (...args: any[]) => PromiseUnaryCall { return (...args) => { @@ -228,7 +378,7 @@ function promisifyUnaryCall( const { p: pMeta, resolveP: resolveMetaP } = promise(); const callback = (error: ServiceError, ...values) => { if (error != null) { - rejectP(toError(error)); + rejectP(toError(error, metadata)); return; } resolveP(values.length === 1 ? values[0] : values); @@ -253,13 +403,33 @@ function promisifyUnaryCall( */ function generatorReadable( stream: ClientReadableStream, + metadata: { nodeId: NodeId; command: string } & POJO, ): AsyncGeneratorReadableStream>; function generatorReadable( stream: ServerReadableStream, + metadata: { nodeId: NodeId; command: string } & POJO, ): AsyncGeneratorReadableStream>; function generatorReadable( stream: ClientReadableStream | ServerReadableStream, + metadata: { nodeId: NodeId; command: string } & POJO, ) { + const peerAddress = stream + .getPeer() + .match( + /([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}:\d{1,5}|(\d{1,3}\.){3}\d{1,3}:\d{1,5}/, + ); + if (!('host' in metadata) && peerAddress != null) { + metadata.host = networkUtils.parseAddress(peerAddress[0])[0]; + } + if (!('port' in metadata) && peerAddress != null) { + metadata.port = networkUtils.parseAddress(peerAddress[0])[1]; + } + const clientMetadata: ClientMetadata = { + nodeId: metadata.nodeId, + host: metadata.host, + port: metadata.port, + command: metadata.command, + }; const gf = async function* () { try { let vR = yield; @@ -280,7 +450,7 @@ function generatorReadable( } } catch (e) { stream.destroy(); - throw toError(e); + throw toError(e, clientMetadata); } }; const g: any = gf(); @@ -297,6 +467,7 @@ function generatorReadable( */ function promisifyReadableStreamCall( client: grpc.Client, + metadata: ClientMetadata, f: (...args: any[]) => ClientReadableStream, ): ( ...args: any[] @@ -309,6 +480,7 @@ function promisifyReadableStreamCall( }); const g = generatorReadable( stream, + metadata, ) as AsyncGeneratorReadableStreamClient>; g.meta = pMeta; return g; @@ -324,12 +496,15 @@ function promisifyReadableStreamCall( */ function generatorWritable( stream: ClientWritableStream, + sensitive: boolean, ): AsyncGeneratorWritableStream>; function generatorWritable( stream: ServerWritableStream, + sensitive: boolean, ): AsyncGeneratorWritableStream>; function generatorWritable( stream: ClientWritableStream | ServerWritableStream, + sensitive: boolean = false, ) { const streamWrite = promisify(stream.write).bind(stream); const gf = async function* () { @@ -338,7 +513,7 @@ function generatorWritable( try { vW = yield; } catch (e) { - stream.emit('error', fromError(e)); + stream.emit('error', fromError(e, sensitive)); stream.end(); return; } @@ -364,6 +539,7 @@ function generatorWritable( */ function promisifyWritableStreamCall( client: grpc.Client, + metadata: ClientMetadata, f: (...args: any[]) => ClientWritableStream, ): ( ...args: any[] @@ -379,7 +555,7 @@ function promisifyWritableStreamCall( }; const callback = (error, ...values) => { if (error != null) { - return rejectP(toError(error)); + return rejectP(toError(error, metadata)); } return resolveP(values.length === 1 ? values[0] : values); }; @@ -391,6 +567,7 @@ function promisifyWritableStreamCall( }); const g = generatorWritable( stream, + false, ) as AsyncGeneratorWritableStreamClient< TWrite, ClientWritableStream @@ -410,15 +587,21 @@ function promisifyWritableStreamCall( */ function generatorDuplex( stream: ClientDuplexStream, + metadata: { nodeId: NodeId; command: string } & POJO, + sensitive: boolean, ): AsyncGeneratorDuplexStream>; function generatorDuplex( stream: ServerDuplexStream, + metadata: { nodeId: NodeId; command: string } & POJO, + sensitive: boolean, ): AsyncGeneratorDuplexStream>; function generatorDuplex( stream: ClientDuplexStream | ServerDuplexStream, + metadata: { nodeId: NodeId; command: string } & POJO, + sensitive: boolean = false, ) { - const gR = generatorReadable(stream as any); - const gW = generatorWritable(stream as any); + const gR = generatorReadable(stream as any, metadata); + const gW = generatorWritable(stream as any, sensitive); const gf = async function* () { let vR: any, vW: any; while (true) { @@ -467,6 +650,7 @@ function generatorDuplex( */ function promisifyDuplexStreamCall( client: grpc.Client, + metadata: ClientMetadata, f: (...args: any[]) => ClientDuplexStream, ): ( ...args: any[] @@ -483,6 +667,8 @@ function promisifyDuplexStreamCall( }); const g = generatorDuplex( stream, + metadata, + false, ) as AsyncGeneratorDuplexStreamClient< TRead, TWrite, @@ -509,4 +695,6 @@ export { promisifyDuplexStreamCall, toError, fromError, + replacer, + reviver, }; diff --git a/src/identities/IdentitiesManager.ts b/src/identities/IdentitiesManager.ts index c007c8883..83c92334e 100644 --- a/src/identities/IdentitiesManager.ts +++ b/src/identities/IdentitiesManager.ts @@ -4,15 +4,14 @@ import type { ProviderTokens, TokenData, } from './types'; -import type { DB, DBLevel } from '@matrixai/db'; +import type { DB, DBTransaction, KeyPath, LevelPath } from '@matrixai/db'; import type Provider from './Provider'; - -import { Mutex } from 'async-mutex'; import Logger from '@matrixai/logger'; import { CreateDestroyStartStop, ready, } from '@matrixai/async-init/dist/CreateDestroyStartStop'; +import { withF } from '@matrixai/resources'; import * as identitiesErrors from './errors'; interface IdentitiesManager extends CreateDestroyStartStop {} @@ -21,18 +20,6 @@ interface IdentitiesManager extends CreateDestroyStartStop {} new identitiesErrors.ErrorIdentitiesManagerDestroyed(), ) class IdentitiesManager { - protected logger: Logger; - protected db: DB; - protected identitiesDbDomain: string = this.constructor.name; - protected identitiesTokensDbDomain: Array = [ - this.identitiesDbDomain, - 'tokens', - ]; - protected identitiesDb: DBLevel; - protected identitiesTokensDb: DBLevel; - protected lock: Mutex = new Mutex(); - protected providers: Map = new Map(); - static async createIdentitiesManager({ db, logger = new Logger(this.name), @@ -49,29 +36,29 @@ class IdentitiesManager { return identitiesManager; } + protected logger: Logger; + protected db: DB; + protected identitiesDbPath: LevelPath = [this.constructor.name]; + /** + * Tokens stores ProviderId -> ProviderTokens + */ + protected identitiesTokensDbPath: LevelPath = [ + this.constructor.name, + 'tokens', + ]; + protected providers: Map = new Map(); + constructor({ db, logger }: { db: DB; logger: Logger }) { this.logger = logger; this.db = db; } - get locked(): boolean { - return this.lock.isLocked(); - } - public async start({ fresh = false }: { fresh?: boolean } = {}) { this.logger.info(`Starting ${this.constructor.name}`); - const identitiesDb = await this.db.level(this.identitiesDbDomain); - // Tokens stores ProviderId -> ProviderTokens - const identitiesTokensDb = await this.db.level( - this.identitiesTokensDbDomain[1], - identitiesDb, - ); if (fresh) { - await identitiesDb.clear(); + await this.db.clear(this.identitiesDbPath); this.providers = new Map(); } - this.identitiesDb = identitiesDb; - this.identitiesTokensDb = identitiesTokensDb; this.logger.info(`Started ${this.constructor.name}`); } @@ -82,38 +69,16 @@ class IdentitiesManager { async destroy() { this.logger.info(`Destroying ${this.constructor.name}`); - const identitiesDb = await this.db.level(this.identitiesDbDomain); - await identitiesDb.clear(); + await this.db.clear(this.identitiesDbPath); this.providers = new Map(); this.logger.info(`Destroyed ${this.constructor.name}`); } - /** - * Run several operations within the same lock - * This does not ensure atomicity of the underlying database - * Database atomicity still depends on the underlying operation - */ - public async transaction( - f: (identitiesManager: IdentitiesManager) => Promise, + @ready(new identitiesErrors.ErrorIdentitiesManagerNotRunning()) + public async withTransactionF( + f: (tran: DBTransaction) => Promise, ): Promise { - const release = await this.lock.acquire(); - try { - return await f(this); - } finally { - release(); - } - } - - /** - * Transaction wrapper that will not lock if the operation was executed - * within a transaction context - */ - public async _transaction(f: () => Promise): Promise { - if (this.lock.isLocked()) { - return await f(); - } else { - return await this.transaction(f); - } + return withF([this.db.transaction()], ([tran]) => f(tran)); } @ready(new identitiesErrors.ErrorIdentitiesManagerNotRunning()) @@ -146,34 +111,46 @@ class IdentitiesManager { } @ready(new identitiesErrors.ErrorIdentitiesManagerNotRunning()) - public async getTokens(providerId: ProviderId): Promise { - return await this._transaction(async () => { - const providerTokens = await this.db.get( - this.identitiesTokensDbDomain, - providerId, + public async getTokens( + providerId: ProviderId, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.withTransactionF(async (tran) => + this.getTokens(providerId, tran), ); - if (providerTokens == null) { - return {}; - } - return providerTokens; - }); + } + const providerIdPath = [ + ...this.identitiesTokensDbPath, + providerId, + ] as unknown as KeyPath; + const providerTokens = await tran.get(providerIdPath); + if (providerTokens == null) { + return {}; + } + return providerTokens; } @ready(new identitiesErrors.ErrorIdentitiesManagerNotRunning()) public async getToken( providerId: ProviderId, identityId: IdentityId, + tran?: DBTransaction, ): Promise { - return await this._transaction(async () => { - const providerTokens = await this.db.get( - this.identitiesTokensDbDomain, - providerId, + if (tran == null) { + return this.withTransactionF(async (tran) => + this.getToken(providerId, identityId, tran), ); - if (providerTokens == null) { - return undefined; - } - return providerTokens[identityId]; - }); + } + const providerIdPath = [ + ...this.identitiesTokensDbPath, + providerId, + ] as unknown as KeyPath; + const providerTokens = await tran.get(providerIdPath); + if (providerTokens == null) { + return undefined; + } + return providerTokens[identityId]; } @ready(new identitiesErrors.ErrorIdentitiesManagerNotRunning()) @@ -181,39 +158,47 @@ class IdentitiesManager { providerId: ProviderId, identityId: IdentityId, tokenData: TokenData, + tran?: DBTransaction, ): Promise { - return await this._transaction(async () => { - const providerTokens = await this.getTokens(providerId); - providerTokens[identityId] = tokenData; - await this.db.put( - this.identitiesTokensDbDomain, - providerId, - providerTokens, + if (tran == null) { + return this.withTransactionF(async (tran) => + this.putToken(providerId, identityId, tokenData, tran), ); - }); + } + const providerTokens = await this.getTokens(providerId); + providerTokens[identityId] = tokenData; + const providerIdPath = [ + ...this.identitiesTokensDbPath, + providerId, + ] as unknown as KeyPath; + await tran.put(providerIdPath, providerTokens); } @ready(new identitiesErrors.ErrorIdentitiesManagerNotRunning()) public async delToken( providerId: ProviderId, identityId: IdentityId, + tran?: DBTransaction, ): Promise { - return await this._transaction(async () => { - const providerTokens = await this.getTokens(providerId); - if (!(identityId in providerTokens)) { - return; - } - delete providerTokens[identityId]; - if (!Object.keys(providerTokens).length) { - await this.db.del(this.identitiesTokensDbDomain, providerId); - return; - } - await this.db.put( - this.identitiesTokensDbDomain, - providerId, - providerTokens, + if (tran == null) { + return this.withTransactionF(async (tran) => + this.delToken(providerId, identityId, tran), ); - }); + } + const providerTokens = await this.getTokens(providerId, tran); + if (!(identityId in providerTokens)) { + return; + } + delete providerTokens[identityId]; + const providerIdPath = [ + ...this.identitiesTokensDbPath, + providerId, + ] as unknown as KeyPath; + if (!Object.keys(providerTokens).length) { + await tran.del(providerIdPath); + return; + } + await tran.put(providerIdPath, providerTokens); } } diff --git a/src/identities/errors.ts b/src/identities/errors.ts index 0effd2306..40ade2ceb 100644 --- a/src/identities/errors.ts +++ b/src/identities/errors.ts @@ -1,24 +1,52 @@ -import { ErrorPolykey } from '../errors'; - -class ErrorIdentities extends ErrorPolykey {} - -class ErrorIdentitiesManagerRunning extends ErrorIdentities {} - -class ErrorIdentitiesManagerNotRunning extends ErrorIdentities {} - -class ErrorIdentitiesManagerDestroyed extends ErrorIdentities {} - -class ErrorProviderDuplicate extends ErrorIdentities {} - -class ErrorProviderCall extends ErrorIdentities {} - -class ErrorProviderAuthentication extends ErrorIdentities {} - -class ErrorProviderUnauthenticated extends ErrorIdentities {} - -class ErrorProviderUnimplemented extends ErrorIdentities {} - -class ErrorProviderMissing extends ErrorIdentities {} +import { ErrorPolykey, sysexits } from '../errors'; + +class ErrorIdentities extends ErrorPolykey {} + +class ErrorIdentitiesManagerRunning extends ErrorIdentities { + static description = 'IdentitiesManager is running'; + exitCode = sysexits.USAGE; +} + +class ErrorIdentitiesManagerNotRunning extends ErrorIdentities { + static description = 'IdentitiesManager is not running'; + exitCode = sysexits.USAGE; +} + +class ErrorIdentitiesManagerDestroyed extends ErrorIdentities { + static description = 'IdentitiesManager is destroyed'; + exitCode = sysexits.USAGE; +} + +class ErrorProviderDuplicate extends ErrorIdentities { + static description = 'Provider has already been registered'; + exitCode = sysexits.USAGE; +} + +class ErrorProviderCall extends ErrorIdentities { + static description = 'Invalid response received from provider'; + exitCode = sysexits.UNAVAILABLE; +} + +class ErrorProviderAuthentication extends ErrorIdentities { + static description = 'Could not authenticate provider'; + exitCode = sysexits.UNKNOWN; +} + +class ErrorProviderUnauthenticated extends ErrorIdentities { + static description = + 'Provider has not been authenticated or access token is expired or invalid'; + exitCode = sysexits.NOPERM; +} + +class ErrorProviderUnimplemented extends ErrorIdentities { + static description = 'Functionality is unavailable'; + exitCode = sysexits.USAGE; +} + +class ErrorProviderMissing extends ErrorIdentities { + static description = 'Provider has not been registered'; + exitCode = sysexits.USAGE; +} export { ErrorIdentities, diff --git a/src/identities/providers/github/GitHubProvider.ts b/src/identities/providers/github/GitHubProvider.ts index 4dc939999..bfbce7766 100644 --- a/src/identities/providers/github/GitHubProvider.ts +++ b/src/identities/providers/github/GitHubProvider.ts @@ -121,6 +121,7 @@ class GitHubProvider extends Provider { } catch (e) { throw new identitiesErrors.ErrorProviderAuthentication( 'Provider access token response is not valid JSON', + { cause: e }, ); } if (data.error) { @@ -200,6 +201,7 @@ class GitHubProvider extends Provider { } catch (e) { throw new identitiesErrors.ErrorProviderCall( `Provider response body is not valid JSON`, + { cause: e }, ); } return data.login; @@ -246,6 +248,7 @@ class GitHubProvider extends Provider { } catch (e) { throw new identitiesErrors.ErrorProviderCall( `Provider response body is not valid JSON`, + { cause: e }, ); } return { @@ -297,6 +300,7 @@ class GitHubProvider extends Provider { } catch (e) { throw new identitiesErrors.ErrorProviderCall( `Provider response body is not valid JSON`, + { cause: e }, ); } for (const item of data) { @@ -343,6 +347,7 @@ class GitHubProvider extends Provider { } catch (e) { throw new identitiesErrors.ErrorProviderCall( `Provider response body is not valid JSON`, + { cause: e }, ); } for (const item of data) { @@ -414,6 +419,7 @@ class GitHubProvider extends Provider { } catch (e) { throw new identitiesErrors.ErrorProviderCall( `Provider response body is not valid JSON`, + { cause: e }, ); } return { @@ -466,6 +472,7 @@ class GitHubProvider extends Provider { } catch (e) { throw new identitiesErrors.ErrorProviderCall( `Provider response body is not valid JSON`, + { cause: e }, ); } const linkClaimData = data.files[this.gistFilename]?.content; @@ -507,7 +514,7 @@ class GitHubProvider extends Provider { ); } const data = await response.text(); - const claimIds = await this.extractClaimIds(data); + const claimIds = this.extractClaimIds(data); for (const claimId of claimIds) { const claim = await this.getClaim(authIdentityId, claimId); if (claim != null) { diff --git a/src/keys/KeyManager.ts b/src/keys/KeyManager.ts index 8addec87c..937c80d98 100644 --- a/src/keys/KeyManager.ts +++ b/src/keys/KeyManager.ts @@ -269,10 +269,13 @@ class KeyManager { }); } catch (e) { throw new keysErrors.ErrorRootKeysRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } try { @@ -414,10 +417,13 @@ class KeyManager { ); } catch (e) { throw new keysErrors.ErrorRootCertRenew(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } await this.garbageCollectRootCerts(); @@ -535,10 +541,13 @@ class KeyManager { } } catch (e) { throw new keysErrors.ErrorRootCertsGC(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } } @@ -630,10 +639,13 @@ class KeyManager { return false; } throw new keysErrors.ErrorRootKeysRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } return true; @@ -649,10 +661,13 @@ class KeyManager { ]); } catch (e) { throw new keysErrors.ErrorRootKeysRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } let keyPair; @@ -665,7 +680,7 @@ class KeyManager { password, ); } catch (e) { - throw new keysErrors.ErrorRootKeysParse(e.message); + throw new keysErrors.ErrorRootKeysParse(e.message, { cause: e }); } return keyPair; } @@ -693,10 +708,13 @@ class KeyManager { ]); } catch (e) { throw new keysErrors.ErrorRootKeysWrite(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } } @@ -717,10 +735,13 @@ class KeyManager { }); } catch (e) { throw new keysErrors.ErrorRootKeysRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } const rootKeyPairBits = keysUtils.publicKeyBitSize( @@ -764,10 +785,13 @@ class KeyManager { return false; } throw new keysErrors.ErrorDBKeyRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } return true; @@ -780,10 +804,13 @@ class KeyManager { keysDbKeyCipher = await this.fs.promises.readFile(keyPath); } catch (e) { throw new keysErrors.ErrorDBKeyRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } let keysDbKeyPlain; @@ -793,7 +820,7 @@ class KeyManager { keysDbKeyCipher, ); } catch (e) { - throw new keysErrors.ErrorDBKeyParse(e.message); + throw new keysErrors.ErrorDBKeyParse(e.message, { cause: e }); } return keysDbKeyPlain; } @@ -814,10 +841,13 @@ class KeyManager { await this.fs.promises.rename(`${keyPath}.tmp`, keyPath); } catch (e) { throw new keysErrors.ErrorDBKeyWrite(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } } @@ -855,10 +885,13 @@ class KeyManager { return false; } throw new keysErrors.ErrorRootCertRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } return true; @@ -873,10 +906,13 @@ class KeyManager { }); } catch (e) { throw new keysErrors.ErrorRootCertRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } const rootCert = keysUtils.certFromPem(rootCertPem); @@ -894,10 +930,13 @@ class KeyManager { ); } catch (e) { throw new keysErrors.ErrorRootCertWrite(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } } @@ -913,10 +952,13 @@ class KeyManager { rootCertsNames = await this.fs.promises.readdir(this.rootCertsPath); } catch (e) { throw new keysErrors.ErrorRootCertRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } rootCertsNames.sort((a, b) => { @@ -950,10 +992,13 @@ class KeyManager { ); } catch (e) { throw new keysErrors.ErrorRootCertRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } return rootCertsPems; diff --git a/src/keys/errors.ts b/src/keys/errors.ts index e239f3a4b..59863b617 100644 --- a/src/keys/errors.ts +++ b/src/keys/errors.ts @@ -1,50 +1,92 @@ import { ErrorPolykey, sysexits } from '../errors'; -class ErrorKeys extends ErrorPolykey {} +class ErrorKeys extends ErrorPolykey {} -class ErrorKeyManagerRunning extends ErrorKeys {} +class ErrorKeyManagerRunning extends ErrorKeys { + static description = 'KeyManager is running'; + exitCode = sysexits.USAGE; +} -class ErrorKeyManagerNotRunning extends ErrorKeys {} +class ErrorKeyManagerNotRunning extends ErrorKeys { + static description = 'KeyManager is not running'; + exitCode = sysexits.USAGE; +} -class ErrorKeyManagerDestroyed extends ErrorKeys {} +class ErrorKeyManagerDestroyed extends ErrorKeys { + static description = 'KeyManager is destroyed'; + exitCode = sysexits.USAGE; +} -class ErrorKeysPasswordInvalid extends ErrorKeys { - description = 'Password has invalid format'; +class ErrorKeysPasswordInvalid extends ErrorKeys { + static description = 'Password has invalid format'; exitCode = sysexits.USAGE; } -class ErrorKeysRecoveryCodeInvalid extends ErrorKeys { - description = 'Recovery code has invalid format'; +class ErrorKeysRecoveryCodeInvalid extends ErrorKeys { + static description = 'Recovery code has invalid format'; exitCode = sysexits.USAGE; } -class ErrorKeysRecoveryCodeIncorrect extends ErrorKeys { - description = +class ErrorKeysRecoveryCodeIncorrect extends ErrorKeys { + static description = "Recovered key pair's public key does not match the root public key"; exitCode = sysexits.USAGE; } -class ErrorRootKeysRead extends ErrorKeys {} +class ErrorRootKeysRead extends ErrorKeys { + static description = 'Unable to read root keypair'; + exitCode = sysexits.IOERR; +} -class ErrorRootKeysParse extends ErrorKeys {} +class ErrorRootKeysParse extends ErrorKeys { + static description = 'Unable to parse root keypair'; + exitCode = sysexits.IOERR; +} -class ErrorRootKeysWrite extends ErrorKeys {} +class ErrorRootKeysWrite extends ErrorKeys { + static description = 'Unable to write root keypair'; + exitCode = sysexits.IOERR; +} -class ErrorRootCertRead extends ErrorKeys {} +class ErrorRootCertRead extends ErrorKeys { + static description = 'Unable to read root certificate'; + exitCode = sysexits.IOERR; +} -class ErrorRootCertWrite extends ErrorKeys {} +class ErrorRootCertWrite extends ErrorKeys { + static description = 'Unable to write root certificate'; + exitCode = sysexits.IOERR; +} -class ErrorRootCertRenew extends ErrorKeys {} +class ErrorRootCertRenew extends ErrorKeys { + static description = 'Unable to renew root certificate'; + exitCode = sysexits.IOERR; +} -class ErrorRootCertsGC extends ErrorKeys {} +class ErrorRootCertsGC extends ErrorKeys { + static description = 'Unexpected error during garbage collection'; + exitCode = sysexits.IOERR; +} -class ErrorEncryptSize extends ErrorKeys {} +class ErrorEncryptSize extends ErrorKeys { + static description = 'Cannot encrypt data with key bit size'; + exitCode = sysexits.USAGE; +} -class ErrorDBKeyRead extends ErrorKeys {} +class ErrorDBKeyRead extends ErrorKeys { + static description = 'Unable to read key'; + exitCode = sysexits.IOERR; +} -class ErrorDBKeyWrite extends ErrorKeys {} +class ErrorDBKeyWrite extends ErrorKeys { + static description = 'Unable to write key'; + exitCode = sysexits.IOERR; +} -class ErrorDBKeyParse extends ErrorKeys {} +class ErrorDBKeyParse extends ErrorKeys { + static description = 'Unable to decrypt key'; + exitCode = sysexits.IOERR; +} export { ErrorKeys, diff --git a/src/keys/utils.ts b/src/keys/utils.ts index 02ea313f9..14b82a92d 100644 --- a/src/keys/utils.ts +++ b/src/keys/utils.ts @@ -508,7 +508,16 @@ function publicKeyBitSize(publicKey: PublicKey): number { } async function getRandomBytes(size: number): Promise { - return Buffer.from(await random.getBytes(size), 'binary'); + const p = new Promise((resolve, reject) => { + random.getBytes(size, (e, bytes) => { + if (e != null) { + reject(e); + } else { + resolve(bytes); + } + }); + }); + return Buffer.from(await p, 'binary'); } function getRandomBytesSync(size: number): Buffer { @@ -541,7 +550,7 @@ async function decryptWithKey( cipherText: ArrayBuffer, ): Promise { const cipherTextBuf = Buffer.from(cipherText); - if (cipherTextBuf.byteLength <= 32) { + if (cipherTextBuf.byteLength < 32) { return; } const iv = cipherTextBuf.subarray(0, ivSize); diff --git a/src/network/ConnectionForward.ts b/src/network/ConnectionForward.ts index c41238a92..5e8a829d4 100644 --- a/src/network/ConnectionForward.ts +++ b/src/network/ConnectionForward.ts @@ -165,9 +165,12 @@ class ConnectionForward extends Connection { } this.utpSocket.off('message', this.handleMessage); throw new networkErrors.ErrorConnectionStart(e.message, { - code: e.code, - errno: e.errno, - syscall: e.syscall, + data: { + code: e.code, + errno: e.errno, + syscall: e.syscall, + }, + cause: e, }); } finally { clearInterval(punchInterval); diff --git a/src/network/ConnectionReverse.ts b/src/network/ConnectionReverse.ts index a746454bf..d7000d9d0 100644 --- a/src/network/ConnectionReverse.ts +++ b/src/network/ConnectionReverse.ts @@ -157,9 +157,12 @@ class ConnectionReverse extends Connection { this.serverSocket.destroy(); this.utpSocket.off('message', this.handleMessage); throw new networkErrors.ErrorConnectionStart(e.message, { - code: e.code, - errno: e.errno, - syscall: e.syscall, + data: { + code: e.code, + errno: e.errno, + syscall: e.syscall, + }, + cause: e, }); } finally { clearInterval(punchInterval); @@ -214,7 +217,6 @@ class ConnectionReverse extends Connection { if (this._composed) { throw new networkErrors.ErrorConnectionComposed(); } - this._composed = true; this.logger.info('Composing Connection Reverse'); // Promise for secure establishment const { p: secureP, resolveP: resolveSecureP } = promise(); @@ -247,9 +249,12 @@ class ConnectionReverse extends Connection { tlsSocket.destroy(); } throw new networkErrors.ErrorConnectionCompose(e.message, { - code: e.code, - errno: e.errno, - syscall: e.syscall, + data: { + code: e.code, + errno: e.errno, + syscall: e.syscall, + }, + cause: e, }); } tlsSocket.on('error', async (e) => { @@ -300,6 +305,7 @@ class ConnectionReverse extends Connection { }); this.clientCertChain = clientCertChain; this.logger.info('Composed Connection Reverse'); + this._composed = true; } catch (e) { this._composed = false; throw e; diff --git a/src/network/Proxy.ts b/src/network/Proxy.ts index 15bbf4d05..973c7f525 100644 --- a/src/network/Proxy.ts +++ b/src/network/Proxy.ts @@ -1,5 +1,12 @@ import type { AddressInfo, Socket } from 'net'; -import type { Host, Port, Address, ConnectionInfo, TLSConfig } from './types'; +import type { + Host, + Port, + Address, + ConnectionInfo, + TLSConfig, + ConnectionEstablishedCallback, +} from './types'; import type { ConnectionsForward } from './ConnectionForward'; import type { NodeId } from '../nodes/types'; import type { Timer } from '../types'; @@ -7,8 +14,9 @@ import type UTPConnection from 'utp-native/lib/connection'; import type { ConnectionsReverse } from './ConnectionReverse'; import http from 'http'; import UTP from 'utp-native'; -import { Mutex } from 'async-mutex'; import Logger from '@matrixai/logger'; +import { Lock } from '@matrixai/async-locks'; +import { withF } from '@matrixai/resources'; import { StartStop, ready } from '@matrixai/async-init/dist/StartStop'; import ConnectionReverse from './ConnectionReverse'; import ConnectionForward from './ConnectionForward'; @@ -37,16 +45,17 @@ class Proxy { protected server: http.Server; protected utpSocket: UTP; protected tlsConfig: TLSConfig; - protected connectionLocksForward: Map = new Map(); + protected connectionLocksForward: Map = new Map(); protected connectionsForward: ConnectionsForward = { proxy: new Map(), client: new Map(), }; - protected connectionLocksReverse: Map = new Map(); + protected connectionLocksReverse: Map = new Map(); protected connectionsReverse: ConnectionsReverse = { proxy: new Map(), reverse: new Map(), }; + protected connectionEstablishedCallback: ConnectionEstablishedCallback; constructor({ authToken, @@ -55,6 +64,7 @@ class Proxy { connEndTime = 1000, connPunchIntervalTime = 1000, connKeepAliveIntervalTime = 1000, + connectionEstablishedCallback = () => {}, logger, }: { authToken: string; @@ -63,6 +73,7 @@ class Proxy { connEndTime?: number; connPunchIntervalTime?: number; connKeepAliveIntervalTime?: number; + connectionEstablishedCallback?: ConnectionEstablishedCallback; logger?: Logger; }) { this.logger = logger ?? new Logger(Proxy.name); @@ -76,6 +87,7 @@ class Proxy { this.server = http.createServer(); this.server.on('request', this.handleRequest); this.server.on('connect', this.handleConnectForward); + this.connectionEstablishedCallback = connectionEstablishedCallback; this.logger.info(`Created ${Proxy.name}`); } @@ -316,24 +328,24 @@ class Proxy { const proxyAddress = networkUtils.buildAddress(proxyHost, proxyPort); let lock = this.connectionLocksForward.get(proxyAddress); if (lock == null) { - lock = new Mutex(); + lock = new Lock(); this.connectionLocksForward.set(proxyAddress, lock); } - const release = await lock.acquire(); - try { - await this.establishConnectionForward( - nodeId, - proxyHost, - proxyPort, - timer_, - ); - } finally { - if (timer === undefined) { - timerStop(timer_!); + await withF([lock.lock()], async () => { + try { + await this.establishConnectionForward( + nodeId, + proxyHost, + proxyPort, + timer_, + ); + } finally { + if (timer === undefined) { + timerStop(timer_!); + } + this.connectionLocksForward.delete(proxyAddress); } - release(); - this.connectionLocksForward.delete(proxyAddress); - } + }); } @ready(new networkErrors.ErrorProxyNotRunning(), true) @@ -344,20 +356,20 @@ class Proxy { const proxyAddress = networkUtils.buildAddress(proxyHost, proxyPort); let lock = this.connectionLocksForward.get(proxyAddress); if (lock == null) { - lock = new Mutex(); + lock = new Lock(); this.connectionLocksForward.set(proxyAddress, lock); } - const release = await lock.acquire(); - try { - const conn = this.connectionsForward.proxy.get(proxyAddress); - if (conn == null) { - return; + await withF([lock.lock()], async () => { + try { + const conn = this.connectionsForward.proxy.get(proxyAddress); + if (conn == null) { + return; + } + await conn.stop(); + } finally { + this.connectionLocksForward.delete(proxyAddress); } - await conn.stop(); - } finally { - release(); - this.connectionLocksForward.delete(proxyAddress); - } + }); } /** @@ -419,61 +431,71 @@ class Proxy { } let lock = this.connectionLocksForward.get(proxyAddress as Address); if (lock == null) { - lock = new Mutex(); + lock = new Lock(); this.connectionLocksForward.set(proxyAddress as Address, lock); } - const release = await lock.acquire(); - try { - const timer = timerStart(this.connConnectTime); + await withF([lock.lock()], async () => { try { - await this.connectForward( - nodeId, - proxyHost, - proxyPort, - clientSocket, - timer, - ); - } catch (e) { - if (e instanceof networkErrors.ErrorProxyConnectInvalidUrl) { - if (!clientSocket.destroyed) { - await clientSocketEnd('HTTP/1.1 400 Bad Request\r\n' + '\r\n'); - clientSocket.destroy(e); + const timer = timerStart(this.connConnectTime); + try { + await this.connectForward( + nodeId, + proxyHost, + proxyPort, + clientSocket, + timer, + ); + } catch (e) { + if (e instanceof networkErrors.ErrorProxyConnectInvalidUrl) { + if (!clientSocket.destroyed) { + await clientSocketEnd('HTTP/1.1 400 Bad Request\r\n' + '\r\n'); + clientSocket.destroy(e); + } + return; } - return; - } - if (e instanceof networkErrors.ErrorConnectionStartTimeout) { - if (!clientSocket.destroyed) { - await clientSocketEnd('HTTP/1.1 504 Gateway Timeout\r\n' + '\r\n'); - clientSocket.destroy(e); + if (e instanceof networkErrors.ErrorConnectionStartTimeout) { + if (!clientSocket.destroyed) { + await clientSocketEnd( + 'HTTP/1.1 504 Gateway Timeout\r\n' + '\r\n', + ); + clientSocket.destroy(e); + } + return; } - return; - } - if (e instanceof networkErrors.ErrorConnectionStart) { - if (!clientSocket.destroyed) { - await clientSocketEnd('HTTP/1.1 502 Bad Gateway\r\n' + '\r\n'); - clientSocket.destroy(e); + if (e instanceof networkErrors.ErrorConnectionStart) { + if (!clientSocket.destroyed) { + await clientSocketEnd('HTTP/1.1 502 Bad Gateway\r\n' + '\r\n'); + clientSocket.destroy(e); + } + return; } - return; - } - if (e instanceof networkErrors.ErrorCertChain) { - if (!clientSocket.destroyed) { - await clientSocketEnd( - 'HTTP/1.1 526 Invalid SSL Certificate\r\n' + '\r\n', - ); - clientSocket.destroy(e); + if (e instanceof networkErrors.ErrorCertChain) { + if (!clientSocket.destroyed) { + await clientSocketEnd( + 'HTTP/1.1 526 Invalid SSL Certificate\r\n' + '\r\n', + ); + clientSocket.destroy(e); + } + return; } - return; - } - if (e instanceof networkErrors.ErrorConnectionTimeout) { - if (!clientSocket.destroyed) { - await clientSocketEnd( - 'HTTP/1.1 524 A Timeout Occurred\r\n' + '\r\n', - ); - clientSocket.destroy(e); + if (e instanceof networkErrors.ErrorConnectionTimeout) { + if (!clientSocket.destroyed) { + await clientSocketEnd( + 'HTTP/1.1 524 A Timeout Occurred\r\n' + '\r\n', + ); + clientSocket.destroy(e); + } + return; + } + if (e instanceof networkErrors.ErrorConnection) { + if (!clientSocket.destroyed) { + await clientSocketEnd( + 'HTTP/1.1 500 Internal Server Error\r\n' + '\r\n', + ); + clientSocket.destroy(e); + } + return; } - return; - } - if (e instanceof networkErrors.ErrorConnection) { if (!clientSocket.destroyed) { await clientSocketEnd( 'HTTP/1.1 500 Internal Server Error\r\n' + '\r\n', @@ -481,27 +503,19 @@ class Proxy { clientSocket.destroy(e); } return; + } finally { + timerStop(timer); } - if (!clientSocket.destroyed) { - await clientSocketEnd( - 'HTTP/1.1 500 Internal Server Error\r\n' + '\r\n', - ); - clientSocket.destroy(e); - } - return; + // After composing, switch off this error handler + clientSocket.off('error', handleConnectError); + await clientSocketWrite( + 'HTTP/1.1 200 Connection Established\r\n' + '\r\n', + ); + this.logger.info(`Handled CONNECT to ${proxyAddress}`); } finally { - timerStop(timer); + this.connectionLocksForward.delete(proxyAddress as Address); } - // After composing, switch off this error handler - clientSocket.off('error', handleConnectError); - await clientSocketWrite( - 'HTTP/1.1 200 Connection Established\r\n' + '\r\n', - ); - this.logger.info(`Handled CONNECT to ${proxyAddress}`); - } finally { - release(); - this.connectionLocksForward.delete(proxyAddress as Address); - } + }); }; protected async connectForward( @@ -518,6 +532,14 @@ class Proxy { timer, ); conn.compose(clientSocket); + // With the connection composed without error we can assume that the + // connection was established and verified + await this.connectionEstablishedCallback({ + remoteNodeId: conn.getServerNodeIds()[0], + remoteHost: conn.host, + remotePort: conn.port, + type: 'forward', + }); } protected async establishConnectionForward( @@ -590,19 +612,19 @@ class Proxy { const proxyAddress = networkUtils.buildAddress(proxyHost, proxyPort); let lock = this.connectionLocksReverse.get(proxyAddress); if (lock == null) { - lock = new Mutex(); + lock = new Lock(); this.connectionLocksReverse.set(proxyAddress, lock); } - const release = await lock.acquire(); - try { - await this.establishConnectionReverse(proxyHost, proxyPort, timer_); - } finally { - if (timer === undefined) { - timerStop(timer_!); + await withF([lock.lock()], async () => { + try { + await this.establishConnectionReverse(proxyHost, proxyPort, timer_); + } finally { + if (timer === undefined) { + timerStop(timer_!); + } + this.connectionLocksReverse.delete(proxyAddress); } - release(); - this.connectionLocksReverse.delete(proxyAddress); - } + }); } @ready(new networkErrors.ErrorProxyNotRunning(), true) @@ -613,20 +635,20 @@ class Proxy { const proxyAddress = networkUtils.buildAddress(proxyHost, proxyPort); let lock = this.connectionLocksReverse.get(proxyAddress); if (lock == null) { - lock = new Mutex(); + lock = new Lock(); this.connectionLocksReverse.set(proxyAddress, lock); } - const release = await lock.acquire(); - try { - const conn = this.connectionsReverse.proxy.get(proxyAddress); - if (conn == null) { - return; + await withF([lock.lock()], async () => { + try { + const conn = this.connectionsReverse.proxy.get(proxyAddress); + if (conn == null) { + return; + } + await conn.stop(); + } finally { + this.connectionLocksReverse.delete(proxyAddress); } - await conn.stop(); - } finally { - release(); - this.connectionLocksReverse.delete(proxyAddress); - } + }); } protected handleConnectionReverse = async ( @@ -638,38 +660,38 @@ class Proxy { ); let lock = this.connectionLocksReverse.get(proxyAddress); if (lock == null) { - lock = new Mutex(); + lock = new Lock(); this.connectionLocksReverse.set(proxyAddress, lock); } - const release = await lock.acquire(); - try { - this.logger.info(`Handling connection from ${proxyAddress}`); - const timer = timerStart(this.connConnectTime); + await withF([lock.lock()], async () => { try { - await this.connectReverse( - utpConn.remoteAddress, - utpConn.remotePort, - utpConn, - timer, - ); - } catch (e) { - if (!(e instanceof networkErrors.ErrorNetwork)) { - throw e; - } - if (!utpConn.destroyed) { - utpConn.destroy(); + this.logger.info(`Handling connection from ${proxyAddress}`); + const timer = timerStart(this.connConnectTime); + try { + await this.connectReverse( + utpConn.remoteAddress, + utpConn.remotePort, + utpConn, + timer, + ); + } catch (e) { + if (!(e instanceof networkErrors.ErrorNetwork)) { + throw e; + } + if (!utpConn.destroyed) { + utpConn.destroy(); + } + this.logger.warn( + `Failed connection from ${proxyAddress} - ${e.toString()}`, + ); + } finally { + timerStop(timer); } - this.logger.warn( - `Failed connection from ${proxyAddress} - ${e.toString()}`, - ); + this.logger.info(`Handled connection from ${proxyAddress}`); } finally { - timerStop(timer); + this.connectionLocksReverse.delete(proxyAddress); } - this.logger.info(`Handled connection from ${proxyAddress}`); - } finally { - release(); - this.connectionLocksReverse.delete(proxyAddress); - } + }); }; protected async connectReverse( @@ -684,6 +706,14 @@ class Proxy { timer, ); await conn.compose(utpConn, timer); + // With the connection composed without error we can assume that the + // connection was established and verified + await this.connectionEstablishedCallback({ + remoteNodeId: conn.getClientNodeIds()[0], + remoteHost: conn.host, + remotePort: conn.port, + type: 'reverse', + }); } protected async establishConnectionReverse( diff --git a/src/network/errors.ts b/src/network/errors.ts index 234f90a22..e8189010c 100644 --- a/src/network/errors.ts +++ b/src/network/errors.ts @@ -1,128 +1,132 @@ import { ErrorPolykey, sysexits } from '../errors'; -class ErrorNetwork extends ErrorPolykey {} +class ErrorNetwork extends ErrorPolykey {} -class ErrorProxy extends ErrorNetwork {} +class ErrorProxy extends ErrorNetwork {} -class ErrorProxyNotRunning extends ErrorProxy { - description = 'Proxy is not running'; +class ErrorProxyNotRunning extends ErrorProxy { + static description = 'Proxy is not running'; exitCode = sysexits.USAGE; } -class ErrorProxyConnectInvalidUrl extends ErrorProxy { - description = 'Invalid target host used for HTTP connect proxy'; +class ErrorProxyConnectInvalidUrl extends ErrorProxy { + static description = 'Invalid target host used for HTTP connect proxy'; exitCode = sysexits.PROTOCOL; } -class ErrorProxyConnectMissingNodeId extends ErrorProxy { - description = 'Node ID query parameter is required for HTTP connect proxy'; +class ErrorProxyConnectMissingNodeId extends ErrorProxy { + static description = + 'Node ID query parameter is required for HTTP connect proxy'; exitCode = sysexits.PROTOCOL; } -class ErrorProxyConnectAuth extends ErrorProxy { - description = 'Incorrect HTTP connect proxy password'; +class ErrorProxyConnectAuth extends ErrorProxy { + static description = 'Incorrect HTTP connect proxy password'; exitCode = sysexits.NOPERM; } -class ErrorConnection extends ErrorNetwork {} +class ErrorConnection extends ErrorNetwork {} -class ErrorConnectionNotRunning extends ErrorConnection { - description = 'Connection is not running'; +class ErrorConnectionNotRunning extends ErrorConnection { + static description = 'Connection is not running'; exitCode = sysexits.USAGE; } -class ErrorConnectionComposed extends ErrorConnection { - description = 'Connection is composed'; +class ErrorConnectionComposed extends ErrorConnection { + static description = 'Connection is composed'; exitCode = sysexits.USAGE; } -class ErrorConnectionNotComposed extends ErrorConnection { - description = 'Connection is not composed'; +class ErrorConnectionNotComposed extends ErrorConnection { + static description = 'Connection is not composed'; exitCode = sysexits.USAGE; } -class ErrorConnectionMessageParse extends ErrorConnection { - description = 'Network message received is invalid'; +class ErrorConnectionMessageParse extends ErrorConnection { + static description = 'Network message received is invalid'; exitCode = sysexits.TEMPFAIL; } -class ErrorConnectionTimeout extends ErrorConnection { - description = 'Connection keep-alive timed out'; +class ErrorConnectionTimeout extends ErrorConnection { + static description = 'Connection keep-alive timed out'; exitCode = sysexits.UNAVAILABLE; } -class ErrorConnectionEndTimeout extends ErrorConnection { - description = 'Connection end timed out'; +class ErrorConnectionEndTimeout extends ErrorConnection { + static description = 'Connection end timed out'; exitCode = sysexits.UNAVAILABLE; } /** * Used by ConnectionForward and ConnectionReverse */ -class ErrorConnectionStart extends ErrorConnection { - description = 'Connection start failed'; +class ErrorConnectionStart extends ErrorConnection { + static description = 'Connection start failed'; exitCode = sysexits.PROTOCOL; } -class ErrorConnectionStartTimeout extends ErrorConnectionStart { - description = 'Connection start timed out'; +class ErrorConnectionStartTimeout extends ErrorConnectionStart { + static description = 'Connection start timed out'; exitCode = sysexits.NOHOST; } /** * Used by ConnectionReverse */ -class ErrorConnectionCompose extends ErrorConnection { - description = 'Connection compose failed'; +class ErrorConnectionCompose extends ErrorConnection { + static description = 'Connection compose failed'; exitCode = sysexits.PROTOCOL; } -class ErrorConnectionComposeTimeout extends ErrorConnectionCompose { - description = 'Connection compose timed out'; +class ErrorConnectionComposeTimeout extends ErrorConnectionCompose { + static description = 'Connection compose timed out'; exitCode = sysexits.NOHOST; } /** * Used for certificate verification */ -class ErrorCertChain extends ErrorNetwork {} +class ErrorCertChain extends ErrorNetwork {} -class ErrorCertChainEmpty extends ErrorCertChain { - description = 'Certificate chain is empty'; +class ErrorCertChainEmpty extends ErrorCertChain { + static description = 'Certificate chain is empty'; exitCode = sysexits.PROTOCOL; } -class ErrorCertChainUnclaimed extends ErrorCertChain { - description = 'The target node id is not claimed by any certificate'; +class ErrorCertChainUnclaimed extends ErrorCertChain { + static description = 'The target node id is not claimed by any certificate'; exitCode = sysexits.PROTOCOL; } -class ErrorCertChainBroken extends ErrorCertChain { - description = 'The signature chain is broken'; +class ErrorCertChainBroken extends ErrorCertChain { + static description = 'The signature chain is broken'; exitCode = sysexits.PROTOCOL; } -class ErrorCertChainDateInvalid extends ErrorCertChain { - description = 'Certificate in the chain is expired'; +class ErrorCertChainDateInvalid extends ErrorCertChain { + static description = 'Certificate in the chain is expired'; exitCode = sysexits.PROTOCOL; } -class ErrorCertChainNameInvalid extends ErrorCertChain { - description = 'Certificate is missing the common name'; +class ErrorCertChainNameInvalid extends ErrorCertChain { + static description = 'Certificate is missing the common name'; exitCode = sysexits.PROTOCOL; } -class ErrorCertChainKeyInvalid extends ErrorCertChain { - description = 'Certificate public key does not generate the Node ID'; +class ErrorCertChainKeyInvalid extends ErrorCertChain { + static description = 'Certificate public key does not generate the Node ID'; exitCode = sysexits.PROTOCOL; } -class ErrorCertChainSignatureInvalid extends ErrorCertChain { - description = 'Certificate self-signed signature is invalid'; +class ErrorCertChainSignatureInvalid extends ErrorCertChain { + static description = 'Certificate self-signed signature is invalid'; exitCode = sysexits.PROTOCOL; } -class ErrorHostnameResolutionFailed extends ErrorNetwork {} +class ErrorHostnameResolutionFailed extends ErrorNetwork { + static description = 'Unable to resolve hostname'; + exitCode = sysexits.USAGE; +} export { ErrorNetwork, diff --git a/src/network/types.ts b/src/network/types.ts index 40d672a85..a5a62b4c2 100644 --- a/src/network/types.ts +++ b/src/network/types.ts @@ -55,6 +55,15 @@ type ConnectionInfo = { remotePort: Port; }; +type ConnectionData = { + remoteNodeId: NodeId; + remoteHost: Host; + remotePort: Port; + type: 'forward' | 'reverse'; +}; + +type ConnectionEstablishedCallback = (data: ConnectionData) => any; + type PingMessage = { type: 'ping'; }; @@ -73,6 +82,8 @@ export type { TLSConfig, ProxyConfig, ConnectionInfo, + ConnectionData, + ConnectionEstablishedCallback, PingMessage, PongMessage, NetworkMessage, diff --git a/src/network/utils.ts b/src/network/utils.ts index 8347da631..c5786a754 100644 --- a/src/network/utils.ts +++ b/src/network/utils.ts @@ -45,10 +45,12 @@ function isHostname(hostname: any): hostname is Hostname { /** * Ports must be numbers between 0 and 65535 inclusive + * If connect is true, then port must be a number between 1 and 65535 inclusive */ -function isPort(port: any): port is Port { +function isPort(port: any, connect: boolean = false): port is Port { if (typeof port !== 'number') return false; if (port < 0 || port > 65535) return false; + if (connect && port === 0) return false; return true; } @@ -101,7 +103,9 @@ async function resolveHost(host: Host | Hostname): Promise { // Resolve the hostname and get the IPv4 address resolvedHost = await lookup(host, 4); } catch (e) { - throw new networkErrors.ErrorHostnameResolutionFailed(e.message); + throw new networkErrors.ErrorHostnameResolutionFailed(e.message, { + cause: e, + }); } // Returns an array of [ resolved address, family (4 or 6) ] return resolvedHost[0] as Host; @@ -201,11 +205,13 @@ function verifyServerCertificateChain( throw new networkErrors.ErrorCertChainDateInvalid( 'Chain certificate date is invalid', { - cert, - certIndex, - notBefore: cert.validity.notBefore, - notAfter: cert.validity.notAfter, - now, + data: { + cert, + certIndex, + notBefore: cert.validity.notBefore, + notAfter: cert.validity.notAfter, + now, + }, }, ); } @@ -214,8 +220,10 @@ function verifyServerCertificateChain( throw new networkErrors.ErrorCertChainNameInvalid( 'Chain certificate common name attribute is missing', { - cert, - certIndex, + data: { + cert, + certIndex, + }, }, ); } @@ -224,10 +232,12 @@ function verifyServerCertificateChain( throw new networkErrors.ErrorCertChainKeyInvalid( 'Chain certificate public key does not generate its node id', { - cert, - certIndex, - nodeId: certNodeId, - commonName: commonName.value, + data: { + cert, + certIndex, + nodeId: certNodeId, + commonName: commonName.value, + }, }, ); } @@ -235,8 +245,10 @@ function verifyServerCertificateChain( throw new networkErrors.ErrorCertChainSignatureInvalid( 'Chain certificate does not have a valid node-signature', { - cert, - certIndex, + data: { + cert, + certIndex, + }, }, ); } @@ -251,7 +263,7 @@ function verifyServerCertificateChain( throw new networkErrors.ErrorCertChainUnclaimed( 'Node ID is not claimed by any certificate', { - nodeId, + data: { nodeId }, }, ); } @@ -268,9 +280,11 @@ function verifyServerCertificateChain( throw new networkErrors.ErrorCertChainBroken( 'Chain certificate is not signed by parent certificate', { - cert: certChild, - certIndex: certIndex - 1, - certParent, + data: { + cert: certChild, + certIndex: certIndex - 1, + certParent, + }, }, ); } @@ -296,11 +310,13 @@ function verifyClientCertificateChain(certChain: Array): void { throw new networkErrors.ErrorCertChainDateInvalid( 'Chain certificate date is invalid', { - cert, - certIndex, - notBefore: cert.validity.notBefore, - notAfter: cert.validity.notAfter, - now, + data: { + cert, + certIndex, + notBefore: cert.validity.notBefore, + notAfter: cert.validity.notAfter, + now, + }, }, ); } @@ -309,8 +325,10 @@ function verifyClientCertificateChain(certChain: Array): void { throw new networkErrors.ErrorCertChainNameInvalid( 'Chain certificate common name attribute is missing', { - cert, - certIndex, + data: { + cert, + certIndex, + }, }, ); } @@ -319,10 +337,12 @@ function verifyClientCertificateChain(certChain: Array): void { throw new networkErrors.ErrorCertChainKeyInvalid( 'Chain certificate public key does not generate its node id', { - cert, - certIndex, - nodeId: certNodeId, - commonName: commonName.value, + data: { + cert, + certIndex, + nodeId: certNodeId, + commonName: commonName.value, + }, }, ); } @@ -330,8 +350,10 @@ function verifyClientCertificateChain(certChain: Array): void { throw new networkErrors.ErrorCertChainSignatureInvalid( 'Chain certificate does not have a valid node-signature', { - cert, - certIndex, + data: { + cert, + certIndex, + }, }, ); } @@ -343,9 +365,11 @@ function verifyClientCertificateChain(certChain: Array): void { throw new networkErrors.ErrorCertChainSignatureInvalid( 'Chain certificate is not signed by parent certificate', { - cert, - certIndex, - certParent: certNext, + data: { + cert, + certIndex, + certParent: certNext, + }, }, ); } diff --git a/src/nodes/NodeConnection.ts b/src/nodes/NodeConnection.ts index 6788c20fe..c90260afc 100644 --- a/src/nodes/NodeConnection.ts +++ b/src/nodes/NodeConnection.ts @@ -5,6 +5,7 @@ import type { Certificate, PublicKey, PublicKeyPem } from '../keys/types'; import type Proxy from '../network/Proxy'; import type GRPCClient from '../grpc/GRPCClient'; import type NodeConnectionManager from './NodeConnectionManager'; +import type { Timer } from '../types'; import Logger from '@matrixai/logger'; import { CreateDestroy, ready } from '@matrixai/async-init/dist/CreateDestroy'; import * as asyncInit from '@matrixai/async-init'; @@ -38,7 +39,7 @@ class NodeConnection { targetHost, targetPort, targetHostname, - connConnectTime = 20000, + timer, proxy, keyManager, clientFactory, @@ -50,7 +51,7 @@ class NodeConnection { targetHost: Host; targetPort: Port; targetHostname?: Hostname; - connConnectTime?: number; + timer?: Timer; proxy: Proxy; keyManager: KeyManager; clientFactory: (...args) => Promise; @@ -125,9 +126,9 @@ class NodeConnection { await nodeConnection.destroy(); } }, - timeout: connConnectTime, + timer: timer, }), - holePunchPromises, + ...holePunchPromises, ]); // 5. When finished, you have a connection to other node // The GRPCClient is ready to be used for requests @@ -135,7 +136,9 @@ class NodeConnection { await nodeConnection.destroy(); // If the connection times out, re-throw this with a higher level nodes exception if (e instanceof grpcErrors.ErrorGRPCClientTimeout) { - throw new nodesErrors.ErrorNodeConnectionTimeout(); + throw new nodesErrors.ErrorNodeConnectionTimeout(e.message, { + cause: e, + }); } throw e; } diff --git a/src/nodes/NodeConnectionManager.ts b/src/nodes/NodeConnectionManager.ts index 6160aa60a..30550b6a4 100644 --- a/src/nodes/NodeConnectionManager.ts +++ b/src/nodes/NodeConnectionManager.ts @@ -1,20 +1,24 @@ +import type { ResourceAcquire } from '@matrixai/resources'; import type KeyManager from '../keys/KeyManager'; import type Proxy from '../network/Proxy'; import type { Host, Hostname, Port } from '../network/types'; -import type { ResourceAcquire } from '../utils'; import type { Timer } from '../types'; import type NodeGraph from './NodeGraph'; +import type Queue from './Queue'; import type { - NodeId, NodeAddress, NodeData, - SeedNodes, + NodeId, NodeIdString, + SeedNodes, } from './types'; +import type NodeManager from './NodeManager'; +import { withF } from '@matrixai/resources'; import Logger from '@matrixai/logger'; -import { StartStop, ready } from '@matrixai/async-init/dist/StartStop'; +import { ready, StartStop } from '@matrixai/async-init/dist/StartStop'; import { IdInternal } from '@matrixai/id'; import { status } from '@matrixai/async-init'; +import { LockBox, RWLockWriter } from '@matrixai/async-locks'; import NodeConnection from './NodeConnection'; import * as nodesUtils from './utils'; import * as nodesErrors from './errors'; @@ -24,19 +28,18 @@ import * as networkUtils from '../network/utils'; import * as agentErrors from '../agent/errors'; import * as grpcErrors from '../grpc/errors'; import * as nodesPB from '../proto/js/polykey/v1/nodes/nodes_pb'; -import { RWLock, withF } from '../utils'; +import { timerStart } from '../utils'; -type ConnectionAndLock = { - connection?: NodeConnection; - timer?: NodeJS.Timer; - lock: RWLock; +type ConnectionAndTimer = { + connection: NodeConnection; + timer: NodeJS.Timer; }; interface NodeConnectionManager extends StartStop {} @StartStop() class NodeConnectionManager { /** - * Time used to estalish `NodeConnection` + * Time used to establish `NodeConnection` */ public readonly connConnectTime: number; @@ -46,7 +49,7 @@ class NodeConnectionManager { public readonly connTimeoutTime: number; /** * Alpha constant for kademlia - * The number of closest nodes to contact initially + * The number of the closest nodes to contact initially */ public readonly initialClosestNodes: number; @@ -54,6 +57,9 @@ class NodeConnectionManager { protected nodeGraph: NodeGraph; protected keyManager: KeyManager; protected proxy: Proxy; + protected queue: Queue; + // NodeManager has to be passed in during start to allow co-dependency + protected nodeManager: NodeManager | undefined; protected seedNodes: SeedNodes; /** * Data structure to store all NodeConnections. If a connection to a node n does @@ -65,12 +71,14 @@ class NodeConnectionManager { * A nodeIdString is used for the key here since * NodeIds can't be used to properly retrieve a value from the map. */ - protected connections: Map = new Map(); + protected connections: Map = new Map(); + protected connectionLocks: LockBox = new LockBox(); public constructor({ keyManager, nodeGraph, proxy, + queue, seedNodes = {}, initialClosestNodes = 3, connConnectTime = 20000, @@ -80,6 +88,7 @@ class NodeConnectionManager { nodeGraph: NodeGraph; keyManager: KeyManager; proxy: Proxy; + queue: Queue; seedNodes?: SeedNodes; initialClosestNodes?: number; connConnectTime?: number; @@ -90,23 +99,31 @@ class NodeConnectionManager { this.keyManager = keyManager; this.nodeGraph = nodeGraph; this.proxy = proxy; + this.queue = queue; this.seedNodes = seedNodes; this.initialClosestNodes = initialClosestNodes; this.connConnectTime = connConnectTime; this.connTimeoutTime = connTimeoutTime; } - public async start() { + public async start({ nodeManager }: { nodeManager: NodeManager }) { this.logger.info(`Starting ${this.constructor.name}`); + this.nodeManager = nodeManager; for (const nodeIdEncoded in this.seedNodes) { const nodeId = nodesUtils.decodeNodeId(nodeIdEncoded)!; - await this.nodeGraph.setNode(nodeId, this.seedNodes[nodeIdEncoded]); + await this.nodeManager.setNode( + nodeId, + this.seedNodes[nodeIdEncoded], + true, + true, + ); } this.logger.info(`Started ${this.constructor.name}`); } public async stop() { this.logger.info(`Stopping ${this.constructor.name}`); + this.nodeManager = undefined; for (const [nodeId, connAndLock] of this.connections) { if (connAndLock == null) continue; if (connAndLock.connection == null) continue; @@ -117,29 +134,46 @@ class NodeConnectionManager { } /** - * For usage with withF, to acquire a connection in a + * For usage with withF, to acquire a connection * This unique acquire function structure of returning the ResourceAcquire * itself is such that we can pass targetNodeId as a parameter (as opposed to * an acquire function with no parameters). * @param targetNodeId Id of target node to communicate with + * @param timer Connection timeout timer * @returns ResourceAcquire Resource API for use in with contexts */ @ready(new nodesErrors.ErrorNodeConnectionManagerNotRunning()) public async acquireConnection( targetNodeId: NodeId, + timer?: Timer, ): Promise>> { return async () => { - const connAndLock = await this.getConnection(targetNodeId); + const { connection, timer: timeToLiveTimer } = await this.getConnection( + targetNodeId, + timer, + ); // Acquire the read lock and the release function - const release = await connAndLock.lock.acquireRead(); + const [release] = await this.connectionLocks.lock([ + targetNodeId.toString(), + RWLockWriter, + 'write', + ])(); // Resetting TTL timer - connAndLock.timer?.refresh(); + timeToLiveTimer?.refresh(); // Return tuple of [ResourceRelease, Resource] return [ - async () => { - release(); + async (e) => { + await release(); + if ( + e instanceof nodesErrors.ErrorNodeConnectionDestroyed || + e instanceof grpcErrors.ErrorGRPC || + e instanceof agentErrors.ErrorAgentClientDestroyed + ) { + // Error with connection, shutting connection down + await this.destroyConnection(targetNodeId); + } }, - connAndLock.connection, + connection, ]; }; } @@ -151,35 +185,25 @@ class NodeConnectionManager { * for use with normal arrow function * @param targetNodeId Id of target node to communicate with * @param f Function to handle communication + * @param timer Connection timeout timer */ @ready(new nodesErrors.ErrorNodeConnectionManagerNotRunning()) public async withConnF( targetNodeId: NodeId, f: (conn: NodeConnection) => Promise, + timer?: Timer, ): Promise { - try { - return await withF( - [await this.acquireConnection(targetNodeId)], - async ([conn]) => { - this.logger.info( - `withConnF calling function with connection to ${nodesUtils.encodeNodeId( - targetNodeId, - )}`, - ); - return await f(conn); - }, - ); - } catch (err) { - if ( - err instanceof nodesErrors.ErrorNodeConnectionDestroyed || - err instanceof grpcErrors.ErrorGRPC || - err instanceof agentErrors.ErrorAgentClientDestroyed - ) { - // Error with connection, shutting connection down - await this.destroyConnection(targetNodeId); - } - throw err; - } + return await withF( + [await this.acquireConnection(targetNodeId, timer)], + async ([conn]) => { + this.logger.info( + `withConnF calling function with connection to ${nodesUtils.encodeNodeId( + targetNodeId, + )}`, + ); + return await f(conn); + }, + ); } /** @@ -189,6 +213,7 @@ class NodeConnectionManager { * for use with a generator function * @param targetNodeId Id of target node to communicate with * @param g Generator function to handle communication + * @param timer Connection timeout timer */ @ready(new nodesErrors.ErrorNodeConnectionManagerNotRunning()) public async *withConnG( @@ -196,24 +221,18 @@ class NodeConnectionManager { g: ( conn: NodeConnection, ) => AsyncGenerator, + timer?: Timer, ): AsyncGenerator { - const acquire = await this.acquireConnection(targetNodeId); + const acquire = await this.acquireConnection(targetNodeId, timer); const [release, conn] = await acquire(); + let caughtError; try { - return yield* await g(conn!); - } catch (err) { - if ( - err instanceof nodesErrors.ErrorNodeConnectionDestroyed || - err instanceof grpcErrors.ErrorGRPC || - err instanceof agentErrors.ErrorAgentClientDestroyed - ) { - // Error with connection, shutting connection down - await release(); - await this.destroyConnection(targetNodeId); - } - throw err; + return yield* g(conn!); + } catch (e) { + caughtError = e; + throw e; } finally { - await release(); + await release(caughtError); } // Wait for any destruction to complete after locking is removed } @@ -222,127 +241,90 @@ class NodeConnectionManager { * Create a connection to another node (without performing any function). * This is a NOOP if a connection already exists. * @param targetNodeId Id of node we are creating connection to - * @returns ConnectionAndLock that was create or exists in the connection map. + * @param timer Connection timeout timer + * @returns ConnectionAndLock that was created or exists in the connection map */ protected async getConnection( targetNodeId: NodeId, - ): Promise { + timer?: Timer, + ): Promise { this.logger.info( `Getting connection to ${nodesUtils.encodeNodeId(targetNodeId)}`, ); - let connection: NodeConnection | undefined; - let lock: RWLock; - let connAndLock = this.connections.get( - targetNodeId.toString() as NodeIdString, - ); - if (connAndLock != null) { - ({ connection, lock } = connAndLock); - // Connection already exists, so return - if (connection != null) return connAndLock; - // Acquire the write (creation) lock - return await lock.withWrite(async () => { - // Once lock is released, check again if the conn now exists - connAndLock = this.connections.get( - targetNodeId.toString() as NodeIdString, - ); - if (connAndLock != null && connAndLock.connection != null) { + const targetNodeIdString = targetNodeId.toString() as NodeIdString; + return await this.connectionLocks.withF( + [targetNodeIdString, RWLockWriter, 'write'], + async () => { + const connAndTimer = this.connections.get(targetNodeIdString); + if (connAndTimer != null) { this.logger.info( `existing entry found for ${nodesUtils.encodeNodeId(targetNodeId)}`, ); - return connAndLock; + return connAndTimer; } - this.logger.info( - `existing lock, creating connection to ${nodesUtils.encodeNodeId( - targetNodeId, - )}`, - ); - // Creating the connection and set in map - return await this.establishNodeConnection(targetNodeId, lock); - }); - } else { - lock = new RWLock(); - connAndLock = { lock }; - this.connections.set( - targetNodeId.toString() as NodeIdString, - connAndLock, - ); - return await lock.withWrite(async () => { this.logger.info( `no existing entry, creating connection to ${nodesUtils.encodeNodeId( targetNodeId, )}`, ); // Creating the connection and set in map - return await this.establishNodeConnection(targetNodeId, lock); - }); - } - } + const targetAddress = await this.findNode(targetNodeId); + if (targetAddress == null) { + throw new nodesErrors.ErrorNodeGraphNodeIdNotFound(); + } + // If the stored host is not a valid host (IP address), + // then we assume it to be a hostname + const targetHostname = !networkUtils.isHost(targetAddress.host) + ? (targetAddress.host as string as Hostname) + : undefined; + const targetHost = await networkUtils.resolveHost(targetAddress.host); + // Creating the destroyCallback + const destroyCallback = async () => { + // To avoid deadlock only in the case where this is called + // we want to check for destroying connection and read lock + const connAndTimer = this.connections.get(targetNodeIdString); + // If the connection is calling destroyCallback then it SHOULD + // exist in the connection map + if (connAndTimer == null) return; + // Already locked so already destroying + if (this.connectionLocks.isLocked(targetNodeIdString)) return; + // Connection is already destroying + if (connAndTimer?.connection?.[status] === 'destroying') return; + await this.destroyConnection(targetNodeId); + }; + // Creating new connection + const newConnection = await NodeConnection.createNodeConnection({ + targetNodeId: targetNodeId, + targetHost: targetHost, + targetHostname: targetHostname, + targetPort: targetAddress.port, + proxy: this.proxy, + keyManager: this.keyManager, + nodeConnectionManager: this, + destroyCallback, + timer: timer ?? timerStart(this.connConnectTime), + logger: this.logger.getChild( + `${NodeConnection.name} ${targetHost}:${targetAddress.port}`, + ), + clientFactory: async (args) => + GRPCClientAgent.createGRPCClientAgent(args), + }); + // We can assume connection was established and destination was valid, + // we can add the target to the nodeGraph + await this.nodeManager?.setNode(targetNodeId, targetAddress, false); + // Creating TTL timeout + const timeToLiveTimer = setTimeout(async () => { + await this.destroyConnection(targetNodeId); + }, this.connTimeoutTime); - /** - * Strictly a helper function for this.createConnection. Do not call this - * function anywhere else. - * To create a connection to a node, always use createConnection, or - * withConnection. - * This only adds the connection to the connection map if the connection was established. - * @param targetNodeId Id of node we are establishing connection to - * @param lock Lock associated with connection - * @returns ConnectionAndLock that was added to the connection map - */ - protected async establishNodeConnection( - targetNodeId: NodeId, - lock: RWLock, - ): Promise { - const targetAddress = await this.findNode(targetNodeId); - // If the stored host is not a valid host (IP address), then we assume it to - // be a hostname - const targetHostname = !networkUtils.isHost(targetAddress.host) - ? (targetAddress.host as string as Hostname) - : undefined; - const targetHost = await networkUtils.resolveHost(targetAddress.host); - // Creating the destroyCallback - const destroyCallback = async () => { - // To avoid deadlock only in the case where this is called - // we want to check for destroying connection and read lock - const connAndLock = this.connections.get( - targetNodeId.toString() as NodeIdString, - ); - // If the connection is calling destroyCallback then it SHOULD - // exist in the connection map - if (connAndLock == null) throw Error('temp error, bad logic'); - // Already locked so already destroying - if (connAndLock.lock.readerCount > 0) return; - const connectionStatus = connAndLock?.connection?.[status]; - // Connection is already destroying - if (connectionStatus === 'destroying') return; - await this.destroyConnection(targetNodeId); - }; - const connection = await NodeConnection.createNodeConnection({ - targetNodeId: targetNodeId, - targetHost: targetHost, - targetHostname: targetHostname, - targetPort: targetAddress.port, - proxy: this.proxy, - keyManager: this.keyManager, - nodeConnectionManager: this, - destroyCallback, - connConnectTime: this.connConnectTime, - logger: this.logger.getChild( - `${NodeConnection.name} ${targetHost}:${targetAddress.port}`, - ), - clientFactory: async (args) => - GRPCClientAgent.createGRPCClientAgent(args), - }); - // Creating TTL timeout - const timer = setTimeout(async () => { - await this.destroyConnection(targetNodeId); - }, this.connTimeoutTime); - // Add it to the map of active connections - const connectionAndLock = { connection, lock, timer }; - this.connections.set( - targetNodeId.toString() as NodeIdString, - connectionAndLock, + const newConnAndTimer: ConnectionAndTimer = { + connection: newConnection, + timer: timeToLiveTimer, + }; + this.connections.set(targetNodeIdString, newConnAndTimer); + return newConnAndTimer; + }, ); - return connectionAndLock; } /** @@ -350,23 +332,19 @@ class NodeConnectionManager { * @param targetNodeId Id of node we are destroying connection to */ protected async destroyConnection(targetNodeId: NodeId): Promise { - const connAndLock = this.connections.get( - targetNodeId.toString() as NodeIdString, + const targetNodeIdString = targetNodeId.toString() as NodeIdString; + return await this.connectionLocks.withF( + [targetNodeIdString, RWLockWriter, 'write'], + async () => { + const connAndTimer = this.connections.get(targetNodeIdString); + if (connAndTimer?.connection == null) return; + await connAndTimer.connection.destroy(); + // Destroying TTL timer + if (connAndTimer.timer != null) clearTimeout(connAndTimer.timer); + // Updating the connection map + this.connections.delete(targetNodeIdString); + }, ); - if (connAndLock == null) return; - const connection = connAndLock.connection; - if (connection == null) return; - const lock = connAndLock.lock; - - // If the connection exists then we lock, destroy and remove it from the map - await lock.withWrite(async () => { - // Destroying connection - await connection.destroy(); - // Destroying TTL timer - if (connAndLock.timer != null) clearTimeout(connAndLock.timer); - // Updating the connection map - this.connections.set(targetNodeId.toString() as NodeIdString, { lock }); - }); } /** @@ -398,7 +376,7 @@ class NodeConnectionManager { * sends hole-punching packets back to the server's reverse proxy. * This is not needed to be called when doing hole punching since the * ForwardProxy automatically starts the process. - * @param nodeId Node Id of the node we are connecting to + * @param nodeId Node ID of the node we are connecting to * @param proxyHost Proxy host of the reverse proxy * @param proxyPort Proxy port of the reverse proxy * @param timer Connection timeout timer @@ -416,67 +394,26 @@ class NodeConnectionManager { * Retrieves the node address. If an entry doesn't exist in the db, then * proceeds to locate it using Kademlia. * @param targetNodeId Id of the node we are tying to find + * @param options */ @ready(new nodesErrors.ErrorNodeConnectionManagerNotRunning()) - public async findNode(targetNodeId: NodeId): Promise { + public async findNode( + targetNodeId: NodeId, + options: { signal?: AbortSignal } = {}, + ): Promise { + const { signal } = { ...options }; // First check if we already have an existing ID -> address record - - let address = await this.nodeGraph.getNode(targetNodeId); + let address = (await this.nodeGraph.getNode(targetNodeId))?.address; // Otherwise, attempt to locate it by contacting network - if (address == null) { - address = await this.getClosestGlobalNodes(targetNodeId); - // TODO: This currently just does one iteration - // If not found in this single iteration, we throw an exception - if (address == null) { - throw new nodesErrors.ErrorNodeGraphNodeIdNotFound(); - } - } - // We ensure that we always return a NodeAddress (either by lookup, or - // network search) - if we can't locate it from either, we throw an exception + address = + address ?? + (await this.getClosestGlobalNodes(targetNodeId, undefined, { + signal, + })); + // TODO: This currently just does one iteration return address; } - /** - * Finds the set of nodes (of size k) known by the current node (i.e. in its - * buckets database) that have the smallest distance to the target node (i.e. - * are closest to the target node). - * i.e. FIND_NODE RPC from Kademlia spec - * - * Used by the RPC service. - * - * @param targetNodeId the node ID to find other nodes closest to it - * @param numClosest the number of closest nodes to return (by default, returns - * according to the maximum number of nodes per bucket) - * @returns a mapping containing exactly k nodeIds -> nodeAddresses (unless the - * current node has less than k nodes in all of its buckets, in which case it - * returns all nodes it has knowledge of) - */ - @ready(new nodesErrors.ErrorNodeConnectionManagerNotRunning()) - public async getClosestLocalNodes( - targetNodeId: NodeId, - numClosest: number = this.nodeGraph.maxNodesPerBucket, - ): Promise> { - // Retrieve all nodes from buckets in database - const buckets = await this.nodeGraph.getAllBuckets(); - // Iterate over all of the nodes in each bucket - const distanceToNodes: Array = []; - buckets.forEach(function (bucket) { - for (const nodeIdString of Object.keys(bucket)) { - // Compute the distance from the node, and add it to the array - const nodeId = IdInternal.fromString(nodeIdString); - distanceToNodes.push({ - id: nodeId, - address: bucket[nodeId].address, - distance: nodesUtils.calculateDistance(nodeId, targetNodeId), - }); - } - }); - // Sort the array (based on the distance at index 1) - distanceToNodes.sort(nodesUtils.sortByDistance); - // Return the closest k nodes (i.e. the first k), or all nodes if < k in array - return distanceToNodes.slice(0, numClosest); - } - /** * Attempts to locate a target node in the network (using Kademlia). * Adds all discovered, active nodes to the current node's database (up to k @@ -489,21 +426,30 @@ class NodeConnectionManager { * port). * @param targetNodeId ID of the node attempting to be found (i.e. attempting * to find its IP address and port) + * @param timer Connection timeout timer + * @param options * @returns whether the target node was located in the process */ @ready(new nodesErrors.ErrorNodeConnectionManagerNotRunning()) public async getClosestGlobalNodes( targetNodeId: NodeId, + timer?: Timer, + options: { signal?: AbortSignal } = {}, ): Promise { + const localNodeId = this.keyManager.getNodeId(); + const { signal } = { ...options }; // Let foundTarget: boolean = false; let foundAddress: NodeAddress | undefined = undefined; // Get the closest alpha nodes to the target node (set as shortlist) - const shortlist: Array = await this.getClosestLocalNodes( + // FIXME? this is an array. Shouldn't it be a set? + // It's possible for this to grow faster than we can consume it, + // doubly so if we allow duplicates + const shortlist = await this.nodeGraph.getClosestNodes( targetNodeId, this.initialClosestNodes, ); // If we have no nodes at all in our database (even after synchronising), - // then we should throw an error. We aren't going to find any others + // then we should throw an eor. We aren't going to find any others if (shortlist.length === 0) { throw new nodesErrors.ErrorNodeGraphEmptyDatabase(); } @@ -512,8 +458,9 @@ class NodeConnectionManager { // in nodeConnections - what if there's been more than 1 invocation of // getClosestGlobalNodes()? const contacted: { [nodeId: string]: boolean } = {}; - // Iterate until we've found found and contacted k nodes - while (Object.keys(contacted).length <= this.nodeGraph.maxNodesPerBucket) { + // Iterate until we've found and contacted k nodes + while (Object.keys(contacted).length <= this.nodeGraph.nodeBucketLimit) { + if (signal?.aborted) throw new nodesErrors.ErrorNodeAborted(); // While (!foundTarget) { // Remove the node from the front of the array const nextNode = shortlist.shift(); @@ -521,50 +468,66 @@ class NodeConnectionManager { if (nextNode == null) { break; } + const [nextNodeId, nextNodeAddress] = nextNode; // Skip if the node has already been contacted - if (contacted[nextNode.id]) { + if (contacted[nextNodeId]) { continue; } // Connect to the node (check if pre-existing connection exists, otherwise // create a new one) - try { - // Add the node to the database so that we can find its address in - // call to getConnectionToNode - await this.nodeGraph.setNode(nextNode.id, nextNode.address); - await this.getConnection(nextNode.id); - } catch (e) { - // If we can't connect to the node, then skip it + if ( + await this.pingNode( + nextNodeId, + nextNodeAddress.address.host, + nextNodeAddress.address.port, + ) + ) { + await this.nodeManager!.setNode(nextNodeId, nextNodeAddress.address); + } else { continue; } - contacted[nextNode.id] = true; + contacted[nextNodeId] = true; // Ask the node to get their own closest nodes to the target const foundClosest = await this.getRemoteNodeClosestNodes( - nextNode.id, + nextNodeId, targetNodeId, + timer, ); // Check to see if any of these are the target node. At the same time, add // them to the shortlist - for (const nodeData of foundClosest) { - // Ignore any nodes that have been contacted - if (contacted[nodeData.id]) { + for (const [nodeId, nodeData] of foundClosest) { + if (signal?.aborted) throw new nodesErrors.ErrorNodeAborted(); + // Ignore any nodes that have been contacted or our own node + if (contacted[nodeId] || localNodeId.equals(nodeId)) { continue; } - if (nodeData.id.equals(targetNodeId)) { - await this.nodeGraph.setNode(nodeData.id, nodeData.address); + if ( + nodeId.equals(targetNodeId) && + (await this.pingNode( + nodeId, + nodeData.address.host, + nodeData.address.port, + )) + ) { + await this.nodeManager!.setNode(nodeId, nodeData.address); foundAddress = nodeData.address; // We have found the target node, so we can stop trying to look for it // in the shortlist break; } - shortlist.push(nodeData); + shortlist.push([nodeId, nodeData]); } // To make the number of jumps relatively short, should connect to the nodes // closest to the target first, and ask if they know of any closer nodes - // Then we can simply unshift the first (closest) element from the shortlist - shortlist.sort(function (a: NodeData, b: NodeData) { - if (a.distance > b.distance) { + // than we can simply unshift the first (closest) element from the shortlist + const distance = (nodeId: NodeId) => + nodesUtils.nodeDistance(targetNodeId, nodeId); + shortlist.sort(function ([nodeIdA], [nodeIdB]) { + const distanceA = distance(nodeIdA); + const distanceB = distance(nodeIdB); + if (distanceA > distanceB) { return 1; - } else if (a.distance < b.distance) { + } else if (distanceA < distanceB) { return -1; } else { return 0; @@ -579,69 +542,108 @@ class NodeConnectionManager { * target node ID. * @param nodeId the node ID to search on * @param targetNodeId the node ID to find other nodes closest to it + * @param timer Connection timeout timer * @returns list of nodes and their IP/port that are closest to the target */ @ready(new nodesErrors.ErrorNodeConnectionManagerNotRunning()) public async getRemoteNodeClosestNodes( nodeId: NodeId, targetNodeId: NodeId, - ): Promise> { + timer?: Timer, + ): Promise> { // Construct the message const nodeIdMessage = new nodesPB.Node(); nodeIdMessage.setNodeId(nodesUtils.encodeNodeId(targetNodeId)); // Send through client - return this.withConnF(nodeId, async (connection) => { - const client = await connection.getClient(); - const response = await client.nodesClosestLocalNodesGet(nodeIdMessage); - const nodes: Array = []; - // Loop over each map element (from the returned response) and populate nodes - response.getNodeTableMap().forEach((address, nodeIdString: string) => { - const nodeId = nodesUtils.decodeNodeId(nodeIdString); - // If the nodeId is not valid we don't add it to the list of nodes - if (nodeId != null) { - nodes.push({ - id: nodeId, - address: { - host: address.getHost() as Host | Hostname, - port: address.getPort() as Port, - }, - distance: nodesUtils.calculateDistance(targetNodeId, nodeId), - }); - } - }); - return nodes; - }); + return this.withConnF( + nodeId, + async (connection) => { + const client = connection.getClient(); + const response = await client.nodesClosestLocalNodesGet(nodeIdMessage); + const nodes: Array<[NodeId, NodeData]> = []; + // Loop over each map element (from the returned response) and populate nodes + response.getNodeTableMap().forEach((address, nodeIdString: string) => { + const nodeId = nodesUtils.decodeNodeId(nodeIdString); + // If the nodeId is not valid we don't add it to the list of nodes + if (nodeId != null) { + nodes.push([ + nodeId, + { + address: { + host: address.getHost() as Host | Hostname, + port: address.getPort() as Port, + }, + // Not really needed + // But if it's needed then we need to add the information to the proto definition + lastUpdated: 0, + }, + ]); + } + }); + return nodes; + }, + timer, + ); } /** - * Perform an initial database synchronisation: get the k closest nodes + * Perform an initial database synchronisation: get k of the closest nodes * from each seed node and add them to this database - * For now, we also attempt to establish a connection to each of them. - * If these nodes are offline, this will impose a performance penalty, - * so we should investigate performing this in the background if possible. - * Alternatively, we can also just add the nodes to our database without - * establishing connection. - * This has been removed from start() as there's a chicken-egg scenario - * where we require the NodeGraph instance to be created in order to get - * connections. + * Establish a proxy connection to each node before adding it + * By default this operation is blocking, set `block` to false to make it + * non-blocking */ @ready(new nodesErrors.ErrorNodeConnectionManagerNotRunning()) - public async syncNodeGraph() { + public async syncNodeGraph(block: boolean = true, timer?: Timer) { + this.logger.info('Syncing nodeGraph'); for (const seedNodeId of this.getSeedNodes()) { // Check if the connection is viable try { - await this.getConnection(seedNodeId); + await this.getConnection(seedNodeId, timer); } catch (e) { if (e instanceof nodesErrors.ErrorNodeConnectionTimeout) continue; throw e; } - const nodes = await this.getRemoteNodeClosestNodes( seedNodeId, this.keyManager.getNodeId(), + timer, ); - for (const n of nodes) { - await this.nodeGraph.setNode(n.id, n.address); + for (const [nodeId, nodeData] of nodes) { + if (!nodeId.equals(this.keyManager.getNodeId())) { + const pingAndAddNode = async () => { + const port = nodeData.address.port; + const host = await networkUtils.resolveHost(nodeData.address.host); + if (await this.pingNode(nodeId, host, port)) { + await this.nodeManager!.setNode(nodeId, nodeData.address, true); + } + }; + + if (!block) { + this.queue.push(pingAndAddNode); + } else { + try { + await pingAndAddNode(); + } catch (e) { + if (!(e instanceof nodesErrors.ErrorNodeGraphSameNodeId)) throw e; + } + } + } + } + // Refreshing every bucket above the closest node + const refreshBuckets = async () => { + const [closestNode] = ( + await this.nodeGraph.getClosestNodes(this.keyManager.getNodeId(), 1) + ).pop()!; + const [bucketIndex] = this.nodeGraph.bucketIndex(closestNode); + for (let i = bucketIndex; i < this.nodeGraph.nodeIdBits; i++) { + this.nodeManager?.refreshBucketQueueAdd(i); + } + }; + if (!block) { + this.queue.push(refreshBuckets); + } else { + await refreshBuckets(); } } } @@ -653,8 +655,9 @@ class NodeConnectionManager { * @param relayNodeId node ID of the relay node (i.e. the seed node) * @param sourceNodeId node ID of the current node (i.e. the sender) * @param targetNodeId node ID of the target node to hole punch - * @param proxyAddress stringified address of `proxyHost:proxyPort` + * @param proxyAddress string of address in the form `proxyHost:proxyPort` * @param signature signature to verify source node is sender (signature based + * @param timer Connection timeout timer * on proxyAddress as message) */ @ready(new nodesErrors.ErrorNodeConnectionManagerNotRunning()) @@ -664,16 +667,21 @@ class NodeConnectionManager { targetNodeId: NodeId, proxyAddress: string, signature: Buffer, + timer?: Timer, ): Promise { const relayMsg = new nodesPB.Relay(); relayMsg.setSrcId(nodesUtils.encodeNodeId(sourceNodeId)); relayMsg.setTargetId(nodesUtils.encodeNodeId(targetNodeId)); relayMsg.setProxyAddress(proxyAddress); relayMsg.setSignature(signature.toString()); - await this.withConnF(relayNodeId, async (connection) => { - const client = connection.getClient(); - await client.nodesHolePunchMessageSend(relayMsg); - }); + await this.withConnF( + relayNodeId, + async (connection) => { + const client = connection.getClient(); + await client.nodesHolePunchMessageSend(relayMsg); + }, + timer, + ); } /** @@ -683,15 +691,32 @@ class NodeConnectionManager { * node). * @param message the original relay message (assumed to be created in * nodeConnection.start()) + * @param timer Connection timeout timer */ @ready(new nodesErrors.ErrorNodeConnectionManagerNotRunning()) - public async relayHolePunchMessage(message: nodesPB.Relay): Promise { + public async relayHolePunchMessage( + message: nodesPB.Relay, + timer?: Timer, + ): Promise { + // First check if we already have an existing ID -> address record + // If we're relaying then we trust our own node graph records over + // what was provided in the message + const sourceNode = validationUtils.parseNodeId(message.getSrcId()); + const knownAddress = (await this.nodeGraph.getNode(sourceNode))?.address; + let proxyAddress = message.getProxyAddress(); + if (knownAddress != null) { + proxyAddress = networkUtils.buildAddress( + knownAddress.host as Host, + knownAddress.port, + ); + } await this.sendHolePunchMessage( validationUtils.parseNodeId(message.getTargetId()), - validationUtils.parseNodeId(message.getSrcId()), + sourceNode, validationUtils.parseNodeId(message.getTargetId()), - message.getProxyAddress(), + proxyAddress, Buffer.from(message.getSignature()), + timer, ); } @@ -700,10 +725,60 @@ class NodeConnectionManager { */ @ready(new nodesErrors.ErrorNodeConnectionManagerNotRunning()) public getSeedNodes(): Array { - const nodeIds = Object.keys(this.seedNodes).map( + return Object.keys(this.seedNodes).map( (nodeIdEncoded) => nodesUtils.decodeNodeId(nodeIdEncoded)!, ); - return nodeIds; + } + + /** + * Checks if a connection can be made to the target. Returns true if the + * connection can be authenticated, it's certificate matches the nodeId and + * the addresses match if provided. Otherwise returns false. + * @param nodeId - NodeId of the target + * @param host - Host of the target node + * @param port - Port of the target node + * @param timer Connection timeout timer + */ + @ready(new nodesErrors.ErrorNodeConnectionManagerNotRunning()) + public async pingNode( + nodeId: NodeId, + host: Host | Hostname, + port: Port, + timer?: Timer, + ): Promise { + host = await networkUtils.resolveHost(host); + // If we can create a connection then we have punched though the NAT, + // authenticated and confirmed the nodeId matches + const proxyAddress = networkUtils.buildAddress( + this.proxy.getProxyHost(), + this.proxy.getProxyPort(), + ); + const signature = await this.keyManager.signWithRootKeyPair( + Buffer.from(proxyAddress), + ); + // FIXME: this needs to handle aborting + const holePunchPromises = Array.from(this.getSeedNodes(), (seedNodeId) => { + return this.sendHolePunchMessage( + seedNodeId, + this.keyManager.getNodeId(), + nodeId, + proxyAddress, + signature, + ); + }); + const forwardPunchPromise = this.holePunchForward( + nodeId, + host, + port, + timer, + ); + + try { + await Promise.any([forwardPunchPromise, ...holePunchPromises]); + } catch (e) { + return false; + } + return true; } } diff --git a/src/nodes/NodeGraph.ts b/src/nodes/NodeGraph.ts index 4237b5529..6bd6b2f2d 100644 --- a/src/nodes/NodeGraph.ts +++ b/src/nodes/NodeGraph.ts @@ -1,9 +1,14 @@ -import type { DB, DBLevel, DBOp } from '@matrixai/db'; -import type { NodeId, NodeAddress, NodeBucket } from './types'; +import type { DB, DBTransaction, KeyPath, LevelPath } from '@matrixai/db'; +import type { + NodeId, + NodeAddress, + NodeBucket, + NodeData, + NodeBucketMeta, + NodeBucketIndex, + NodeGraphSpace, +} from './types'; import type KeyManager from '../keys/KeyManager'; -import type { Host, Hostname, Port } from '../network/types'; -import { Mutex } from 'async-mutex'; -import lexi from 'lexicographic-integer'; import Logger from '@matrixai/logger'; import { CreateDestroyStartStop, @@ -12,10 +17,11 @@ import { import { IdInternal } from '@matrixai/id'; import * as nodesUtils from './utils'; import * as nodesErrors from './errors'; +import { getUnixtime, never } from '../utils'; /** * NodeGraph is an implementation of Kademlia for maintaining peer to peer information - * We maintain a map of buckets. Where each bucket has k number of node infos + * It is a database of fixed-size buckets, where each bucket contains NodeId -> NodeData */ interface NodeGraph extends CreateDestroyStartStop {} @CreateDestroyStartStop( @@ -23,29 +29,16 @@ interface NodeGraph extends CreateDestroyStartStop {} new nodesErrors.ErrorNodeGraphDestroyed(), ) class NodeGraph { - // Max number of nodes in each k-bucket (a.k.a. k) - public readonly maxNodesPerBucket: number = 20; - - protected logger: Logger; - protected db: DB; - protected keyManager: KeyManager; - protected nodeGraphDbDomain: string = this.constructor.name; - protected nodeGraphBucketsDbDomain: Array = [ - this.nodeGraphDbDomain, - 'buckets', - ]; - protected nodeGraphDb: DBLevel; - protected nodeGraphBucketsDb: DBLevel; - protected lock: Mutex = new Mutex(); - public static async createNodeGraph({ db, keyManager, + nodeIdBits = 256, logger = new Logger(this.name), fresh = false, }: { db: DB; keyManager: KeyManager; + nodeIdBits?: number; logger?: Logger; fresh?: boolean; }): Promise { @@ -53,6 +46,7 @@ class NodeGraph { const nodeGraph = new NodeGraph({ db, keyManager, + nodeIdBits, logger, }); await nodeGraph.start({ fresh }); @@ -60,375 +54,769 @@ class NodeGraph { return nodeGraph; } + /** + * Bit size of the NodeIds + * This equals the number of buckets + */ + public readonly nodeIdBits: number; + /** + * Max number of nodes in each k-bucket + */ + public readonly nodeBucketLimit: number = 20; + + protected logger: Logger; + protected db: DB; + protected keyManager: KeyManager; + protected space: NodeGraphSpace; + protected nodeGraphDbPath: LevelPath = [this.constructor.name]; + protected nodeGraphMetaDbPath: LevelPath; + protected nodeGraphBucketsDbPath: LevelPath; + protected nodeGraphLastUpdatedDbPath: LevelPath; + constructor({ db, keyManager, + nodeIdBits, logger, }: { db: DB; keyManager: KeyManager; + nodeIdBits: number; logger: Logger; }) { this.logger = logger; this.db = db; this.keyManager = keyManager; - } - - get locked(): boolean { - return this.lock.isLocked(); + this.nodeIdBits = nodeIdBits; } public async start({ fresh = false, - }: { - fresh?: boolean; - } = {}) { + }: { fresh?: boolean } = {}): Promise { this.logger.info(`Starting ${this.constructor.name}`); - const nodeGraphDb = await this.db.level(this.nodeGraphDbDomain); - // Buckets stores NodeBucketIndex -> NodeBucket - const nodeGraphBucketsDb = await this.db.level( - this.nodeGraphBucketsDbDomain[1], - nodeGraphDb, - ); - if (fresh) { - await nodeGraphDb.clear(); - } - this.nodeGraphDb = nodeGraphDb; - this.nodeGraphBucketsDb = nodeGraphBucketsDb; + const space = await this.db.withTransactionF(async (tran) => { + if (fresh) { + await tran.clear(this.nodeGraphDbPath); + } + // Space key is used to create a swappable sublevel + // when remapping the buckets during `this.refreshBuckets` + return await this.setupSpace(tran); + }); + // Bucket metadata sublevel: `!meta!! -> value` + this.nodeGraphMetaDbPath = [...this.nodeGraphDbPath, 'meta' + space]; + // Bucket sublevel: `!buckets!! -> NodeData` + // The BucketIndex can range from 0 to NodeId bit-size minus 1 + // So 256 bits means 256 buckets of 0 to 255 + this.nodeGraphBucketsDbPath = [...this.nodeGraphDbPath, 'buckets' + space]; + // Last updated sublevel: `!lastUpdated!!- -> NodeId` + // This is used as a sorted index of the NodeId by `lastUpdated` timestamp + // The `NodeId` must be appended in the key in order to disambiguate `NodeId` with same `lastUpdated` timestamp + this.nodeGraphLastUpdatedDbPath = [ + ...this.nodeGraphDbPath, + 'lastUpdated' + space, + ]; + this.space = space; this.logger.info(`Started ${this.constructor.name}`); } - public async stop() { + public async stop(): Promise { this.logger.info(`Stopping ${this.constructor.name}`); this.logger.info(`Stopped ${this.constructor.name}`); } - public async destroy() { + public async destroy(): Promise { this.logger.info(`Destroying ${this.constructor.name}`); - const nodeGraphDb = await this.db.level(this.nodeGraphDbDomain); - await nodeGraphDb.clear(); + // If the DB was stopped, the existing sublevel `this.nodeGraphDb` will not be valid + // Therefore we recreate the sublevel here + await this.db.clear(this.nodeGraphDbPath); this.logger.info(`Destroyed ${this.constructor.name}`); } /** - * Run several operations within the same lock - * This does not ensure atomicity of the underlying database - * Database atomicity still depends on the underlying operation + * Sets up the space key + * The space string is suffixed to the `buckets` and `meta` sublevels + * This is used to allow swapping of sublevels when remapping buckets + * during `this.refreshBuckets` */ - public async transaction( - f: (nodeGraph: NodeGraph) => Promise, - ): Promise { - const release = await this.lock.acquire(); - try { - return await f(this); - } finally { - release(); + protected async setupSpace(tran: DBTransaction): Promise { + let space = await tran.get([ + ...this.nodeGraphDbPath, + 'space', + ]); + if (space != null) { + return space; } + space = '0'; + await tran.put([...this.nodeGraphDbPath, 'space'], space); + return space; + } + + @ready(new nodesErrors.ErrorNodeGraphNotRunning()) + public async getNode( + nodeId: NodeId, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.getNode(nodeId, tran), + ); + } + + const [bucketIndex] = this.bucketIndex(nodeId); + const bucketDomain = [ + ...this.nodeGraphBucketsDbPath, + nodesUtils.bucketKey(bucketIndex), + nodesUtils.bucketDbKey(nodeId), + ]; + return await tran.get(bucketDomain); } /** - * Transaction wrapper that will not lock if the operation was executed - * within a transaction context + * Get all nodes. + * Nodes are always sorted by `NodeBucketIndex` first + * Then secondly by the node IDs + * The `order` parameter applies to both, for example possible sorts: + * NodeBucketIndex asc, NodeID asc + * NodeBucketIndex desc, NodeId desc */ - public async _transaction(f: () => Promise): Promise { - if (this.lock.isLocked()) { - return await f(); - } else { - return await this.transaction(f); + @ready(new nodesErrors.ErrorNodeGraphNotRunning()) + public async *getNodes( + order: 'asc' | 'desc' = 'asc', + tran?: DBTransaction, + ): AsyncGenerator<[NodeId, NodeData]> { + if (tran == null) { + const getNodes = (tran) => this.getNodes(order, tran); + return yield* this.db.withTransactionG(async function* (tran) { + return yield* getNodes(tran); + }); + } + + for await (const [keyPath, nodeData] of tran.iterator( + { + reverse: order !== 'asc', + valueAsBuffer: false, + }, + this.nodeGraphBucketsDbPath, + )) { + const { nodeId } = nodesUtils.parseBucketsDbKey(keyPath); + yield [nodeId, nodeData]; } } /** - * Retrieves the node Address - * @param nodeId node ID of the target node - * @returns Node Address of the target node + * Will add a node to the node graph and increment the bucket count. + * If the node already existed it will be updated. + * @param nodeId NodeId to add to the NodeGraph + * @param nodeAddress Address information to add + * @param tran */ @ready(new nodesErrors.ErrorNodeGraphNotRunning()) - public async getNode(nodeId: NodeId): Promise { - return await this._transaction(async () => { - const bucketIndex = this.getBucketIndex(nodeId); - const bucket = await this.db.get( - this.nodeGraphBucketsDbDomain, - bucketIndex, + public async setNode( + nodeId: NodeId, + nodeAddress: NodeAddress, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.setNode(nodeId, nodeAddress, tran), ); - if (bucket != null && nodeId in bucket) { - return bucket[nodeId].address; - } - return; + } + + const [bucketIndex, bucketKey] = this.bucketIndex(nodeId); + const lastUpdatedPath = [...this.nodeGraphLastUpdatedDbPath, bucketKey]; + const nodeIdKey = nodesUtils.bucketDbKey(nodeId); + const bucketPath = [...this.nodeGraphBucketsDbPath, bucketKey, nodeIdKey]; + const nodeData = await tran.get(bucketPath); + if (nodeData != null) { + this.logger.debug( + `Updating node ${nodesUtils.encodeNodeId( + nodeId, + )} in bucket ${bucketIndex}`, + ); + // If the node already exists we want to remove the old `lastUpdated` + const lastUpdatedKey = nodesUtils.lastUpdatedKey(nodeData.lastUpdated); + await tran.del([...lastUpdatedPath, lastUpdatedKey, nodeIdKey]); + } else { + this.logger.debug( + `Adding node ${nodesUtils.encodeNodeId( + nodeId, + )} to bucket ${bucketIndex}`, + ); + // It didn't exist, so we want to increment the bucket count + const count = await this.getBucketMetaProp(bucketIndex, 'count', tran); + await this.setBucketMetaProp(bucketIndex, 'count', count + 1, tran); + } + const lastUpdated = getUnixtime(); + await tran.put(bucketPath, { + address: nodeAddress, + lastUpdated, }); + const newLastUpdatedKey = nodesUtils.lastUpdatedKey(lastUpdated); + await tran.put( + [...lastUpdatedPath, newLastUpdatedKey, nodeIdKey], + nodeIdKey, + true, + ); } - /** - * Determines whether a node ID -> node address mapping exists in this node's - * node table. - * @param targetNodeId the node ID of the node to find - * @returns true if the node exists in the table, false otherwise - */ @ready(new nodesErrors.ErrorNodeGraphNotRunning()) - public async knowsNode(targetNodeId: NodeId): Promise { - return !!(await this.getNode(targetNodeId)); + public async getOldestNode( + bucketIndex: number, + limit: number = 1, + tran?: DBTransaction, + ): Promise> { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.getOldestNode(bucketIndex, limit, tran), + ); + } + const bucketKey = nodesUtils.bucketKey(bucketIndex); + // Remove the oldest entry in the bucket + const oldestNodeIds: Array = []; + for await (const [keyPath] of tran.iterator({ limit }, [ + ...this.nodeGraphLastUpdatedDbPath, + bucketKey, + ])) { + const { nodeId } = nodesUtils.parseLastUpdatedBucketDbKey(keyPath); + oldestNodeIds.push(nodeId); + } + return oldestNodeIds; } - /** - * Returns the specified bucket if it exists - * @param bucketIndex - */ @ready(new nodesErrors.ErrorNodeGraphNotRunning()) - public async getBucket(bucketIndex: number): Promise { - return await this._transaction(async () => { - const bucket = await this.db.get( - this.nodeGraphBucketsDbDomain, - lexi.pack(bucketIndex, 'hex'), + public async unsetNode(nodeId: NodeId, tran?: DBTransaction): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.unsetNode(nodeId, tran), ); - // Cast the non-primitive types correctly (ensures type safety when using them) - for (const nodeId in bucket) { - bucket[nodeId].address.host = bucket[nodeId].address.host as - | Host - | Hostname; - bucket[nodeId].address.port = bucket[nodeId].address.port as Port; - bucket[nodeId].lastUpdated = new Date(bucket[nodeId].lastUpdated); - } - return bucket; - }); + } + + const [bucketIndex, bucketKey] = this.bucketIndex(nodeId); + const bucketPath = [...this.nodeGraphBucketsDbPath, bucketKey]; + const lastUpdatedPath = [...this.nodeGraphLastUpdatedDbPath, bucketKey]; + const nodeIdKey = nodesUtils.bucketDbKey(nodeId); + const nodeData = await tran.get([...bucketPath, nodeIdKey]); + if (nodeData != null) { + this.logger.debug( + `Removing node ${nodesUtils.encodeNodeId( + nodeId, + )} from bucket ${bucketIndex}`, + ); + const count = await this.getBucketMetaProp(bucketIndex, 'count', tran); + await this.setBucketMetaProp(bucketIndex, 'count', count - 1, tran); + await tran.del([...bucketPath, nodeIdKey]); + const lastUpdatedKey = nodesUtils.lastUpdatedKey(nodeData.lastUpdated); + await tran.del([...lastUpdatedPath, lastUpdatedKey, nodeIdKey]); + } } /** - * Sets a node to the bucket database - * This may delete an existing node if the bucket is filled up + * Gets a bucket + * The bucket's node IDs is sorted lexicographically by default + * Alternatively you can acquire them sorted by lastUpdated timestamp + * or by distance to the own NodeId */ @ready(new nodesErrors.ErrorNodeGraphNotRunning()) - public async setNode( - nodeId: NodeId, - nodeAddress: NodeAddress, - ): Promise { - return await this._transaction(async () => { - const ops = await this.setNodeOps(nodeId, nodeAddress); - await this.db.batch(ops); - }); - } + public async getBucket( + bucketIndex: NodeBucketIndex, + sort: 'nodeId' | 'distance' | 'lastUpdated' = 'nodeId', + order: 'asc' | 'desc' = 'asc', + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.getBucket(bucketIndex, sort, order, tran), + ); + } - protected async setNodeOps( - nodeId: NodeId, - nodeAddress: NodeAddress, - ): Promise> { - const bucketIndex = this.getBucketIndex(nodeId); - let bucket = await this.db.get( - this.nodeGraphBucketsDbDomain, - bucketIndex, - ); - if (bucket == null) { - bucket = {}; + if (bucketIndex < 0 || bucketIndex >= this.nodeIdBits) { + throw new nodesErrors.ErrorNodeGraphBucketIndex( + `bucketIndex must be between 0 and ${this.nodeIdBits - 1} inclusive`, + ); } - bucket[nodeId] = { - address: nodeAddress, - lastUpdated: new Date(), - }; - // Perform the check on size after we add/update the node. If it's an update, - // then we don't need to perform the deletion - let bucketEntries = Object.entries(bucket); - if (bucketEntries.length > this.maxNodesPerBucket) { - const leastActive = bucketEntries.reduce((prev, curr) => { - return new Date(prev[1].lastUpdated) < new Date(curr[1].lastUpdated) - ? prev - : curr; - }); - delete bucket[leastActive[0]]; - bucketEntries = Object.entries(bucket); - // For safety, make sure that the bucket is actually at maxNodesPerBucket - if (bucketEntries.length !== this.maxNodesPerBucket) { - throw new nodesErrors.ErrorNodeGraphOversizedBucket(); + const bucketKey = nodesUtils.bucketKey(bucketIndex); + const bucket: NodeBucket = []; + if (sort === 'nodeId' || sort === 'distance') { + for await (const [key, nodeData] of tran.iterator( + { + reverse: order !== 'asc', + valueAsBuffer: false, + }, + [...this.nodeGraphBucketsDbPath, bucketKey], + )) { + const nodeId = nodesUtils.parseBucketDbKey(key[0] as Buffer); + bucket.push([nodeId, nodeData]); + } + if (sort === 'distance') { + nodesUtils.bucketSortByDistance( + bucket, + this.keyManager.getNodeId(), + order, + ); + } + } else if (sort === 'lastUpdated') { + const bucketDbIterator = tran.iterator( + { valueAsBuffer: false }, + [...this.nodeGraphBucketsDbPath, bucketKey], + ); + try { + for await (const [, nodeIdBuffer] of tran.iterator( + { + reverse: order !== 'asc', + }, + [...this.nodeGraphLastUpdatedDbPath, bucketKey], + )) { + const nodeId = IdInternal.fromBuffer(nodeIdBuffer); + bucketDbIterator.seek(nodeIdBuffer); + // @ts-ignore + // eslint-disable-next-line + const iteratorResult = await bucketDbIterator.next(); + if (iteratorResult == null) never(); + const [, nodeData] = iteratorResult; + bucket.push([nodeId, nodeData]); + } + } finally { + // @ts-ignore + await bucketDbIterator.end(); } } - return [ - { - type: 'put', - domain: this.nodeGraphBucketsDbDomain, - key: bucketIndex, - value: bucket, - }, - ]; + return bucket; } /** - * Updates an existing node - * It will update the lastUpdated time - * Optionally it can replace the NodeAddress + * Gets all buckets. + * Buckets are always sorted by `NodeBucketIndex` first + * Then secondly by the `sort` parameter + * The `order` parameter applies to both, for example possible sorts: + * NodeBucketIndex asc, NodeID asc + * NodeBucketIndex desc, NodeId desc + * NodeBucketIndex asc, distance asc + * NodeBucketIndex desc, distance desc + * NodeBucketIndex asc, lastUpdated asc + * NodeBucketIndex desc, lastUpdated desc */ @ready(new nodesErrors.ErrorNodeGraphNotRunning()) - public async updateNode( - nodeId: NodeId, - nodeAddress?: NodeAddress, - ): Promise { - return await this._transaction(async () => { - const ops = await this.updateNodeOps(nodeId, nodeAddress); - await this.db.batch(ops); - }); + public async *getBuckets( + sort: 'nodeId' | 'distance' | 'lastUpdated' = 'nodeId', + order: 'asc' | 'desc' = 'asc', + tran?: DBTransaction, + ): AsyncGenerator<[NodeBucketIndex, NodeBucket]> { + if (tran == null) { + const getBuckets = (tran) => this.getBuckets(sort, order, tran); + return yield* this.db.withTransactionG(async function* (tran) { + return yield* getBuckets(tran); + }); + } + + let bucketIndex: NodeBucketIndex | undefined = undefined; + let bucket: NodeBucket = []; + if (sort === 'nodeId' || sort === 'distance') { + for await (const [key, nodeData] of tran.iterator( + { + reverse: order !== 'asc', + valueAsBuffer: false, + }, + this.nodeGraphBucketsDbPath, + )) { + const { bucketIndex: bucketIndex_, nodeId } = + nodesUtils.parseBucketsDbKey(key); + if (bucketIndex == null) { + // First entry of the first bucket + bucketIndex = bucketIndex_; + bucket.push([nodeId, nodeData]); + } else if (bucketIndex === bucketIndex_) { + // Subsequent entries of the same bucket + bucket.push([nodeId, nodeData]); + } else if (bucketIndex !== bucketIndex_) { + // New bucket + if (sort === 'distance') { + nodesUtils.bucketSortByDistance( + bucket, + this.keyManager.getNodeId(), + order, + ); + } + yield [bucketIndex, bucket]; + bucketIndex = bucketIndex_; + bucket = [[nodeId, nodeData]]; + } + } + // Yield the last bucket if it exists + if (bucketIndex != null) { + if (sort === 'distance') { + nodesUtils.bucketSortByDistance( + bucket, + this.keyManager.getNodeId(), + order, + ); + } + yield [bucketIndex, bucket]; + } + } else if (sort === 'lastUpdated') { + const bucketsDbIterator = tran.iterator( + { valueAsBuffer: false }, + this.nodeGraphBucketsDbPath, + ); + try { + for await (const [key] of tran.iterator( + { + reverse: order !== 'asc', + }, + this.nodeGraphLastUpdatedDbPath, + )) { + const { bucketIndex: bucketIndex_, nodeId } = + nodesUtils.parseLastUpdatedBucketsDbKey(key); + bucketsDbIterator.seek([key[0], key[2]]); + // @ts-ignore + // eslint-disable-next-line + const iteratorResult = await bucketsDbIterator.next(); + if (iteratorResult == null) never(); + const [, nodeData] = iteratorResult; + if (bucketIndex == null) { + // First entry of the first bucket + bucketIndex = bucketIndex_; + bucket.push([nodeId, nodeData]); + } else if (bucketIndex === bucketIndex_) { + // Subsequent entries of the same bucket + bucket.push([nodeId, nodeData]); + } else if (bucketIndex !== bucketIndex_) { + // New bucket + yield [bucketIndex, bucket]; + bucketIndex = bucketIndex_; + bucket = [[nodeId, nodeData]]; + } + } + // Yield the last bucket if it exists + if (bucketIndex != null) { + yield [bucketIndex, bucket]; + } + } finally { + // @ts-ignore + await bucketsDbIterator.end(); + } + } } - protected async updateNodeOps( - nodeId: NodeId, - nodeAddress?: NodeAddress, - ): Promise> { - const bucketIndex = this.getBucketIndex(nodeId); - const bucket = await this.db.get( - this.nodeGraphBucketsDbDomain, - bucketIndex, - ); - const ops: Array = []; - if (bucket != null && nodeId in bucket) { - bucket[nodeId].lastUpdated = new Date(); - if (nodeAddress != null) { - bucket[nodeId].address = nodeAddress; + @ready(new nodesErrors.ErrorNodeGraphNotRunning()) + public async resetBuckets( + nodeIdOwn: NodeId, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.resetBuckets(nodeIdOwn, tran), + ); + } + + const logger = this.logger.getChild('resetBuckets'); + // Setup new space + const spaceNew = this.space === '0' ? '1' : '0'; + logger.debug('new space: ' + spaceNew); + const nodeGraphMetaDbPathNew = [...this.nodeGraphDbPath, 'meta' + spaceNew]; + const nodeGraphBucketsDbPathNew = [ + ...this.nodeGraphDbPath, + 'buckets' + spaceNew, + ]; + const nodeGraphLastUpdatedDbPathNew = [ + ...this.nodeGraphDbPath, + 'index' + spaceNew, + ]; + // Clear the new space (in case it wasn't cleaned properly last time) + await tran.clear(nodeGraphMetaDbPathNew); + await tran.clear(nodeGraphBucketsDbPathNew); + await tran.clear(nodeGraphLastUpdatedDbPathNew); + // Iterating over all entries across all buckets + + for await (const [key, nodeData] of tran.iterator( + { valueAsBuffer: false }, + this.nodeGraphBucketsDbPath, + )) { + // The key is a combined bucket key and node ID + const { bucketIndex: bucketIndexOld, nodeId } = + nodesUtils.parseBucketsDbKey(key); + const nodeIdEncoded = nodesUtils.encodeNodeId(nodeId); + const nodeIdKey = nodesUtils.bucketDbKey(nodeId); + // If the new own node ID is one of the existing node IDs, it is just dropped + // We only map to the new bucket if it isn't one of the existing node IDs + if (nodeId.equals(nodeIdOwn)) { + logger.debug( + `nodeId ${nodeIdEncoded} from bucket ${bucketIndexOld} was identical to new NodeId and was dropped.`, + ); + continue; } - ops.push({ - type: 'put', - domain: this.nodeGraphBucketsDbDomain, - key: bucketIndex, - value: bucket, - }); - } else { - throw new nodesErrors.ErrorNodeGraphNodeIdNotFound(); + const bucketIndexNew = nodesUtils.bucketIndex(nodeIdOwn, nodeId); + const bucketKeyNew = nodesUtils.bucketKey(bucketIndexNew); + const metaPathNew = [...nodeGraphMetaDbPathNew, bucketKeyNew]; + const bucketPathNew = [...nodeGraphBucketsDbPathNew, bucketKeyNew]; + const indexPathNew = [...nodeGraphLastUpdatedDbPathNew, bucketKeyNew]; + const countNew = (await tran.get([...metaPathNew, 'count'])) ?? 0; + if (countNew < this.nodeBucketLimit) { + await tran.put([...metaPathNew, 'count'], countNew + 1); + } else { + let oldestIndexKey: KeyPath | undefined = undefined; + let oldestNodeId: NodeId | undefined = undefined; + for await (const [key] of tran.iterator( + { + limit: 1, + }, + indexPathNew, + )) { + oldestIndexKey = key; + ({ nodeId: oldestNodeId } = + nodesUtils.parseLastUpdatedBucketDbKey(key)); + } + await tran.del([ + ...bucketPathNew, + nodesUtils.bucketDbKey(oldestNodeId!), + ]); + await tran.del([...indexPathNew, ...oldestIndexKey!]); + } + if (bucketIndexOld !== bucketIndexNew) { + logger.debug( + `nodeId ${nodeIdEncoded} moved ${bucketIndexOld}=>${bucketIndexNew}`, + ); + } else { + logger.debug(`nodeId ${nodeIdEncoded} unchanged ${bucketIndexOld}`); + } + await tran.put([...bucketPathNew, nodeIdKey], nodeData); + const lastUpdatedKey = nodesUtils.lastUpdatedKey(nodeData.lastUpdated); + await tran.put( + [...indexPathNew, lastUpdatedKey, nodeIdKey], + nodeIdKey, + true, + ); } - return ops; + // Swap to the new space + await tran.put([...this.nodeGraphDbPath, 'space'], spaceNew); + // Clear old space + await tran.clear(this.nodeGraphMetaDbPath); + await tran.clear(this.nodeGraphBucketsDbPath); + await tran.clear(this.nodeGraphLastUpdatedDbPath); + // Swap the spaces + this.space = spaceNew; + this.nodeGraphMetaDbPath = nodeGraphMetaDbPathNew; + this.nodeGraphBucketsDbPath = nodeGraphBucketsDbPathNew; + this.nodeGraphLastUpdatedDbPath = nodeGraphLastUpdatedDbPathNew; } - /** - * Removes a node from the bucket database - * @param nodeId - */ @ready(new nodesErrors.ErrorNodeGraphNotRunning()) - public async unsetNode(nodeId: NodeId): Promise { - return await this._transaction(async () => { - const ops = await this.unsetNodeOps(nodeId); - await this.db.batch(ops); - }); + public async getBucketMeta( + bucketIndex: NodeBucketIndex, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.getBucketMeta(bucketIndex, tran), + ); + } + + if (bucketIndex < 0 || bucketIndex >= this.nodeIdBits) { + throw new nodesErrors.ErrorNodeGraphBucketIndex( + `bucketIndex must be between 0 and ${this.nodeIdBits - 1} inclusive`, + ); + } + const metaDomain = [ + ...this.nodeGraphMetaDbPath, + nodesUtils.bucketKey(bucketIndex), + ]; + const props = await Promise.all([ + tran.get([...metaDomain, 'count']), + ]); + const [count] = props; + // Bucket meta properties have defaults + return { + count: count ?? 0, + }; } - protected async unsetNodeOps(nodeId: NodeId): Promise> { - const bucketIndex = this.getBucketIndex(nodeId); - const bucket = await this.db.get( - this.nodeGraphBucketsDbDomain, - bucketIndex, - ); - const ops: Array = []; - if (bucket == null) { - return ops; + @ready(new nodesErrors.ErrorNodeGraphNotRunning()) + public async getBucketMetaProp( + bucketIndex: NodeBucketIndex, + key: Key, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.getBucketMetaProp(bucketIndex, key, tran), + ); } - delete bucket[nodeId]; - if (Object.keys(bucket).length === 0) { - ops.push({ - type: 'del', - domain: this.nodeGraphBucketsDbDomain, - key: bucketIndex, - }); - } else { - ops.push({ - type: 'put', - domain: this.nodeGraphBucketsDbDomain, - key: bucketIndex, - value: bucket, - }); + + if (bucketIndex < 0 || bucketIndex >= this.nodeIdBits) { + throw new nodesErrors.ErrorNodeGraphBucketIndex( + `bucketIndex must be between 0 and ${this.nodeIdBits - 1} inclusive`, + ); } - return ops; + const metaDomain = [ + ...this.nodeGraphMetaDbPath, + nodesUtils.bucketKey(bucketIndex), + ]; + // Bucket meta properties have defaults + let value; + switch (key) { + case 'count': + value = (await tran.get([...metaDomain, key])) ?? 0; + break; + } + return value; } /** - * Find the correct index of the k-bucket to add a new node to (for this node's - * bucket database). Packs it as a lexicographic integer, such that the order - * of buckets in leveldb is numerical order. + * Finds the set of nodes (of size k) known by the current node (i.e. in its + * buckets' database) that have the smallest distance to the target node (i.e. + * are closest to the target node). + * i.e. FIND_NODE RPC from Kademlia spec + * + * Used by the RPC service. + * + * @param nodeId the node ID to find other nodes closest to it + * @param limit the number of the closest nodes to return (by default, returns + * according to the maximum number of nodes per bucket) + * @param tran + * @returns a mapping containing exactly k nodeIds -> nodeAddresses (unless the + * current node has less than k nodes in all of its buckets, in which case it + * returns all nodes it has knowledge of) */ - protected getBucketIndex(nodeId: NodeId): string { - const index = nodesUtils.calculateBucketIndex( + @ready(new nodesErrors.ErrorNodeGraphNotRunning()) + public async getClosestNodes( + nodeId: NodeId, + limit: number = this.nodeBucketLimit, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.getClosestNodes(nodeId, limit, tran), + ); + } + + // Buckets map to the target node in the following way; + // 1. 0, 1, ..., T-1 -> T + // 2. T -> 0, 1, ..., T-1 + // 3. T+1, T+2, ..., 255 are unchanged + // We need to obtain nodes in the following bucket order + // 1. T + // 2. iterate over 0 ---> T-1 + // 3. iterate over T+1 ---> K + // Need to work out the relevant bucket to start from + const localNodeId = this.keyManager.getNodeId(); + const startingBucket = localNodeId.equals(nodeId) + ? 0 + : nodesUtils.bucketIndex(this.keyManager.getNodeId(), nodeId); + // Getting the whole target's bucket first + const nodeIds: NodeBucket = await this.getBucket( + startingBucket, + undefined, + undefined, + tran, + ); + // We need to iterate over the key stream + // When streaming we want all nodes in the starting bucket + // The keys takes the form `!(lexpack bucketId)!(nodeId)` + // We can just use `!(lexpack bucketId)` to start from + // Less than `!(bucketId 101)!` gets us buckets 100 and lower + // greater than `!(bucketId 99)!` gets up buckets 100 and greater + if (nodeIds.length < limit) { + // Just before target bucket + const bucketIdKey = Buffer.from(nodesUtils.bucketKey(startingBucket)); + const remainingLimit = limit - nodeIds.length; + // Iterate over lower buckets + for await (const [key, nodeData] of tran.iterator( + { + lt: [bucketIdKey, ''], + limit: remainingLimit, + valueAsBuffer: false, + }, + this.nodeGraphBucketsDbPath, + )) { + const info = nodesUtils.parseBucketsDbKey(key); + nodeIds.push([info.nodeId, nodeData]); + } + } + if (nodeIds.length < limit) { + // Just after target bucket + const bucketId = Buffer.from(nodesUtils.bucketKey(startingBucket + 1)); + const remainingLimit = limit - nodeIds.length; + // Iterate over ids further away + tran.iterator( + { + gt: [bucketId, ''], + limit: remainingLimit, + }, + this.nodeGraphBucketsDbPath, + ); + for await (const [key, nodeData] of tran.iterator( + { + gt: [bucketId, ''], + limit: remainingLimit, + valueAsBuffer: false, + }, + this.nodeGraphBucketsDbPath, + )) { + const info = nodesUtils.parseBucketsDbKey(key); + nodeIds.push([info.nodeId, nodeData]); + } + } + // If no nodes were found, return nothing + if (nodeIds.length === 0) return []; + // Need to get the whole of the last bucket + const lastBucketIndex = nodesUtils.bucketIndex( this.keyManager.getNodeId(), - nodeId, + nodeIds[nodeIds.length - 1][0], ); - return lexi.pack(index, 'hex') as string; + const lastBucket = await this.getBucket( + lastBucketIndex, + undefined, + undefined, + tran, + ); + // Pop off elements of the same bucket to avoid duplicates + let element = nodeIds.pop(); + while ( + element != null && + nodesUtils.bucketIndex(this.keyManager.getNodeId(), element[0]) === + lastBucketIndex + ) { + element = nodeIds.pop(); + } + if (element != null) nodeIds.push(element); + // Adding last bucket to the list + nodeIds.push(...lastBucket); + + nodesUtils.bucketSortByDistance(nodeIds, nodeId, 'asc'); + return nodeIds.slice(0, limit); } /** - * Returns all of the buckets in an array + * Sets a bucket meta property + * This is protected because users cannot directly manipulate bucket meta */ - @ready(new nodesErrors.ErrorNodeGraphNotRunning()) - public async getAllBuckets(): Promise> { - return await this._transaction(async () => { - const buckets: Array = []; - for await (const o of this.nodeGraphBucketsDb.createReadStream()) { - const data = (o as any).value as Buffer; - const bucket = await this.db.deserializeDecrypt( - data, - false, - ); - buckets.push(bucket); - } - return buckets; - }); + protected async setBucketMetaProp( + bucketIndex: NodeBucketIndex, + key: Key, + value: NodeBucketMeta[Key], + tran: DBTransaction, + ): Promise { + const metaKey = [ + ...this.nodeGraphMetaDbPath, + nodesUtils.bucketKey(bucketIndex), + key, + ]; + await tran.put(metaKey, value); + return; } /** - * To be called on key renewal. Re-orders all nodes in all buckets with respect - * to the new node ID. - * NOTE: original nodes may be lost in this process. If they're redistributed - * to a newly full bucket, the least active nodes in the newly full bucket - * will be removed. + * Derive the bucket index of the k-buckets from the new `NodeId` + * The bucket key is the string encoded version of bucket index + * that preserves lexicographic order */ - @ready(new nodesErrors.ErrorNodeGraphNotRunning()) - public async refreshBuckets(): Promise { - return await this._transaction(async () => { - const ops: Array = []; - // Get a local copy of all the buckets - const buckets = await this.getAllBuckets(); - // Wrap as a batch operation. We want to rollback if we encounter any - // errors (such that we don't clear the DB without re-adding the nodes) - // 1. Delete every bucket - for await (const k of this.nodeGraphBucketsDb.createKeyStream()) { - const hexBucketIndex = k as string; - ops.push({ - type: 'del', - domain: this.nodeGraphBucketsDbDomain, - key: hexBucketIndex, - }); - } - const tempBuckets: Record = {}; - // 2. Re-add all the nodes from all buckets - for (const b of buckets) { - for (const n of Object.keys(b)) { - const nodeId = IdInternal.fromString(n); - const newIndex = this.getBucketIndex(nodeId); - let expectedBucket = tempBuckets[newIndex]; - // The following is more or less copied from setNodeOps - if (expectedBucket == null) { - expectedBucket = {}; - } - const bucketEntries = Object.entries(expectedBucket); - // Add the old node - expectedBucket[nodeId] = { - address: b[nodeId].address, - lastUpdated: b[nodeId].lastUpdated, - }; - // If, with the old node added, we exceed the limit - if (bucketEntries.length > this.maxNodesPerBucket) { - // Then, with the old node added, find the least active and remove - const leastActive = bucketEntries.reduce((prev, curr) => { - return prev[1].lastUpdated < curr[1].lastUpdated ? prev : curr; - }); - delete expectedBucket[leastActive[0]]; - } - // Add this reconstructed bucket (with old node) into the temp storage - tempBuckets[newIndex] = expectedBucket; - } - } - // Now that we've reconstructed all the buckets, perform batch operations - // on a bucket level (i.e. per bucket, instead of per node) - for (const bucketIndex in tempBuckets) { - ops.push({ - type: 'put', - domain: this.nodeGraphBucketsDbDomain, - key: bucketIndex, - value: tempBuckets[bucketIndex], - }); - } - await this.db.batch(ops); - }); + public bucketIndex(nodeId: NodeId): [NodeBucketIndex, string] { + const nodeIdOwn = this.keyManager.getNodeId(); + if (nodeId.equals(nodeIdOwn)) { + throw new nodesErrors.ErrorNodeGraphSameNodeId(); + } + const bucketIndex = nodesUtils.bucketIndex(nodeIdOwn, nodeId); + const bucketKey = nodesUtils.bucketKey(bucketIndex); + return [bucketIndex, bucketKey]; } } diff --git a/src/nodes/NodeManager.ts b/src/nodes/NodeManager.ts index b28343667..7245ab5c4 100644 --- a/src/nodes/NodeManager.ts +++ b/src/nodes/NodeManager.ts @@ -1,23 +1,34 @@ -import type { DB } from '@matrixai/db'; +import type { DB, DBTransaction } from '@matrixai/db'; import type NodeConnectionManager from './NodeConnectionManager'; import type NodeGraph from './NodeGraph'; +import type Queue from './Queue'; import type KeyManager from '../keys/KeyManager'; import type { PublicKeyPem } from '../keys/types'; import type Sigchain from '../sigchain/Sigchain'; import type { ChainData, ChainDataEncoded } from '../sigchain/types'; -import type { NodeId, NodeAddress, NodeBucket } from '../nodes/types'; +import type { + NodeId, + NodeAddress, + NodeBucket, + NodeBucketIndex, +} from '../nodes/types'; import type { ClaimEncoded } from '../claims/types'; +import type { Timer } from '../types'; +import type { PromiseDeconstructed } from '../utils/utils'; import Logger from '@matrixai/logger'; +import { StartStop, ready } from '@matrixai/async-init/dist/StartStop'; import * as nodesErrors from './errors'; import * as nodesUtils from './utils'; -import { utils as validationUtils } from '../validation'; +import * as networkUtils from '../network/utils'; +import * as validationUtils from '../validation/utils'; import * as utilsPB from '../proto/js/polykey/v1/utils/utils_pb'; import * as claimsErrors from '../claims/errors'; -import * as networkErrors from '../network/errors'; -import * as networkUtils from '../network/utils'; import * as sigchainUtils from '../sigchain/utils'; import * as claimsUtils from '../claims/utils'; +import { promise, timerStart } from '../utils/utils'; +interface NodeManager extends StartStop {} +@StartStop() class NodeManager { protected db: DB; protected logger: Logger; @@ -25,6 +36,19 @@ class NodeManager { protected keyManager: KeyManager; protected nodeConnectionManager: NodeConnectionManager; protected nodeGraph: NodeGraph; + protected queue: Queue; + // Refresh bucket timer + protected refreshBucketDeadlineMap: Map = new Map(); + protected refreshBucketTimer: NodeJS.Timer; + protected refreshBucketNext: NodeBucketIndex; + public readonly refreshBucketTimerDefault; + protected refreshBucketQueue: Set = new Set(); + protected refreshBucketQueueRunning: boolean = false; + protected refreshBucketQueueRunner: Promise; + protected refreshBucketQueuePlug_: PromiseDeconstructed = promise(); + protected refreshBucketQueueDrained_: PromiseDeconstructed = promise(); + protected refreshBucketQueuePause_: PromiseDeconstructed = promise(); + protected refreshBucketQueueAbortController: AbortController; constructor({ db, @@ -32,6 +56,8 @@ class NodeManager { sigchain, nodeConnectionManager, nodeGraph, + queue, + refreshBucketTimerDefault = 3600000, // 1 hour in milliseconds logger, }: { db: DB; @@ -39,6 +65,8 @@ class NodeManager { sigchain: Sigchain; nodeConnectionManager: NodeConnectionManager; nodeGraph: NodeGraph; + queue: Queue; + refreshBucketTimerDefault?: number; logger?: Logger; }) { this.logger = logger ?? new Logger(this.constructor.name); @@ -47,32 +75,50 @@ class NodeManager { this.sigchain = sigchain; this.nodeConnectionManager = nodeConnectionManager; this.nodeGraph = nodeGraph; + this.queue = queue; + this.refreshBucketTimerDefault = refreshBucketTimerDefault; + } + + public async start() { + this.logger.info(`Starting ${this.constructor.name}`); + this.startRefreshBucketTimers(); + this.refreshBucketQueueRunner = this.startRefreshBucketQueue(); + this.logger.info(`Started ${this.constructor.name}`); + } + + public async stop() { + this.logger.info(`Stopping ${this.constructor.name}`); + await this.stopRefreshBucketTimers(); + await this.stopRefreshBucketQueue(); + this.logger.info(`Stopped ${this.constructor.name}`); } /** * Determines whether a node in the Polykey network is online. * @return true if online, false if offline + * @param nodeId - NodeId of the node we're pinging + * @param address - Optional Host and Port we want to ping + * @param timer Connection timeout timer */ - public async pingNode(targetNodeId: NodeId): Promise { - const targetAddress: NodeAddress = - await this.nodeConnectionManager.findNode(targetNodeId); - try { - // Attempt to open a connection via the forward proxy - // i.e. no NodeConnection object created (no need for GRPCClient) - await this.nodeConnectionManager.holePunchForward( - targetNodeId, - await networkUtils.resolveHost(targetAddress.host), - targetAddress.port, - ); - } catch (e) { - // If the connection request times out, then return false - if (e instanceof networkErrors.ErrorConnectionStart) { - return false; - } - // Throw any other error back up the callstack - throw e; + public async pingNode( + nodeId: NodeId, + address?: NodeAddress, + timer?: Timer, + ): Promise { + // We need to attempt a connection using the proxies + // For now we will just do a forward connect + relay message + const targetAddress = + address ?? (await this.nodeConnectionManager.findNode(nodeId)); + if (targetAddress == null) { + throw new nodesErrors.ErrorNodeGraphNodeIdNotFound(); } - return true; + const targetHost = await networkUtils.resolveHost(targetAddress.host); + return await this.nodeConnectionManager.pingNode( + nodeId, + targetHost, + targetAddress.port, + timer, + ); } /** @@ -182,189 +228,514 @@ class NodeManager { * Call this function upon receiving a "claim node request" notification from * another node. */ - public async claimNode(targetNodeId: NodeId): Promise { - await this.sigchain.transaction(async (sigchain) => { - // 2. Create your intermediary claim - const singlySignedClaim = await sigchain.createIntermediaryClaim({ + public async claimNode( + targetNodeId: NodeId, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => { + return this.claimNode(targetNodeId, tran); + }); + } + + // 2. Create your intermediary claim + const singlySignedClaim = await this.sigchain.createIntermediaryClaim( + { type: 'node', node1: nodesUtils.encodeNodeId(this.keyManager.getNodeId()), node2: nodesUtils.encodeNodeId(targetNodeId), - }); - let doublySignedClaim: ClaimEncoded; - await this.nodeConnectionManager.withConnF( - targetNodeId, - async (connection) => { - const client = connection.getClient(); - const genClaims = client.nodesCrossSignClaim(); - try { - // 2. Set up the intermediary claim message (the singly signed claim) to send - const crossSignMessage = claimsUtils.createCrossSignMessage({ - singlySignedClaim: singlySignedClaim, - }); - await genClaims.write(crossSignMessage); // Get the generator here - // 3. We expect to receieve our singly signed claim we sent to now be a - // doubly signed claim (signed by the other node), as well as a singly - // signed claim to be signed by us - const readStatus = await genClaims.read(); - // If nothing to read, end and destroy - if (readStatus.done) { - throw new claimsErrors.ErrorEmptyStream(); - } - const receivedMessage = readStatus.value; - const intermediaryClaimMessage = - receivedMessage.getSinglySignedClaim(); - const doublySignedClaimMessage = - receivedMessage.getDoublySignedClaim(); - // Ensure all of our expected messages are defined - if (!intermediaryClaimMessage) { - throw new claimsErrors.ErrorUndefinedSinglySignedClaim(); - } - const intermediaryClaimSignature = - intermediaryClaimMessage.getSignature(); - if (!intermediaryClaimSignature) { - throw new claimsErrors.ErrorUndefinedSignature(); - } - if (!doublySignedClaimMessage) { - throw new claimsErrors.ErrorUndefinedDoublySignedClaim(); - } - // Reconstruct the expected objects from the messages - const constructedIntermediaryClaim = - claimsUtils.reconstructClaimIntermediary( - intermediaryClaimMessage, - ); - const constructedDoublySignedClaim = - claimsUtils.reconstructClaimEncoded(doublySignedClaimMessage); - // Verify the singly signed claim with the sender's public key - const senderPublicKey = - connection.getExpectedPublicKey(targetNodeId); - if (!senderPublicKey) { - throw new nodesErrors.ErrorNodeConnectionPublicKeyNotFound(); - } - const verifiedSingly = - await claimsUtils.verifyIntermediaryClaimSignature( - constructedIntermediaryClaim, - senderPublicKey, - ); - if (!verifiedSingly) { - throw new claimsErrors.ErrorSinglySignedClaimVerificationFailed(); - } - // Verify the doubly signed claim with both our public key, and the sender's - const verifiedDoubly = - (await claimsUtils.verifyClaimSignature( - constructedDoublySignedClaim, - this.keyManager.getRootKeyPairPem().publicKey, - )) && - (await claimsUtils.verifyClaimSignature( - constructedDoublySignedClaim, - senderPublicKey, - )); - if (!verifiedDoubly) { - throw new claimsErrors.ErrorDoublySignedClaimVerificationFailed(); - } - // 4. X <- responds with double signing the X signed claim <- Y - const doublySignedClaimResponse = - await claimsUtils.signIntermediaryClaim({ - claim: constructedIntermediaryClaim, - privateKey: this.keyManager.getRootKeyPairPem().privateKey, - signeeNodeId: nodesUtils.encodeNodeId( - this.keyManager.getNodeId(), - ), - }); - // Should never be reached, but just for type safety - if (!doublySignedClaimResponse.payload) { - throw new claimsErrors.ErrorClaimsUndefinedClaimPayload(); - } - const crossSignMessageResponse = claimsUtils.createCrossSignMessage( - { - doublySignedClaim: doublySignedClaimResponse, - }, + }, + tran, + ); + let doublySignedClaim: ClaimEncoded; + await this.nodeConnectionManager.withConnF( + targetNodeId, + async (connection) => { + const client = connection.getClient(); + const genClaims = client.nodesCrossSignClaim(); + try { + // 2. Set up the intermediary claim message (the singly signed claim) to send + const crossSignMessage = claimsUtils.createCrossSignMessage({ + singlySignedClaim: singlySignedClaim, + }); + await genClaims.write(crossSignMessage); // Get the generator here + // 3. We expect to receive our singly signed claim we sent to now be a + // doubly signed claim (signed by the other node), as well as a singly + // signed claim to be signed by us + const readStatus = await genClaims.read(); + // If nothing to read, end and destroy + if (readStatus.done) { + throw new claimsErrors.ErrorEmptyStream(); + } + const receivedMessage = readStatus.value; + const intermediaryClaimMessage = + receivedMessage.getSinglySignedClaim(); + const doublySignedClaimMessage = + receivedMessage.getDoublySignedClaim(); + // Ensure all of our expected messages are defined + if (!intermediaryClaimMessage) { + throw new claimsErrors.ErrorUndefinedSinglySignedClaim(); + } + const intermediaryClaimSignature = + intermediaryClaimMessage.getSignature(); + if (!intermediaryClaimSignature) { + throw new claimsErrors.ErrorUndefinedSignature(); + } + if (!doublySignedClaimMessage) { + throw new claimsErrors.ErrorUndefinedDoublySignedClaim(); + } + // Reconstruct the expected objects from the messages + const constructedIntermediaryClaim = + claimsUtils.reconstructClaimIntermediary(intermediaryClaimMessage); + const constructedDoublySignedClaim = + claimsUtils.reconstructClaimEncoded(doublySignedClaimMessage); + // Verify the singly signed claim with the sender's public key + const senderPublicKey = connection.getExpectedPublicKey(targetNodeId); + if (!senderPublicKey) { + throw new nodesErrors.ErrorNodeConnectionPublicKeyNotFound(); + } + const verifiedSingly = + await claimsUtils.verifyIntermediaryClaimSignature( + constructedIntermediaryClaim, + senderPublicKey, ); - await genClaims.write(crossSignMessageResponse); - - // Check the stream is closed (should be closed by other side) - const finalResponse = await genClaims.read(); - if (finalResponse.done != null) { - await genClaims.next(null); - } + if (!verifiedSingly) { + throw new claimsErrors.ErrorSinglySignedClaimVerificationFailed(); + } + // Verify the doubly signed claim with both our public key, and the sender's + const verifiedDoubly = + (await claimsUtils.verifyClaimSignature( + constructedDoublySignedClaim, + this.keyManager.getRootKeyPairPem().publicKey, + )) && + (await claimsUtils.verifyClaimSignature( + constructedDoublySignedClaim, + senderPublicKey, + )); + if (!verifiedDoubly) { + throw new claimsErrors.ErrorDoublySignedClaimVerificationFailed(); + } + // 4. X <- responds with double signing the X signed claim <- Y + const doublySignedClaimResponse = + await claimsUtils.signIntermediaryClaim({ + claim: constructedIntermediaryClaim, + privateKey: this.keyManager.getRootKeyPairPem().privateKey, + signeeNodeId: nodesUtils.encodeNodeId( + this.keyManager.getNodeId(), + ), + }); + // Should never be reached, but just for type safety + if (!doublySignedClaimResponse.payload) { + throw new claimsErrors.ErrorClaimsUndefinedClaimPayload(); + } + const crossSignMessageResponse = claimsUtils.createCrossSignMessage({ + doublySignedClaim: doublySignedClaimResponse, + }); + await genClaims.write(crossSignMessageResponse); - doublySignedClaim = constructedDoublySignedClaim; - } catch (e) { - await genClaims.throw(e); - throw e; + // Check the stream is closed (should be closed by other side) + const finalResponse = await genClaims.read(); + if (finalResponse.done != null) { + await genClaims.next(null); } - await sigchain.addExistingClaim(doublySignedClaim); - }, - ); - }); + + doublySignedClaim = constructedDoublySignedClaim; + } catch (e) { + await genClaims.throw(e); + throw e; + } + await this.sigchain.addExistingClaim(doublySignedClaim, tran); + }, + ); } /** * Retrieves the node Address from the NodeGraph * @param nodeId node ID of the target node + * @param tran * @returns Node Address of the target node */ public async getNodeAddress( nodeId: NodeId, + tran: DBTransaction, ): Promise { - return await this.nodeGraph.getNode(nodeId); + return (await this.nodeGraph.getNode(nodeId, tran))?.address; } /** * Determines whether a node ID -> node address mapping exists in the NodeGraph * @param targetNodeId the node ID of the node to find + * @param tran * @returns true if the node exists in the table, false otherwise */ - public async knowsNode(targetNodeId: NodeId): Promise { - return await this.nodeGraph.knowsNode(targetNodeId); + public async knowsNode( + targetNodeId: NodeId, + tran: DBTransaction, + ): Promise { + return (await this.nodeGraph.getNode(targetNodeId, tran)) != null; } /** * Gets the specified bucket from the NodeGraph */ - public async getBucket(bucketIndex: number): Promise { - return await this.nodeGraph.getBucket(bucketIndex); + public async getBucket( + bucketIndex: number, + tran?: DBTransaction, + ): Promise { + return await this.nodeGraph.getBucket( + bucketIndex, + undefined, + undefined, + tran, + ); } /** - * Sets a node in the NodeGraph + * Adds a node to the node graph. This assumes that you have already authenticated the node + * Updates the node if the node already exists + * This operation is blocking by default - set `block` 2qto false to make it non-blocking + * @param nodeId - Id of the node we wish to add + * @param nodeAddress - Expected address of the node we want to add + * @param block - Flag for if the operation should block or utilize the async queue + * @param force - Flag for if we want to add the node without authenticating or if the bucket is full. + * This will drop the oldest node in favor of the new. + * @param timeout Connection timeout + * @param tran */ + @ready(new nodesErrors.ErrorNodeManagerNotRunning()) public async setNode( nodeId: NodeId, nodeAddress: NodeAddress, + block: boolean = true, + force: boolean = false, + timeout?: number, + tran?: DBTransaction, ): Promise { - return await this.nodeGraph.setNode(nodeId, nodeAddress); + // We don't want to add our own node + if (nodeId.equals(this.keyManager.getNodeId())) { + this.logger.debug('Is own NodeId, skipping'); + return; + } + + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.setNode(nodeId, nodeAddress, block, force, timeout, tran), + ); + } + + // When adding a node we need to handle 3 cases + // 1. The node already exists. We need to update it's last updated field + // 2. The node doesn't exist and bucket has room. + // We need to add the node to the bucket + // 3. The node doesn't exist and the bucket is full. + // We need to ping the oldest node. If the ping succeeds we need to update + // the lastUpdated of the oldest node and drop the new one. If the ping + // fails we delete the old node and add in the new one. + const nodeData = await this.nodeGraph.getNode(nodeId, tran); + // If this is a new entry, check the bucket limit + const [bucketIndex] = this.nodeGraph.bucketIndex(nodeId); + const count = await this.nodeGraph.getBucketMetaProp( + bucketIndex, + 'count', + tran, + ); + if (nodeData != null || count < this.nodeGraph.nodeBucketLimit) { + // Either already exists or has room in the bucket + // We want to add or update the node + await this.nodeGraph.setNode(nodeId, nodeAddress, tran); + // Updating the refreshBucket timer + this.refreshBucketUpdateDeadline(bucketIndex); + } else { + // We want to add a node but the bucket is full + // We need to ping the oldest node + if (force) { + // We just add the new node anyway without checking the old one + const oldNodeId = ( + await this.nodeGraph.getOldestNode(bucketIndex, 1, tran) + ).pop()!; + this.logger.debug( + `Force was set, removing ${nodesUtils.encodeNodeId( + oldNodeId, + )} and adding ${nodesUtils.encodeNodeId(nodeId)}`, + ); + await this.nodeGraph.unsetNode(oldNodeId, tran); + await this.nodeGraph.setNode(nodeId, nodeAddress, tran); + // Updating the refreshBucket timer + this.refreshBucketUpdateDeadline(bucketIndex); + return; + } else if (block) { + this.logger.debug( + `Bucket was full and blocking was true, garbage collecting old nodes to add ${nodesUtils.encodeNodeId( + nodeId, + )}`, + ); + await this.garbageCollectOldNode( + bucketIndex, + nodeId, + nodeAddress, + timeout, + ); + } else { + this.logger.debug( + `Bucket was full and blocking was false, adding ${nodesUtils.encodeNodeId( + nodeId, + )} to queue`, + ); + // Re-attempt this later asynchronously by adding the the queue + this.queue.push(() => + this.setNode(nodeId, nodeAddress, true, false, timeout), + ); + } + } } - /** - * Updates the node in the NodeGraph - */ - public async updateNode( + private async garbageCollectOldNode( + bucketIndex: number, nodeId: NodeId, - nodeAddress?: NodeAddress, - ): Promise { - return await this.nodeGraph.updateNode(nodeId, nodeAddress); + nodeAddress: NodeAddress, + timeout?: number, + ) { + const oldestNodeIds = await this.nodeGraph.getOldestNode(bucketIndex, 3); + // We want to concurrently ping the nodes + const pingPromises = oldestNodeIds.map((nodeId) => { + const doPing = async (): Promise<{ + nodeId: NodeId; + success: boolean; + }> => { + // This needs to return nodeId and ping result + const data = await this.nodeGraph.getNode(nodeId); + if (data == null) return { nodeId, success: false }; + const timer = timeout != null ? timerStart(timeout) : undefined; + const result = await this.pingNode(nodeId, nodeAddress, timer); + return { nodeId, success: result }; + }; + return doPing(); + }); + const pingResults = await Promise.all(pingPromises); + for (const { nodeId, success } of pingResults) { + if (success) { + // Ping succeeded, update the node + this.logger.debug( + `Ping succeeded for ${nodesUtils.encodeNodeId(nodeId)}`, + ); + const node = (await this.nodeGraph.getNode(nodeId))!; + await this.nodeGraph.setNode(nodeId, node.address); + // Updating the refreshBucket timer + this.refreshBucketUpdateDeadline(bucketIndex); + } else { + this.logger.debug(`Ping failed for ${nodesUtils.encodeNodeId(nodeId)}`); + // Otherwise we remove the node + await this.nodeGraph.unsetNode(nodeId); + } + } + // Check if we now have room and add the new node + const count = await this.nodeGraph.getBucketMetaProp(bucketIndex, 'count'); + if (count < this.nodeGraph.nodeBucketLimit) { + this.logger.debug(`Bucket ${bucketIndex} now has room, adding new node`); + await this.nodeGraph.setNode(nodeId, nodeAddress); + // Updating the refreshBucket timer + this.refreshBucketUpdateDeadline(bucketIndex); + } } /** * Removes a node from the NodeGraph */ - public async unsetNode(nodeId: NodeId): Promise { - return await this.nodeGraph.unsetNode(nodeId); + public async unsetNode(nodeId: NodeId, tran: DBTransaction): Promise { + return await this.nodeGraph.unsetNode(nodeId, tran); } /** - * Gets all buckets from the NodeGraph + * To be called on key renewal. Re-orders all nodes in all buckets with respect + * to the new node ID. */ - public async getAllBuckets(): Promise> { - return await this.nodeGraph.getAllBuckets(); + public async resetBuckets(): Promise { + return await this.nodeGraph.resetBuckets(this.keyManager.getNodeId()); } /** - * To be called on key renewal. Re-orders all nodes in all buckets with respect - * to the new node ID. + * Kademlia refresh bucket operation. + * It picks a random node within a bucket and does a search for that node. + * Connections during the search will will share node information with other + * nodes. + * @param bucketIndex + * @param options */ - public async refreshBuckets(): Promise { - return await this.nodeGraph.refreshBuckets(); + public async refreshBucket( + bucketIndex: NodeBucketIndex, + options: { signal?: AbortSignal } = {}, + ) { + const { signal } = { ...options }; + // We need to generate a random nodeId for this bucket + const nodeId = this.keyManager.getNodeId(); + const bucketRandomNodeId = nodesUtils.generateRandomNodeIdForBucket( + nodeId, + bucketIndex, + ); + // We then need to start a findNode procedure + await this.nodeConnectionManager.findNode(bucketRandomNodeId, { signal }); + } + + // Refresh bucket activity timer methods + + private startRefreshBucketTimers() { + // Setting initial bucket to refresh + this.refreshBucketNext = 0; + // Setting initial deadline + this.refreshBucketTimerReset(this.refreshBucketTimerDefault); + + for ( + let bucketIndex = 0; + bucketIndex < this.nodeGraph.nodeIdBits; + bucketIndex++ + ) { + const deadline = Date.now() + this.refreshBucketTimerDefault; + this.refreshBucketDeadlineMap.set(bucketIndex, deadline); + } + } + + private async stopRefreshBucketTimers() { + clearTimeout(this.refreshBucketTimer); + } + + private refreshBucketTimerReset(timeout: number) { + clearTimeout(this.refreshBucketTimer); + this.refreshBucketTimer = setTimeout(() => { + this.refreshBucketRefreshTimer(); + }, timeout); + } + + public refreshBucketUpdateDeadline(bucketIndex: NodeBucketIndex) { + // Update the map deadline + this.refreshBucketDeadlineMap.set( + bucketIndex, + Date.now() + this.refreshBucketTimerDefault, + ); + // If the bucket was pending a refresh we remove it + this.refreshBucketQueueRemove(bucketIndex); + if (bucketIndex === this.refreshBucketNext) { + // Bucket is same as next bucket, this affects the timer + this.refreshBucketRefreshTimer(); + } + } + + private refreshBucketRefreshTimer() { + // Getting new closest deadline + let closestBucket = this.refreshBucketNext; + let closestDeadline = Date.now() + this.refreshBucketTimerDefault; + const now = Date.now(); + for (const [bucketIndex, deadline] of this.refreshBucketDeadlineMap) { + // Skip any queued buckets marked by 0 deadline + if (deadline === 0) continue; + if (deadline <= now) { + // Deadline for this has already passed, we add it to the queue + this.refreshBucketQueueAdd(bucketIndex); + continue; + } + if (deadline < closestDeadline) { + closestBucket = bucketIndex; + closestDeadline = deadline; + } + } + // Working out time left + const timeout = closestDeadline - Date.now(); + this.logger.debug( + `Refreshing refreshBucket timer with new timeout ${timeout}`, + ); + // Updating timer and next + this.refreshBucketNext = closestBucket; + this.refreshBucketTimerReset(timeout); + } + + // Refresh bucket async queue methods + + public refreshBucketQueueAdd(bucketIndex: NodeBucketIndex) { + this.logger.debug(`Adding bucket ${bucketIndex} to queue`); + this.refreshBucketDeadlineMap.set(bucketIndex, 0); + this.refreshBucketQueue.add(bucketIndex); + this.refreshBucketQueueUnplug(); + } + + public refreshBucketQueueRemove(bucketIndex: NodeBucketIndex) { + this.logger.debug(`Removing bucket ${bucketIndex} from queue`); + this.refreshBucketQueue.delete(bucketIndex); + } + + public async refreshBucketQueueDrained() { + await this.refreshBucketQueueDrained_.p; + } + + public refreshBucketQueuePause() { + this.logger.debug('Pausing refreshBucketQueue'); + this.refreshBucketQueuePause_ = promise(); + } + + public refreshBucketQueueResume() { + this.logger.debug('Resuming refreshBucketQueue'); + this.refreshBucketQueuePause_.resolveP(); + } + + private async startRefreshBucketQueue(): Promise { + this.refreshBucketQueueRunning = true; + this.refreshBucketQueuePlug(); + this.refreshBucketQueueResume(); + let iterator: IterableIterator | undefined; + this.refreshBucketQueueAbortController = new AbortController(); + const pace = async () => { + // Wait if paused + await this.refreshBucketQueuePause_.p; + // Wait for plug + await this.refreshBucketQueuePlug_.p; + if (iterator == null) { + iterator = this.refreshBucketQueue[Symbol.iterator](); + } + return this.refreshBucketQueueRunning; + }; + while (await pace()) { + const bucketIndex: NodeBucketIndex = iterator?.next().value; + if (bucketIndex == null) { + // Iterator is empty, plug and continue + iterator = undefined; + this.refreshBucketQueuePlug(); + continue; + } + // Do the job + this.logger.debug( + `processing refreshBucket for bucket ${bucketIndex}, ${this.refreshBucketQueue.size} left in queue`, + ); + try { + await this.refreshBucket(bucketIndex, { + signal: this.refreshBucketQueueAbortController.signal, + }); + } catch (e) { + if (e instanceof nodesErrors.ErrorNodeAborted) break; + throw e; + } + // Remove from queue and update bucket deadline + this.refreshBucketQueue.delete(bucketIndex); + this.refreshBucketUpdateDeadline(bucketIndex); + } + this.logger.debug('startRefreshBucketQueue has ended'); + } + + private async stopRefreshBucketQueue(): Promise { + // Flag end and await queue finish + this.refreshBucketQueueAbortController.abort(); + this.refreshBucketQueueRunning = false; + this.refreshBucketQueueUnplug(); + this.refreshBucketQueueResume(); + } + + private refreshBucketQueuePlug() { + this.logger.debug('refresh bucket queue has plugged'); + this.refreshBucketQueuePlug_ = promise(); + this.refreshBucketQueueDrained_?.resolveP(); + } + + private refreshBucketQueueUnplug() { + this.logger.debug('refresh bucket queue has unplugged'); + this.refreshBucketQueueDrained_ = promise(); + this.refreshBucketQueuePlug_?.resolveP(); } } diff --git a/src/nodes/Queue.ts b/src/nodes/Queue.ts new file mode 100644 index 000000000..602efd5ae --- /dev/null +++ b/src/nodes/Queue.ts @@ -0,0 +1,91 @@ +import type { PromiseDeconstructed } from '../utils'; +import Logger from '@matrixai/logger'; +import { StartStop, ready } from '@matrixai/async-init/dist/StartStop'; +import * as nodesErrors from './errors'; +import { promise } from '../utils'; + +interface Queue extends StartStop {} +@StartStop() +class Queue { + protected logger: Logger; + protected end: boolean = false; + protected queue: Array<() => Promise> = []; + protected runner: Promise; + protected plug_: PromiseDeconstructed = promise(); + protected drained_: PromiseDeconstructed = promise(); + + constructor({ logger }: { logger?: Logger }) { + this.logger = logger ?? new Logger(this.constructor.name); + } + + public async start() { + this.logger.info(`Starting ${this.constructor.name}`); + const start = async () => { + this.logger.debug('Starting queue'); + this.plug(); + const pace = async () => { + await this.plug_.p; + return !this.end; + }; + // While queue hasn't ended + while (await pace()) { + const job = this.queue.shift(); + if (job == null) { + // If the queue is empty then we pause the queue + this.plug(); + continue; + } + try { + await job(); + } catch (e) { + if (!(e instanceof nodesErrors.ErrorNodeGraphSameNodeId)) throw e; + } + } + this.logger.debug('queue has ended'); + }; + this.runner = start(); + this.logger.info(`Started ${this.constructor.name}`); + } + + public async stop() { + this.logger.info(`Stopping ${this.constructor.name}`); + this.logger.debug('Stopping queue'); + // Tell the queue runner to end + this.end = true; + this.unplug(); + // Wait for runner to finish it's current job + await this.runner; + this.logger.info(`Stopped ${this.constructor.name}`); + } + + /** + * This adds a setNode operation to the queue + */ + public push(f: () => Promise): void { + this.queue.push(f); + this.unplug(); + } + + @ready(new nodesErrors.ErrorQueueNotRunning()) + public async drained(): Promise { + await this.drained_.p; + } + + private plug(): void { + this.logger.debug('Plugging queue'); + // Pausing queue + this.plug_ = promise(); + // Signaling queue is empty + this.drained_.resolveP(); + } + + private unplug(): void { + this.logger.debug('Unplugging queue'); + // Starting queue + this.plug_.resolveP(); + // Signalling queue is running + this.drained_ = promise(); + } +} + +export default Queue; diff --git a/src/nodes/errors.ts b/src/nodes/errors.ts index a7074ae41..bc0185025 100644 --- a/src/nodes/errors.ts +++ b/src/nodes/errors.ts @@ -1,74 +1,102 @@ import { ErrorPolykey, sysexits } from '../errors'; -class ErrorNodes extends ErrorPolykey {} +class ErrorNodes extends ErrorPolykey {} -class ErrorNodeGraphRunning extends ErrorNodes { - description = 'NodeGraph is running'; +class ErrorNodeAborted extends ErrorNodes { + static description = 'Operation was aborted'; exitCode = sysexits.USAGE; } -class ErrorNodeGraphNotRunning extends ErrorNodes { - description = 'NodeGraph is not running'; +class ErrorNodeManagerNotRunning extends ErrorNodes { + static description = 'NodeManager is not running'; exitCode = sysexits.USAGE; } -class ErrorNodeGraphDestroyed extends ErrorNodes { - description = 'NodeGraph is destroyed'; +class ErrorQueueNotRunning extends ErrorNodes { + static description = 'queue is not running'; exitCode = sysexits.USAGE; } -class ErrorNodeGraphNodeIdNotFound extends ErrorNodes { - description = 'Could not find NodeId'; +class ErrorNodeGraphRunning extends ErrorNodes { + static description = 'NodeGraph is running'; + exitCode = sysexits.USAGE; +} + +class ErrorNodeGraphNotRunning extends ErrorNodes { + static description = 'NodeGraph is not running'; + exitCode = sysexits.USAGE; +} + +class ErrorNodeGraphDestroyed extends ErrorNodes { + static description = 'NodeGraph is destroyed'; + exitCode = sysexits.USAGE; +} + +class ErrorNodeGraphNodeIdNotFound extends ErrorNodes { + static description = 'Could not find NodeId'; exitCode = sysexits.NOUSER; } -class ErrorNodeGraphEmptyDatabase extends ErrorNodes { - description = 'NodeGraph database was empty'; +class ErrorNodeGraphEmptyDatabase extends ErrorNodes { + static description = 'NodeGraph database was empty'; + exitCode = sysexits.USAGE; +} + +class ErrorNodeGraphOversizedBucket extends ErrorNodes { + static description: 'Bucket invalidly contains more nodes than capacity'; exitCode = sysexits.USAGE; } -class ErrorNodeGraphOversizedBucket extends ErrorNodes { - description: 'Bucket invalidly contains more nodes than capacity'; +class ErrorNodeGraphSameNodeId extends ErrorNodes { + static description: 'NodeId must be different for valid bucket calculation'; exitCode = sysexits.USAGE; } -class ErrorNodeGraphSameNodeId extends ErrorNodes { - description: 'NodeId must be different for valid bucket calculation'; +class ErrorNodeGraphBucketIndex extends ErrorNodes { + static description: 'Bucket index is out of range'; exitCode = sysexits.USAGE; } -class ErrorNodeConnectionDestroyed extends ErrorNodes { - description = 'NodeConnection is destroyed'; +class ErrorNodeConnectionDestroyed extends ErrorNodes { + static description = 'NodeConnection is destroyed'; exitCode = sysexits.USAGE; } -class ErrorNodeConnectionTimeout extends ErrorNodes { - description: 'A node connection could not be established (timed out)'; +class ErrorNodeConnectionTimeout extends ErrorNodes { + static description: 'A node connection could not be established (timed out)'; exitCode = sysexits.UNAVAILABLE; } -class ErrorNodeConnectionInfoNotExist extends ErrorNodes { - description: 'NodeConnection info was not found'; +class ErrorNodeConnectionInfoNotExist extends ErrorNodes { + static description: 'NodeConnection info was not found'; exitCode = sysexits.UNAVAILABLE; } -class ErrorNodeConnectionPublicKeyNotFound extends ErrorNodes { - description: 'Public key was not found'; +class ErrorNodeConnectionPublicKeyNotFound extends ErrorNodes { + static description: 'Public key was not found'; exitCode = sysexits.UNAVAILABLE; } -class ErrorNodeConnectionManagerNotRunning extends ErrorNodes { - description = 'NodeConnectionManager is not running'; +class ErrorNodeConnectionManagerNotRunning extends ErrorNodes { + static description = 'NodeConnectionManager is not running'; exitCode = sysexits.USAGE; } -class ErrorNodeConnectionHostWildcard extends ErrorNodes { - description = 'An IP wildcard was provided for the target host'; +class ErrorNodeConnectionHostWildcard extends ErrorNodes { + static description = 'An IP wildcard was provided for the target host'; exitCode = sysexits.USAGE; } +class ErrorNodePingFailed extends ErrorNodes { + static description = + 'Failed to ping the node when attempting to authenticate'; + exitCode = sysexits.NOHOST; +} export { ErrorNodes, + ErrorNodeAborted, + ErrorNodeManagerNotRunning, + ErrorQueueNotRunning, ErrorNodeGraphRunning, ErrorNodeGraphNotRunning, ErrorNodeGraphDestroyed, @@ -76,10 +104,12 @@ export { ErrorNodeGraphEmptyDatabase, ErrorNodeGraphOversizedBucket, ErrorNodeGraphSameNodeId, + ErrorNodeGraphBucketIndex, ErrorNodeConnectionDestroyed, ErrorNodeConnectionTimeout, ErrorNodeConnectionInfoNotExist, ErrorNodeConnectionPublicKeyNotFound, ErrorNodeConnectionManagerNotRunning, ErrorNodeConnectionHostWildcard, + ErrorNodePingFailed, }; diff --git a/src/nodes/types.ts b/src/nodes/types.ts index ffb916851..37775be9d 100644 --- a/src/nodes/types.ts +++ b/src/nodes/types.ts @@ -4,6 +4,10 @@ import type { Host, Hostname, Port } from '../network/types'; import type { Claim, ClaimId } from '../claims/types'; import type { ChainData } from '../sigchain/types'; +// This should be a string +// actually cause it is a domain +type NodeGraphSpace = '0' | '1'; + type NodeId = Opaque<'NodeId', Id>; type NodeIdString = Opaque<'NodeIdString', string>; type NodeIdEncoded = Opaque<'NodeIdEncoded', string>; @@ -13,14 +17,34 @@ type NodeAddress = { port: Port; }; -type SeedNodes = Record; +type NodeBucketIndex = number; +// Type NodeBucket = Record; + +// TODO: +// No longer need to use NodeIdString +// It's an array, if you want to lookup +// It's ordered by the last updated date +// On the other hand, does this matter +// Not really? +// USE THIS TYPE INSTEAD +type NodeBucket = Array<[NodeId, NodeData]>; + +type NodeBucketMeta = { + count: number; +}; + +// Just make the bucket entries also +// bucketIndex anot as a key +// but as the domain +// !!NodeGraph!!meta!!ff!!count type NodeData = { - id: NodeId; address: NodeAddress; - distance: BigInt; + lastUpdated: number; }; +type SeedNodes = Record; + /** * A claim made on a node. That is, can be either: * - a claim from a node -> node @@ -41,16 +65,6 @@ type NodeInfo = { chain: ChainData; }; -type NodeBucketIndex = number; - -// The data type to be stored in each leveldb entry for the node table -type NodeBucket = { - [key: string]: { - address: NodeAddress; - lastUpdated: Date; - }; -}; - // Only 1 domain, so don't need a 'domain' value (like /gestalts/types.ts) type NodeGraphOp_ = { // Bucket index @@ -72,10 +86,12 @@ export type { NodeIdEncoded, NodeAddress, SeedNodes, - NodeData, NodeClaim, NodeInfo, NodeBucketIndex, + NodeBucketMeta, NodeBucket, + NodeData, NodeGraphOp, + NodeGraphSpace, }; diff --git a/src/nodes/utils.ts b/src/nodes/utils.ts index 696e31d43..1fe3c799d 100644 --- a/src/nodes/utils.ts +++ b/src/nodes/utils.ts @@ -1,29 +1,75 @@ -import type { NodeData, NodeId, NodeIdEncoded } from './types'; +import type { + NodeBucket, + NodeBucketIndex, + NodeId, + NodeIdEncoded, +} from './types'; +import type { KeyPath } from '@matrixai/db'; import { IdInternal } from '@matrixai/id'; +import lexi from 'lexicographic-integer'; +import { utils as dbUtils } from '@matrixai/db'; import { bytes2BigInt } from '../utils'; +import * as keysUtils from '../keys/utils'; + +const sepBuffer = dbUtils.sep; /** - * Compute the distance between two nodes. - * distance = nodeId1 ^ nodeId2 - * where ^ = bitwise XOR operator + * Encodes the NodeId as a `base32hex` string */ -function calculateDistance(nodeId1: NodeId, nodeId2: NodeId): bigint { - const distance = nodeId1.map((byte, i) => byte ^ nodeId2[i]); - return bytes2BigInt(distance); +function encodeNodeId(nodeId: NodeId): NodeIdEncoded { + return nodeId.toMultibase('base32hex') as NodeIdEncoded; } /** - * Find the correct index of the k-bucket to add a new node to. + * Decodes an encoded NodeId string into a NodeId + */ +function decodeNodeId(nodeIdEncoded: any): NodeId | undefined { + if (typeof nodeIdEncoded !== 'string') { + return; + } + const nodeId = IdInternal.fromMultibase(nodeIdEncoded); + if (nodeId == null) { + return; + } + // All NodeIds are 32 bytes long + // The NodeGraph requires a fixed size for Node Ids + if (nodeId.length !== 32) { + return; + } + return nodeId; +} + +/** + * Calculate the bucket index that the target node should be located in * A node's k-buckets are organised such that for the ith k-bucket where * 0 <= i < nodeIdBits, the contacts in this ith bucket are known to adhere to * the following inequality: * 2^i <= distance (from current node) < 2^(i+1) + * This means lower buckets will have less nodes then the upper buckets. + * The highest bucket will contain half of all possible nodes. + * The lowest bucket will only contain 1 node. * * NOTE: because XOR is a commutative operation (i.e. a XOR b = b XOR a), the * order of the passed parameters is actually irrelevant. These variables are * purely named for communicating function purpose. + * + * NOTE: Kademlia literature generally talks about buckets with 1-based indexing + * and that the buckets are ordered from largest to smallest. This means the first + * 1th-bucket is far & large bucket, and the last 255th-bucket is the close bucket. + * This is reversed in our `NodeBucketIndex` encoding. This is so that lexicographic + * sort orders our buckets from closest bucket to farthest bucket. + * + * To convert from `NodeBucketIndex` to nth-bucket in Kademlia literature: + * + * | NodeBucketIndex | Nth-Bucket | + * | --------------- | ---------- | + * | 255 | 1 | farthest & largest + * | 254 | 2 | + * | ... | ... | + * | 1 | 254 | + * | 0 | 256 | closest & smallest */ -function calculateBucketIndex(sourceNode: NodeId, targetNode: NodeId): number { +function bucketIndex(sourceNode: NodeId, targetNode: NodeId): NodeBucketIndex { const distance = sourceNode.map((byte, i) => byte ^ targetNode[i]); const MSByteIndex = distance.findIndex((byte) => byte !== 0); if (MSByteIndex === -1) { @@ -37,48 +83,251 @@ function calculateBucketIndex(sourceNode: NodeId, targetNode: NodeId): number { } /** - * A sorting compareFn to sort an array of NodeData by increasing distance. + * Encodes bucket index to bucket sublevel key */ -function sortByDistance(a: NodeData, b: NodeData) { - if (a.distance > b.distance) { - return 1; - } else if (a.distance < b.distance) { - return -1; - } else { - return 0; +function bucketKey(bucketIndex: NodeBucketIndex): string { + return lexi.pack(bucketIndex, 'hex'); +} + +/** + * Creates key for buckets sublevel + */ +function bucketsDbKey(bucketIndex: NodeBucketIndex, nodeId: NodeId): Buffer { + return Buffer.concat([ + sepBuffer, + Buffer.from(bucketKey(bucketIndex)), + sepBuffer, + bucketDbKey(nodeId), + ]); +} + +/** + * Creates key for single bucket sublevel + */ +function bucketDbKey(nodeId: NodeId): Buffer { + return nodeId.toBuffer(); +} + +/** + * Creates key for buckets indexed by lastUpdated sublevel + */ +function lastUpdatedBucketsDbKey( + bucketIndex: NodeBucketIndex, + lastUpdated: number, + nodeId: NodeId, +): Buffer { + return Buffer.concat([ + sepBuffer, + Buffer.from(bucketKey(bucketIndex)), + sepBuffer, + lastUpdatedBucketDbKey(lastUpdated, nodeId), + ]); +} + +/** + * Creates key for single bucket indexed by lastUpdated sublevel + */ +function lastUpdatedBucketDbKey(lastUpdated: number, nodeId: NodeId): Buffer { + return Buffer.concat([ + Buffer.from(lexi.pack(lastUpdated, 'hex')), + Buffer.from('-'), + nodeId.toBuffer(), + ]); +} + +function lastUpdatedKey(lastUpdated: number): Buffer { + return Buffer.from(lexi.pack(lastUpdated, 'hex')); +} + +/** + * Parse the NodeGraph buckets sublevel key + * The keys look like `!!` + * It is assumed that the `!` is the sublevel prefix. + */ +function parseBucketsDbKey(keyPath: KeyPath): { + bucketIndex: NodeBucketIndex; + bucketKey: string; + nodeId: NodeId; +} { + const [bucketKeyPath, nodeIdKey] = keyPath; + if (bucketKeyPath == null || nodeIdKey == null) { + throw new TypeError('Buffer is not an NodeGraph buckets key'); } + const bucketKey = bucketKeyPath.toString(); + const bucketIndex = lexi.unpack(bucketKey); + const nodeId = IdInternal.fromBuffer(Buffer.from(nodeIdKey)); + return { + bucketIndex, + bucketKey, + nodeId, + }; } /** - * Encodes the NodeId as a `base32hex` string + * Parse the NodeGraph bucket key + * The keys look like `` */ -function encodeNodeId(nodeId: NodeId): NodeIdEncoded { - return nodeId.toMultibase('base32hex') as NodeIdEncoded; +function parseBucketDbKey(keyBuffer: Buffer): NodeId { + return IdInternal.fromBuffer(keyBuffer); } /** - * Decodes an encoded NodeId string into a NodeId + * Parse the NodeGraph index sublevel key + * The keys look like `!!-` + * It is assumed that the `!` is the sublevel prefix. */ -function decodeNodeId(nodeIdEncoded: any): NodeId | undefined { - if (typeof nodeIdEncoded !== 'string') { - return; +function parseLastUpdatedBucketsDbKey(keyPath: KeyPath): { + bucketIndex: NodeBucketIndex; + bucketKey: string; + lastUpdated: number; + nodeId: NodeId; +} { + const [bucketLevel, ...lastUpdatedKeyPath] = keyPath; + if (bucketLevel == null || lastUpdatedKeyPath == null) { + throw new TypeError('Buffer is not an NodeGraph index key'); } - const nodeId = IdInternal.fromMultibase(nodeIdEncoded); - if (nodeId == null) { - return; + const bucketKey = bucketLevel.toString(); + const bucketIndex = lexi.unpack(bucketKey); + if (bucketIndex == null) { + throw new TypeError('Buffer is not an NodeGraph index key'); } - // All NodeIds are 32 bytes long - // The NodeGraph requires a fixed size for Node Ids - if (nodeId.length !== 32) { - return; + const { lastUpdated, nodeId } = + parseLastUpdatedBucketDbKey(lastUpdatedKeyPath); + return { + bucketIndex, + bucketKey, + lastUpdated, + nodeId, + }; +} + +/** + * Parse the NodeGraph index bucket sublevel key + * The keys look like `-` + * It is assumed that the `!` is the sublevel prefix. + */ +function parseLastUpdatedBucketDbKey(keyPath: KeyPath): { + lastUpdated: number; + nodeId: NodeId; +} { + const [lastUpdatedLevel, nodeIdKey] = keyPath; + if (lastUpdatedLevel == null || nodeIdKey == null) { + throw new TypeError('Buffer is not an NodeGraph index bucket key'); } - return nodeId; + const lastUpdated = lexi.unpack(lastUpdatedLevel.toString()); + if (lastUpdated == null) { + throw new TypeError('Buffer is not an NodeGraph index bucket key'); + } + const nodeId = IdInternal.fromBuffer(Buffer.from(nodeIdKey)); + return { + lastUpdated, + nodeId, + }; +} + +/** + * Compute the distance between two nodes. + * distance = nodeId1 ^ nodeId2 + * where ^ = bitwise XOR operator + */ +function nodeDistance(nodeId1: NodeId, nodeId2: NodeId): bigint { + const distance = nodeId1.map((byte, i) => byte ^ nodeId2[i]); + return bytes2BigInt(distance); +} + +function bucketSortByDistance( + bucket: NodeBucket, + nodeId: NodeId, + order: 'asc' | 'desc' = 'asc', +): void { + const distances = {}; + if (order === 'asc') { + bucket.sort(([nodeId1], [nodeId2]) => { + const d1 = (distances[nodeId1] = + distances[nodeId1] ?? nodeDistance(nodeId, nodeId1)); + const d2 = (distances[nodeId2] = + distances[nodeId2] ?? nodeDistance(nodeId, nodeId2)); + if (d1 < d2) { + return -1; + } else if (d1 > d2) { + return 1; + } else { + return 0; + } + }); + } else { + bucket.sort(([nodeId1], [nodeId2]) => { + const d1 = (distances[nodeId1] = + distances[nodeId1] ?? nodeDistance(nodeId, nodeId1)); + const d2 = (distances[nodeId2] = + distances[nodeId2] ?? nodeDistance(nodeId, nodeId2)); + if (d1 > d2) { + return -1; + } else if (d1 < d2) { + return 1; + } else { + return 0; + } + }); + } +} + +function generateRandomDistanceForBucket(bucketIndex: NodeBucketIndex): NodeId { + const buffer = keysUtils.getRandomBytesSync(32); + // Calculate the most significant byte for bucket + const base = bucketIndex / 8; + const mSigByte = Math.floor(base); + const mSigBit = (base - mSigByte) * 8 + 1; + const mSigByteIndex = buffer.length - mSigByte - 1; + // Creating masks + // AND mask should look like 0b00011111 + // OR mask should look like 0b00010000 + const shift = 8 - mSigBit; + const andMask = 0b11111111 >>> shift; + const orMask = 0b10000000 >>> shift; + let byte = buffer[mSigByteIndex]; + byte = byte & andMask; // Forces 0 for bits above bucket bit + byte = byte | orMask; // Forces 1 in the desired bucket bit + buffer[mSigByteIndex] = byte; + // Zero out byte 'above' mSigByte + for (let byteIndex = 0; byteIndex < mSigByteIndex; byteIndex++) { + buffer[byteIndex] = 0; + } + return IdInternal.fromBuffer(buffer); +} + +function xOrNodeId(node1: NodeId, node2: NodeId): NodeId { + const xOrNodeArray = node1.map((byte, i) => byte ^ node2[i]); + const xOrNodeBuffer = Buffer.from(xOrNodeArray); + return IdInternal.fromBuffer(xOrNodeBuffer); +} + +function generateRandomNodeIdForBucket( + nodeId: NodeId, + bucket: NodeBucketIndex, +): NodeId { + const randomDistanceForBucket = generateRandomDistanceForBucket(bucket); + return xOrNodeId(nodeId, randomDistanceForBucket); } export { - calculateDistance, - calculateBucketIndex, - sortByDistance, + sepBuffer, encodeNodeId, decodeNodeId, + bucketIndex, + bucketKey, + bucketsDbKey, + bucketDbKey, + lastUpdatedBucketsDbKey, + lastUpdatedBucketDbKey, + lastUpdatedKey, + parseBucketsDbKey, + parseBucketDbKey, + parseLastUpdatedBucketsDbKey, + parseLastUpdatedBucketDbKey, + nodeDistance, + bucketSortByDistance, + generateRandomDistanceForBucket, + xOrNodeId, + generateRandomNodeIdForBucket, }; diff --git a/src/notifications/NotificationsManager.ts b/src/notifications/NotificationsManager.ts index 44974ae67..8031311bf 100644 --- a/src/notifications/NotificationsManager.ts +++ b/src/notifications/NotificationsManager.ts @@ -1,27 +1,29 @@ +import type { DB, DBTransaction, KeyPath, LevelPath } from '@matrixai/db'; import type { NotificationId, Notification, NotificationData, NotificationIdGenerator, } from './types'; -import type { ACL } from '../acl'; -import type { DB, DBLevel } from '@matrixai/db'; -import type { KeyManager } from '../keys'; -import type { NodeManager, NodeConnectionManager } from '../nodes'; +import type ACL from '../acl/ACL'; +import type KeyManager from '../keys/KeyManager'; +import type NodeManager from '../nodes/NodeManager'; +import type NodeConnectionManager from '../nodes/NodeConnectionManager'; import type { NodeId } from '../nodes/types'; import Logger from '@matrixai/logger'; import { IdInternal } from '@matrixai/id'; -import { Mutex } from 'async-mutex'; +import { Lock, LockBox } from '@matrixai/async-locks'; import { CreateDestroyStartStop, ready, } from '@matrixai/async-init/dist/CreateDestroyStartStop'; import { utils as idUtils } from '@matrixai/id'; +import { withF } from '@matrixai/resources'; import * as notificationsUtils from './utils'; import * as notificationsErrors from './errors'; -import { createNotificationIdGenerator } from './utils'; import * as notificationsPB from '../proto/js/polykey/v1/notifications/notifications_pb'; -import { utils as nodesUtils } from '../nodes'; +import * as nodesUtils from '../nodes/utils'; +import { never } from '../utils/utils'; const MESSAGE_COUNT_KEY = 'numMessages'; @@ -34,27 +36,6 @@ interface NotificationsManager extends CreateDestroyStartStop {} new notificationsErrors.ErrorNotificationsDestroyed(), ) class NotificationsManager { - protected logger: Logger; - protected acl: ACL; - protected db: DB; - protected keyManager: KeyManager; - protected nodeManager: NodeManager; - protected nodeConnectionManager: NodeConnectionManager; - - protected messageCap: number; - - protected notificationsDomain: string = this.constructor.name; - protected notificationsDbDomain: Array = [this.notificationsDomain]; - protected notificationsMessagesDbDomain: Array = [ - this.notificationsDomain, - 'messages', - ]; - protected notificationsDb: DBLevel; - protected notificationsMessagesDb: DBLevel; - protected lock: Mutex = new Mutex(); - - protected notificationIdGenerator: NotificationIdGenerator; - static async createNotificationsManager({ acl, db, @@ -90,6 +71,29 @@ class NotificationsManager { return notificationsManager; } + protected logger: Logger; + protected acl: ACL; + protected db: DB; + protected keyManager: KeyManager; + protected nodeManager: NodeManager; + protected nodeConnectionManager: NodeConnectionManager; + protected messageCap: number; + protected locks: LockBox = new LockBox(); + + /** + * Top level stores MESSAGE_COUNT_KEY -> number (of messages) + */ + protected notificationsDbPath: LevelPath = [this.constructor.name]; + /** + * Messages stores NotificationId -> string (message) + */ + protected notificationsMessagesDbPath: LevelPath = [ + this.constructor.name, + 'messages', + ]; + + protected notificationIdGenerator: NotificationIdGenerator; + constructor({ acl, db, @@ -116,38 +120,38 @@ class NotificationsManager { this.nodeManager = nodeManager; } - get locked(): boolean { - return this.lock.isLocked(); - } - public async start({ fresh = false, }: { fresh?: boolean } = {}): Promise { - this.logger.info(`Starting ${this.constructor.name}`); - // Sub-level stores MESSAGE_COUNT_KEY -> number (of messages) - const notificationsDb = await this.db.level(this.notificationsDomain); - // Sub-sub-level stores NotificationId -> string (message) - const notificationsMessagesDb = await this.db.level( - this.notificationsMessagesDbDomain[1], - notificationsDb, - ); - if (fresh) { - await notificationsDb.clear(); - } - this.notificationsDb = notificationsDb; - this.notificationsMessagesDb = notificationsMessagesDb; + await withF( + [ + this.db.transaction(), + this.locks.lock([ + [...this.notificationsDbPath, MESSAGE_COUNT_KEY], + Lock, + ]), + ], + async ([tran]) => { + this.logger.info(`Starting ${this.constructor.name}`); + if (fresh) { + await tran.clear(this.notificationsDbPath); + } - // Getting latest ID and creating ID generator - let latestId: NotificationId | undefined; - const keyStream = this.notificationsMessagesDb.createKeyStream({ - limit: 1, - reverse: true, - }); - for await (const o of keyStream) { - latestId = IdInternal.fromBuffer(o as Buffer); - } - this.notificationIdGenerator = createNotificationIdGenerator(latestId); - this.logger.info(`Started ${this.constructor.name}`); + // Getting latest ID and creating ID generator + let latestId: NotificationId | undefined; + const keyIterator = tran.iterator( + { limit: 1, reverse: true, values: false }, + this.notificationsMessagesDbPath, + ); + for await (const [keyPath] of keyIterator) { + const key = keyPath[0] as Buffer; + latestId = IdInternal.fromBuffer(key); + } + this.notificationIdGenerator = + notificationsUtils.createNotificationIdGenerator(latestId); + this.logger.info(`Started ${this.constructor.name}`); + }, + ); } public async stop() { @@ -157,37 +161,24 @@ class NotificationsManager { public async destroy() { this.logger.info(`Destroying ${this.constructor.name}`); - const notificationsDb = await this.db.level(this.notificationsDomain); - await notificationsDb.clear(); + await this.db.withTransactionF(async (tran) => { + await tran.clear(this.notificationsDbPath); + }); this.logger.info(`Destroyed ${this.constructor.name}`); } - /** - * Run several operations within the same lock - * This does not ensure atomicity of the underlying database - * Database atomicity still depends on the underlying operation - */ - public async transaction( - f: (notificationsManager: NotificationsManager) => Promise, + @ready(new notificationsErrors.ErrorNotificationsNotRunning()) + public async withTransactionF( + ...params: [...keys: Array, f: (tran: DBTransaction) => Promise] ): Promise { - const release = await this.lock.acquire(); - try { - return await f(this); - } finally { - release(); - } - } - - /** - * Transaction wrapper that will not lock if the operation was executed - * within a transaction context - */ - public async _transaction(f: () => Promise): Promise { - if (this.lock.isLocked()) { - return await f(); - } else { - return await this.transaction(f); - } + const f = params.pop() as (tran: DBTransaction) => Promise; + const lockRequests = (params as Array).map<[KeyPath, typeof Lock]>( + (key) => [key, Lock], + ); + return withF( + [this.db.transaction(), this.locks.lock(...lockRequests)], + ([tran]) => f(tran), + ); } /** @@ -195,7 +186,10 @@ class NotificationsManager { * The `data` parameter must match one of the NotificationData types outlined in ./types */ @ready(new notificationsErrors.ErrorNotificationsNotRunning()) - public async sendNotification(nodeId: NodeId, data: NotificationData) { + public async sendNotification( + nodeId: NodeId, + data: NotificationData, + ): Promise { const notification = { data: data, senderId: nodesUtils.encodeNodeId(this.keyManager.getNodeId()), @@ -217,46 +211,45 @@ class NotificationsManager { * Receive a notification */ @ready(new notificationsErrors.ErrorNotificationsNotRunning()) - public async receiveNotification(notification: Notification) { - await this._transaction(async () => { - const nodePerms = await this.acl.getNodePerm( - nodesUtils.decodeNodeId(notification.senderId)!, + public async receiveNotification( + notification: Notification, + tran?: DBTransaction, + ): Promise { + const messageCountPath = [...this.notificationsDbPath, MESSAGE_COUNT_KEY]; + if (tran == null) { + return this.withTransactionF(messageCountPath, async (tran) => + this.receiveNotification(notification, tran), ); - if (nodePerms === undefined) { - throw new notificationsErrors.ErrorNotificationsPermissionsNotFound(); + } + const nodePerms = await this.acl.getNodePerm( + nodesUtils.decodeNodeId(notification.senderId)!, + ); + if (nodePerms === undefined) { + throw new notificationsErrors.ErrorNotificationsPermissionsNotFound(); + } + // Only keep the message if the sending node has the correct permissions + if (Object.keys(nodePerms.gestalt).includes('notify')) { + // If the number stored in notificationsDb >= 10000 + let numMessages = await tran.get(messageCountPath); + if (numMessages === undefined) { + numMessages = 0; + await tran.put(messageCountPath, 0); } - // Only keep the message if the sending node has the correct permissions - if (Object.keys(nodePerms.gestalt).includes('notify')) { - // If the number stored in notificationsDb >= 10000 - let numMessages = await this.db.get( - this.notificationsDbDomain, - MESSAGE_COUNT_KEY, - ); - if (numMessages === undefined) { - numMessages = 0; - await this.db.put(this.notificationsDbDomain, MESSAGE_COUNT_KEY, 0); - } - if (numMessages >= this.messageCap) { - // Remove the oldest notification from notificationsMessagesDb - const oldestId = await this.getOldestNotificationId(); - await this.removeNotification(oldestId!); - } - // Store the new notification in notificationsMessagesDb - const notificationId = this.notificationIdGenerator(); - await this.db.put( - this.notificationsMessagesDbDomain, - idUtils.toBuffer(notificationId), - notification, - ); - // Number of messages += 1 - const newNumMessages = numMessages + 1; - await this.db.put( - this.notificationsDbDomain, - MESSAGE_COUNT_KEY, - newNumMessages, - ); + if (numMessages >= this.messageCap) { + // Remove the oldest notification from notificationsMessagesDb + const oldestId = await this.getOldestNotificationId(tran); + await this.removeNotification(oldestId!, tran); } - }); + // Store the new notification in notificationsMessagesDb + const notificationId = this.notificationIdGenerator(); + await tran.put( + [...this.notificationsMessagesDbPath, idUtils.toBuffer(notificationId)], + notification, + ); + // Number of messages += 1 + const newNumMessages = numMessages + 1; + await tran.put(messageCountPath, newNumMessages); + } } /** @@ -267,16 +260,23 @@ class NotificationsManager { unread = false, number = 'all', order = 'newest', + tran, }: { unread?: boolean; number?: number | 'all'; order?: 'newest' | 'oldest'; + tran?: DBTransaction; } = {}): Promise> { + if (tran == null) { + return this.withTransactionF(async (tran) => + this.readNotifications({ unread, number, order, tran }), + ); + } let notificationIds: Array; - if (unread === true) { - notificationIds = await this.getNotificationIds('unread'); + if (unread) { + notificationIds = await this.getNotificationIds('unread', tran); } else { - notificationIds = await this.getNotificationIds('all'); + notificationIds = await this.getNotificationIds('all', tran); } if (order === 'newest') { @@ -290,8 +290,9 @@ class NotificationsManager { const notifications: Array = []; for (const id of notificationIds) { - const notification = await this.readNotificationById(id); - notifications.push(notification!); + const notification = await this.readNotificationById(id, tran); + if (notification == null) never(); + notifications.push(notification); } return notifications; @@ -304,8 +305,14 @@ class NotificationsManager { @ready(new notificationsErrors.ErrorNotificationsNotRunning()) public async findGestaltInvite( fromNode: NodeId, + tran?: DBTransaction, ): Promise { - const notifications = await this.getNotifications('all'); + if (tran == null) { + return this.withTransactionF(async (tran) => + this.findGestaltInvite(fromNode, tran), + ); + } + const notifications = await this.getNotifications('all', tran); for (const notification of notifications) { if ( notification.data.type === 'GestaltInvite' && @@ -320,119 +327,114 @@ class NotificationsManager { * Removes all notifications */ @ready(new notificationsErrors.ErrorNotificationsNotRunning()) - public async clearNotifications() { - await this._transaction(async () => { - const notificationIds = await this.getNotificationIds('all'); - const numMessages = await this.db.get( - this.notificationsDbDomain, - MESSAGE_COUNT_KEY, + public async clearNotifications(tran?: DBTransaction): Promise { + const messageCountPath = [...this.notificationsDbPath, MESSAGE_COUNT_KEY]; + if (tran == null) { + return this.withTransactionF(messageCountPath, async (tran) => + this.clearNotifications(tran), ); - if (numMessages !== undefined) { - for (const id of notificationIds) { - await this.removeNotification(id); - } + } + const notificationIds = await this.getNotificationIds('all', tran); + const numMessages = await tran.get(messageCountPath); + if (numMessages !== undefined) { + for (const id of notificationIds) { + await this.removeNotification(id, tran); } - }); + } } - private async readNotificationById( + protected async readNotificationById( notificationId: NotificationId, + tran: DBTransaction, ): Promise { - return await this._transaction(async () => { - const notification = await this.db.get( - this.notificationsMessagesDbDomain, - idUtils.toBuffer(notificationId), - ); - if (notification === undefined) { - return undefined; - } - notification.isRead = true; - await this.db.put( - this.notificationsMessagesDbDomain, - idUtils.toBuffer(notificationId), - notification, - ); - return notification; - }); + const notification = await tran.get([ + ...this.notificationsMessagesDbPath, + idUtils.toBuffer(notificationId), + ]); + if (notification === undefined) { + return undefined; + } + notification.isRead = true; + await tran.put( + [...this.notificationsMessagesDbPath, idUtils.toBuffer(notificationId)], + notification, + ); + return notification; } - private async getNotificationIds( + protected async getNotificationIds( type: 'unread' | 'all', + tran: DBTransaction, ): Promise> { - return await this._transaction(async () => { - const notificationIds: Array = []; - for await (const o of this.notificationsMessagesDb.createReadStream()) { - const notificationId = IdInternal.fromBuffer( - (o as any).key, - ); - const data = (o as any).value as Buffer; - const notification = await this.db.deserializeDecrypt( - data, - false, - ); - if (type === 'all') { + const notificationIds: Array = []; + const messageIterator = tran.iterator( + { valueAsBuffer: false }, + this.notificationsMessagesDbPath, + ); + for await (const [keyPath, notification] of messageIterator) { + const key = keyPath[0] as Buffer; + const notificationId = IdInternal.fromBuffer(key); + if (type === 'all') { + notificationIds.push(notificationId); + } else if (type === 'unread') { + if (!notification.isRead) { notificationIds.push(notificationId); - } else if (type === 'unread') { - if (notification.isRead === false) { - notificationIds.push(notificationId); - } } } - return notificationIds; - }); + } + return notificationIds; } - private async getNotifications( + protected async getNotifications( type: 'unread' | 'all', + tran: DBTransaction, ): Promise> { - return await this._transaction(async () => { - const notifications: Array = []; - for await (const v of this.notificationsMessagesDb.createValueStream()) { - const data = v as Buffer; - const notification = await this.db.deserializeDecrypt( - data, - false, - ); - if (type === 'all') { + const notifications: Array = []; + for await (const [, notification] of tran.iterator( + { valueAsBuffer: false }, + this.notificationsMessagesDbPath, + )) { + if (type === 'all') { + notifications.push(notification); + } else if (type === 'unread') { + if (!notification.isRead) { notifications.push(notification); - } else if (type === 'unread') { - if (notification.isRead === false) { - notifications.push(notification); - } } } - return notifications; - }); + } + return notifications; } - private async getOldestNotificationId(): Promise { - const notificationIds = await this.getNotificationIds('all'); + protected async getOldestNotificationId( + tran: DBTransaction, + ): Promise { + const notificationIds = await this.getNotificationIds('all', tran); if (notificationIds.length === 0) { return undefined; } return notificationIds[0]; } - private async removeNotification(messageId: NotificationId) { - await this._transaction(async () => { - const numMessages = await this.db.get( - this.notificationsDbDomain, - MESSAGE_COUNT_KEY, - ); - if (numMessages === undefined) { - throw new notificationsErrors.ErrorNotificationsDb(); - } + protected async removeNotification( + messageId: NotificationId, + tran: DBTransaction, + ): Promise { + const numMessages = await tran.get([ + ...this.notificationsDbPath, + MESSAGE_COUNT_KEY, + ]); + if (numMessages === undefined) { + throw new notificationsErrors.ErrorNotificationsDb(); + } - await this.db.del( - this.notificationsMessagesDbDomain, - idUtils.toBuffer(messageId), - ); - await this.db.put( - this.notificationsDbDomain, - MESSAGE_COUNT_KEY, - numMessages - 1, - ); - }); + await tran.del([ + ...this.notificationsMessagesDbPath, + idUtils.toBuffer(messageId), + ]); + await tran.put( + [...this.notificationsDbPath, MESSAGE_COUNT_KEY], + numMessages - 1, + ); } } diff --git a/src/notifications/errors.ts b/src/notifications/errors.ts index 5d6f68493..028862e50 100644 --- a/src/notifications/errors.ts +++ b/src/notifications/errors.ts @@ -1,39 +1,69 @@ -import { ErrorPolykey } from '../errors'; +import { ErrorPolykey, sysexits } from '../errors'; -class ErrorNotifications extends ErrorPolykey {} +class ErrorNotifications extends ErrorPolykey {} -class ErrorNotificationsUnknownNode extends ErrorNotifications {} +class ErrorNotificationsRunning extends ErrorNotifications { + static description = 'NotiticationsManager is running'; + exitCode = sysexits.USAGE; +} -class ErrorNotificationsRunning extends ErrorNotifications {} +class ErrorNotificationsNotRunning extends ErrorNotifications { + static description = 'NotiticationsManager is not running'; + exitCode = sysexits.USAGE; +} -class ErrorNotificationsNotRunning extends ErrorNotifications {} +class ErrorNotificationsDestroyed extends ErrorNotifications { + static description = 'NotiticationsManager is destroyed'; + exitCode = sysexits.USAGE; +} -class ErrorNotificationsDestroyed extends ErrorNotifications {} +class ErrorNotificationsPermissionsNotFound extends ErrorNotifications { + static description = 'Could not find permissions for NodeId'; + exitCode = sysexits.NOUSER; +} -class ErrorNotificationsPermissionsNotFound extends ErrorNotifications {} +class ErrorNotificationsDb extends ErrorNotifications { + static description = 'Database consistency error'; + exitCode = sysexits.IOERR; +} -class ErrorNotificationsDb extends ErrorNotifications {} - -class ErrorNotificationsParse extends ErrorNotifications {} +class ErrorNotificationsParse extends ErrorNotifications { + static description = 'Unable to verify notification'; + exitCode = sysexits.IOERR; +} /** * Exceptions raised when validating a Notification against a JSON schema */ -class ErrorSchemaValidate extends ErrorNotifications {} +class ErrorSchemaValidate extends ErrorNotifications {} -class ErrorNotificationsInvalidType extends ErrorSchemaValidate {} +class ErrorNotificationsInvalidType extends ErrorSchemaValidate { + static description = 'Invalid notification type'; + exitCode = sysexits.USAGE; +} -class ErrorNotificationsGeneralInvalid extends ErrorSchemaValidate {} +class ErrorNotificationsGeneralInvalid extends ErrorSchemaValidate { + static description = 'Invalid notification data'; + exitCode = sysexits.USAGE; +} -class ErrorNotificationsGestaltInviteInvalid extends ErrorSchemaValidate {} +class ErrorNotificationsGestaltInviteInvalid extends ErrorSchemaValidate { + static description = 'Invalid notification data'; + exitCode = sysexits.USAGE; +} -class ErrorNotificationsVaultShareInvalid extends ErrorSchemaValidate {} +class ErrorNotificationsVaultShareInvalid extends ErrorSchemaValidate { + static description = 'Invalid notification data'; + exitCode = sysexits.USAGE; +} -class ErrorNotificationsValidationFailed extends ErrorSchemaValidate {} +class ErrorNotificationsValidationFailed extends ErrorSchemaValidate { + static description = 'Notification does not match schema'; + exitCode = sysexits.USAGE; +} export { ErrorNotifications, - ErrorNotificationsUnknownNode, ErrorNotificationsRunning, ErrorNotificationsNotRunning, ErrorNotificationsDestroyed, diff --git a/src/notifications/utils.ts b/src/notifications/utils.ts index 08532f524..a31b0ad59 100644 --- a/src/notifications/utils.ts +++ b/src/notifications/utils.ts @@ -78,7 +78,9 @@ async function verifyAndDecodeNotif(notifJWT: string): Promise { throw err; } else { // Error came from jose - throw new notificationsErrors.ErrorNotificationsParse(); + throw new notificationsErrors.ErrorNotificationsParse(err.message, { + cause: err, + }); } } } diff --git a/src/proto/js/google/protobuf/any_pb.js b/src/proto/js/google/protobuf/any_pb.js index 2154f2078..cec1761c8 100644 --- a/src/proto/js/google/protobuf/any_pb.js +++ b/src/proto/js/google/protobuf/any_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/google/protobuf/descriptor_pb.js b/src/proto/js/google/protobuf/descriptor_pb.js index 64e84878b..9c345b93d 100644 --- a/src/proto/js/google/protobuf/descriptor_pb.js +++ b/src/proto/js/google/protobuf/descriptor_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/google/protobuf/duration_pb.js b/src/proto/js/google/protobuf/duration_pb.js index 74166f0fd..1b5f0fd84 100644 --- a/src/proto/js/google/protobuf/duration_pb.js +++ b/src/proto/js/google/protobuf/duration_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/google/protobuf/empty_pb.js b/src/proto/js/google/protobuf/empty_pb.js index d85fa310a..bd5d8a4e1 100644 --- a/src/proto/js/google/protobuf/empty_pb.js +++ b/src/proto/js/google/protobuf/empty_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/google/protobuf/field_mask_pb.js b/src/proto/js/google/protobuf/field_mask_pb.js index 67860a3a2..34e581b04 100644 --- a/src/proto/js/google/protobuf/field_mask_pb.js +++ b/src/proto/js/google/protobuf/field_mask_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/google/protobuf/struct_pb.js b/src/proto/js/google/protobuf/struct_pb.js index bff1ed412..b16b8b2fa 100644 --- a/src/proto/js/google/protobuf/struct_pb.js +++ b/src/proto/js/google/protobuf/struct_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/google/protobuf/timestamp_pb.js b/src/proto/js/google/protobuf/timestamp_pb.js index 6881a1d93..a270c1c47 100644 --- a/src/proto/js/google/protobuf/timestamp_pb.js +++ b/src/proto/js/google/protobuf/timestamp_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/google/protobuf/wrappers_pb.js b/src/proto/js/google/protobuf/wrappers_pb.js index 9c89af542..458e1b436 100644 --- a/src/proto/js/google/protobuf/wrappers_pb.js +++ b/src/proto/js/google/protobuf/wrappers_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/agent/agent_pb.js b/src/proto/js/polykey/v1/agent/agent_pb.js index 13a458c48..29361addf 100644 --- a/src/proto/js/polykey/v1/agent/agent_pb.js +++ b/src/proto/js/polykey/v1/agent/agent_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/agent_service_pb.js b/src/proto/js/polykey/v1/agent_service_pb.js index ade0b70fa..9fa48c738 100644 --- a/src/proto/js/polykey/v1/agent_service_pb.js +++ b/src/proto/js/polykey/v1/agent_service_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/client_service_grpc_pb.d.ts b/src/proto/js/polykey/v1/client_service_grpc_pb.d.ts index 023631a45..b230f8df4 100644 --- a/src/proto/js/polykey/v1/client_service_grpc_pb.d.ts +++ b/src/proto/js/polykey/v1/client_service_grpc_pb.d.ts @@ -27,6 +27,7 @@ interface IClientServiceService extends grpc.ServiceDefinition; responseDeserialize: grpc.deserialize; } -interface IClientServiceService_INodesAdd extends grpc.MethodDefinition { +interface IClientServiceService_INodesAdd extends grpc.MethodDefinition { path: "/polykey.v1.ClientService/NodesAdd"; requestStream: false; responseStream: false; - requestSerialize: grpc.serialize; - requestDeserialize: grpc.deserialize; + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; responseSerialize: grpc.serialize; responseDeserialize: grpc.deserialize; } @@ -157,6 +158,15 @@ interface IClientServiceService_INodesFind extends grpc.MethodDefinition; responseDeserialize: grpc.deserialize; } +interface IClientServiceService_INodesGetAll extends grpc.MethodDefinition { + path: "/polykey.v1.ClientService/NodesGetAll"; + requestStream: false; + responseStream: false; + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; + responseSerialize: grpc.serialize; + responseDeserialize: grpc.deserialize; +} interface IClientServiceService_IKeysKeyPairRoot extends grpc.MethodDefinition { path: "/polykey.v1.ClientService/KeysKeyPairRoot"; requestStream: false; @@ -669,10 +679,11 @@ export interface IClientServiceServer extends grpc.UntypedServiceImplementation agentStatus: grpc.handleUnaryCall; agentStop: grpc.handleUnaryCall; agentUnlock: grpc.handleUnaryCall; - nodesAdd: grpc.handleUnaryCall; + nodesAdd: grpc.handleUnaryCall; nodesPing: grpc.handleUnaryCall; nodesClaim: grpc.handleUnaryCall; nodesFind: grpc.handleUnaryCall; + nodesGetAll: grpc.handleUnaryCall; keysKeyPairRoot: grpc.handleUnaryCall; keysKeyPairReset: grpc.handleUnaryCall; keysKeyPairRenew: grpc.handleUnaryCall; @@ -744,9 +755,9 @@ export interface IClientServiceClient { agentUnlock(request: polykey_v1_utils_utils_pb.EmptyMessage, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; agentUnlock(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; agentUnlock(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; - nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAddress, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; - nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAddress, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; - nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAddress, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAdd, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAdd, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAdd, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; nodesPing(request: polykey_v1_nodes_nodes_pb.Node, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.StatusMessage) => void): grpc.ClientUnaryCall; nodesPing(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.StatusMessage) => void): grpc.ClientUnaryCall; nodesPing(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.StatusMessage) => void): grpc.ClientUnaryCall; @@ -756,6 +767,9 @@ export interface IClientServiceClient { nodesFind(request: polykey_v1_nodes_nodes_pb.Node, callback: (error: grpc.ServiceError | null, response: polykey_v1_nodes_nodes_pb.NodeAddress) => void): grpc.ClientUnaryCall; nodesFind(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_nodes_nodes_pb.NodeAddress) => void): grpc.ClientUnaryCall; nodesFind(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_nodes_nodes_pb.NodeAddress) => void): grpc.ClientUnaryCall; + nodesGetAll(request: polykey_v1_utils_utils_pb.EmptyMessage, callback: (error: grpc.ServiceError | null, response: polykey_v1_nodes_nodes_pb.NodeBuckets) => void): grpc.ClientUnaryCall; + nodesGetAll(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_nodes_nodes_pb.NodeBuckets) => void): grpc.ClientUnaryCall; + nodesGetAll(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_nodes_nodes_pb.NodeBuckets) => void): grpc.ClientUnaryCall; keysKeyPairRoot(request: polykey_v1_utils_utils_pb.EmptyMessage, callback: (error: grpc.ServiceError | null, response: polykey_v1_keys_keys_pb.KeyPair) => void): grpc.ClientUnaryCall; keysKeyPairRoot(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_keys_keys_pb.KeyPair) => void): grpc.ClientUnaryCall; keysKeyPairRoot(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_keys_keys_pb.KeyPair) => void): grpc.ClientUnaryCall; @@ -929,9 +943,9 @@ export class ClientServiceClient extends grpc.Client implements IClientServiceCl public agentUnlock(request: polykey_v1_utils_utils_pb.EmptyMessage, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; public agentUnlock(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; public agentUnlock(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; - public nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAddress, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; - public nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAddress, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; - public nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAddress, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + public nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAdd, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + public nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAdd, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + public nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAdd, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; public nodesPing(request: polykey_v1_nodes_nodes_pb.Node, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.StatusMessage) => void): grpc.ClientUnaryCall; public nodesPing(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.StatusMessage) => void): grpc.ClientUnaryCall; public nodesPing(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.StatusMessage) => void): grpc.ClientUnaryCall; @@ -941,6 +955,9 @@ export class ClientServiceClient extends grpc.Client implements IClientServiceCl public nodesFind(request: polykey_v1_nodes_nodes_pb.Node, callback: (error: grpc.ServiceError | null, response: polykey_v1_nodes_nodes_pb.NodeAddress) => void): grpc.ClientUnaryCall; public nodesFind(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_nodes_nodes_pb.NodeAddress) => void): grpc.ClientUnaryCall; public nodesFind(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_nodes_nodes_pb.NodeAddress) => void): grpc.ClientUnaryCall; + public nodesGetAll(request: polykey_v1_utils_utils_pb.EmptyMessage, callback: (error: grpc.ServiceError | null, response: polykey_v1_nodes_nodes_pb.NodeBuckets) => void): grpc.ClientUnaryCall; + public nodesGetAll(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_nodes_nodes_pb.NodeBuckets) => void): grpc.ClientUnaryCall; + public nodesGetAll(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_nodes_nodes_pb.NodeBuckets) => void): grpc.ClientUnaryCall; public keysKeyPairRoot(request: polykey_v1_utils_utils_pb.EmptyMessage, callback: (error: grpc.ServiceError | null, response: polykey_v1_keys_keys_pb.KeyPair) => void): grpc.ClientUnaryCall; public keysKeyPairRoot(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_keys_keys_pb.KeyPair) => void): grpc.ClientUnaryCall; public keysKeyPairRoot(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_keys_keys_pb.KeyPair) => void): grpc.ClientUnaryCall; diff --git a/src/proto/js/polykey/v1/client_service_grpc_pb.js b/src/proto/js/polykey/v1/client_service_grpc_pb.js index ede2e9470..e08b6512c 100644 --- a/src/proto/js/polykey/v1/client_service_grpc_pb.js +++ b/src/proto/js/polykey/v1/client_service_grpc_pb.js @@ -201,6 +201,17 @@ function deserialize_polykey_v1_nodes_Node(buffer_arg) { return polykey_v1_nodes_nodes_pb.Node.deserializeBinary(new Uint8Array(buffer_arg)); } +function serialize_polykey_v1_nodes_NodeAdd(arg) { + if (!(arg instanceof polykey_v1_nodes_nodes_pb.NodeAdd)) { + throw new Error('Expected argument of type polykey.v1.nodes.NodeAdd'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_polykey_v1_nodes_NodeAdd(buffer_arg) { + return polykey_v1_nodes_nodes_pb.NodeAdd.deserializeBinary(new Uint8Array(buffer_arg)); +} + function serialize_polykey_v1_nodes_NodeAddress(arg) { if (!(arg instanceof polykey_v1_nodes_nodes_pb.NodeAddress)) { throw new Error('Expected argument of type polykey.v1.nodes.NodeAddress'); @@ -212,6 +223,17 @@ function deserialize_polykey_v1_nodes_NodeAddress(buffer_arg) { return polykey_v1_nodes_nodes_pb.NodeAddress.deserializeBinary(new Uint8Array(buffer_arg)); } +function serialize_polykey_v1_nodes_NodeBuckets(arg) { + if (!(arg instanceof polykey_v1_nodes_nodes_pb.NodeBuckets)) { + throw new Error('Expected argument of type polykey.v1.nodes.NodeBuckets'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_polykey_v1_nodes_NodeBuckets(buffer_arg) { + return polykey_v1_nodes_nodes_pb.NodeBuckets.deserializeBinary(new Uint8Array(buffer_arg)); +} + function serialize_polykey_v1_notifications_List(arg) { if (!(arg instanceof polykey_v1_notifications_notifications_pb.List)) { throw new Error('Expected argument of type polykey.v1.notifications.List'); @@ -517,10 +539,10 @@ nodesAdd: { path: '/polykey.v1.ClientService/NodesAdd', requestStream: false, responseStream: false, - requestType: polykey_v1_nodes_nodes_pb.NodeAddress, + requestType: polykey_v1_nodes_nodes_pb.NodeAdd, responseType: polykey_v1_utils_utils_pb.EmptyMessage, - requestSerialize: serialize_polykey_v1_nodes_NodeAddress, - requestDeserialize: deserialize_polykey_v1_nodes_NodeAddress, + requestSerialize: serialize_polykey_v1_nodes_NodeAdd, + requestDeserialize: deserialize_polykey_v1_nodes_NodeAdd, responseSerialize: serialize_polykey_v1_utils_EmptyMessage, responseDeserialize: deserialize_polykey_v1_utils_EmptyMessage, }, @@ -557,6 +579,17 @@ nodesAdd: { responseSerialize: serialize_polykey_v1_nodes_NodeAddress, responseDeserialize: deserialize_polykey_v1_nodes_NodeAddress, }, + nodesGetAll: { + path: '/polykey.v1.ClientService/NodesGetAll', + requestStream: false, + responseStream: false, + requestType: polykey_v1_utils_utils_pb.EmptyMessage, + responseType: polykey_v1_nodes_nodes_pb.NodeBuckets, + requestSerialize: serialize_polykey_v1_utils_EmptyMessage, + requestDeserialize: deserialize_polykey_v1_utils_EmptyMessage, + responseSerialize: serialize_polykey_v1_nodes_NodeBuckets, + responseDeserialize: deserialize_polykey_v1_nodes_NodeBuckets, + }, // Keys keysKeyPairRoot: { path: '/polykey.v1.ClientService/KeysKeyPairRoot', diff --git a/src/proto/js/polykey/v1/client_service_pb.js b/src/proto/js/polykey/v1/client_service_pb.js index 4adc8bd6d..68a9ebcb8 100644 --- a/src/proto/js/polykey/v1/client_service_pb.js +++ b/src/proto/js/polykey/v1/client_service_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/gestalts/gestalts_pb.js b/src/proto/js/polykey/v1/gestalts/gestalts_pb.js index 90435b3be..36b225293 100644 --- a/src/proto/js/polykey/v1/gestalts/gestalts_pb.js +++ b/src/proto/js/polykey/v1/gestalts/gestalts_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/identities/identities_pb.js b/src/proto/js/polykey/v1/identities/identities_pb.js index cbfb21ed9..a52a535f4 100644 --- a/src/proto/js/polykey/v1/identities/identities_pb.js +++ b/src/proto/js/polykey/v1/identities/identities_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/keys/keys_pb.js b/src/proto/js/polykey/v1/keys/keys_pb.js index dfbc1df0b..323ef8f16 100644 --- a/src/proto/js/polykey/v1/keys/keys_pb.js +++ b/src/proto/js/polykey/v1/keys/keys_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/nodes/nodes_pb.d.ts b/src/proto/js/polykey/v1/nodes/nodes_pb.d.ts index 0da62ce43..09fb028eb 100644 --- a/src/proto/js/polykey/v1/nodes/nodes_pb.d.ts +++ b/src/proto/js/polykey/v1/nodes/nodes_pb.d.ts @@ -98,6 +98,60 @@ export namespace Claim { } } +export class NodeAdd extends jspb.Message { + getNodeId(): string; + setNodeId(value: string): NodeAdd; + + hasAddress(): boolean; + clearAddress(): void; + getAddress(): Address | undefined; + setAddress(value?: Address): NodeAdd; + getForce(): boolean; + setForce(value: boolean): NodeAdd; + getPing(): boolean; + setPing(value: boolean): NodeAdd; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): NodeAdd.AsObject; + static toObject(includeInstance: boolean, msg: NodeAdd): NodeAdd.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: NodeAdd, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): NodeAdd; + static deserializeBinaryFromReader(message: NodeAdd, reader: jspb.BinaryReader): NodeAdd; +} + +export namespace NodeAdd { + export type AsObject = { + nodeId: string, + address?: Address.AsObject, + force: boolean, + ping: boolean, + } +} + +export class NodeBuckets extends jspb.Message { + + getBucketsMap(): jspb.Map; + clearBucketsMap(): void; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): NodeBuckets.AsObject; + static toObject(includeInstance: boolean, msg: NodeBuckets): NodeBuckets.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: NodeBuckets, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): NodeBuckets; + static deserializeBinaryFromReader(message: NodeBuckets, reader: jspb.BinaryReader): NodeBuckets; +} + +export namespace NodeBuckets { + export type AsObject = { + + bucketsMap: Array<[number, NodeTable.AsObject]>, + } +} + export class Connection extends jspb.Message { getAId(): string; setAId(value: string): Connection; diff --git a/src/proto/js/polykey/v1/nodes/nodes_pb.js b/src/proto/js/polykey/v1/nodes/nodes_pb.js index 01d29ce4f..6dd70cdc3 100644 --- a/src/proto/js/polykey/v1/nodes/nodes_pb.js +++ b/src/proto/js/polykey/v1/nodes/nodes_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public @@ -24,7 +25,9 @@ goog.exportSymbol('proto.polykey.v1.nodes.Claims', null, global); goog.exportSymbol('proto.polykey.v1.nodes.Connection', null, global); goog.exportSymbol('proto.polykey.v1.nodes.CrossSign', null, global); goog.exportSymbol('proto.polykey.v1.nodes.Node', null, global); +goog.exportSymbol('proto.polykey.v1.nodes.NodeAdd', null, global); goog.exportSymbol('proto.polykey.v1.nodes.NodeAddress', null, global); +goog.exportSymbol('proto.polykey.v1.nodes.NodeBuckets', null, global); goog.exportSymbol('proto.polykey.v1.nodes.NodeTable', null, global); goog.exportSymbol('proto.polykey.v1.nodes.Relay', null, global); goog.exportSymbol('proto.polykey.v1.nodes.Signature', null, global); @@ -112,6 +115,48 @@ if (goog.DEBUG && !COMPILED) { */ proto.polykey.v1.nodes.Claim.displayName = 'proto.polykey.v1.nodes.Claim'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.polykey.v1.nodes.NodeAdd = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.polykey.v1.nodes.NodeAdd, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.polykey.v1.nodes.NodeAdd.displayName = 'proto.polykey.v1.nodes.NodeAdd'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.polykey.v1.nodes.NodeBuckets = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.polykey.v1.nodes.NodeBuckets, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.polykey.v1.nodes.NodeBuckets.displayName = 'proto.polykey.v1.nodes.NodeBuckets'; +} /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -956,6 +1001,380 @@ proto.polykey.v1.nodes.Claim.prototype.setForceInvite = function(value) { +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.polykey.v1.nodes.NodeAdd.prototype.toObject = function(opt_includeInstance) { + return proto.polykey.v1.nodes.NodeAdd.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.polykey.v1.nodes.NodeAdd} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.polykey.v1.nodes.NodeAdd.toObject = function(includeInstance, msg) { + var f, obj = { + nodeId: jspb.Message.getFieldWithDefault(msg, 1, ""), + address: (f = msg.getAddress()) && proto.polykey.v1.nodes.Address.toObject(includeInstance, f), + force: jspb.Message.getBooleanFieldWithDefault(msg, 3, false), + ping: jspb.Message.getBooleanFieldWithDefault(msg, 4, false) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.polykey.v1.nodes.NodeAdd} + */ +proto.polykey.v1.nodes.NodeAdd.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.polykey.v1.nodes.NodeAdd; + return proto.polykey.v1.nodes.NodeAdd.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.polykey.v1.nodes.NodeAdd} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.polykey.v1.nodes.NodeAdd} + */ +proto.polykey.v1.nodes.NodeAdd.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setNodeId(value); + break; + case 2: + var value = new proto.polykey.v1.nodes.Address; + reader.readMessage(value,proto.polykey.v1.nodes.Address.deserializeBinaryFromReader); + msg.setAddress(value); + break; + case 3: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setForce(value); + break; + case 4: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setPing(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.polykey.v1.nodes.NodeAdd.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.polykey.v1.nodes.NodeAdd.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.polykey.v1.nodes.NodeAdd} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.polykey.v1.nodes.NodeAdd.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getNodeId(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getAddress(); + if (f != null) { + writer.writeMessage( + 2, + f, + proto.polykey.v1.nodes.Address.serializeBinaryToWriter + ); + } + f = message.getForce(); + if (f) { + writer.writeBool( + 3, + f + ); + } + f = message.getPing(); + if (f) { + writer.writeBool( + 4, + f + ); + } +}; + + +/** + * optional string node_id = 1; + * @return {string} + */ +proto.polykey.v1.nodes.NodeAdd.prototype.getNodeId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.polykey.v1.nodes.NodeAdd} returns this + */ +proto.polykey.v1.nodes.NodeAdd.prototype.setNodeId = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional Address address = 2; + * @return {?proto.polykey.v1.nodes.Address} + */ +proto.polykey.v1.nodes.NodeAdd.prototype.getAddress = function() { + return /** @type{?proto.polykey.v1.nodes.Address} */ ( + jspb.Message.getWrapperField(this, proto.polykey.v1.nodes.Address, 2)); +}; + + +/** + * @param {?proto.polykey.v1.nodes.Address|undefined} value + * @return {!proto.polykey.v1.nodes.NodeAdd} returns this +*/ +proto.polykey.v1.nodes.NodeAdd.prototype.setAddress = function(value) { + return jspb.Message.setWrapperField(this, 2, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.polykey.v1.nodes.NodeAdd} returns this + */ +proto.polykey.v1.nodes.NodeAdd.prototype.clearAddress = function() { + return this.setAddress(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.polykey.v1.nodes.NodeAdd.prototype.hasAddress = function() { + return jspb.Message.getField(this, 2) != null; +}; + + +/** + * optional bool force = 3; + * @return {boolean} + */ +proto.polykey.v1.nodes.NodeAdd.prototype.getForce = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 3, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.polykey.v1.nodes.NodeAdd} returns this + */ +proto.polykey.v1.nodes.NodeAdd.prototype.setForce = function(value) { + return jspb.Message.setProto3BooleanField(this, 3, value); +}; + + +/** + * optional bool ping = 4; + * @return {boolean} + */ +proto.polykey.v1.nodes.NodeAdd.prototype.getPing = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 4, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.polykey.v1.nodes.NodeAdd} returns this + */ +proto.polykey.v1.nodes.NodeAdd.prototype.setPing = function(value) { + return jspb.Message.setProto3BooleanField(this, 4, value); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.polykey.v1.nodes.NodeBuckets.prototype.toObject = function(opt_includeInstance) { + return proto.polykey.v1.nodes.NodeBuckets.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.polykey.v1.nodes.NodeBuckets} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.polykey.v1.nodes.NodeBuckets.toObject = function(includeInstance, msg) { + var f, obj = { + bucketsMap: (f = msg.getBucketsMap()) ? f.toObject(includeInstance, proto.polykey.v1.nodes.NodeTable.toObject) : [] + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.polykey.v1.nodes.NodeBuckets} + */ +proto.polykey.v1.nodes.NodeBuckets.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.polykey.v1.nodes.NodeBuckets; + return proto.polykey.v1.nodes.NodeBuckets.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.polykey.v1.nodes.NodeBuckets} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.polykey.v1.nodes.NodeBuckets} + */ +proto.polykey.v1.nodes.NodeBuckets.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = msg.getBucketsMap(); + reader.readMessage(value, function(message, reader) { + jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readInt32, jspb.BinaryReader.prototype.readMessage, proto.polykey.v1.nodes.NodeTable.deserializeBinaryFromReader, 0, new proto.polykey.v1.nodes.NodeTable()); + }); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.polykey.v1.nodes.NodeBuckets.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.polykey.v1.nodes.NodeBuckets.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.polykey.v1.nodes.NodeBuckets} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.polykey.v1.nodes.NodeBuckets.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getBucketsMap(true); + if (f && f.getLength() > 0) { + f.serializeBinary(1, writer, jspb.BinaryWriter.prototype.writeInt32, jspb.BinaryWriter.prototype.writeMessage, proto.polykey.v1.nodes.NodeTable.serializeBinaryToWriter); + } +}; + + +/** + * map buckets = 1; + * @param {boolean=} opt_noLazyCreate Do not create the map if + * empty, instead returning `undefined` + * @return {!jspb.Map} + */ +proto.polykey.v1.nodes.NodeBuckets.prototype.getBucketsMap = function(opt_noLazyCreate) { + return /** @type {!jspb.Map} */ ( + jspb.Message.getMapField(this, 1, opt_noLazyCreate, + proto.polykey.v1.nodes.NodeTable)); +}; + + +/** + * Clears values from the map. The map will be non-null. + * @return {!proto.polykey.v1.nodes.NodeBuckets} returns this + */ +proto.polykey.v1.nodes.NodeBuckets.prototype.clearBucketsMap = function() { + this.getBucketsMap().clear(); + return this;}; + + + + + if (jspb.Message.GENERATE_TO_OBJECT) { /** * Creates an object representation of this proto. diff --git a/src/proto/js/polykey/v1/notifications/notifications_pb.js b/src/proto/js/polykey/v1/notifications/notifications_pb.js index f50f614f5..80794ae7f 100644 --- a/src/proto/js/polykey/v1/notifications/notifications_pb.js +++ b/src/proto/js/polykey/v1/notifications/notifications_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/permissions/permissions_pb.js b/src/proto/js/polykey/v1/permissions/permissions_pb.js index 53e129985..1b55e4f47 100644 --- a/src/proto/js/polykey/v1/permissions/permissions_pb.js +++ b/src/proto/js/polykey/v1/permissions/permissions_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/secrets/secrets_pb.js b/src/proto/js/polykey/v1/secrets/secrets_pb.js index 5008028d8..28d2e02ae 100644 --- a/src/proto/js/polykey/v1/secrets/secrets_pb.js +++ b/src/proto/js/polykey/v1/secrets/secrets_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/sessions/sessions_pb.js b/src/proto/js/polykey/v1/sessions/sessions_pb.js index c2d81541f..212d584bc 100644 --- a/src/proto/js/polykey/v1/sessions/sessions_pb.js +++ b/src/proto/js/polykey/v1/sessions/sessions_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/test_service_pb.js b/src/proto/js/polykey/v1/test_service_pb.js index f5ab8f2de..56dd0245c 100644 --- a/src/proto/js/polykey/v1/test_service_pb.js +++ b/src/proto/js/polykey/v1/test_service_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/utils/utils_pb.js b/src/proto/js/polykey/v1/utils/utils_pb.js index 852c0903d..39b5c869e 100644 --- a/src/proto/js/polykey/v1/utils/utils_pb.js +++ b/src/proto/js/polykey/v1/utils/utils_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/vaults/vaults_pb.js b/src/proto/js/polykey/v1/vaults/vaults_pb.js index 6b793dc63..153565a46 100644 --- a/src/proto/js/polykey/v1/vaults/vaults_pb.js +++ b/src/proto/js/polykey/v1/vaults/vaults_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/schemas/polykey/v1/client_service.proto b/src/proto/schemas/polykey/v1/client_service.proto index 57788c678..9c90e0286 100644 --- a/src/proto/schemas/polykey/v1/client_service.proto +++ b/src/proto/schemas/polykey/v1/client_service.proto @@ -22,10 +22,11 @@ service ClientService { rpc AgentUnlock (polykey.v1.utils.EmptyMessage) returns (polykey.v1.utils.EmptyMessage); // Nodes - rpc NodesAdd(polykey.v1.nodes.NodeAddress) returns (polykey.v1.utils.EmptyMessage); + rpc NodesAdd(polykey.v1.nodes.NodeAdd) returns (polykey.v1.utils.EmptyMessage); rpc NodesPing(polykey.v1.nodes.Node) returns (polykey.v1.utils.StatusMessage); rpc NodesClaim(polykey.v1.nodes.Claim) returns (polykey.v1.utils.StatusMessage); rpc NodesFind(polykey.v1.nodes.Node) returns (polykey.v1.nodes.NodeAddress); + rpc NodesGetAll(polykey.v1.utils.EmptyMessage) returns (polykey.v1.nodes.NodeBuckets); // Keys rpc KeysKeyPairRoot (polykey.v1.utils.EmptyMessage) returns (polykey.v1.keys.KeyPair); diff --git a/src/proto/schemas/polykey/v1/nodes/nodes.proto b/src/proto/schemas/polykey/v1/nodes/nodes.proto index 4c5d64a51..cd8b23785 100644 --- a/src/proto/schemas/polykey/v1/nodes/nodes.proto +++ b/src/proto/schemas/polykey/v1/nodes/nodes.proto @@ -25,6 +25,18 @@ message Claim { bool force_invite = 2; } +message NodeAdd { + string node_id = 1; + Address address = 2; + bool force = 3; + bool ping = 4; +} + +// Bucket index -> a node bucket (from NodeGraph) +message NodeBuckets { + map buckets = 1; +} + // Agent specific. message Connection { diff --git a/src/schema/Schema.ts b/src/schema/Schema.ts index 546d81956..b7c66be4c 100644 --- a/src/schema/Schema.ts +++ b/src/schema/Schema.ts @@ -1,9 +1,9 @@ import type { StateVersion } from './types'; import type { FileSystem } from '../types'; - import path from 'path'; import Logger from '@matrixai/logger'; import { CreateDestroyStartStop } from '@matrixai/async-init/dist/CreateDestroyStartStop'; +import { RWLockWriter } from '@matrixai/async-locks'; import * as schemaErrors from './errors'; import * as utils from '../utils'; import config from '../config'; @@ -17,14 +17,12 @@ class Schema { public static async createSchema({ statePath, stateVersion = config.stateVersion as StateVersion, - lock = new utils.RWLock(), fs = require('fs'), logger = new Logger(this.name), fresh = false, }: { statePath: string; stateVersion?: StateVersion; - lock?: utils.RWLock; fs?: FileSystem; logger?: Logger; fresh?: boolean; @@ -33,7 +31,6 @@ class Schema { const schema = new Schema({ statePath, stateVersion, - lock, fs, logger, }); @@ -45,20 +42,18 @@ class Schema { public readonly statePath: string; public readonly stateVersionPath: string; public readonly stateVersion: StateVersion; - protected lock: utils.RWLock; + protected lock: RWLockWriter = new RWLockWriter(); protected fs: FileSystem; protected logger: Logger; public constructor({ statePath, stateVersion = config.stateVersion as StateVersion, - lock = new utils.RWLock(), fs = require('fs'), logger, }: { statePath: string; stateVersion?: StateVersion; - lock?: utils.RWLock; fs?: FileSystem; logger?: Logger; }) { @@ -69,7 +64,6 @@ class Schema { config.defaults.stateVersionBase, ); this.stateVersion = stateVersion; - this.lock = lock; this.fs = fs; } @@ -88,10 +82,13 @@ class Schema { }); } catch (e) { throw new schemaErrors.ErrorSchemaStateDelete(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } } @@ -99,10 +96,13 @@ class Schema { await utils.mkdirExists(this.fs, this.statePath); } catch (e) { throw new schemaErrors.ErrorSchemaStateCreate(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } const stateVersion = await this.readVersion(); @@ -132,17 +132,20 @@ class Schema { }); } catch (e) { throw new schemaErrors.ErrorSchemaStateDelete(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } this.logger.info(`Destroyed ${this.constructor.name}`); } public async readVersion(): Promise { - return await this.lock.withRead(async () => { + return await this.lock.withReadF(async () => { let stateVersionData: string; try { stateVersionData = await this.fs.promises.readFile( @@ -154,10 +157,13 @@ class Schema { return; } throw new schemaErrors.ErrorSchemaVersionRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } const stateVersion = parseInt(stateVersionData.trim()); @@ -169,7 +175,7 @@ class Schema { } protected async writeVersion(stateVersion: StateVersion): Promise { - return await this.lock.withWrite(async () => { + return await this.lock.withWriteF(async () => { try { await this.fs.promises.writeFile( this.stateVersionPath, @@ -178,10 +184,13 @@ class Schema { ); } catch (e) { throw new schemaErrors.ErrorSchemaVersionWrite(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } }); @@ -191,7 +200,7 @@ class Schema { * This is only called when the version is older. */ protected async upgradeVersion(_stateVersion: StateVersion): Promise { - return await this.lock.withWrite(async () => { + return await this.lock.withWriteF(async () => { // TODO: to be implemented throw new schemaErrors.ErrorSchemaVersionTooOld(); }); diff --git a/src/schema/errors.ts b/src/schema/errors.ts index ee5b8b01b..973961386 100644 --- a/src/schema/errors.ts +++ b/src/schema/errors.ts @@ -1,30 +1,60 @@ -import { ErrorPolykey } from '../errors'; +import { ErrorPolykey, sysexits } from '../errors'; -class ErrorSchema extends ErrorPolykey {} +class ErrorSchema extends ErrorPolykey {} -class ErrorSchemaRunning extends ErrorSchema {} +class ErrorSchemaRunning extends ErrorSchema { + static description = 'Schema is running'; + exitCode = sysexits.USAGE; +} -class ErrorSchemaNotRunning extends ErrorSchema {} +class ErrorSchemaNotRunning extends ErrorSchema { + static description = 'Schema is not running'; + exitCode = sysexits.USAGE; +} -class ErrorSchemaDestroyed extends ErrorSchema {} +class ErrorSchemaDestroyed extends ErrorSchema { + static description = 'Schema is destroyed'; + exitCode = sysexits.USAGE; +} -class ErrorSchemaStateCreate extends ErrorSchema {} +class ErrorSchemaStateCreate extends ErrorSchema { + static description = 'Unable to create schema state'; + exitCode = sysexits.IOERR; +} -class ErrorSchemaStateDelete extends ErrorSchema {} +class ErrorSchemaStateDelete extends ErrorSchema { + static description = 'Unable to delete schema state'; + exitCode = sysexits.IOERR; +} -class ErrorSchemaVersionRead extends ErrorSchema {} +class ErrorSchemaVersionRead extends ErrorSchema { + static description = 'Unable to read schema version'; + exitCode = sysexits.IOERR; +} -class ErrorSchemaVersionParse extends ErrorSchema {} +class ErrorSchemaVersionParse extends ErrorSchema { + static description = 'Invalid schema version'; + exitCode = sysexits.IOERR; +} -class ErrorSchemaVersionWrite extends ErrorSchema {} +class ErrorSchemaVersionWrite extends ErrorSchema { + static description = 'Unable to write schema version'; + exitCode = sysexits.IOERR; +} -class ErrorSchemaVersionTooNew extends ErrorSchema {} +class ErrorSchemaVersionTooNew extends ErrorSchema { + static description = 'Invalid state version'; + exitCode = sysexits.USAGE; +} -class ErrorSchemaVersionTooOld extends ErrorSchema {} +class ErrorSchemaVersionTooOld extends ErrorSchema { + static description = 'Unable to upgrade schema version'; + exitCode = sysexits.USAGE; +} -class ErrorSchemaMigrationFail extends ErrorSchema {} +class ErrorSchemaMigrationFail extends ErrorSchema {} -class ErrorSchemaMigrationMissing extends ErrorSchema {} +class ErrorSchemaMigrationMissing extends ErrorSchema {} export { ErrorSchema, diff --git a/src/sessions/SessionManager.ts b/src/sessions/SessionManager.ts index 6ee4bbbc9..f7e618a0b 100644 --- a/src/sessions/SessionManager.ts +++ b/src/sessions/SessionManager.ts @@ -1,17 +1,16 @@ -import type { DB, DBLevel } from '@matrixai/db'; +import type { DB, DBTransaction, LevelPath } from '@matrixai/db'; import type { SessionToken } from './types'; -import type { KeyManager } from '../keys'; - +import type KeyManager from '../keys/KeyManager'; import Logger from '@matrixai/logger'; import { CreateDestroyStartStop, ready, } from '@matrixai/async-init/dist/CreateDestroyStartStop'; -import { Mutex } from 'async-mutex'; +import { withF } from '@matrixai/resources'; import * as sessionsUtils from './utils'; import * as sessionsErrors from './errors'; import * as keysUtils from '../keys/utils'; -import { utils as nodesUtils } from '../nodes'; +import * as nodesUtils from '../nodes/utils'; interface SessionManager extends CreateDestroyStartStop {} @CreateDestroyStartStop( @@ -53,10 +52,7 @@ class SessionManager { protected logger: Logger; protected db: DB; protected keyManager: KeyManager; - protected sessionsDbDomain: string = this.constructor.name; - protected sessionsDb: DBLevel; - protected lock: Mutex = new Mutex(); - protected key: Uint8Array; + protected sessionsDbPath: LevelPath = [this.constructor.name]; public constructor({ db, @@ -78,23 +74,16 @@ class SessionManager { this.keyBits = keyBits; } - get locked(): boolean { - return this.lock.isLocked(); - } - public async start({ fresh = false, }: { fresh?: boolean; } = {}): Promise { this.logger.info(`Starting ${this.constructor.name}`); - const sessionsDb = await this.db.level(this.sessionsDbDomain); if (fresh) { - await sessionsDb.clear(); + await this.db.clear(this.sessionsDbPath); } - const key = await this.setupKey(this.keyBits); - this.sessionsDb = sessionsDb; - this.key = key; + await this.setupKey(this.keyBits); this.logger.info(`Started ${this.constructor.name}`); } @@ -105,44 +94,24 @@ class SessionManager { public async destroy() { this.logger.info(`Destroying ${this.constructor.name}`); - const sessionsDb = await this.db.level(this.sessionsDbDomain); - await sessionsDb.clear(); + await this.db.clear(this.sessionsDbPath); this.logger.info(`Destroyed ${this.constructor.name}`); } - /** - * Run several operations within the same lock - * This does not ensure atomicity of the underlying database - * Database atomicity still depends on the underlying operation - */ - public async transaction(f: (that: this) => Promise): Promise { - const release = await this.lock.acquire(); - try { - return await f(this); - } finally { - release(); - } - } - - /** - * Transaction wrapper that will not lock if the operation was executed - * within a transaction context - */ - protected async _transaction(f: () => Promise): Promise { - if (this.locked) { - return await f(); - } else { - return await this.transaction(f); - } + @ready(new sessionsErrors.ErrorSessionManagerNotRunning()) + public async withTransactionF( + f: (tran: DBTransaction) => Promise, + ): Promise { + return withF([this.db.transaction()], ([tran]) => f(tran)); } @ready(new sessionsErrors.ErrorSessionManagerNotRunning()) - public async resetKey(): Promise { - await this._transaction(async () => { - const key = await this.generateKey(this.keyBits); - await this.db.put([this.sessionsDbDomain], 'key', key, true); - this.key = key; - }); + public async resetKey(tran?: DBTransaction): Promise { + if (tran == null) { + return this.withTransactionF(async (tran) => this.resetKey(tran)); + } + const key = await this.generateKey(this.keyBits); + await tran.put([...this.sessionsDbPath, 'key'], key, true); } /** @@ -153,35 +122,49 @@ class SessionManager { @ready(new sessionsErrors.ErrorSessionManagerNotRunning()) public async createToken( expiry: number | undefined = this.expiry, + tran?: DBTransaction, ): Promise { + if (tran == null) { + return this.withTransactionF(async (tran) => + this.createToken(expiry, tran), + ); + } const payload = { iss: nodesUtils.encodeNodeId(this.keyManager.getNodeId()), sub: nodesUtils.encodeNodeId(this.keyManager.getNodeId()), }; - const token = await sessionsUtils.createSessionToken( - payload, - this.key, - expiry, - ); + const key = await tran.get([...this.sessionsDbPath, 'key'], true); + const token = await sessionsUtils.createSessionToken(payload, key!, expiry); return token; } @ready(new sessionsErrors.ErrorSessionManagerNotRunning()) - public async verifyToken(token: SessionToken): Promise { - const result = await sessionsUtils.verifySessionToken(token, this.key); + public async verifyToken( + token: SessionToken, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.withTransactionF(async (tran) => + this.verifyToken(token, tran), + ); + } + const key = await tran.get([...this.sessionsDbPath, 'key'], true); + const result = await sessionsUtils.verifySessionToken(token, key!); return result !== undefined; } protected async setupKey(bits: 128 | 192 | 256): Promise { - let key: Buffer | undefined; - key = await this.db.get([this.sessionsDbDomain], 'key', true); - if (key != null) { + return await withF([this.db.transaction()], async ([tran]) => { + let key: Buffer | undefined; + key = await tran.get([...this.sessionsDbPath, 'key'], true); + if (key != null) { + return key; + } + this.logger.info('Generating sessions key'); + key = await this.generateKey(bits); + await tran.put([...this.sessionsDbPath, 'key'], key, true); return key; - } - this.logger.info('Generating sessions key'); - key = await this.generateKey(bits); - await this.db.put([this.sessionsDbDomain], 'key', key, true); - return key; + }); } protected async generateKey(bits: 128 | 192 | 256): Promise { diff --git a/src/sessions/errors.ts b/src/sessions/errors.ts index 837020898..588ff9036 100644 --- a/src/sessions/errors.ts +++ b/src/sessions/errors.ts @@ -1,18 +1,36 @@ -import { ErrorPolykey } from '../errors'; - -class ErrorSessions extends ErrorPolykey {} - -class ErrorSessionRunning extends ErrorSessions {} - -class ErrorSessionNotRunning extends ErrorSessions {} - -class ErrorSessionDestroyed extends ErrorSessions {} - -class ErrorSessionManagerRunning extends ErrorSessions {} - -class ErrorSessionManagerNotRunning extends ErrorSessions {} - -class ErrorSessionManagerDestroyed extends ErrorSessions {} +import { ErrorPolykey, sysexits } from '../errors'; + +class ErrorSessions extends ErrorPolykey {} + +class ErrorSessionRunning extends ErrorSessions { + static description = 'Session is running'; + exitCode = sysexits.USAGE; +} + +class ErrorSessionNotRunning extends ErrorSessions { + static description = 'Session is not running'; + exitCode = sysexits.USAGE; +} + +class ErrorSessionDestroyed extends ErrorSessions { + static description = 'Session is destroyed'; + exitCode = sysexits.USAGE; +} + +class ErrorSessionManagerRunning extends ErrorSessions { + static description = 'SessionManager is running'; + exitCode = sysexits.USAGE; +} + +class ErrorSessionManagerNotRunning extends ErrorSessions { + static description = 'SessionManager is not running'; + exitCode = sysexits.USAGE; +} + +class ErrorSessionManagerDestroyed extends ErrorSessions { + static description = 'SessionManager is destroyed'; + exitCode = sysexits.USAGE; +} export { ErrorSessions, diff --git a/src/sigchain/Sigchain.ts b/src/sigchain/Sigchain.ts index 5d5744bf4..da543b82b 100644 --- a/src/sigchain/Sigchain.ts +++ b/src/sigchain/Sigchain.ts @@ -1,4 +1,4 @@ -import type { DB, DBLevel, DBOp } from '@matrixai/db'; +import type { DB, DBTransaction, KeyPath, LevelPath } from '@matrixai/db'; import type { ChainDataEncoded } from './types'; import type { ClaimData, @@ -8,15 +8,16 @@ import type { ClaimIntermediary, ClaimType, } from '../claims/types'; -import type { KeyManager } from '../keys'; +import type KeyManager from '../keys/KeyManager'; import type { NodeIdEncoded } from '../nodes/types'; import Logger from '@matrixai/logger'; import { IdInternal } from '@matrixai/id'; -import { Mutex } from 'async-mutex'; import { CreateDestroyStartStop, ready, } from '@matrixai/async-init/dist/CreateDestroyStartStop'; +import { Lock, LockBox } from '@matrixai/async-locks'; +import { withF } from '@matrixai/resources'; import * as sigchainErrors from './errors'; import * as claimsUtils from '../claims/utils'; @@ -26,30 +27,23 @@ interface Sigchain extends CreateDestroyStartStop {} new sigchainErrors.ErrorSigchainDestroyed(), ) class Sigchain { - public readonly sigchainPath: string; - public readonly sigchainDbPath: string; protected readonly sequenceNumberKey: string = 'prevSequenceNumber'; protected logger: Logger; protected keyManager: KeyManager; protected db: DB; - protected sigchainDbDomain: string = this.constructor.name; - protected sigchainClaimsDbDomain: Array = [ - this.sigchainDbDomain, - 'claims', - ]; - protected sigchainMetadataDbDomain: Array = [ - this.sigchainDbDomain, - 'metadata', - ]; - protected sigchainDb: DBLevel; + protected locks: LockBox = new LockBox(); + // Top-level database for the sigchain domain + protected sigchainDbPath: LevelPath = [this.constructor.name]; // ClaimId (the lexicographic integer of the sequence number) // -> ClaimEncoded (a JWS in General JSON Serialization) - protected sigchainClaimsDb: DBLevel; + protected sigchainClaimsDbPath: LevelPath = [this.constructor.name, 'claims']; // Sub-level database for numerical metadata to be persisted // e.g. "sequenceNumber" -> current sequence number - protected sigchainMetadataDb: DBLevel; - protected lock: Mutex = new Mutex(); + protected sigchainMetadataDbPath: LevelPath = [ + this.constructor.name, + 'metadata', + ]; protected generateClaimId: ClaimIdGenerator; @@ -85,61 +79,37 @@ class Sigchain { this.keyManager = keyManager; } - get locked(): boolean { - return this.lock.isLocked(); - } - public async start({ fresh = false, }: { fresh?: boolean; } = {}): Promise { this.logger.info(`Starting ${this.constructor.name}`); - // Top-level database for the sigchain domain - const sigchainDb = await this.db.level(this.sigchainDbDomain); - // ClaimId (the lexicographic integer of the sequence number) - // -> ClaimEncoded (a JWS in General JSON Serialization) - const sigchainClaimsDb = await this.db.level( - this.sigchainClaimsDbDomain[1], - sigchainDb, - ); - // Sub-level database for numerical metadata to be persisted - // e.g. "sequenceNumber" -> current sequence number - const sigchainMetadataDb = await this.db.level( - this.sigchainMetadataDbDomain[1], - sigchainDb, - ); if (fresh) { - await sigchainDb.clear(); + await this.db.clear(this.sigchainDbPath); } - this.sigchainDb = sigchainDb; - this.sigchainClaimsDb = sigchainClaimsDb; - this.sigchainMetadataDb = sigchainMetadataDb; - // Initialise the sequence number (if not already done). // First claim in the sigchain has a sequence number of 1. // Therefore, with no claims in the sigchain, the previous sequence number // is set to 0. - await this._transaction(async () => { - const sequenceNumber = await this.db.get( - this.sigchainMetadataDbDomain, + await withF([this.db.transaction()], async ([tran]) => { + const sequenceNumber = await tran.get([ + ...this.sigchainMetadataDbPath, this.sequenceNumberKey, - ); + ]); if (sequenceNumber == null) { - await this.db.put( - this.sigchainMetadataDbDomain, - this.sequenceNumberKey, + await tran.put( + [...this.sigchainMetadataDbPath, this.sequenceNumberKey], 0, ); } + // Creating the ID generator + const latestId = await this.getLatestClaimId(tran); + this.generateClaimId = claimsUtils.createClaimIdGenerator( + this.keyManager.getNodeId(), + latestId, + ); }); - - // Creating the ID generator - const latestId = await this.getLatestClaimId(); - this.generateClaimId = claimsUtils.createClaimIdGenerator( - this.keyManager.getNodeId(), - latestId, - ); this.logger.info(`Started ${this.constructor.name}`); } @@ -150,35 +120,22 @@ class Sigchain { public async destroy() { this.logger.info(`Destroying ${this.constructor.name}`); - const sigchainDb = await this.db.level(this.sigchainDbDomain); - await sigchainDb.clear(); + await this.db.clear(this.sigchainDbPath); this.logger.info(`Destroyed ${this.constructor.name}`); } - /** - * Run several operations within the same lock - * This does not ensure atomicity of the underlying database - * Database atomicity still depends on the underlying operation - */ - public async transaction(f: (that: this) => Promise): Promise { - const release = await this.lock.acquire(); - try { - return await f(this); - } finally { - release(); - } - } - - /** - * Transaction wrapper that will not lock if the operation was executed - * within a transaction context - */ - protected async _transaction(f: () => Promise): Promise { - if (this.lock.isLocked()) { - return await f(); - } else { - return await this.transaction(f); - } + @ready(new sigchainErrors.ErrorSigchainNotRunning()) + public async withTransactionF( + ...params: [...keys: Array, f: (tran: DBTransaction) => Promise] + ): Promise { + const f = params.pop() as (tran: DBTransaction) => Promise; + const lockRequests = (params as Array).map<[KeyPath, typeof Lock]>( + (key) => [key, Lock], + ); + return withF( + [this.db.transaction(), this.locks.lock(...lockRequests)], + ([tran]) => f(tran), + ); } /** @@ -220,36 +177,32 @@ class Sigchain { @ready(new sigchainErrors.ErrorSigchainNotRunning()) public async addClaim( claimData: ClaimData, + tran?: DBTransaction, ): Promise<[ClaimId, ClaimEncoded]> { - return await this._transaction(async () => { - const prevSequenceNumber = await this.getSequenceNumber(); - const newSequenceNumber = prevSequenceNumber + 1; - - const claim = await this.createClaim({ - hPrev: await this.getHashPrevious(), - seq: newSequenceNumber, - data: claimData, - }); - - // Add the claim to the sigchain database, and update the sequence number - const claimId = this.generateClaimId(); - const ops: Array = [ - { - type: 'put', - domain: this.sigchainClaimsDbDomain, - key: claimId.toBuffer(), - value: claim, - }, - { - type: 'put', - domain: this.sigchainMetadataDbDomain, - key: this.sequenceNumberKey, - value: newSequenceNumber, - }, - ]; - await this.db.batch(ops); - return [claimId, claim]; + const claimId = this.generateClaimId(); + const claimIdPath = [...this.sigchainClaimsDbPath, claimId.toBuffer()]; + const sequenceNumberPath = [ + ...this.sigchainMetadataDbPath, + this.sequenceNumberKey, + ]; + if (tran == null) { + return this.withTransactionF( + claimIdPath, + sequenceNumberPath, + async (tran) => this.addClaim(claimData, tran), + ); + } + const prevSequenceNumber = await this.getSequenceNumber(tran); + const newSequenceNumber = prevSequenceNumber + 1; + const claim = await this.createClaim({ + hPrev: await this.getHashPrevious(tran), + seq: newSequenceNumber, + data: claimData, }); + // Add the claim to the sigchain database, and update the sequence number + await tran.put(claimIdPath, claim); + await tran.put(sequenceNumberPath, newSequenceNumber); + return [claimId, claim]; } /** @@ -261,34 +214,35 @@ class Sigchain { * an exception could be thrown. */ @ready(new sigchainErrors.ErrorSigchainNotRunning()) - public async addExistingClaim(claim: ClaimEncoded): Promise { - await this._transaction(async () => { - const decodedClaim = claimsUtils.decodeClaim(claim); - const prevSequenceNumber = await this.getSequenceNumber(); - const expectedSequenceNumber = prevSequenceNumber + 1; - // Ensure the sequence number and hash are correct before appending - if (decodedClaim.payload.seq !== expectedSequenceNumber) { - throw new sigchainErrors.ErrorSigchainInvalidSequenceNum(); - } - if (decodedClaim.payload.hPrev !== (await this.getHashPrevious())) { - throw new sigchainErrors.ErrorSigchainInvalidHash(); - } - const ops: Array = [ - { - type: 'put', - domain: this.sigchainClaimsDbDomain, - key: this.generateClaimId().toBuffer(), - value: claim, - }, - { - type: 'put', - domain: this.sigchainMetadataDbDomain, - key: this.sequenceNumberKey, - value: expectedSequenceNumber, - }, - ]; - await this.db.batch(ops); - }); + public async addExistingClaim( + claim: ClaimEncoded, + tran?: DBTransaction, + ): Promise { + const claimId = this.generateClaimId(); + const claimIdPath = [...this.sigchainClaimsDbPath, claimId.toBuffer()]; + const sequenceNumberPath = [ + ...this.sigchainMetadataDbPath, + this.sequenceNumberKey, + ]; + if (tran == null) { + return this.withTransactionF( + claimIdPath, + sequenceNumberPath, + async (tran) => this.addExistingClaim(claim, tran), + ); + } + const decodedClaim = claimsUtils.decodeClaim(claim); + const prevSequenceNumber = await this.getSequenceNumber(tran); + const expectedSequenceNumber = prevSequenceNumber + 1; + // Ensure the sequence number and hash are correct before appending + if (decodedClaim.payload.seq !== expectedSequenceNumber) { + throw new sigchainErrors.ErrorSigchainInvalidSequenceNum(); + } + if (decodedClaim.payload.hPrev !== (await this.getHashPrevious(tran))) { + throw new sigchainErrors.ErrorSigchainInvalidHash(); + } + await tran.put(claimIdPath, claim); + await tran.put(sequenceNumberPath, expectedSequenceNumber); } /** @@ -298,19 +252,26 @@ class Sigchain { @ready(new sigchainErrors.ErrorSigchainNotRunning()) public async createIntermediaryClaim( claimData: ClaimData, + tran?: DBTransaction, ): Promise { - return await this._transaction(async () => { - const claim = await this.createClaim({ - hPrev: await this.getHashPrevious(), - seq: (await this.getSequenceNumber()) + 1, - data: claimData, - }); - const intermediaryClaim: ClaimIntermediary = { - payload: claim.payload, - signature: claim.signatures[0], - }; - return intermediaryClaim; + const sequenceNumberPath = [ + ...this.sigchainMetadataDbPath, + this.sequenceNumberKey, + ]; + if (tran == null) { + return this.withTransactionF(sequenceNumberPath, async (tran) => + this.createIntermediaryClaim(claimData, tran), + ); + } + const claim = await this.createClaim({ + hPrev: await this.getHashPrevious(tran), + seq: (await this.getSequenceNumber(tran)) + 1, + data: claimData, }); + return { + payload: claim.payload, + signature: claim.signatures[0], + }; } /** @@ -320,20 +281,20 @@ class Sigchain { * claimUtils.decodeClaim() to decode each claim. */ @ready(new sigchainErrors.ErrorSigchainNotRunning()) - public async getChainData(): Promise { - return await this._transaction(async () => { - const chainData: ChainDataEncoded = {}; - for await (const o of this.sigchainClaimsDb.createReadStream()) { - const claimId = IdInternal.fromBuffer((o as any).key); - const encryptedClaim = (o as any).value; - const claim = await this.db.deserializeDecrypt( - encryptedClaim, - false, - ); - chainData[claimsUtils.encodeClaimId(claimId)] = claim; - } - return chainData; - }); + public async getChainData(tran?: DBTransaction): Promise { + if (tran == null) { + return this.withTransactionF(async (tran) => this.getChainData(tran)); + } + const chainData: ChainDataEncoded = {}; + const readIterator = tran.iterator({ valueAsBuffer: false }, [ + ...this.sigchainClaimsDbPath, + ]); + for await (const [keyPath, claimEncoded] of readIterator) { + const key = keyPath[0] as Buffer; + const claimId = IdInternal.fromBuffer(key); + chainData[claimsUtils.encodeClaimId(claimId)] = claimEncoded; + } + return chainData; } /** @@ -345,22 +306,26 @@ class Sigchain { * requesting client. */ @ready(new sigchainErrors.ErrorSigchainNotRunning()) - public async getClaims(claimType: ClaimType): Promise> { - return await this._transaction(async () => { - const relevantClaims: Array = []; - for await (const o of this.sigchainClaimsDb.createReadStream()) { - const data = (o as any).value; - const claim = await this.db.deserializeDecrypt( - data, - false, - ); - const decodedClaim = claimsUtils.decodeClaim(claim); - if (decodedClaim.payload.data.type === claimType) { - relevantClaims.push(claim); - } + public async getClaims( + claimType: ClaimType, + tran?: DBTransaction, + ): Promise> { + if (tran == null) { + return this.withTransactionF(async (tran) => + this.getClaims(claimType, tran), + ); + } + const relevantClaims: Array = []; + const readIterator = tran.iterator({ valueAsBuffer: false }, [ + ...this.sigchainClaimsDbPath, + ]); + for await (const [, claim] of readIterator) { + const decodedClaim = claimsUtils.decodeClaim(claim); + if (decodedClaim.payload.data.type === claimType) { + relevantClaims.push(claim); } - return relevantClaims; - }); + } + return relevantClaims; } /** @@ -369,37 +334,33 @@ class Sigchain { * @returns previous sequence number */ @ready(new sigchainErrors.ErrorSigchainNotRunning()) - public async getSequenceNumber(): Promise { - return await this._transaction(async () => { - const sequenceNumber = await this.db.get( - this.sigchainMetadataDbDomain, - this.sequenceNumberKey, - ); - // Should never be reached: getSigchainDb() has a check whether sigchain - // has been started (where the sequence number is initialised) - if (sequenceNumber === undefined) { - throw new sigchainErrors.ErrorSigchainSequenceNumUndefined(); - } - return sequenceNumber; - }); + protected async getSequenceNumber(tran: DBTransaction): Promise { + const sequenceNumber = await tran.get([ + ...this.sigchainMetadataDbPath, + this.sequenceNumberKey, + ]); + // Should never be reached: getSigchainDb() has a check whether sigchain + // has been started (where the sequence number is initialised) + if (sequenceNumber === undefined) { + throw new sigchainErrors.ErrorSigchainSequenceNumUndefined(); + } + return sequenceNumber; } /** * Helper function to compute the hash of the previous claim. */ @ready(new sigchainErrors.ErrorSigchainNotRunning()) - public async getHashPrevious(): Promise { - return await this._transaction(async () => { - const prevSequenceNumber = await this.getLatestClaimId(); - if (prevSequenceNumber == null) { - // If no other claims, then null - return null; - } else { - // Otherwise, create a hash of the previous claim - const previousClaim = await this.getClaim(prevSequenceNumber); - return claimsUtils.hashClaim(previousClaim); - } - }); + protected async getHashPrevious(tran: DBTransaction): Promise { + const prevSequenceNumber = await this.getLatestClaimId(tran); + if (prevSequenceNumber == null) { + // If no other claims, then null + return null; + } else { + // Otherwise, create a hash of the previous claim + const previousClaim = await this.getClaim(prevSequenceNumber, tran); + return claimsUtils.hashClaim(previousClaim); + } } /** @@ -408,59 +369,61 @@ class Sigchain { * (otherwise, if you want to check for existence, just use getSigchainDb() * and check if returned value is undefined). * @param claimId the ClaimId of the claim to retrieve + * @param tran * @returns the claim (a JWS) */ @ready(new sigchainErrors.ErrorSigchainNotRunning()) - public async getClaim(claimId: ClaimId): Promise { - return await this._transaction(async () => { - const claim = await this.db.get( - this.sigchainClaimsDbDomain, - claimId.toBuffer(), + public async getClaim( + claimId: ClaimId, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.withTransactionF(async (tran) => + this.getClaim(claimId, tran), ); - if (claim == null) { - throw new sigchainErrors.ErrorSigchainClaimUndefined(); - } - return claim; - }); + } + const claim = await tran.get([ + ...this.sigchainClaimsDbPath, + claimId.toBuffer(), + ]); + if (claim == null) { + throw new sigchainErrors.ErrorSigchainClaimUndefined(); + } + return claim; } @ready(new sigchainErrors.ErrorSigchainNotRunning()) - public async getSeqMap(): Promise> { + public async getSeqMap( + tran?: DBTransaction, + ): Promise> { + if (tran == null) { + return this.withTransactionF(async (tran) => this.getSeqMap(tran)); + } const map: Record = {}; - const claimStream = this.sigchainClaimsDb.createKeyStream(); + const claimStream = tran.iterator({ values: false }, [ + ...this.sigchainClaimsDbPath, + ]); let seq = 1; - for await (const o of claimStream) { - map[seq] = IdInternal.fromBuffer(o as Buffer); + for await (const [keyPath] of claimStream) { + const key = keyPath[0] as Buffer; + map[seq] = IdInternal.fromBuffer(key); seq++; } return map; } - @ready(new sigchainErrors.ErrorSigchainNotRunning()) - public async clearDB() { - await this.sigchainDb.clear(); - - await this._transaction(async () => { - await this.db.put( - this.sigchainMetadataDbDomain, - this.sequenceNumberKey, - 0, - ); - }); - } - - protected async getLatestClaimId(): Promise { - return await this._transaction(async () => { - let latestId: ClaimId | undefined; - const keyStream = this.sigchainClaimsDb.createKeyStream({ - limit: 1, - reverse: true, - }); - for await (const o of keyStream) { - latestId = IdInternal.fromBuffer(o as Buffer); - } - return latestId; - }); + protected async getLatestClaimId( + tran: DBTransaction, + ): Promise { + let latestId: ClaimId | undefined; + const keyStream = tran.iterator( + { limit: 1, reverse: true, values: false }, + [...this.sigchainClaimsDbPath], + ); + for await (const [keyPath] of keyStream) { + latestId = IdInternal.fromBuffer(keyPath[0] as Buffer); + } + return latestId; } } diff --git a/src/sigchain/errors.ts b/src/sigchain/errors.ts index 254c7fef6..0d839c490 100644 --- a/src/sigchain/errors.ts +++ b/src/sigchain/errors.ts @@ -1,26 +1,45 @@ -import { ErrorPolykey } from '../errors'; +import { ErrorPolykey, sysexits } from '../errors'; -class ErrorSigchain extends ErrorPolykey {} +class ErrorSigchain extends ErrorPolykey {} -class ErrorSigchainRunning extends ErrorSigchain {} +class ErrorSigchainRunning extends ErrorSigchain { + static description = 'Sigchain is running'; + exitCode = sysexits.USAGE; +} -class ErrorSigchainNotRunning extends ErrorSigchain {} +class ErrorSigchainNotRunning extends ErrorSigchain { + static description = 'Sigchain is not running'; + exitCode = sysexits.USAGE; +} -class ErrorSigchainDestroyed extends ErrorSigchain {} +class ErrorSigchainDestroyed extends ErrorSigchain { + static description = 'Sigchain is destroyed'; + exitCode = sysexits.USAGE; +} -class ErrorSigchainSequenceNumUndefined extends ErrorSigchain {} +class ErrorSigchainSequenceNumUndefined extends ErrorSigchain { + static description = 'Invalid database state'; + exitCode = sysexits.IOERR; +} -class ErrorSigchainClaimUndefined extends ErrorSigchain {} +class ErrorSigchainClaimUndefined extends ErrorSigchain { + static description = 'Could not retrieve claim'; + exitCode = sysexits.USAGE; +} -class ErrorSigchainInvalidSequenceNum extends ErrorSigchain {} +class ErrorSigchainInvalidSequenceNum extends ErrorSigchain { + static description = 'Claim has invalid sequence number'; + exitCode = sysexits.USAGE; +} -class ErrorSigchainInvalidHash extends ErrorSigchain {} +class ErrorSigchainInvalidHash extends ErrorSigchain { + static description = 'Claim has invalid hash'; + exitCode = sysexits.USAGE; +} -class ErrorSighainClaimVerificationFailed extends ErrorSigchain {} +class ErrorSigchainDecrypt extends ErrorSigchain {} -class ErrorSigchainDecrypt extends ErrorSigchain {} - -class ErrorSigchainParse extends ErrorSigchain {} +class ErrorSigchainParse extends ErrorSigchain {} export { ErrorSigchainRunning, @@ -30,7 +49,6 @@ export { ErrorSigchainClaimUndefined, ErrorSigchainInvalidSequenceNum, ErrorSigchainInvalidHash, - ErrorSighainClaimVerificationFailed, ErrorSigchainDecrypt, ErrorSigchainParse, }; diff --git a/src/sigchain/utils.ts b/src/sigchain/utils.ts index 7f40dd6a3..fe8cc83f8 100644 --- a/src/sigchain/utils.ts +++ b/src/sigchain/utils.ts @@ -19,7 +19,7 @@ async function verifyChainData( continue; } // If verified, add the claim to the decoded chain - decodedChain[claimId] = await claimsUtils.decodeClaim(encodedClaim); + decodedChain[claimId] = claimsUtils.decodeClaim(encodedClaim); } return decodedChain; } diff --git a/src/status/Status.ts b/src/status/Status.ts index 4569f1ed9..01647736c 100644 --- a/src/status/Status.ts +++ b/src/status/Status.ts @@ -112,10 +112,13 @@ class Status { return; } throw new statusErrors.ErrorStatusRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } while (!lock(statusFile.fd)) { @@ -126,10 +129,13 @@ class Status { statusData = (await statusFile.readFile('utf-8')).trim(); } catch (e) { throw new statusErrors.ErrorStatusRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } if (statusData === '') { @@ -139,13 +145,15 @@ class Status { try { statusInfo = JSON.parse(statusData, this.statusReviver); } catch (e) { - throw new statusErrors.ErrorStatusParse('JSON parsing failed'); + throw new statusErrors.ErrorStatusParse('JSON parsing failed', { + cause: e, + }); } if (!statusUtils.statusValidate(statusInfo)) { throw new statusErrors.ErrorStatusParse( 'StatusInfo validation failed', { - errors: statusUtils.statusValidate.errors, + data: { errors: statusUtils.statusValidate.errors }, }, ); } @@ -182,10 +190,13 @@ class Status { ); } catch (e) { throw new statusErrors.ErrorStatusWrite(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } } finally { @@ -207,10 +218,13 @@ class Status { statusFile = await this.fs.promises.open(this.statusPath, 'r+'); } catch (e) { throw new statusErrors.ErrorStatusRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } while (!lock(statusFile.fd)) { @@ -221,23 +235,28 @@ class Status { statusData = (await statusFile.readFile('utf-8')).trim(); } catch (e) { throw new statusErrors.ErrorStatusRead(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } let statusInfo; try { statusInfo = JSON.parse(statusData, this.statusReviver); } catch (e) { - throw new statusErrors.ErrorStatusParse('JSON parsing failed'); + throw new statusErrors.ErrorStatusParse('JSON parsing failed', { + cause: e, + }); } if (!statusUtils.statusValidate(statusInfo)) { throw new statusErrors.ErrorStatusParse( 'StatusInfo validation failed', { - errors: statusUtils.statusValidate.errors, + data: { errors: statusUtils.statusValidate.errors }, }, ); } @@ -256,10 +275,13 @@ class Status { ); } catch (e) { throw new statusErrors.ErrorStatusWrite(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } return statusInfo; @@ -299,7 +321,7 @@ class Status { ); } catch (e) { if (e instanceof errors.ErrorUtilsPollTimeout) { - throw new errors.ErrorStatusTimeout(); + throw new errors.ErrorStatusTimeout(e.message, { cause: e }); } throw e; } diff --git a/src/status/errors.ts b/src/status/errors.ts index 22a2bea08..a56192557 100644 --- a/src/status/errors.ts +++ b/src/status/errors.ts @@ -1,36 +1,39 @@ import { ErrorPolykey, sysexits } from '../errors'; -class ErrorStatus extends ErrorPolykey {} +class ErrorStatus extends ErrorPolykey {} -class ErrorStatusNotRunning extends ErrorStatus {} +class ErrorStatusNotRunning extends ErrorStatus { + static description = 'Status is not running'; + exitCode = sysexits.USAGE; +} -class ErrorStatusLocked extends ErrorStatus { - description = 'Status is locked by another process'; +class ErrorStatusLocked extends ErrorStatus { + static description = 'Status is locked by another process'; exitCode = sysexits.TEMPFAIL; } -class ErrorStatusRead extends ErrorStatus { - description = 'Failed to read status info'; +class ErrorStatusRead extends ErrorStatus { + static description = 'Failed to read status info'; exitCode = sysexits.IOERR; } -class ErrorStatusWrite extends ErrorStatus { - description = 'Failed to write status info'; +class ErrorStatusWrite extends ErrorStatus { + static description = 'Failed to write status info'; exitCode = sysexits.IOERR; } -class ErrorStatusLiveUpdate extends ErrorStatus { - description = 'Failed to update LIVE status info'; +class ErrorStatusLiveUpdate extends ErrorStatus { + static description = 'Failed to update LIVE status info'; exitCode = sysexits.USAGE; } -class ErrorStatusParse extends ErrorStatus { - description = 'Failed to parse status info'; +class ErrorStatusParse extends ErrorStatus { + static description = 'Failed to parse status info'; exitCode = sysexits.CONFIG; } -class ErrorStatusTimeout extends ErrorStatus { - description = 'Poll timed out'; +class ErrorStatusTimeout extends ErrorStatus { + static description = 'Poll timed out'; exitCode = sysexits.TEMPFAIL; } diff --git a/src/types.ts b/src/types.ts index b09954b32..fae58ae01 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,7 +10,21 @@ type POJO = { [key: string]: any }; * Opaque types are wrappers of existing types * that require smart constructors */ -type Opaque = T & { __TYPE__: K }; +type Opaque = T & { readonly [brand]: K }; +declare const brand: unique symbol; + +/** + * Generic callback + */ +type Callback

= [], R = any, E extends Error = Error> = { + (e: E, ...params: Partial

): R; + (e?: null | undefined, ...params: P): R; +}; + +/** + * Non-empty array + */ +type NonEmptyArray = [T, ...T[]]; /** * Allows extension of constructors that use POJOs @@ -72,9 +86,29 @@ interface FileSystem { type FileHandle = fs.promises.FileHandle; +type FunctionPropertyNames = { + [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never; +}[keyof T]; + +/** + * Functional properties of an object + */ +type FunctionProperties = Pick>; + +type NonFunctionPropertyNames = { + [K in keyof T]: T[K] extends (...args: any[]) => any ? never : K; +}[keyof T]; + +/** + * Non-functional properties of an object + */ +type NonFunctionProperties = Pick>; + export type { POJO, Opaque, + Callback, + NonEmptyArray, AbstractConstructorParameters, Initial, InitialParameters, @@ -83,4 +117,6 @@ export type { Timer, FileSystem, FileHandle, + FunctionProperties, + NonFunctionProperties, }; diff --git a/src/utils/context.ts b/src/utils/context.ts deleted file mode 100644 index d4102debc..000000000 --- a/src/utils/context.ts +++ /dev/null @@ -1,75 +0,0 @@ -type ResourceAcquire = () => Promise< - readonly [ResourceRelease, Resource?] ->; - -type ResourceRelease = () => Promise; - -type Resources[]> = { - [K in keyof T]: T[K] extends ResourceAcquire ? R : never; -}; - -/** - * Make sure to explicitly declare or cast `acquires` as a tuple using `[ResourceAcquire...]` or `as const` - */ -async function withF< - ResourceAcquires extends - | readonly [ResourceAcquire] - | readonly ResourceAcquire[], - T, ->( - acquires: ResourceAcquires, - f: (resources: Resources) => Promise, -): Promise { - const releases: Array = []; - const resources: Array = []; - try { - for (const acquire of acquires) { - const [release, resource] = await acquire(); - releases.push(release); - resources.push(resource); - } - return await f(resources as unknown as Resources); - } finally { - releases.reverse(); - for (const release of releases) { - await release(); - } - } -} - -/** - * Make sure to explicitly declare or cast `acquires` as a tuple using `[ResourceAcquire...]` or `as const` - */ -async function* withG< - ResourceAcquires extends - | readonly [ResourceAcquire] - | readonly ResourceAcquire[], - T = unknown, - TReturn = any, - TNext = unknown, ->( - acquires: ResourceAcquires, - g: ( - resources: Resources, - ) => AsyncGenerator, -): AsyncGenerator { - const releases: Array = []; - const resources: Array = []; - try { - for (const acquire of acquires) { - const [release, resource] = await acquire(); - releases.push(release); - resources.push(resource); - } - return yield* g(resources as unknown as Resources); - } finally { - releases.reverse(); - for (const release of releases) { - await release(); - } - } -} - -export { withF, withG }; - -export type { ResourceAcquire, ResourceRelease }; diff --git a/src/utils/errors.ts b/src/utils/errors.ts index af54ffa86..23ea67744 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -1,25 +1,25 @@ import sysexits from './sysexits'; import ErrorPolykey from '../ErrorPolykey'; -class ErrorUtils extends ErrorPolykey {} +class ErrorUtils extends ErrorPolykey {} /** * This is a special error that is only used for absurd situations * Intended to placate typescript so that unreachable code type checks * If this is thrown, this means there is a bug in the code */ -class ErrorUtilsUndefinedBehaviour extends ErrorUtils { - description = 'You should never see this error'; +class ErrorUtilsUndefinedBehaviour extends ErrorUtils { + static description = 'You should never see this error'; exitCode = sysexits.SOFTWARE; } -class ErrorUtilsPollTimeout extends ErrorUtils { - description = 'Poll timed out'; +class ErrorUtilsPollTimeout extends ErrorUtils { + static description = 'Poll timed out'; exitCode = sysexits.TEMPFAIL; } -class ErrorUtilsNodePath extends ErrorUtils { - description = 'Cannot derive default node path from unknown platform'; +class ErrorUtilsNodePath extends ErrorUtils { + static description = 'Cannot derive default node path from unknown platform'; exitCode = sysexits.USAGE; } diff --git a/src/utils/index.ts b/src/utils/index.ts index cbb38a8be..2ee8414ff 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,7 +1,6 @@ export { default as sysexits } from './sysexits'; -export * from './locks'; -export * from './context'; export * from './utils'; export * from './matchers'; export * from './binary'; +export * from './random'; export * as errors from './errors'; diff --git a/src/utils/locks.ts b/src/utils/locks.ts deleted file mode 100644 index eb6f95245..000000000 --- a/src/utils/locks.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { MutexInterface } from 'async-mutex'; -import { Mutex } from 'async-mutex'; - -/** - * Single threaded write-preferring read write lock - */ -class RWLock { - protected readersLock: Mutex = new Mutex(); - protected writersLock: Mutex = new Mutex(); - protected readersRelease: MutexInterface.Releaser; - protected readerCountBlocked: number = 0; - protected _readerCount: number = 0; - protected _writerCount: number = 0; - - public get readerCount(): number { - return this._readerCount + this.readerCountBlocked; - } - - public get writerCount(): number { - return this._writerCount; - } - - public async withRead(f: () => Promise): Promise { - const release = await this.acquireRead(); - try { - return await f(); - } finally { - release(); - } - } - - public async withWrite(f: () => Promise): Promise { - const release = await this.acquireWrite(); - try { - return await f(); - } finally { - release(); - } - } - - public async acquireRead(): Promise<() => void> { - if (this._writerCount > 0) { - ++this.readerCountBlocked; - await this.writersLock.waitForUnlock(); - --this.readerCountBlocked; - } - const readerCount = ++this._readerCount; - // The first reader locks - if (readerCount === 1) { - this.readersRelease = await this.readersLock.acquire(); - } - return () => { - const readerCount = --this._readerCount; - // The last reader unlocks - if (readerCount === 0) { - this.readersRelease(); - } - }; - } - - public async acquireWrite(): Promise<() => void> { - ++this._writerCount; - const writersRelease = await this.writersLock.acquire(); - this.readersRelease = await this.readersLock.acquire(); - return () => { - this.readersRelease(); - writersRelease(); - --this._writerCount; - }; - } - - public isLocked(): boolean { - return this.readersLock.isLocked() || this.writersLock.isLocked(); - } - - public async waitForUnlock(): Promise { - await Promise.all([ - this.readersLock.waitForUnlock(), - this.writersLock.waitForUnlock(), - ]); - return; - } -} - -export { RWLock }; diff --git a/src/utils/random.ts b/src/utils/random.ts new file mode 100644 index 000000000..fa0c3ecda --- /dev/null +++ b/src/utils/random.ts @@ -0,0 +1,11 @@ +/** + * Gets a random number between min (inc) and max (exc) + * This is not cryptographically-secure + */ +function getRandomInt(min: number, max: number) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +export { getRandomInt }; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 6b4ca4759..0a1519d19 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,4 +1,4 @@ -import type { FileSystem, Timer } from '../types'; +import type { FileSystem, Timer, Callback } from '../types'; import os from 'os'; import process from 'process'; import path from 'path'; @@ -138,30 +138,48 @@ async function poll( /** * Convert callback-style to promise-style + * If this is applied to overloaded function + * it will only choose one of the function signatures to use */ -function promisify(f): (...args: any[]) => Promise { - return function (...args): Promise { +function promisify< + T extends Array, + P extends Array, + R extends T extends [] ? void : T extends [unknown] ? T[0] : T, +>( + f: (...args: [...params: P, callback: Callback]) => unknown, +): (...params: P) => Promise { + // Uses a regular function so that `this` can be bound + return function (...params: P): Promise { return new Promise((resolve, reject) => { const callback = (error, ...values) => { if (error != null) { return reject(error); } - return resolve(values.length === 1 ? values[0] : values); + if (values.length === 0) { + (resolve as () => void)(); + } else if (values.length === 1) { + resolve(values[0] as R); + } else { + resolve(values as R); + } + return; }; - args.push(callback); - f.apply(this, args); + params.push(callback); + f.apply(this, params); }); }; } -/** - * Deconstructed promise - */ -function promise(): { +type PromiseDeconstructed = { p: Promise; resolveP: (value: T | PromiseLike) => void; rejectP: (reason?: any) => void; -} { +}; + +/** + * Deconstructed promise + */ +function promise(): PromiseDeconstructed { let resolveP, rejectP; const p = new Promise((resolve, reject) => { resolveP = resolve; @@ -220,6 +238,67 @@ function arrayZipWithPadding( ]); } +async function asyncIterableArray( + iterable: AsyncIterable, +): Promise> { + const arr: Array = []; + for await (const item of iterable) { + arr.push(item); + } + return arr; +} + +function bufferSplit( + input: Buffer, + delimiter?: Buffer, + limit?: number, + remaining: boolean = false, +): Array { + const output: Array = []; + let delimiterOffset = 0; + let delimiterIndex = 0; + let i = 0; + if (delimiter != null) { + while (true) { + if (i === limit) break; + delimiterIndex = input.indexOf(delimiter, delimiterOffset); + if (delimiterIndex > -1) { + output.push(input.subarray(delimiterOffset, delimiterIndex)); + delimiterOffset = delimiterIndex + delimiter.byteLength; + } else { + const chunk = input.subarray(delimiterOffset); + output.push(chunk); + delimiterOffset += chunk.byteLength; + break; + } + i++; + } + } else { + for (; delimiterIndex < input.byteLength; ) { + if (i === limit) break; + delimiterIndex++; + const chunk = input.subarray(delimiterOffset, delimiterIndex); + output.push(chunk); + delimiterOffset += chunk.byteLength; + i++; + } + } + // If remaining, then the rest of the input including delimiters is extracted + if ( + remaining && + limit != null && + output.length > 0 && + delimiterIndex > -1 && + delimiterIndex <= input.byteLength + ) { + const inputRemaining = input.subarray( + delimiterIndex - output[output.length - 1].byteLength, + ); + output[output.length - 1] = inputRemaining; + } + return output; +} + function debounce

( f: (...params: P) => any, timeout: number = 0, @@ -231,6 +310,7 @@ function debounce

( }; } +export type { PromiseDeconstructed }; export { getDefaultNodePath, never, @@ -250,5 +330,7 @@ export { arrayUnset, arrayZip, arrayZipWithPadding, + asyncIterableArray, + bufferSplit, debounce, }; diff --git a/src/validation/errors.ts b/src/validation/errors.ts index e918ce38b..da01aff34 100644 --- a/src/validation/errors.ts +++ b/src/validation/errors.ts @@ -1,12 +1,12 @@ -import { CustomError } from 'ts-custom-error'; +import { AbstractError } from '@matrixai/errors'; import { ErrorPolykey, sysexits } from '../errors'; /** * Generic error containing all parsing errors that occurred during * execution. */ -class ErrorValidation extends ErrorPolykey { - description = 'Input data failed validation'; +class ErrorValidation extends ErrorPolykey { + static description = 'Input data failed validation'; exitCode = sysexits.DATAERR; public errors: Array; constructor(message, data) { @@ -51,13 +51,12 @@ class ErrorValidation extends ErrorPolykey { * While JS allows us to throw POJOs directly, having a nominal type * is easier to check against */ -class ErrorParse extends CustomError { +class ErrorParse extends AbstractError { + static description: string = 'Failed to parse data into valid format'; + exitCode = sysexits.DATAERR; public keyPath: Array; public value: any; public context: object; - constructor(message?: string) { - super(message); - } } export { ErrorValidation, ErrorParse }; diff --git a/src/validation/utils.ts b/src/validation/utils.ts index 3ce13f258..8197348a9 100644 --- a/src/validation/utils.ts +++ b/src/validation/utils.ts @@ -165,7 +165,7 @@ function parseHostOrHostname(data: any): Host | Hostname { * Parses number into a Port * Data can be a string-number */ -function parsePort(data: any): Port { +function parsePort(data: any, connect: boolean = false): Port { if (typeof data === 'string') { try { data = parseInteger(data); @@ -176,10 +176,16 @@ function parsePort(data: any): Port { throw e; } } - if (!networkUtils.isPort(data)) { - throw new validationErrors.ErrorParse( - 'Port must be a number between 0 and 65535 inclusive', - ); + if (!networkUtils.isPort(data, connect)) { + if (!connect) { + throw new validationErrors.ErrorParse( + 'Port must be a number between 0 and 65535 inclusive', + ); + } else { + throw new validationErrors.ErrorParse( + 'Port must be a number between 1 and 65535 inclusive', + ); + } } return data; } @@ -222,14 +228,14 @@ function parseSeedNodes(data: any): [SeedNodes, boolean] { } let seedNodeUrl: URL; try { - seedNodeUrl = new URL(`pk://${seedNodeString}`); + const seedNodeStringProtocol = /^pk:\/\//.test(seedNodeString) + ? seedNodeString + : `pk://${seedNodeString}`; + seedNodeUrl = new URL(seedNodeStringProtocol); } catch (e) { - if (e instanceof TypeError) { - throw new validationErrors.ErrorParse( - 'Seed nodes must be of format `nodeId@host:port;...`', - ); - } - throw e; + throw new validationErrors.ErrorParse( + 'Seed nodes must be of format `nodeId@host:port;...`', + ); } const nodeIdEncoded = seedNodeUrl.username; // Remove square braces for IPv6 diff --git a/src/vaults/VaultInternal.ts b/src/vaults/VaultInternal.ts index 63611b1a8..b5e32da06 100644 --- a/src/vaults/VaultInternal.ts +++ b/src/vaults/VaultInternal.ts @@ -1,6 +1,6 @@ import type { ReadCommitResult } from 'isomorphic-git'; import type { EncryptedFS } from 'encryptedfs'; -import type { DB, DBDomain, DBLevel } from '@matrixai/db'; +import type { DB, DBTransaction, LevelPath } from '@matrixai/db'; import type { CommitId, CommitLog, @@ -15,7 +15,6 @@ import type { import type KeyManager from '../keys/KeyManager'; import type { NodeId, NodeIdEncoded } from '../nodes/types'; import type NodeConnectionManager from '../nodes/NodeConnectionManager'; -import type { ResourceAcquire } from '../utils/context'; import type GRPCClientAgent from '../agent/GRPCClientAgent'; import type { POJO } from '../types'; import path from 'path'; @@ -26,17 +25,17 @@ import { CreateDestroyStartStop, ready, } from '@matrixai/async-init/dist/CreateDestroyStartStop'; +import { withF, withG } from '@matrixai/resources'; +import { RWLockWriter } from '@matrixai/async-locks'; import * as vaultsErrors from './errors'; import * as vaultsUtils from './utils'; import { tagLast } from './types'; import * as nodesUtils from '../nodes/utils'; import * as validationUtils from '../validation/utils'; -import { withF, withG } from '../utils/context'; -import { RWLock } from '../utils/locks'; import * as vaultsPB from '../proto/js/polykey/v1/vaults/vaults_pb'; import { never } from '../utils/utils'; -export type RemoteInfo = { +type RemoteInfo = { remoteNode: NodeIdEncoded; remoteVault: VaultIdEncoded; }; @@ -51,35 +50,50 @@ class VaultInternal { vaultId, vaultName, db, - vaultsDb, - vaultsDbDomain, + vaultsDbPath, keyManager, efs, logger = new Logger(this.name), fresh = false, + tran, }: { vaultId: VaultId; vaultName?: VaultName; db: DB; - vaultsDb: DBLevel; - vaultsDbDomain: DBDomain; + vaultsDbPath: LevelPath; keyManager: KeyManager; efs: EncryptedFS; logger?: Logger; fresh?: boolean; + tran?: DBTransaction; }): Promise { + if (tran == null) { + return await db.withTransactionF(async (tran) => + this.createVaultInternal({ + vaultId, + vaultName, + db, + vaultsDbPath, + keyManager, + efs, + logger, + fresh, + tran, + }), + ); + } + const vaultIdEncoded = vaultsUtils.encodeVaultId(vaultId); logger.info(`Creating ${this.name} - ${vaultIdEncoded}`); const vault = new VaultInternal({ vaultId, db, - vaultsDb, - vaultsDbDomain, + vaultsDbPath, keyManager, efs, logger, }); - await vault.start({ fresh, vaultName }); + await vault.start({ fresh, vaultName, tran }); logger.info(`Created ${this.name} - ${vaultIdEncoded}`); return vault; } @@ -89,31 +103,47 @@ class VaultInternal { targetVaultNameOrId, vaultId, db, - vaultsDb, - vaultsDbDomain, + vaultsDbPath, keyManager, nodeConnectionManager, efs, logger = new Logger(this.name), + tran, }: { targetNodeId: NodeId; targetVaultNameOrId: VaultId | VaultName; vaultId: VaultId; db: DB; - vaultsDb: DBLevel; - vaultsDbDomain: DBDomain; + vaultsDbPath: LevelPath; efs: EncryptedFS; keyManager: KeyManager; nodeConnectionManager: NodeConnectionManager; logger?: Logger; + tran?: DBTransaction; }): Promise { + if (tran == null) { + return await db.withTransactionF(async (tran) => + this.cloneVaultInternal({ + targetNodeId, + targetVaultNameOrId, + vaultId, + db, + vaultsDbPath, + keyManager, + nodeConnectionManager, + efs, + logger, + tran, + }), + ); + } + const vaultIdEncoded = vaultsUtils.encodeVaultId(vaultId); logger.info(`Cloning ${this.name} - ${vaultIdEncoded}`); const vault = new VaultInternal({ vaultId, db, - vaultsDb, - vaultsDbDomain, + vaultsDbPath, keyManager, efs, logger, @@ -160,11 +190,10 @@ class VaultInternal { throw e; } - await vault.start({ vaultName }); + await vault.start({ vaultName, tran }); // Setting the remote in the metadata - await vault.db.put( - vault.vaultMetadataDbDomain, - VaultInternal.remoteKey, + await tran.put( + [...vault.vaultMetadataDbPath, VaultInternal.remoteKey], remote, ); logger.info(`Cloned ${this.name} - ${vaultIdEncoded}`); @@ -182,39 +211,29 @@ class VaultInternal { protected logger: Logger; protected db: DB; - protected vaultsDbDomain: DBDomain; - protected vaultsDb: DBLevel; - protected vaultMetadataDbDomain: DBDomain; - protected vaultMetadataDb: DBLevel; + protected vaultsDbPath: LevelPath; + protected vaultMetadataDbPath: LevelPath; protected keyManager: KeyManager; - protected vaultsNamesDomain: DBDomain; + protected vaultsNamesPath: LevelPath; protected efs: EncryptedFS; protected efsVault: EncryptedFS; - protected lock: RWLock = new RWLock(); - - public readLock: ResourceAcquire = async () => { - const release = await this.lock.acquireRead(); - return [async () => release()]; - }; + protected lock: RWLockWriter = new RWLockWriter(); - public writeLock: ResourceAcquire = async () => { - const release = await this.lock.acquireWrite(); - return [async () => release()]; - }; + public getLock(): RWLockWriter { + return this.lock; + } constructor({ vaultId, db, - vaultsDbDomain, - vaultsDb, + vaultsDbPath, keyManager, efs, logger, }: { vaultId: VaultId; db: DB; - vaultsDbDomain: DBDomain; - vaultsDb: DBLevel; + vaultsDbPath: LevelPath; keyManager: KeyManager; efs: EncryptedFS; logger: Logger; @@ -226,8 +245,7 @@ class VaultInternal { this.vaultDataDir = path.join(vaultIdEncoded, 'data'); this.vaultGitDir = path.join(vaultIdEncoded, '.git'); this.db = db; - this.vaultsDbDomain = vaultsDbDomain; - this.vaultsDb = vaultsDb; + this.vaultsDbPath = vaultsDbPath; this.keyManager = keyManager; this.efs = efs; } @@ -236,27 +254,38 @@ class VaultInternal { * * @param fresh Clears all state before starting * @param vaultName Name of the vault, Only used when creating a new vault + * @param tran */ public async start({ fresh = false, vaultName, + tran, }: { fresh?: boolean; vaultName?: VaultName; + tran?: DBTransaction; } = {}): Promise { + if (tran == null) { + return await this.db.withTransactionF(async (tran) => + this.start_(fresh, tran, vaultName), + ); + } + return await this.start_(fresh, tran, vaultName); + } + + protected async start_( + fresh: boolean, + tran: DBTransaction, + vaultName?: VaultName, + ) { this.logger.info( `Starting ${this.constructor.name} - ${this.vaultIdEncoded}`, ); - this.vaultMetadataDbDomain = [...this.vaultsDbDomain, this.vaultIdEncoded]; - this.vaultsNamesDomain = [...this.vaultsDbDomain, 'names']; - this.vaultMetadataDb = await this.db.level( - this.vaultIdEncoded, - this.vaultsDb, - ); + this.vaultMetadataDbPath = [...this.vaultsDbPath, this.vaultIdEncoded]; + this.vaultsNamesPath = [...this.vaultsDbPath, 'names']; // Let's backup any metadata - if (fresh) { - await this.vaultMetadataDb.clear(); + await tran.clear(this.vaultMetadataDbPath); try { await this.efs.rmdir(this.vaultIdEncoded, { recursive: true, @@ -270,15 +299,15 @@ class VaultInternal { await this.mkdirExists(this.vaultIdEncoded); await this.mkdirExists(this.vaultDataDir); await this.mkdirExists(this.vaultGitDir); - await this.setupMeta({ vaultName }); - await this.setupGit(); + await this.setupMeta({ vaultName, tran }); + await this.setupGit(tran); this.efsVault = await this.efs.chroot(this.vaultDataDir); this.logger.info( `Started ${this.constructor.name} - ${this.vaultIdEncoded}`, ); } - private async mkdirExists(directory: string) { + protected async mkdirExists(directory: string) { try { await this.efs.mkdir(directory, { recursive: true }); } catch (e) { @@ -297,12 +326,20 @@ class VaultInternal { ); } - public async destroy(): Promise { + public async destroy(tran?: DBTransaction): Promise { + if (tran == null) { + return await this.db.withTransactionF(async (tran) => + this.destroy_(tran), + ); + } + return await this.destroy_(tran); + } + + protected async destroy_(tran: DBTransaction) { this.logger.info( `Destroying ${this.constructor.name} - ${this.vaultIdEncoded}`, ); - const vaultDb = await this.db.level(this.vaultIdEncoded, this.vaultsDb); - await vaultDb.clear(); + await tran.clear(this.vaultMetadataDbPath); try { await this.efs.rmdir(this.vaultIdEncoded, { recursive: true, @@ -376,7 +413,9 @@ class VaultInternal { e instanceof git.Errors.NotFoundError || e instanceof git.Errors.CommitNotFetchedError ) { - throw new vaultsErrors.ErrorVaultReferenceMissing(); + throw new vaultsErrors.ErrorVaultReferenceMissing(e.message, { + cause: e, + }); } throw e; } @@ -384,7 +423,7 @@ class VaultInternal { @ready(new vaultsErrors.ErrorVaultNotRunning()) public async readF(f: (fs: FileSystemReadable) => Promise): Promise { - return withF([this.readLock], async () => { + return withF([this.lock.read()], async () => { return await f(this.efsVault); }); } @@ -394,7 +433,7 @@ class VaultInternal { g: (fs: FileSystemReadable) => AsyncGenerator, ): AsyncGenerator { const efsVault = this.efsVault; - return withG([this.readLock], async function* () { + return withG([this.lock.read()], async function* () { return yield* g(efsVault); }); } @@ -402,24 +441,28 @@ class VaultInternal { @ready(new vaultsErrors.ErrorVaultNotRunning()) public async writeF( f: (fs: FileSystemWritable) => Promise, + tran?: DBTransaction, ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => this.writeF(f, tran)); + } + // This should really be an internal property // get whether this is remote, and the remote address // if it is, we consider this repo an "attached repo" // this vault is a "mirrored" vault if ( - (await this.db.get( - this.vaultMetadataDbDomain, + (await tran.get([ + ...this.vaultMetadataDbPath, VaultInternal.remoteKey, - )) != null + ])) != null ) { // Mirrored vaults are immutable throw new vaultsErrors.ErrorVaultRemoteDefined(); } - return withF([this.writeLock], async () => { - await this.db.put( - this.vaultMetadataDbDomain, - VaultInternal.dirtyKey, + return withF([this.lock.write()], async () => { + await tran.put( + [...this.vaultMetadataDbPath, VaultInternal.dirtyKey], true, ); try { @@ -431,9 +474,8 @@ class VaultInternal { await this.cleanWorkingDirectory(); throw e; } - await this.db.put( - this.vaultMetadataDbDomain, - VaultInternal.dirtyKey, + await tran.put( + [...this.vaultMetadataDbPath, VaultInternal.dirtyKey], false, ); }); @@ -442,18 +484,25 @@ class VaultInternal { @ready(new vaultsErrors.ErrorVaultNotRunning()) public writeG( g: (fs: FileSystemWritable) => AsyncGenerator, + tran?: DBTransaction, ): AsyncGenerator { + if (tran == null) { + return this.db.withTransactionG((tran) => this.writeG(g, tran)); + } + const efsVault = this.efsVault; - const db = this.db; - const vaultDbDomain = this.vaultMetadataDbDomain; + const vaultMetadataDbPath = this.vaultMetadataDbPath; const createCommit = () => this.createCommit(); const cleanWorkingDirectory = () => this.cleanWorkingDirectory(); - return withG([this.writeLock], async function* () { - if ((await db.get(vaultDbDomain, VaultInternal.remoteKey)) != null) { + return withG([this.lock.write()], async function* () { + if ( + (await tran.get([...vaultMetadataDbPath, VaultInternal.remoteKey])) != + null + ) { // Mirrored vaults are immutable throw new vaultsErrors.ErrorVaultRemoteDefined(); } - await db.put(vaultDbDomain, VaultInternal.dirtyKey, true); + await tran.put([...vaultMetadataDbPath, VaultInternal.dirtyKey], true); let result; // Do what you need to do here, create the commit @@ -470,7 +519,7 @@ class VaultInternal { await cleanWorkingDirectory(); throw e; } - await db.put(vaultDbDomain, VaultInternal.dirtyKey, false); + await tran.put([...vaultMetadataDbPath, VaultInternal.dirtyKey], false); return result; }); } @@ -480,20 +529,33 @@ class VaultInternal { nodeConnectionManager, pullNodeId, pullVaultNameOrId, + tran, }: { nodeConnectionManager: NodeConnectionManager; pullNodeId?: NodeId; pullVaultNameOrId?: VaultId | VaultName; - }) { + tran?: DBTransaction; + }): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.pullVault({ + nodeConnectionManager, + pullNodeId, + pullVaultNameOrId, + tran, + }), + ); + } + // This error flag will contain the error returned by the cloning grpc stream let error; // Keeps track of whether the metadata needs changing to avoid unnecessary db ops // 0 = no change, 1 = change with vault Id, 2 = change with vault name let metaChange = 0; - const remoteInfo = await this.db.get( - this.vaultMetadataDbDomain, + const remoteInfo = await tran.get([ + ...this.vaultMetadataDbPath, VaultInternal.remoteKey, - ); + ]); if (remoteInfo == null) throw new vaultsErrors.ErrorVaultRemoteUndefined(); if (pullNodeId == null) { @@ -528,7 +590,7 @@ class VaultInternal { pullVaultNameOrId!, 'pull', ); - await withF([this.writeLock], async () => { + await withF([this.lock.write()], async () => { await git.pull({ fs: this.efs, http: { request }, @@ -551,7 +613,9 @@ class VaultInternal { if (err instanceof git.Errors.SmartHttpError && error) { throw error; } else if (err instanceof git.Errors.MergeNotSupportedError) { - throw new vaultsErrors.ErrorVaultsMergeConflict(); + throw new vaultsErrors.ErrorVaultsMergeConflict(err.message, { + cause: err, + }); } throw err; } @@ -559,9 +623,8 @@ class VaultInternal { if (metaChange === 2) { remoteInfo.remoteVault = vaultsUtils.encodeVaultId(remoteVaultId); } - await this.db.put( - this.vaultMetadataDbDomain, - VaultInternal.remoteKey, + await tran.put( + [...this.vaultMetadataDbPath, VaultInternal.remoteKey], remoteInfo, ); } @@ -577,8 +640,10 @@ class VaultInternal { */ protected async setupMeta({ vaultName, + tran, }: { vaultName?: VaultName; + tran: DBTransaction; }): Promise { // Setup the vault metadata // and you need to make certain preparations @@ -589,29 +654,27 @@ class VaultInternal { // If this is not existing // setup default vaults db if ( - (await this.db.get( - this.vaultMetadataDbDomain, + (await tran.get([ + ...this.vaultMetadataDbPath, VaultInternal.dirtyKey, - )) == null + ])) == null ) { - await this.db.put( - this.vaultMetadataDbDomain, - VaultInternal.dirtyKey, + await tran.put( + [...this.vaultMetadataDbPath, VaultInternal.dirtyKey], false, ); } // Set up vault Name if ( - (await this.db.get( - this.vaultMetadataDbDomain, + (await tran.get([ + ...this.vaultMetadataDbPath, VaultInternal.nameKey, - )) == null && + ])) == null && vaultName != null ) { - await this.db.put( - this.vaultMetadataDbDomain, - VaultInternal.nameKey, + await tran.put( + [...this.vaultMetadataDbPath, VaultInternal.nameKey], vaultName, ); } @@ -621,7 +684,7 @@ class VaultInternal { // name: string | undefined } - protected async setupGit(): Promise { + protected async setupGit(tran: DBTransaction): Promise { // Initialization is idempotent // It works even with an existing git repository await git.init({ @@ -669,10 +732,10 @@ class VaultInternal { } else { // Checking for dirty if ( - (await this.db.get( - this.vaultMetadataDbDomain, + (await tran.get([ + ...this.vaultMetadataDbPath, VaultInternal.dirtyKey, - )) === true + ])) === true ) { // Force checkout out to the latest commit // This ensures that any uncommitted state is dropped @@ -681,9 +744,8 @@ class VaultInternal { await this.garbageCollectGitObjects(); // Setting dirty back to false - await this.db.put( - this.vaultMetadataDbDomain, - VaultInternal.dirtyKey, + await tran.put( + [...this.vaultMetadataDbPath, VaultInternal.dirtyKey], false, ); } @@ -1036,3 +1098,4 @@ class VaultInternal { } export default VaultInternal; +export type { RemoteInfo }; diff --git a/src/vaults/VaultManager.ts b/src/vaults/VaultManager.ts index ed65478f5..fb09137a0 100644 --- a/src/vaults/VaultManager.ts +++ b/src/vaults/VaultManager.ts @@ -1,4 +1,4 @@ -import type { DB, DBDomain, DBLevel } from '@matrixai/db'; +import type { DB, DBTransaction, LevelPath } from '@matrixai/db'; import type { VaultId, VaultName, @@ -15,10 +15,9 @@ import type NodeConnectionManager from '../nodes/NodeConnectionManager'; import type GestaltGraph from '../gestalts/GestaltGraph'; import type NotificationsManager from '../notifications/NotificationsManager'; import type ACL from '../acl/ACL'; - import type { RemoteInfo } from './VaultInternal'; -import type { ResourceAcquire } from '../utils/context'; import type { VaultAction } from './types'; +import type { LockRequest } from '@matrixai/async-locks'; import path from 'path'; import { PassThrough } from 'readable-stream'; import { EncryptedFS, errors as encryptedFsErrors } from 'encryptedfs'; @@ -28,6 +27,8 @@ import { ready, } from '@matrixai/async-init/dist/CreateDestroyStartStop'; import { IdInternal } from '@matrixai/id'; +import { withF, withG } from '@matrixai/resources'; +import { LockBox, RWLockWriter } from '@matrixai/async-locks'; import VaultInternal from './VaultInternal'; import * as vaultsUtils from '../vaults/utils'; import * as vaultsErrors from '../vaults/errors'; @@ -37,20 +38,12 @@ import * as nodesUtils from '../nodes/utils'; import * as keysUtils from '../keys/utils'; import config from '../config'; import { mkdirExists } from '../utils/utils'; -import { RWLock } from '../utils/locks'; -import { withF, withG } from '../utils/context'; import * as utilsPB from '../proto/js/polykey/v1/utils/utils_pb'; /** * Object map pattern for each vault */ -type VaultMap = Map< - VaultIdString, - { - vault?: VaultInternal; - lock: RWLock; - } ->; +type VaultMap = Map; type VaultList = Map; type VaultMetadata = { @@ -121,13 +114,12 @@ class VaultManager { protected nodeConnectionManager: NodeConnectionManager; protected gestaltGraph: GestaltGraph; protected notificationsManager: NotificationsManager; - protected vaultsDbDomain: DBDomain = [this.constructor.name]; - protected vaultsDb: DBLevel; - protected vaultsNamesDbDomain: DBDomain = [...this.vaultsDbDomain, 'names']; - protected vaultsNamesDb: DBLevel; - protected vaultsNamesLock: RWLock = new RWLock(); + protected vaultsDbPath: LevelPath = [this.constructor.name]; + protected vaultsNamesDbPath: LevelPath = [this.constructor.name, 'names']; + protected vaultsNamesLock: RWLockWriter = new RWLockWriter(); // VaultId -> VaultMetadata protected vaultMap: VaultMap = new Map(); + protected vaultLocks: LockBox = new LockBox(); protected vaultKey: Buffer; protected efs: EncryptedFS; @@ -172,50 +164,50 @@ class VaultManager { }: { fresh?: boolean; } = {}): Promise { - try { - this.logger.info(`Starting ${this.constructor.name}`); - const vaultsDb = await this.db.level(this.vaultsDbDomain[0]); - const vaultsNamesDb = await this.db.level( - this.vaultsNamesDbDomain[1], - vaultsDb, - ); - if (fresh) { - await vaultsDb.clear(); - await this.fs.promises.rm(this.vaultsPath, { - force: true, - recursive: true, - }); - } - await mkdirExists(this.fs, this.vaultsPath); - const vaultKey = await this.setupKey(this.keyBits); - let efs; + await this.db.withTransactionF(async (tran) => { try { - efs = await EncryptedFS.createEncryptedFS({ - dbPath: this.efsPath, - dbKey: vaultKey, - logger: this.logger.getChild('EncryptedFileSystem'), - }); - } catch (e) { - if (e instanceof encryptedFsErrors.ErrorEncryptedFSKey) { - throw new vaultsErrors.ErrorVaultManagerKey(); + this.logger.info(`Starting ${this.constructor.name}`); + if (fresh) { + await tran.clear(this.vaultsDbPath); + await this.fs.promises.rm(this.vaultsPath, { + force: true, + recursive: true, + }); } - throw new vaultsErrors.ErrorVaultManagerEFS(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, - }); + await mkdirExists(this.fs, this.vaultsPath); + const vaultKey = await this.setupKey(this.keyBits, tran); + let efs; + try { + efs = await EncryptedFS.createEncryptedFS({ + dbPath: this.efsPath, + dbKey: vaultKey, + logger: this.logger.getChild('EncryptedFileSystem'), + }); + } catch (e) { + if (e instanceof encryptedFsErrors.ErrorEncryptedFSKey) { + throw new vaultsErrors.ErrorVaultManagerKey(e.message, { + cause: e, + }); + } + throw new vaultsErrors.ErrorVaultManagerEFS(e.message, { + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, + }); + } + this.vaultKey = vaultKey; + this.efs = efs; + this.logger.info(`Started ${this.constructor.name}`); + } catch (e) { + this.logger.warn(`Failed Starting ${this.constructor.name}`); + await this.efs?.stop(); + throw e; } - this.vaultsDb = vaultsDb; - this.vaultsNamesDb = vaultsNamesDb; - this.vaultKey = vaultKey; - this.efs = efs; - this.logger.info(`Started ${this.constructor.name}`); - } catch (e) { - this.logger.warn(`Failed Starting ${this.constructor.name}`); - await this.efs?.stop(); - throw e; - } + }); } public async stop(): Promise { @@ -223,15 +215,22 @@ class VaultManager { // Iterate over vaults in memory and destroy them, ensuring that // the working directory commit state is saved - - for (const [vaultIdString, vaultAndLock] of this.vaultMap) { + const promises: Array> = []; + for (const vaultIdString of this.vaultMap.keys()) { const vaultId = IdInternal.fromString(vaultIdString); - await withF([this.getWriteLock(vaultId)], async () => { - await vaultAndLock.vault?.stop(); - }); - this.vaultMap.delete(vaultIdString); + promises.push( + withF( + [this.vaultLocks.lock([vaultId.toString(), RWLockWriter, 'write'])], + async () => { + const vault = this.vaultMap.get(vaultIdString); + if (vault == null) return; + await vault.stop(); + this.vaultMap.delete(vaultIdString); + }, + ), + ); } - + await Promise.all(promises); await this.efs.stop(); this.vaultMap = new Map(); this.logger.info(`Stopped ${this.constructor.name}`); @@ -240,12 +239,9 @@ class VaultManager { public async destroy(): Promise { this.logger.info(`Destroying ${this.constructor.name}`); await this.efs.destroy(); - // If the DB was stopped, the existing sublevel `this.vaultsDb` will not be valid - // Therefore we recreate the sublevel here - const vaultsDb = await this.db.level(this.vaultsDbDomain[0]); // Clearing all vaults db data - await vaultsDb.clear(); - // Is it necessary to remove the vaults domain? + await this.db.clear(this.vaultsDbPath); + // Is it necessary to remove the vaults' domain? await this.fs.promises.rm(this.vaultsPath, { force: true, recursive: true, @@ -261,108 +257,93 @@ class VaultManager { this.efs.unsetWorkerManager(); } - protected getLock(vaultId: VaultId): RWLock { - const vaultIdString = vaultId.toString() as VaultIdString; - const vaultAndLock = this.vaultMap.get(vaultIdString); - if (vaultAndLock != null) return vaultAndLock.lock; - const lock = new RWLock(); - this.vaultMap.set(vaultIdString, { lock }); - return lock; - } - - protected getReadLock(vaultId: VaultId): ResourceAcquire { - const lock = this.getLock(vaultId); - return async () => { - const release = await lock.acquireRead(); - return [async () => release()]; - }; - } - - protected getWriteLock(vaultId: VaultId): ResourceAcquire { - const lock = this.getLock(vaultId); - return async () => { - const release = await lock.acquireWrite(); - return [async () => release()]; - }; - } - /** * Constructs a new vault instance with a given name and * stores it in memory */ - - // this should actually - @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) - public async createVault(vaultName: VaultName): Promise { + public async createVault( + vaultName: VaultName, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.createVault(vaultName, tran), + ); + } // Adding vault to name map const vaultId = await this.generateVaultId(); - await this.vaultsNamesLock.withWrite(async () => { - const vaultIdBuffer = await this.db.get( - this.vaultsNamesDbDomain, - vaultName, + return await this.vaultsNamesLock.withWriteF(async () => { + const vaultIdBuffer = await tran.get( + [...this.vaultsNamesDbPath, vaultName], true, ); // Check if the vault name already exists; if (vaultIdBuffer != null) { throw new vaultsErrors.ErrorVaultsVaultDefined(); } - await this.db.put( - this.vaultsNamesDbDomain, - vaultName, + await tran.put( + [...this.vaultsNamesDbPath, vaultName], vaultId.toBuffer(), true, ); - }); - const lock = new RWLock(); - const vaultIdString = vaultId.toString() as VaultIdString; - this.vaultMap.set(vaultIdString, { lock }); - return await withF([this.getWriteLock(vaultId)], async () => { - // Creating vault - const vault = await VaultInternal.createVaultInternal({ - vaultId, - vaultName, - keyManager: this.keyManager, - efs: this.efs, - logger: this.logger.getChild(VaultInternal.name), - db: this.db, - vaultsDb: this.vaultsDb, - vaultsDbDomain: this.vaultsDbDomain, - fresh: true, - }); - // Adding vault to object map - this.vaultMap.set(vaultIdString, { lock, vault }); - return vault.vaultId; + const vaultIdString = vaultId.toString() as VaultIdString; + return await this.vaultLocks.withF( + [vaultId, RWLockWriter, 'write'], + async () => { + // Creating vault + const vault = await VaultInternal.createVaultInternal({ + vaultId, + vaultName, + keyManager: this.keyManager, + efs: this.efs, + logger: this.logger.getChild(VaultInternal.name), + db: this.db, + vaultsDbPath: this.vaultsDbPath, + fresh: true, + tran, + }); + // Adding vault to object map + this.vaultMap.set(vaultIdString, vault); + return vault.vaultId; + }, + ); }); } /** - * Retreives the vault metadata using the vault Id + * Retrieves the vault metadata using the VaultId * and parses it to return the associated vault name */ @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) public async getVaultMeta( vaultId: VaultId, + tran?: DBTransaction, ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.getVaultMeta(vaultId, tran), + ); + } + // First check if the metadata exists const vaultIdEncoded = vaultsUtils.encodeVaultId(vaultId); - const vaultDbDomain = [...this.vaultsDbDomain, vaultIdEncoded]; - const vaultDb = await this.db.level(vaultIdEncoded, this.vaultsDb); + const vaultDbPath: LevelPath = [...this.vaultsDbPath, vaultIdEncoded]; // Return if metadata has no data - if ((await this.db.count(vaultDb)) === 0) return; + if ((await tran.count(vaultDbPath)) === 0) return; // Obtain the metadata; - const dirty = (await this.db.get( - vaultDbDomain, + const dirty = (await tran.get([ + ...vaultDbPath, VaultInternal.dirtyKey, - ))!; - const vaultName = (await this.db.get( - vaultDbDomain, + ]))!; + const vaultName = (await tran.get([ + ...vaultDbPath, VaultInternal.nameKey, - ))!; - const remoteInfo = await this.db.get( - vaultDbDomain, + ]))!; + const remoteInfo = await tran.get([ + ...vaultDbPath, VaultInternal.remoteKey, - ); + ]); return { dirty, vaultName, @@ -372,25 +353,34 @@ class VaultManager { /** * Removes the metadata and EFS state of a vault using a - * given vault Id + * given VaultId */ @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) - public async destroyVault(vaultId: VaultId) { - const vaultMeta = await this.getVaultMeta(vaultId); + public async destroyVault( + vaultId: VaultId, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.destroyVault(vaultId, tran), + ); + } + + const vaultMeta = await this.getVaultMeta(vaultId, tran); if (vaultMeta == null) return; const vaultName = vaultMeta.vaultName; this.logger.info(`Destroying Vault ${vaultsUtils.encodeVaultId(vaultId)}`); const vaultIdString = vaultId.toString() as VaultIdString; - await withF([this.getWriteLock(vaultId)], async () => { - const vault = await this.getVault(vaultId); + await this.vaultLocks.withF([vaultId, RWLockWriter, 'write'], async () => { + const vault = await this.getVault(vaultId, tran); // Destroying vault state and metadata await vault.stop(); - await vault.destroy(); + await vault.destroy(tran); // Removing from map this.vaultMap.delete(vaultIdString); // Removing name->id mapping - await this.vaultsNamesLock.withWrite(async () => { - await this.db.del(this.vaultsNamesDbDomain, vaultName); + await this.vaultsNamesLock.withWriteF(async () => { + await tran.del([...this.vaultsNamesDbPath, vaultName]); }); }); this.logger.info(`Destroyed Vault ${vaultsUtils.encodeVaultId(vaultId)}`); @@ -400,66 +390,88 @@ class VaultManager { * Removes vault from the vault map */ @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) - public async closeVault(vaultId: VaultId) { - if ((await this.getVaultName(vaultId)) == null) { + public async closeVault( + vaultId: VaultId, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.closeVault(vaultId, tran), + ); + } + + if ((await this.getVaultName(vaultId, tran)) == null) { throw new vaultsErrors.ErrorVaultsVaultUndefined(); } const vaultIdString = vaultId.toString() as VaultIdString; - await withF([this.getWriteLock(vaultId)], async () => { - const vault = await this.getVault(vaultId); + await this.vaultLocks.withF([vaultId, RWLockWriter, 'write'], async () => { + const vault = await this.getVault(vaultId, tran); await vault.stop(); this.vaultMap.delete(vaultIdString); }); } /** - * Lists the vault name and associated vault Id of all + * Lists the vault name and associated VaultId of all * the vaults stored */ @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) - public async listVaults(): Promise { + public async listVaults(tran?: DBTransaction): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => this.listVaults(tran)); + } + const vaults: VaultList = new Map(); // Stream of vaultName VaultId key value pairs - for await (const vaultNameBuffer of this.vaultsNamesDb.createKeyStream()) { + for await (const [vaultNameBuffer, vaultIdBuffer] of tran.iterator( + undefined, + this.vaultsNamesDbPath, + )) { const vaultName = vaultNameBuffer.toString() as VaultName; - const vaultId = (await this.getVaultId(vaultName))!; + const vaultId = IdInternal.fromBuffer(vaultIdBuffer); vaults.set(vaultName, vaultId); } return vaults; } /** - * Changes the vault name metadata of a vault Id + * Changes the vault name metadata of a VaultId */ @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) public async renameVault( vaultId: VaultId, newVaultName: VaultName, + tran?: DBTransaction, ): Promise { - await withF([this.getWriteLock(vaultId)], async () => { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.renameVault(vaultId, newVaultName, tran), + ); + } + + await this.vaultLocks.withF([vaultId, RWLockWriter, 'write'], async () => { this.logger.info(`Renaming Vault ${vaultsUtils.encodeVaultId(vaultId)}`); // Checking if new name exists - if (await this.getVaultId(newVaultName)) { + if (await this.getVaultId(newVaultName, tran)) { throw new vaultsErrors.ErrorVaultsVaultDefined(); } // Checking if vault exists - const vaultMetadata = await this.getVaultMeta(vaultId); + const vaultMetadata = await this.getVaultMeta(vaultId, tran); if (vaultMetadata == null) { throw new vaultsErrors.ErrorVaultsVaultUndefined(); } const oldVaultName = vaultMetadata.vaultName; // Updating metadata with new name; - const vaultDbDomain = [ - ...this.vaultsDbDomain, + const vaultDbPath = [ + ...this.vaultsDbPath, vaultsUtils.encodeVaultId(vaultId), ]; - await this.db.put(vaultDbDomain, VaultInternal.nameKey, newVaultName); + await tran.put([...vaultDbPath, VaultInternal.nameKey], newVaultName); // Updating name->id map - await this.vaultsNamesLock.withWrite(async () => { - await this.db.del(this.vaultsNamesDbDomain, oldVaultName); - await this.db.put( - this.vaultsNamesDbDomain, - newVaultName, + await this.vaultsNamesLock.withWriteF(async () => { + await tran.del([...this.vaultsNamesDbPath, oldVaultName]); + await tran.put( + [...this.vaultsNamesDbPath, newVaultName], vaultId.toBuffer(), true, ); @@ -468,14 +480,22 @@ class VaultManager { } /** - * Retreives the vault Id associated with a vault name + * Retrieves the VaultId associated with a vault name */ @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) - public async getVaultId(vaultName: VaultName): Promise { - return await this.vaultsNamesLock.withWrite(async () => { - const vaultIdBuffer = await this.db.get( - this.vaultsNamesDbDomain, - vaultName, + public async getVaultId( + vaultName: VaultName, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.getVaultId(vaultName, tran), + ); + } + + return await this.vaultsNamesLock.withWriteF(async () => { + const vaultIdBuffer = await tran.get( + [...this.vaultsNamesDbPath, vaultName], true, ); if (vaultIdBuffer == null) return; @@ -484,23 +504,37 @@ class VaultManager { } /** - * Retreives the vault name associated with a vault Id + * Retrieves the vault name associated with a VaultId */ @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) - public async getVaultName(vaultId: VaultId): Promise { - const metadata = await this.getVaultMeta(vaultId); + public async getVaultName( + vaultId: VaultId, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.getVaultName(vaultId, tran), + ); + } + const metadata = await this.getVaultMeta(vaultId, tran); return metadata?.vaultName; } /** * Returns a dictionary of VaultActions for each node - * @param vaultId */ @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) public async getVaultPermission( vaultId: VaultId, + tran?: DBTransaction, ): Promise> { - const rawPermissions = await this.acl.getVaultPerm(vaultId); + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.getVaultPermission(vaultId, tran), + ); + } + + const rawPermissions = await this.acl.getVaultPerm(vaultId, tran); const permissions: Record = {}; // Getting the relevant information for (const nodeId in rawPermissions) { @@ -514,14 +548,24 @@ class VaultManager { * gestalt and send a notification to this gestalt */ @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) - public async shareVault(vaultId: VaultId, nodeId: NodeId): Promise { - const vaultMeta = await this.getVaultMeta(vaultId); - if (!vaultMeta) throw new vaultsErrors.ErrorVaultsVaultUndefined(); - // Node Id permissions translated to other nodes in + public async shareVault( + vaultId: VaultId, + nodeId: NodeId, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.shareVault(vaultId, nodeId, tran), + ); + } + + const vaultMeta = await this.getVaultMeta(vaultId, tran); + if (vaultMeta == null) throw new vaultsErrors.ErrorVaultsVaultUndefined(); + // NodeId permissions translated to other nodes in // a gestalt by other domains - await this.gestaltGraph.setGestaltActionByNode(nodeId, 'scan'); - await this.acl.setVaultAction(vaultId, nodeId, 'pull'); - await this.acl.setVaultAction(vaultId, nodeId, 'clone'); + await this.gestaltGraph.setGestaltActionByNode(nodeId, 'scan', tran); + await this.acl.setVaultAction(vaultId, nodeId, 'pull', tran); + await this.acl.setVaultAction(vaultId, nodeId, 'clone', tran); await this.notificationsManager.sendNotification(nodeId, { type: 'VaultShare', vaultId: vaultsUtils.encodeVaultId(vaultId), @@ -538,12 +582,22 @@ class VaultManager { * gestalt */ @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) - public async unshareVault(vaultId: VaultId, nodeId: NodeId): Promise { - const vaultMeta = await this.getVaultMeta(vaultId); + public async unshareVault( + vaultId: VaultId, + nodeId: NodeId, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.unshareVault(vaultId, nodeId, tran), + ); + } + + const vaultMeta = await this.getVaultMeta(vaultId, tran); if (!vaultMeta) throw new vaultsErrors.ErrorVaultsVaultUndefined(); - await this.gestaltGraph.unsetGestaltActionByNode(nodeId, 'scan'); - await this.acl.unsetVaultAction(vaultId, nodeId, 'pull'); - await this.acl.unsetVaultAction(vaultId, nodeId, 'clone'); + await this.gestaltGraph.unsetGestaltActionByNode(nodeId, 'scan', tran); + await this.acl.unsetVaultAction(vaultId, nodeId, 'pull', tran); + await this.acl.unsetVaultAction(vaultId, nodeId, 'clone', tran); } /** @@ -554,65 +608,77 @@ class VaultManager { public async cloneVault( nodeId: NodeId, vaultNameOrId: VaultId | VaultName, + tran?: DBTransaction, ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.cloneVault(nodeId, vaultNameOrId, tran), + ); + } + const vaultId = await this.generateVaultId(); - const lock = new RWLock(); const vaultIdString = vaultId.toString() as VaultIdString; - this.vaultMap.set(vaultIdString, { lock }); this.logger.info( `Cloning Vault ${vaultsUtils.encodeVaultId(vaultId)} on Node ${nodeId}`, ); - return await withF([this.getWriteLock(vaultId)], async () => { - const vault = await VaultInternal.cloneVaultInternal({ - targetNodeId: nodeId, - targetVaultNameOrId: vaultNameOrId, - vaultId, - db: this.db, - nodeConnectionManager: this.nodeConnectionManager, - vaultsDb: this.vaultsDb, - vaultsDbDomain: this.vaultsDbDomain, - keyManager: this.keyManager, - efs: this.efs, - logger: this.logger.getChild(VaultInternal.name), - }); - this.vaultMap.set(vaultIdString, { lock, vault }); - const vaultMetadata = (await this.getVaultMeta(vaultId))!; - const baseVaultName = vaultMetadata.vaultName; - // Need to check if the name is taken, 10 attempts - let newVaultName = baseVaultName; - let attempts = 1; - while (true) { - const existingVaultId = await this.db.get( - this.vaultsNamesDbDomain, + return await this.vaultLocks.withF( + [vaultId, RWLockWriter, 'write'], + async () => { + const vault = await VaultInternal.cloneVaultInternal({ + targetNodeId: nodeId, + targetVaultNameOrId: vaultNameOrId, + vaultId, + db: this.db, + nodeConnectionManager: this.nodeConnectionManager, + vaultsDbPath: this.vaultsDbPath, + keyManager: this.keyManager, + efs: this.efs, + logger: this.logger.getChild(VaultInternal.name), + tran, + }); + this.vaultMap.set(vaultIdString, vault); + const vaultMetadata = (await this.getVaultMeta(vaultId, tran))!; + const baseVaultName = vaultMetadata.vaultName; + // Need to check if the name is taken, 10 attempts + let newVaultName = baseVaultName; + let attempts = 1; + while (true) { + const existingVaultId = await tran.get([ + ...this.vaultsNamesDbPath, + newVaultName, + ]); + if (existingVaultId == null) break; + newVaultName = `${baseVaultName}-${attempts}`; + if (attempts >= 50) { + throw new vaultsErrors.ErrorVaultsNameConflict( + `Too many copies of ${baseVaultName}`, + ); + } + attempts++; + } + // Set the vaultName -> vaultId mapping + await tran.put( + [...this.vaultsNamesDbPath, newVaultName], + vaultId.toBuffer(), + true, + ); + // Update vault metadata + await tran.put( + [ + ...this.vaultsDbPath, + vaultsUtils.encodeVaultId(vaultId), + VaultInternal.nameKey, + ], newVaultName, ); - if (existingVaultId == null) break; - newVaultName = `${baseVaultName}-${attempts}`; - if (attempts >= 50) { - throw new vaultsErrors.ErrorVaultsNameConflict( - `Too many copies of ${baseVaultName}`, - ); - } - attempts++; - } - // Set the vaultName -> vaultId mapping - await this.db.put( - this.vaultsNamesDbDomain, - newVaultName, - vaultId.toBuffer(), - true, - ); - // Update vault metadata - await this.db.put( - [...this.vaultsDbDomain, vaultsUtils.encodeVaultId(vaultId)], - VaultInternal.nameKey, - newVaultName, - ); - this.logger.info( - `Cloned Vault ${vaultsUtils.encodeVaultId(vaultId)} on Node ${nodeId}`, - ); - return vault.vaultId; - }); + this.logger.info( + `Cloned Vault ${vaultsUtils.encodeVaultId( + vaultId, + )} on Node ${nodeId}`, + ); + return vault.vaultId; + }, + ); } /** @@ -623,18 +689,27 @@ class VaultManager { vaultId, pullNodeId, pullVaultNameOrId, + tran, }: { vaultId: VaultId; pullNodeId?: NodeId; pullVaultNameOrId?: VaultId | VaultName; + tran?: DBTransaction; }): Promise { - if ((await this.getVaultName(vaultId)) == null) return; - await withF([this.getWriteLock(vaultId)], async () => { - const vault = await this.getVault(vaultId); + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.pullVault({ vaultId, pullNodeId, pullVaultNameOrId, tran }), + ); + } + + if ((await this.getVaultName(vaultId, tran)) == null) return; + await this.vaultLocks.withF([vaultId, RWLockWriter, 'write'], async () => { + const vault = await this.getVault(vaultId, tran); await vault.pullVault({ nodeConnectionManager: this.nodeConnectionManager, pullNodeId, pullVaultNameOrId, + tran, }); }); } @@ -644,11 +719,23 @@ class VaultManager { * cloned or pulled from */ @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) - public async *handleInfoRequest(vaultId: VaultId): AsyncGenerator { + public async *handleInfoRequest( + vaultId: VaultId, + tran?: DBTransaction, + ): AsyncGenerator { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.handleInfoRequest(vaultId, tran), + ); + } + const efs = this.efs; - const vault = await this.getVault(vaultId); + const vault = await this.getVault(vaultId, tran); return yield* withG( - [this.getReadLock(vaultId), vault.readLock], + [ + this.vaultLocks.lock([vaultId, RWLockWriter, 'read']), + vault.getLock().read(), + ], async function* (): AsyncGenerator { // Adherence to git protocol yield Buffer.from( @@ -677,10 +764,20 @@ class VaultManager { public async handlePackRequest( vaultId: VaultId, body: Buffer, + tran?: DBTransaction, ): Promise<[PassThrough, PassThrough]> { - const vault = await this.getVault(vaultId); + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.handlePackRequest(vaultId, body, tran), + ); + } + + const vault = await this.getVault(vaultId, tran); return await withF( - [this.getReadLock(vaultId), vault.readLock], + [ + this.vaultLocks.lock([vaultId, RWLockWriter, 'read']), + vault.getLock().read(), + ], async () => { if (body.toString().slice(4, 8) === 'want') { // Parse the request to get the wanted git object @@ -744,14 +841,25 @@ class VaultManager { /** * Returns all the shared vaults for a NodeId. */ - public async *handleScanVaults(nodeId: NodeId): AsyncGenerator<{ + public async *handleScanVaults( + nodeId: NodeId, + tran?: DBTransaction, + ): AsyncGenerator<{ vaultId: VaultId; vaultName: VaultName; vaultPermissions: VaultAction[]; }> { + if (tran == null) { + // Lambda to maintain `this` context + const handleScanVaults = (tran) => this.handleScanVaults(nodeId, tran); + return yield* this.db.withTransactionG(async function* (tran) { + return yield* handleScanVaults(tran); + }); + } + // Checking permission const nodeIdEncoded = nodesUtils.encodeNodeId(nodeId); - const permissions = await this.acl.getNodePerm(nodeId); + const permissions = await this.acl.getNodePerm(nodeId, tran); if (permissions == null) { throw new vaultsErrors.ErrorVaultsPermissionDenied( `No permissions found for ${nodeIdEncoded}`, @@ -772,7 +880,7 @@ class VaultManager { vaults[vaultIdString], ) as VaultAction[]; // Getting the vault name - const metadata = await this.getVaultMeta(vaultId); + const metadata = await this.getVaultMeta(vaultId, tran); const vaultName = metadata!.vaultName; const element = { vaultId, @@ -800,116 +908,83 @@ class VaultManager { } @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) - protected async getVault(vaultId: VaultId): Promise { - let vault: VaultInternal | undefined; - let lock: RWLock; + protected async getVault( + vaultId: VaultId, + tran: DBTransaction, + ): Promise { + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.getVault(vaultId, tran), + ); + } + const vaultIdString = vaultId.toString() as VaultIdString; - let vaultAndLock = this.vaultMap.get(vaultIdString); - if (vaultAndLock != null) { - ({ vault, lock } = vaultAndLock); - // Lock and vault exist - if (vault != null) { - return vault; - } - // Only lock exists - let release; - try { - release = await lock.acquireWrite(); - ({ vault } = vaultAndLock); - if (vault != null) { - return vault; - } - // Only create if the vault state already exists - if ((await this.getVaultMeta(vaultId)) == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined( - `Vault ${vaultsUtils.encodeVaultId(vaultId)} doesn't exist`, - ); - } - vault = await VaultInternal.createVaultInternal({ - vaultId, - keyManager: this.keyManager, - efs: this.efs, - logger: this.logger.getChild(VaultInternal.name), - db: this.db, - vaultsDb: this.vaultsDb, - vaultsDbDomain: this.vaultsDbDomain, - }); - vaultAndLock.vault = vault; - this.vaultMap.set(vaultIdString, vaultAndLock); - return vault; - } finally { - release(); - } - } else { - // Neither vault nor lock exists - lock = new RWLock(); - vaultAndLock = { lock }; - this.vaultMap.set(vaultIdString, vaultAndLock); - let release; - try { - release = await lock.acquireWrite(); - // Only create if the vault state already exists - if ((await this.getVaultMeta(vaultId)) == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined( - `Vault ${vaultsUtils.encodeVaultId(vaultId)} doesn't exist`, - ); - } - vault = await VaultInternal.createVaultInternal({ - vaultId, - keyManager: this.keyManager, - efs: this.efs, - db: this.db, - vaultsDb: this.vaultsDb, - vaultsDbDomain: this.vaultsDbDomain, - logger: this.logger.getChild(VaultInternal.name), - }); - vaultAndLock.vault = vault; - this.vaultMap.set(vaultIdString, vaultAndLock); - return vault; - } finally { - release(); - } + // 1. get the vault, if it exists then return that + const vault = this.vaultMap.get(vaultIdString); + if (vault != null) return vault; + // No vault or state exists then we throw error? + if ((await this.getVaultMeta(vaultId, tran)) == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined( + `Vault ${vaultsUtils.encodeVaultId(vaultId)} doesn't exist`, + ); } + // 2. if the state exists then create, add to map and return that + const newVault = await VaultInternal.createVaultInternal({ + vaultId, + keyManager: this.keyManager, + efs: this.efs, + logger: this.logger.getChild(VaultInternal.name), + db: this.db, + vaultsDbPath: this.vaultsDbPath, + tran, + }); + this.vaultMap.set(vaultIdString, newVault); + return newVault; } - // THIS can also be replaced with generic withF and withG - /** * Takes a function and runs it with the listed vaults. locking is handled automatically * @param vaultIds List of vault ID for vaults you wish to use * @param f Function you wish to run with the provided vaults + * @param tran */ @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) public async withVaults( vaultIds: VaultId[], f: (...args: Vault[]) => Promise, + tran?: DBTransaction, ): Promise { - // Stages: - // 1. Obtain vaults - // 2. Call function with vaults while locking the vaults - // 3. Catch any problems and preform clean up in finally - // 4. return result - - const vaults = await Promise.all( - vaultIds.map(async (vaultId) => { - return await this.getVault(vaultId); - }), - ); + if (tran == null) { + return this.db.withTransactionF(async (tran) => + this.withVaults(vaultIds, f, tran), + ); + } // Obtaining locks - const vaultLocks = vaultIds.map((vaultId) => { - return this.getReadLock(vaultId); - }); + const vaultLocks: Array> = vaultIds.map( + (vaultId) => { + return [vaultId.toString(), RWLockWriter, 'read']; + }, + ); // Running the function with locking - return await withF(vaultLocks, () => { + return await this.vaultLocks.withF(...vaultLocks, async () => { + // Getting the vaults while locked + const vaults = await Promise.all( + vaultIds.map(async (vaultId) => { + return await this.getVault(vaultId, tran); + }), + ); return f(...vaults); }); } - protected async setupKey(bits: 128 | 192 | 256): Promise { + protected async setupKey( + bits: 128 | 192 | 256, + tran: DBTransaction, + ): Promise { let key: Buffer | undefined; - key = await this.db.get(this.vaultsDbDomain, 'key', true); + key = await tran.get([...this.vaultsDbPath, 'key'], true); // If the EFS already exists, but the key doesn't, then we have lost the key if (key == null && (await this.existsEFS())) { throw new vaultsErrors.ErrorVaultManagerKey(); @@ -919,7 +994,7 @@ class VaultManager { } this.logger.info('Generating vaults key'); key = await this.generateKey(bits); - await this.db.put(this.vaultsDbDomain, 'key', key, true); + await tran.put([...this.vaultsDbPath, 'key'], key, true); return key; } @@ -935,10 +1010,13 @@ class VaultManager { return false; } throw new vaultsErrors.ErrorVaultManagerEFS(e.message, { - errno: e.errno, - syscall: e.syscall, - code: e.code, - path: e.path, + data: { + errno: e.errno, + syscall: e.syscall, + code: e.code, + path: e.path, + }, + cause: e, }); } } diff --git a/src/vaults/VaultOps.ts b/src/vaults/VaultOps.ts index 203bb4a4a..3de1edd1c 100644 --- a/src/vaults/VaultOps.ts +++ b/src/vaults/VaultOps.ts @@ -109,6 +109,7 @@ async function getSecret(vault: Vault, secretName: string): Promise { if (err.code === 'ENOENT') { throw new vaultsErrors.ErrorSecretsSecretUndefined( `Secret with name: ${secretName} does not exist`, + { cause: err }, ); } throw err; @@ -124,6 +125,7 @@ async function statSecret(vault: Vault, secretName: string): Promise { if (err.code === 'ENOENT') { throw new vaultsErrors.ErrorSecretsSecretUndefined( `Secret with name: ${secretName} does not exist`, + { cause: err }, ); } throw err; @@ -178,6 +180,7 @@ async function mkdir( if (err.code === 'ENOENT' && !recursive) { throw new vaultsErrors.ErrorVaultsRecursive( `Could not create directory '${dirPath}' without recursive option`, + { cause: err }, ); } } diff --git a/src/vaults/errors.ts b/src/vaults/errors.ts index 43e877caf..f8db78e31 100644 --- a/src/vaults/errors.ts +++ b/src/vaults/errors.ts @@ -1,113 +1,114 @@ import { ErrorPolykey, sysexits } from '../errors'; -class ErrorVaults extends ErrorPolykey {} +class ErrorVaults extends ErrorPolykey {} -class ErrorVaultManagerRunning extends ErrorVaults { - description = 'VaultManager is running'; +class ErrorVaultManagerRunning extends ErrorVaults { + static description = 'VaultManager is running'; exitCode = sysexits.USAGE; } -class ErrorVaultManagerNotRunning extends ErrorVaults { - description = 'VaultManager is not running'; +class ErrorVaultManagerNotRunning extends ErrorVaults { + static description = 'VaultManager is not running'; exitCode = sysexits.USAGE; } -class ErrorVaultManagerDestroyed extends ErrorVaults { - description = 'VaultManager is destroyed'; +class ErrorVaultManagerDestroyed extends ErrorVaults { + static description = 'VaultManager is destroyed'; exitCode = sysexits.USAGE; } -class ErrorVaultManagerKey extends ErrorVaults { - description = 'Vault key is invalid'; +class ErrorVaultManagerKey extends ErrorVaults { + static description = 'Vault key is invalid'; exitCode = sysexits.CONFIG; } -class ErrorVaultManagerEFS extends ErrorVaults { - description = 'EFS failed'; +class ErrorVaultManagerEFS extends ErrorVaults { + static description = 'EFS failed'; exitCode = sysexits.UNAVAILABLE; } -class ErrorVault extends ErrorVaults {} +class ErrorVault extends ErrorVaults {} -class ErrorVaultRunning extends ErrorVault { - description = 'Vault is running'; +class ErrorVaultRunning extends ErrorVault { + static description = 'Vault is running'; exitCode = sysexits.USAGE; } -class ErrorVaultNotRunning extends ErrorVault { - description = 'Vault is not running'; +class ErrorVaultNotRunning extends ErrorVault { + static description = 'Vault is not running'; exitCode = sysexits.USAGE; } -class ErrorVaultDestroyed extends ErrorVault { - description = 'Vault is destroyed'; +class ErrorVaultDestroyed extends ErrorVault { + static description = 'Vault is destroyed'; exitCode = sysexits.USAGE; } -class ErrorVaultReferenceInvalid extends ErrorVault { - description = 'Reference is invalid'; +class ErrorVaultReferenceInvalid extends ErrorVault { + static description = 'Reference is invalid'; exitCode = sysexits.USAGE; } -class ErrorVaultReferenceMissing extends ErrorVault { - description = 'Reference does not exist'; +class ErrorVaultReferenceMissing extends ErrorVault { + static description = 'Reference does not exist'; exitCode = sysexits.USAGE; } -class ErrorVaultRemoteDefined extends ErrorVaults { - description = 'Vault is a clone of a remote vault and can not be mutated'; +class ErrorVaultRemoteDefined extends ErrorVaults { + static description = + 'Vault is a clone of a remote vault and can not be mutated'; exitCode = sysexits.USAGE; } -class ErrorVaultRemoteUndefined extends ErrorVaults { - description = 'Vault has no remote set and can not be pulled'; +class ErrorVaultRemoteUndefined extends ErrorVaults { + static description = 'Vault has no remote set and can not be pulled'; exitCode = sysexits.USAGE; } -class ErrorVaultsVaultUndefined extends ErrorVaults { - description = 'Vault does not exist'; +class ErrorVaultsVaultUndefined extends ErrorVaults { + static description = 'Vault does not exist'; exitCode = sysexits.USAGE; } -class ErrorVaultsVaultDefined extends ErrorVaults { - description = 'Vault already exists'; +class ErrorVaultsVaultDefined extends ErrorVaults { + static description = 'Vault already exists'; exitCode = sysexits.USAGE; } -class ErrorVaultsRecursive extends ErrorVaults { - description = 'Recursive option was not set'; +class ErrorVaultsRecursive extends ErrorVaults { + static description = 'Recursive option was not set'; exitCode = sysexits.USAGE; } -class ErrorVaultsCreateVaultId extends ErrorVaults { - description = 'Failed to create unique VaultId'; +class ErrorVaultsCreateVaultId extends ErrorVaults { + static description = 'Failed to create unique VaultId'; exitCode = sysexits.SOFTWARE; } -class ErrorVaultsMergeConflict extends ErrorVaults { - description = 'Merge Conflicts are not supported yet'; +class ErrorVaultsMergeConflict extends ErrorVaults { + static description = 'Merge Conflicts are not supported yet'; exitCode = sysexits.SOFTWARE; } -class ErrorVaultsPermissionDenied extends ErrorVaults { - description = 'Permission was denied'; +class ErrorVaultsPermissionDenied extends ErrorVaults { + static description = 'Permission was denied'; exitCode = sysexits.NOPERM; } -class ErrorVaultsNameConflict extends ErrorVaults { - description = 'Unique name could not be created'; +class ErrorVaultsNameConflict extends ErrorVaults { + static description = 'Unique name could not be created'; exitCode = sysexits.UNAVAILABLE; } -class ErrorSecrets extends ErrorPolykey {} +class ErrorSecrets extends ErrorPolykey {} -class ErrorSecretsSecretUndefined extends ErrorSecrets { - description = 'Secret does not exist'; +class ErrorSecretsSecretUndefined extends ErrorSecrets { + static description = 'Secret does not exist'; exitCode = sysexits.USAGE; } -class ErrorSecretsSecretDefined extends ErrorSecrets { - description = 'Secret already exists'; +class ErrorSecretsSecretDefined extends ErrorSecrets { + static description = 'Secret already exists'; exitCode = sysexits.USAGE; } diff --git a/tests/PolykeyAgent.test.ts b/tests/PolykeyAgent.test.ts index 9423050ab..7cb1f2fc7 100644 --- a/tests/PolykeyAgent.test.ts +++ b/tests/PolykeyAgent.test.ts @@ -1,4 +1,5 @@ import type { StateVersion } from '@/schema/types'; +import type { KeyManagerChangeData } from '@/keys/types'; import os from 'os'; import path from 'path'; import fs from 'fs'; @@ -9,6 +10,7 @@ import { Status } from '@/status'; import { Schema } from '@/schema'; import * as errors from '@/errors'; import config from '@/config'; +import { promise } from '@/utils/index'; import * as testUtils from './utils'; describe('PolykeyAgent', () => { @@ -175,4 +177,76 @@ describe('PolykeyAgent', () => { }), ).rejects.toThrow(errors.ErrorSchemaVersionTooOld); }); + test('renewRootKeyPair change event propagates', async () => { + const nodePath = `${dataDir}/polykey`; + let pkAgent: PolykeyAgent | undefined; + try { + pkAgent = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath, + logger, + }); + const prom = promise(); + pkAgent.events.on( + PolykeyAgent.eventSymbols.KeyManager, + async (data: KeyManagerChangeData) => { + prom.resolveP(data); + }, + ); + await pkAgent.keyManager.renewRootKeyPair(password); + + await expect(prom.p).resolves.toBeDefined(); + } finally { + await pkAgent?.stop(); + await pkAgent?.destroy(); + } + }); + test('resetRootKeyPair change event propagates', async () => { + const nodePath = `${dataDir}/polykey`; + let pkAgent: PolykeyAgent | undefined; + try { + pkAgent = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath, + logger, + }); + const prom = promise(); + pkAgent.events.on( + PolykeyAgent.eventSymbols.KeyManager, + async (data: KeyManagerChangeData) => { + prom.resolveP(data); + }, + ); + await pkAgent.keyManager.resetRootKeyPair(password); + + await expect(prom.p).resolves.toBeDefined(); + } finally { + await pkAgent?.stop(); + await pkAgent?.destroy(); + } + }); + test('resetRootCert change event propagates', async () => { + const nodePath = `${dataDir}/polykey`; + let pkAgent: PolykeyAgent | undefined; + try { + pkAgent = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath, + logger, + }); + const prom = promise(); + pkAgent.events.on( + PolykeyAgent.eventSymbols.KeyManager, + async (data: KeyManagerChangeData) => { + prom.resolveP(data); + }, + ); + await pkAgent.keyManager.resetRootCert(); + + await expect(prom.p).resolves.toBeDefined(); + } finally { + await pkAgent?.stop(); + await pkAgent?.destroy(); + } + }); }); diff --git a/tests/acl/ACL.test.ts b/tests/acl/ACL.test.ts index a75819f2f..ec4020a1b 100644 --- a/tests/acl/ACL.test.ts +++ b/tests/acl/ACL.test.ts @@ -7,10 +7,11 @@ import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; -import { ACL, errors as aclErrors } from '@/acl'; -import { utils as keysUtils } from '@/keys'; -import { utils as vaultsUtils } from '@/vaults'; -import * as testUtils from '../utils'; +import ACL from '@/acl/ACL'; +import * as aclErrors from '@/acl/errors'; +import * as keysUtils from '@/keys/utils'; +import * as vaultsUtils from '@/vaults/utils'; +import * as testNodesUtils from '../nodes/utils'; describe(ACL.name, () => { const logger = new Logger(`${ACL.name} test`, LogLevel.WARN, [ @@ -18,14 +19,14 @@ describe(ACL.name, () => { ]); // Node Ids - const nodeIdX = testUtils.generateRandomNodeId(); - const nodeIdY = testUtils.generateRandomNodeId(); - const nodeIdG1First = testUtils.generateRandomNodeId(); - const nodeIdG1Second = testUtils.generateRandomNodeId(); - const nodeIdG1Third = testUtils.generateRandomNodeId(); - const nodeIdG1Fourth = testUtils.generateRandomNodeId(); - const nodeIdG2First = testUtils.generateRandomNodeId(); - const nodeIdG2Second = testUtils.generateRandomNodeId(); + const nodeIdX = testNodesUtils.generateRandomNodeId(); + const nodeIdY = testNodesUtils.generateRandomNodeId(); + const nodeIdG1First = testNodesUtils.generateRandomNodeId(); + const nodeIdG1Second = testNodesUtils.generateRandomNodeId(); + const nodeIdG1Third = testNodesUtils.generateRandomNodeId(); + const nodeIdG1Fourth = testNodesUtils.generateRandomNodeId(); + const nodeIdG2First = testNodesUtils.generateRandomNodeId(); + const nodeIdG2Second = testNodesUtils.generateRandomNodeId(); let dataDir: string; let db: DB; @@ -107,30 +108,18 @@ describe(ACL.name, () => { await expect(acl.setNodesPerm([], {} as Permission)).rejects.toThrow( aclErrors.ErrorACLNotRunning, ); - await expect(acl.setNodesPermOps([], {} as Permission)).rejects.toThrow( - aclErrors.ErrorACLNotRunning, - ); await expect(acl.setNodePerm(nodeIdX, {} as Permission)).rejects.toThrow( aclErrors.ErrorACLNotRunning, ); - await expect(acl.setNodePermOps(nodeIdX, {} as Permission)).rejects.toThrow( - aclErrors.ErrorACLNotRunning, - ); await expect(acl.unsetNodePerm(nodeIdX)).rejects.toThrow( aclErrors.ErrorACLNotRunning, ); - await expect(acl.unsetNodePermOps(nodeIdX)).rejects.toThrow( - aclErrors.ErrorACLNotRunning, - ); await expect(acl.unsetVaultPerms(1 as VaultId)).rejects.toThrow( aclErrors.ErrorACLNotRunning, ); await expect(acl.joinNodePerm(nodeIdX, [])).rejects.toThrow( aclErrors.ErrorACLNotRunning, ); - await expect(acl.joinNodePermOps(nodeIdX, [])).rejects.toThrow( - aclErrors.ErrorACLNotRunning, - ); await expect(acl.joinVaultPerms(1 as VaultId, [])).rejects.toThrow( aclErrors.ErrorACLNotRunning, ); @@ -417,35 +406,45 @@ describe(ACL.name, () => { test('transactional operations', async () => { const acl = await ACL.createACL({ db, logger }); const p1 = acl.getNodePerms(); - const p2 = acl.transaction(async (acl) => { - await acl.setNodesPerm([nodeIdG1First, nodeIdG1Second] as Array, { - gestalt: { - notify: null, + const p2 = acl.withTransactionF(async (tran) => { + await acl.setNodesPerm( + [nodeIdG1First, nodeIdG1Second] as Array, + { + gestalt: { + notify: null, + }, + vaults: {}, }, - vaults: {}, - }); - await acl.setNodesPerm([nodeIdG2First, nodeIdG2Second] as Array, { - gestalt: { - notify: null, + tran, + ); + await acl.setNodesPerm( + [nodeIdG2First, nodeIdG2Second] as Array, + { + gestalt: { + notify: null, + }, + vaults: {}, }, - vaults: {}, - }); - await acl.setVaultAction(vaultId1, nodeIdG1First, 'pull'); - await acl.setVaultAction(vaultId1, nodeIdG2First, 'clone'); - await acl.joinNodePerm(nodeIdG1Second, [ - nodeIdG1Third, - nodeIdG1Fourth, - ] as Array); + tran, + ); + await acl.setVaultAction(vaultId1, nodeIdG1First, 'pull', tran); + await acl.setVaultAction(vaultId1, nodeIdG2First, 'clone', tran); + await acl.joinNodePerm( + nodeIdG1Second, + [nodeIdG1Third, nodeIdG1Fourth] as Array, + undefined, + tran, + ); // V3 and v4 joins v1 // this means v3 and v4 now has g1 and g2 permissions - await acl.joinVaultPerms(vaultId1, [vaultId3, vaultId4]); + await acl.joinVaultPerms(vaultId1, [vaultId3, vaultId4], tran); // Removing v3 - await acl.unsetVaultPerms(vaultId3); + await acl.unsetVaultPerms(vaultId3, tran); // Removing g1-second - await acl.unsetNodePerm(nodeIdG1Second); + await acl.unsetNodePerm(nodeIdG1Second, tran); // Unsetting pull just for v1 for g1 - await acl.unsetVaultAction(vaultId1, nodeIdG1First, 'pull'); - return await acl.getNodePerms(); + await acl.unsetVaultAction(vaultId1, nodeIdG1First, 'pull', tran); + return await acl.getNodePerms(tran); }); const p3 = acl.getNodePerms(); const results = await Promise.all([p1, p2, p3]); diff --git a/tests/agent/GRPCClientAgent.test.ts b/tests/agent/GRPCClientAgent.test.ts index 78808e361..134273e30 100644 --- a/tests/agent/GRPCClientAgent.test.ts +++ b/tests/agent/GRPCClientAgent.test.ts @@ -6,6 +6,7 @@ import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; +import Queue from '@/nodes/Queue'; import GestaltGraph from '@/gestalts/GestaltGraph'; import ACL from '@/acl/ACL'; import KeyManager from '@/keys/KeyManager'; @@ -21,6 +22,7 @@ import NotificationsManager from '@/notifications/NotificationsManager'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as agentErrors from '@/agent/errors'; import * as keysUtils from '@/keys/utils'; +import { timerStart } from '@/utils'; import * as testAgentUtils from './utils'; describe(GRPCClientAgent.name, () => { @@ -48,6 +50,7 @@ describe(GRPCClientAgent.name, () => { let keyManager: KeyManager; let vaultManager: VaultManager; let nodeGraph: NodeGraph; + let queue: Queue; let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; let sigchain: Sigchain; @@ -109,21 +112,26 @@ describe(GRPCClientAgent.name, () => { keyManager, logger, }); + queue = new Queue({ logger }); nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, logger, }); - await nodeConnectionManager.start(); nodeManager = new NodeManager({ db: db, sigchain: sigchain, keyManager: keyManager, nodeGraph: nodeGraph, nodeConnectionManager: nodeConnectionManager, + queue, logger: logger, }); + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); notificationsManager = await NotificationsManager.createNotificationsManager({ acl: acl, @@ -156,6 +164,8 @@ describe(GRPCClientAgent.name, () => { acl, gestaltGraph, proxy, + db, + logger, }); client = await testAgentUtils.openTestAgentClient(port); await proxy.start({ @@ -173,6 +183,8 @@ describe(GRPCClientAgent.name, () => { await notificationsManager.stop(); await sigchain.stop(); await nodeConnectionManager.stop(); + await nodeManager.stop(); + await queue.stop(); await nodeGraph.stop(); await gestaltGraph.stop(); await acl.stop(); @@ -255,7 +267,7 @@ describe(GRPCClientAgent.name, () => { port: clientProxy1.getForwardPort(), authToken: clientProxy1.authToken, }, - timeout: 5000, + timer: timerStart(5000), logger, }); @@ -289,7 +301,7 @@ describe(GRPCClientAgent.name, () => { port: clientProxy2.getForwardPort(), authToken: clientProxy2.authToken, }, - timeout: 5000, + timer: timerStart(5000), }); }); afterEach(async () => { diff --git a/tests/agent/service/nodesChainDataGet.test.ts b/tests/agent/service/nodesChainDataGet.test.ts new file mode 100644 index 000000000..306d9cd06 --- /dev/null +++ b/tests/agent/service/nodesChainDataGet.test.ts @@ -0,0 +1,110 @@ +import type { Host, Port } from '@/network/types'; +import type { NodeIdEncoded } from '@/nodes/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import PolykeyAgent from '@/PolykeyAgent'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientAgent from '@/agent/GRPCClientAgent'; +import { AgentServiceService } from '@/proto/js/polykey/v1/agent_service_grpc_pb'; +import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; +import * as keysUtils from '@/keys/utils'; +import * as nodesUtils from '@/nodes/utils'; +import nodesClosestLocalNodesGet from '@/agent/service/nodesClosestLocalNodesGet'; +import * as testNodesUtils from '../../nodes/utils'; +import * as testUtils from '../../utils'; + +describe('nodesClosestLocalNode', () => { + const logger = new Logger('nodesClosestLocalNode test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + let dataDir: string; + let nodePath: string; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientAgent; + let pkAgent: PolykeyAgent; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValueOnce(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValueOnce(globalKeyPair); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + nodePath = path.join(dataDir, 'keynode'); + pkAgent = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath, + keysConfig: { + rootKeyPairBits: 2048, + }, + seedNodes: {}, // Explicitly no seed nodes on startup + networkConfig: { + proxyHost: '127.0.0.1' as Host, + }, + logger, + }); + // Setting up a remote keynode + const agentService = { + nodesClosestLocalNodesGet: nodesClosestLocalNodesGet({ + nodeGraph: pkAgent.nodeGraph, + db: pkAgent.db, + logger, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[AgentServiceService, agentService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientAgent.createGRPCClientAgent({ + nodeId: pkAgent.keyManager.getNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }, global.defaultTimeout); + afterAll(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await pkAgent.stop(); + await pkAgent.destroy(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + test('should get closest local nodes', async () => { + // Adding 10 nodes + const nodes: Array = []; + for (let i = 0; i < 10; i++) { + const nodeId = testNodesUtils.generateRandomNodeId(); + await pkAgent.nodeGraph.setNode(nodeId, { + host: 'localhost' as Host, + port: 55555 as Port, + }); + nodes.push(nodesUtils.encodeNodeId(nodeId)); + } + const nodeIdEncoded = nodesUtils.encodeNodeId( + testNodesUtils.generateRandomNodeId(), + ); + const nodeMessage = new nodesPB.Node(); + nodeMessage.setNodeId(nodeIdEncoded); + const result = await grpcClient.nodesClosestLocalNodesGet(nodeMessage); + const resultNodes: Array = []; + for (const [resultNode] of result.toObject().nodeTableMap) { + resultNodes.push(resultNode as NodeIdEncoded); + } + expect(nodes.sort()).toEqual(resultNodes.sort()); + }); +}); diff --git a/tests/agent/service/nodesClosestLocalNode.test.ts b/tests/agent/service/nodesClosestLocalNode.test.ts new file mode 100644 index 000000000..4e080443a --- /dev/null +++ b/tests/agent/service/nodesClosestLocalNode.test.ts @@ -0,0 +1,120 @@ +import type { Host, Port } from '@/network/types'; +import type { ClaimData } from '@/claims/types'; +import type { IdentityId, ProviderId } from '@/identities/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import PolykeyAgent from '@/PolykeyAgent'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientAgent from '@/agent/GRPCClientAgent'; +import { AgentServiceService } from '@/proto/js/polykey/v1/agent_service_grpc_pb'; +import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import * as keysUtils from '@/keys/utils'; +import * as nodesUtils from '@/nodes/utils'; +import nodesChainDataGet from '@/agent/service/nodesChainDataGet'; +import * as testUtils from '../../utils'; +import * as testNodesUtils from '../../nodes/utils'; + +describe('nodesChainDataGet', () => { + const logger = new Logger('nodesChainDataGet test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + let dataDir: string; + let nodePath: string; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientAgent; + let pkAgent: PolykeyAgent; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValueOnce(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValueOnce(globalKeyPair); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + nodePath = path.join(dataDir, 'keynode'); + pkAgent = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath, + keysConfig: { + rootKeyPairBits: 2048, + }, + seedNodes: {}, // Explicitly no seed nodes on startup + networkConfig: { + proxyHost: '127.0.0.1' as Host, + }, + logger, + }); + const agentService = { + nodesChainDataGet: nodesChainDataGet({ + sigchain: pkAgent.sigchain, + db: pkAgent.db, + logger, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[AgentServiceService, agentService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientAgent.createGRPCClientAgent({ + nodeId: pkAgent.keyManager.getNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }, global.defaultTimeout); + afterAll(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await pkAgent.stop(); + await pkAgent.destroy(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + test('should get closest nodes', async () => { + const srcNodeIdEncoded = nodesUtils.encodeNodeId( + pkAgent.keyManager.getNodeId(), + ); + // Add 10 claims + for (let i = 1; i <= 5; i++) { + const node2 = nodesUtils.encodeNodeId( + testNodesUtils.generateRandomNodeId(), + ); + const nodeLink: ClaimData = { + type: 'node', + node1: srcNodeIdEncoded, + node2: node2, + }; + await pkAgent.sigchain.addClaim(nodeLink); + } + for (let i = 6; i <= 10; i++) { + const identityLink: ClaimData = { + type: 'identity', + node: srcNodeIdEncoded, + provider: ('ProviderId' + i.toString()) as ProviderId, + identity: ('IdentityId' + i.toString()) as IdentityId, + }; + await pkAgent.sigchain.addClaim(identityLink); + } + + const response = await grpcClient.nodesChainDataGet( + new utilsPB.EmptyMessage(), + ); + const chainIds: Array = []; + for (const [id] of response.toObject().chainDataMap) chainIds.push(id); + expect(chainIds).toHaveLength(10); + }); +}); diff --git a/tests/agent/service/nodesCrossSignClaim.test.ts b/tests/agent/service/nodesCrossSignClaim.test.ts index 0c3a1da7a..aea5d7a6e 100644 --- a/tests/agent/service/nodesCrossSignClaim.test.ts +++ b/tests/agent/service/nodesCrossSignClaim.test.ts @@ -14,7 +14,7 @@ import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; import * as keysUtils from '@/keys/utils'; import * as nodesUtils from '@/nodes/utils'; import * as claimsUtils from '@/claims/utils'; -import * as claimsErrors from '@/claims/errors'; +import * as grpcErrors from '@/grpc/errors'; import * as testNodesUtils from '../../nodes/utils'; import * as testUtils from '../../utils'; @@ -22,7 +22,7 @@ describe('nodesCrossSignClaim', () => { const logger = new Logger('nodesCrossSignClaim test', LogLevel.WARN, [ new StreamHandler(), ]); - const password = 'helloworld'; + const password = 'hello-world'; let dataDir: string; let nodePath: string; let grpcServer: GRPCServer; @@ -78,6 +78,8 @@ describe('nodesCrossSignClaim', () => { keyManager: pkAgent.keyManager, nodeManager: pkAgent.nodeManager, sigchain: pkAgent.sigchain, + db: pkAgent.db, + logger, }), }; grpcServer = new GRPCServer({ logger }); @@ -136,7 +138,6 @@ describe('nodesCrossSignClaim', () => { // 2. Singly signed intermediary claim const response = await genClaims.read(); // Check X's sigchain is locked at start - expect(pkAgent.sigchain.locked).toBe(true); expect(response.done).toBe(false); expect(response.value).toBeInstanceOf(nodesPB.CrossSign); const receivedMessage = response.value as nodesPB.CrossSign; @@ -175,14 +176,12 @@ describe('nodesCrossSignClaim', () => { doublySignedClaim: doublyResponse, }); // Just before we complete the last step, check X's sigchain is still locked - expect(pkAgent.sigchain.locked).toBe(true); await genClaims.write(doublyMessage); // Expect the stream to be closed. const finalResponse = await genClaims.read(); expect(finalResponse.done).toBe(true); expect(genClaims.stream.destroyed).toBe(true); // Check X's sigchain is released at end. - expect(pkAgent.sigchain.locked).toBe(false); // Check claim is in both node's sigchains // Rather, check it's in X's sigchain const chain = await pkAgent.sigchain.getChainData(); @@ -205,11 +204,10 @@ describe('nodesCrossSignClaim', () => { const crossSignMessageUndefinedSingly = new nodesPB.CrossSign(); await genClaims.write(crossSignMessageUndefinedSingly); await expect(() => genClaims.read()).rejects.toThrow( - claimsErrors.ErrorUndefinedSinglySignedClaim, + grpcErrors.ErrorPolykeyRemote, ); expect(genClaims.stream.destroyed).toBe(true); // Check sigchain's lock is released - expect(pkAgent.sigchain.locked).toBe(false); // Revert side effects await pkAgent.sigchain.stop(); await pkAgent.sigchain.destroy(); @@ -227,11 +225,10 @@ describe('nodesCrossSignClaim', () => { ); await genClaims.write(crossSignMessageUndefinedSinglySignature); await expect(() => genClaims.read()).rejects.toThrow( - claimsErrors.ErrorUndefinedSignature, + grpcErrors.ErrorPolykeyRemote, ); expect(genClaims.stream.destroyed).toBe(true); // Check sigchain's lock is released - expect(pkAgent.sigchain.locked).toBe(false); // Revert side effects await pkAgent.sigchain.stop(); await pkAgent.sigchain.destroy(); diff --git a/tests/agent/service/nodesHolePunchMessage.test.ts b/tests/agent/service/nodesHolePunchMessage.test.ts new file mode 100644 index 000000000..70615948c --- /dev/null +++ b/tests/agent/service/nodesHolePunchMessage.test.ts @@ -0,0 +1,105 @@ +import type { Host, Port } from '@/network/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import PolykeyAgent from '@/PolykeyAgent'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientAgent from '@/agent/GRPCClientAgent'; +import { AgentServiceService } from '@/proto/js/polykey/v1/agent_service_grpc_pb'; +import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; +import * as keysUtils from '@/keys/utils'; +import * as nodesUtils from '@/nodes/utils'; +import nodesHolePunchMessageSend from '@/agent/service/nodesHolePunchMessageSend'; +import * as networkUtils from '@/network/utils'; +import * as testUtils from '../../utils'; + +describe('nodesHolePunchMessage', () => { + const logger = new Logger('nodesHolePunchMessage test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + let dataDir: string; + let nodePath: string; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientAgent; + let pkAgent: PolykeyAgent; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValueOnce(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValueOnce(globalKeyPair); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + nodePath = path.join(dataDir, 'keynode'); + pkAgent = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath, + keysConfig: { + rootKeyPairBits: 2048, + }, + seedNodes: {}, // Explicitly no seed nodes on startup + networkConfig: { + proxyHost: '127.0.0.1' as Host, + }, + logger, + }); + const agentService = { + nodesHolePunchMessageSend: nodesHolePunchMessageSend({ + keyManager: pkAgent.keyManager, + nodeConnectionManager: pkAgent.nodeConnectionManager, + nodeManager: pkAgent.nodeManager, + db: pkAgent.db, + logger, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[AgentServiceService, agentService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientAgent.createGRPCClientAgent({ + nodeId: pkAgent.keyManager.getNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }, global.defaultTimeout); + afterAll(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await pkAgent.stop(); + await pkAgent.destroy(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + test('should get the chain data', async () => { + const nodeId = nodesUtils.encodeNodeId(pkAgent.keyManager.getNodeId()); + const proxyAddress = networkUtils.buildAddress( + pkAgent.proxy.getProxyHost(), + pkAgent.proxy.getProxyPort(), + ); + const signature = await pkAgent.keyManager.signWithRootKeyPair( + Buffer.from(proxyAddress), + ); + const relayMessage = new nodesPB.Relay(); + relayMessage + .setTargetId(nodeId) + .setSrcId(nodeId) + .setSignature(signature.toString()) + .setProxyAddress(proxyAddress); + await grpcClient.nodesHolePunchMessageSend(relayMessage); + // TODO: check if the ping was sent + }); +}); diff --git a/tests/agent/service/notificationsSend.test.ts b/tests/agent/service/notificationsSend.test.ts index a0eb81ffa..6d08b842a 100644 --- a/tests/agent/service/notificationsSend.test.ts +++ b/tests/agent/service/notificationsSend.test.ts @@ -8,6 +8,7 @@ import { createPrivateKey, createPublicKey } from 'crypto'; import { exportJWK, SignJWT } from 'jose'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; +import Queue from '@/nodes/Queue'; import KeyManager from '@/keys/KeyManager'; import GRPCServer from '@/grpc/GRPCServer'; import NodeConnectionManager from '@/nodes/NodeConnectionManager'; @@ -28,6 +29,7 @@ import * as keysUtils from '@/keys/utils'; import * as nodesUtils from '@/nodes/utils'; import * as notificationsUtils from '@/notifications/utils'; import * as testUtils from '../../utils'; +import { expectRemoteError } from '../../utils'; describe('notificationsSend', () => { const logger = new Logger('notificationsSend test', LogLevel.WARN, [ @@ -39,6 +41,7 @@ describe('notificationsSend', () => { let senderKeyManager: KeyManager; let dataDir: string; let nodeGraph: NodeGraph; + let queue: Queue; let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; let notificationsManager: NotificationsManager; @@ -108,23 +111,30 @@ describe('notificationsSend', () => { keyManager, logger: logger.getChild('NodeGraph'), }); + queue = new Queue({ + logger: logger.getChild('queue'), + }); nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, connConnectTime: 2000, connTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); - await nodeConnectionManager.start(); nodeManager = new NodeManager({ db, keyManager, nodeGraph, nodeConnectionManager, sigchain, + queue, logger, }); + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); notificationsManager = await NotificationsManager.createNotificationsManager({ acl, @@ -137,6 +147,8 @@ describe('notificationsSend', () => { const agentService = { notificationsSend: notificationsSend({ notificationsManager, + logger, + db, }), }; grpcServer = new GRPCServer({ logger }); @@ -157,6 +169,8 @@ describe('notificationsSend', () => { await grpcServer.stop(); await notificationsManager.stop(); await nodeConnectionManager.stop(); + await queue.stop(); + await nodeManager.stop(); await sigchain.stop(); await sigchain.stop(); await proxy.stop(); @@ -221,9 +235,10 @@ describe('notificationsSend', () => { }; const request1 = new notificationsPB.AgentNotification(); request1.setContent(notification1.toString()); - await expect(async () => + await expectRemoteError( grpcClient.notificationsSend(request1), - ).rejects.toThrow(notificationsErrors.ErrorNotificationsParse); + notificationsErrors.ErrorNotificationsParse, + ); // Check notification was not received let receivedNotifications = await notificationsManager.readNotifications(); expect(receivedNotifications).toHaveLength(0); @@ -248,9 +263,10 @@ describe('notificationsSend', () => { .sign(privateKey); const request2 = new notificationsPB.AgentNotification(); request2.setContent(signedNotification); - await expect(async () => + await expectRemoteError( grpcClient.notificationsSend(request2), - ).rejects.toThrow(notificationsErrors.ErrorNotificationsValidationFailed); + notificationsErrors.ErrorNotificationsValidationFailed, + ); // Check notification was not received receivedNotifications = await notificationsManager.readNotifications(); expect(receivedNotifications).toHaveLength(0); @@ -273,9 +289,8 @@ describe('notificationsSend', () => { ); const request = new notificationsPB.AgentNotification(); request.setContent(signedNotification); - await expect(async () => + await expectRemoteError( grpcClient.notificationsSend(request), - ).rejects.toThrow( notificationsErrors.ErrorNotificationsPermissionsNotFound, ); // Check notification was not received diff --git a/tests/agent/utils.ts b/tests/agent/utils.ts index 8cf77303e..afa61c0c0 100644 --- a/tests/agent/utils.ts +++ b/tests/agent/utils.ts @@ -1,24 +1,26 @@ import type { Host, Port, ProxyConfig } from '@/network/types'; - import type { IAgentServiceServer } from '@/proto/js/polykey/v1/agent_service_grpc_pb'; import type { KeyManager } from '@/keys'; import type { VaultManager } from '@/vaults'; -import type { NodeGraph, NodeConnectionManager, NodeManager } from '@/nodes'; +import type { NodeConnectionManager, NodeGraph, NodeManager } from '@/nodes'; import type { Sigchain } from '@/sigchain'; import type { NotificationsManager } from '@/notifications'; import type { ACL } from '@/acl'; import type { GestaltGraph } from '@/gestalts'; import type { NodeId } from 'nodes/types'; import type Proxy from 'network/Proxy'; +import type { DB } from '@matrixai/db'; +import type { Server } from '@grpc/grpc-js'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import * as grpc from '@grpc/grpc-js'; import { promisify } from '@/utils'; import { + AgentServiceService, createAgentService, GRPCClientAgent, - AgentServiceService, } from '@/agent'; -import * as testUtils from '../utils'; +import { timerStart } from '@/utils'; +import * as testNodesUtils from '../nodes/utils'; async function openTestAgentServer({ keyManager, @@ -31,6 +33,8 @@ async function openTestAgentServer({ acl, gestaltGraph, proxy, + db, + logger, }: { keyManager: KeyManager; vaultManager: VaultManager; @@ -42,7 +46,9 @@ async function openTestAgentServer({ acl: ACL; gestaltGraph: GestaltGraph; proxy: Proxy; -}) { + db: DB; + logger: Logger; +}): Promise<[Server, Port]> { const agentService: IAgentServiceServer = createAgentService({ keyManager, vaultManager, @@ -54,6 +60,8 @@ async function openTestAgentServer({ acl, gestaltGraph, proxy, + db, + logger, }); const server = new grpc.Server(); @@ -80,16 +88,15 @@ async function openTestAgentClient( const logger = new Logger('AgentClientTest', LogLevel.WARN, [ new StreamHandler(), ]); - const agentClient = await GRPCClientAgent.createGRPCClientAgent({ - nodeId: nodeId ?? testUtils.generateRandomNodeId(), + return await GRPCClientAgent.createGRPCClientAgent({ + nodeId: nodeId ?? testNodesUtils.generateRandomNodeId(), host: '127.0.0.1' as Host, port: port as Port, logger: logger, destroyCallback: async () => {}, proxyConfig, - timeout: 30000, + timer: timerStart(30000), }); - return agentClient; } async function closeTestAgentClient(client: GRPCClientAgent) { diff --git a/tests/bin/agent/lock.test.ts b/tests/bin/agent/lock.test.ts index 012bbcaf1..f12a7fc89 100644 --- a/tests/bin/agent/lock.test.ts +++ b/tests/bin/agent/lock.test.ts @@ -1,7 +1,7 @@ import path from 'path'; import fs from 'fs'; import prompts from 'prompts'; -import { mocked } from 'ts-jest/utils'; +import { mocked } from 'jest-mock'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import Session from '@/sessions/Session'; import config from '@/config'; @@ -48,7 +48,7 @@ describe('lock', () => { expect(await session.readToken()).toBeUndefined(); await session.stop(); }); - test('lock ensures reauthentication is required', async () => { + test('lock ensures re-authentication is required', async () => { const password = globalAgentPassword; mockedPrompts.mockClear(); mockedPrompts.mockImplementation(async (_opts: any) => { diff --git a/tests/bin/agent/lockall.test.ts b/tests/bin/agent/lockall.test.ts index c0096ff14..1f39d4b9e 100644 --- a/tests/bin/agent/lockall.test.ts +++ b/tests/bin/agent/lockall.test.ts @@ -1,11 +1,11 @@ import path from 'path'; import fs from 'fs'; import prompts from 'prompts'; -import { mocked } from 'ts-jest/utils'; +import { mocked } from 'jest-mock'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import Session from '@/sessions/Session'; import config from '@/config'; -import * as clientErrors from '@/client/errors'; +import * as errors from '@/errors'; import * as testBinUtils from '../utils'; import * as testUtils from '../../utils'; @@ -113,17 +113,15 @@ describe('lockall', () => { ); // Old token is invalid const { exitCode, stderr } = await testBinUtils.pkStdio( - ['agent', 'status'], + ['agent', 'status', '--format', 'json'], { PK_NODE_PATH: globalAgentDir, PK_TOKEN: token, }, globalAgentDir, ); - testBinUtils.expectProcessError( - exitCode, - stderr, - new clientErrors.ErrorClientAuthDenied(), - ); + testBinUtils.expectProcessError(exitCode, stderr, [ + new errors.ErrorClientAuthDenied(), + ]); }); }); diff --git a/tests/bin/agent/start.test.ts b/tests/bin/agent/start.test.ts index 06a83f544..6b419fde0 100644 --- a/tests/bin/agent/start.test.ts +++ b/tests/bin/agent/start.test.ts @@ -218,6 +218,8 @@ describe('start', () => { '--workers', '0', '--verbose', + '--format', + 'json', ], { PK_NODE_PATH: path.join(dataDir, 'polykey'), @@ -239,6 +241,8 @@ describe('start', () => { '--workers', '0', '--verbose', + '--format', + 'json', ], { PK_NODE_PATH: path.join(dataDir, 'polykey'), @@ -274,21 +278,17 @@ describe('start', () => { const errorStatusLocked = new statusErrors.ErrorStatusLocked(); // It's either the first or second process if (index === 0) { - testBinUtils.expectProcessError( - exitCode!, - stdErrLine1, + testBinUtils.expectProcessError(exitCode!, stdErrLine1, [ errorStatusLocked, - ); + ]); agentProcess2.kill('SIGQUIT'); [exitCode, signal] = await testBinUtils.processExit(agentProcess2); expect(exitCode).toBe(null); expect(signal).toBe('SIGQUIT'); } else if (index === 1) { - testBinUtils.expectProcessError( - exitCode!, - stdErrLine2, + testBinUtils.expectProcessError(exitCode!, stdErrLine2, [ errorStatusLocked, - ); + ]); agentProcess1.kill('SIGQUIT'); [exitCode, signal] = await testBinUtils.processExit(agentProcess1); expect(exitCode).toBe(null); @@ -316,6 +316,8 @@ describe('start', () => { '--workers', '0', '--verbose', + '--format', + 'json', ], { PK_NODE_PATH: path.join(dataDir, 'polykey'), @@ -325,7 +327,15 @@ describe('start', () => { logger.getChild('agentProcess'), ), testBinUtils.pkSpawn( - ['bootstrap', '--fresh', '--root-key-pair-bits', '1024', '--verbose'], + [ + 'bootstrap', + '--fresh', + '--root-key-pair-bits', + '1024', + '--verbose', + '--format', + 'json', + ], { PK_NODE_PATH: path.join(dataDir, 'polykey'), PK_PASSWORD: password, @@ -360,21 +370,17 @@ describe('start', () => { const errorStatusLocked = new statusErrors.ErrorStatusLocked(); // It's either the first or second process if (index === 0) { - testBinUtils.expectProcessError( - exitCode!, - stdErrLine1, + testBinUtils.expectProcessError(exitCode!, stdErrLine1, [ errorStatusLocked, - ); + ]); bootstrapProcess.kill('SIGTERM'); [exitCode, signal] = await testBinUtils.processExit(bootstrapProcess); expect(exitCode).toBe(null); expect(signal).toBe('SIGTERM'); } else if (index === 1) { - testBinUtils.expectProcessError( - exitCode!, - stdErrLine2, + testBinUtils.expectProcessError(exitCode!, stdErrLine2, [ errorStatusLocked, - ); + ]); agentProcess.kill('SIGTERM'); [exitCode, signal] = await testBinUtils.processExit(agentProcess); expect(exitCode).toBe(null); diff --git a/tests/bin/agent/stop.test.ts b/tests/bin/agent/stop.test.ts index c22843e45..b56f9b42c 100644 --- a/tests/bin/agent/stop.test.ts +++ b/tests/bin/agent/stop.test.ts @@ -197,17 +197,15 @@ describe('stop', () => { ); await status.waitFor('STARTING'); const { exitCode, stderr } = await testBinUtils.pkStdio( - ['agent', 'stop'], + ['agent', 'stop', '--format', 'json'], { PK_NODE_PATH: path.join(dataDir, 'polykey'), }, dataDir, ); - testBinUtils.expectProcessError( - exitCode, - stderr, + testBinUtils.expectProcessError(exitCode, stderr, [ new binErrors.ErrorCLIPolykeyAgentStatus('agent is starting'), - ); + ]); await status.waitFor('LIVE'); await testBinUtils.pkStdio( ['agent', 'stop'], @@ -256,18 +254,16 @@ describe('stop', () => { logger, }); const { exitCode, stderr } = await testBinUtils.pkStdio( - ['agent', 'stop'], + ['agent', 'stop', '--format', 'json'], { PK_NODE_PATH: path.join(dataDir, 'polykey'), PK_PASSWORD: 'wrong password', }, dataDir, ); - testBinUtils.expectProcessError( - exitCode, - stderr, + testBinUtils.expectProcessError(exitCode, stderr, [ new clientErrors.ErrorClientAuthDenied(), - ); + ]); // Should still be LIVE await sleep(500); const statusInfo = await status.readStatus(); diff --git a/tests/bin/bootstrap.test.ts b/tests/bin/bootstrap.test.ts index c108d0345..409afc4f7 100644 --- a/tests/bin/bootstrap.test.ts +++ b/tests/bin/bootstrap.test.ts @@ -5,7 +5,6 @@ import readline from 'readline'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { errors as statusErrors } from '@/status'; import { errors as bootstrapErrors } from '@/bootstrap'; -import * as binUtils from '@/bin/utils'; import * as testBinUtils from './utils'; describe('bootstrap', () => { @@ -68,6 +67,8 @@ describe('bootstrap', () => { '--root-key-pair-bits', '1024', '--verbose', + '--format', + 'json', ], { PK_PASSWORD: password, @@ -76,17 +77,9 @@ describe('bootstrap', () => { )); const errorBootstrapExistingState = new bootstrapErrors.ErrorBootstrapExistingState(); - expect(exitCode).toBe(errorBootstrapExistingState.exitCode); - const stdErrLine = stderr.trim().split('\n').pop(); - const eOutput = binUtils - .outputFormatter({ - type: 'error', - name: errorBootstrapExistingState.name, - description: errorBootstrapExistingState.description, - message: errorBootstrapExistingState.message, - }) - .trim(); - expect(stdErrLine).toBe(eOutput); + testBinUtils.expectProcessError(exitCode, stderr, [ + errorBootstrapExistingState, + ]); ({ exitCode, stdout, stderr } = await testBinUtils.pkStdio( [ 'bootstrap', @@ -117,7 +110,14 @@ describe('bootstrap', () => { const password = 'password'; const [bootstrapProcess1, bootstrapProcess2] = await Promise.all([ testBinUtils.pkSpawn( - ['bootstrap', '--root-key-pair-bits', '1024', '--verbose'], + [ + 'bootstrap', + '--root-key-pair-bits', + '1024', + '--verbose', + '--format', + 'json', + ], { PK_NODE_PATH: path.join(dataDir, 'polykey'), PK_PASSWORD: password, @@ -126,7 +126,14 @@ describe('bootstrap', () => { logger.getChild('bootstrapProcess1'), ), testBinUtils.pkSpawn( - ['bootstrap', '--root-key-pair-bits', '1024', '--verbose'], + [ + 'bootstrap', + '--root-key-pair-bits', + '1024', + '--verbose', + '--format', + 'json', + ], { PK_NODE_PATH: path.join(dataDir, 'polykey'), PK_PASSWORD: password, @@ -158,27 +165,22 @@ describe('bootstrap', () => { }); }); const errorStatusLocked = new statusErrors.ErrorStatusLocked(); - expect(exitCode).toBe(errorStatusLocked.exitCode); expect(signal).toBe(null); - const eOutput = binUtils - .outputFormatter({ - type: 'error', - name: errorStatusLocked.name, - description: errorStatusLocked.description, - message: errorStatusLocked.message, - }) - .trim(); // It's either the first or second process if (index === 0) { expect(stdErrLine1).toBeDefined(); - expect(stdErrLine1).toBe(eOutput); - const [exitCode] = await testBinUtils.processExit(bootstrapProcess2); - expect(exitCode).toBe(0); + testBinUtils.expectProcessError(exitCode!, stdErrLine1, [ + errorStatusLocked, + ]); + const [exitCode2] = await testBinUtils.processExit(bootstrapProcess2); + expect(exitCode2).toBe(0); } else if (index === 1) { expect(stdErrLine2).toBeDefined(); - expect(stdErrLine2).toBe(eOutput); - const [exitCode] = await testBinUtils.processExit(bootstrapProcess1); - expect(exitCode).toBe(0); + testBinUtils.expectProcessError(exitCode!, stdErrLine2, [ + errorStatusLocked, + ]); + const [exitCode2] = await testBinUtils.processExit(bootstrapProcess1); + expect(exitCode2).toBe(0); } }, global.defaultTimeout * 2, @@ -217,28 +219,27 @@ describe('bootstrap', () => { expect(signal).toBe('SIGINT'); // Attempting to bootstrap should fail with existing state const bootstrapProcess2 = await testBinUtils.pkStdio( - ['bootstrap', '--root-key-pair-bits', '1024', '--verbose'], + [ + 'bootstrap', + '--root-key-pair-bits', + '1024', + '--verbose', + '--format', + 'json', + ], { PK_NODE_PATH: path.join(dataDir, 'polykey'), PK_PASSWORD: password, }, dataDir, ); - const stdErrLine = bootstrapProcess2.stderr.trim().split('\n').pop(); const errorBootstrapExistingState = new bootstrapErrors.ErrorBootstrapExistingState(); - expect(bootstrapProcess2.exitCode).toBe( - errorBootstrapExistingState.exitCode, + testBinUtils.expectProcessError( + bootstrapProcess2.exitCode, + bootstrapProcess2.stderr, + [errorBootstrapExistingState], ); - const eOutput = binUtils - .outputFormatter({ - type: 'error', - name: errorBootstrapExistingState.name, - description: errorBootstrapExistingState.description, - message: errorBootstrapExistingState.message, - }) - .trim(); - expect(stdErrLine).toBe(eOutput); // Attempting to bootstrap with --fresh should succeed const bootstrapProcess3 = await testBinUtils.pkStdio( ['bootstrap', '--root-key-pair-bits', '1024', '--fresh', '--verbose'], diff --git a/tests/bin/identities/claim.test.ts b/tests/bin/identities/claim.test.ts index de1817c30..f2e730b9c 100644 --- a/tests/bin/identities/claim.test.ts +++ b/tests/bin/identities/claim.test.ts @@ -125,7 +125,7 @@ describe('claim', () => { }, dataDir, ); - expect(exitCode).toBe(1); + expect(exitCode).toBe(sysexits.NOPERM); }); test('should fail on invalid inputs', async () => { let exitCode; diff --git a/tests/bin/identities/trustUntrustList.test.ts b/tests/bin/identities/trustUntrustList.test.ts index a11c13260..4f0816cbe 100644 --- a/tests/bin/identities/trustUntrustList.test.ts +++ b/tests/bin/identities/trustUntrustList.test.ts @@ -1,14 +1,13 @@ import type { Host, Port } from '@/network/types'; import type { IdentityId, ProviderId } from '@/identities/types'; import type { ClaimLinkIdentity } from '@/claims/types'; -import type { Gestalt } from '@/gestalts/types'; import type { NodeId } from '@/nodes/types'; import os from 'os'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import PolykeyAgent from '@/PolykeyAgent'; -import { poll, sysexits } from '@/utils'; +import { sysexits } from '@/utils'; import * as nodesUtils from '@/nodes/utils'; import * as keysUtils from '@/keys/utils'; import * as claimsUtils from '@/claims/utils'; @@ -107,288 +106,256 @@ describe('trust/untrust/list', () => { mockedGenerateKeyPair.mockRestore(); mockedGenerateDeterministicKeyPair.mockRestore(); }); - test('trusts and untrusts a gestalt by node, adds it to the gestalt graph, and lists the gestalt with notify permission', async () => { - let exitCode, stdout; - // Add the node to our node graph and authenticate an identity on the - // provider - // This allows us to contact the members of the gestalt we want to trust - await testBinUtils.pkStdio( - [ - 'nodes', - 'add', - nodesUtils.encodeNodeId(nodeId), - nodeHost, - `${nodePort}`, - ], - { - PK_NODE_PATH: nodePath, - PK_PASSWORD: password, - }, - dataDir, - ); - const mockedBrowser = jest - .spyOn(identitiesUtils, 'browser') - .mockImplementation(() => {}); - await testBinUtils.pkStdio( - [ - 'identities', - 'authenticate', - testToken.providerId, - testToken.identityId, - ], - { - PK_NODE_PATH: nodePath, - PK_PASSWORD: password, - }, - dataDir, - ); - mockedBrowser.mockRestore(); - // Trust node - this should trigger discovery on the gestalt the node - // belongs to and add it to our gestalt graph - ({ exitCode } = await testBinUtils.pkStdio( - ['identities', 'trust', nodesUtils.encodeNodeId(nodeId)], - { - PK_NODE_PATH: nodePath, - PK_PASSWORD: password, - }, - dataDir, - )); - expect(exitCode).toBe(0); - // Since discovery is a background process we need to wait for the - // gestalt to be discovered - await poll( - async () => { - const gestalts = await poll>( - async () => { - return await pkAgent.gestaltGraph.getGestalts(); - }, - (_, result) => { - if (result.length === 1) return true; - return false; + test( + 'trusts and untrusts a gestalt by node, adds it to the gestalt graph, and lists the gestalt with notify permission', + async () => { + let exitCode, stdout; + // Add the node to our node graph and authenticate an identity on the + // provider + // This allows us to contact the members of the gestalt we want to trust + await testBinUtils.pkStdio( + [ + 'nodes', + 'add', + nodesUtils.encodeNodeId(nodeId), + nodeHost, + `${nodePort}`, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + const mockedBrowser = jest + .spyOn(identitiesUtils, 'browser') + .mockImplementation(() => {}); + await testBinUtils.pkStdio( + [ + 'identities', + 'authenticate', + testToken.providerId, + testToken.identityId, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + mockedBrowser.mockRestore(); + // Trust node - this should trigger discovery on the gestalt the node + // belongs to and add it to our gestalt graph + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'trust', nodesUtils.encodeNodeId(nodeId)], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + // Since discovery is a background process we need to wait for the + // gestalt to be discovered + await pkAgent.discovery.waitForDrained(); + // Check that gestalt was discovered and permission was set + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['identities', 'list', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toHaveLength(1); + expect(JSON.parse(stdout)[0]).toEqual({ + permissions: ['notify'], + nodes: [{ id: nodesUtils.encodeNodeId(nodeId) }], + identities: [ + { + providerId: provider.id, + identityId: identity, }, - 100, - ); - return gestalts[0]; - }, - (_, result) => { - if (result === undefined) return false; - if (Object.keys(result.matrix).length === 2) return true; - return false; - }, - 100, - ); - // Check that gestalt was discovered and permission was set - ({ exitCode, stdout } = await testBinUtils.pkStdio( - ['identities', 'list', '--format', 'json'], - { - PK_NODE_PATH: nodePath, - PK_PASSWORD: password, - }, - dataDir, - )); - expect(exitCode).toBe(0); - expect(JSON.parse(stdout)).toHaveLength(1); - expect(JSON.parse(stdout)[0]).toEqual({ - permissions: ['notify'], - nodes: [{ id: nodesUtils.encodeNodeId(nodeId) }], - identities: [ + ], + }); + // Untrust the gestalt by node + // This should remove the permission, but not the gestalt (from the gestalt + // graph) + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'untrust', nodesUtils.encodeNodeId(nodeId)], { - providerId: provider.id, - identityId: identity, + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, }, - ], - }); - // Untrust the gestalt by node - // This should remove the permission, but not the gestalt (from the gestalt - // graph) - ({ exitCode } = await testBinUtils.pkStdio( - ['identities', 'untrust', nodesUtils.encodeNodeId(nodeId)], - { - PK_NODE_PATH: nodePath, - PK_PASSWORD: password, - }, - dataDir, - )); - expect(exitCode).toBe(0); - // Check that gestalt still exists but has no permissions - ({ exitCode, stdout } = await testBinUtils.pkStdio( - ['identities', 'list', '--format', 'json'], - { - PK_NODE_PATH: nodePath, - PK_PASSWORD: password, - }, - dataDir, - )); - expect(exitCode).toBe(0); - expect(JSON.parse(stdout)).toHaveLength(1); - expect(JSON.parse(stdout)[0]).toEqual({ - permissions: null, - nodes: [{ id: nodesUtils.encodeNodeId(nodeId) }], - identities: [ + dataDir, + )); + expect(exitCode).toBe(0); + // Check that gestalt still exists but has no permissions + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['identities', 'list', '--format', 'json'], { - providerId: provider.id, - identityId: identity, + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, }, - ], - }); - // Revert side-effects - await pkAgent.gestaltGraph.unsetNode(nodeId); - await pkAgent.gestaltGraph.unsetIdentity(provider.id, identity); - await pkAgent.nodeGraph.unsetNode(nodeId); - await pkAgent.identitiesManager.delToken( - testToken.providerId, - testToken.identityId, - ); - // @ts-ignore - get protected property - pkAgent.discovery.visitedVertices.clear(); - }); - test('trusts and untrusts a gestalt by identity, adds it to the gestalt graph, and lists the gestalt with notify permission', async () => { - let exitCode, stdout; - // Add the node to our node graph and authenticate an identity on the - // provider - // This allows us to contact the members of the gestalt we want to trust - await testBinUtils.pkStdio( - [ - 'nodes', - 'add', - nodesUtils.encodeNodeId(nodeId), - nodeHost, - `${nodePort}`, - ], - { - PK_NODE_PATH: nodePath, - PK_PASSWORD: password, - }, - dataDir, - ); - const mockedBrowser = jest - .spyOn(identitiesUtils, 'browser') - .mockImplementation(() => {}); - await testBinUtils.pkStdio( - [ - 'identities', - 'authenticate', + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toHaveLength(1); + expect(JSON.parse(stdout)[0]).toEqual({ + permissions: null, + nodes: [{ id: nodesUtils.encodeNodeId(nodeId) }], + identities: [ + { + providerId: provider.id, + identityId: identity, + }, + ], + }); + // Revert side-effects + await pkAgent.gestaltGraph.unsetNode(nodeId); + await pkAgent.gestaltGraph.unsetIdentity(provider.id, identity); + await pkAgent.nodeGraph.unsetNode(nodeId); + await pkAgent.identitiesManager.delToken( testToken.providerId, testToken.identityId, - ], - { - PK_NODE_PATH: nodePath, - PK_PASSWORD: password, - }, - dataDir, - ); - mockedBrowser.mockRestore(); - // Trust identity - this should trigger discovery on the gestalt the node - // belongs to and add it to our gestalt graph - // This command should fail first time as we need to allow time for the - // identity to be linked to a node in the node graph - ({ exitCode } = await testBinUtils.pkStdio( - ['identities', 'trust', providerString], - { - PK_NODE_PATH: nodePath, - PK_PASSWORD: password, - }, - dataDir, - )); - expect(exitCode).toBe(1); - // Since discovery is a background process we need to wait for the - // gestalt to be discovered - await poll( - async () => { - const gestalts = await poll>( - async () => { - return await pkAgent.gestaltGraph.getGestalts(); - }, - (_, result) => { - if (result.length === 1) return true; - return false; + ); + // @ts-ignore - get protected property + pkAgent.discovery.visitedVertices.clear(); + }, + global.defaultTimeout * 2, + ); + test( + 'trusts and untrusts a gestalt by identity, adds it to the gestalt graph, and lists the gestalt with notify permission', + async () => { + let exitCode, stdout; + // Add the node to our node graph and authenticate an identity on the + // provider + // This allows us to contact the members of the gestalt we want to trust + await testBinUtils.pkStdio( + [ + 'nodes', + 'add', + nodesUtils.encodeNodeId(nodeId), + nodeHost, + `${nodePort}`, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + const mockedBrowser = jest + .spyOn(identitiesUtils, 'browser') + .mockImplementation(() => {}); + await testBinUtils.pkStdio( + [ + 'identities', + 'authenticate', + testToken.providerId, + testToken.identityId, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + mockedBrowser.mockRestore(); + // Trust identity - this should trigger discovery on the gestalt the node + // belongs to and add it to our gestalt graph + // This command should fail first time as we need to allow time for the + // identity to be linked to a node in the node graph + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'trust', providerString], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(sysexits.NOUSER); + // Since discovery is a background process we need to wait for the + // gestalt to be discovered + await pkAgent.discovery.waitForDrained(); + // This time the command should succeed + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'trust', providerString], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + // Check that gestalt was discovered and permission was set + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['identities', 'list', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toHaveLength(1); + expect(JSON.parse(stdout)[0]).toEqual({ + permissions: ['notify'], + nodes: [{ id: nodesUtils.encodeNodeId(nodeId) }], + identities: [ + { + providerId: provider.id, + identityId: identity, }, - 100, - ); - return gestalts[0]; - }, - (_, result) => { - if (result === undefined) return false; - if (Object.keys(result.matrix).length === 2) return true; - return false; - }, - 100, - ); - // This time the command should succeed - ({ exitCode } = await testBinUtils.pkStdio( - ['identities', 'trust', providerString], - { - PK_NODE_PATH: nodePath, - PK_PASSWORD: password, - }, - dataDir, - )); - expect(exitCode).toBe(0); - // Check that gestalt was discovered and permission was set - ({ exitCode, stdout } = await testBinUtils.pkStdio( - ['identities', 'list', '--format', 'json'], - { - PK_NODE_PATH: nodePath, - PK_PASSWORD: password, - }, - dataDir, - )); - expect(exitCode).toBe(0); - expect(JSON.parse(stdout)).toHaveLength(1); - expect(JSON.parse(stdout)[0]).toEqual({ - permissions: ['notify'], - nodes: [{ id: nodesUtils.encodeNodeId(nodeId) }], - identities: [ + ], + }); + // Untrust the gestalt by node + // This should remove the permission, but not the gestalt (from the gestalt + // graph) + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'untrust', providerString], { - providerId: provider.id, - identityId: identity, + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, }, - ], - }); - // Untrust the gestalt by node - // This should remove the permission, but not the gestalt (from the gestalt - // graph) - ({ exitCode } = await testBinUtils.pkStdio( - ['identities', 'untrust', providerString], - { - PK_NODE_PATH: nodePath, - PK_PASSWORD: password, - }, - dataDir, - )); - expect(exitCode).toBe(0); - // Check that gestalt still exists but has no permissions - ({ exitCode, stdout } = await testBinUtils.pkStdio( - ['identities', 'list', '--format', 'json'], - { - PK_NODE_PATH: nodePath, - PK_PASSWORD: password, - }, - dataDir, - )); - expect(exitCode).toBe(0); - expect(JSON.parse(stdout)).toHaveLength(1); - expect(JSON.parse(stdout)[0]).toEqual({ - permissions: null, - nodes: [{ id: nodesUtils.encodeNodeId(nodeId) }], - identities: [ + dataDir, + )); + expect(exitCode).toBe(0); + // Check that gestalt still exists but has no permissions + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['identities', 'list', '--format', 'json'], { - providerId: provider.id, - identityId: identity, + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, }, - ], - }); - // Revert side-effects - await pkAgent.gestaltGraph.unsetNode(nodeId); - await pkAgent.gestaltGraph.unsetIdentity(provider.id, identity); - await pkAgent.nodeGraph.unsetNode(nodeId); - await pkAgent.identitiesManager.delToken( - testToken.providerId, - testToken.identityId, - ); - // @ts-ignore - get protected property - pkAgent.discovery.visitedVertices.clear(); - }); + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toHaveLength(1); + expect(JSON.parse(stdout)[0]).toEqual({ + permissions: null, + nodes: [{ id: nodesUtils.encodeNodeId(nodeId) }], + identities: [ + { + providerId: provider.id, + identityId: identity, + }, + ], + }); + // Revert side-effects + await pkAgent.gestaltGraph.unsetNode(nodeId); + await pkAgent.gestaltGraph.unsetIdentity(provider.id, identity); + await pkAgent.nodeGraph.unsetNode(nodeId); + await pkAgent.identitiesManager.delToken( + testToken.providerId, + testToken.identityId, + ); + // @ts-ignore - get protected property + pkAgent.discovery.visitedVertices.clear(); + }, + global.defaultTimeout * 2, + ); test('should fail on invalid inputs', async () => { let exitCode; // Trust diff --git a/tests/bin/nodes/add.test.ts b/tests/bin/nodes/add.test.ts index 062cf6cdf..b3bd7cc67 100644 --- a/tests/bin/nodes/add.test.ts +++ b/tests/bin/nodes/add.test.ts @@ -9,22 +9,25 @@ import { sysexits } from '@/utils'; import PolykeyAgent from '@/PolykeyAgent'; import * as nodesUtils from '@/nodes/utils'; import * as keysUtils from '@/keys/utils'; +import NodeManager from '@/nodes/NodeManager'; import * as testBinUtils from '../utils'; import * as testUtils from '../../utils'; +import * as testNodesUtils from '../../nodes/utils'; describe('add', () => { const logger = new Logger('add test', LogLevel.WARN, [new StreamHandler()]); const password = 'helloworld'; - const validNodeId = testUtils.generateRandomNodeId(); + const validNodeId = testNodesUtils.generateRandomNodeId(); const invalidNodeId = IdInternal.fromString('INVALIDID'); const validHost = '0.0.0.0'; const invalidHost = 'INVALIDHOST'; - const port = '55555'; + const port = 55555; let dataDir: string; let nodePath: string; let pkAgent: PolykeyAgent; let mockedGenerateKeyPair: jest.SpyInstance; let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + let mockedPingNode: jest.SpyInstance; beforeAll(async () => { const globalKeyPair = await testUtils.setupGlobalKeypair(); mockedGenerateKeyPair = jest @@ -37,6 +40,7 @@ describe('add', () => { path.join(os.tmpdir(), 'polykey-test-'), ); nodePath = path.join(dataDir, 'polykey'); + mockedPingNode = jest.spyOn(NodeManager.prototype, 'pingNode'); // Cannot use the shared global agent since we can't 'un-add' a node pkAgent = await PolykeyAgent.createPolykeyAgent({ password, @@ -59,10 +63,22 @@ describe('add', () => { }); mockedGenerateKeyPair.mockRestore(); mockedGenerateDeterministicKeyPair.mockRestore(); + mockedPingNode.mockRestore(); + }); + beforeEach(async () => { + await pkAgent.nodeGraph.stop(); + await pkAgent.nodeGraph.start({ fresh: true }); + mockedPingNode.mockImplementation(() => true); }); test('adds a node', async () => { const { exitCode } = await testBinUtils.pkStdio( - ['nodes', 'add', nodesUtils.encodeNodeId(validNodeId), validHost, port], + [ + 'nodes', + 'add', + nodesUtils.encodeNodeId(validNodeId), + validHost, + `${port}`, + ], { PK_NODE_PATH: nodePath, PK_PASSWORD: password, @@ -80,11 +96,17 @@ describe('add', () => { dataDir, ); expect(stdout).toContain(validHost); - expect(stdout).toContain(port); + expect(stdout).toContain(`${port}`); }); test('fails to add a node (invalid node ID)', async () => { const { exitCode } = await testBinUtils.pkStdio( - ['nodes', 'add', nodesUtils.encodeNodeId(invalidNodeId), validHost, port], + [ + 'nodes', + 'add', + nodesUtils.encodeNodeId(invalidNodeId), + validHost, + `${port}`, + ], { PK_NODE_PATH: nodePath, PK_PASSWORD: password, @@ -95,7 +117,13 @@ describe('add', () => { }); test('fails to add a node (invalid IP address)', async () => { const { exitCode } = await testBinUtils.pkStdio( - ['nodes', 'add', nodesUtils.encodeNodeId(validNodeId), invalidHost, port], + [ + 'nodes', + 'add', + nodesUtils.encodeNodeId(validNodeId), + invalidHost, + `${port}`, + ], { PK_NODE_PATH: nodePath, PK_PASSWORD: password, @@ -104,4 +132,65 @@ describe('add', () => { ); expect(exitCode).toBe(sysexits.USAGE); }); + test('adds a node with --force flag', async () => { + const { exitCode } = await testBinUtils.pkStdio( + [ + 'nodes', + 'add', + '--force', + nodesUtils.encodeNodeId(validNodeId), + validHost, + `${port}`, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(0); + // Checking if node was added. + const node = await pkAgent.nodeGraph.getNode(validNodeId); + expect(node?.address).toEqual({ host: validHost, port: port }); + }); + test('fails to add node when ping fails', async () => { + mockedPingNode.mockImplementation(() => false); + const { exitCode } = await testBinUtils.pkStdio( + [ + 'nodes', + 'add', + nodesUtils.encodeNodeId(validNodeId), + validHost, + `${port}`, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(sysexits.NOHOST); + }); + test('adds a node with --no-ping flag', async () => { + mockedPingNode.mockImplementation(() => false); + const { exitCode } = await testBinUtils.pkStdio( + [ + 'nodes', + 'add', + '--no-ping', + nodesUtils.encodeNodeId(validNodeId), + validHost, + `${port}`, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(0); + // Checking if node was added. + const node = await pkAgent.nodeGraph.getNode(validNodeId); + expect(node?.address).toEqual({ host: validHost, port: port }); + }); }); diff --git a/tests/bin/nodes/find.test.ts b/tests/bin/nodes/find.test.ts index 6be67177a..b60804c64 100644 --- a/tests/bin/nodes/find.test.ts +++ b/tests/bin/nodes/find.test.ts @@ -7,6 +7,7 @@ import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import PolykeyAgent from '@/PolykeyAgent'; import * as nodesUtils from '@/nodes/utils'; import * as keysUtils from '@/keys/utils'; +import { sysexits } from '@/errors'; import * as testBinUtils from '../utils'; import * as testNodesUtils from '../../nodes/utils'; import * as testUtils from '../../utils'; @@ -157,31 +158,37 @@ describe('find', () => { port: remoteOfflinePort, }); }); - test('fails to find an unknown node', async () => { - const unknownNodeId = nodesUtils.decodeNodeId( - 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', - ); - const { exitCode, stdout } = await testBinUtils.pkStdio( - [ - 'nodes', - 'find', - nodesUtils.encodeNodeId(unknownNodeId!), - '--format', - 'json', - ], - { - PK_NODE_PATH: nodePath, - PK_PASSWORD: password, - }, - dataDir, - ); - expect(exitCode).toBe(1); - expect(JSON.parse(stdout)).toEqual({ - success: false, - message: `Failed to find node ${nodesUtils.encodeNodeId(unknownNodeId!)}`, - id: nodesUtils.encodeNodeId(unknownNodeId!), - host: '', - port: 0, - }); - }); + test( + 'fails to find an unknown node', + async () => { + const unknownNodeId = nodesUtils.decodeNodeId( + 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', + ); + const { exitCode, stdout } = await testBinUtils.pkStdio( + [ + 'nodes', + 'find', + nodesUtils.encodeNodeId(unknownNodeId!), + '--format', + 'json', + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(sysexits.GENERAL); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: `Failed to find node ${nodesUtils.encodeNodeId( + unknownNodeId!, + )}`, + id: nodesUtils.encodeNodeId(unknownNodeId!), + host: '', + port: 0, + }); + }, + global.failedConnectionTimeout, + ); }); diff --git a/tests/bin/nodes/ping.test.ts b/tests/bin/nodes/ping.test.ts index 196ec8ce8..f531a04d2 100644 --- a/tests/bin/nodes/ping.test.ts +++ b/tests/bin/nodes/ping.test.ts @@ -7,6 +7,7 @@ import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import PolykeyAgent from '@/PolykeyAgent'; import * as nodesUtils from '@/nodes/utils'; import * as keysUtils from '@/keys/utils'; +import { sysexits } from '@/errors'; import * as testBinUtils from '../utils'; import * as testNodesUtils from '../../nodes/utils'; import * as testUtils from '../../utils'; @@ -119,7 +120,7 @@ describe('ping', () => { }, dataDir, ); - expect(exitCode).toBe(1); // Should fail with no response. for automation purposes. + expect(exitCode).toBe(sysexits.GENERAL); // Should fail with no response. for automation purposes. expect(stderr).toContain('No response received'); expect(JSON.parse(stdout)).toEqual({ success: false, diff --git a/tests/bin/sessions.test.ts b/tests/bin/sessions.test.ts index b688eb2ef..0487b9f97 100644 --- a/tests/bin/sessions.test.ts +++ b/tests/bin/sessions.test.ts @@ -6,7 +6,7 @@ import os from 'os'; import path from 'path'; import fs from 'fs'; -import { mocked } from 'ts-jest/utils'; +import { mocked } from 'jest-mock'; import prompts from 'prompts'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Session } from '@/sessions'; @@ -83,7 +83,7 @@ describe('sessions', () => { let exitCode, stderr; // Password and Token set ({ exitCode, stderr } = await testBinUtils.pkStdio( - ['agent', 'status'], + ['agent', 'status', '--format', 'json'], { PK_NODE_PATH: globalAgentDir, PK_PASSWORD: 'invalid', @@ -91,14 +91,12 @@ describe('sessions', () => { }, globalAgentDir, )); - testBinUtils.expectProcessError( - exitCode, - stderr, + testBinUtils.expectProcessError(exitCode, stderr, [ new clientErrors.ErrorClientAuthDenied(), - ); + ]); // Password set ({ exitCode, stderr } = await testBinUtils.pkStdio( - ['agent', 'status'], + ['agent', 'status', '--format', 'json'], { PK_NODE_PATH: globalAgentDir, PK_PASSWORD: 'invalid', @@ -106,14 +104,12 @@ describe('sessions', () => { }, globalAgentDir, )); - testBinUtils.expectProcessError( - exitCode, - stderr, + testBinUtils.expectProcessError(exitCode, stderr, [ new clientErrors.ErrorClientAuthDenied(), - ); + ]); // Token set ({ exitCode, stderr } = await testBinUtils.pkStdio( - ['agent', 'status'], + ['agent', 'status', '--format', 'json'], { PK_NODE_PATH: globalAgentDir, PK_PASSWORD: undefined, @@ -121,11 +117,9 @@ describe('sessions', () => { }, globalAgentDir, )); - testBinUtils.expectProcessError( - exitCode, - stderr, + testBinUtils.expectProcessError(exitCode, stderr, [ new clientErrors.ErrorClientAuthDenied(), - ); + ]); }); test('prompt for password to authenticate attended commands', async () => { const password = globalAgentPassword; diff --git a/tests/bin/utils.retryAuthentication.test.ts b/tests/bin/utils.retryAuthentication.test.ts index 9a97f050f..32e45eee3 100644 --- a/tests/bin/utils.retryAuthentication.test.ts +++ b/tests/bin/utils.retryAuthentication.test.ts @@ -1,5 +1,5 @@ import prompts from 'prompts'; -import { mocked } from 'ts-jest/utils'; +import { mocked } from 'jest-mock'; import mockedEnv from 'mocked-env'; import { utils as clientUtils, errors as clientErrors } from '@/client'; import * as binUtils from '@/bin/utils'; diff --git a/tests/bin/utils.test.ts b/tests/bin/utils.test.ts index 42661b0af..6a53667da 100644 --- a/tests/bin/utils.test.ts +++ b/tests/bin/utils.test.ts @@ -1,4 +1,9 @@ -import * as binUtils from '@/bin/utils'; +import type { Host, Port } from '@/network/types'; +import ErrorPolykey from '@/ErrorPolykey'; +import * as binUtils from '@/bin/utils/utils'; +import * as nodesUtils from '@/nodes/utils'; +import * as grpcErrors from '@/grpc/errors'; +import * as testUtils from '../utils'; describe('bin/utils', () => { test('list in human and json format', () => { @@ -70,4 +75,113 @@ describe('bin/utils', () => { }), ).toBe('{"key1":"value1","key2":"value2"}\n'); }); + test('errors in human and json format', () => { + const timestamp = new Date(); + const data = { string: 'one', number: 1 }; + const host = '127.0.0.1' as Host; + const port = 55555 as Port; + const nodeId = testUtils.generateRandomNodeId(); + const standardError = new TypeError('some error'); + const pkError = new ErrorPolykey('some pk error', { + timestamp, + data, + }); + const remoteError = new grpcErrors.ErrorPolykeyRemote( + { + nodeId, + host, + port, + command: 'some command', + }, + 'some remote error', + { timestamp, cause: pkError }, + ); + const twoRemoteErrors = new grpcErrors.ErrorPolykeyRemote( + { + nodeId, + host, + port, + command: 'command 2', + }, + 'remote error', + { + timestamp, + cause: new grpcErrors.ErrorPolykeyRemote( + { + nodeId, + host, + port, + command: 'command 1', + }, + undefined, + { + timestamp, + cause: new ErrorPolykey('pk error', { + timestamp, + cause: standardError, + }), + }, + ), + }, + ); + // Human + expect( + binUtils.outputFormatter({ type: 'error', data: standardError }), + ).toBe(`${standardError.name}: ${standardError.message}\n`); + expect(binUtils.outputFormatter({ type: 'error', data: pkError })).toBe( + `${pkError.name}: ${pkError.description} - ${pkError.message}\n` + + ` exitCode\t${pkError.exitCode}\n` + + ` timestamp\t${timestamp.toString()}\n` + + ` data\t${JSON.stringify(data)}\n`, + ); + expect(binUtils.outputFormatter({ type: 'error', data: remoteError })).toBe( + `${remoteError.name}: ${remoteError.description} - ${remoteError.message}\n` + + ` command\t${remoteError.metadata.command}\n` + + ` nodeId\t${nodesUtils.encodeNodeId(nodeId)}\n` + + ` host\t${host}\n` + + ` port\t${port}\n` + + ` timestamp\t${timestamp.toString()}\n` + + ` cause: ${remoteError.cause.name}: ${remoteError.cause.description} - ${remoteError.cause.message}\n` + + ` exitCode\t${pkError.exitCode}\n` + + ` timestamp\t${timestamp.toString()}\n` + + ` data\t${JSON.stringify(data)}\n`, + ); + expect( + binUtils.outputFormatter({ type: 'error', data: twoRemoteErrors }), + ).toBe( + `${twoRemoteErrors.name}: ${twoRemoteErrors.description} - ${twoRemoteErrors.message}\n` + + ` command\t${twoRemoteErrors.metadata.command}\n` + + ` nodeId\t${nodesUtils.encodeNodeId(nodeId)}\n` + + ` host\t${host}\n` + + ` port\t${port}\n` + + ` timestamp\t${timestamp.toString()}\n` + + ` cause: ${twoRemoteErrors.cause.name}: ${twoRemoteErrors.cause.description}\n` + + ` command\t${twoRemoteErrors.cause.metadata.command}\n` + + ` nodeId\t${nodesUtils.encodeNodeId(nodeId)}\n` + + ` host\t${host}\n` + + ` port\t${port}\n` + + ` timestamp\t${timestamp.toString()}\n` + + ` cause: ${twoRemoteErrors.cause.cause.name}: ${twoRemoteErrors.cause.cause.description} - ${twoRemoteErrors.cause.cause.message}\n` + + ` exitCode\t${pkError.exitCode}\n` + + ` timestamp\t${timestamp.toString()}\n` + + ` cause: ${standardError.name}: ${standardError.message}\n`, + ); + // JSON + expect( + binUtils.outputFormatter({ type: 'json', data: standardError }), + ).toBe( + `{"type":"${standardError.name}","data":{"message":"${ + standardError.message + }","stack":"${standardError.stack?.replaceAll('\n', '\\n')}"}}\n`, + ); + expect(binUtils.outputFormatter({ type: 'json', data: pkError })).toBe( + JSON.stringify(pkError.toJSON()) + '\n', + ); + expect(binUtils.outputFormatter({ type: 'json', data: remoteError })).toBe( + JSON.stringify(remoteError.toJSON()) + '\n', + ); + expect( + binUtils.outputFormatter({ type: 'json', data: twoRemoteErrors }), + ).toBe(JSON.stringify(twoRemoteErrors.toJSON()) + '\n'); + }); }); diff --git a/tests/bin/utils.ts b/tests/bin/utils.ts index 47211b018..c6cc42c54 100644 --- a/tests/bin/utils.ts +++ b/tests/bin/utils.ts @@ -1,17 +1,45 @@ import type { ChildProcess } from 'child_process'; import type ErrorPolykey from '@/ErrorPolykey'; +import child_process from 'child_process'; import os from 'os'; import fs from 'fs'; import path from 'path'; import process from 'process'; -import child_process from 'child_process'; import readline from 'readline'; import * as mockProcess from 'jest-mock-process'; import mockedEnv from 'mocked-env'; import nexpect from 'nexpect'; import Logger from '@matrixai/logger'; import main from '@/bin/polykey'; -import * as binUtils from '@/bin/utils'; + +/** + * Wrapper for execFile to make it asynchronous and non-blocking + */ +async function exec( + command: string, + args: Array = [], +): Promise<{ + stdout: string; + stderr: string; +}> { + return new Promise((resolve, reject) => { + child_process.execFile( + command, + args, + { windowsHide: true }, + (error, stdout, stderr) => { + if (error) { + reject(error); + } else { + return resolve({ + stdout, + stderr, + }); + } + }, + ); + }); +} /** * Runs pk command functionally @@ -339,26 +367,30 @@ async function processExit( /** * Checks exit code and stderr against ErrorPolykey + * Errors should contain all of the errors in the expected error chain + * starting with the outermost error (excluding ErrorPolykeyRemote) + * When using this function, the command must be run with --format=json */ function expectProcessError( exitCode: number, stderr: string, - error: ErrorPolykey, + errors: Array>, ) { - expect(exitCode).toBe(error.exitCode); + expect(exitCode).toBe(errors[0].exitCode); const stdErrLine = stderr.trim().split('\n').pop(); - const errorOutput = binUtils - .outputFormatter({ - type: 'error', - name: error.name, - description: error.description, - message: error.message, - }) - .trim(); - expect(stdErrLine).toBe(errorOutput); + let currentError = JSON.parse(stdErrLine!); + while (currentError.type === 'ErrorPolykeyRemote') { + currentError = currentError.data.cause; + } + for (const error of errors) { + expect(currentError.type).toBe(error.name); + expect(currentError.data.message).toBe(error.message); + currentError = currentError.data.cause; + } } export { + exec, pk, pkStdio, pkExec, diff --git a/tests/bin/vaults/vaults.test.ts b/tests/bin/vaults/vaults.test.ts index 52b5f4e4c..949f208ee 100644 --- a/tests/bin/vaults/vaults.test.ts +++ b/tests/bin/vaults/vaults.test.ts @@ -11,7 +11,7 @@ import * as vaultsUtils from '@/vaults/utils'; import sysexits from '@/utils/sysexits'; import NotificationsManager from '@/notifications/NotificationsManager'; import * as testBinUtils from '../utils'; -import * as testUtils from '../../utils'; +import * as testNodesUtils from '../../nodes/utils'; jest.mock('@/keys/utils', () => ({ ...jest.requireActual('@/keys/utils'), @@ -378,7 +378,7 @@ describe('CLI vaults', () => { mockedSendNotification.mockImplementation(async (_) => {}); const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); const vaultIdEncoded = vaultsUtils.encodeVaultId(vaultId); - const targetNodeId = testUtils.generateRandomNodeId(); + const targetNodeId = testNodesUtils.generateRandomNodeId(); const targetNodeIdEncoded = nodesUtils.encodeNodeId(targetNodeId); await polykeyAgent.gestaltGraph.setNode({ id: nodesUtils.encodeNodeId(targetNodeId), @@ -418,7 +418,7 @@ describe('CLI vaults', () => { ); const vaultIdEncoded1 = vaultsUtils.encodeVaultId(vaultId1); const vaultIdEncoded2 = vaultsUtils.encodeVaultId(vaultId2); - const targetNodeId = testUtils.generateRandomNodeId(); + const targetNodeId = testNodesUtils.generateRandomNodeId(); const targetNodeIdEncoded = nodesUtils.encodeNodeId(targetNodeId); await polykeyAgent.gestaltGraph.setNode({ id: nodesUtils.encodeNodeId(targetNodeId), @@ -489,7 +489,7 @@ describe('CLI vaults', () => { ); const vaultIdEncoded1 = vaultsUtils.encodeVaultId(vaultId1); const vaultIdEncoded2 = vaultsUtils.encodeVaultId(vaultId2); - const targetNodeId = testUtils.generateRandomNodeId(); + const targetNodeId = testNodesUtils.generateRandomNodeId(); const targetNodeIdEncoded = nodesUtils.encodeNodeId(targetNodeId); await polykeyAgent.gestaltGraph.setNode({ id: nodesUtils.encodeNodeId(targetNodeId), diff --git a/tests/claims/utils.test.ts b/tests/claims/utils.test.ts index f7c6e6410..e57403683 100644 --- a/tests/claims/utils.test.ts +++ b/tests/claims/utils.test.ts @@ -11,12 +11,13 @@ import * as claimsErrors from '@/claims/errors'; import { utils as keysUtils } from '@/keys'; import { utils as nodesUtils } from '@/nodes'; import * as testUtils from '../utils'; +import * as testNodesUtils from '../nodes/utils'; describe('claims/utils', () => { // Node Ids - const nodeId1 = testUtils.generateRandomNodeId(); + const nodeId1 = testNodesUtils.generateRandomNodeId(); const nodeId1Encoded = nodesUtils.encodeNodeId(nodeId1); - const nodeId2 = testUtils.generateRandomNodeId(); + const nodeId2 = testNodesUtils.generateRandomNodeId(); const nodeId2Encoded = nodesUtils.encodeNodeId(nodeId2); let publicKey: PublicKeyPem; @@ -327,9 +328,7 @@ describe('claims/utils', () => { // Create some dummy public key, and check that this does not verify const dummyKeyPair = await keysUtils.generateKeyPair(2048); - const dummyPublicKey = await keysUtils.publicKeyToPem( - dummyKeyPair.publicKey, - ); + const dummyPublicKey = keysUtils.publicKeyToPem(dummyKeyPair.publicKey); expect(await claimsUtils.verifyClaimSignature(claim, dummyPublicKey)).toBe( false, ); diff --git a/tests/client/GRPCClientClient.test.ts b/tests/client/GRPCClientClient.test.ts index a6ce3f3bb..b90406a80 100644 --- a/tests/client/GRPCClientClient.test.ts +++ b/tests/client/GRPCClientClient.test.ts @@ -1,6 +1,6 @@ +import type * as grpc from '@grpc/grpc-js'; import type { Host, Port } from '@/network/types'; import type { NodeId } from '@/nodes/types'; -import type * as grpc from '@grpc/grpc-js'; import os from 'os'; import path from 'path'; import fs from 'fs'; @@ -11,6 +11,7 @@ import Session from '@/sessions/Session'; import * as keysUtils from '@/keys/utils'; import * as clientErrors from '@/client/errors'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import { timerStart } from '@/utils'; import * as testClientUtils from './utils'; import * as testUtils from '../utils'; @@ -76,7 +77,7 @@ describe(GRPCClientClient.name, () => { port: port as Port, tlsConfig: { keyPrivatePem: undefined, certChainPem: undefined }, logger: logger, - timeout: 10000, + timer: timerStart(10000), session: session, }); await client.destroy(); diff --git a/tests/client/rpcVaults.test.ts b/tests/client/rpcVaults.test.ts deleted file mode 100644 index 6e477e62d..000000000 --- a/tests/client/rpcVaults.test.ts +++ /dev/null @@ -1,698 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type VaultManager from '@/vaults/VaultManager'; -import type { VaultId, VaultName } from '@/vaults/types'; -import type { ClientServiceClient } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import type { Stat } from 'encryptedfs'; -import os from 'os'; -import path from 'path'; -import fs from 'fs'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { PolykeyAgent } from '@'; -import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; -import * as secretsPB from '@/proto/js/polykey/v1/secrets/secrets_pb'; -import KeyManager from '@/keys/KeyManager'; -import Proxy from '@/network/Proxy'; -import * as grpcUtils from '@/grpc/utils'; -import * as vaultErrors from '@/vaults/errors'; -import * as vaultsUtils from '@/vaults/utils'; -import * as vaultOps from '@/vaults/VaultOps'; -import * as nodesUtils from '@/nodes/utils'; -import * as clientUtils from './utils'; - -jest.mock('@/keys/utils', () => ({ - ...jest.requireActual('@/keys/utils'), - generateDeterministicKeyPair: - jest.requireActual('@/keys/utils').generateKeyPair, -})); - -describe('Vaults client service', () => { - const password = 'password'; - const logger = new Logger('VaultsClientServerTest', LogLevel.WARN, [ - new StreamHandler(), - ]); - const vaultList = [ - 'Vault1' as VaultName, - 'Vault2' as VaultName, - 'Vault3' as VaultName, - 'Vault4' as VaultName, - ]; - const secretList = ['Secret1', 'Secret2', 'Secret3', 'Secret4']; - - let client: ClientServiceClient; - let server: grpc.Server; - let port: number; - let dataDir: string; - let pkAgent: PolykeyAgent; - let keyManager: KeyManager; - let vaultManager: VaultManager; - let passwordFile: string; - let callCredentials: grpc.Metadata; - - beforeAll(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - - passwordFile = path.join(dataDir, 'password'); - await fs.promises.writeFile(passwordFile, 'password'); - const keysPath = path.join(dataDir, 'keys'); - - keyManager = await KeyManager.createKeyManager({ - keysPath, - password, - logger, - }); - - const proxy = new Proxy({ - authToken: 'abc', - logger: logger, - }); - - pkAgent = await PolykeyAgent.createPolykeyAgent({ - password, - nodePath: dataDir, - logger, - proxy, - keyManager, - }); - - vaultManager = pkAgent.vaultManager; - - [server, port] = await clientUtils.openTestClientServer({ - pkAgent, - secure: false, - }); - - client = await clientUtils.openSimpleClientClient(port); - }, global.polykeyStartupTimeout); - afterAll(async () => { - await clientUtils.closeTestClientServer(server); - clientUtils.closeSimpleClientClient(client); - - await pkAgent.stop(); - await pkAgent.destroy(); - - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - await fs.promises.rm(passwordFile); - }); - beforeEach(async () => { - const sessionToken = await pkAgent.sessionManager.createToken(); - callCredentials = clientUtils.createCallCredentials(sessionToken); - }); - afterEach(async () => { - const aliveVaults = await vaultManager.listVaults(); - for (const vaultId of aliveVaults.values()) { - await vaultManager.destroyVault(vaultId); - } - }); - - describe('Vaults', () => { - test('should get vaults', async () => { - const listVaults = grpcUtils.promisifyReadableStreamCall( - client, - client.vaultsList, - ); - for (const vaultName of vaultList) { - await vaultManager.createVault(vaultName); - } - const emptyMessage = new utilsPB.EmptyMessage(); - const vaultStream = listVaults(emptyMessage, callCredentials); - const names: Array = []; - for await (const vault of vaultStream) { - names.push(vault.getVaultName()); - } - expect(names.sort()).toStrictEqual(vaultList.sort()); - }); - test('should create vault', async () => { - const createVault = grpcUtils.promisifyUnaryCall( - client, - client.vaultsCreate, - ); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultList[0]); - const vaultId = await createVault(vaultMessage, callCredentials); - const vaultNames = await vaultManager.listVaults(); - expect(vaultNames.get(vaultList[0])).toBeTruthy(); - expect( - vaultsUtils.encodeVaultId(vaultNames.get(vaultList[0])!), - ).toStrictEqual(vaultId.getNameOrId()); - }); - test('should delete vaults', async () => { - const deleteVault = grpcUtils.promisifyUnaryCall( - client, - client.vaultsDelete, - ); - for (const vaultName of vaultList) { - await vaultManager.createVault(vaultName); - } - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultList[0]); - await deleteVault(vaultMessage, callCredentials); - const listVaults = await vaultManager.listVaults(); - const modifiedVaultList: string[] = []; - for (const [vaultName] of listVaults) { - modifiedVaultList.push(vaultName); - } - expect(modifiedVaultList.sort()).toStrictEqual(vaultList.slice(1).sort()); - }); - test('should rename vaults', async () => { - const renameVault = grpcUtils.promisifyUnaryCall( - client, - client.vaultsRename, - ); - const vaultId1 = await vaultManager.createVault(vaultList[0]); - - const vaultRenameMessage = new vaultsPB.Rename(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId1)); - vaultRenameMessage.setVault(vaultMessage); - vaultRenameMessage.setNewName(vaultList[1]); - - const vaultId2 = await renameVault(vaultRenameMessage, callCredentials); - expect(vaultsUtils.decodeVaultId(vaultId2.getNameOrId())).toStrictEqual( - vaultId1, - ); - - const renamedVaultId = await vaultManager.getVaultId(vaultList[1]); - expect(renamedVaultId).toEqual(vaultId1); - }); - describe('Version', () => { - const secretVer1 = { - name: secretList[0], - content: 'Secret-1-content-ver1', - }; - const secretVer2 = { - name: secretList[0], - content: 'Secret-1-content-ver2', - }; - let vaultId: VaultId; - let vaultsVersion; - - beforeEach(async () => { - vaultId = await vaultManager.createVault(vaultList[0]); - vaultsVersion = grpcUtils.promisifyUnaryCall( - client, - client.vaultsVersion, - ); - }); - - test('should switch a vault to a version', async () => { - // Commit some history - const ver1Oid = await vaultManager.withVaults( - [vaultId], - async (vault) => { - await vault.writeF(async (efs) => { - await efs.writeFile(secretVer1.name, secretVer1.content); - }); - const ver1Oid = (await vault.log())[0].commitId; - await vault.writeF(async (efs) => { - await efs.writeFile(secretVer2.name, secretVer2.content); - }); - return ver1Oid; - }, - ); - - // Revert the version - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultList[0]); - - const vaultVersionMessage = new vaultsPB.Version(); - vaultVersionMessage.setVault(vaultMessage); - vaultVersionMessage.setVersionId(ver1Oid); - - const version = await vaultsVersion( - vaultVersionMessage, - callCredentials, - ); - expect(version.getIsLatestVersion()).toBeFalsy(); - // Read old history - - await vaultManager.withVaults([vaultId], async (vault) => { - await vault.readF(async (efs) => { - expect( - (await efs.readFile(secretVer1.name)).toString(), - ).toStrictEqual(secretVer1.content); - }); - }); - }); - test('should fail to find a non existent version', async () => { - // Revert the version - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - const vaultVersionMessage = new vaultsPB.Version(); - vaultVersionMessage.setVault(vaultMessage); - vaultVersionMessage.setVersionId('invalidOid'); - const version = vaultsVersion(vaultVersionMessage, callCredentials); - await expect(version).rejects.toThrow( - vaultErrors.ErrorVaultReferenceInvalid, - ); - - vaultVersionMessage.setVersionId( - '7660aa9a2fee90e875c2d19e5deefe882ca1d4d9', - ); - const version2 = vaultsVersion(vaultVersionMessage, callCredentials); - await expect(version2).rejects.toThrow( - vaultErrors.ErrorVaultReferenceMissing, - ); - }); - }); - describe('Vault Log', () => { - let vaultLog; - const secret1 = { name: secretList[0], content: 'Secret-1-content' }; - const secret2 = { name: secretList[1], content: 'Secret-2-content' }; - let vaultId: VaultId; - let commit1Oid: string; - let commit2Oid: string; - let commit3Oid: string; - - beforeEach(async () => { - vaultLog = grpcUtils.promisifyReadableStreamCall( - client, - client.vaultsLog, - ); - vaultId = await vaultManager.createVault(vaultList[0]); - - await vaultManager.withVaults([vaultId], async (vault) => { - await vault.writeF(async (efs) => { - await efs.writeFile(secret1.name, secret1.content); - }); - commit1Oid = (await vault.log(undefined, 0))[0].commitId; - - await vault.writeF(async (efs) => { - await efs.writeFile(secret2.name, secret2.content); - }); - commit2Oid = (await vault.log(undefined, 0))[0].commitId; - - await vault.writeF(async (efs) => { - await efs.unlink(secret2.name); - }); - commit3Oid = (await vault.log(undefined, 0))[0].commitId; - }); - }); - - test('should get the full log', async () => { - const vaultLog = - grpcUtils.promisifyReadableStreamCall( - client, - client.vaultsLog, - ); - const vaultsLogMessage = new vaultsPB.Log(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultList[0]); - vaultsLogMessage.setVault(vaultMessage); - const logStream = vaultLog(vaultsLogMessage, callCredentials); - const logMessages: vaultsPB.LogEntry[] = []; - for await (const log of logStream) { - logMessages.push(log); - } - // Checking commits exist in order. - expect(logMessages[2].getOid()).toEqual(commit1Oid); - expect(logMessages[1].getOid()).toEqual(commit2Oid); - expect(logMessages[0].getOid()).toEqual(commit3Oid); - }); - test('should get a part of the log', async () => { - const vaultsLogMessage = new vaultsPB.Log(); - const vaultMessage = new vaultsPB.Vault(); - - vaultMessage.setNameOrId(vaultList[0]); - vaultsLogMessage.setVault(vaultMessage); - vaultsLogMessage.setLogDepth(2); - - const logStream = await vaultLog(vaultsLogMessage, callCredentials); - const logMessages: vaultsPB.LogEntry[] = []; - for await (const log of logStream) { - logMessages.push(log); - } - - // Checking commits exist in order. - expect(logMessages[1].getOid()).toEqual(commit2Oid); - expect(logMessages[0].getOid()).toEqual(commit3Oid); - }); - test('should get a specific commit', async () => { - const vaultsLogMessage = new vaultsPB.Log(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultList[0]); - vaultsLogMessage.setVault(vaultMessage); - vaultsLogMessage.setCommitId(commit2Oid); - const logStream = await vaultLog(vaultsLogMessage, callCredentials); - const logMessages: vaultsPB.LogEntry[] = []; - for await (const log of logStream) { - logMessages.push(log); - } - // Checking commits exist in order. - expect(logMessages[0].getOid()).toEqual(commit2Oid); - }); - }); - test('should get vault permissions', async () => { - const vaultsPermissionsGet = - grpcUtils.promisifyReadableStreamCall( - client, - client.vaultsPermissionGet, - ); - - let remoteKeynode1: PolykeyAgent | undefined; - let remoteKeynode2: PolykeyAgent | undefined; - try { - remoteKeynode1 = await PolykeyAgent.createPolykeyAgent({ - password, - logger: logger.getChild('Remote Keynode 1'), - nodePath: path.join(dataDir, 'remoteKeynode1'), - }); - remoteKeynode2 = await PolykeyAgent.createPolykeyAgent({ - password, - logger: logger.getChild('Remote Keynode 2'), - nodePath: path.join(dataDir, 'remoteKeynode2'), - }); - const targetNodeId1 = remoteKeynode1.keyManager.getNodeId(); - const targetNodeId2 = remoteKeynode2.keyManager.getNodeId(); - const pkAgentNodeId = pkAgent.keyManager.getNodeId(); - await pkAgent.gestaltGraph.setNode({ - id: nodesUtils.encodeNodeId(targetNodeId1), - chain: {}, - }); - await pkAgent.gestaltGraph.setNode({ - id: nodesUtils.encodeNodeId(targetNodeId2), - chain: {}, - }); - - await pkAgent.nodeManager.setNode(targetNodeId1, { - host: remoteKeynode1.proxy.getProxyHost(), - port: remoteKeynode1.proxy.getProxyPort(), - }); - await pkAgent.nodeManager.setNode(targetNodeId2, { - host: remoteKeynode2.proxy.getProxyHost(), - port: remoteKeynode2.proxy.getProxyPort(), - }); - - await remoteKeynode1.nodeManager.setNode(pkAgentNodeId, { - host: pkAgent.proxy.getProxyHost(), - port: pkAgent.proxy.getProxyPort(), - }); - await remoteKeynode2.nodeManager.setNode(pkAgentNodeId, { - host: pkAgent.proxy.getProxyHost(), - port: pkAgent.proxy.getProxyPort(), - }); - await remoteKeynode1.acl.setNodePerm(pkAgentNodeId, { - gestalt: { - notify: null, - }, - vaults: {}, - }); - await remoteKeynode2.acl.setNodePerm(pkAgentNodeId, { - gestalt: { - notify: null, - }, - vaults: {}, - }); - - const vaultId1 = await vaultManager.createVault(vaultList[0]); - const vaultId2 = await vaultManager.createVault(vaultList[1]); - - await pkAgent.gestaltGraph.setGestaltActionByNode( - targetNodeId1, - 'scan', - ); - await pkAgent.acl.setVaultAction(vaultId1, targetNodeId1, 'clone'); - await pkAgent.acl.setVaultAction(vaultId1, targetNodeId1, 'pull'); - await pkAgent.gestaltGraph.setGestaltActionByNode( - targetNodeId2, - 'scan', - ); - await pkAgent.acl.setVaultAction(vaultId1, targetNodeId2, 'clone'); - await pkAgent.acl.setVaultAction(vaultId1, targetNodeId2, 'pull'); - await pkAgent.gestaltGraph.setGestaltActionByNode( - targetNodeId1, - 'scan', - ); - await pkAgent.acl.setVaultAction(vaultId2, targetNodeId1, 'clone'); - await pkAgent.acl.setVaultAction(vaultId2, targetNodeId1, 'pull'); - - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId1)); - - const permissionsStream = vaultsPermissionsGet( - vaultMessage, - callCredentials, - ); - const list: Record[] = []; - for await (const permission of permissionsStream) { - const permissionsList = permission.getVaultPermissionsList(); - expect(permissionsList).toContain('pull'); - expect(permissionsList).toContain('clone'); - list.push(permission.toObject()); - } - expect(list).toHaveLength(2); - - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId2)); - const permissionStream2 = vaultsPermissionsGet( - vaultMessage, - callCredentials, - ); - for await (const permission of permissionStream2) { - const permissionsList = permission.getVaultPermissionsList(); - expect(permissionsList).toContain('pull'); - expect(permissionsList).toContain('clone'); - const node = permission.getNode(); - const nodeId = node?.getNodeId(); - expect(nodeId).toEqual(nodesUtils.encodeNodeId(targetNodeId1)); - } - } finally { - await remoteKeynode1?.stop(); - await remoteKeynode2?.stop(); - } - }); - }); - describe('Secrets', () => { - test('should make a directory in a vault', async () => { - const mkdirVault = grpcUtils.promisifyUnaryCall( - client, - client.vaultsSecretsMkdir, - ); - - const vaultId = await vaultManager.createVault(vaultList[0]); - const dirPath = 'dir/dir1/dir2'; - const vaultMkdirMessage = new vaultsPB.Mkdir(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - vaultMkdirMessage.setVault(vaultMessage); - vaultMkdirMessage.setDirName(dirPath); - vaultMkdirMessage.setRecursive(true); - await mkdirVault(vaultMkdirMessage, callCredentials); - await vaultManager.withVaults([vaultId], async (vault) => { - await vault.readF(async (efs) => { - expect(await efs.exists(dirPath)).toBeTruthy(); - }); - }); - }); - test('should list secrets in a vault', async () => { - const listSecretsVault = - grpcUtils.promisifyReadableStreamCall( - client, - client.vaultsSecretsList, - ); - - const vaultId = await vaultManager.createVault(vaultList[0]); - await vaultManager.withVaults([vaultId], async (vault) => { - await vault.writeF(async (efs) => { - for (const secretName of secretList) { - await efs.writeFile(secretName, secretName); - } - }); - }); - - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - const secretsStream = listSecretsVault(vaultMessage, callCredentials); - const names: Array = []; - for await (const secret of secretsStream) { - names.push(secret.getSecretName()); - } - expect(names.sort()).toStrictEqual(secretList.sort()); - }); - test('should delete secrets in a vault', async () => { - const deleteSecretVault = - grpcUtils.promisifyUnaryCall( - client, - client.vaultsSecretsDelete, - ); - - const vaultId = await vaultManager.createVault(vaultList[0]); - await vaultManager.withVaults([vaultId], async (vault) => { - await vault.writeF(async (efs) => { - for (const secretName of secretList) { - await efs.writeFile(secretName, secretName); - } - }); - }); - - const secretMessage = new secretsPB.Secret(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - secretMessage.setVault(vaultMessage); - secretMessage.setSecretName(secretList[0]); - await deleteSecretVault(secretMessage, callCredentials); - await vaultManager.withVaults([vaultId], async (vault) => { - const secrets = await vault.readF(async (efs) => { - return await efs.readdir('.', { encoding: 'utf8' }); - }); - expect(secrets.sort()).toEqual(secretList.slice(1).sort()); - }); - }); - test('should edit secrets in a vault', async () => { - const editSecretVault = - grpcUtils.promisifyUnaryCall( - client, - client.vaultsSecretsEdit, - ); - const vaultId = await vaultManager.createVault(vaultList[0]); - await vaultManager.withVaults([vaultId], async (vault) => { - await vault.writeF(async (efs) => { - await efs.writeFile(secretList[0], secretList[0]); - }); - }); - const secretMessage = new secretsPB.Secret(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - secretMessage.setVault(vaultMessage); - secretMessage.setSecretName(secretList[0]); - secretMessage.setSecretContent(Buffer.from('content-change')); - await editSecretVault(secretMessage, callCredentials); - - await vaultManager.withVaults([vaultId], async (vault) => { - await vault.readF(async (efs) => { - expect((await efs.readFile(secretList[0])).toString()).toStrictEqual( - 'content-change', - ); - }); - }); - }); - test('should get secrets in a vault', async () => { - const getSecretVault = grpcUtils.promisifyUnaryCall( - client, - client.vaultsSecretsGet, - ); - const vaultId = await vaultManager.createVault(vaultList[0]); - await vaultManager.withVaults([vaultId], async (vault) => { - await vault.writeF(async (efs) => { - await efs.writeFile(secretList[0], secretList[0]); - }); - }); - const secretMessage = new secretsPB.Secret(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - secretMessage.setVault(vaultMessage); - secretMessage.setSecretName(secretList[0]); - const secret = await getSecretVault(secretMessage, callCredentials); - const secretContent = Buffer.from(secret.getSecretContent()).toString(); - expect(secretContent).toStrictEqual(secretList[0]); - }); - test('should rename secrets in a vault', async () => { - const renameSecretVault = - grpcUtils.promisifyUnaryCall( - client, - client.vaultsSecretsRename, - ); - const vaultId = await vaultManager.createVault(vaultList[0]); - await vaultManager.withVaults([vaultId], async (vault) => { - await vault.writeF(async (efs) => { - await efs.writeFile(secretList[0], secretList[0]); - }); - }); - const secretRenameMessage = new secretsPB.Rename(); - const vaultMessage = new vaultsPB.Vault(); - const secretMessage = new secretsPB.Secret(); - - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - secretMessage.setSecretName(secretList[0]); - secretMessage.setVault(vaultMessage); - secretRenameMessage.setNewName(secretList[1]); - secretRenameMessage.setOldSecret(secretMessage); - await renameSecretVault(secretRenameMessage, callCredentials); - - await vaultManager.withVaults([vaultId], async (vault) => { - const secrets = await vault.readF(async (efs) => { - return await efs.readdir('.'); - }); - expect(secrets.sort()).toEqual(secretList.splice(1, 1).sort()); - }); - }); - test('should add secrets in a vault', async () => { - const newSecretVault = - grpcUtils.promisifyUnaryCall( - client, - client.vaultsSecretsNew, - ); - - const vaultId = await vaultManager.createVault(vaultList[0]); - const secretMessage = new secretsPB.Secret(); - const vaultMessage = new vaultsPB.Vault(); - - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - secretMessage.setVault(vaultMessage); - secretMessage.setSecretName(secretList[0]); - secretMessage.setSecretContent(Buffer.from(secretList[0])); - await newSecretVault(secretMessage, callCredentials); - await vaultManager.withVaults([vaultId], async (vault) => { - const secret = await vault.readF(async (efs) => { - return await efs.readFile(secretList[0], { encoding: 'utf8' }); - }); - expect(secret).toBe(secretList[0]); - }); - }); - test('should add a directory of secrets in a vault', async () => { - const newDirSecretVault = - grpcUtils.promisifyUnaryCall( - client, - client.vaultsSecretsNewDir, - ); - - const secretDir = path.join(dataDir, 'secretDir'); - await fs.promises.mkdir(secretDir); - for (const secret of secretList) { - const secretFile = path.join(secretDir, secret); - // Write secret to file - await fs.promises.writeFile(secretFile, secret); - } - const vaultId = await vaultManager.createVault(vaultList[0]); - const secretDirectoryMessage = new secretsPB.Directory(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - secretDirectoryMessage.setVault(vaultMessage); - secretDirectoryMessage.setSecretDirectory(secretDir); - await newDirSecretVault(secretDirectoryMessage, callCredentials); - await vaultManager.withVaults([vaultId], async (vault) => { - const secrets = await vaultOps.listSecrets(vault); - expect(secrets.sort()).toEqual( - secretList.map((secret) => path.join('secretDir', secret)).sort(), - ); - }); - }); - test('should stat a file', async () => { - const getSecretStat = grpcUtils.promisifyUnaryCall( - client, - client.vaultsSecretsStat, - ); - const vaultId = await vaultManager.createVault(vaultList[0]); - await vaultManager.withVaults([vaultId], async (vault) => { - await vault.writeF(async (efs) => { - await efs.writeFile(secretList[0], secretList[0]); - }); - }); - const secretMessage = new secretsPB.Secret(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - secretMessage.setVault(vaultMessage); - secretMessage.setSecretName(secretList[0]); - const result = await getSecretStat(secretMessage, callCredentials); - const stat: Stat = JSON.parse(result.getJson()); - expect(stat.size).toBe(7); - expect(stat.blksize).toBe(4096); - expect(stat.blocks).toBe(1); - expect(stat.nlink).toBe(1); - }); - }); -}); diff --git a/tests/client/service/agentLockAll.test.ts b/tests/client/service/agentLockAll.test.ts index 21a533dd6..fe56a0d7d 100644 --- a/tests/client/service/agentLockAll.test.ts +++ b/tests/client/service/agentLockAll.test.ts @@ -14,6 +14,7 @@ import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_ import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as keysUtils from '@/keys/utils'; import * as clientUtils from '@/client/utils/utils'; +import { timerStart } from '@/utils/index'; import * as testUtils from '../../utils'; describe('agentLockall', () => { @@ -75,6 +76,8 @@ describe('agentLockall', () => { agentLockAll: agentLockAll({ authenticate, sessionManager, + logger, + db, }), }; grpcServer = new GRPCServer({ logger }); @@ -87,6 +90,7 @@ describe('agentLockall', () => { nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, port: grpcServer.getPort(), + timer: timerStart(5000), logger, }); }); diff --git a/tests/client/service/agentStatus.test.ts b/tests/client/service/agentStatus.test.ts index 04c68aa2e..cb26d32d2 100644 --- a/tests/client/service/agentStatus.test.ts +++ b/tests/client/service/agentStatus.test.ts @@ -83,6 +83,7 @@ describe('agentStatus', () => { grpcServerClient, grpcServerAgent, proxy, + logger, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/agentStop.test.ts b/tests/client/service/agentStop.test.ts index 6a5e00dd4..a799729cb 100644 --- a/tests/client/service/agentStop.test.ts +++ b/tests/client/service/agentStop.test.ts @@ -59,6 +59,7 @@ describe('agentStop', () => { agentStop: agentStop({ authenticate, pkAgent: pkAgent as unknown as PolykeyAgent, + logger, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/agentUnlock.test.ts b/tests/client/service/agentUnlock.test.ts index 4ca05aba0..4a64076ad 100644 --- a/tests/client/service/agentUnlock.test.ts +++ b/tests/client/service/agentUnlock.test.ts @@ -22,6 +22,7 @@ describe('agentUnlock', () => { const clientService = { agentUnlock: agentUnlock({ authenticate, + logger, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/gestaltsActionsSetUnsetGetByIdentity.test.ts b/tests/client/service/gestaltsActionsSetUnsetGetByIdentity.test.ts index 4ea77f742..381ec9b60 100644 --- a/tests/client/service/gestaltsActionsSetUnsetGetByIdentity.test.ts +++ b/tests/client/service/gestaltsActionsSetUnsetGetByIdentity.test.ts @@ -72,14 +72,20 @@ describe('gestaltsActionsByIdentity', () => { gestaltsActionsSetByIdentity: gestaltsActionsSetByIdentity({ authenticate, gestaltGraph, + logger, + db, }), gestaltsActionsGetByIdentity: gestaltsActionsGetByIdentity({ authenticate, gestaltGraph, + logger, + db, }), gestaltsActionsUnsetByIdentity: gestaltsActionsUnsetByIdentity({ authenticate, gestaltGraph, + logger, + db, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/gestaltsActionsSetUnsetGetByNode.test.ts b/tests/client/service/gestaltsActionsSetUnsetGetByNode.test.ts index 5eb2e40bf..439f9b754 100644 --- a/tests/client/service/gestaltsActionsSetUnsetGetByNode.test.ts +++ b/tests/client/service/gestaltsActionsSetUnsetGetByNode.test.ts @@ -66,14 +66,20 @@ describe('gestaltsActionsByNode', () => { gestaltsActionsSetByNode: gestaltsActionsSetByNode({ authenticate, gestaltGraph, + logger, + db, }), gestaltsActionsGetByNode: gestaltsActionsGetByNode({ authenticate, gestaltGraph, + logger, + db, }), gestaltsActionsUnsetByNode: gestaltsActionsUnsetByNode({ authenticate, gestaltGraph, + logger, + db, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/gestaltsDiscoveryByIdentity.test.ts b/tests/client/service/gestaltsDiscoveryByIdentity.test.ts index a987f6aad..f9789cb60 100644 --- a/tests/client/service/gestaltsDiscoveryByIdentity.test.ts +++ b/tests/client/service/gestaltsDiscoveryByIdentity.test.ts @@ -6,6 +6,7 @@ import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { Metadata } from '@grpc/grpc-js'; +import Queue from '@/nodes/Queue'; import GestaltGraph from '@/gestalts/GestaltGraph'; import ACL from '@/acl/ACL'; import KeyManager from '@/keys/KeyManager'; @@ -59,6 +60,7 @@ describe('gestaltsDiscoveryByIdentity', () => { let gestaltGraph: GestaltGraph; let identitiesManager: IdentitiesManager; let nodeGraph: NodeGraph; + let queue: Queue; let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; let sigchain: Sigchain; @@ -125,23 +127,30 @@ describe('gestaltsDiscoveryByIdentity', () => { keyManager, logger: logger.getChild('NodeGraph'), }); + queue = new Queue({ + logger: logger.getChild('queue'), + }); nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, connConnectTime: 2000, connTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); - await nodeConnectionManager.start(); nodeManager = new NodeManager({ db, keyManager, nodeConnectionManager, nodeGraph, sigchain, + queue, logger, }); + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); discovery = await Discovery.createDiscovery({ db, keyManager, @@ -155,6 +164,7 @@ describe('gestaltsDiscoveryByIdentity', () => { gestaltsDiscoveryByIdentity: gestaltsDiscoveryByIdentity({ authenticate, discovery, + logger, }), }; grpcServer = new GRPCServer({ logger }); @@ -176,6 +186,8 @@ describe('gestaltsDiscoveryByIdentity', () => { await discovery.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); + await nodeManager.stop(); + await queue.stop(); await sigchain.stop(); await proxy.stop(); await identitiesManager.stop(); diff --git a/tests/client/service/gestaltsDiscoveryByNode.test.ts b/tests/client/service/gestaltsDiscoveryByNode.test.ts index d03fe307a..3c0f00b10 100644 --- a/tests/client/service/gestaltsDiscoveryByNode.test.ts +++ b/tests/client/service/gestaltsDiscoveryByNode.test.ts @@ -6,6 +6,7 @@ import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { Metadata } from '@grpc/grpc-js'; +import Queue from '@/nodes/Queue'; import GestaltGraph from '@/gestalts/GestaltGraph'; import ACL from '@/acl/ACL'; import KeyManager from '@/keys/KeyManager'; @@ -26,6 +27,7 @@ import * as clientUtils from '@/client/utils/utils'; import * as keysUtils from '@/keys/utils'; import * as nodesUtils from '@/nodes/utils'; import * as testUtils from '../../utils'; +import * as testNodesUtils from '../../nodes/utils'; describe('gestaltsDiscoveryByNode', () => { const logger = new Logger('gestaltsDiscoveryByNode test', LogLevel.WARN, [ @@ -35,7 +37,7 @@ describe('gestaltsDiscoveryByNode', () => { const authenticate = async (metaClient, metaServer = new Metadata()) => metaServer; const node: NodeInfo = { - id: nodesUtils.encodeNodeId(testUtils.generateRandomNodeId()), + id: nodesUtils.encodeNodeId(testNodesUtils.generateRandomNodeId()), chain: {}, }; let mockedGenerateKeyPair: jest.SpyInstance; @@ -59,6 +61,7 @@ describe('gestaltsDiscoveryByNode', () => { let gestaltGraph: GestaltGraph; let identitiesManager: IdentitiesManager; let nodeGraph: NodeGraph; + let queue: Queue; let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; let sigchain: Sigchain; @@ -125,23 +128,30 @@ describe('gestaltsDiscoveryByNode', () => { keyManager, logger: logger.getChild('NodeGraph'), }); + queue = new Queue({ + logger: logger.getChild('queue'), + }); nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, connConnectTime: 2000, connTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); - await nodeConnectionManager.start(); nodeManager = new NodeManager({ db, keyManager, nodeConnectionManager, nodeGraph, sigchain, + queue, logger, }); + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); discovery = await Discovery.createDiscovery({ db, keyManager, @@ -155,6 +165,7 @@ describe('gestaltsDiscoveryByNode', () => { gestaltsDiscoveryByNode: gestaltsDiscoveryByNode({ authenticate, discovery, + logger, }), }; grpcServer = new GRPCServer({ logger }); @@ -176,6 +187,8 @@ describe('gestaltsDiscoveryByNode', () => { await discovery.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); + await nodeManager.stop(); + await queue.stop(); await sigchain.stop(); await proxy.stop(); await identitiesManager.stop(); diff --git a/tests/client/service/gestaltsGestaltGetByIdentity.test.ts b/tests/client/service/gestaltsGestaltGetByIdentity.test.ts index a8aa0b5bf..b6ecc2d71 100644 --- a/tests/client/service/gestaltsGestaltGetByIdentity.test.ts +++ b/tests/client/service/gestaltsGestaltGetByIdentity.test.ts @@ -89,6 +89,8 @@ describe('gestaltsGestaltGetByIdentity', () => { gestaltsGestaltGetByIdentity: gestaltsGestaltGetByIdentity({ authenticate, gestaltGraph, + logger, + db, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/gestaltsGestaltGetByNode.test.ts b/tests/client/service/gestaltsGestaltGetByNode.test.ts index ebff5bf7d..1d7a3ceb6 100644 --- a/tests/client/service/gestaltsGestaltGetByNode.test.ts +++ b/tests/client/service/gestaltsGestaltGetByNode.test.ts @@ -86,6 +86,8 @@ describe('gestaltsGestaltGetByNode', () => { gestaltsGestaltGetByNode: gestaltsGestaltGetByNode({ authenticate, gestaltGraph, + logger, + db, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/gestaltsGestaltList.test.ts b/tests/client/service/gestaltsGestaltList.test.ts index 6b714e073..1075a34f8 100644 --- a/tests/client/service/gestaltsGestaltList.test.ts +++ b/tests/client/service/gestaltsGestaltList.test.ts @@ -92,6 +92,8 @@ describe('gestaltsGestaltList', () => { gestaltsGestaltList: gestaltsGestaltList({ authenticate, gestaltGraph, + logger, + db, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/gestaltsGestaltTrustByIdentity.test.ts b/tests/client/service/gestaltsGestaltTrustByIdentity.test.ts index 8bd0a749e..01a162e31 100644 --- a/tests/client/service/gestaltsGestaltTrustByIdentity.test.ts +++ b/tests/client/service/gestaltsGestaltTrustByIdentity.test.ts @@ -1,7 +1,6 @@ import type { NodeIdEncoded } from '@/nodes/types'; import type { ClaimLinkIdentity } from '@/claims/types'; import type { ChainData } from '@/sigchain/types'; -import type { Gestalt } from '@/gestalts/types'; import type { IdentityId } from '@/identities/types'; import type { Host, Port } from '@/network/types'; import fs from 'fs'; @@ -10,6 +9,7 @@ import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { Metadata } from '@grpc/grpc-js'; +import Queue from '@/nodes/Queue'; import PolykeyAgent from '@/PolykeyAgent'; import KeyManager from '@/keys/KeyManager'; import Discovery from '@/discovery/Discovery'; @@ -25,7 +25,6 @@ import GRPCServer from '@/grpc/GRPCServer'; import GRPCClientClient from '@/client/GRPCClientClient'; import gestaltsGestaltTrustByIdentity from '@/client/service/gestaltsGestaltTrustByIdentity'; import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import { poll } from '@/utils'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; import * as claimsUtils from '@/claims/utils'; @@ -35,6 +34,7 @@ import * as clientUtils from '@/client/utils/utils'; import * as nodesUtils from '@/nodes/utils'; import * as testUtils from '../../utils'; import TestProvider from '../../identities/TestProvider'; +import { expectRemoteError } from '../../utils'; describe('gestaltsGestaltTrustByIdentity', () => { const logger = new Logger( @@ -116,6 +116,7 @@ describe('gestaltsGestaltTrustByIdentity', () => { let discovery: Discovery; let gestaltGraph: GestaltGraph; let identitiesManager: IdentitiesManager; + let queue: Queue; let nodeManager: NodeManager; let nodeConnectionManager: NodeConnectionManager; let nodeGraph: NodeGraph; @@ -192,23 +193,30 @@ describe('gestaltsGestaltTrustByIdentity', () => { keyManager, logger: logger.getChild('NodeGraph'), }); + queue = new Queue({ + logger: logger.getChild('queue'), + }); nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, connConnectTime: 2000, connTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); - await nodeConnectionManager.start(); nodeManager = new NodeManager({ db, keyManager, - sigchain, - nodeGraph, nodeConnectionManager, - logger: logger.getChild('nodeManager'), + nodeGraph, + sigchain, + queue, + logger, }); + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); await nodeManager.setNode(nodesUtils.decodeNodeId(nodeId)!, { host: node.proxy.getProxyHost(), port: node.proxy.getProxyPort(), @@ -227,6 +235,8 @@ describe('gestaltsGestaltTrustByIdentity', () => { authenticate, gestaltGraph, discovery, + logger, + db, }), }; grpcServer = new GRPCServer({ logger }); @@ -247,6 +257,8 @@ describe('gestaltsGestaltTrustByIdentity', () => { await grpcServer.stop(); await discovery.stop(); await nodeConnectionManager.stop(); + await nodeManager.stop(); + await queue.stop(); await nodeGraph.stop(); await proxy.stop(); await sigchain.stop(); @@ -299,34 +311,15 @@ describe('gestaltsGestaltTrustByIdentity', () => { request.setIdentityId(connectedIdentity); // Should fail on first attempt - need to allow time for the identity to be // linked to a node via discovery - await expect( + await expectRemoteError( grpcClient.gestaltsGestaltTrustByIdentity( request, clientUtils.encodeAuthFromPassword(password), ), - ).rejects.toThrow(gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing); - // Wait for both identity and node to be set in GG - await poll( - async () => { - const gestalts = await poll>( - async () => { - return await gestaltGraph.getGestalts(); - }, - (_, result) => { - if (result.length === 1) return true; - return false; - }, - 100, - ); - return gestalts[0]; - }, - (_, result) => { - if (result === undefined) return false; - if (Object.keys(result.matrix).length === 2) return true; - return false; - }, - 100, + gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, ); + // Wait for both identity and node to be set in GG + await discovery.waitForDrained(); const response = await grpcClient.gestaltsGestaltTrustByIdentity( request, clientUtils.encodeAuthFromPassword(password), @@ -350,20 +343,22 @@ describe('gestaltsGestaltTrustByIdentity', () => { request.setProviderId(testProvider.id); request.setIdentityId('disconnected-user'); // Should fail on first attempt - attempt to find a connected node - await expect( + await expectRemoteError( grpcClient.gestaltsGestaltTrustByIdentity( request, clientUtils.encodeAuthFromPassword(password), ), - ).rejects.toThrow(gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing); + gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, + ); // Wait and try again - should fail again because the identity has no // linked nodes we can trust - await expect( + await expectRemoteError( grpcClient.gestaltsGestaltTrustByIdentity( request, clientUtils.encodeAuthFromPassword(password), ), - ).rejects.toThrow(gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing); + gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, + ); }); test('trust extends to entire gestalt', async () => { await gestaltGraph.linkNodeAndIdentity( @@ -414,35 +409,16 @@ describe('gestaltsGestaltTrustByIdentity', () => { request.setIdentityId(connectedIdentity); // Should fail on first attempt - need to allow time for the identity to be // linked to a node via discovery - await expect( + await expectRemoteError( grpcClient.gestaltsGestaltTrustByIdentity( request, clientUtils.encodeAuthFromPassword(password), ), - ).rejects.toThrow(gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing); + gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, + ); // Wait and try again - should succeed second time // Wait for both identity and node to be set in GG - await poll( - async () => { - const gestalts = await poll>( - async () => { - return await gestaltGraph.getGestalts(); - }, - (_, result) => { - if (result.length === 1) return true; - return false; - }, - 100, - ); - return gestalts[0]; - }, - (_, result) => { - if (result === undefined) return false; - if (Object.keys(result.matrix).length === 2) return true; - return false; - }, - 100, - ); + await discovery.waitForDrained(); const response = await grpcClient.gestaltsGestaltTrustByIdentity( request, clientUtils.encodeAuthFromPassword(password), diff --git a/tests/client/service/gestaltsGestaltTrustByNode.test.ts b/tests/client/service/gestaltsGestaltTrustByNode.test.ts index ccc7c827d..df84503a7 100644 --- a/tests/client/service/gestaltsGestaltTrustByNode.test.ts +++ b/tests/client/service/gestaltsGestaltTrustByNode.test.ts @@ -10,6 +10,7 @@ import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { Metadata } from '@grpc/grpc-js'; +import Queue from '@/nodes/Queue'; import PolykeyAgent from '@/PolykeyAgent'; import KeyManager from '@/keys/KeyManager'; import Discovery from '@/discovery/Discovery'; @@ -114,6 +115,7 @@ describe('gestaltsGestaltTrustByNode', () => { let discovery: Discovery; let gestaltGraph: GestaltGraph; let identitiesManager: IdentitiesManager; + let queue: Queue; let nodeManager: NodeManager; let nodeConnectionManager: NodeConnectionManager; let nodeGraph: NodeGraph; @@ -190,23 +192,30 @@ describe('gestaltsGestaltTrustByNode', () => { keyManager, logger: logger.getChild('NodeGraph'), }); + queue = new Queue({ + logger: logger.getChild('queue'), + }); nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, connConnectTime: 2000, connTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); - await nodeConnectionManager.start(); nodeManager = new NodeManager({ db, keyManager, - sigchain, - nodeGraph, nodeConnectionManager, - logger: logger.getChild('nodeManager'), + nodeGraph, + sigchain, + queue, + logger, }); + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); await nodeManager.setNode(nodesUtils.decodeNodeId(nodeId)!, { host: node.proxy.getProxyHost(), port: node.proxy.getProxyPort(), @@ -225,6 +234,8 @@ describe('gestaltsGestaltTrustByNode', () => { authenticate, gestaltGraph, discovery, + logger, + db, }), }; grpcServer = new GRPCServer({ logger }); @@ -245,6 +256,8 @@ describe('gestaltsGestaltTrustByNode', () => { await grpcServer.stop(); await discovery.stop(); await nodeConnectionManager.stop(); + await nodeManager.stop(); + await queue.stop(); await nodeGraph.stop(); await proxy.stop(); await sigchain.stop(); diff --git a/tests/client/service/identitiesAuthenticate.test.ts b/tests/client/service/identitiesAuthenticate.test.ts index fb3c2a9ff..21b4f78dc 100644 --- a/tests/client/service/identitiesAuthenticate.test.ts +++ b/tests/client/service/identitiesAuthenticate.test.ts @@ -16,6 +16,7 @@ import * as validationErrors from '@/validation/errors'; import * as clientUtils from '@/client/utils/utils'; import * as nodesUtils from '@/nodes/utils'; import TestProvider from '../../identities/TestProvider'; +import { expectRemoteError } from '../../utils'; describe('identitiesAuthenticate', () => { const logger = new Logger('identitiesAuthenticate test', LogLevel.WARN, [ @@ -56,6 +57,7 @@ describe('identitiesAuthenticate', () => { identitiesAuthenticate: identitiesAuthenticate({ authenticate, identitiesManager, + logger, }), }; grpcServer = new GRPCServer({ logger }); @@ -124,13 +126,14 @@ describe('identitiesAuthenticate', () => { test('cannot authenticate invalid provider', async () => { const request = new identitiesPB.Provider(); request.setProviderId(''); - await expect( + await expectRemoteError( grpcClient .identitiesAuthenticate( request, clientUtils.encodeAuthFromPassword(password), ) .next(), - ).rejects.toThrow(validationErrors.ErrorValidation); + validationErrors.ErrorValidation, + ); }); }); diff --git a/tests/client/service/identitiesAuthenticatedGet.test.ts b/tests/client/service/identitiesAuthenticatedGet.test.ts index 8233589d5..1dacdddbc 100644 --- a/tests/client/service/identitiesAuthenticatedGet.test.ts +++ b/tests/client/service/identitiesAuthenticatedGet.test.ts @@ -48,6 +48,7 @@ describe('identitiesAuthenticatedGet', () => { identitiesAuthenticatedGet: identitiesAuthenticatedGet({ authenticate, identitiesManager, + logger, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/identitiesClaim.test.ts b/tests/client/service/identitiesClaim.test.ts index b040a7b0a..3a17b79a8 100644 --- a/tests/client/service/identitiesClaim.test.ts +++ b/tests/client/service/identitiesClaim.test.ts @@ -2,12 +2,14 @@ import type { ClaimLinkIdentity } from '@/claims/types'; import type { NodeIdEncoded } from '@/nodes/types'; import type { IdentityId, ProviderId } from '@/identities/types'; import type { Host, Port } from '@/network/types'; +import type NodeManager from '@/nodes/NodeManager'; import fs from 'fs'; import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { Metadata } from '@grpc/grpc-js'; +import Queue from '@/nodes/Queue'; import KeyManager from '@/keys/KeyManager'; import IdentitiesManager from '@/identities/IdentitiesManager'; import NodeConnectionManager from '@/nodes/NodeConnectionManager'; @@ -27,6 +29,7 @@ import * as nodesUtils from '@/nodes/utils'; import * as validationErrors from '@/validation/errors'; import * as testUtils from '../../utils'; import TestProvider from '../../identities/TestProvider'; +import { expectRemoteError } from '../../utils'; describe('identitiesClaim', () => { const logger = new Logger('identitiesClaim test', LogLevel.WARN, [ @@ -54,6 +57,7 @@ describe('identitiesClaim', () => { let mockedGenerateKeyPair: jest.SpyInstance; let mockedGenerateDeterministicKeyPair: jest.SpyInstance; let mockedAddClaim: jest.SpyInstance; + const dummyNodeManager = { setNode: jest.fn() } as unknown as NodeManager; beforeAll(async () => { const globalKeyPair = await testUtils.setupGlobalKeypair(); const claim = await claimsUtils.createClaim({ @@ -83,6 +87,7 @@ describe('identitiesClaim', () => { let testProvider: TestProvider; let identitiesManager: IdentitiesManager; let nodeGraph: NodeGraph; + let queue: Queue; let nodeConnectionManager: NodeConnectionManager; let sigchain: Sigchain; let proxy: Proxy; @@ -134,20 +139,27 @@ describe('identitiesClaim', () => { keyManager, logger: logger.getChild('NodeGraph'), }); + queue = new Queue({ + logger: logger.getChild('queue'), + }); nodeConnectionManager = new NodeConnectionManager({ connConnectTime: 2000, proxy, keyManager, nodeGraph, - logger: logger.getChild('nodeConnectionManager'), + queue, + logger: logger.getChild('NodeConnectionManager'), }); - await nodeConnectionManager.start(); + await queue.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); const clientService = { identitiesClaim: identitiesClaim({ authenticate, identitiesManager, sigchain, keyManager, + logger, + db, }), }; grpcServer = new GRPCServer({ logger }); @@ -167,6 +179,7 @@ describe('identitiesClaim', () => { await grpcClient.destroy(); await grpcServer.stop(); await nodeConnectionManager.stop(); + await queue.stop(); await nodeGraph.stop(); await sigchain.stop(); await proxy.stop(); @@ -207,27 +220,30 @@ describe('identitiesClaim', () => { const request = new identitiesPB.Provider(); request.setIdentityId(''); request.setProviderId(testToken.providerId); - await expect( + await expectRemoteError( grpcClient.identitiesClaim( request, clientUtils.encodeAuthFromPassword(password), ), - ).rejects.toThrow(validationErrors.ErrorValidation); + validationErrors.ErrorValidation, + ); request.setIdentityId(testToken.identityId); request.setProviderId(''); - await expect( + await expectRemoteError( grpcClient.identitiesClaim( request, clientUtils.encodeAuthFromPassword(password), ), - ).rejects.toThrow(validationErrors.ErrorValidation); + validationErrors.ErrorValidation, + ); request.setIdentityId(''); request.setProviderId(''); - await expect( + await expectRemoteError( grpcClient.identitiesClaim( request, clientUtils.encodeAuthFromPassword(password), ), - ).rejects.toThrow(validationErrors.ErrorValidation); + validationErrors.ErrorValidation, + ); }); }); diff --git a/tests/client/service/identitiesInfoConnectedGet.test.ts b/tests/client/service/identitiesInfoConnectedGet.test.ts index 0dce4a1bb..532690fe4 100644 --- a/tests/client/service/identitiesInfoConnectedGet.test.ts +++ b/tests/client/service/identitiesInfoConnectedGet.test.ts @@ -16,6 +16,7 @@ import * as clientUtils from '@/client/utils/utils'; import * as nodesUtils from '@/nodes/utils'; import * as identitiesErrors from '@/identities/errors'; import TestProvider from '../../identities/TestProvider'; +import { expectRemoteError } from '../../utils'; describe('identitiesInfoConnectedGet', () => { const logger = new Logger('identitiesInfoConnectedGet test', LogLevel.WARN, [ @@ -52,6 +53,7 @@ describe('identitiesInfoConnectedGet', () => { identitiesInfoConnectedGet: identitiesInfoConnectedGet({ authenticate, identitiesManager, + logger, }), }; grpcServer = new GRPCServer({ logger }); @@ -728,13 +730,14 @@ describe('identitiesInfoConnectedGet', () => { // This feature is not implemented yet - should throw error const request = new identitiesPB.ProviderSearch(); request.setDisconnected(true); - await expect( + await expectRemoteError( grpcClient .identitiesInfoConnectedGet( request, clientUtils.encodeAuthFromPassword(password), ) .next(), - ).rejects.toThrow(identitiesErrors.ErrorProviderUnimplemented); + identitiesErrors.ErrorProviderUnimplemented, + ); }); }); diff --git a/tests/client/service/identitiesInfoGet.test.ts b/tests/client/service/identitiesInfoGet.test.ts index 41bc2a1e0..68b9df655 100644 --- a/tests/client/service/identitiesInfoGet.test.ts +++ b/tests/client/service/identitiesInfoGet.test.ts @@ -51,6 +51,7 @@ describe('identitiesInfoGet', () => { identitiesInfoGet: identitiesInfoGet({ authenticate, identitiesManager, + logger, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/identitiesProvidersList.test.ts b/tests/client/service/identitiesProvidersList.test.ts index 071703386..e75ffd477 100644 --- a/tests/client/service/identitiesProvidersList.test.ts +++ b/tests/client/service/identitiesProvidersList.test.ts @@ -60,6 +60,7 @@ describe('identitiesProvidersList', () => { identitiesProvidersList: identitiesProvidersList({ authenticate, identitiesManager, + logger, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/identitiesTokenPutDeleteGet.test.ts b/tests/client/service/identitiesTokenPutDeleteGet.test.ts index 6fca93e68..1752e2f94 100644 --- a/tests/client/service/identitiesTokenPutDeleteGet.test.ts +++ b/tests/client/service/identitiesTokenPutDeleteGet.test.ts @@ -56,14 +56,20 @@ describe('identitiesTokenPutDeleteGet', () => { identitiesTokenPut: identitiesTokenPut({ authenticate, identitiesManager, + logger, + db, }), identitiesTokenGet: identitiesTokenGet({ authenticate, identitiesManager, + logger, + db, }), identitiesTokenDelete: identitiesTokenDelete({ authenticate, identitiesManager, + logger, + db, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/keysCertsChainGet.test.ts b/tests/client/service/keysCertsChainGet.test.ts index f0b97a681..48b734c95 100644 --- a/tests/client/service/keysCertsChainGet.test.ts +++ b/tests/client/service/keysCertsChainGet.test.ts @@ -61,6 +61,7 @@ describe('keysCertsChainGet', () => { keysCertsChainGet: keysCertsChainGet({ authenticate, keyManager, + logger, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/keysCertsGet.test.ts b/tests/client/service/keysCertsGet.test.ts index ad2fb28ba..d3bd83e09 100644 --- a/tests/client/service/keysCertsGet.test.ts +++ b/tests/client/service/keysCertsGet.test.ts @@ -60,6 +60,7 @@ describe('keysCertsGet', () => { keysCertsGet: keysCertsGet({ authenticate, keyManager, + logger, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/keysEncryptDecrypt.test.ts b/tests/client/service/keysEncryptDecrypt.test.ts index 0c2a1259a..a6421649f 100644 --- a/tests/client/service/keysEncryptDecrypt.test.ts +++ b/tests/client/service/keysEncryptDecrypt.test.ts @@ -55,10 +55,12 @@ describe('keysEncryptDecrypt', () => { keysEncrypt: keysEncrypt({ authenticate, keyManager, + logger, }), keysDecrypt: keysDecrypt({ authenticate, keyManager, + logger, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/keysKeyPairRenew.test.ts b/tests/client/service/keysKeyPairRenew.test.ts index 714055cf0..47445ead0 100644 --- a/tests/client/service/keysKeyPairRenew.test.ts +++ b/tests/client/service/keysKeyPairRenew.test.ts @@ -7,7 +7,6 @@ import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; -import NodeGraph from '@/nodes/NodeGraph'; import PolykeyAgent from '@/PolykeyAgent'; import GRPCServer from '@/grpc/GRPCServer'; import GRPCClientClient from '@/client/GRPCClientClient'; @@ -17,6 +16,7 @@ import * as keysPB from '@/proto/js/polykey/v1/keys/keys_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as clientUtils from '@/client/utils/utils'; import * as keysUtils from '@/keys/utils'; +import { NodeManager } from '@/nodes'; import * as testUtils from '../../utils'; describe('keysKeyPairRenew', () => { @@ -32,7 +32,7 @@ describe('keysKeyPairRenew', () => { beforeAll(async () => { const globalKeyPair = await testUtils.setupGlobalKeypair(); const newKeyPair = await keysUtils.generateKeyPair(1024); - mockedRefreshBuckets = jest.spyOn(NodeGraph.prototype, 'refreshBuckets'); + mockedRefreshBuckets = jest.spyOn(NodeManager.prototype, 'resetBuckets'); mockedGenerateKeyPair = jest .spyOn(keysUtils, 'generateKeyPair') .mockResolvedValueOnce(globalKeyPair) @@ -74,6 +74,7 @@ describe('keysKeyPairRenew', () => { keysKeyPairRenew: keysKeyPairRenew({ authenticate, keyManager, + logger, }), }; grpcServer = new GRPCServer({ logger }); @@ -110,7 +111,7 @@ describe('keysKeyPairRenew', () => { certChainPem: await keyManager.getRootCertChainPem(), }; const nodeIdStatus1 = (await status.readStatus())!.data.nodeId; - expect(mockedRefreshBuckets.mock.calls).toHaveLength(0); + expect(mockedRefreshBuckets).toHaveBeenCalledTimes(0); expect(fwdTLSConfig1).toEqual(expectedTLSConfig1); expect(serverTLSConfig1).toEqual(expectedTLSConfig1); expect(nodeId1.equals(nodeIdStatus1)).toBe(true); @@ -133,7 +134,7 @@ describe('keysKeyPairRenew', () => { certChainPem: await keyManager.getRootCertChainPem(), }; const nodeIdStatus2 = (await status.readStatus())!.data.nodeId; - expect(mockedRefreshBuckets.mock.calls).toHaveLength(1); + expect(mockedRefreshBuckets).toHaveBeenCalled(); expect(fwdTLSConfig2).toEqual(expectedTLSConfig2); expect(serverTLSConfig2).toEqual(expectedTLSConfig2); expect(rootKeyPair2.privateKey).not.toBe(rootKeyPair1.privateKey); diff --git a/tests/client/service/keysKeyPairReset.test.ts b/tests/client/service/keysKeyPairReset.test.ts index 155d6071e..55af8f35c 100644 --- a/tests/client/service/keysKeyPairReset.test.ts +++ b/tests/client/service/keysKeyPairReset.test.ts @@ -7,7 +7,6 @@ import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; -import NodeGraph from '@/nodes/NodeGraph'; import PolykeyAgent from '@/PolykeyAgent'; import GRPCServer from '@/grpc/GRPCServer'; import GRPCClientClient from '@/client/GRPCClientClient'; @@ -17,6 +16,7 @@ import * as keysPB from '@/proto/js/polykey/v1/keys/keys_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as clientUtils from '@/client/utils/utils'; import * as keysUtils from '@/keys/utils'; +import { NodeManager } from '@/nodes'; import * as testUtils from '../../utils'; describe('keysKeyPairReset', () => { @@ -32,7 +32,7 @@ describe('keysKeyPairReset', () => { beforeAll(async () => { const globalKeyPair = await testUtils.setupGlobalKeypair(); const newKeyPair = await keysUtils.generateKeyPair(1024); - mockedRefreshBuckets = jest.spyOn(NodeGraph.prototype, 'refreshBuckets'); + mockedRefreshBuckets = jest.spyOn(NodeManager.prototype, 'resetBuckets'); mockedGenerateKeyPair = jest .spyOn(keysUtils, 'generateKeyPair') .mockResolvedValueOnce(globalKeyPair) @@ -74,6 +74,7 @@ describe('keysKeyPairReset', () => { keysKeyPairReset: keysKeyPairReset({ authenticate, keyManager, + logger, }), }; grpcServer = new GRPCServer({ logger }); @@ -110,7 +111,7 @@ describe('keysKeyPairReset', () => { certChainPem: await keyManager.getRootCertChainPem(), }; const nodeIdStatus1 = (await status.readStatus())!.data.nodeId; - expect(mockedRefreshBuckets.mock.calls).toHaveLength(0); + expect(mockedRefreshBuckets).not.toHaveBeenCalled(); expect(fwdTLSConfig1).toEqual(expectedTLSConfig1); expect(serverTLSConfig1).toEqual(expectedTLSConfig1); expect(nodeId1.equals(nodeIdStatus1)).toBe(true); @@ -133,7 +134,7 @@ describe('keysKeyPairReset', () => { certChainPem: await keyManager.getRootCertChainPem(), }; const nodeIdStatus2 = (await status.readStatus())!.data.nodeId; - expect(mockedRefreshBuckets.mock.calls).toHaveLength(1); + expect(mockedRefreshBuckets).toHaveBeenCalled(); expect(fwdTLSConfig2).toEqual(expectedTLSConfig2); expect(serverTLSConfig2).toEqual(expectedTLSConfig2); expect(rootKeyPair2.privateKey).not.toBe(rootKeyPair1.privateKey); diff --git a/tests/client/service/keysKeyPairRoot.test.ts b/tests/client/service/keysKeyPairRoot.test.ts index 19330b0cc..e5d5f2629 100644 --- a/tests/client/service/keysKeyPairRoot.test.ts +++ b/tests/client/service/keysKeyPairRoot.test.ts @@ -56,6 +56,7 @@ describe('keysKeyPairRoot', () => { keysKeyPairRoot: keysKeyPairRoot({ authenticate, keyManager, + logger, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/keysPasswordChange.test.ts b/tests/client/service/keysPasswordChange.test.ts index 8f22e95e2..7814ec86a 100644 --- a/tests/client/service/keysPasswordChange.test.ts +++ b/tests/client/service/keysPasswordChange.test.ts @@ -62,6 +62,7 @@ describe('keysPasswordChange', () => { keysPasswordChange: keysPasswordChange({ authenticate, keyManager, + logger, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/keysSignVerify.test.ts b/tests/client/service/keysSignVerify.test.ts index 34cf424fd..c420d7ed6 100644 --- a/tests/client/service/keysSignVerify.test.ts +++ b/tests/client/service/keysSignVerify.test.ts @@ -56,10 +56,12 @@ describe('keysSignVerify', () => { keysSign: keysSign({ authenticate, keyManager, + logger, }), keysVerify: keysVerify({ authenticate, keyManager, + logger, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/nodesAdd.test.ts b/tests/client/service/nodesAdd.test.ts index 1cd51eb05..f00e62566 100644 --- a/tests/client/service/nodesAdd.test.ts +++ b/tests/client/service/nodesAdd.test.ts @@ -5,6 +5,7 @@ import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { Metadata } from '@grpc/grpc-js'; +import Queue from '@/nodes/Queue'; import KeyManager from '@/keys/KeyManager'; import NodeConnectionManager from '@/nodes/NodeConnectionManager'; import NodeGraph from '@/nodes/NodeGraph'; @@ -23,6 +24,7 @@ import * as clientUtils from '@/client/utils/utils'; import * as keysUtils from '@/keys/utils'; import * as validationErrors from '@/validation/errors'; import * as testUtils from '../../utils'; +import { expectRemoteError } from '../../utils'; describe('nodesAdd', () => { const logger = new Logger('nodesAdd test', LogLevel.WARN, [ @@ -49,6 +51,7 @@ describe('nodesAdd', () => { const authToken = 'abc123'; let dataDir: string; let nodeGraph: NodeGraph; + let queue: Queue; let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; let sigchain: Sigchain; @@ -95,27 +98,36 @@ describe('nodesAdd', () => { keyManager, logger: logger.getChild('NodeGraph'), }); + queue = new Queue({ + logger: logger.getChild('queue'), + }); nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, connConnectTime: 2000, connTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); - await nodeConnectionManager.start(); nodeManager = new NodeManager({ db, keyManager, nodeConnectionManager, nodeGraph, sigchain, + queue, logger, }); + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); const clientService = { nodesAdd: nodesAdd({ authenticate, nodeManager, + logger, + db, }), }; grpcServer = new GRPCServer({ logger }); @@ -136,6 +148,8 @@ describe('nodesAdd', () => { await grpcServer.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); + await nodeManager.stop(); + await queue.stop(); await sigchain.stop(); await proxy.stop(); await db.stop(); @@ -149,13 +163,15 @@ describe('nodesAdd', () => { const addressMessage = new nodesPB.Address(); addressMessage.setHost('127.0.0.1'); addressMessage.setPort(11111); - const request = new nodesPB.NodeAddress(); + const request = new nodesPB.NodeAdd(); request.setNodeId('vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0'); request.setAddress(addressMessage); const response = await grpcClient.nodesAdd( request, clientUtils.encodeAuthFromPassword(password), ); + request.setPing(false); + request.setForce(false); expect(response).toBeInstanceOf(utilsPB.EmptyMessage); const result = await nodeGraph.getNode( nodesUtils.decodeNodeId( @@ -163,40 +179,44 @@ describe('nodesAdd', () => { )!, ); expect(result).toBeDefined(); - expect(result!.host).toBe('127.0.0.1'); - expect(result!.port).toBe(11111); + expect(result!.address).toEqual({ host: '127.0.0.1', port: 11111 }); }); test('cannot add invalid node', async () => { // Invalid host const addressMessage = new nodesPB.Address(); addressMessage.setHost(''); addressMessage.setPort(11111); - const request = new nodesPB.NodeAddress(); + const request = new nodesPB.NodeAdd(); + request.setPing(false); + request.setForce(false); request.setNodeId('vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0'); request.setAddress(addressMessage); - await expect( + await expectRemoteError( grpcClient.nodesAdd( request, clientUtils.encodeAuthFromPassword(password), ), - ).rejects.toThrow(validationErrors.ErrorValidation); + validationErrors.ErrorValidation, + ); // Invalid port addressMessage.setHost('127.0.0.1'); addressMessage.setPort(111111); - await expect( + await expectRemoteError( grpcClient.nodesAdd( request, clientUtils.encodeAuthFromPassword(password), ), - ).rejects.toThrow(validationErrors.ErrorValidation); + validationErrors.ErrorValidation, + ); // Invalid nodeid addressMessage.setPort(11111); request.setNodeId('nodeId'); - await expect( + await expectRemoteError( grpcClient.nodesAdd( request, clientUtils.encodeAuthFromPassword(password), ), - ).rejects.toThrow(validationErrors.ErrorValidation); + validationErrors.ErrorValidation, + ); }); }); diff --git a/tests/client/service/nodesClaim.test.ts b/tests/client/service/nodesClaim.test.ts index 07d41e500..95eaf8b6e 100644 --- a/tests/client/service/nodesClaim.test.ts +++ b/tests/client/service/nodesClaim.test.ts @@ -7,6 +7,7 @@ import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { Metadata } from '@grpc/grpc-js'; +import Queue from '@/nodes/Queue'; import KeyManager from '@/keys/KeyManager'; import NotificationsManager from '@/notifications/NotificationsManager'; import ACL from '@/acl/ACL'; @@ -15,7 +16,6 @@ import NodeGraph from '@/nodes/NodeGraph'; import NodeManager from '@/nodes/NodeManager'; import Sigchain from '@/sigchain/Sigchain'; import Proxy from '@/network/Proxy'; - import GRPCServer from '@/grpc/GRPCServer'; import GRPCClientClient from '@/client/GRPCClientClient'; import nodesClaim from '@/client/service/nodesClaim'; @@ -62,7 +62,7 @@ describe('nodesClaim', () => { mockedSendNotification = jest .spyOn(NotificationsManager.prototype, 'sendNotification') .mockResolvedValue(undefined); - mockedSendNotification = jest + mockedClaimNode = jest .spyOn(NodeManager.prototype, 'claimNode') .mockResolvedValue(undefined); }); @@ -76,13 +76,13 @@ describe('nodesClaim', () => { const authToken = 'abc123'; let dataDir: string; let nodeGraph: NodeGraph; + let queue: Queue; let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; let notificationsManager: NotificationsManager; let acl: ACL; let sigchain: Sigchain; let proxy: Proxy; - let db: DB; let keyManager: KeyManager; let grpcServer: GRPCServer; @@ -128,23 +128,30 @@ describe('nodesClaim', () => { keyManager, logger: logger.getChild('NodeGraph'), }); + queue = new Queue({ + logger: logger.getChild('queue'), + }); nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, connConnectTime: 2000, connTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); - await nodeConnectionManager.start(); nodeManager = new NodeManager({ db, keyManager, - sigchain, - nodeGraph, nodeConnectionManager, + nodeGraph, + sigchain, + queue, logger, }); + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); notificationsManager = await NotificationsManager.createNotificationsManager({ acl, @@ -159,6 +166,8 @@ describe('nodesClaim', () => { authenticate, nodeManager, notificationsManager, + logger, + db, }), }; grpcServer = new GRPCServer({ logger }); @@ -178,6 +187,8 @@ describe('nodesClaim', () => { await grpcClient.destroy(); await grpcServer.stop(); await nodeConnectionManager.stop(); + await nodeManager.stop(); + await queue.stop(); await nodeGraph.stop(); await notificationsManager.stop(); await sigchain.stop(); @@ -228,11 +239,12 @@ describe('nodesClaim', () => { test('cannot claim an invalid node', async () => { const request = new nodesPB.Claim(); request.setNodeId('nodeId'); - await expect( + await testUtils.expectRemoteError( grpcClient.nodesClaim( request, clientUtils.encodeAuthFromPassword(password), ), - ).rejects.toThrow(validationErrors.ErrorValidation); + validationErrors.ErrorValidation, + ); }); }); diff --git a/tests/client/service/nodesFind.test.ts b/tests/client/service/nodesFind.test.ts index 1197638f5..4ff59d9f1 100644 --- a/tests/client/service/nodesFind.test.ts +++ b/tests/client/service/nodesFind.test.ts @@ -1,10 +1,12 @@ import type { Host, Port } from '@/network/types'; +import type NodeManager from '@/nodes/NodeManager'; import fs from 'fs'; import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { Metadata } from '@grpc/grpc-js'; +import Queue from '@/nodes/Queue'; import KeyManager from '@/keys/KeyManager'; import NodeConnectionManager from '@/nodes/NodeConnectionManager'; import NodeGraph from '@/nodes/NodeGraph'; @@ -20,6 +22,7 @@ import * as clientUtils from '@/client/utils/utils'; import * as keysUtils from '@/keys/utils'; import * as validationErrors from '@/validation/errors'; import * as testUtils from '../../utils'; +import { expectRemoteError } from '../../utils'; describe('nodesFind', () => { const logger = new Logger('nodesFind test', LogLevel.WARN, [ @@ -54,6 +57,7 @@ describe('nodesFind', () => { const authToken = 'abc123'; let dataDir: string; let nodeGraph: NodeGraph; + let queue: Queue; let nodeConnectionManager: NodeConnectionManager; let sigchain: Sigchain; let proxy: Proxy; @@ -99,19 +103,25 @@ describe('nodesFind', () => { keyManager, logger: logger.getChild('NodeGraph'), }); + queue = new Queue({ + logger: logger.getChild('queue'), + }); nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, connConnectTime: 2000, connTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); - await nodeConnectionManager.start(); + await queue.start(); + await nodeConnectionManager.start({ nodeManager: {} as NodeManager }); const clientService = { nodesFind: nodesFind({ - nodeConnectionManager, authenticate, + nodeConnectionManager, + logger, }), }; grpcServer = new GRPCServer({ logger }); @@ -133,6 +143,7 @@ describe('nodesFind', () => { await sigchain.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); + await queue.stop(); await proxy.stop(); await db.stop(); await keyManager.stop(); @@ -158,11 +169,12 @@ describe('nodesFind', () => { test('cannot find an invalid node', async () => { const request = new nodesPB.Node(); request.setNodeId('nodeId'); - await expect( + await expectRemoteError( grpcClient.nodesFind( request, clientUtils.encodeAuthFromPassword(password), ), - ).rejects.toThrow(validationErrors.ErrorValidation); + validationErrors.ErrorValidation, + ); }); }); diff --git a/tests/client/service/nodesPing.test.ts b/tests/client/service/nodesPing.test.ts index 0bfcabc97..14f9cbcee 100644 --- a/tests/client/service/nodesPing.test.ts +++ b/tests/client/service/nodesPing.test.ts @@ -5,6 +5,7 @@ import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { Metadata } from '@grpc/grpc-js'; +import Queue from '@/nodes/Queue'; import KeyManager from '@/keys/KeyManager'; import NodeConnectionManager from '@/nodes/NodeConnectionManager'; import NodeGraph from '@/nodes/NodeGraph'; @@ -22,6 +23,7 @@ import * as clientUtils from '@/client/utils/utils'; import * as keysUtils from '@/keys/utils'; import * as validationErrors from '@/validation/errors'; import * as testUtils from '../../utils'; +import { expectRemoteError } from '../../utils'; describe('nodesPing', () => { const logger = new Logger('nodesPing test', LogLevel.WARN, [ @@ -54,6 +56,7 @@ describe('nodesPing', () => { const authToken = 'abc123'; let dataDir: string; let nodeGraph: NodeGraph; + let queue: Queue; let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; let sigchain: Sigchain; @@ -100,27 +103,34 @@ describe('nodesPing', () => { keyManager, logger: logger.getChild('NodeGraph'), }); + queue = new Queue({ + logger: logger.getChild('queue'), + }); nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, connConnectTime: 2000, connTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); - await nodeConnectionManager.start(); nodeManager = new NodeManager({ db, keyManager, nodeConnectionManager, nodeGraph, sigchain, + queue, logger, }); + await queue.start(); + await nodeConnectionManager.start({ nodeManager }); const clientService = { nodesPing: nodesPing({ authenticate, nodeManager, + logger, }), }; grpcServer = new GRPCServer({ logger }); @@ -142,6 +152,7 @@ describe('nodesPing', () => { await sigchain.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); + await queue.stop(); await proxy.stop(); await db.stop(); await keyManager.stop(); @@ -173,11 +184,12 @@ describe('nodesPing', () => { test('cannot ping an invalid node', async () => { const request = new nodesPB.Node(); request.setNodeId('nodeId'); - await expect( + await expectRemoteError( grpcClient.nodesPing( request, clientUtils.encodeAuthFromPassword(password), ), - ).rejects.toThrow(validationErrors.ErrorValidation); + validationErrors.ErrorValidation, + ); }); }); diff --git a/tests/client/service/notificationsClear.test.ts b/tests/client/service/notificationsClear.test.ts index 2fab2e233..4a9002f21 100644 --- a/tests/client/service/notificationsClear.test.ts +++ b/tests/client/service/notificationsClear.test.ts @@ -5,6 +5,7 @@ import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; import { DB } from '@matrixai/db'; +import Queue from '@/nodes/Queue'; import KeyManager from '@/keys/KeyManager'; import GRPCServer from '@/grpc/GRPCServer'; import NodeConnectionManager from '@/nodes/NodeConnectionManager'; @@ -53,6 +54,7 @@ describe('notificationsClear', () => { const authToken = 'abc123'; let dataDir: string; let nodeGraph: NodeGraph; + let queue: Queue; let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; let notificationsManager: NotificationsManager; @@ -105,23 +107,30 @@ describe('notificationsClear', () => { keyManager, logger: logger.getChild('NodeGraph'), }); + queue = new Queue({ + logger: logger.getChild('queue'), + }); nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, connConnectTime: 2000, connTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); - await nodeConnectionManager.start(); nodeManager = new NodeManager({ db, keyManager, nodeConnectionManager, nodeGraph, sigchain, + queue, logger, }); + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); notificationsManager = await NotificationsManager.createNotificationsManager({ acl, @@ -135,6 +144,8 @@ describe('notificationsClear', () => { notificationsClear: notificationsClear({ authenticate, notificationsManager, + logger, + db, }), }; grpcServer = new GRPCServer({ logger }); @@ -156,6 +167,8 @@ describe('notificationsClear', () => { await notificationsManager.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); + await nodeManager.stop(); + await queue.stop(); await sigchain.stop(); await proxy.stop(); await acl.stop(); diff --git a/tests/client/service/notificationsRead.test.ts b/tests/client/service/notificationsRead.test.ts index 1b77af1a3..b5a3de17a 100644 --- a/tests/client/service/notificationsRead.test.ts +++ b/tests/client/service/notificationsRead.test.ts @@ -6,6 +6,7 @@ import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; import { DB } from '@matrixai/db'; +import Queue from '@/nodes/Queue'; import KeyManager from '@/keys/KeyManager'; import GRPCServer from '@/grpc/GRPCServer'; import NodeConnectionManager from '@/nodes/NodeConnectionManager'; @@ -24,12 +25,13 @@ import * as keysUtils from '@/keys/utils'; import * as nodesUtils from '@/nodes/utils'; import * as clientUtils from '@/client/utils'; import * as testUtils from '../../utils'; +import * as testNodesUtils from '../../nodes/utils'; describe('notificationsRead', () => { const logger = new Logger('notificationsRead test', LogLevel.WARN, [ new StreamHandler(), ]); - const nodeIdSender = testUtils.generateRandomNodeId(); + const nodeIdSender = testNodesUtils.generateRandomNodeId(); const nodeIdSenderEncoded = nodesUtils.encodeNodeId(nodeIdSender); const password = 'helloworld'; const authenticate = async (metaClient, metaServer = new Metadata()) => @@ -127,6 +129,7 @@ describe('notificationsRead', () => { const authToken = 'abc123'; let dataDir: string; let nodeGraph: NodeGraph; + let queue: Queue; let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; let notificationsManager: NotificationsManager; @@ -179,23 +182,30 @@ describe('notificationsRead', () => { keyManager, logger: logger.getChild('NodeGraph'), }); + queue = new Queue({ + logger: logger.getChild('queue'), + }); nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, connConnectTime: 2000, connTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); - await nodeConnectionManager.start(); nodeManager = new NodeManager({ db, keyManager, - nodeGraph, nodeConnectionManager, + nodeGraph, sigchain, + queue, logger, }); + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); notificationsManager = await NotificationsManager.createNotificationsManager({ acl, @@ -209,6 +219,8 @@ describe('notificationsRead', () => { notificationsRead: notificationsRead({ authenticate, notificationsManager, + logger, + db, }), }; grpcServer = new GRPCServer({ logger }); @@ -231,6 +243,8 @@ describe('notificationsRead', () => { await sigchain.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); + await nodeManager.stop(); + await queue.stop(); await proxy.stop(); await acl.stop(); await db.stop(); diff --git a/tests/client/service/notificationsSend.test.ts b/tests/client/service/notificationsSend.test.ts index 01764d368..35a6a15bb 100644 --- a/tests/client/service/notificationsSend.test.ts +++ b/tests/client/service/notificationsSend.test.ts @@ -6,6 +6,7 @@ import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; import { DB } from '@matrixai/db'; +import Queue from '@/nodes/Queue'; import KeyManager from '@/keys/KeyManager'; import GRPCServer from '@/grpc/GRPCServer'; import NodeConnectionManager from '@/nodes/NodeConnectionManager'; @@ -63,6 +64,7 @@ describe('notificationsSend', () => { const authToken = 'abc123'; let dataDir: string; let nodeGraph: NodeGraph; + let queue: Queue; let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; let notificationsManager: NotificationsManager; @@ -114,23 +116,30 @@ describe('notificationsSend', () => { keyManager, logger: logger.getChild('NodeGraph'), }); + queue = new Queue({ + logger: logger.getChild('queue'), + }); nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, connConnectTime: 2000, connTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); - await nodeConnectionManager.start(); nodeManager = new NodeManager({ db, keyManager, - nodeGraph, nodeConnectionManager, + nodeGraph, sigchain, + queue, logger, }); + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); notificationsManager = await NotificationsManager.createNotificationsManager({ acl, @@ -144,6 +153,7 @@ describe('notificationsSend', () => { notificationsSend: notificationsSend({ authenticate, notificationsManager, + logger, }), }; grpcServer = new GRPCServer({ logger }); @@ -165,6 +175,8 @@ describe('notificationsSend', () => { await notificationsManager.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); + await nodeManager.stop(); + await queue.stop(); await sigchain.stop(); await proxy.stop(); await acl.stop(); diff --git a/tests/client/service/vaultsClone.test.ts b/tests/client/service/vaultsClone.test.ts new file mode 100644 index 000000000..b54f629db --- /dev/null +++ b/tests/client/service/vaultsClone.test.ts @@ -0,0 +1,99 @@ +import type { Host, Port } from '@/network/types'; +import type KeyManager from '@/keys/KeyManager'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type ACL from '@/acl/ACL'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import { Metadata } from '@grpc/grpc-js'; +import VaultManager from '@/vaults/VaultManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import vaultsClone from '@/client/service/vaultsClone'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; +import * as keysUtils from '@/keys/utils'; +import * as testUtils from '../../utils'; + +describe('vaultsClone', () => { + const logger = new Logger('vaultsClone test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + }); + afterAll(async () => { + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + let dataDir: string; + let db: DB; + let vaultManager: VaultManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyManager: {} as KeyManager, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + const clientService = { + vaultsClone: vaultsClone({ + authenticate, + vaultManager, + db, + logger, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: testUtils.generateRandomNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await vaultManager.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test.todo('clones a vault'); +}); diff --git a/tests/client/service/vaultsCreateDeleteList.test.ts b/tests/client/service/vaultsCreateDeleteList.test.ts new file mode 100644 index 000000000..c04644056 --- /dev/null +++ b/tests/client/service/vaultsCreateDeleteList.test.ts @@ -0,0 +1,168 @@ +import type { Host, Port } from '@/network/types'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type ACL from '@/acl/ACL'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import { Metadata } from '@grpc/grpc-js'; +import KeyManager from '@/keys/KeyManager'; +import VaultManager from '@/vaults/VaultManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import vaultsCreate from '@/client/service/vaultsCreate'; +import vaultsDelete from '@/client/service/vaultsDelete'; +import vaultsList from '@/client/service/vaultsList'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; +import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; +import * as clientUtils from '@/client/utils/utils'; +import * as keysUtils from '@/keys/utils'; +import * as testUtils from '../../utils'; + +describe('vaultsCreateDeleteList', () => { + const logger = new Logger('vaultsCreateDeleteList test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + }); + afterAll(async () => { + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + let dataDir: string; + let keyManager: KeyManager; + let db: DB; + let vaultManager: VaultManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyManager = await KeyManager.createKeyManager({ + password, + keysPath, + logger, + }); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyManager, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + const clientService = { + vaultsCreate: vaultsCreate({ + authenticate, + vaultManager, + db, + logger, + }), + vaultsDelete: vaultsDelete({ + authenticate, + vaultManager, + db, + logger, + }), + vaultsList: vaultsList({ + authenticate, + vaultManager, + db, + logger, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: testUtils.generateRandomNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await vaultManager.stop(); + await db.stop(); + await keyManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('creates, lists, and deletes vaults', async () => { + // Create vault + const createRequest = new vaultsPB.Vault(); + createRequest.setNameOrId('test-vault'); + const createResponse = await grpcClient.vaultsCreate( + createRequest, + clientUtils.encodeAuthFromPassword(password), + ); + expect(createResponse).toBeInstanceOf(vaultsPB.Vault); + const vaultId = createResponse.getNameOrId(); + // List vault + const emptyMessage = new utilsPB.EmptyMessage(); + const listResponse1 = grpcClient.vaultsList( + emptyMessage, + clientUtils.encodeAuthFromPassword(password), + ); + const vaults1: Array<{ name: string; id: string }> = []; + for await (const vault of listResponse1) { + expect(vault).toBeInstanceOf(vaultsPB.List); + vaults1.push({ name: vault.getVaultName(), id: vault.getVaultId() }); + } + expect(vaults1).toHaveLength(1); + expect(vaults1[0].name).toBe('test-vault'); + expect(vaults1[0].id).toBe(vaultId); + // Delete vault + const deleteRequest = createRequest; + const deleteResponse = await grpcClient.vaultsDelete( + deleteRequest, + clientUtils.encodeAuthFromPassword(password), + ); + expect(deleteResponse).toBeInstanceOf(utilsPB.StatusMessage); + expect(deleteResponse.getSuccess()).toBeTruthy(); + // Check vault was deleted + const listResponse2 = grpcClient.vaultsList( + emptyMessage, + clientUtils.encodeAuthFromPassword(password), + ); + const vaults2: Array<{ name: string; id: string }> = []; + for await (const vault of listResponse2) { + expect(vault).toBeInstanceOf(vaultsPB.List); + vaults2.push({ name: vault.getVaultName(), id: vault.getVaultId() }); + } + expect(vaults2).toHaveLength(0); + }); +}); diff --git a/tests/client/service/vaultsLog.test.ts b/tests/client/service/vaultsLog.test.ts new file mode 100644 index 000000000..9a3e9f6c9 --- /dev/null +++ b/tests/client/service/vaultsLog.test.ts @@ -0,0 +1,188 @@ +import type { Host, Port } from '@/network/types'; +import type { VaultId } from '@/vaults/types'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type ACL from '@/acl/ACL'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import { Metadata } from '@grpc/grpc-js'; +import KeyManager from '@/keys/KeyManager'; +import VaultManager from '@/vaults/VaultManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import vaultsLog from '@/client/service/vaultsLog'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; +import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; +import * as clientUtils from '@/client/utils/utils'; +import * as keysUtils from '@/keys/utils'; +import * as testUtils from '../../utils'; + +describe('vaultsLog', () => { + const logger = new Logger('vaultsLog test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + }); + afterAll(async () => { + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + const vaultName = 'test-vault'; + const secret1 = { name: 'secret1', content: 'Secret-1-content' }; + const secret2 = { name: 'secret2', content: 'Secret-2-content' }; + let dataDir: string; + let vaultId: VaultId; + let commit1Oid: string; + let commit2Oid: string; + let commit3Oid: string; + let keyManager: KeyManager; + let db: DB; + let vaultManager: VaultManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyManager = await KeyManager.createKeyManager({ + password, + keysPath, + logger, + }); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyManager, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + vaultId = await vaultManager.createVault(vaultName); + await vaultManager.withVaults([vaultId], async (vault) => { + await vault.writeF(async (efs) => { + await efs.writeFile(secret1.name, secret1.content); + }); + commit1Oid = (await vault.log(undefined, 0))[0].commitId; + await vault.writeF(async (efs) => { + await efs.writeFile(secret2.name, secret2.content); + }); + commit2Oid = (await vault.log(undefined, 0))[0].commitId; + await vault.writeF(async (efs) => { + await efs.unlink(secret2.name); + }); + commit3Oid = (await vault.log(undefined, 0))[0].commitId; + }); + const clientService = { + vaultsLog: vaultsLog({ + authenticate, + vaultManager, + db, + logger, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: testUtils.generateRandomNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }, global.defaultTimeout * 2); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await vaultManager.stop(); + await db.stop(); + await keyManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('should get the full log', async () => { + const vaultsLogMessage = new vaultsPB.Log(); + const vaultMessage = new vaultsPB.Vault(); + vaultMessage.setNameOrId(vaultName); + vaultsLogMessage.setVault(vaultMessage); + const logStream = grpcClient.vaultsLog( + vaultsLogMessage, + clientUtils.encodeAuthFromPassword(password), + ); + const logMessages: vaultsPB.LogEntry[] = []; + for await (const log of logStream) { + expect(log).toBeInstanceOf(vaultsPB.LogEntry); + logMessages.push(log); + } + // Checking commits exist in order. + expect(logMessages[2].getOid()).toEqual(commit1Oid); + expect(logMessages[1].getOid()).toEqual(commit2Oid); + expect(logMessages[0].getOid()).toEqual(commit3Oid); + }); + test('should get a part of the log', async () => { + const vaultsLogMessage = new vaultsPB.Log(); + const vaultMessage = new vaultsPB.Vault(); + vaultMessage.setNameOrId(vaultName); + vaultsLogMessage.setVault(vaultMessage); + vaultsLogMessage.setLogDepth(2); + const logStream = grpcClient.vaultsLog( + vaultsLogMessage, + clientUtils.encodeAuthFromPassword(password), + ); + const logMessages: vaultsPB.LogEntry[] = []; + for await (const log of logStream) { + expect(log).toBeInstanceOf(vaultsPB.LogEntry); + logMessages.push(log); + } + // Checking commits exist in order. + expect(logMessages[1].getOid()).toEqual(commit2Oid); + expect(logMessages[0].getOid()).toEqual(commit3Oid); + }); + test('should get a specific commit', async () => { + const vaultsLogMessage = new vaultsPB.Log(); + const vaultMessage = new vaultsPB.Vault(); + vaultMessage.setNameOrId(vaultName); + vaultsLogMessage.setVault(vaultMessage); + vaultsLogMessage.setCommitId(commit2Oid); + const logStream = grpcClient.vaultsLog( + vaultsLogMessage, + clientUtils.encodeAuthFromPassword(password), + ); + const logMessages: vaultsPB.LogEntry[] = []; + for await (const log of logStream) { + expect(log).toBeInstanceOf(vaultsPB.LogEntry); + logMessages.push(log); + } + // Checking commits exist in order. + expect(logMessages[0].getOid()).toEqual(commit2Oid); + }); +}); diff --git a/tests/client/service/vaultsPermissionSetUnsetGet.test.ts b/tests/client/service/vaultsPermissionSetUnsetGet.test.ts new file mode 100644 index 000000000..299ab6219 --- /dev/null +++ b/tests/client/service/vaultsPermissionSetUnsetGet.test.ts @@ -0,0 +1,226 @@ +import type { Host, Port } from '@/network/types'; +import type NodeManager from '@/nodes/NodeManager'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import { Metadata } from '@grpc/grpc-js'; +import ACL from '@/acl/ACL'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import KeyManager from '@/keys/KeyManager'; +import NotificationsManager from '@/notifications/NotificationsManager'; +import VaultManager from '@/vaults/VaultManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import vaultsPermissionGet from '@/client/service/vaultsPermissionGet'; +import vaultsPermissionSet from '@/client/service/vaultsPermissionSet'; +import vaultsPermissionUnset from '@/client/service/vaultsPermissionUnset'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; +import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; +import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; +import * as clientUtils from '@/client/utils/utils'; +import * as keysUtils from '@/keys/utils'; +import * as nodesUtils from '@/nodes/utils'; +import * as testUtils from '../../utils'; + +describe('vaultsPermissionSetUnsetGet', () => { + const logger = new Logger('vaultsPermissionSetUnsetGet test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + let mockedSendNotification: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + mockedSendNotification = jest + .spyOn(NotificationsManager.prototype, 'sendNotification') + .mockImplementation(); + }); + afterAll(async () => { + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + mockedSendNotification.mockRestore(); + }); + const nodeId = testUtils.generateRandomNodeId(); + let dataDir: string; + let keyManager: KeyManager; + let db: DB; + let acl: ACL; + let gestaltGraph: GestaltGraph; + let notificationsManager: NotificationsManager; + let vaultManager: VaultManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyManager = await KeyManager.createKeyManager({ + password, + keysPath, + logger, + }); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + acl = await ACL.createACL({ + db, + logger, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + }); + await gestaltGraph.setNode({ + id: nodesUtils.encodeNodeId(nodeId), + chain: {}, + }); + notificationsManager = + await NotificationsManager.createNotificationsManager({ + acl, + db, + nodeConnectionManager: {} as NodeConnectionManager, + nodeManager: {} as NodeManager, + keyManager, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl, + keyManager, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph, + notificationsManager: notificationsManager, + logger, + }); + const clientService = { + vaultsPermissionSet: vaultsPermissionSet({ + authenticate, + vaultManager, + gestaltGraph, + acl, + notificationsManager, + db, + logger, + }), + vaultsPermissionUnset: vaultsPermissionUnset({ + authenticate, + vaultManager, + gestaltGraph, + acl, + db, + logger, + }), + vaultsPermissionGet: vaultsPermissionGet({ + authenticate, + vaultManager, + acl, + db, + logger, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: testUtils.generateRandomNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await vaultManager.stop(); + await notificationsManager.stop(); + await gestaltGraph.stop(); + await acl.stop(); + await db.stop(); + await keyManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('sets, gets, and unsets vault permissions', async () => { + const vaultName = 'test-vault'; + await vaultManager.createVault(vaultName); + // Set permissions + const vault = new vaultsPB.Vault(); + vault.setNameOrId(vaultName); + const node = new nodesPB.Node(); + node.setNodeId(nodesUtils.encodeNodeId(nodeId)); + const permissions = new vaultsPB.Permissions(); + permissions.setVault(vault); + permissions.setNode(node); + permissions.setVaultPermissionsList(['clone', 'pull']); + const setResponse = await grpcClient.vaultsPermissionSet( + permissions, + clientUtils.encodeAuthFromPassword(password), + ); + expect(setResponse).toBeInstanceOf(utilsPB.StatusMessage); + expect(setResponse.getSuccess()).toBeTruthy(); + // Get permissions + const getResponse1 = grpcClient.vaultsPermissionGet( + vault, + clientUtils.encodeAuthFromPassword(password), + ); + const list1: Record[] = []; + for await (const permission of getResponse1) { + expect(permission).toBeInstanceOf(vaultsPB.Permissions); + const permissionsList = permission.getVaultPermissionsList(); + expect(permissionsList).toContain('pull'); + expect(permissionsList).toContain('clone'); + const node = permission.getNode(); + const receivedNodeId = node?.getNodeId(); + expect(receivedNodeId).toEqual(nodesUtils.encodeNodeId(nodeId)); + list1.push(permission.toObject()); + } + expect(list1).toHaveLength(1); + // Unset permissions + const deleteResponse = await grpcClient.vaultsPermissionUnset( + permissions, + clientUtils.encodeAuthFromPassword(password), + ); + expect(deleteResponse).toBeInstanceOf(utilsPB.StatusMessage); + expect(deleteResponse.getSuccess()).toBeTruthy(); + // Check permissions were unset + const getResponse2 = grpcClient.vaultsPermissionGet( + vault, + clientUtils.encodeAuthFromPassword(password), + ); + const list2: Record[] = []; + for await (const permission of getResponse2) { + expect(permission).toBeInstanceOf(vaultsPB.Permissions); + const permissionsList = permission.getVaultPermissionsList(); + expect(permissionsList).toEqual([]); + const node = permission.getNode(); + const receivedNodeId = node?.getNodeId(); + expect(receivedNodeId).toEqual(nodesUtils.encodeNodeId(nodeId)); + list2.push(permission.toObject()); + } + expect(list2).toHaveLength(1); + }); +}); diff --git a/tests/client/service/vaultsPull.test.ts b/tests/client/service/vaultsPull.test.ts new file mode 100644 index 000000000..8240e167d --- /dev/null +++ b/tests/client/service/vaultsPull.test.ts @@ -0,0 +1,99 @@ +import type { Host, Port } from '@/network/types'; +import type KeyManager from '@/keys/KeyManager'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type ACL from '@/acl/ACL'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import { Metadata } from '@grpc/grpc-js'; +import VaultManager from '@/vaults/VaultManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import vaultsPull from '@/client/service/vaultsPull'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; +import * as keysUtils from '@/keys/utils'; +import * as testUtils from '../../utils'; + +describe('vaultsPull', () => { + const logger = new Logger('vaultsPull test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + }); + afterAll(async () => { + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + let dataDir: string; + let db: DB; + let vaultManager: VaultManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyManager: {} as KeyManager, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + const clientService = { + vaultsPull: vaultsPull({ + authenticate, + vaultManager, + db, + logger, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: testUtils.generateRandomNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await vaultManager.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test.todo('pulls from a vault'); +}); diff --git a/tests/client/service/vaultsRename.test.ts b/tests/client/service/vaultsRename.test.ts new file mode 100644 index 000000000..0e7dd856e --- /dev/null +++ b/tests/client/service/vaultsRename.test.ts @@ -0,0 +1,128 @@ +import type { Host, Port } from '@/network/types'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type ACL from '@/acl/ACL'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import { Metadata } from '@grpc/grpc-js'; +import KeyManager from '@/keys/KeyManager'; +import VaultManager from '@/vaults/VaultManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import vaultsRename from '@/client/service/vaultsRename'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; +import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; +import * as clientUtils from '@/client/utils/utils'; +import * as keysUtils from '@/keys/utils'; +import * as vaultsUtils from '@/vaults/utils'; +import * as testUtils from '../../utils'; + +describe('vaultsRename', () => { + const logger = new Logger('vaultsRename test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + }); + afterAll(async () => { + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + let dataDir: string; + let keyManager: KeyManager; + let db: DB; + let vaultManager: VaultManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyManager = await KeyManager.createKeyManager({ + password, + keysPath, + logger, + }); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyManager, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + const clientService = { + vaultsRename: vaultsRename({ + authenticate, + vaultManager, + db, + logger, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: testUtils.generateRandomNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await vaultManager.stop(); + await db.stop(); + await keyManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('should rename vault', async () => { + const vaultId1 = await vaultManager.createVault('test-vault1'); + const vaultRenameMessage = new vaultsPB.Rename(); + const vaultMessage = new vaultsPB.Vault(); + vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId1)); + vaultRenameMessage.setVault(vaultMessage); + vaultRenameMessage.setNewName('test-vault2'); + const vaultId2 = await grpcClient.vaultsRename( + vaultRenameMessage, + clientUtils.encodeAuthFromPassword(password), + ); + expect(vaultId2).toBeInstanceOf(vaultsPB.Vault); + expect(vaultsUtils.decodeVaultId(vaultId2.getNameOrId())).toStrictEqual( + vaultId1, + ); + const renamedVaultId = await vaultManager.getVaultId('test-vault2'); + expect(renamedVaultId).toEqual(vaultId1); + }); +}); diff --git a/tests/client/service/vaultsScan.test.ts b/tests/client/service/vaultsScan.test.ts new file mode 100644 index 000000000..40abc72eb --- /dev/null +++ b/tests/client/service/vaultsScan.test.ts @@ -0,0 +1,91 @@ +import type { DB } from '@matrixai/db'; +import type { Host, Port } from '@/network/types'; +import type KeyManager from '@/keys/KeyManager'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type ACL from '@/acl/ACL'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { Metadata } from '@grpc/grpc-js'; +import VaultManager from '@/vaults/VaultManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import vaultsScan from '@/client/service/vaultsScan'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; +import * as keysUtils from '@/keys/utils'; +import * as testUtils from '../../utils'; + +describe('vaultsScan', () => { + const logger = new Logger('vaultsScan test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + }); + afterAll(async () => { + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + let dataDir: string; + let vaultManager: VaultManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db: {} as DB, + acl: {} as ACL, + keyManager: {} as KeyManager, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + const clientService = { + vaultsScan: vaultsScan({ + authenticate, + vaultManager, + logger, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: testUtils.generateRandomNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await vaultManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test.todo('scans a vault'); +}); diff --git a/tests/client/service/vaultsSecretsEdit.test.ts b/tests/client/service/vaultsSecretsEdit.test.ts new file mode 100644 index 000000000..0956bac33 --- /dev/null +++ b/tests/client/service/vaultsSecretsEdit.test.ts @@ -0,0 +1,141 @@ +import type { Host, Port } from '@/network/types'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type ACL from '@/acl/ACL'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import { Metadata } from '@grpc/grpc-js'; +import KeyManager from '@/keys/KeyManager'; +import VaultManager from '@/vaults/VaultManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import vaultsSecretsEdit from '@/client/service/vaultsSecretsEdit'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; +import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; +import * as secretsPB from '@/proto/js/polykey/v1/secrets/secrets_pb'; +import * as clientUtils from '@/client/utils/utils'; +import * as vaultsUtils from '@/vaults/utils'; +import * as keysUtils from '@/keys/utils'; +import * as testUtils from '../../utils'; + +describe('vaultsSecretsEdit', () => { + const logger = new Logger('vaultsSecretsEdit test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + }); + afterAll(async () => { + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + let dataDir: string; + let keyManager: KeyManager; + let db: DB; + let vaultManager: VaultManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyManager = await KeyManager.createKeyManager({ + password, + keysPath, + logger, + }); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyManager, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + const clientService = { + vaultsSecretsEdit: vaultsSecretsEdit({ + authenticate, + vaultManager, + db, + logger, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: testUtils.generateRandomNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await vaultManager.stop(); + await db.stop(); + await keyManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('edits secrets', async () => { + const vaultName = 'test-vault'; + const secretName = 'test-secret'; + const vaultId = await vaultManager.createVault(vaultName); + await vaultManager.withVaults([vaultId], async (vault) => { + await vault.writeF(async (efs) => { + await efs.writeFile(secretName, secretName); + }); + }); + const secretMessage = new secretsPB.Secret(); + const vaultMessage = new vaultsPB.Vault(); + vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); + secretMessage.setVault(vaultMessage); + secretMessage.setSecretName(secretName); + secretMessage.setSecretContent(Buffer.from('content-change')); + const response = await grpcClient.vaultsSecretsEdit( + secretMessage, + clientUtils.encodeAuthFromPassword(password), + ); + expect(response).toBeInstanceOf(utilsPB.StatusMessage); + expect(response.getSuccess()).toBeTruthy(); + await vaultManager.withVaults([vaultId], async (vault) => { + await vault.readF(async (efs) => { + expect((await efs.readFile(secretName)).toString()).toStrictEqual( + 'content-change', + ); + }); + }); + }); +}); diff --git a/tests/client/service/vaultsSecretsMkdir.test.ts b/tests/client/service/vaultsSecretsMkdir.test.ts new file mode 100644 index 000000000..1e4c1b971 --- /dev/null +++ b/tests/client/service/vaultsSecretsMkdir.test.ts @@ -0,0 +1,133 @@ +import type { Host, Port } from '@/network/types'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type ACL from '@/acl/ACL'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import { Metadata } from '@grpc/grpc-js'; +import KeyManager from '@/keys/KeyManager'; +import VaultManager from '@/vaults/VaultManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import vaultsSecretsMkdir from '@/client/service/vaultsSecretsMkdir'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; +import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; +import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '@/client/utils/utils'; +import * as vaultsUtils from '@/vaults/utils'; +import * as keysUtils from '@/keys/utils'; +import * as testUtils from '../../utils'; + +describe('vaultsSecretsMkdir', () => { + const logger = new Logger('vaultsSecretsMkdir test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + }); + afterAll(async () => { + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + let dataDir: string; + let keyManager: KeyManager; + let db: DB; + let vaultManager: VaultManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyManager = await KeyManager.createKeyManager({ + password, + keysPath, + logger, + }); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyManager, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + const clientService = { + vaultsSecretsMkdir: vaultsSecretsMkdir({ + authenticate, + vaultManager, + db, + logger, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: testUtils.generateRandomNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await vaultManager.stop(); + await db.stop(); + await keyManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('makes a directory', async () => { + const vaultName = 'test-vault'; + const vaultId = await vaultManager.createVault(vaultName); + const dirPath = 'dir/dir1/dir2'; + const vaultMkdirMessage = new vaultsPB.Mkdir(); + const vaultMessage = new vaultsPB.Vault(); + vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); + vaultMkdirMessage.setVault(vaultMessage); + vaultMkdirMessage.setDirName(dirPath); + vaultMkdirMessage.setRecursive(true); + const response = await grpcClient.vaultsSecretsMkdir( + vaultMkdirMessage, + clientUtils.encodeAuthFromPassword(password), + ); + expect(response).toBeInstanceOf(utilsPB.StatusMessage); + expect(response.getSuccess()).toBeTruthy(); + await vaultManager.withVaults([vaultId], async (vault) => { + await vault.readF(async (efs) => { + expect(await efs.exists(dirPath)).toBeTruthy(); + }); + }); + }); +}); diff --git a/tests/client/service/vaultsSecretsNewDeleteGet.test.ts b/tests/client/service/vaultsSecretsNewDeleteGet.test.ts new file mode 100644 index 000000000..f743f6ff0 --- /dev/null +++ b/tests/client/service/vaultsSecretsNewDeleteGet.test.ts @@ -0,0 +1,169 @@ +import type { Host, Port } from '@/network/types'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type ACL from '@/acl/ACL'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import { Metadata } from '@grpc/grpc-js'; +import KeyManager from '@/keys/KeyManager'; +import VaultManager from '@/vaults/VaultManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import vaultsSecretsNew from '@/client/service/vaultsSecretsNew'; +import vaultsSecretsDelete from '@/client/service/vaultsSecretsDelete'; +import vaultsSecretsGet from '@/client/service/vaultsSecretsGet'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; +import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; +import * as secretsPB from '@/proto/js/polykey/v1/secrets/secrets_pb'; +import * as clientUtils from '@/client/utils/utils'; +import * as vaultsUtils from '@/vaults/utils'; +import * as keysUtils from '@/keys/utils'; +import * as vaultsErrors from '@/vaults/errors'; +import * as testUtils from '../../utils'; + +describe('vaultsSecretsNewDeleteGet', () => { + const logger = new Logger('vaultsSecretsNewDeleteGet test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + }); + afterAll(async () => { + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + let dataDir: string; + let keyManager: KeyManager; + let db: DB; + let vaultManager: VaultManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyManager = await KeyManager.createKeyManager({ + password, + keysPath, + logger, + }); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyManager, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + const clientService = { + vaultsSecretsNew: vaultsSecretsNew({ + authenticate, + vaultManager, + db, + logger, + }), + vaultsSecretsDelete: vaultsSecretsDelete({ + authenticate, + vaultManager, + db, + logger, + }), + vaultsSecretsGet: vaultsSecretsGet({ + authenticate, + vaultManager, + db, + logger, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: testUtils.generateRandomNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await vaultManager.stop(); + await db.stop(); + await keyManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('creates, gets, and deletes secrets', async () => { + // Create secret + const secret = 'test-secret'; + const vaultId = await vaultManager.createVault('test-vault'); + const secretMessage = new secretsPB.Secret(); + const vaultMessage = new vaultsPB.Vault(); + vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); + secretMessage.setVault(vaultMessage); + secretMessage.setSecretName(secret); + secretMessage.setSecretContent(Buffer.from(secret)); + const createResponse = await grpcClient.vaultsSecretsNew( + secretMessage, + clientUtils.encodeAuthFromPassword(password), + ); + expect(createResponse).toBeInstanceOf(utilsPB.StatusMessage); + expect(createResponse.getSuccess()).toBeTruthy(); + // Get secret + const getResponse1 = await grpcClient.vaultsSecretsGet( + secretMessage, + clientUtils.encodeAuthFromPassword(password), + ); + expect(getResponse1).toBeInstanceOf(secretsPB.Secret); + const secretContent = Buffer.from( + getResponse1.getSecretContent(), + ).toString(); + expect(secretContent).toStrictEqual(secret); + // Delete secret + const deleteResponse = await grpcClient.vaultsSecretsDelete( + secretMessage, + clientUtils.encodeAuthFromPassword(password), + ); + expect(deleteResponse).toBeInstanceOf(utilsPB.StatusMessage); + expect(deleteResponse.getSuccess()).toBeTruthy(); + // Check secret was deleted + await testUtils.expectRemoteError( + grpcClient.vaultsSecretsGet( + secretMessage, + clientUtils.encodeAuthFromPassword(password), + ), + vaultsErrors.ErrorSecretsSecretUndefined, + ); + }); +}); diff --git a/tests/client/service/vaultsSecretsNewDirList.test.ts b/tests/client/service/vaultsSecretsNewDirList.test.ts new file mode 100644 index 000000000..7e8911dbd --- /dev/null +++ b/tests/client/service/vaultsSecretsNewDirList.test.ts @@ -0,0 +1,157 @@ +import type { Host, Port } from '@/network/types'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type ACL from '@/acl/ACL'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import { Metadata } from '@grpc/grpc-js'; +import KeyManager from '@/keys/KeyManager'; +import VaultManager from '@/vaults/VaultManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import vaultsSecretsNewDir from '@/client/service/vaultsSecretsNewDir'; +import vaultsSecretsList from '@/client/service/vaultsSecretsList'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; +import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; +import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import * as secretsPB from '@/proto/js/polykey/v1/secrets/secrets_pb'; +import * as clientUtils from '@/client/utils/utils'; +import * as vaultsUtils from '@/vaults/utils'; +import * as keysUtils from '@/keys/utils'; +import * as testUtils from '../../utils'; + +describe('vaultsSecretsNewDirList', () => { + const logger = new Logger('vaultsSecretsNewDirList test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + }); + afterAll(async () => { + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + let dataDir: string; + let keyManager: KeyManager; + let db: DB; + let vaultManager: VaultManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyManager = await KeyManager.createKeyManager({ + password, + keysPath, + logger, + }); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyManager, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + const clientService = { + vaultsSecretsNewDir: vaultsSecretsNewDir({ + authenticate, + vaultManager, + fs, + db, + logger, + }), + vaultsSecretsList: vaultsSecretsList({ + authenticate, + vaultManager, + db, + logger, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: testUtils.generateRandomNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await vaultManager.stop(); + await db.stop(); + await keyManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('adds and lists a directory of secrets', async () => { + // Add directory of secrets + const vaultName = 'test-vault'; + const secretList = ['test-secret1', 'test-secret2', 'test-secret3']; + const secretDir = path.join(dataDir, 'secretDir'); + await fs.promises.mkdir(secretDir); + for (const secret of secretList) { + const secretFile = path.join(secretDir, secret); + // Write secret to file + await fs.promises.writeFile(secretFile, secret); + } + const vaultId = await vaultManager.createVault(vaultName); + const secretDirectoryMessage = new secretsPB.Directory(); + const vaultMessage = new vaultsPB.Vault(); + vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); + secretDirectoryMessage.setVault(vaultMessage); + secretDirectoryMessage.setSecretDirectory(secretDir); + const addResponse = await grpcClient.vaultsSecretsNewDir( + secretDirectoryMessage, + clientUtils.encodeAuthFromPassword(password), + ); + expect(addResponse).toBeInstanceOf(utilsPB.StatusMessage); + expect(addResponse.getSuccess()).toBeTruthy(); + // List secrets + const listResponse = grpcClient.vaultsSecretsList( + vaultMessage, + clientUtils.encodeAuthFromPassword(password), + ); + const secrets: Array = []; + for await (const secret of listResponse) { + expect(secret).toBeInstanceOf(secretsPB.Secret); + secrets.push(secret.getSecretName()); + } + expect(secrets.sort()).toStrictEqual( + secretList.map((secret) => path.join('secretDir', secret)).sort(), + ); + }); +}); diff --git a/tests/client/service/vaultsSecretsRename.test.ts b/tests/client/service/vaultsSecretsRename.test.ts new file mode 100644 index 000000000..1d6027aa3 --- /dev/null +++ b/tests/client/service/vaultsSecretsRename.test.ts @@ -0,0 +1,143 @@ +import type { Host, Port } from '@/network/types'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type ACL from '@/acl/ACL'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import { Metadata } from '@grpc/grpc-js'; +import KeyManager from '@/keys/KeyManager'; +import VaultManager from '@/vaults/VaultManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import vaultsSecretsRename from '@/client/service/vaultsSecretsRename'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; +import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; +import * as secretsPB from '@/proto/js/polykey/v1/secrets/secrets_pb'; +import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import * as clientUtils from '@/client/utils/utils'; +import * as vaultsUtils from '@/vaults/utils'; +import * as keysUtils from '@/keys/utils'; +import * as testUtils from '../../utils'; + +describe('vaultsSecretsRename', () => { + const logger = new Logger('vaultsSecretsRename test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + }); + afterAll(async () => { + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + let dataDir: string; + let keyManager: KeyManager; + let db: DB; + let vaultManager: VaultManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyManager = await KeyManager.createKeyManager({ + password, + keysPath, + logger, + }); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyManager, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + const clientService = { + vaultsSecretsRename: vaultsSecretsRename({ + authenticate, + vaultManager, + db, + logger, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: testUtils.generateRandomNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await vaultManager.stop(); + await db.stop(); + await keyManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('renames a secret', async () => { + const vaultName = 'test-vault'; + const secretName = 'test-secret'; + const vaultId = await vaultManager.createVault(vaultName); + await vaultManager.withVaults([vaultId], async (vault) => { + await vault.writeF(async (efs) => { + await efs.writeFile(secretName, secretName); + }); + }); + const secretRenameMessage = new secretsPB.Rename(); + const vaultMessage = new vaultsPB.Vault(); + const secretMessage = new secretsPB.Secret(); + vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); + secretMessage.setSecretName(secretName); + secretMessage.setVault(vaultMessage); + secretRenameMessage.setNewName('name-change'); + secretRenameMessage.setOldSecret(secretMessage); + const response = await grpcClient.vaultsSecretsRename( + secretRenameMessage, + clientUtils.encodeAuthFromPassword(password), + ); + expect(response).toBeInstanceOf(utilsPB.StatusMessage); + expect(response.getSuccess()).toBeTruthy(); + await vaultManager.withVaults([vaultId], async (vault) => { + await vault.readF(async (efs) => { + expect((await efs.readFile('name-change')).toString()).toStrictEqual( + secretName, + ); + }); + }); + }); +}); diff --git a/tests/client/service/vaultsSecretsStat.test.ts b/tests/client/service/vaultsSecretsStat.test.ts new file mode 100644 index 000000000..909ee82b8 --- /dev/null +++ b/tests/client/service/vaultsSecretsStat.test.ts @@ -0,0 +1,137 @@ +import type { Stat } from 'encryptedfs'; +import type { Host, Port } from '@/network/types'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type ACL from '@/acl/ACL'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import { Metadata } from '@grpc/grpc-js'; +import KeyManager from '@/keys/KeyManager'; +import VaultManager from '@/vaults/VaultManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import vaultsSecretsStat from '@/client/service/vaultsSecretsStat'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; +import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; +import * as secretsPB from '@/proto/js/polykey/v1/secrets/secrets_pb'; +import * as clientUtils from '@/client/utils/utils'; +import * as vaultsUtils from '@/vaults/utils'; +import * as keysUtils from '@/keys/utils'; +import * as testUtils from '../../utils'; + +describe('vaultsSecretsStat', () => { + const logger = new Logger('vaultsSecretsStat test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + }); + afterAll(async () => { + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + let dataDir: string; + let keyManager: KeyManager; + let db: DB; + let vaultManager: VaultManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyManager = await KeyManager.createKeyManager({ + password, + keysPath, + logger, + }); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyManager, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + const clientService = { + vaultsSecretsStat: vaultsSecretsStat({ + authenticate, + vaultManager, + db, + logger, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: testUtils.generateRandomNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await vaultManager.stop(); + await db.stop(); + await keyManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('stats a file', async () => { + const vaultName = 'test-vault'; + const secretName = 'test-secret'; + const vaultId = await vaultManager.createVault(vaultName); + await vaultManager.withVaults([vaultId], async (vault) => { + await vault.writeF(async (efs) => { + await efs.writeFile(secretName, secretName); + }); + }); + const secretMessage = new secretsPB.Secret(); + const vaultMessage = new vaultsPB.Vault(); + vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); + secretMessage.setVault(vaultMessage); + secretMessage.setSecretName(secretName); + const response = await grpcClient.vaultsSecretsStat( + secretMessage, + clientUtils.encodeAuthFromPassword(password), + ); + expect(response).toBeInstanceOf(secretsPB.Stat); + const stat: Stat = JSON.parse(response.getJson()); + expect(stat.size).toBe(secretName.length); + expect(stat.blksize).toBe(4096); + expect(stat.blocks).toBe(1); + expect(stat.nlink).toBe(1); + }); +}); diff --git a/tests/client/service/vaultsVersion.test.ts b/tests/client/service/vaultsVersion.test.ts new file mode 100644 index 000000000..09373743a --- /dev/null +++ b/tests/client/service/vaultsVersion.test.ts @@ -0,0 +1,182 @@ +import type { Host, Port } from '@/network/types'; +import type { VaultId } from '@/vaults/types'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type ACL from '@/acl/ACL'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import { Metadata } from '@grpc/grpc-js'; +import KeyManager from '@/keys/KeyManager'; +import VaultManager from '@/vaults/VaultManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import vaultsVersion from '@/client/service/vaultsVersion'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; +import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; +import * as clientUtils from '@/client/utils/utils'; +import * as keysUtils from '@/keys/utils'; +import * as vaultsUtils from '@/vaults/utils'; +import * as vaultsErrors from '@/vaults/errors'; +import * as testUtils from '../../utils'; + +describe('vaultsVersion', () => { + const logger = new Logger('vaultsVersion test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + }); + afterAll(async () => { + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + const secretVer1 = { + name: 'secret1v1', + content: 'Secret-1-content-ver1', + }; + const secretVer2 = { + name: 'secret1v2', + content: 'Secret-1-content-ver2', + }; + const vaultName = 'test-vault'; + let vaultId: VaultId; + let dataDir: string; + let keyManager: KeyManager; + let db: DB; + let vaultManager: VaultManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyManager = await KeyManager.createKeyManager({ + password, + keysPath, + logger, + }); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyManager, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + vaultId = await vaultManager.createVault(vaultName); + const clientService = { + vaultsVersion: vaultsVersion({ + authenticate, + vaultManager, + db, + logger, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: testUtils.generateRandomNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await vaultManager.stop(); + await db.stop(); + await keyManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('should switch a vault to a version', async () => { + // Commit some history + const ver1Oid = await vaultManager.withVaults([vaultId], async (vault) => { + await vault.writeF(async (efs) => { + await efs.writeFile(secretVer1.name, secretVer1.content); + }); + const ver1Oid = (await vault.log())[0].commitId; + await vault.writeF(async (efs) => { + await efs.writeFile(secretVer2.name, secretVer2.content); + }); + return ver1Oid; + }); + // Revert the version + const vaultMessage = new vaultsPB.Vault(); + vaultMessage.setNameOrId(vaultName); + const vaultVersionMessage = new vaultsPB.Version(); + vaultVersionMessage.setVault(vaultMessage); + vaultVersionMessage.setVersionId(ver1Oid); + const version = await grpcClient.vaultsVersion( + vaultVersionMessage, + clientUtils.encodeAuthFromPassword(password), + ); + expect(version.getIsLatestVersion()).toBeFalsy(); + // Read old history + await vaultManager.withVaults([vaultId], async (vault) => { + await vault.readF(async (efs) => { + expect((await efs.readFile(secretVer1.name)).toString()).toStrictEqual( + secretVer1.content, + ); + }); + }); + }); + test('should fail to find a non existent version', async () => { + // Revert the version + const vaultMessage = new vaultsPB.Vault(); + vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); + const vaultVersionMessage = new vaultsPB.Version(); + vaultVersionMessage.setVault(vaultMessage); + vaultVersionMessage.setVersionId('invalidOid'); + const version = grpcClient.vaultsVersion( + vaultVersionMessage, + clientUtils.encodeAuthFromPassword(password), + ); + await testUtils.expectRemoteError( + version, + vaultsErrors.ErrorVaultReferenceInvalid, + ); + vaultVersionMessage.setVersionId( + '7660aa9a2fee90e875c2d19e5deefe882ca1d4d9', + ); + const version2 = grpcClient.vaultsVersion( + vaultVersionMessage, + clientUtils.encodeAuthFromPassword(password), + ); + await testUtils.expectRemoteError( + version2, + vaultsErrors.ErrorVaultReferenceMissing, + ); + }); +}); diff --git a/tests/client/utils.ts b/tests/client/utils.ts index 247b1da8b..9af325dba 100644 --- a/tests/client/utils.ts +++ b/tests/client/utils.ts @@ -1,18 +1,17 @@ import type { IClientServiceServer } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import type { SessionToken } from '@/sessions/types'; -import type { PolykeyAgent } from '@'; +import type PolykeyAgent from '@/PolykeyAgent'; import type { NodeId } from '@/nodes/types'; import type { Host, Port } from '@/network/types'; import * as grpc from '@grpc/grpc-js'; - import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { ClientServiceService, ClientServiceClient, } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import { createClientService } from '@/client'; +import createClientService from '@/client/service'; import PolykeyClient from '@/PolykeyClient'; -import { promisify } from '@/utils'; +import { promisify, timerStart } from '@/utils'; import * as grpcUtils from '@/grpc/utils'; async function openTestClientServer({ @@ -41,6 +40,8 @@ async function openTestClientServer({ grpcServerClient: pkAgent.grpcServerClient, grpcServerAgent: pkAgent.grpcServerAgent, fs: pkAgent.fs, + db: pkAgent.db, + logger: pkAgent.logger, }); const callCredentials = _secure @@ -81,7 +82,7 @@ async function openTestClientClient( port: port, fs, logger, - timeout: 30000, + timer: timerStart(30000), }); return pkc; diff --git a/tests/discovery/Discovery.test.ts b/tests/discovery/Discovery.test.ts index 70c4641dd..a267cc7d8 100644 --- a/tests/discovery/Discovery.test.ts +++ b/tests/discovery/Discovery.test.ts @@ -1,22 +1,23 @@ import type { ClaimLinkIdentity } from '@/claims/types'; import type { IdentityId, ProviderId } from '@/identities/types'; import type { Host, Port } from '@/network/types'; -import type { Gestalt } from '@/gestalts/types'; import fs from 'fs'; import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; -import { PolykeyAgent } from '@'; -import { Discovery } from '@/discovery'; -import { GestaltGraph } from '@/gestalts'; -import { IdentitiesManager } from '@/identities'; -import { NodeConnectionManager, NodeGraph, NodeManager } from '@/nodes'; -import { KeyManager } from '@/keys'; -import { ACL } from '@/acl'; -import { Sigchain } from '@/sigchain'; +import Queue from '@/nodes/Queue'; +import PolykeyAgent from '@/PolykeyAgent'; +import Discovery from '@/discovery/Discovery'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import NodeGraph from '@/nodes/NodeGraph'; +import NodeManager from '@/nodes/NodeManager'; +import KeyManager from '@/keys/KeyManager'; +import ACL from '@/acl/ACL'; +import Sigchain from '@/sigchain/Sigchain'; import Proxy from '@/network/Proxy'; -import { poll } from '@/utils'; import * as nodesUtils from '@/nodes/utils'; import * as claimsUtils from '@/claims/utils'; import * as discoveryErrors from '@/discovery/errors'; @@ -47,6 +48,7 @@ describe('Discovery', () => { let gestaltGraph: GestaltGraph; let identitiesManager: IdentitiesManager; let nodeGraph: NodeGraph; + let queue: Queue; let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; let db: DB; @@ -130,23 +132,30 @@ describe('Discovery', () => { keyManager, logger: logger.getChild('NodeGraph'), }); + queue = new Queue({ + logger: logger.getChild('queue'), + }); nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, connConnectTime: 2000, connTimeoutTime: 2000, logger: logger.getChild('NodeConnectionManager'), }); - await nodeConnectionManager.start(); nodeManager = new NodeManager({ db, keyManager, - sigchain, - nodeGraph, nodeConnectionManager, - logger: logger.getChild('nodeManager'), + nodeGraph, + sigchain, + queue, + logger, }); + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); // Set up other gestalt nodeA = await PolykeyAgent.createPolykeyAgent({ password: password, @@ -202,6 +211,8 @@ describe('Discovery', () => { await nodeA.stop(); await nodeB.stop(); await nodeConnectionManager.stop(); + await nodeManager.stop(); + await queue.stop(); await nodeGraph.stop(); await proxy.stop(); await sigchain.stop(); @@ -237,7 +248,7 @@ describe('Discovery', () => { discovery.queueDiscoveryByIdentity('' as ProviderId, '' as IdentityId), ).rejects.toThrow(discoveryErrors.ErrorDiscoveryNotRunning); await expect( - discovery.queueDiscoveryByNode(testUtils.generateRandomNodeId()), + discovery.queueDiscoveryByNode(testNodesUtils.generateRandomNodeId()), ).rejects.toThrow(discoveryErrors.ErrorDiscoveryNotRunning); }); test('discovery by node', async () => { @@ -251,34 +262,15 @@ describe('Discovery', () => { logger, }); await discovery.queueDiscoveryByNode(nodeA.keyManager.getNodeId()); - const gestalt = await poll( - async () => { - const gestalts = await poll>( - async () => { - return await gestaltGraph.getGestalts(); - }, - (_, result) => { - if (result.length === 1) return true; - return false; - }, - 100, - ); - return gestalts[0]; - }, - (_, result) => { - if (result === undefined) return false; - if (Object.keys(result.matrix).length === 3) return true; - return false; - }, - 100, - ); - const gestaltMatrix = gestalt.matrix; - const gestaltNodes = gestalt.nodes; - const gestaltIdentities = gestalt.identities; + await discovery.waitForDrained(); + const gestalt = await gestaltGraph.getGestalts(); + const gestaltMatrix = gestalt[0].matrix; + const gestaltNodes = gestalt[0].nodes; + const gestaltIdentities = gestalt[0].identities; expect(Object.keys(gestaltMatrix)).toHaveLength(3); expect(Object.keys(gestaltNodes)).toHaveLength(2); expect(Object.keys(gestaltIdentities)).toHaveLength(1); - const gestaltString = JSON.stringify(gestalt); + const gestaltString = JSON.stringify(gestalt[0]); expect(gestaltString).toContain( nodesUtils.encodeNodeId(nodeA.keyManager.getNodeId()), ); @@ -304,27 +296,8 @@ describe('Discovery', () => { logger, }); await discovery.queueDiscoveryByIdentity(testToken.providerId, identityId); - const gestalt = await poll( - async () => { - const gestalts = await poll>( - async () => { - return await gestaltGraph.getGestalts(); - }, - (_, result) => { - if (result.length === 1) return true; - return false; - }, - 100, - ); - return gestalts[0]; - }, - (_, result) => { - if (result === undefined) return false; - if (Object.keys(result.matrix).length === 3) return true; - return false; - }, - 100, - ); + await discovery.waitForDrained(); + const gestalt = (await gestaltGraph.getGestalts())[0]; const gestaltMatrix = gestalt.matrix; const gestaltNodes = gestalt.nodes; const gestaltIdentities = gestalt.identities; @@ -357,27 +330,8 @@ describe('Discovery', () => { logger, }); await discovery.queueDiscoveryByNode(nodeA.keyManager.getNodeId()); - const gestalt1 = await poll( - async () => { - const gestalts = await poll>( - async () => { - return await gestaltGraph.getGestalts(); - }, - (_, result) => { - if (result.length === 1) return true; - return false; - }, - 100, - ); - return gestalts[0]; - }, - (_, result) => { - if (result === undefined) return false; - if (Object.keys(result.matrix).length === 3) return true; - return false; - }, - 100, - ); + await discovery.waitForDrained(); + const gestalt1 = (await gestaltGraph.getGestalts())[0]; const gestaltMatrix1 = gestalt1.matrix; const gestaltNodes1 = gestalt1.nodes; const gestaltIdentities1 = gestalt1.identities; @@ -410,27 +364,8 @@ describe('Discovery', () => { // Note that eventually we would like to add in a system of revisiting // already discovered vertices, however for now we must do this manually. await discovery.queueDiscoveryByNode(nodeA.keyManager.getNodeId()); - const gestalt2 = await poll( - async () => { - const gestalts = await poll>( - async () => { - return await gestaltGraph.getGestalts(); - }, - (_, result) => { - if (result.length === 1) return true; - return false; - }, - 100, - ); - return gestalts[0]; - }, - (_, result) => { - if (result === undefined) return false; - if (Object.keys(result.matrix).length === 4) return true; - return false; - }, - 100, - ); + await discovery.waitForDrained(); + const gestalt2 = (await gestaltGraph.getGestalts())[0]; const gestaltMatrix2 = gestalt2.matrix; const gestaltNodes2 = gestalt2.nodes; const gestaltIdentities2 = gestalt2.identities; @@ -470,27 +405,8 @@ describe('Discovery', () => { await discovery.queueDiscoveryByNode(nodeA.keyManager.getNodeId()); await discovery.stop(); await discovery.start(); - const gestalt = await poll( - async () => { - const gestalts = await poll>( - async () => { - return await gestaltGraph.getGestalts(); - }, - (_, result) => { - if (result.length === 1) return true; - return false; - }, - 100, - ); - return gestalts[0]; - }, - (_, result) => { - if (result === undefined) return false; - if (Object.keys(result.matrix).length === 3) return true; - return false; - }, - 100, - ); + await discovery.waitForDrained(); + const gestalt = (await gestaltGraph.getGestalts())[0]; const gestaltMatrix = gestalt.matrix; const gestaltNodes = gestalt.nodes; const gestaltIdentities = gestalt.identities; diff --git a/tests/gestalts/GestaltGraph.test.ts b/tests/gestalts/GestaltGraph.test.ts index fa30c86bd..e24a08e00 100644 --- a/tests/gestalts/GestaltGraph.test.ts +++ b/tests/gestalts/GestaltGraph.test.ts @@ -14,25 +14,25 @@ import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; -import { GestaltGraph } from '@/gestalts'; -import { ACL } from '@/acl'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import ACL from '@/acl/ACL'; import * as gestaltsErrors from '@/gestalts/errors'; import * as gestaltsUtils from '@/gestalts/utils'; import * as keysUtils from '@/keys/utils'; import * as nodesUtils from '@/nodes/utils'; -import * as testUtils from '../utils'; +import * as testNodesUtils from '../nodes/utils'; describe('GestaltGraph', () => { const logger = new Logger('GestaltGraph Test', LogLevel.WARN, [ new StreamHandler(), ]); - const nodeIdABC = testUtils.generateRandomNodeId(); + const nodeIdABC = testNodesUtils.generateRandomNodeId(); const nodeIdABCEncoded = nodesUtils.encodeNodeId(nodeIdABC); - const nodeIdDEE = testUtils.generateRandomNodeId(); + const nodeIdDEE = testNodesUtils.generateRandomNodeId(); const nodeIdDEEEncoded = nodesUtils.encodeNodeId(nodeIdDEE); - const nodeIdDEF = testUtils.generateRandomNodeId(); + const nodeIdDEF = testNodesUtils.generateRandomNodeId(); const nodeIdDEFEncoded = nodesUtils.encodeNodeId(nodeIdDEF); - const nodeIdZZZ = testUtils.generateRandomNodeId(); + const nodeIdZZZ = testNodesUtils.generateRandomNodeId(); const nodeIdZZZEncoded = nodesUtils.encodeNodeId(nodeIdZZZ); let dataDir: string; @@ -1248,8 +1248,8 @@ describe('GestaltGraph', () => { // its just that node 1 is eliminated nodePerms = await acl.getNodePerms(); expect(Object.keys(nodePerms)).toHaveLength(1); - expect(nodePerms[0]).not.toHaveProperty(nodeIdABC.toString()); - expect(nodePerms[0]).toHaveProperty(nodeIdDEE.toString()); + expect(nodePerms[0][nodeIdABC.toString()]).toBeUndefined(); + expect(nodePerms[0][nodeIdDEE.toString()]).toBeDefined(); await gestaltGraph.unsetNode(nodeIdDEE); nodePerms = await acl.getNodePerms(); expect(Object.keys(nodePerms)).toHaveLength(0); diff --git a/tests/git/utils.test.ts b/tests/git/utils.test.ts index 3f4b6cf9b..e1f59103f 100644 --- a/tests/git/utils.test.ts +++ b/tests/git/utils.test.ts @@ -165,9 +165,11 @@ describe('Git utils', () => { expect(object).toContain(firstCommit.commit.tree); expect(object).toContain(firstCommit.commit.parent[0]); expect(object).toContain(firstCommit.commit.author.name); - expect(object).toContain(firstCommit.commit.author.timestamp); + expect(object).toContain(firstCommit.commit.author.timestamp.toString()); expect(object).toContain(firstCommit.commit.committer.name); - expect(object).toContain(firstCommit.commit.committer.timestamp); + expect(object).toContain( + firstCommit.commit.committer.timestamp.toString(), + ); }); test('wrapped', async () => { const ref = await gitUtils.readObject({ @@ -190,9 +192,11 @@ describe('Git utils', () => { expect(object).toContain(firstCommit.commit.tree); expect(object).toContain(firstCommit.commit.parent[0]); expect(object).toContain(firstCommit.commit.author.name); - expect(object).toContain(firstCommit.commit.author.timestamp); + expect(object).toContain(firstCommit.commit.author.timestamp.toString()); expect(object).toContain(firstCommit.commit.committer.name); - expect(object).toContain(firstCommit.commit.committer.timestamp); + expect(object).toContain( + firstCommit.commit.committer.timestamp.toString(), + ); }); test('deflated', async () => { const ref = await gitUtils.readObject({ @@ -234,9 +238,11 @@ describe('Git utils', () => { expect(object).toContain(firstCommit.commit.tree); expect(object).toContain(firstCommit.commit.parent[0]); expect(object).toContain(firstCommit.commit.author.name); - expect(object).toContain(firstCommit.commit.author.timestamp); + expect(object).toContain(firstCommit.commit.author.timestamp.toString()); expect(object).toContain(firstCommit.commit.committer.name); - expect(object).toContain(firstCommit.commit.committer.timestamp); + expect(object).toContain( + firstCommit.commit.committer.timestamp.toString(), + ); }); }); }); diff --git a/tests/grpc/GRPCClient.test.ts b/tests/grpc/GRPCClient.test.ts index a4f83a1e0..bf252bc6d 100644 --- a/tests/grpc/GRPCClient.test.ts +++ b/tests/grpc/GRPCClient.test.ts @@ -10,13 +10,16 @@ import path from 'path'; import fs from 'fs'; import { DB } from '@matrixai/db'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { utils as keysUtils } from '@/keys'; -import { Session, SessionManager } from '@/sessions'; -import { errors as grpcErrors } from '@/grpc'; +import Session from '@/sessions/Session'; +import SessionManager from '@/sessions/SessionManager'; +import * as keysUtils from '@/keys/utils'; +import * as grpcErrors from '@/grpc/errors'; import * as clientUtils from '@/client/utils'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import { timerStart } from '@/utils'; import * as utils from './utils'; -import * as testUtils from '../utils'; +import * as testNodesUtils from '../nodes/utils'; +import { expectRemoteError } from '../utils'; describe('GRPCClient', () => { const logger = new Logger('GRPCClient Test', LogLevel.WARN, [ @@ -60,7 +63,7 @@ describe('GRPCClient', () => { }, }); const keyManager = { - getNodeId: () => testUtils.generateRandomNodeId(), + getNodeId: () => testNodesUtils.generateRandomNodeId(), } as KeyManager; // Cheeky mocking. sessionManager = await SessionManager.createSessionManager({ db, @@ -108,7 +111,7 @@ describe('GRPCClient', () => { keyPrivatePem: keysUtils.privateKeyToPem(clientKeyPair.privateKey), certChainPem: keysUtils.certToPem(clientCert), }, - timeout: 1000, + timer: timerStart(1000), logger, }); await client.destroy(); @@ -122,7 +125,7 @@ describe('GRPCClient', () => { keyPrivatePem: keysUtils.privateKeyToPem(clientKeyPair.privateKey), certChainPem: keysUtils.certToPem(clientCert), }, - timeout: 1000, + timer: timerStart(1000), logger, }); const m = new utilsPB.EchoMessage(); @@ -155,7 +158,7 @@ describe('GRPCClient', () => { certChainPem: keysUtils.certToPem(clientCert), }, session, - timeout: 1000, + timer: timerStart(1000), logger, }); let pCall: PromiseUnaryCall; @@ -172,7 +175,7 @@ describe('GRPCClient', () => { const m2 = new utilsPB.EchoMessage(); m2.setChallenge('error'); pCall = client.unary(m2); - await expect(pCall).rejects.toThrow(grpcErrors.ErrorGRPC); + await expectRemoteError(pCall, grpcErrors.ErrorGRPC); meta = await pCall.meta; // Expect reflected reflected session token expect(clientUtils.decodeAuthToSession(meta)).toBe( @@ -191,7 +194,7 @@ describe('GRPCClient', () => { keyPrivatePem: keysUtils.privateKeyToPem(clientKeyPair.privateKey), certChainPem: keysUtils.certToPem(clientCert), }, - timeout: 1000, + timer: timerStart(1000), logger, }); const challenge = 'f9s8d7f4'; @@ -234,7 +237,7 @@ describe('GRPCClient', () => { certChainPem: keysUtils.certToPem(clientCert), }, session, - timeout: 1000, + timer: timerStart(1000), logger, }); const challenge = 'f9s8d7f4'; @@ -259,7 +262,7 @@ describe('GRPCClient', () => { keyPrivatePem: keysUtils.privateKeyToPem(clientKeyPair.privateKey), certChainPem: keysUtils.certToPem(clientCert), }, - timeout: 1000, + timer: timerStart(1000), logger, }); const [stream, response] = client.clientStream(); @@ -297,7 +300,7 @@ describe('GRPCClient', () => { certChainPem: keysUtils.certToPem(clientCert), }, session, - timeout: 1000, + timer: timerStart(1000), logger, }); const [stream] = client.clientStream(); @@ -320,7 +323,7 @@ describe('GRPCClient', () => { keyPrivatePem: keysUtils.privateKeyToPem(clientKeyPair.privateKey), certChainPem: keysUtils.certToPem(clientCert), }, - timeout: 1000, + timer: timerStart(1000), logger, }); const stream = client.duplexStream(); @@ -355,7 +358,7 @@ describe('GRPCClient', () => { certChainPem: keysUtils.certToPem(clientCert), }, session, - timeout: 1000, + timer: timerStart(1000), logger, }); const stream = client.duplexStream(); diff --git a/tests/grpc/GRPCServer.test.ts b/tests/grpc/GRPCServer.test.ts index 2c62b70f9..83455859b 100644 --- a/tests/grpc/GRPCServer.test.ts +++ b/tests/grpc/GRPCServer.test.ts @@ -5,11 +5,13 @@ import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; -import { GRPCServer, utils as grpcUtils } from '@/grpc'; -import { KeyManager, utils as keysUtils } from '@/keys'; -import { SessionManager } from '@/sessions'; +import GRPCServer from '@/grpc/GRPCServer'; +import KeyManager from '@/keys/KeyManager'; +import SessionManager from '@/sessions/SessionManager'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as grpcErrors from '@/grpc/errors'; +import * as grpcUtils from '@/grpc/utils'; +import * as keysUtils from '@/keys/utils'; import * as clientUtils from '@/client/utils'; import * as testGrpcUtils from './utils'; import * as testUtils from '../utils'; @@ -158,6 +160,12 @@ describe('GRPCServer', () => { ); const unary = grpcUtils.promisifyUnaryCall( client, + { + nodeId: nodeIdServer, + host: server.getHost(), + port: server.getPort(), + command: 'unary', + }, client.unary, ); const m = new utilsPB.EchoMessage(); @@ -211,6 +219,12 @@ describe('GRPCServer', () => { ); const unary1 = grpcUtils.promisifyUnaryCall( client1, + { + nodeId: nodeIdServer1, + host: server.getHost(), + port: server.getPort(), + command: 'unary1', + }, client1.unary, ); const m1 = new utilsPB.EchoMessage(); @@ -248,6 +262,12 @@ describe('GRPCServer', () => { ); const unary2 = grpcUtils.promisifyUnaryCall( client2, + { + nodeId: nodeIdServer2, + host: server.getHost(), + port: server.getPort(), + command: 'unary2', + }, client2.unary, ); const m3 = new utilsPB.EchoMessage(); @@ -301,6 +321,12 @@ describe('GRPCServer', () => { ); const unary = grpcUtils.promisifyUnaryCall( client, + { + nodeId: nodeIdServer, + host: server.getHost(), + port: server.getPort(), + command: 'unary', + }, client.unaryAuthenticated, ); const m = new utilsPB.EchoMessage(); diff --git a/tests/grpc/utils.test.ts b/tests/grpc/utils.test.ts index c17f0457d..f757ee78e 100644 --- a/tests/grpc/utils.test.ts +++ b/tests/grpc/utils.test.ts @@ -1,17 +1,22 @@ import type { TestServiceClient } from '@/proto/js/polykey/v1/test_service_grpc_pb'; +import type { Host, Port } from '@/network/types'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import * as grpc from '@grpc/grpc-js'; import { getLogger } from '@grpc/grpc-js/build/src/logging'; import * as grpcUtils from '@/grpc/utils'; import * as grpcErrors from '@/grpc/errors'; +import * as errors from '@/errors'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as utils from './utils'; +import * as testUtils from '../utils'; describe('GRPC utils', () => { const logger = new Logger('GRPC utils Test', LogLevel.WARN, [ new StreamHandler(), ]); let client: TestServiceClient, server: grpc.Server, port: number; + const nodeId = testUtils.generateRandomNodeId(); + const host = '127.0.0.1' as Host; beforeAll(async () => { // Mocked authenticate always passes authentication const authenticate = async (metaClient, metaServer = new grpc.Metadata()) => @@ -40,6 +45,12 @@ describe('GRPC utils', () => { test('promisified client unary call', async () => { const unary = grpcUtils.promisifyUnaryCall( client, + { + nodeId, + host, + port: port as Port, + command: 'unary', + }, client.unary, ); const messageTo = new utilsPB.EchoMessage(); @@ -52,18 +63,29 @@ describe('GRPC utils', () => { test('promisified client unary call error', async () => { const unary = grpcUtils.promisifyUnaryCall( client, + { + nodeId, + host, + port: port as Port, + command: 'unary', + }, client.unary, ); const messageTo = new utilsPB.EchoMessage(); messageTo.setChallenge('error'); const pCall = unary(messageTo); - await expect(pCall).rejects.toThrow(grpcErrors.ErrorGRPC); + await expect(pCall).rejects.toThrow(grpcErrors.ErrorPolykeyRemote); try { await pCall; } catch (e) { // This information comes from the server expect(e.message).toBe('test error'); - expect(e.data).toMatchObject({ + expect(e.metadata.nodeId).toBe(nodeId); + expect(e.metadata.host).toBe(host); + expect(e.metadata.port).toBe(port); + expect(e.metadata.command).toBe('unary'); + expect(e.cause).toBeInstanceOf(grpcErrors.ErrorGRPC); + expect(e.cause.data).toMatchObject({ grpc: true, }); } @@ -72,6 +94,12 @@ describe('GRPC utils', () => { const serverStream = grpcUtils.promisifyReadableStreamCall( client, + { + nodeId, + host, + port: port as Port, + command: 'serverStream', + }, client.serverStream, ); const challenge = '4444'; @@ -97,13 +125,21 @@ describe('GRPC utils', () => { const serverStream = grpcUtils.promisifyReadableStreamCall( client, + { + nodeId, + host, + port: port as Port, + command: 'serverStream', + }, client.serverStream, ); const challenge = 'error'; const messageTo = new utilsPB.EchoMessage(); messageTo.setChallenge(challenge); const stream = serverStream(messageTo); - await expect(() => stream.next()).rejects.toThrow(grpcErrors.ErrorGRPC); + await expect(() => stream.next()).rejects.toThrow( + grpcErrors.ErrorPolykeyRemote, + ); // The generator will have ended // the internal stream will be automatically destroyed const result = await stream.next(); @@ -119,6 +155,12 @@ describe('GRPC utils', () => { const serverStream = grpcUtils.promisifyReadableStreamCall( client, + { + nodeId, + host, + port: port as Port, + command: 'serverStream', + }, client.serverStream, ); const challenge = '4444'; @@ -143,6 +185,12 @@ describe('GRPC utils', () => { const serverStream = grpcUtils.promisifyReadableStreamCall( client, + { + nodeId, + host, + port: port as Port, + command: 'serverStream', + }, client.serverStream, ); const challenge = '4444'; @@ -170,7 +218,16 @@ describe('GRPC utils', () => { const clientStream = grpcUtils.promisifyWritableStreamCall< utilsPB.EchoMessage, utilsPB.EchoMessage - >(client, client.clientStream); + >( + client, + { + nodeId, + host, + port: port as Port, + command: 'clientStream', + }, + client.clientStream, + ); const [genStream, response] = clientStream(); const m = new utilsPB.EchoMessage(); m.setChallenge('d89f7u983e4d'); @@ -187,7 +244,16 @@ describe('GRPC utils', () => { const clientStream = grpcUtils.promisifyWritableStreamCall< utilsPB.EchoMessage, utilsPB.EchoMessage - >(client, client.clientStream); + >( + client, + { + nodeId, + host, + port: port as Port, + command: 'clientStream', + }, + client.clientStream, + ); const [genStream, response] = clientStream(); const result1 = await genStream.next(null); // Closed stream expect(result1).toMatchObject({ @@ -208,7 +274,16 @@ describe('GRPC utils', () => { const duplexStream = grpcUtils.promisifyDuplexStreamCall< utilsPB.EchoMessage, utilsPB.EchoMessage - >(client, client.duplexStream); + >( + client, + { + nodeId, + host, + port: port as Port, + command: 'duplexStream', + }, + client.duplexStream, + ); const genDuplex = duplexStream(); const messageTo = new utilsPB.EchoMessage(); messageTo.setChallenge('d89f7u983e4d'); @@ -226,7 +301,16 @@ describe('GRPC utils', () => { const duplexStream = grpcUtils.promisifyDuplexStreamCall< utilsPB.EchoMessage, utilsPB.EchoMessage - >(client, client.duplexStream); + >( + client, + { + nodeId, + host, + port: port as Port, + command: 'duplexStream', + }, + client.duplexStream, + ); const genDuplex = duplexStream(); await genDuplex.next(null); expect(genDuplex.stream.destroyed).toBe(true); @@ -236,7 +320,16 @@ describe('GRPC utils', () => { const duplexStream = grpcUtils.promisifyDuplexStreamCall< utilsPB.EchoMessage, utilsPB.EchoMessage - >(client, client.duplexStream); + >( + client, + { + nodeId, + host, + port: port as Port, + command: 'duplexStream', + }, + client.duplexStream, + ); const genDuplex = duplexStream(); // When duplex streams are ended, reading will hang await genDuplex.write(null); @@ -249,7 +342,16 @@ describe('GRPC utils', () => { const duplexStream = grpcUtils.promisifyDuplexStreamCall< utilsPB.EchoMessage, utilsPB.EchoMessage - >(client, client.duplexStream); + >( + client, + { + nodeId, + host, + port: port as Port, + command: 'duplexStream', + }, + client.duplexStream, + ); const genDuplex = duplexStream(); await genDuplex.read(null); const messageTo = new utilsPB.EchoMessage(); @@ -269,12 +371,23 @@ describe('GRPC utils', () => { const duplexStream = grpcUtils.promisifyDuplexStreamCall< utilsPB.EchoMessage, utilsPB.EchoMessage - >(client, client.duplexStream); + >( + client, + { + nodeId, + host, + port: port as Port, + command: 'duplexStream', + }, + client.duplexStream, + ); const genDuplex = duplexStream(); const messageTo = new utilsPB.EchoMessage(); messageTo.setChallenge('error'); await genDuplex.write(messageTo); - await expect(() => genDuplex.read()).rejects.toThrow(grpcErrors.ErrorGRPC); + await expect(() => genDuplex.read()).rejects.toThrow( + grpcErrors.ErrorPolykeyRemote, + ); expect(genDuplex.stream.destroyed).toBe(true); expect(genDuplex.stream.getPeer()).toBe(`127.0.0.1:${port}`); }); @@ -282,14 +395,201 @@ describe('GRPC utils', () => { const duplexStream = grpcUtils.promisifyDuplexStreamCall< utilsPB.EchoMessage, utilsPB.EchoMessage - >(client, client.duplexStream); + >( + client, + { + nodeId, + host, + port: port as Port, + command: 'duplexStream', + }, + client.duplexStream, + ); const genDuplex = duplexStream(); const messageTo = new utilsPB.EchoMessage(); messageTo.setChallenge('error'); await expect(() => genDuplex.next(messageTo)).rejects.toThrow( - grpcErrors.ErrorGRPC, + grpcErrors.ErrorPolykeyRemote, ); expect(genDuplex.stream.destroyed).toBe(true); expect(genDuplex.stream.getPeer()).toBe(`127.0.0.1:${port}`); }); + test('serialising and deserialising Polykey errors', async () => { + const timestamp = new Date(); + const error = new errors.ErrorPolykey('test error', { + timestamp, + data: { + int: 1, + str: 'one', + }, + }); + error.exitCode = 255; + const serialised = grpcUtils.fromError(error).metadata!; + const stringifiedError = serialised.get('error')[0] as string; + const parsedError = JSON.parse(stringifiedError); + expect(parsedError).toMatchObject({ + type: 'ErrorPolykey', + data: expect.any(Object), + }); + const deserialisedError = grpcUtils.toError( + { + name: '', + message: '', + code: 2, + details: '', + metadata: serialised, + }, + { + nodeId, + host, + port: port as Port, + command: 'testCall', + }, + ); + expect(deserialisedError).toBeInstanceOf(grpcErrors.ErrorPolykeyRemote); + expect(deserialisedError.message).toBe('test error'); + // @ts-ignore - already checked above that error is ErrorPolykeyRemote + expect(deserialisedError.metadata.nodeId).toBe(nodeId); + // @ts-ignore + expect(deserialisedError.metadata.host).toBe(host); + // @ts-ignore + expect(deserialisedError.metadata.port).toBe(port); + // @ts-ignore + expect(deserialisedError.metadata.command).toBe('testCall'); + expect(deserialisedError.cause).toBeInstanceOf(errors.ErrorPolykey); + expect(deserialisedError.cause.message).toBe('test error'); + expect(deserialisedError.cause.exitCode).toBe(255); + expect(deserialisedError.cause.timestamp).toEqual(timestamp); + expect(deserialisedError.cause.data).toEqual(error.data); + expect(deserialisedError.cause.stack).toBe(error.stack); + }); + test('serialising and deserialising generic errors', async () => { + const error = new TypeError('test error'); + const serialised = grpcUtils.fromError(error).metadata!; + const stringifiedError = serialised.get('error')[0] as string; + const parsedError = JSON.parse(stringifiedError); + expect(parsedError).toMatchObject({ + type: 'TypeError', + data: expect.any(Object), + }); + const deserialisedError = grpcUtils.toError( + { + name: '', + message: '', + code: 2, + details: '', + metadata: serialised, + }, + { + nodeId, + host, + port: port as Port, + command: 'testCall', + }, + ); + expect(deserialisedError).toBeInstanceOf(grpcErrors.ErrorPolykeyRemote); + expect(deserialisedError.message).toBe('test error'); + // @ts-ignore - already checked above that error is ErrorPolykeyRemote + expect(deserialisedError.metadata.nodeId).toBe(nodeId); + // @ts-ignore + expect(deserialisedError.metadata.host).toBe(host); + // @ts-ignore + expect(deserialisedError.metadata.port).toBe(port); + // @ts-ignore + expect(deserialisedError.metadata.command).toBe('testCall'); + expect(deserialisedError.cause).toBeInstanceOf(TypeError); + expect(deserialisedError.cause.message).toBe('test error'); + expect(deserialisedError.cause.stack).toBe(error.stack); + }); + test('serialising and de-serialising non-errors', async () => { + const error = 'not an error' as unknown as Error; + const serialised = grpcUtils.fromError(error).metadata!; + const stringifiedError = serialised.get('error')[0] as string; + const parsedError = JSON.parse(stringifiedError); + expect(parsedError).toEqual('not an error'); + const deserialisedError = grpcUtils.toError( + { + name: '', + message: '', + code: 2, + details: '', + metadata: serialised, + }, + { + nodeId, + host, + port: port as Port, + command: 'testCall', + }, + ); + expect(deserialisedError).toBeInstanceOf(grpcErrors.ErrorPolykeyRemote); + // @ts-ignore - already checked above that error is ErrorPolykeyRemote + expect(deserialisedError.metadata.nodeId).toBe(nodeId); + // @ts-ignore + expect(deserialisedError.metadata.host).toBe(host); + // @ts-ignore + expect(deserialisedError.metadata.port).toBe(port); + // @ts-ignore + expect(deserialisedError.metadata.command).toBe('testCall'); + expect(deserialisedError.cause).toBeInstanceOf(errors.ErrorPolykeyUnknown); + // This is slightly brittle because it's based on what we choose to do + // with unknown data in our grpc reviver + expect(deserialisedError.cause.data.json).toEqual('not an error'); + }); + test('serialising and deserialising sensitive errors', async () => { + const timestamp = new Date(); + const error = new errors.ErrorPolykey('test error', { + timestamp, + data: { + int: 1, + str: 'one', + }, + }); + error.exitCode = 255; + const serialised = grpcUtils.fromError(error, true).metadata!; + const stringifiedError = serialised.get('error')[0] as string; + const parsedError = JSON.parse(stringifiedError); + // Stack is the only thing that should not be serialised + expect(parsedError).toEqual({ + type: 'ErrorPolykey', + data: { + description: errors.ErrorPolykey.description, + message: 'test error', + exitCode: 255, + timestamp: expect.any(String), + data: error.data, + }, + }); + const deserialisedError = grpcUtils.toError( + { + name: '', + message: '', + code: 2, + details: '', + metadata: serialised, + }, + { + nodeId, + host, + port: port as Port, + command: 'testCall', + }, + ); + expect(deserialisedError).toBeInstanceOf(grpcErrors.ErrorPolykeyRemote); + expect(deserialisedError.message).toBe('test error'); + // @ts-ignore - already checked above that error is ErrorPolykeyRemote + expect(deserialisedError.metadata.nodeId).toBe(nodeId); + // @ts-ignore + expect(deserialisedError.metadata.host).toBe(host); + // @ts-ignore + expect(deserialisedError.metadata.port).toBe(port); + // @ts-ignore + expect(deserialisedError.metadata.command).toBe('testCall'); + expect(deserialisedError.cause).toBeInstanceOf(errors.ErrorPolykey); + expect(deserialisedError.cause.message).toBe('test error'); + expect(deserialisedError.cause.exitCode).toBe(255); + expect(deserialisedError.cause.timestamp).toEqual(timestamp); + expect(deserialisedError.cause.data).toEqual(error.data); + expect(deserialisedError.cause.stack).not.toBe(error.stack); + }); }); diff --git a/tests/grpc/utils/GRPCClientTest.ts b/tests/grpc/utils/GRPCClientTest.ts index c4b55b1d1..3b2af291d 100644 --- a/tests/grpc/utils/GRPCClientTest.ts +++ b/tests/grpc/utils/GRPCClientTest.ts @@ -1,13 +1,15 @@ import type { Interceptor } from '@grpc/grpc-js'; -import type { Session } from '@/sessions'; +import type Session from '@/sessions/Session'; import type { NodeId } from '@/nodes/types'; import type { Host, Port, TLSConfig, ProxyConfig } from '@/network/types'; import type * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import type { ClientReadableStream } from '@grpc/grpc-js/build/src/call'; import type { AsyncGeneratorReadableStreamClient } from '@/grpc/types'; +import type { Timer } from '@/types'; import Logger from '@matrixai/logger'; import { CreateDestroy, ready } from '@matrixai/async-init/dist/CreateDestroy'; -import { GRPCClient, utils as grpcUtils } from '@/grpc'; +import GRPCClient from '@/grpc/GRPCClient'; +import * as grpcUtils from '@/grpc/utils'; import * as clientUtils from '@/client/utils'; import { TestServiceClient } from '@/proto/js/polykey/v1/test_service_grpc_pb'; @@ -21,7 +23,7 @@ class GRPCClientTest extends GRPCClient { tlsConfig, proxyConfig, session, - timeout = Infinity, + timer, destroyCallback, logger = new Logger(this.name), }: { @@ -31,7 +33,7 @@ class GRPCClientTest extends GRPCClient { tlsConfig?: TLSConfig; proxyConfig?: ProxyConfig; session?: Session; - timeout?: number; + timer?: Timer; destroyCallback?: () => Promise; logger?: Logger; }): Promise { @@ -47,7 +49,7 @@ class GRPCClientTest extends GRPCClient { port, tlsConfig, proxyConfig, - timeout, + timer, interceptors, logger, }); @@ -74,6 +76,12 @@ class GRPCClientTest extends GRPCClient { public unary(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.unary.name, + }, this.client.unary, )(...args); } @@ -82,6 +90,12 @@ class GRPCClientTest extends GRPCClient { public serverStream(...args) { return grpcUtils.promisifyReadableStreamCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.serverStream.name, + }, this.client.serverStream, )(...args); } @@ -93,6 +107,12 @@ class GRPCClientTest extends GRPCClient { utilsPB.EchoMessage >( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.clientStream.name, + }, this.client.clientStream, )(...args); } @@ -104,6 +124,12 @@ class GRPCClientTest extends GRPCClient { utilsPB.EchoMessage >( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.duplexStream.name, + }, this.client.duplexStream, )(...args); } @@ -112,6 +138,12 @@ class GRPCClientTest extends GRPCClient { public unaryAuthenticated(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.unaryAuthenticated.name, + }, this.client.unaryAuthenticated, )(...args); } @@ -125,6 +157,12 @@ class GRPCClientTest extends GRPCClient { > { return grpcUtils.promisifyReadableStreamCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.serverStreamFail.name, + }, this.client.serverStreamFail, )(...args); } @@ -133,6 +171,12 @@ class GRPCClientTest extends GRPCClient { public unaryFail(...args) { return grpcUtils.promisifyUnaryCall( this.client, + { + nodeId: this.nodeId, + host: this.host, + port: this.port, + command: this.unaryFail.name, + }, this.client.unaryFail, )(...args); } diff --git a/tests/grpc/utils/testService.ts b/tests/grpc/utils/testService.ts index 5c3356d7f..bec280ffc 100644 --- a/tests/grpc/utils/testService.ts +++ b/tests/grpc/utils/testService.ts @@ -7,12 +7,15 @@ import type { Authenticate } from '@/client/types'; import type { SessionToken } from '@/sessions/types'; import type { ITestServiceServer } from '@/proto/js/polykey/v1/test_service_grpc_pb'; +import type { Host, Port } from '@/network/types'; import Logger from '@matrixai/logger'; import * as grpc from '@grpc/grpc-js'; -import { utils as grpcUtils, errors as grpcErrors } from '@/grpc'; +import * as grpcUtils from '@/grpc/utils'; +import * as grpcErrors from '@/grpc/errors'; import * as clientUtils from '@/client/utils'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import { sleep } from '@/utils'; +import * as testUtils from '../../utils'; function createTestService({ authenticate, @@ -21,6 +24,9 @@ function createTestService({ authenticate: Authenticate; logger?: Logger; }) { + const nodeId = testUtils.generateRandomNodeId(); + const host = '127.0.0.1' as Host; + const port = 0 as Port; const testService: ITestServiceServer = { unary: async ( call: grpc.ServerUnaryCall, @@ -43,7 +49,7 @@ function createTestService({ // we'll send back an error callback( grpcUtils.fromError( - new grpcErrors.ErrorGRPC('test error', { grpc: true }), + new grpcErrors.ErrorGRPC('test error', { data: { grpc: true } }), ), ); } else { @@ -67,13 +73,13 @@ function createTestService({ ); call.sendMetadata(meta); } - const genWritable = grpcUtils.generatorWritable(call); + const genWritable = grpcUtils.generatorWritable(call, false); const messageFrom = call.request; const messageTo = new utilsPB.EchoMessage(); const challenge = messageFrom.getChallenge(); if (challenge === 'error') { await genWritable.throw( - new grpcErrors.ErrorGRPC('test error', { grpc: true }), + new grpcErrors.ErrorGRPC('test error', { data: { grpc: true } }), ); } else { // Will send back a number of message @@ -101,8 +107,15 @@ function createTestService({ ); call.sendMetadata(meta); } - const genReadable = - grpcUtils.generatorReadable(call); + const genReadable = grpcUtils.generatorReadable( + call, + { + nodeId, + host, + port, + command: 'genReadable', + }, + ); let data = ''; try { for await (const m of genReadable) { @@ -131,7 +144,16 @@ function createTestService({ ); call.sendMetadata(meta); } - const genDuplex = grpcUtils.generatorDuplex(call); + const genDuplex = grpcUtils.generatorDuplex( + call, + { + nodeId, + host, + port, + command: 'genDuplex', + }, + false, + ); const readStatus = await genDuplex.read(); // If nothing to read, end and destroy if (readStatus.done) { @@ -143,7 +165,7 @@ function createTestService({ const incomingMessage = readStatus.value; if (incomingMessage.getChallenge() === 'error') { await genDuplex.throw( - new grpcErrors.ErrorGRPC('test error', { grpc: true }), + new grpcErrors.ErrorGRPC('test error', { data: { grpc: true } }), ); } else { const outgoingMessage = new utilsPB.EchoMessage(); @@ -169,7 +191,7 @@ function createTestService({ // we'll send back an error callback( grpcUtils.fromError( - new grpcErrors.ErrorGRPC('test error', { grpc: true }), + new grpcErrors.ErrorGRPC('test error', { data: { grpc: true } }), ), ); } else { @@ -182,7 +204,7 @@ function createTestService({ serverStreamFail: async ( call: grpc.ServerWritableStream, ): Promise => { - const genWritable = grpcUtils.generatorWritable(call); + const genWritable = grpcUtils.generatorWritable(call, false); try { const echoMessage = new utilsPB.EchoMessage().setChallenge('Hello!'); for (let i = 0; i < 10; i++) { diff --git a/tests/identities/IdentitiesManager.test.ts b/tests/identities/IdentitiesManager.test.ts index b7ca969b0..23000440b 100644 --- a/tests/identities/IdentitiesManager.test.ts +++ b/tests/identities/IdentitiesManager.test.ts @@ -17,7 +17,7 @@ import * as identitiesErrors from '@/identities/errors'; import * as keysUtils from '@/keys/utils'; import * as nodesUtils from '@/nodes/utils'; import TestProvider from './TestProvider'; -import * as testUtils from '../utils'; +import * as testNodesUtils from '../nodes/utils'; describe('IdentitiesManager', () => { const logger = new Logger('IdentitiesManager Test', LogLevel.WARN, [ @@ -219,7 +219,7 @@ describe('IdentitiesManager', () => { expect(identityDatas).toHaveLength(1); expect(identityDatas).not.toContainEqual(identityData); // Now publish a claim - const nodeIdSome = testUtils.generateRandomNodeId(); + const nodeIdSome = testNodesUtils.generateRandomNodeId(); const nodeIdSomeEncoded = nodesUtils.encodeNodeId(nodeIdSome); const signatures: Record = {}; signatures[nodeIdSome] = { diff --git a/tests/keys/KeyManager.test.ts b/tests/keys/KeyManager.test.ts index 773b5d3eb..c1aaa345e 100644 --- a/tests/keys/KeyManager.test.ts +++ b/tests/keys/KeyManager.test.ts @@ -88,9 +88,9 @@ describe('KeyManager', () => { expect(keysPathContents).toContain('root_certs'); expect(keysPathContents).toContain('db.key'); expect(keyManager.dbKey.toString()).toBeTruthy(); - const rootKeyPairPem = await keyManager.getRootKeyPairPem(); + const rootKeyPairPem = keyManager.getRootKeyPairPem(); expect(rootKeyPairPem).not.toBeUndefined(); - const rootCertPem = await keyManager.getRootCertPem(); + const rootCertPem = keyManager.getRootCertPem(); expect(rootCertPem).not.toBeUndefined(); const rootCertPems = await keyManager.getRootCertChainPems(); expect(rootCertPems.length).toBe(1); @@ -315,7 +315,7 @@ describe('KeyManager', () => { const rootKeyPair1 = keyManager.getRootKeyPair(); const rootCert1 = keyManager.getRootCert(); await sleep(2000); // Let's just make sure there is time diff - await db.put(['test'], 'hello', 'world'); + await db.put(['test', 'hello'], 'world'); // Reset root key pair takes time await keyManager.resetRootKeyPair('password'); expect(keyManager.getRecoveryCode()).toBeDefined(); @@ -339,7 +339,7 @@ describe('KeyManager', () => { ); await db.stop(); await db.start(); - expect(await db.get(['test'], 'hello')).toBe('world'); + expect(await db.get(['test', 'hello'])).toBe('world'); await keyManager.stop(); }); test('can renew root key pair', async () => { @@ -364,7 +364,7 @@ describe('KeyManager', () => { const rootKeyPair1 = keyManager.getRootKeyPair(); const rootCert1 = keyManager.getRootCert(); await sleep(2000); // Let's just make sure there is time diff - await db.put(['test'], 'hello', 'world'); + await db.put(['test', 'hello'], 'world'); await keyManager.renewRootKeyPair('newpassword'); expect(keyManager.getRecoveryCode()).toBeDefined(); const rootKeyPair2 = keyManager.getRootKeyPair(); @@ -393,7 +393,7 @@ describe('KeyManager', () => { expect(keysUtils.certVerified(rootCert1, rootCert2)).toBe(true); await db.stop(); await db.start(); - expect(await db.get(['test'], 'hello')).toBe('world'); + expect(await db.get(['test', 'hello'])).toBe('world'); await keyManager.stop(); }); test('order of certificate chain should be leaf to root', async () => { diff --git a/tests/nat/DMZ.test.ts b/tests/nat/DMZ.test.ts new file mode 100644 index 000000000..ae54d2d15 --- /dev/null +++ b/tests/nat/DMZ.test.ts @@ -0,0 +1,280 @@ +import os from 'os'; +import path from 'path'; +import fs from 'fs'; +import readline from 'readline'; +import process from 'process'; +import shell from 'shelljs'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import Status from '@/status/Status'; +import config from '@/config'; +import * as testNatUtils from './utils'; +import { describeIf } from '../utils'; +import * as testBinUtils from '../bin/utils'; + +describeIf( + process.platform === 'linux' && + shell.which('ip') && + shell.which('iptables') && + shell.which('nsenter') && + shell.which('unshare'), + 'DMZ', + () => { + const logger = new Logger('DMZ test', LogLevel.WARN, [new StreamHandler()]); + let dataDir: string; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + }); + afterEach(async () => { + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test( + 'can create an agent in a namespace', + async () => { + const password = 'abc123'; + const usrns = testNatUtils.createUserNamespace(logger); + const netns = testNatUtils.createNetworkNamespace(usrns.pid!, logger); + const agentProcess = await testNatUtils.pkSpawnNs( + usrns.pid!, + netns.pid!, + [ + 'agent', + 'start', + '--node-path', + path.join(dataDir, 'polykey'), + '--root-key-pair-bits', + '1024', + '--client-host', + '127.0.0.1', + '--proxy-host', + '127.0.0.1', + '--workers', + '0', + '--verbose', + '--format', + 'json', + ], + { + PK_PASSWORD: password, + }, + dataDir, + logger.getChild('agentProcess'), + ); + const rlOut = readline.createInterface(agentProcess.stdout!); + const stdout = await new Promise((resolve, reject) => { + rlOut.once('line', resolve); + rlOut.once('close', reject); + }); + const statusLiveData = JSON.parse(stdout); + expect(statusLiveData).toMatchObject({ + pid: agentProcess.pid, + nodeId: expect.any(String), + clientHost: expect.any(String), + clientPort: expect.any(Number), + agentHost: expect.any(String), + agentPort: expect.any(Number), + forwardHost: expect.any(String), + forwardPort: expect.any(Number), + proxyHost: expect.any(String), + proxyPort: expect.any(Number), + recoveryCode: expect.any(String), + }); + expect( + statusLiveData.recoveryCode.split(' ').length === 12 || + statusLiveData.recoveryCode.split(' ').length === 24, + ).toBe(true); + agentProcess.kill('SIGTERM'); + let exitCode, signal; + [exitCode, signal] = await testBinUtils.processExit(agentProcess); + expect(exitCode).toBe(null); + expect(signal).toBe('SIGTERM'); + // Check for graceful exit + const status = new Status({ + statusPath: path.join(dataDir, 'polykey', config.defaults.statusBase), + statusLockPath: path.join( + dataDir, + 'polykey', + config.defaults.statusLockBase, + ), + fs, + logger, + }); + const statusInfo = (await status.readStatus())!; + expect(statusInfo.status).toBe('DEAD'); + netns.kill('SIGTERM'); + [exitCode, signal] = await testBinUtils.processExit(netns); + expect(exitCode).toBe(null); + expect(signal).toBe('SIGTERM'); + usrns.kill('SIGTERM'); + [exitCode, signal] = await testBinUtils.processExit(usrns); + expect(exitCode).toBe(null); + expect(signal).toBe('SIGTERM'); + }, + global.defaultTimeout * 2, + ); + test( + 'agents in different namespaces can ping each other', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent1Host, + agent1ProxyPort, + agent2NodeId, + agent2Host, + agent2ProxyPort, + tearDownNAT, + } = await testNatUtils.setupNAT('dmz', 'dmz', logger); + // Namespace1 Namespace2 + // ┌────────────────────────────────────┐ ┌────────────────────────────────────┐ + // │ │ │ │ + // │ ┌────────┐ ┌─────────┐ │ │ ┌─────────┐ ┌────────┐ │ + // │ │ Agent1 ├────────┤ Router1 │ │ │ │ Router2 ├────────┤ Agent2 │ │ + // │ └────────┘ └─────────┘ │ │ └─────────┘ └────────┘ │ + // │ 10.0.0.2:55551 192.168.0.1:55555 │ │ 192.168.0.2:55555 10.0.0.2:55552 │ + // │ │ │ │ + // └────────────────────────────────────┘ └────────────────────────────────────┘ + // Since neither node is behind a NAT can directly add eachother's + // details using pk nodes add + await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + [ + 'nodes', + 'add', + agent2NodeId, + agent2Host, + agent2ProxyPort, + '--no-ping', + ], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + [ + 'nodes', + 'add', + agent1NodeId, + agent1Host, + agent1ProxyPort, + '--no-ping', + ], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'ping', agent1NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + test( + 'agents in different namespaces can ping each other via seed node', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent2NodeId, + tearDownNAT, + } = await testNatUtils.setupNATWithSeedNode('dmz', 'dmz', logger); + // Namespace1 Namespace3 Namespace2 + // ┌────────────────────────────────────┐ ┌──────────────────┐ ┌────────────────────────────────────┐ + // │ │ │ │ │ │ + // │ ┌────────┐ ┌─────────┐ │ │ ┌──────────┐ │ │ ┌─────────┐ ┌────────┐ │ + // │ │ Agent1 ├────────┤ Router1 │ │ │ │ SeedNode │ │ │ │ Router2 ├────────┤ Agent2 │ │ + // │ └────────┘ └─────────┘ │ │ └──────────┘ │ │ └─────────┘ └────────┘ │ + // │ 10.0.0.2:55551 192.168.0.1:55555 │ │ 192.168.0.3:PORT │ │ 192.168.0.2:55555 10.0.0.2:55552 │ + // │ │ │ │ │ │ + // └────────────────────────────────────┘ └──────────────────┘ └────────────────────────────────────┘ + // Should be able to ping straight away using the details from the + // seed node + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'ping', agent1NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + }, +); diff --git a/tests/nat/endpointDependentNAT.test.ts b/tests/nat/endpointDependentNAT.test.ts new file mode 100644 index 000000000..663293f4a --- /dev/null +++ b/tests/nat/endpointDependentNAT.test.ts @@ -0,0 +1,261 @@ +import os from 'os'; +import path from 'path'; +import fs from 'fs'; +import process from 'process'; +import shell from 'shelljs'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import * as testNatUtils from './utils'; +import { describeIf } from '../utils'; + +describeIf( + process.platform === 'linux' && + shell.which('ip') && + shell.which('iptables') && + shell.which('nsenter') && + shell.which('unshare'), + 'endpoint dependent NAT traversal', + () => { + const logger = new Logger('EDM NAT test', LogLevel.WARN, [ + new StreamHandler(), + ]); + let dataDir: string; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + }); + afterEach(async () => { + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test( + 'node1 behind EDM NAT connects to node2', + async () => { + const { + userPid, + agent1Pid, + password, + dataDir, + agent1NodePath, + agent2NodeId, + agent2Host, + agent2ProxyPort, + tearDownNAT, + } = await testNatUtils.setupNAT('edm', 'dmz', logger); + // Since node2 is not behind a NAT can directly add its details + await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + [ + 'nodes', + 'add', + agent2NodeId, + agent2Host, + agent2ProxyPort, + '--no-ping', + ], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + const { exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + test( + 'node1 connects to node2 behind EDM NAT', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent1Host, + agent1ProxyPort, + agent2NodeId, + tearDownNAT, + } = await testNatUtils.setupNAT('dmz', 'edm', logger); + // Agent 2 must ping Agent 1 first, since Agent 2 is behind a NAT + await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + [ + 'nodes', + 'add', + agent1NodeId, + agent1Host, + agent1ProxyPort, + '--no-ping', + ], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'ping', agent1NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + // Can now ping Agent 2 (it will be expecting a response) + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + test( + 'node1 behind EDM NAT cannot connect to node2 behind EDM NAT', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent2NodeId, + tearDownNAT, + } = await testNatUtils.setupNATWithSeedNode('edm', 'edm', logger); + // Contact details are retrieved from the seed node, but cannot be used + // since port mapping changes between targets in EDM mapping + // Node 2 -> Node 1 ping should fail (Node 1 behind NAT) + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'ping', agent1NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: `Failed to resolve node ID ${agent1NodeId} to an address.`, + }); + // Node 1 -> Node 2 ping should also fail for the same reason + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: `Failed to resolve node ID ${agent2NodeId} to an address.`, + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + test( + 'node1 behind EDM NAT cannot connect to node2 behind EIM NAT', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent2NodeId, + tearDownNAT, + } = await testNatUtils.setupNATWithSeedNode('edm', 'eim', logger); + // Since one of the nodes uses EDM NAT we cannot punch through + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'ping', agent1NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: `Failed to resolve node ID ${agent1NodeId} to an address.`, + }); + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: `Failed to resolve node ID ${agent2NodeId} to an address.`, + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + }, +); diff --git a/tests/nat/endpointIndependentNAT.test.ts b/tests/nat/endpointIndependentNAT.test.ts new file mode 100644 index 000000000..9bdbf2abd --- /dev/null +++ b/tests/nat/endpointIndependentNAT.test.ts @@ -0,0 +1,400 @@ +import os from 'os'; +import path from 'path'; +import fs from 'fs'; +import process from 'process'; +import shell from 'shelljs'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import * as testNatUtils from './utils'; +import { describeIf } from '../utils'; + +describeIf( + process.platform === 'linux' && + shell.which('ip') && + shell.which('iptables') && + shell.which('nsenter') && + shell.which('unshare'), + 'endpoint independent NAT traversal', + () => { + const logger = new Logger('EIM NAT test', LogLevel.WARN, [ + new StreamHandler(), + ]); + let dataDir: string; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + }); + afterEach(async () => { + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test( + 'node1 behind EIM NAT connects to node2', + async () => { + const { + userPid, + agent1Pid, + password, + dataDir, + agent1NodePath, + agent2NodeId, + agent2Host, + agent2ProxyPort, + tearDownNAT, + } = await testNatUtils.setupNAT('eim', 'dmz', logger); + // Since node2 is not behind a NAT can directly add its details + await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + [ + 'nodes', + 'add', + agent2NodeId, + agent2Host, + agent2ProxyPort, + '--no-ping', + ], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + const { exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + test( + 'node1 connects to node2 behind EIM NAT', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent1Host, + agent1ProxyPort, + agent2NodeId, + agent2Host, + agent2ProxyPort, + tearDownNAT, + } = await testNatUtils.setupNAT('dmz', 'eim', logger); + await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + [ + 'nodes', + 'add', + agent1NodeId, + agent1Host, + agent1ProxyPort, + '--no-ping', + ], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + [ + 'nodes', + 'add', + agent2NodeId, + agent2Host, + agent2ProxyPort, + '--no-ping', + ], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + // If we try to ping Agent 2 it will fail + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: 'No response received', + }); + // But Agent 2 can ping Agent 1 because Agent 1 is not behind a NAT + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'ping', agent1NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + // Can now ping Agent 2 (it will be expecting a response) + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + test( + 'node1 behind EIM NAT connects to node2 behind EIM NAT', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent1Host, + agent1ProxyPort, + agent2NodeId, + agent2Host, + agent2ProxyPort, + tearDownNAT, + } = await testNatUtils.setupNAT('dmz', 'eim', logger); + await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + [ + 'nodes', + 'add', + agent1NodeId, + agent1Host, + agent1ProxyPort, + '--no-ping', + ], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + [ + 'nodes', + 'add', + agent2NodeId, + agent2Host, + agent2ProxyPort, + '--no-ping', + ], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + // If we try to ping Agent 2 it will fail + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: 'No response received', + }); + // But Agent 2 can ping Agent 1 because it's expecting a response now + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'ping', agent1NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + // Can now ping Agent 2 (it will be expecting a response too) + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + test( + 'node1 behind EIM NAT connects to node2 behind EIM NAT via seed node', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent2NodeId, + tearDownNAT, + } = await testNatUtils.setupNATWithSeedNode('eim', 'eim', logger); + // Should be able to ping straight away using the seed node as a + // signaller + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'ping', agent1NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + test( + 'node1 behind EIM NAT cannot connect to node2 behind EDM NAT', + async () => { + const { + userPid, + agent1Pid, + agent2Pid, + password, + dataDir, + agent1NodePath, + agent2NodePath, + agent1NodeId, + agent2NodeId, + tearDownNAT, + } = await testNatUtils.setupNATWithSeedNode('eim', 'edm', logger); + // Since one of the nodes uses EDM NAT we cannot punch through + let exitCode, stdout; + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent2Pid!, + ['nodes', 'ping', agent1NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent2NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: `Failed to resolve node ID ${agent1NodeId} to an address.`, + }); + ({ exitCode, stdout } = await testNatUtils.pkExecNs( + userPid!, + agent1Pid!, + ['nodes', 'ping', agent2NodeId, '--format', 'json'], + { + PK_NODE_PATH: agent1NodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: `Failed to resolve node ID ${agent2NodeId} to an address.`, + }); + await tearDownNAT(); + }, + global.defaultTimeout * 2, + ); + }, +); diff --git a/tests/nat/utils.ts b/tests/nat/utils.ts new file mode 100644 index 000000000..4509ebacc --- /dev/null +++ b/tests/nat/utils.ts @@ -0,0 +1,1546 @@ +import type { ChildProcess } from 'child_process'; +import os from 'os'; +import fs from 'fs'; +import path from 'path'; +import process from 'process'; +import child_process from 'child_process'; +import readline from 'readline'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import * as testBinUtils from '../bin/utils'; + +type NATType = 'eim' | 'edm' | 'dmz'; + +/** + * Veth end for Agent 1 + * Connects to Router 1 + */ +const AGENT1_VETH = 'agent1'; +/** + * Veth end for Agent 2 + * Connects to Router 2 + */ +const AGENT2_VETH = 'agent2'; +/** + * Internal veth end for Router 1 + * Connects to Agent 1 + */ +const ROUTER1_VETH_INT = 'router1-int'; +/** + * External veth end for Router 1 + * Connects to Router 2 + */ +const ROUTER1_VETH_EXT = 'router1-ext'; +/** + * Internal veth end for Router 2 + * Connects to Agent 2 + */ +const ROUTER2_VETH_INT = 'router2-int'; +/** + * External veth end for Router 2 + * Connects to Router 1 + */ +const ROUTER2_VETH_EXT = 'router2-ext'; +/** + * External veth end for Router 1 + * Connects to a seed node + */ +const ROUTER1_VETH_SEED = 'router1-seed'; +/** + * External veth end for Router 2 + * Connects to a seed node + */ +const ROUTER2_VETH_SEED = 'router2-seed'; +/** + * Veth end for a seed node + * Connects to Router 1 + */ +const SEED_VETH_ROUTER1 = 'seed-router1'; +/** + * Veth end for a seed node + * Connects to Router 2 + */ +const SEED_VETH_ROUTER2 = 'seed-router2'; + +/** + * Subnet for Agent 1 + */ +const AGENT1_HOST = '10.0.0.2'; +/** + * Subnet for Agent 2 + */ +const AGENT2_HOST = '10.0.0.2'; +/** + * Subnet for internal communication from Router 1 + * Forwards to Agent 1 + */ +const ROUTER1_HOST_INT = '10.0.0.1'; +/** + * Subnet for internal communication from Router 2 + * Forwards to Agent 2 + */ +const ROUTER2_HOST_INT = '10.0.0.1'; +/** + * Subnet for external communication from Router 1 + * Forwards to Router 2 + */ +const ROUTER1_HOST_EXT = '192.168.0.1'; +/** + * Subnet for external communication from Router 2 + * Forwards to Router 1 + */ +const ROUTER2_HOST_EXT = '192.168.0.2'; +/** + * Subnet for external communication from Router 1 + * Forwards to a seed node + */ +const ROUTER1_HOST_SEED = '192.168.0.1'; +/** + * Subnet for external communication from a seed node + */ +const SEED_HOST = '192.168.0.3'; +/** + * Subnet for external communication from Router 2 + * Forwards to a seed node + */ +const ROUTER2_HOST_SEED = '192.168.0.2'; + +/** + * Subnet mask + */ +const SUBNET_MASK = '/24'; + +/** + * Port on Agent 1 + */ +const AGENT1_PORT = '55551'; +/** + * Port on Agent 2 + */ +const AGENT2_PORT = '55552'; +/** + * Mapped port for DMZ + */ +const DMZ_PORT = '55555'; + +/** + * Formats the command to enter a namespace to run a process inside it + */ +const nsenter = (usrnsPid: number, netnsPid: number) => { + return [ + '--target', + usrnsPid.toString(), + '--user', + '--preserve-credentials', + 'nsenter', + '--target', + netnsPid.toString(), + '--net', + ]; +}; + +/** + * Create a user namespace from which network namespaces can be created without + * requiring sudo + */ +function createUserNamespace( + logger: Logger = new Logger(createUserNamespace.name), +): ChildProcess { + logger.info('unshare --user --map-root-user'); + const subprocess = child_process.spawn( + 'unshare', + ['--user', '--map-root-user'], + { + shell: true, + }, + ); + const rlErr = readline.createInterface(subprocess.stderr!); + rlErr.on('line', (l) => { + // The readline library will trim newlines + logger.info(l); + }); + return subprocess; +} + +/** + * Create a network namespace inside a user namespace + */ +function createNetworkNamespace( + usrnsPid: number, + logger: Logger = new Logger(createNetworkNamespace.name), +): ChildProcess { + logger.info( + `nsenter --target ${usrnsPid.toString()} --user --preserve-credentials unshare --net`, + ); + const subprocess = child_process.spawn( + 'nsenter', + [ + '--target', + usrnsPid.toString(), + '--user', + '--preserve-credentials', + 'unshare', + '--net', + ], + { shell: true }, + ); + const rlErr = readline.createInterface(subprocess.stderr!); + rlErr.on('line', (l) => { + // The readline library will trim newlines + logger.info(l); + }); + return subprocess; +} + +/** + * Set up four network namespaces to allow communication between two agents + * each behind a router + * Brings up loopback interfaces, creates and brings up a veth pair + * between each pair of adjacent namespaces, and adds default routing to allow + * cross-communication + */ +async function setupNetworkNamespaceInterfaces( + usrnsPid: number, + agent1NetnsPid: number, + router1NetnsPid: number, + router2NetnsPid: number, + agent2NetnsPid: number, + logger: Logger = new Logger(setupNetworkNamespaceInterfaces.name), +) { + let args: Array = []; + try { + // Bring up loopback + args = [ + ...nsenter(usrnsPid, agent1NetnsPid), + 'ip', + 'link', + 'set', + 'lo', + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'set', + 'lo', + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'set', + 'lo', + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, agent2NetnsPid), + 'ip', + 'link', + 'set', + 'lo', + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Create veth pair to link the namespaces + args = [ + ...nsenter(usrnsPid, agent1NetnsPid), + 'ip', + 'link', + 'add', + AGENT1_VETH, + 'type', + 'veth', + 'peer', + 'name', + ROUTER1_VETH_INT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'add', + ROUTER1_VETH_EXT, + 'type', + 'veth', + 'peer', + 'name', + ROUTER2_VETH_EXT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'add', + ROUTER2_VETH_INT, + 'type', + 'veth', + 'peer', + 'name', + AGENT2_VETH, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Link up the ends to the correct namespaces + args = [ + ...nsenter(usrnsPid, agent1NetnsPid), + 'ip', + 'link', + 'set', + 'dev', + ROUTER1_VETH_INT, + 'netns', + router1NetnsPid.toString(), + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'set', + 'dev', + ROUTER2_VETH_EXT, + 'netns', + router2NetnsPid.toString(), + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'set', + 'dev', + AGENT2_VETH, + 'netns', + agent2NetnsPid.toString(), + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Bring up each end + args = [ + ...nsenter(usrnsPid, agent1NetnsPid), + 'ip', + 'link', + 'set', + AGENT1_VETH, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'set', + ROUTER1_VETH_INT, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'set', + ROUTER1_VETH_EXT, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'set', + ROUTER2_VETH_EXT, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'set', + ROUTER2_VETH_INT, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, agent2NetnsPid), + 'ip', + 'link', + 'set', + AGENT2_VETH, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Assign ip addresses to each end + args = [ + ...nsenter(usrnsPid, agent1NetnsPid), + 'ip', + 'addr', + 'add', + `${AGENT1_HOST}${SUBNET_MASK}`, + 'dev', + AGENT1_VETH, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'addr', + 'add', + `${ROUTER1_HOST_INT}${SUBNET_MASK}`, + 'dev', + ROUTER1_VETH_INT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'addr', + 'add', + `${ROUTER1_HOST_EXT}${SUBNET_MASK}`, + 'dev', + ROUTER1_VETH_EXT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'addr', + 'add', + `${ROUTER2_HOST_EXT}${SUBNET_MASK}`, + 'dev', + ROUTER2_VETH_EXT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'addr', + 'add', + `${ROUTER2_HOST_INT}${SUBNET_MASK}`, + 'dev', + ROUTER2_VETH_INT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, agent2NetnsPid), + 'ip', + 'addr', + 'add', + `${AGENT2_HOST}${SUBNET_MASK}`, + 'dev', + AGENT2_VETH, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Add default routing + args = [ + ...nsenter(usrnsPid, agent1NetnsPid), + 'ip', + 'route', + 'add', + 'default', + 'via', + ROUTER1_HOST_INT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'route', + 'add', + 'default', + 'via', + ROUTER2_HOST_EXT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'route', + 'add', + 'default', + 'via', + ROUTER1_HOST_EXT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, agent2NetnsPid), + 'ip', + 'route', + 'add', + 'default', + 'via', + ROUTER2_HOST_INT, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + } catch (e) { + logger.error(e.message); + } +} + +/** + * Set up four network namespaces to allow communication between two agents + * each behind a router + * Brings up loopback interfaces, creates and brings up a veth pair + * between each pair of adjacent namespaces, and adds default routing to allow + * cross-communication + */ +async function setupSeedNamespaceInterfaces( + usrnsPid: number, + seedNetnsPid: number, + router1NetnsPid: number, + router2NetnsPid: number, + logger: Logger = new Logger(setupSeedNamespaceInterfaces.name), +) { + let args: Array = []; + try { + // Bring up loopback + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'link', + 'set', + 'lo', + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Create veth pairs to link the namespaces + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'add', + ROUTER1_VETH_SEED, + 'type', + 'veth', + 'peer', + 'name', + SEED_VETH_ROUTER1, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'add', + ROUTER2_VETH_SEED, + 'type', + 'veth', + 'peer', + 'name', + SEED_VETH_ROUTER2, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Move seed ends into seed network namespace + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'set', + 'dev', + SEED_VETH_ROUTER1, + 'netns', + seedNetnsPid.toString(), + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'set', + 'dev', + SEED_VETH_ROUTER2, + 'netns', + seedNetnsPid.toString(), + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Bring up each end + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'link', + 'set', + ROUTER1_VETH_SEED, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'link', + 'set', + SEED_VETH_ROUTER1, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'link', + 'set', + SEED_VETH_ROUTER2, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'link', + 'set', + ROUTER2_VETH_SEED, + 'up', + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Assign ip addresses to each end + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'addr', + 'add', + `${ROUTER1_HOST_SEED}${SUBNET_MASK}`, + 'dev', + ROUTER1_VETH_SEED, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'addr', + 'add', + `${SEED_HOST}${SUBNET_MASK}`, + 'dev', + SEED_VETH_ROUTER1, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'addr', + 'add', + `${SEED_HOST}${SUBNET_MASK}`, + 'dev', + SEED_VETH_ROUTER2, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'addr', + 'add', + `${ROUTER2_HOST_SEED}${SUBNET_MASK}`, + 'dev', + ROUTER2_VETH_SEED, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + // Add default routing + args = [ + ...nsenter(usrnsPid, router1NetnsPid), + 'ip', + 'route', + 'add', + SEED_HOST, + 'dev', + ROUTER1_VETH_SEED, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, router2NetnsPid), + 'ip', + 'route', + 'add', + SEED_HOST, + 'dev', + ROUTER2_VETH_SEED, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'route', + 'add', + ROUTER1_HOST_SEED, + 'dev', + SEED_VETH_ROUTER1, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + args = [ + ...nsenter(usrnsPid, seedNetnsPid), + 'ip', + 'route', + 'add', + ROUTER2_HOST_SEED, + 'dev', + SEED_VETH_ROUTER2, + ]; + logger.info(['nsenter', ...args].join(' ')); + await testBinUtils.exec('nsenter', args); + } catch (e) { + logger.error(e.message); + } +} + +/** + * Runs pk command through subprocess inside a network namespace + * This is used when a subprocess functionality needs to be used + * This is intended for terminating subprocesses + * Both stdout and stderr are the entire output including newlines + * @param env Augments env for command execution + * @param cwd Defaults to temporary directory + */ +async function pkExecNs( + usrnsPid: number, + netnsPid: number, + args: Array = [], + env: Record = {}, + cwd?: string, +): Promise<{ + exitCode: number; + stdout: string; + stderr: string; +}> { + cwd = + cwd ?? (await fs.promises.mkdtemp(path.join(os.tmpdir(), 'polykey-test-'))); + env = { + ...process.env, + ...env, + }; + // Recall that we attempt to connect to all specified seed nodes on agent start. + // Therefore, for testing purposes only, we default the seed nodes as empty + // (if not defined in the env) to ensure no attempted connections. A regular + // PolykeyAgent is expected to initially connect to the mainnet seed nodes + env['PK_SEED_NODES'] = env['PK_SEED_NODES'] ?? ''; + const tsConfigPath = path.resolve( + path.join(global.projectDir, 'tsconfig.json'), + ); + const tsConfigPathsRegisterPath = path.resolve( + path.join(global.projectDir, 'node_modules/tsconfig-paths/register'), + ); + const polykeyPath = path.resolve( + path.join(global.projectDir, 'src/bin/polykey.ts'), + ); + return new Promise((resolve, reject) => { + child_process.execFile( + 'nsenter', + [ + ...nsenter(usrnsPid, netnsPid), + 'ts-node', + '--project', + tsConfigPath, + '--require', + tsConfigPathsRegisterPath, + '--compiler', + 'typescript-cached-transpile', + '--transpile-only', + polykeyPath, + ...args, + ], + { + env, + cwd, + windowsHide: true, + }, + (error, stdout, stderr) => { + if (error != null && error.code === undefined) { + // This can only happen when the command is killed + return reject(error); + } else { + // Success and Unsuccessful exits are valid here + return resolve({ + exitCode: error && error.code != null ? error.code : 0, + stdout, + stderr, + }); + } + }, + ); + }); +} + +/** + * Launch pk command through subprocess inside a network namespace + * This is used when a subprocess functionality needs to be used + * This is intended for non-terminating subprocesses + * @param env Augments env for command execution + * @param cwd Defaults to temporary directory + */ +async function pkSpawnNs( + usrnsPid: number, + netnsPid: number, + args: Array = [], + env: Record = {}, + cwd?: string, + logger: Logger = new Logger(pkSpawnNs.name), +): Promise { + cwd = + cwd ?? (await fs.promises.mkdtemp(path.join(os.tmpdir(), 'polykey-test-'))); + env = { + ...process.env, + ...env, + }; + // Recall that we attempt to connect to all specified seed nodes on agent start. + // Therefore, for testing purposes only, we default the seed nodes as empty + // (if not defined in the env) to ensure no attempted connections. A regular + // PolykeyAgent is expected to initially connect to the mainnet seed nodes + env['PK_SEED_NODES'] = env['PK_SEED_NODES'] ?? ''; + const tsConfigPath = path.resolve( + path.join(global.projectDir, 'tsconfig.json'), + ); + const tsConfigPathsRegisterPath = path.resolve( + path.join(global.projectDir, 'node_modules/tsconfig-paths/register'), + ); + const polykeyPath = path.resolve( + path.join(global.projectDir, 'src/bin/polykey.ts'), + ); + const subprocess = child_process.spawn( + 'nsenter', + [ + ...nsenter(usrnsPid, netnsPid), + 'ts-node', + '--project', + tsConfigPath, + '--require', + tsConfigPathsRegisterPath, + '--compiler', + 'typescript-cached-transpile', + '--transpile-only', + polykeyPath, + ...args, + ], + { + env, + cwd, + stdio: ['pipe', 'pipe', 'pipe'], + windowsHide: true, + shell: true, + }, + ); + const rlErr = readline.createInterface(subprocess.stderr!); + rlErr.on('line', (l) => { + // The readline library will trim newlines + logger.info(l); + }); + return subprocess; +} + +/** + * Setup routing between an agent and router with no NAT rules + */ +async function setupDMZ( + usrnsPid: number, + routerNsPid: number, + agentIp: string, + agentPort: string, + routerExt: string, + routerExtIp: string, + logger: Logger = new Logger(setupDMZ.name), +) { + const postroutingCommand = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'nat', + '--append', + 'POSTROUTING', + '--protocol', + 'udp', + '--source', + `${agentIp}${SUBNET_MASK}`, + '--out-interface', + routerExt, + '--jump', + 'SNAT', + '--to-source', + `${routerExtIp}:${DMZ_PORT}`, + ]; + const preroutingCommand = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'nat', + '--append', + 'PREROUTING', + '--protocol', + 'udp', + '--destination-port', + DMZ_PORT, + '--in-interface', + routerExt, + '--jump', + 'DNAT', + '--to-destination', + `${agentIp}:${agentPort}`, + ]; + try { + logger.info(['nsenter', ...postroutingCommand].join(' ')); + await testBinUtils.exec('nsenter', postroutingCommand); + logger.info(['nsenter', ...preroutingCommand].join(' ')); + await testBinUtils.exec('nsenter', preroutingCommand); + } catch (e) { + logger.error(e.message); + } +} + +/** + * Setup Port-Restricted Cone NAT for a namespace (on the router namespace) + */ +async function setupNATEndpointIndependentMapping( + usrnsPid: number, + routerNsPid: number, + agentIp: string, + routerExt: string, + routerInt: string, + logger: Logger = new Logger(setupNATEndpointIndependentMapping.name), +) { + const natCommand = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'nat', + '--append', + 'POSTROUTING', + '--protocol', + 'udp', + '--source', + `${agentIp}${SUBNET_MASK}`, + '--out-interface', + routerExt, + '--jump', + 'MASQUERADE', + ]; + const acceptLocalCommand = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'filter', + '--append', + 'INPUT', + '--in-interface', + routerInt, + '--jump', + 'ACCEPT', + ]; + const acceptEstablishedCommand = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'filter', + '--append', + 'INPUT', + '--match', + 'conntrack', + '--ctstate', + 'RELATED,ESTABLISHED', + '--jump', + 'ACCEPT', + ]; + const dropCommand = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'filter', + '--append', + 'INPUT', + '--jump', + 'DROP', + ]; + try { + logger.info(['nsenter', ...acceptLocalCommand].join(' ')); + await testBinUtils.exec('nsenter', acceptLocalCommand); + logger.info(['nsenter', ...acceptEstablishedCommand].join(' ')); + await testBinUtils.exec('nsenter', acceptEstablishedCommand); + logger.info(['nsenter', ...dropCommand].join(' ')); + await testBinUtils.exec('nsenter', dropCommand); + logger.info(['nsenter', ...natCommand].join(' ')); + await testBinUtils.exec('nsenter', natCommand); + } catch (e) { + logger.error(e.message); + } +} + +/** + * Setup Symmetric NAT for a namespace (on the router namespace) + */ +async function setupNATEndpointDependentMapping( + usrnsPid: number, + routerNsPid: number, + routerExt: string, + logger: Logger = new Logger(setupNATEndpointDependentMapping.name), +) { + const command = [ + ...nsenter(usrnsPid, routerNsPid), + 'iptables', + '--table', + 'nat', + '--append', + 'POSTROUTING', + '--protocol', + 'udp', + '--out-interface', + routerExt, + '--jump', + 'MASQUERADE', + `--random`, + ]; + try { + logger.info(['nsenter', ...command].join(' ')); + await testBinUtils.exec('nsenter', command); + } catch (e) { + logger.error(e.message); + } +} + +async function setupNATWithSeedNode( + agent1NAT: NATType, + agent2NAT: NATType, + logger: Logger = new Logger(setupNAT.name, LogLevel.WARN, [ + new StreamHandler(), + ]), +) { + const dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const password = 'password'; + // Create a user namespace containing five network namespaces + // Two agents, two routers, one seed node + const usrns = createUserNamespace(logger); + const seedNetns = createNetworkNamespace(usrns.pid!, logger); + const agent1Netns = createNetworkNamespace(usrns.pid!, logger); + const agent2Netns = createNetworkNamespace(usrns.pid!, logger); + const router1Netns = createNetworkNamespace(usrns.pid!, logger); + const router2Netns = createNetworkNamespace(usrns.pid!, logger); + // Apply appropriate NAT rules + switch (agent1NAT) { + case 'dmz': { + await setupDMZ( + usrns.pid!, + router1Netns.pid!, + AGENT1_HOST, + AGENT1_PORT, + ROUTER1_VETH_EXT, + ROUTER1_HOST_EXT, + logger, + ); + await setupDMZ( + usrns.pid!, + router1Netns.pid!, + AGENT1_HOST, + AGENT1_PORT, + ROUTER1_VETH_SEED, + ROUTER1_HOST_SEED, + logger, + ); + break; + } + case 'eim': { + await setupNATEndpointIndependentMapping( + usrns.pid!, + router1Netns.pid!, + AGENT1_HOST, + ROUTER1_VETH_EXT, + ROUTER1_VETH_INT, + logger, + ); + await setupNATEndpointIndependentMapping( + usrns.pid!, + router1Netns.pid!, + AGENT1_HOST, + ROUTER1_VETH_SEED, + ROUTER1_VETH_INT, + logger, + ); + break; + } + case 'edm': { + await setupNATEndpointDependentMapping( + usrns.pid!, + router1Netns.pid!, + ROUTER1_VETH_EXT, + logger, + ); + await setupNATEndpointDependentMapping( + usrns.pid!, + router1Netns.pid!, + ROUTER1_VETH_SEED, + logger, + ); + break; + } + } + switch (agent2NAT) { + case 'dmz': { + await setupDMZ( + usrns.pid!, + router2Netns.pid!, + AGENT2_HOST, + AGENT2_PORT, + ROUTER2_VETH_EXT, + ROUTER2_HOST_EXT, + logger, + ); + await setupDMZ( + usrns.pid!, + router2Netns.pid!, + AGENT2_HOST, + AGENT2_PORT, + ROUTER2_VETH_SEED, + ROUTER2_HOST_SEED, + logger, + ); + break; + } + case 'eim': { + await setupNATEndpointIndependentMapping( + usrns.pid!, + router2Netns.pid!, + AGENT2_HOST, + ROUTER2_VETH_EXT, + ROUTER2_VETH_INT, + logger, + ); + await setupNATEndpointIndependentMapping( + usrns.pid!, + router2Netns.pid!, + AGENT2_HOST, + ROUTER2_VETH_SEED, + ROUTER2_VETH_INT, + logger, + ); + break; + } + case 'edm': { + await setupNATEndpointDependentMapping( + usrns.pid!, + router2Netns.pid!, + ROUTER2_VETH_EXT, + logger, + ); + await setupNATEndpointDependentMapping( + usrns.pid!, + router2Netns.pid!, + ROUTER2_VETH_SEED, + logger, + ); + break; + } + } + await setupNetworkNamespaceInterfaces( + usrns.pid!, + agent1Netns.pid!, + router1Netns.pid!, + router2Netns.pid!, + agent2Netns.pid!, + logger, + ); + await setupSeedNamespaceInterfaces( + usrns.pid!, + seedNetns.pid!, + router1Netns.pid!, + router2Netns.pid!, + logger, + ); + const seedNode = await pkSpawnNs( + usrns.pid!, + seedNetns.pid!, + [ + 'agent', + 'start', + '--node-path', + path.join(dataDir, 'seed'), + '--root-key-pair-bits', + '1024', + '--client-host', + '127.0.0.1', + '--proxy-host', + '0.0.0.0', + '--connection-timeout', + '1000', + '--workers', + '0', + '--verbose', + '--format', + 'json', + ], + { + PK_PASSWORD: password, + }, + dataDir, + logger.getChild('seed'), + ); + const rlOutSeed = readline.createInterface(seedNode.stdout!); + const stdoutSeed = await new Promise((resolve, reject) => { + rlOutSeed.once('line', resolve); + rlOutSeed.once('close', reject); + }); + const nodeIdSeed = JSON.parse(stdoutSeed).nodeId; + const proxyPortSeed = JSON.parse(stdoutSeed).proxyPort; + const agent1 = await pkSpawnNs( + usrns.pid!, + agent1Netns.pid!, + [ + 'agent', + 'start', + '--node-path', + path.join(dataDir, 'agent1'), + '--root-key-pair-bits', + '1024', + '--client-host', + '127.0.0.1', + '--proxy-host', + `${AGENT1_HOST}`, + '--proxy-port', + `${AGENT1_PORT}`, + '--workers', + '0', + '--connection-timeout', + '1000', + '--seed-nodes', + `${nodeIdSeed}@${SEED_HOST}:${proxyPortSeed}`, + '--verbose', + '--format', + 'json', + ], + { + PK_PASSWORD: password, + }, + dataDir, + logger.getChild('agent1'), + ); + const rlOutNode1 = readline.createInterface(agent1.stdout!); + const stdoutNode1 = await new Promise((resolve, reject) => { + rlOutNode1.once('line', resolve); + rlOutNode1.once('close', reject); + }); + const nodeId1 = JSON.parse(stdoutNode1).nodeId; + const agent2 = await pkSpawnNs( + usrns.pid!, + agent2Netns.pid!, + [ + 'agent', + 'start', + '--node-path', + path.join(dataDir, 'agent2'), + '--root-key-pair-bits', + '1024', + '--client-host', + '127.0.0.1', + '--proxy-host', + `${AGENT2_HOST}`, + '--proxy-port', + `${AGENT2_PORT}`, + '--workers', + '0', + '--connection-timeout', + '1000', + '--seed-nodes', + `${nodeIdSeed}@${SEED_HOST}:${proxyPortSeed}`, + '--verbose', + '--format', + 'json', + ], + { + PK_PASSWORD: password, + }, + dataDir, + logger.getChild('agent2'), + ); + const rlOutNode2 = readline.createInterface(agent2.stdout!); + const stdoutNode2 = await new Promise((resolve, reject) => { + rlOutNode2.once('line', resolve); + rlOutNode2.once('close', reject); + }); + const nodeId2 = JSON.parse(stdoutNode2).nodeId; + return { + userPid: usrns.pid, + agent1Pid: agent1Netns.pid, + agent2Pid: agent2Netns.pid, + password, + dataDir, + agent1NodePath: path.join(dataDir, 'agent1'), + agent2NodePath: path.join(dataDir, 'agent2'), + agent1NodeId: nodeId1, + agent2NodeId: nodeId2, + tearDownNAT: async () => { + agent2.kill('SIGTERM'); + await testBinUtils.processExit(agent2); + agent1.kill('SIGTERM'); + await testBinUtils.processExit(agent1); + seedNode.kill('SIGTERM'); + await testBinUtils.processExit(seedNode); + router2Netns.kill('SIGTERM'); + await testBinUtils.processExit(router2Netns); + router1Netns.kill('SIGTERM'); + await testBinUtils.processExit(router1Netns); + agent2Netns.kill('SIGTERM'); + await testBinUtils.processExit(agent2Netns); + agent1Netns.kill('SIGTERM'); + await testBinUtils.processExit(agent1Netns); + seedNetns.kill('SIGTERM'); + await testBinUtils.processExit(seedNetns); + usrns.kill('SIGTERM'); + await testBinUtils.processExit(usrns); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }, + }; +} + +async function setupNAT( + agent1NAT: NATType, + agent2NAT: NATType, + logger: Logger = new Logger(setupNAT.name, LogLevel.WARN, [ + new StreamHandler(), + ]), +) { + const dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const password = 'password'; + // Create a user namespace containing four network namespaces + // Two agents and two routers + const usrns = createUserNamespace(logger); + const agent1Netns = createNetworkNamespace(usrns.pid!, logger); + const agent2Netns = createNetworkNamespace(usrns.pid!, logger); + const router1Netns = createNetworkNamespace(usrns.pid!, logger); + const router2Netns = createNetworkNamespace(usrns.pid!, logger); + // Apply appropriate NAT rules + switch (agent1NAT) { + case 'dmz': { + await setupDMZ( + usrns.pid!, + router1Netns.pid!, + AGENT1_HOST, + AGENT1_PORT, + ROUTER1_VETH_EXT, + ROUTER1_HOST_EXT, + logger, + ); + break; + } + case 'eim': { + await setupNATEndpointIndependentMapping( + usrns.pid!, + router1Netns.pid!, + AGENT1_HOST, + ROUTER1_VETH_EXT, + ROUTER1_VETH_INT, + logger, + ); + break; + } + case 'edm': { + await setupNATEndpointDependentMapping( + usrns.pid!, + router1Netns.pid!, + ROUTER1_VETH_EXT, + logger, + ); + break; + } + } + switch (agent2NAT) { + case 'dmz': { + await setupDMZ( + usrns.pid!, + router2Netns.pid!, + AGENT2_HOST, + AGENT2_PORT, + ROUTER2_VETH_EXT, + ROUTER2_HOST_EXT, + logger, + ); + break; + } + case 'eim': { + await setupNATEndpointIndependentMapping( + usrns.pid!, + router2Netns.pid!, + AGENT2_HOST, + ROUTER2_VETH_EXT, + ROUTER2_VETH_INT, + logger, + ); + break; + } + case 'edm': { + await setupNATEndpointDependentMapping( + usrns.pid!, + router2Netns.pid!, + ROUTER2_VETH_EXT, + logger, + ); + break; + } + } + await setupNetworkNamespaceInterfaces( + usrns.pid!, + agent1Netns.pid!, + router1Netns.pid!, + router2Netns.pid!, + agent2Netns.pid!, + logger, + ); + const agent1 = await pkSpawnNs( + usrns.pid!, + agent1Netns.pid!, + [ + 'agent', + 'start', + '--node-path', + path.join(dataDir, 'agent1'), + '--root-key-pair-bits', + '1024', + '--client-host', + '127.0.0.1', + '--proxy-host', + `${AGENT1_HOST}`, + '--proxy-port', + `${AGENT1_PORT}`, + '--connection-timeout', + '1000', + '--workers', + '0', + '--verbose', + '--format', + 'json', + ], + { + PK_PASSWORD: password, + }, + dataDir, + logger.getChild('agent1'), + ); + const rlOutNode1 = readline.createInterface(agent1.stdout!); + const stdoutNode1 = await new Promise((resolve, reject) => { + rlOutNode1.once('line', resolve); + rlOutNode1.once('close', reject); + }); + const nodeId1 = JSON.parse(stdoutNode1).nodeId; + const agent2 = await pkSpawnNs( + usrns.pid!, + agent2Netns.pid!, + [ + 'agent', + 'start', + '--node-path', + path.join(dataDir, 'agent2'), + '--root-key-pair-bits', + '1024', + '--client-host', + '127.0.0.1', + '--proxy-host', + `${AGENT2_HOST}`, + '--proxy-port', + `${AGENT2_PORT}`, + '--connection-timeout', + '1000', + '--workers', + '0', + '--verbose', + '--format', + 'json', + ], + { + PK_PASSWORD: password, + }, + dataDir, + logger.getChild('agent2'), + ); + const rlOutNode2 = readline.createInterface(agent2.stdout!); + const stdoutNode2 = await new Promise((resolve, reject) => { + rlOutNode2.once('line', resolve); + rlOutNode2.once('close', reject); + }); + const nodeId2 = JSON.parse(stdoutNode2).nodeId; + return { + userPid: usrns.pid, + agent1Pid: agent1Netns.pid, + agent2Pid: agent2Netns.pid, + password, + dataDir, + agent1NodePath: path.join(dataDir, 'agent1'), + agent2NodePath: path.join(dataDir, 'agent2'), + agent1NodeId: nodeId1, + agent1Host: ROUTER1_HOST_EXT, + agent1ProxyPort: agent1NAT === 'dmz' ? DMZ_PORT : AGENT1_PORT, + agent2NodeId: nodeId2, + agent2Host: ROUTER2_HOST_EXT, + agent2ProxyPort: agent2NAT === 'dmz' ? DMZ_PORT : AGENT2_PORT, + tearDownNAT: async () => { + agent2.kill('SIGTERM'); + await testBinUtils.processExit(agent2); + agent1.kill('SIGTERM'); + await testBinUtils.processExit(agent1); + router2Netns.kill('SIGTERM'); + await testBinUtils.processExit(router2Netns); + router1Netns.kill('SIGTERM'); + await testBinUtils.processExit(router1Netns); + agent2Netns.kill('SIGTERM'); + await testBinUtils.processExit(agent2Netns); + agent1Netns.kill('SIGTERM'); + await testBinUtils.processExit(agent1Netns); + usrns.kill('SIGTERM'); + await testBinUtils.processExit(usrns); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }, + }; +} + +export { + createUserNamespace, + createNetworkNamespace, + setupNetworkNamespaceInterfaces, + pkExecNs, + pkSpawnNs, + setupNAT, + setupNATWithSeedNode, +}; diff --git a/tests/network/Proxy.test.ts b/tests/network/Proxy.test.ts index 4393c69b9..5bab753c4 100644 --- a/tests/network/Proxy.test.ts +++ b/tests/network/Proxy.test.ts @@ -1,20 +1,19 @@ -import type { Socket, AddressInfo } from 'net'; +import type { AddressInfo, Socket } from 'net'; import type { KeyPairPem } from '@/keys/types'; -import type { Host, Port } from '@/network/types'; -import http from 'http'; +import type { ConnectionData, Host, Port } from '@/network/types'; import net from 'net'; +import http from 'http'; import tls from 'tls'; import UTP from 'utp-native'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { - Proxy, - utils as networkUtils, - errors as networkErrors, -} from '@/network'; +import Proxy from '@/network/Proxy'; +import * as networkUtils from '@/network/utils'; +import * as networkErrors from '@/network/errors'; import * as keysUtils from '@/keys/utils'; -import { promisify, promise, timerStart, timerStop, poll } from '@/utils'; -import { utils as nodesUtils } from '@/nodes'; +import * as nodesUtils from '@/nodes/utils'; +import { poll, promise, promisify, timerStart, timerStop } from '@/utils'; import * as testUtils from '../utils'; +import * as testNodesUtils from '../nodes/utils'; /** * Mock HTTP Connect Request @@ -29,7 +28,7 @@ async function httpConnect( path: string, ): Promise { const tokenEncoded = Buffer.from(token, 'utf-8').toString('base64'); - const socket = await new Promise((resolve, reject) => { + return await new Promise((resolve, reject) => { const req = http.request({ method: 'CONNECT', path: path, @@ -51,7 +50,6 @@ async function httpConnect( reject(e); }); }); - return socket; } /** @@ -113,11 +111,11 @@ describe(Proxy.name, () => { const logger = new Logger(`${Proxy.name} test`, LogLevel.WARN, [ new StreamHandler(), ]); - const nodeIdABC = testUtils.generateRandomNodeId(); + const nodeIdABC = testNodesUtils.generateRandomNodeId(); const nodeIdABCEncoded = nodesUtils.encodeNodeId(nodeIdABC); - const nodeIdSome = testUtils.generateRandomNodeId(); + const nodeIdSome = testNodesUtils.generateRandomNodeId(); const nodeIdSomeEncoded = nodesUtils.encodeNodeId(nodeIdSome); - const nodeIdRandom = testUtils.generateRandomNodeId(); + const nodeIdRandom = testNodesUtils.generateRandomNodeId(); const authToken = 'abc123'; let keyPairPem: KeyPairPem; let certPem: string; @@ -144,6 +142,8 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); @@ -160,6 +160,8 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); @@ -173,6 +175,7 @@ describe(Proxy.name, () => { // Start it again await proxy.start({ forwardHost: '::1' as Host, + proxyHost: localHost, serverHost: localHost, serverPort: port, tlsConfig: { @@ -196,6 +199,7 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, serverHost: localHost, serverPort: port, }); @@ -247,12 +251,14 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); // Cannot open connection to port 0 await expect(() => - proxy.openConnectionForward(nodeIdABC, '127.0.0.1' as Host, 0 as Port), + proxy.openConnectionForward(nodeIdABC, localHost, 0 as Port), ).rejects.toThrow(networkErrors.ErrorConnectionStart); await expect(() => httpConnect( @@ -281,39 +287,41 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); // This UTP server will just hang and not respond - let recievedCount = 0; + let receivedCount = 0; const utpSocketHang = UTP.createServer(() => { - recievedCount++; + receivedCount++; }); const utpSocketHangListen = promisify(utpSocketHang.listen).bind( utpSocketHang, ); - await utpSocketHangListen(0, '127.0.0.1'); + await utpSocketHangListen(0, localHost); const utpSocketHangPort = utpSocketHang.address().port; await expect(() => proxy.openConnectionForward( nodeIdABC, - '127.0.0.1' as Host, + localHost, utpSocketHangPort as Port, ), ).rejects.toThrow(networkErrors.ErrorConnectionStartTimeout); - expect(recievedCount).toBe(1); + expect(receivedCount).toBe(1); // Can override the timer const timer = timerStart(2000); await expect(() => proxy.openConnectionForward( nodeIdABC, - '127.0.0.1' as Host, + localHost, utpSocketHangPort as Port, timer, ), ).rejects.toThrow(networkErrors.ErrorConnectionStartTimeout); timerStop(timer); - expect(recievedCount).toBe(2); + expect(receivedCount).toBe(2); await expect(() => httpConnect( proxy.getForwardHost(), @@ -324,7 +332,7 @@ describe(Proxy.name, () => { )}`, ), ).rejects.toThrow('504'); - expect(recievedCount).toBe(3); + expect(receivedCount).toBe(3); utpSocketHang.close(); utpSocketHang.unref(); await proxy.stop(); @@ -339,39 +347,41 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); // This UTP Server will immediately end and destroy // the connection upon receiving a connection - let recievedCount = 0; + let receivedCount = 0; const utpSocketEnd = UTP.createServer((utpConn) => { - recievedCount++; + receivedCount++; utpConn.end(); utpConn.destroy(); }); const utpSocketEndListen = promisify(utpSocketEnd.listen).bind( utpSocketEnd, ); - await utpSocketEndListen(0, '127.0.0.1'); + await utpSocketEndListen(0, localHost); const utpSocketEndPort = utpSocketEnd.address().port; await expect(() => proxy.openConnectionForward( nodeIdABC, - '127.0.0.1' as Host, + localHost, utpSocketEndPort as Port, ), ).rejects.toThrow(networkErrors.ErrorConnectionStart); - expect(recievedCount).toBe(1); + expect(receivedCount).toBe(1); // The actual error is UTP_ECONNRESET to be precise await expect(() => proxy.openConnectionForward( nodeIdABC, - '127.0.0.1' as Host, + localHost, utpSocketEndPort as Port, ), ).rejects.toThrow(/UTP_ECONNRESET/); - expect(recievedCount).toBe(2); + expect(receivedCount).toBe(2); // 502 Bad Gateway on HTTP Connect await expect(() => httpConnect( @@ -383,7 +393,7 @@ describe(Proxy.name, () => { )}`, ), ).rejects.toThrow('502'); - expect(recievedCount).toBe(3); + expect(receivedCount).toBe(3); utpSocketEnd.close(); utpSocketEnd.unref(); await proxy.stop(); @@ -398,6 +408,8 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); @@ -463,7 +475,7 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketListen = promisify(utpSocket.listen).bind(utpSocket); - await utpSocketListen(0, '127.0.0.1'); + await utpSocketListen(0, localHost); const utpSocketHost = utpSocket.address().address; const utpSocketPort = utpSocket.address().port; expect(proxy.getConnectionForwardCount()).toBe(0); @@ -476,19 +488,19 @@ describe(Proxy.name, () => { ), ).rejects.toThrow(networkErrors.ErrorConnectionStart); await expect(remoteClosedP).resolves.toBeUndefined(); - expect(utpConnError.mock.calls.length).toBe(0); + expect(utpConnError).toHaveBeenCalledTimes(0); // The TLS socket throw an error because there's no suitable signature algorithm - expect(tlsSocketError.mock.calls.length).toBe(1); + expect(tlsSocketError).toHaveBeenCalledTimes(1); // Expect(tlsSocketError.mock.calls[0][0]).toBeInstanceOf(Error); expect(tlsSocketError.mock.calls[0][0]).toHaveProperty( 'code', 'ERR_SSL_NO_SUITABLE_SIGNATURE_ALGORITHM', ); // The TLS socket end event never was emitted - expect(tlsSocketEnd.mock.calls.length).toBe(0); + expect(tlsSocketEnd).toHaveBeenCalledTimes(0); // The TLS socket close event is emitted with error - expect(tlsSocketClose.mock.calls.length).toBe(1); - expect(tlsSocketClose.mock.calls[0][0]).toBe(true); + expect(tlsSocketClose).toHaveBeenCalledTimes(1); + expect(tlsSocketClose).toHaveBeenCalledWith(true); utpSocket.off('message', handleMessage); utpSocket.close(); utpSocket.unref(); @@ -504,6 +516,8 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); @@ -569,7 +583,7 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketListen = promisify(utpSocket.listen).bind(utpSocket); - await utpSocketListen(0, '127.0.0.1'); + await utpSocketListen(0, localHost); const utpSocketHost = utpSocket.address().address; const utpSocketPort = utpSocket.address().port; expect(proxy.getConnectionForwardCount()).toBe(0); @@ -585,19 +599,19 @@ describe(Proxy.name, () => { ), ).rejects.toThrow('502'); await expect(remoteClosedP).resolves.toBeUndefined(); - expect(utpConnError.mock.calls.length).toBe(0); + expect(utpConnError).toHaveBeenCalledTimes(0); // The TLS socket throw an error because there's no suitable signature algorithm - expect(tlsSocketError.mock.calls.length).toBe(1); + expect(tlsSocketError).toHaveBeenCalledTimes(1); // Expect(tlsSocketError.mock.calls[0][0]).toBeInstanceOf(Error); expect(tlsSocketError.mock.calls[0][0]).toHaveProperty( 'code', 'ERR_SSL_NO_SUITABLE_SIGNATURE_ALGORITHM', ); // The TLS socket end event never was emitted - expect(tlsSocketEnd.mock.calls.length).toBe(0); + expect(tlsSocketEnd).toHaveBeenCalledTimes(0); // The TLS socket close event is emitted with error - expect(tlsSocketClose.mock.calls.length).toBe(1); - expect(tlsSocketClose.mock.calls[0][0]).toBe(true); + expect(tlsSocketClose).toHaveBeenCalledTimes(1); + expect(tlsSocketClose).toHaveBeenCalledWith(true); utpSocket.off('message', handleMessage); utpSocket.close(); utpSocket.unref(); @@ -622,6 +636,8 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); @@ -638,8 +654,17 @@ describe(Proxy.name, () => { let secured = false; const utpSocket = UTP.createServer(async (utpConn) => { utpConn.on('error', (e) => { + logger.warn('utpConn threw: ' + e.message); + // UTP implementation is buggy, + // we sometimes expect to see write after end error + if (e.message === 'Cannot call write after a stream was destroyed') { + return; + } utpConnError(e); }); + utpConn.on('end', async () => { + utpConn.destroy(); + }); const tlsSocket = new tls.TLSSocket(utpConn, { key: Buffer.from(serverKeyPairPem.privateKey, 'ascii'), cert: Buffer.from(serverCertPem, 'ascii'), @@ -690,7 +715,7 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketListen = promisify(utpSocket.listen).bind(utpSocket); - await utpSocketListen(0, '127.0.0.1'); + await utpSocketListen(0, localHost); const utpSocketHost = utpSocket.address().address; const utpSocketPort = utpSocket.address().port; expect(proxy.getConnectionForwardCount()).toBe(0); @@ -705,15 +730,15 @@ describe(Proxy.name, () => { expect(secured).toBe(true); expect(proxy.getConnectionForwardCount()).toBe(0); await expect(remoteClosedP).resolves.toBeUndefined(); - expect(utpConnError.mock.calls.length).toBe(0); + expect(utpConnError).toHaveBeenCalledTimes(0); // No TLS socket errors this time - // The client side figured that the node id is incorect - expect(tlsSocketError.mock.calls.length).toBe(0); + // The client side figured that the node id is incorrect + expect(tlsSocketError).toHaveBeenCalledTimes(0); // This time the tls socket is ended from the client side - expect(tlsSocketEnd.mock.calls.length).toBe(1); + expect(tlsSocketEnd).toHaveBeenCalledTimes(1); // The TLS socket close event is emitted without error - expect(tlsSocketClose.mock.calls.length).toBe(1); - expect(tlsSocketClose.mock.calls[0][0]).toBe(false); + expect(tlsSocketClose).toHaveBeenCalledTimes(1); + expect(tlsSocketClose).toHaveBeenCalledWith(false); utpSocket.off('message', handleMessage); utpSocket.close(); utpSocket.unref(); @@ -738,6 +763,8 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); @@ -754,8 +781,17 @@ describe(Proxy.name, () => { let secured = false; const utpSocket = UTP.createServer(async (utpConn) => { utpConn.on('error', (e) => { + logger.warn('utpConn threw: ' + e.message); + // UTP implementation is buggy, + // we sometimes expect to see write after end error + if (e.message === 'Cannot call write after a stream was destroyed') { + return; + } utpConnError(e); }); + utpConn.on('end', async () => { + utpConn.destroy(); + }); const tlsSocket = new tls.TLSSocket(utpConn, { key: Buffer.from(serverKeyPairPem.privateKey, 'ascii'), cert: Buffer.from(serverCertPem, 'ascii'), @@ -806,7 +842,7 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketListen = promisify(utpSocket.listen).bind(utpSocket); - await utpSocketListen(0, '127.0.0.1'); + await utpSocketListen(0, localHost); const utpSocketHost = utpSocket.address().address; const utpSocketPort = utpSocket.address().port; expect(proxy.getConnectionForwardCount()).toBe(0); @@ -824,15 +860,15 @@ describe(Proxy.name, () => { expect(secured).toBe(true); expect(proxy.getConnectionForwardCount()).toBe(0); await expect(remoteClosedP).resolves.toBeUndefined(); - expect(utpConnError.mock.calls.length).toBe(0); + expect(utpConnError).toHaveBeenCalledTimes(0); // No TLS socket errors this time - // The client side figured taht the node id is incorect - expect(tlsSocketError.mock.calls.length).toBe(0); + // The client side figured that the node id is incorrect + expect(tlsSocketError).toHaveBeenCalledTimes(0); // This time the tls socket is ended from the client side - expect(tlsSocketEnd.mock.calls.length).toBe(1); + expect(tlsSocketEnd).toHaveBeenCalledTimes(1); // The TLS socket close event is emitted without error - expect(tlsSocketClose.mock.calls.length).toBe(1); - expect(tlsSocketClose.mock.calls[0][0]).toBe(false); + expect(tlsSocketClose).toHaveBeenCalledTimes(1); + expect(tlsSocketClose).toHaveBeenCalledWith(false); utpSocket.off('message', handleMessage); utpSocket.close(); utpSocket.unref(); @@ -860,6 +896,8 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); @@ -877,8 +915,17 @@ describe(Proxy.name, () => { // This UTP server will hold the connection const utpSocket = UTP.createServer(async (utpConn) => { utpConn.on('error', (e) => { + logger.warn('utpConn threw: ' + e.message); + // UTP implementation is buggy, + // we sometimes expect to see write after end error + if (e.message === 'Cannot call write after a stream was destroyed') { + return; + } utpConnError(e); }); + utpConn.on('end', async () => { + utpConn.destroy(); + }); const tlsSocket = new tls.TLSSocket(utpConn, { key: Buffer.from(serverKeyPairPem.privateKey, 'ascii'), cert: Buffer.from(serverCertPem, 'ascii'), @@ -930,7 +977,7 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketListen = promisify(utpSocket.listen).bind(utpSocket); - await utpSocketListen(0, '127.0.0.1'); + await utpSocketListen(0, localHost); const utpSocketHost = utpSocket.address().address; const utpSocketPort = utpSocket.address().port; expect(proxy.getConnectionForwardCount()).toBe(0); @@ -954,11 +1001,11 @@ describe(Proxy.name, () => { ); expect(proxy.getConnectionForwardCount()).toBe(0); await expect(remoteClosedP).resolves.toBeUndefined(); - expect(utpConnError.mock.calls.length).toBe(0); - expect(tlsSocketError.mock.calls.length).toBe(0); - expect(tlsSocketEnd.mock.calls.length).toBe(1); - expect(tlsSocketClose.mock.calls.length).toBe(1); - expect(tlsSocketClose.mock.calls[0][0]).toBe(false); + expect(utpConnError).toHaveBeenCalledTimes(0); + expect(tlsSocketError).toHaveBeenCalledTimes(0); + expect(tlsSocketEnd).toHaveBeenCalledTimes(1); + expect(tlsSocketClose).toHaveBeenCalledTimes(1); + expect(tlsSocketClose).toHaveBeenCalledWith(false); utpSocket.off('message', handleMessage); utpSocket.close(); utpSocket.unref(); @@ -987,6 +1034,8 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); @@ -1060,7 +1109,7 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketListen = promisify(utpSocket.listen).bind(utpSocket); - await utpSocketListen(0, '127.0.0.1'); + await utpSocketListen(0, localHost); const utpSocketHost = utpSocket.address().address; const utpSocketPort = utpSocket.address().port; expect(proxy.getConnectionForwardCount()).toBe(0); @@ -1085,7 +1134,7 @@ describe(Proxy.name, () => { tlsSocket_!.once('end', resolveEndP); tlsSocket_!.end(); await endP; - // Force destroy the socket due to buggy tlsSocket and utpConn + // Force destroys the socket due to buggy tlsSocket and utpConn tlsSocket_!.destroy(); logger.debug('Reverse: finishes tlsSocket ending'); await expect(remoteClosedP).resolves.toBeUndefined(); @@ -1096,19 +1145,18 @@ describe(Proxy.name, () => { return proxy.getConnectionForwardCount(); }, (_, result) => { - if (result === 0) return true; - return false; + return result === 0; }, 100, ), ).resolves.toBe(0); - expect(utpConnError.mock.calls.length).toBe(0); - expect(tlsSocketError.mock.calls.length).toBe(0); + expect(utpConnError).toHaveBeenCalledTimes(0); + expect(tlsSocketError).toHaveBeenCalledTimes(0); // This time the reverse side initiates the end // Therefore, this handler is removed - expect(tlsSocketEnd.mock.calls.length).toBe(0); - expect(tlsSocketClose.mock.calls.length).toBe(1); - expect(tlsSocketClose.mock.calls[0][0]).toBe(false); + expect(tlsSocketEnd).toHaveBeenCalledTimes(0); + expect(tlsSocketClose).toHaveBeenCalledTimes(1); + expect(tlsSocketClose).toHaveBeenCalledWith(false); utpSocket.off('message', handleMessage); utpSocket.close(); utpSocket.unref(); @@ -1137,6 +1185,8 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); @@ -1154,8 +1204,17 @@ describe(Proxy.name, () => { // This UTP server will hold the connection const utpSocket = UTP.createServer(async (utpConn) => { utpConn.on('error', (e) => { + logger.warn('utpConn threw: ' + e.message); + // UTP implementation is buggy, + // we sometimes expect to see write after end error + if (e.message === 'Cannot call write after a stream was destroyed') { + return; + } utpConnError(e); }); + utpConn.on('end', async () => { + utpConn.destroy(); + }); const tlsSocket = new tls.TLSSocket(utpConn, { key: Buffer.from(serverKeyPairPem.privateKey, 'ascii'), cert: Buffer.from(serverCertPem, 'ascii'), @@ -1207,7 +1266,7 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketListen = promisify(utpSocket.listen).bind(utpSocket); - await utpSocketListen(0, '127.0.0.1'); + await utpSocketListen(0, localHost); const utpSocketHost = utpSocket.address().address; const utpSocketPort = utpSocket.address().port; expect(proxy.getConnectionForwardCount()).toBe(0); @@ -1250,14 +1309,14 @@ describe(Proxy.name, () => { utpSocketPort as Port, ); expect(proxy.getConnectionForwardCount()).toBe(0); - expect(clientSocketEnd.mock.calls.length).toBe(1); + expect(clientSocketEnd).toHaveBeenCalledTimes(1); await expect(localClosedP).resolves.toBeUndefined(); await expect(remoteClosedP).resolves.toBeUndefined(); - expect(utpConnError.mock.calls.length).toBe(0); - expect(tlsSocketError.mock.calls.length).toBe(0); - expect(tlsSocketEnd.mock.calls.length).toBe(1); - expect(tlsSocketClose.mock.calls.length).toBe(1); - expect(tlsSocketClose.mock.calls[0][0]).toBe(false); + expect(utpConnError).toHaveBeenCalledTimes(0); + expect(tlsSocketError).toHaveBeenCalledTimes(0); + expect(tlsSocketEnd).toHaveBeenCalledTimes(1); + expect(tlsSocketClose).toHaveBeenCalledTimes(1); + expect(tlsSocketClose).toHaveBeenCalledWith(false); utpSocket.off('message', handleMessage); utpSocket.close(); utpSocket.unref(); @@ -1286,6 +1345,8 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); @@ -1359,7 +1420,7 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketListen = promisify(utpSocket.listen).bind(utpSocket); - await utpSocketListen(0, '127.0.0.1'); + await utpSocketListen(0, localHost); const utpSocketHost = utpSocket.address().address; const utpSocketPort = utpSocket.address().port; expect(proxy.getConnectionForwardCount()).toBe(0); @@ -1404,11 +1465,11 @@ describe(Proxy.name, () => { tlsSocket_!.once('end', resolveEndP); tlsSocket_!.end(); await endP; - // Force destroy the socket due to buggy tlsSocket and utpConn + // Force destroys the socket due to buggy tlsSocket and utpConn tlsSocket_!.destroy(); logger.debug('Reverse: finishes tlsSocket ending'); await expect(localClosedP).resolves.toBeUndefined(); - expect(clientSocketEnd.mock.calls.length).toBe(1); + expect(clientSocketEnd).toHaveBeenCalledTimes(1); await expect(remoteClosedP).resolves.toBeUndefined(); // Connection count should reach 0 eventually await expect( @@ -1417,19 +1478,18 @@ describe(Proxy.name, () => { return proxy.getConnectionForwardCount(); }, (_, result) => { - if (result === 0) return true; - return false; + return result === 0; }, 100, ), ).resolves.toBe(0); - expect(utpConnError.mock.calls.length).toBe(0); - expect(tlsSocketError.mock.calls.length).toBe(0); + expect(utpConnError).toHaveBeenCalledTimes(0); + expect(tlsSocketError).toHaveBeenCalledTimes(0); // This time the reverse side initiates the end // Therefore, this handler is removed - expect(tlsSocketEnd.mock.calls.length).toBe(0); - expect(tlsSocketClose.mock.calls.length).toBe(1); - expect(tlsSocketClose.mock.calls[0][0]).toBe(false); + expect(tlsSocketEnd).toHaveBeenCalledTimes(0); + expect(tlsSocketClose).toHaveBeenCalledTimes(1); + expect(tlsSocketClose).toHaveBeenCalledWith(false); utpSocket.off('message', handleMessage); utpSocket.close(); utpSocket.unref(); @@ -1458,6 +1518,8 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); @@ -1528,7 +1590,7 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketListen = promisify(utpSocket.listen).bind(utpSocket); - await utpSocketListen(0, '127.0.0.1'); + await utpSocketListen(0, localHost); const utpSocketHost = utpSocket.address().address; const utpSocketPort = utpSocket.address().port; expect(proxy.getConnectionForwardCount()).toBe(0); @@ -1557,7 +1619,7 @@ describe(Proxy.name, () => { ); expect(proxy.getConnectionForwardCount()).toBe(1); const { p: endP, resolveP: resolveEndP } = promise(); - // By default net sockets have `allowHalfOpen: false` + // By default, net sockets have `allowHalfOpen: false` // Here we override the behaviour by removing the end listener // And replacing it with our own, and remember to also force destroy clientSocket.removeAllListeners('end'); @@ -1578,17 +1640,16 @@ describe(Proxy.name, () => { return proxy.getConnectionForwardCount(); }, (_, result) => { - if (result === 0) return true; - return false; + return result === 0; }, 100, ), ).resolves.toBe(0); - expect(utpConnError.mock.calls.length).toBe(0); - expect(tlsSocketError.mock.calls.length).toBe(0); - expect(tlsSocketEnd.mock.calls.length).toBe(1); - expect(tlsSocketClose.mock.calls.length).toBe(1); - expect(tlsSocketClose.mock.calls[0][0]).toBe(false); + expect(utpConnError).toHaveBeenCalledTimes(0); + expect(tlsSocketError).toHaveBeenCalledTimes(0); + expect(tlsSocketEnd).toHaveBeenCalledTimes(1); + expect(tlsSocketClose).toHaveBeenCalledTimes(1); + expect(tlsSocketClose).toHaveBeenCalledWith(false); utpSocket.off('message', handleMessage); utpSocket.close(); utpSocket.unref(); @@ -1615,6 +1676,8 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); @@ -1684,7 +1747,7 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketListen = promisify(utpSocket.listen).bind(utpSocket); - await utpSocketListen(0, '127.0.0.1'); + await utpSocketListen(0, localHost); const utpSocketHost = utpSocket.address().address; const utpSocketPort = utpSocket.address().port; await proxy.openConnectionForward( @@ -1715,11 +1778,11 @@ describe(Proxy.name, () => { ); await expect(localClosedP).resolves.toBeUndefined(); await expect(remoteClosedP).resolves.toBeUndefined(); - expect(utpConnError.mock.calls.length).toBe(0); - expect(tlsSocketError.mock.calls.length).toBe(0); - expect(tlsSocketEnd.mock.calls.length).toBe(1); - expect(tlsSocketClose.mock.calls.length).toBe(1); - expect(tlsSocketClose.mock.calls[0][0]).toBe(false); + expect(utpConnError).toHaveBeenCalledTimes(0); + expect(tlsSocketError).toHaveBeenCalledTimes(0); + expect(tlsSocketEnd).toHaveBeenCalledTimes(1); + expect(tlsSocketClose).toHaveBeenCalledTimes(1); + expect(tlsSocketClose).toHaveBeenCalledWith(false); utpSocket.off('message', handleMessage); utpSocket.close(); utpSocket.unref(); @@ -1747,6 +1810,8 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); @@ -1817,7 +1882,7 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketListen = promisify(utpSocket.listen).bind(utpSocket); - await utpSocketListen(0, '127.0.0.1'); + await utpSocketListen(0, localHost); const utpSocketHost = utpSocket.address().address; const utpSocketPort = utpSocket.address().port; expect(proxy.getConnectionForwardCount()).toBe(0); @@ -1832,11 +1897,11 @@ describe(Proxy.name, () => { // When ErrorConnectionTimeout is triggered // This results in the destruction of the socket await expect(remoteClosedP).resolves.toBeUndefined(); - expect(utpConnError.mock.calls.length).toBe(0); - expect(tlsSocketError.mock.calls.length).toBe(0); - expect(tlsSocketEnd.mock.calls.length).toBe(1); - expect(tlsSocketClose.mock.calls.length).toBe(1); - expect(tlsSocketClose.mock.calls[0][0]).toBe(false); + expect(utpConnError).toHaveBeenCalledTimes(0); + expect(tlsSocketError).toHaveBeenCalledTimes(0); + expect(tlsSocketEnd).toHaveBeenCalledTimes(1); + expect(tlsSocketClose).toHaveBeenCalledTimes(1); + expect(tlsSocketClose).toHaveBeenCalledWith(false); utpSocket.off('message', handleMessage); utpSocket.close(); utpSocket.unref(); @@ -1865,6 +1930,8 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); @@ -1935,7 +2002,7 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketListen = promisify(utpSocket.listen).bind(utpSocket); - await utpSocketListen(0, '127.0.0.1'); + await utpSocketListen(0, localHost); const utpSocketHost = utpSocket.address().address; const utpSocketPort = utpSocket.address().port; expect(proxy.getConnectionForwardCount()).toBe(0); @@ -1968,17 +2035,16 @@ describe(Proxy.name, () => { return proxy.getConnectionForwardCount(); }, (_, result) => { - if (result === 0) return true; - return false; + return result === 0; }, 100, ), ).resolves.toBe(0); - expect(utpConnError.mock.calls.length).toBe(0); - expect(tlsSocketError.mock.calls.length).toBe(0); - expect(tlsSocketEnd.mock.calls.length).toBe(1); - expect(tlsSocketClose.mock.calls.length).toBe(1); - expect(tlsSocketClose.mock.calls[0][0]).toBe(false); + expect(utpConnError).toHaveBeenCalledTimes(0); + expect(tlsSocketError).toHaveBeenCalledTimes(0); + expect(tlsSocketEnd).toHaveBeenCalledTimes(1); + expect(tlsSocketClose).toHaveBeenCalledTimes(1); + expect(tlsSocketClose).toHaveBeenCalledWith(false); utpSocket.off('message', handleMessage); utpSocket.close(); utpSocket.unref(); @@ -2004,6 +2070,8 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); @@ -2060,7 +2128,7 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketListen = promisify(utpSocket.listen).bind(utpSocket); - await utpSocketListen(0, '127.0.0.1'); + await utpSocketListen(0, localHost); const utpSocketHost = utpSocket.address().address; const utpSocketPort = utpSocket.address().port; expect(proxy.getConnectionForwardCount()).toBe(0); @@ -2111,6 +2179,8 @@ describe(Proxy.name, () => { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, + proxyHost: localHost, + forwardHost: localHost, serverHost: localHost, serverPort: port, }); @@ -2166,7 +2236,7 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketListen1 = promisify(utpSocket1.listen).bind(utpSocket1); - await utpSocketListen1(0, '127.0.0.1'); + await utpSocketListen1(0, localHost); const utpSocketHost1 = utpSocket1.address().address; const utpSocketPort1 = utpSocket1.address().port; const utpSocket2 = UTP.createServer(async (utpConn) => { @@ -2209,7 +2279,7 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketListen2 = promisify(utpSocket2.listen).bind(utpSocket2); - await utpSocketListen2(0, '127.0.0.1'); + await utpSocketListen2(0, localHost); const utpSocketHost2 = utpSocket2.address().address; const utpSocketPort2 = utpSocket2.address().port; expect(proxy.getConnectionForwardCount()).toBe(0); @@ -2245,7 +2315,6 @@ describe(Proxy.name, () => { utpSocket2.unref(); await proxy.stop(); }); - test('open connection to port 0 fails', async () => { const proxy = new Proxy({ logger: logger.getChild('Proxy port 0'), @@ -2263,14 +2332,15 @@ describe(Proxy.name, () => { await proxy.start({ serverHost: serverHost(), serverPort: serverPort(), - + proxyHost: localHost, + forwardHost: localHost, tlsConfig: { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, }, }); await expect( - proxy.openConnectionReverse('127.0.0.1' as Host, 0 as Port), + proxy.openConnectionReverse(localHost, 0 as Port), ).rejects.toThrow(networkErrors.ErrorConnectionStart); await expect(serverConnP).resolves.toBeUndefined(); await expect(serverConnClosedP).resolves.toBeUndefined(); @@ -2294,7 +2364,8 @@ describe(Proxy.name, () => { await proxy.start({ serverHost: serverHost(), serverPort: serverPort(), - + proxyHost: localHost, + forwardHost: localHost, tlsConfig: { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, @@ -2303,15 +2374,11 @@ describe(Proxy.name, () => { // This UTP client will just hang and not respond const utpSocket = UTP(); const utpSocketBind = promisify(utpSocket.bind).bind(utpSocket); - await utpSocketBind(0, '127.0.0.1'); + await utpSocketBind(0, localHost); const utpSocketPort = utpSocket.address().port; const timer = timerStart(3000); await expect( - proxy.openConnectionReverse( - '127.0.0.1' as Host, - utpSocketPort as Port, - timer, - ), + proxy.openConnectionReverse(localHost, utpSocketPort as Port, timer), ).rejects.toThrow(networkErrors.ErrorConnectionStartTimeout); timerStop(timer); await expect(serverConnP).resolves.toBeUndefined(); @@ -2338,6 +2405,8 @@ describe(Proxy.name, () => { await proxy.start({ serverHost: serverHost(), serverPort: serverPort(), + proxyHost: localHost, + forwardHost: localHost, tlsConfig: { keyPrivatePem: keyPairPem.privateKey, @@ -2359,17 +2428,11 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketBind = promisify(utpSocket.bind).bind(utpSocket); - await utpSocketBind(0, '127.0.0.1'); + await utpSocketBind(0, localHost); const utpSocketPort = utpSocket.address().port; - await proxy.openConnectionReverse( - '127.0.0.1' as Host, - utpSocketPort as Port, - ); + await proxy.openConnectionReverse(localHost, utpSocketPort as Port); expect(proxy.getConnectionReverseCount()).toBe(1); - await proxy.closeConnectionReverse( - '127.0.0.1' as Host, - utpSocketPort as Port, - ); + await proxy.closeConnectionReverse(localHost, utpSocketPort as Port); await expect(serverConnP).resolves.toBeUndefined(); await expect(serverConnClosedP).resolves.toBeUndefined(); utpSocket.off('message', handleMessage); @@ -2395,6 +2458,8 @@ describe(Proxy.name, () => { await proxy.start({ serverHost: serverHost(), serverPort: serverPort(), + proxyHost: localHost, + forwardHost: localHost, tlsConfig: { keyPrivatePem: keyPairPem.privateKey, @@ -2417,7 +2482,7 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketBind1 = promisify(utpSocket1.bind).bind(utpSocket1); - await utpSocketBind1(0, '127.0.0.1'); + await utpSocketBind1(0, localHost); const utpSocketPort1 = utpSocket1.address().port; // Second client const utpSocket2 = UTP(); @@ -2433,25 +2498,13 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketBind2 = promisify(utpSocket2.bind).bind(utpSocket2); - await utpSocketBind2(0, '127.0.0.1'); + await utpSocketBind2(0, localHost); const utpSocketPort2 = utpSocket2.address().port; - await proxy.openConnectionReverse( - '127.0.0.1' as Host, - utpSocketPort1 as Port, - ); - await proxy.openConnectionReverse( - '127.0.0.1' as Host, - utpSocketPort2 as Port, - ); + await proxy.openConnectionReverse(localHost, utpSocketPort1 as Port); + await proxy.openConnectionReverse(localHost, utpSocketPort2 as Port); expect(proxy.getConnectionReverseCount()).toBe(2); - await proxy.closeConnectionReverse( - '127.0.0.1' as Host, - utpSocketPort1 as Port, - ); - await proxy.closeConnectionReverse( - '127.0.0.1' as Host, - utpSocketPort2 as Port, - ); + await proxy.closeConnectionReverse(localHost, utpSocketPort1 as Port); + await proxy.closeConnectionReverse(localHost, utpSocketPort2 as Port); expect(proxy.getConnectionReverseCount()).toBe(0); await expect(serverConnP).resolves.toBeUndefined(); await expect(serverConnClosedP).resolves.toBeUndefined(); @@ -2483,6 +2536,8 @@ describe(Proxy.name, () => { await proxy.start({ serverHost: serverHost(), serverPort: serverPort(), + proxyHost: localHost, + forwardHost: localHost, tlsConfig: { keyPrivatePem: keyPairPem.privateKey, @@ -2504,12 +2559,9 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketBind = promisify(utpSocket.bind).bind(utpSocket); - await utpSocketBind(0, '127.0.0.1'); + await utpSocketBind(0, localHost); const utpSocketPort = utpSocket.address().port; - await proxy.openConnectionReverse( - '127.0.0.1' as Host, - utpSocketPort as Port, - ); + await proxy.openConnectionReverse(localHost, utpSocketPort as Port); expect(proxy.getConnectionReverseCount()).toBe(1); await expect(serverConnP).resolves.toBeUndefined(); // The server receives the end confirmation for graceful exit @@ -2551,7 +2603,8 @@ describe(Proxy.name, () => { await proxy.start({ serverHost: serverHost(), serverPort: serverPort(), - + forwardHost: localHost, + proxyHost: localHost, tlsConfig: { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, @@ -2572,12 +2625,9 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; const utpSocketBind = promisify(utpSocket.bind).bind(utpSocket); - await utpSocketBind(0, '127.0.0.1'); + await utpSocketBind(0, localHost); const utpSocketPort = utpSocket.address().port; - await proxy.openConnectionReverse( - '127.0.0.1' as Host, - utpSocketPort as Port, - ); + await proxy.openConnectionReverse(localHost, utpSocketPort as Port); expect(proxy.getConnectionReverseCount()).toBe(1); // This retries multiple times // This will eventually fail and trigger a ErrorConnectionComposeTimeout @@ -2608,8 +2658,7 @@ describe(Proxy.name, () => { return proxy.getConnectionReverseCount(); }, (_, result) => { - if (result === 0) return true; - return false; + return result === 0; }, 100, ), @@ -2648,6 +2697,7 @@ describe(Proxy.name, () => { certChainPem: certPem, }, proxyHost: localHost, + forwardHost: localHost, }); const externalHost = proxy.getProxyHost(); const externalPort = proxy.getProxyPort(); @@ -2664,12 +2714,9 @@ describe(Proxy.name, () => { await utpSocketSend(data, 0, data.byteLength, externalPort, externalHost); }; const utpSocketBind = promisify(utpSocket.bind).bind(utpSocket); - await utpSocketBind(0, '127.0.0.1'); + await utpSocketBind(0, localHost); const utpSocketPort = utpSocket.address().port; - await proxy.openConnectionReverse( - '127.0.0.1' as Host, - utpSocketPort as Port, - ); + await proxy.openConnectionReverse(localHost, utpSocketPort as Port); expect(proxy.getConnectionReverseCount()).toBe(1); const { p: tlsSocketClosedP, resolveP: resolveTlsSocketClosedP } = promise(); @@ -2718,8 +2765,7 @@ describe(Proxy.name, () => { return proxy.getConnectionReverseCount(); }, (_, result) => { - if (result === 0) return true; - return false; + return result === 0; }, 100, ), @@ -2749,7 +2795,7 @@ describe(Proxy.name, () => { serverHost, serverPort, } = tcpServer(); - await serverListen(0, '127.0.0.1'); + await serverListen(0, localHost); const proxy = new Proxy({ logger: logger, authToken: '', @@ -2758,6 +2804,7 @@ describe(Proxy.name, () => { serverHost: serverHost(), serverPort: serverPort(), proxyHost: localHost, + forwardHost: localHost, tlsConfig: { keyPrivatePem: keyPairPem.privateKey, certChainPem: certPem, @@ -2783,12 +2830,9 @@ describe(Proxy.name, () => { const utpSocketSend = promisify(utpSocket.send).bind(utpSocket); await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; - await utpSocketBind(0, '127.0.0.1'); + await utpSocketBind(0, localHost); const utpSocketPort = utpSocket.address().port; - await proxy.openConnectionReverse( - '127.0.0.1' as Host, - utpSocketPort as Port, - ); + await proxy.openConnectionReverse(localHost, utpSocketPort as Port); const utpConn = utpSocket.connect(proxyPort, proxyHost); const tlsSocket = tls.connect( { @@ -2819,10 +2863,7 @@ describe(Proxy.name, () => { await clientReadyP; await clientSecureConnectP; await serverConnP; - await proxy.closeConnectionReverse( - '127.0.0.1' as Host, - utpSocketPort as Port, - ); + await proxy.closeConnectionReverse(localHost, utpSocketPort as Port); expect(proxy.getConnectionReverseCount()).toBe(0); await clientCloseP; await serverConnEndP; @@ -2853,7 +2894,7 @@ describe(Proxy.name, () => { serverHost, serverPort, } = tcpServer(); - await serverListen(0, '127.0.0.1'); + await serverListen(0, localHost); const proxy = new Proxy({ logger: logger, authToken: '', @@ -2887,12 +2928,9 @@ describe(Proxy.name, () => { const utpSocketSend = promisify(utpSocket.send).bind(utpSocket); await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); }; - await utpSocketBind(0, '127.0.0.1'); + await utpSocketBind(0, localHost); const utpSocketPort = utpSocket.address().port; - await proxy.openConnectionReverse( - '127.0.0.1' as Host, - utpSocketPort as Port, - ); + await proxy.openConnectionReverse(localHost, utpSocketPort as Port); const utpConn = utpSocket.connect(proxyPort, proxyHost); const tlsSocket = tls.connect( { @@ -2935,4 +2973,114 @@ describe(Proxy.name, () => { utpSocket.unref(); await serverClose(); }); + test('connectionEstablishedCallback is called when a ReverseConnection is established', async () => { + const clientKeyPair = await keysUtils.generateKeyPair(1024); + const clientKeyPairPem = keysUtils.keyPairToPem(clientKeyPair); + const clientCert = keysUtils.generateCertificate( + clientKeyPair.publicKey, + clientKeyPair.privateKey, + clientKeyPair.privateKey, + 86400, + ); + const clientCertPem = keysUtils.certToPem(clientCert); + const { + serverListen, + serverClose, + serverConnP, + serverConnEndP, + serverConnClosedP, + serverHost, + serverPort, + } = tcpServer(); + await serverListen(0, localHost); + const clientNodeId = keysUtils.certNodeId(clientCert)!; + let callbackData: ConnectionData | undefined; + const proxy = new Proxy({ + logger: logger, + authToken: '', + connectionEstablishedCallback: (data) => { + callbackData = data; + }, + }); + await proxy.start({ + serverHost: serverHost(), + serverPort: serverPort(), + proxyHost: localHost, + tlsConfig: { + keyPrivatePem: keyPairPem.privateKey, + certChainPem: certPem, + }, + }); + + const proxyHost = proxy.getProxyHost(); + const proxyPort = proxy.getProxyPort(); + const { p: clientReadyP, resolveP: resolveClientReadyP } = promise(); + const { p: clientSecureConnectP, resolveP: resolveClientSecureConnectP } = + promise(); + const { p: clientCloseP, resolveP: resolveClientCloseP } = promise(); + const utpSocket = UTP({ allowHalfOpen: true }); + const utpSocketBind = promisify(utpSocket.bind).bind(utpSocket); + const handleMessage = async (data: Buffer) => { + const msg = networkUtils.unserializeNetworkMessage(data); + if (msg.type === 'ping') { + resolveClientReadyP(); + await send(networkUtils.pongBuffer); + } + }; + utpSocket.on('message', handleMessage); + const send = async (data: Buffer) => { + const utpSocketSend = promisify(utpSocket.send).bind(utpSocket); + await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost); + }; + await utpSocketBind(0, localHost); + const utpSocketPort = utpSocket.address().port; + await proxy.openConnectionReverse(localHost, utpSocketPort as Port); + const utpConn = utpSocket.connect(proxyPort, proxyHost); + const tlsSocket = tls.connect( + { + key: Buffer.from(clientKeyPairPem.privateKey, 'ascii'), + cert: Buffer.from(clientCertPem, 'ascii'), + socket: utpConn, + rejectUnauthorized: false, + }, + () => { + resolveClientSecureConnectP(); + }, + ); + let tlsSocketEnded = false; + tlsSocket.on('end', () => { + tlsSocketEnded = true; + if (utpConn.destroyed) { + tlsSocket.destroy(); + } else { + tlsSocket.end(); + tlsSocket.destroy(); + } + }); + tlsSocket.on('close', () => { + resolveClientCloseP(); + }); + await send(networkUtils.pingBuffer); + expect(proxy.getConnectionReverseCount()).toBe(1); + await clientReadyP; + await clientSecureConnectP; + await serverConnP; + await proxy.closeConnectionReverse(localHost, utpSocketPort as Port); + expect(proxy.getConnectionReverseCount()).toBe(0); + await clientCloseP; + await serverConnEndP; + await serverConnClosedP; + expect(tlsSocketEnded).toBe(true); + utpSocket.off('message', handleMessage); + utpSocket.close(); + utpSocket.unref(); + await proxy.stop(); + await serverClose(); + + // Checking callback data + expect(callbackData?.remoteNodeId.equals(clientNodeId)).toBe(true); + expect(callbackData?.remoteHost).toEqual(localHost); + expect(callbackData?.remotePort).toEqual(utpSocketPort); + expect(callbackData?.type).toEqual('reverse'); + }); }); diff --git a/tests/network/utils.test.ts b/tests/network/utils.test.ts index 4ef705fd8..d1a26f6aa 100644 --- a/tests/network/utils.test.ts +++ b/tests/network/utils.test.ts @@ -1,6 +1,6 @@ import type { Host, Port } from '@/network/types'; - -import { utils as networkUtils, errors as networkErrors } from '@/network'; +import * as networkUtils from '@/network/utils'; +import * as networkErrors from '@/network/errors'; describe('utils', () => { test('building addresses', async () => { diff --git a/tests/nodes/NodeConnection.test.ts b/tests/nodes/NodeConnection.test.ts index 52d1ce674..beeb841ed 100644 --- a/tests/nodes/NodeConnection.test.ts +++ b/tests/nodes/NodeConnection.test.ts @@ -1,6 +1,7 @@ import type { AddressInfo } from 'net'; import type { ConnectionInfo, Host, Port, TLSConfig } from '@/network/types'; import type { NodeId, NodeInfo } from '@/nodes/types'; +import type { Server } from '@grpc/grpc-js'; import net from 'net'; import os from 'os'; import path from 'path'; @@ -18,9 +19,6 @@ import NodeManager from '@/nodes/NodeManager'; import VaultManager from '@/vaults/VaultManager'; import KeyManager from '@/keys/KeyManager'; import * as keysUtils from '@/keys/utils'; -import GRPCServer from '@/grpc/GRPCServer'; -import { AgentServiceService } from '@/proto/js/polykey/v1/agent_service_grpc_pb'; -import createAgentService from '@/agent/service'; import GRPCClientAgent from '@/agent/GRPCClientAgent'; import ACL from '@/acl/ACL'; import GestaltGraph from '@/gestalts/GestaltGraph'; @@ -35,13 +33,17 @@ import * as GRPCErrors from '@/grpc/errors'; import * as nodesUtils from '@/nodes/utils'; import * as agentErrors from '@/agent/errors'; import * as grpcUtils from '@/grpc/utils'; +import { timerStart } from '@/utils'; +import Queue from '@/nodes/Queue'; +import * as testNodesUtils from './utils'; import * as testUtils from '../utils'; import * as grpcTestUtils from '../grpc/utils'; +import * as agentTestUtils from '../agent/utils'; const destroyCallback = async () => {}; // Dummy nodeConnectionManager -// We only need the hole punch function and frankly its not used in testing here +// We only need the hole punch function, and frankly it's not used in testing here // This is really dirty so don't do this outside of testing EVER const dummyNodeConnectionManager = { openConnection: async (_host, _port) => { @@ -73,7 +75,7 @@ describe(`${NodeConnection.name} test`, () => { const password = 'password'; const node: NodeInfo = { - id: nodesUtils.encodeNodeId(testUtils.generateRandomNodeId()), + id: nodesUtils.encodeNodeId(testNodesUtils.generateRandomNodeId()), chain: {}, }; @@ -83,6 +85,7 @@ describe(`${NodeConnection.name} test`, () => { let serverKeyManager: KeyManager; let serverVaultManager: VaultManager; let serverNodeGraph: NodeGraph; + let serverQueue: Queue; let serverNodeConnectionManager: NodeConnectionManager; let serverNodeManager: NodeManager; let serverSigchain: Sigchain; @@ -97,9 +100,10 @@ describe(`${NodeConnection.name} test`, () => { let sourceNodeId: NodeId; let clientKeyManager: KeyManager; const authToken = 'AUTH'; - let clientproxy: Proxy; + let clientProxy: Proxy; - let agentServer: GRPCServer; + let agentServer: Server; + let serverPort: Port; let tlsConfig: TLSConfig; const localHost = '127.0.0.1' as Host; @@ -170,6 +174,13 @@ describe(`${NodeConnection.name} test`, () => { }; } + const newTlsConfig = async (keyManager: KeyManager): Promise => { + return { + keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, + certChainPem: await keyManager.getRootCertChainPem(), + }; + }; + beforeEach(async () => { // Server setup serverDataDir = await fs.promises.mkdtemp( @@ -229,22 +240,26 @@ describe(`${NodeConnection.name} test`, () => { logger, }); + serverQueue = new Queue({ logger }); serverNodeConnectionManager = new NodeConnectionManager({ keyManager: serverKeyManager, nodeGraph: serverNodeGraph, proxy: serverProxy, + queue: serverQueue, logger, }); - await serverNodeConnectionManager.start(); - serverNodeManager = new NodeManager({ db: serverDb, sigchain: serverSigchain, keyManager: serverKeyManager, nodeGraph: serverNodeGraph, nodeConnectionManager: serverNodeConnectionManager, + queue: serverQueue, logger: logger, }); + await serverQueue.start(); + await serverNodeManager.start(); + await serverNodeConnectionManager.start({ nodeManager: serverNodeManager }); serverVaultManager = await VaultManager.createVaultManager({ keyManager: serverKeyManager, vaultsPath: serverVaultsPath, @@ -266,7 +281,8 @@ describe(`${NodeConnection.name} test`, () => { logger: logger, }); await serverGestaltGraph.setNode(node); - const agentService = createAgentService({ + [agentServer, serverPort] = await agentTestUtils.openTestAgentServer({ + db: serverDb, keyManager: serverKeyManager, vaultManager: serverVaultManager, nodeConnectionManager: dummyNodeConnectionManager, @@ -277,17 +293,11 @@ describe(`${NodeConnection.name} test`, () => { acl: serverACL, gestaltGraph: serverGestaltGraph, proxy: serverProxy, - }); - agentServer = new GRPCServer({ logger: logger, }); - await agentServer.start({ - services: [[AgentServiceService, agentService]], - host: localHost, - }); await serverProxy.start({ serverHost: localHost, - serverPort: agentServer.getPort(), + serverPort: serverPort, proxyHost: localHost, tlsConfig: serverTLSConfig, }); @@ -311,18 +321,18 @@ describe(`${NodeConnection.name} test`, () => { }; sourceNodeId = clientKeyManager.getNodeId(); - clientproxy = new Proxy({ + clientProxy = new Proxy({ authToken: authToken, logger: logger, }); - await clientproxy.start({ + await clientProxy.start({ forwardHost: localHost, tlsConfig: clientTLSConfig, proxyHost: localHost, serverHost: localHost, serverPort: 0 as Port, }); - sourcePort = clientproxy.getProxyPort(); + sourcePort = clientProxy.getProxyPort(); // Other setup const globalKeyPair = await testUtils.setupGlobalKeypair(); @@ -339,7 +349,7 @@ describe(`${NodeConnection.name} test`, () => { }, global.polykeyStartupTimeout * 2); afterEach(async () => { - await clientproxy.stop(); + await clientProxy.stop(); await clientKeyManager.stop(); await clientKeyManager.destroy(); await fs.promises.rm(clientDataDir, { @@ -358,9 +368,11 @@ describe(`${NodeConnection.name} test`, () => { await serverNodeGraph.stop(); await serverNodeGraph.destroy(); await serverNodeConnectionManager.stop(); + await serverNodeManager.stop(); + await serverQueue.stop(); await serverNotificationsManager.stop(); await serverNotificationsManager.destroy(); - await agentServer.stop(); + await agentTestUtils.closeTestAgentServer(agentServer); await serverProxy.stop(); await serverKeyManager.stop(); await serverKeyManager.destroy(); @@ -378,7 +390,7 @@ describe(`${NodeConnection.name} test`, () => { targetNodeId: targetNodeId, targetHost: localHost, targetPort: targetPort, - proxy: clientproxy, + proxy: clientProxy, keyManager: clientKeyManager, nodeConnectionManager: dummyNodeConnectionManager, destroyCallback, @@ -394,7 +406,7 @@ describe(`${NodeConnection.name} test`, () => { // Explicitly close the connection such that there's no interference in next test await serverProxy.closeConnectionReverse( localHost, - clientproxy.getProxyPort(), + clientProxy.getProxyPort(), ); }); test('connects to its target (via direct connection)', async () => { @@ -402,7 +414,7 @@ describe(`${NodeConnection.name} test`, () => { targetNodeId: targetNodeId, targetHost: localHost, targetPort: targetPort, - proxy: clientproxy, + proxy: clientProxy, keyManager: clientKeyManager, nodeConnectionManager: dummyNodeConnectionManager, destroyCallback, @@ -418,8 +430,7 @@ describe(`${NodeConnection.name} test`, () => { }, (e) => { if (e instanceof networkErrors.ErrorConnectionNotComposed) return false; - if (e instanceof networkErrors.ErrorConnectionNotRunning) return false; - return true; + return !(e instanceof networkErrors.ErrorConnectionNotRunning); }, ); expect(connInfo).toBeDefined(); @@ -434,7 +445,7 @@ describe(`${NodeConnection.name} test`, () => { await conn.destroy(); }); test('connects to its target but proxies connect first', async () => { - await clientproxy.openConnectionForward( + await clientProxy.openConnectionForward( targetNodeId, localHost, targetPort, @@ -443,7 +454,7 @@ describe(`${NodeConnection.name} test`, () => { targetNodeId: targetNodeId, targetHost: localHost, targetPort: targetPort, - proxy: clientproxy, + proxy: clientProxy, keyManager: clientKeyManager, nodeConnectionManager: dummyNodeConnectionManager, destroyCallback, @@ -459,8 +470,7 @@ describe(`${NodeConnection.name} test`, () => { }, (e) => { if (e instanceof networkErrors.ErrorConnectionNotComposed) return false; - if (e instanceof networkErrors.ErrorConnectionNotRunning) return false; - return true; + return !(e instanceof networkErrors.ErrorConnectionNotRunning); }, ); expect(connInfo).toBeDefined(); @@ -489,8 +499,8 @@ describe(`${NodeConnection.name} test`, () => { // Have a nodeConnection try to connect to it const killSelf = jest.fn(); nodeConnection = await NodeConnection.createNodeConnection({ - connConnectTime: 500, - proxy: clientproxy, + timer: timerStart(500), + proxy: clientProxy, keyManager: clientKeyManager, logger: logger, nodeConnectionManager: dummyNodeConnectionManager, @@ -524,8 +534,8 @@ describe(`${NodeConnection.name} test`, () => { targetNodeId: targetNodeId, targetHost: '128.0.0.1' as Host, targetPort: 12345 as Port, - connConnectTime: 300, - proxy: clientproxy, + timer: timerStart(300), + proxy: clientProxy, keyManager: clientKeyManager, nodeConnectionManager: dummyNodeConnectionManager, destroyCallback, @@ -541,7 +551,7 @@ describe(`${NodeConnection.name} test`, () => { targetNodeId: targetNodeId, targetHost: localHost, targetPort: targetPort, - proxy: clientproxy, + proxy: clientProxy, keyManager: clientKeyManager, nodeConnectionManager: dummyNodeConnectionManager, destroyCallback, @@ -562,7 +572,7 @@ describe(`${NodeConnection.name} test`, () => { targetNodeId: targetNodeId, targetHost: localHost, targetPort: targetPort, - proxy: clientproxy, + proxy: clientProxy, keyManager: clientKeyManager, nodeConnectionManager: dummyNodeConnectionManager, destroyCallback, @@ -599,8 +609,8 @@ describe(`${NodeConnection.name} test`, () => { // Have a nodeConnection try to connect to it const killSelf = jest.fn(); const nodeConnectionP = NodeConnection.createNodeConnection({ - connConnectTime: 500, - proxy: clientproxy, + timer: timerStart(500), + proxy: clientProxy, keyManager: clientKeyManager, logger: logger, nodeConnectionManager: dummyNodeConnectionManager, @@ -642,8 +652,8 @@ describe(`${NodeConnection.name} test`, () => { // Have a nodeConnection try to connect to it const killSelf = jest.fn(); const nodeConnectionP = NodeConnection.createNodeConnection({ - connConnectTime: 500, - proxy: clientproxy, + timer: timerStart(500), + proxy: clientProxy, keyManager: clientKeyManager, logger: logger, nodeConnectionManager: dummyNodeConnectionManager, @@ -680,8 +690,8 @@ describe(`${NodeConnection.name} test`, () => { // Have a nodeConnection try to connect to it const killSelf = jest.fn(); nodeConnection = await NodeConnection.createNodeConnection({ - connConnectTime: 500, - proxy: clientproxy, + timer: timerStart(500), + proxy: clientProxy, keyManager: clientKeyManager, logger: logger, nodeConnectionManager: dummyNodeConnectionManager, @@ -742,8 +752,8 @@ describe(`${NodeConnection.name} test`, () => { const killSelfCheck = jest.fn(); const killSelfP = promise(); nodeConnection = await NodeConnection.createNodeConnection({ - connConnectTime: 2000, - proxy: clientproxy, + timer: timerStart(2000), + proxy: clientProxy, keyManager: clientKeyManager, logger: logger, nodeConnectionManager: dummyNodeConnectionManager, @@ -774,6 +784,7 @@ describe(`${NodeConnection.name} test`, () => { await nodeConnection?.destroy(); } }, + global.defaultTimeout * 2, ); test.each(options)( "should call `killSelf and throw if the server %s's during testStreamFail", @@ -811,8 +822,8 @@ describe(`${NodeConnection.name} test`, () => { const killSelfCheck = jest.fn(); const killSelfP = promise(); nodeConnection = await NodeConnection.createNodeConnection({ - connConnectTime: 2000, - proxy: clientproxy, + timer: timerStart(2000), + proxy: clientProxy, keyManager: clientKeyManager, logger: logger, nodeConnectionManager: dummyNodeConnectionManager, @@ -846,5 +857,360 @@ describe(`${NodeConnection.name} test`, () => { await nodeConnection?.destroy(); } }, + global.defaultTimeout * 2, ); + + test('existing connection handles a resetRootKeyPair on sending side', async () => { + let conn: NodeConnection | undefined; + try { + conn = await NodeConnection.createNodeConnection({ + targetNodeId: targetNodeId, + targetHost: localHost, + targetPort: targetPort, + proxy: clientProxy, + keyManager: clientKeyManager, + nodeConnectionManager: dummyNodeConnectionManager, + destroyCallback, + logger: logger, + clientFactory: async (args) => + GRPCClientAgent.createGRPCClientAgent(args), + timer: timerStart(2000), + }); + const client = conn.getClient(); + await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); + + // Simulate key change + await clientKeyManager.resetRootKeyPair(password); + clientProxy.setTLSConfig(await newTlsConfig(clientKeyManager)); + + // Try again + await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); + } finally { + await conn?.destroy(); + } + }); + test('existing connection handles a renewRootKeyPair on sending side', async () => { + let conn: NodeConnection | undefined; + try { + conn = await NodeConnection.createNodeConnection({ + targetNodeId: targetNodeId, + targetHost: localHost, + targetPort: targetPort, + proxy: clientProxy, + keyManager: clientKeyManager, + nodeConnectionManager: dummyNodeConnectionManager, + destroyCallback, + logger: logger, + clientFactory: async (args) => + GRPCClientAgent.createGRPCClientAgent(args), + timer: timerStart(2000), + }); + const client = conn.getClient(); + await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); + + // Simulate key change + await clientKeyManager.renewRootKeyPair(password); + clientProxy.setTLSConfig(await newTlsConfig(clientKeyManager)); + + // Try again + await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); + } finally { + await conn?.destroy(); + } + }); + test('existing connection handles a resetRootCert on sending side', async () => { + let conn: NodeConnection | undefined; + try { + conn = await NodeConnection.createNodeConnection({ + targetNodeId: targetNodeId, + targetHost: localHost, + targetPort: targetPort, + proxy: clientProxy, + keyManager: clientKeyManager, + nodeConnectionManager: dummyNodeConnectionManager, + destroyCallback, + logger: logger, + clientFactory: async (args) => + GRPCClientAgent.createGRPCClientAgent(args), + timer: timerStart(2000), + }); + const client = conn.getClient(); + await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); + + // Simulate key change + await clientKeyManager.resetRootCert(); + clientProxy.setTLSConfig(await newTlsConfig(clientKeyManager)); + + // Try again + await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); + } finally { + await conn?.destroy(); + } + }); + test('existing connection handles a resetRootKeyPair on receiving side', async () => { + let conn: NodeConnection | undefined; + try { + conn = await NodeConnection.createNodeConnection({ + targetNodeId: targetNodeId, + targetHost: localHost, + targetPort: targetPort, + proxy: clientProxy, + keyManager: clientKeyManager, + nodeConnectionManager: dummyNodeConnectionManager, + destroyCallback, + logger: logger, + clientFactory: async (args) => + GRPCClientAgent.createGRPCClientAgent(args), + timer: timerStart(2000), + }); + const client = conn.getClient(); + await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); + + // Simulate key change + await serverKeyManager.resetRootKeyPair(password); + serverProxy.setTLSConfig(await newTlsConfig(serverKeyManager)); + + // Try again + await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); + } finally { + await conn?.destroy(); + } + }); + test('existing connection handles a renewRootKeyPair on receiving side', async () => { + let conn: NodeConnection | undefined; + try { + conn = await NodeConnection.createNodeConnection({ + targetNodeId: targetNodeId, + targetHost: localHost, + targetPort: targetPort, + proxy: clientProxy, + keyManager: clientKeyManager, + nodeConnectionManager: dummyNodeConnectionManager, + destroyCallback, + logger: logger, + clientFactory: async (args) => + GRPCClientAgent.createGRPCClientAgent(args), + timer: timerStart(2000), + }); + const client = conn.getClient(); + await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); + + // Simulate key change + await serverKeyManager.renewRootKeyPair(password); + serverProxy.setTLSConfig(await newTlsConfig(serverKeyManager)); + + // Try again + await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); + } finally { + await conn?.destroy(); + } + }); + test('existing connection handles a resetRootCert on receiving side', async () => { + let conn: NodeConnection | undefined; + try { + conn = await NodeConnection.createNodeConnection({ + targetNodeId: targetNodeId, + targetHost: localHost, + targetPort: targetPort, + proxy: clientProxy, + keyManager: clientKeyManager, + nodeConnectionManager: dummyNodeConnectionManager, + destroyCallback, + logger: logger, + clientFactory: async (args) => + GRPCClientAgent.createGRPCClientAgent(args), + timer: timerStart(2000), + }); + const client = conn.getClient(); + await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); + + // Simulate key change + await serverKeyManager.resetRootCert(); + serverProxy.setTLSConfig(await newTlsConfig(serverKeyManager)); + + // Try again + await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); + } finally { + await conn?.destroy(); + } + }); + test('new connection handles a resetRootKeyPair on sending side', async () => { + let conn: NodeConnection | undefined; + try { + // Simulate key change + await clientKeyManager.resetRootKeyPair(password); + clientProxy.setTLSConfig(await newTlsConfig(clientKeyManager)); + + conn = await NodeConnection.createNodeConnection({ + targetNodeId: targetNodeId, + targetHost: localHost, + targetPort: targetPort, + proxy: clientProxy, + keyManager: clientKeyManager, + nodeConnectionManager: dummyNodeConnectionManager, + destroyCallback, + logger: logger, + clientFactory: async (args) => + GRPCClientAgent.createGRPCClientAgent(args), + timer: timerStart(2000), + }); + + const client = conn.getClient(); + await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); + } finally { + await conn?.destroy(); + } + }); + test('new connection handles a renewRootKeyPair on sending side', async () => { + let conn: NodeConnection | undefined; + try { + // Simulate key change + await clientKeyManager.renewRootKeyPair(password); + clientProxy.setTLSConfig(await newTlsConfig(clientKeyManager)); + + conn = await NodeConnection.createNodeConnection({ + targetNodeId: targetNodeId, + targetHost: localHost, + targetPort: targetPort, + proxy: clientProxy, + keyManager: clientKeyManager, + nodeConnectionManager: dummyNodeConnectionManager, + destroyCallback, + logger: logger, + clientFactory: async (args) => + GRPCClientAgent.createGRPCClientAgent(args), + timer: timerStart(2000), + }); + + const client = conn.getClient(); + await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); + } finally { + await conn?.destroy(); + } + }); + test('new connection handles a resetRootCert on sending side', async () => { + let conn: NodeConnection | undefined; + try { + // Simulate key change + await clientKeyManager.resetRootCert(); + clientProxy.setTLSConfig(await newTlsConfig(clientKeyManager)); + + conn = await NodeConnection.createNodeConnection({ + targetNodeId: targetNodeId, + targetHost: localHost, + targetPort: targetPort, + proxy: clientProxy, + keyManager: clientKeyManager, + nodeConnectionManager: dummyNodeConnectionManager, + destroyCallback, + logger: logger, + clientFactory: async (args) => + GRPCClientAgent.createGRPCClientAgent(args), + timer: timerStart(2000), + }); + + const client = conn.getClient(); + await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); + } finally { + await conn?.destroy(); + } + }); + test('new connection handles a resetRootKeyPair on receiving side', async () => { + // Simulate key change + await serverKeyManager.resetRootKeyPair(password); + serverProxy.setTLSConfig(await newTlsConfig(serverKeyManager)); + + const connProm = NodeConnection.createNodeConnection({ + targetNodeId: targetNodeId, + targetHost: localHost, + targetPort: targetPort, + proxy: clientProxy, + keyManager: clientKeyManager, + nodeConnectionManager: dummyNodeConnectionManager, + destroyCallback, + logger: logger, + clientFactory: async (args) => + GRPCClientAgent.createGRPCClientAgent(args), + timer: timerStart(2000), + }); + + await expect(connProm).rejects.toThrow( + nodesErrors.ErrorNodeConnectionTimeout, + ); + + // Connect with the new NodeId + let conn: NodeConnection | undefined; + try { + conn = await NodeConnection.createNodeConnection({ + targetNodeId: serverKeyManager.getNodeId(), + targetHost: localHost, + targetPort: targetPort, + proxy: clientProxy, + keyManager: clientKeyManager, + nodeConnectionManager: dummyNodeConnectionManager, + destroyCallback, + logger: logger, + clientFactory: async (args) => + GRPCClientAgent.createGRPCClientAgent(args), + }); + const client = conn.getClient(); + await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); + } finally { + await conn?.destroy(); + } + }); + test('new connection handles a renewRootKeyPair on receiving side', async () => { + let conn: NodeConnection | undefined; + try { + // Simulate key change + await serverKeyManager.renewRootKeyPair(password); + serverProxy.setTLSConfig(await newTlsConfig(serverKeyManager)); + + conn = await NodeConnection.createNodeConnection({ + targetNodeId: targetNodeId, + targetHost: localHost, + targetPort: targetPort, + proxy: clientProxy, + keyManager: clientKeyManager, + nodeConnectionManager: dummyNodeConnectionManager, + destroyCallback, + logger: logger, + clientFactory: async (args) => + GRPCClientAgent.createGRPCClientAgent(args), + timer: timerStart(2000), + }); + + const client = conn.getClient(); + await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); + } finally { + await conn?.destroy(); + } + }); + test('new connection handles a resetRootCert on receiving side', async () => { + let conn: NodeConnection | undefined; + try { + // Simulate key change + await serverKeyManager.resetRootCert(); + serverProxy.setTLSConfig(await newTlsConfig(serverKeyManager)); + + conn = await NodeConnection.createNodeConnection({ + targetNodeId: targetNodeId, + targetHost: localHost, + targetPort: targetPort, + proxy: clientProxy, + keyManager: clientKeyManager, + nodeConnectionManager: dummyNodeConnectionManager, + destroyCallback, + logger: logger, + clientFactory: async (args) => + GRPCClientAgent.createGRPCClientAgent(args), + timer: timerStart(2000), + }); + + const client = conn.getClient(); + await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); + } finally { + await conn?.destroy(); + } + }); }); diff --git a/tests/nodes/NodeConnectionManager.general.test.ts b/tests/nodes/NodeConnectionManager.general.test.ts index a6c3638cb..17035b4dd 100644 --- a/tests/nodes/NodeConnectionManager.general.test.ts +++ b/tests/nodes/NodeConnectionManager.general.test.ts @@ -1,11 +1,13 @@ -import type { NodeAddress, NodeData, NodeId, SeedNodes } from '@/nodes/types'; +import type { NodeAddress, NodeBucket, NodeId, SeedNodes } from '@/nodes/types'; import type { Host, Port } from '@/network/types'; +import type NodeManager from '@/nodes/NodeManager'; import fs from 'fs'; import path from 'path'; import os from 'os'; import { DB } from '@matrixai/db'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { IdInternal } from '@matrixai/id'; +import Queue from '@/nodes/Queue'; import PolykeyAgent from '@/PolykeyAgent'; import KeyManager from '@/keys/KeyManager'; import NodeGraph from '@/nodes/NodeGraph'; @@ -14,13 +16,11 @@ import Proxy from '@/network/Proxy'; import GRPCClientAgent from '@/agent/GRPCClientAgent'; import * as nodesUtils from '@/nodes/utils'; -import * as nodesErrors from '@/nodes/errors'; import * as keysUtils from '@/keys/utils'; import * as grpcUtils from '@/grpc/utils'; import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import * as nodesTestUtils from './utils'; -import * as testUtils from '../utils'; +import * as testNodesUtils from './utils'; describe(`${NodeConnectionManager.name} general test`, () => { const logger = new Logger( @@ -75,8 +75,8 @@ describe(`${NodeConnectionManager.name} general test`, () => { let keyManager: KeyManager; let db: DB; let proxy: Proxy; - let nodeGraph: NodeGraph; + let queue: Queue; let remoteNode1: PolykeyAgent; let remoteNode2: PolykeyAgent; @@ -126,6 +126,7 @@ describe(`${NodeConnectionManager.name} general test`, () => { keysUtils, 'generateDeterministicKeyPair', ); + const dummyNodeManager = { setNode: jest.fn() } as unknown as NodeManager; beforeAll(async () => { mockedGenerateDeterministicKeyPair.mockImplementation((bits, _) => { @@ -140,7 +141,10 @@ describe(`${NodeConnectionManager.name} general test`, () => { password, nodePath: path.join(dataDir2, 'remoteNode1'), networkConfig: { - proxyHost: '127.0.0.1' as Host, + proxyHost: localHost, + agentHost: localHost, + clientHost: localHost, + forwardHost: localHost, }, logger: logger.getChild('remoteNode1'), }); @@ -149,7 +153,10 @@ describe(`${NodeConnectionManager.name} general test`, () => { password, nodePath: path.join(dataDir2, 'remoteNode2'), networkConfig: { - proxyHost: '127.0.0.1' as Host, + proxyHost: localHost, + agentHost: localHost, + clientHost: localHost, + forwardHost: localHost, }, logger: logger.getChild('remoteNode2'), }); @@ -191,6 +198,10 @@ describe(`${NodeConnectionManager.name} general test`, () => { keyManager, logger: logger.getChild('NodeGraph'), }); + queue = new Queue({ + logger: logger.getChild('queue'), + }); + await queue.start(); const tlsConfig = { keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, certChainPem: keysUtils.certToPem(keyManager.getRootCert()), @@ -216,6 +227,7 @@ describe(`${NodeConnectionManager.name} general test`, () => { }); afterEach(async () => { + await queue.stop(); await nodeGraph.stop(); await nodeGraph.destroy(); await db.stop(); @@ -232,14 +244,15 @@ describe(`${NodeConnectionManager.name} general test`, () => { keyManager, nodeGraph, proxy, + queue, logger: nodeConnectionManagerLogger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); try { // Case 1: node already exists in the local node graph (no contact required) const nodeId = nodeId1; const nodeAddress: NodeAddress = { - host: '127.0.0.1' as Host, + host: localHost, port: 11111 as Port, }; await nodeGraph.setNode(nodeId, nodeAddress); @@ -254,26 +267,35 @@ describe(`${NodeConnectionManager.name} general test`, () => { test( 'finds node (contacts remote node)', async () => { + const mockedPingNode = jest.spyOn( + NodeConnectionManager.prototype, + 'pingNode', + ); + mockedPingNode.mockImplementation(async () => true); // NodeConnectionManager under test const nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, logger: nodeConnectionManagerLogger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); try { // Case 2: node can be found on the remote node const nodeId = nodeId1; const nodeAddress: NodeAddress = { - host: '127.0.0.1' as Host, + host: localHost, port: 11111 as Port, }; const server = await PolykeyAgent.createPolykeyAgent({ nodePath: path.join(dataDir, 'node2'), password, networkConfig: { - proxyHost: '127.0.0.1' as Host, + proxyHost: localHost, + agentHost: localHost, + clientHost: localHost, + forwardHost: localHost, }, logger: nodeConnectionManagerLogger, }); @@ -288,6 +310,7 @@ describe(`${NodeConnectionManager.name} general test`, () => { await server.stop(); } finally { await nodeConnectionManager.stop(); + mockedPingNode.mockRestore(); } }, global.polykeyStartupTimeout, @@ -300,9 +323,10 @@ describe(`${NodeConnectionManager.name} general test`, () => { keyManager, nodeGraph, proxy, + queue, logger: nodeConnectionManagerLogger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); try { // Case 3: node exhausts all contacts and cannot find node const nodeId = nodeId1; @@ -310,7 +334,10 @@ describe(`${NodeConnectionManager.name} general test`, () => { nodePath: path.join(dataDir, 'node3'), password, networkConfig: { - proxyHost: '127.0.0.1' as Host, + proxyHost: localHost, + agentHost: localHost, + clientHost: localHost, + forwardHost: localHost, }, logger: nodeConnectionManagerLogger, }); @@ -326,9 +353,9 @@ describe(`${NodeConnectionManager.name} general test`, () => { port: 22222 as Port, } as NodeAddress); // Un-findable Node cannot be found - await expect(() => - nodeConnectionManager.findNode(nodeId), - ).rejects.toThrowError(nodesErrors.ErrorNodeGraphNodeIdNotFound); + await expect(nodeConnectionManager.findNode(nodeId)).resolves.toEqual( + undefined, + ); await server.stop(); } finally { @@ -337,129 +364,6 @@ describe(`${NodeConnectionManager.name} general test`, () => { }, global.failedConnectionTimeout * 2, ); - test('finds a single closest node', async () => { - // NodeConnectionManager under test - const nodeConnectionManager = new NodeConnectionManager({ - keyManager, - nodeGraph, - proxy, - logger: nodeConnectionManagerLogger, - }); - await nodeConnectionManager.start(); - try { - // New node added - const newNode2Id = nodeId1; - const newNode2Address = { host: '227.1.1.1', port: 4567 } as NodeAddress; - await nodeGraph.setNode(newNode2Id, newNode2Address); - - // Find the closest nodes to some node, NODEID3 - const closest = await nodeConnectionManager.getClosestLocalNodes(nodeId3); - expect(closest).toContainEqual({ - id: newNode2Id, - distance: 121n, - address: { host: '227.1.1.1', port: 4567 }, - }); - } finally { - await nodeConnectionManager.stop(); - } - }); - test('finds 3 closest nodes', async () => { - const nodeConnectionManager = new NodeConnectionManager({ - keyManager, - nodeGraph, - proxy, - logger: nodeConnectionManagerLogger, - }); - await nodeConnectionManager.start(); - try { - // Add 3 nodes - await nodeGraph.setNode(nodeId1, { - host: '2.2.2.2', - port: 2222, - } as NodeAddress); - await nodeGraph.setNode(nodeId2, { - host: '3.3.3.3', - port: 3333, - } as NodeAddress); - await nodeGraph.setNode(nodeId3, { - host: '4.4.4.4', - port: 4444, - } as NodeAddress); - - // Find the closest nodes to some node, NODEID4 - const closest = await nodeConnectionManager.getClosestLocalNodes(nodeId3); - expect(closest.length).toBe(5); - expect(closest).toContainEqual({ - id: nodeId3, - distance: 0n, - address: { host: '4.4.4.4', port: 4444 }, - }); - expect(closest).toContainEqual({ - id: nodeId2, - distance: 116n, - address: { host: '3.3.3.3', port: 3333 }, - }); - expect(closest).toContainEqual({ - id: nodeId1, - distance: 121n, - address: { host: '2.2.2.2', port: 2222 }, - }); - } finally { - await nodeConnectionManager.stop(); - } - }); - test('finds the 20 closest nodes', async () => { - const nodeConnectionManager = new NodeConnectionManager({ - keyManager, - nodeGraph, - proxy, - logger: nodeConnectionManagerLogger, - }); - await nodeConnectionManager.start(); - try { - // Generate the node ID to find the closest nodes to (in bucket 100) - const nodeId = keyManager.getNodeId(); - const nodeIdToFind = nodesTestUtils.generateNodeIdForBucket(nodeId, 100); - // Now generate and add 20 nodes that will be close to this node ID - const addedClosestNodes: NodeData[] = []; - for (let i = 1; i < 101; i += 5) { - const closeNodeId = nodesTestUtils.generateNodeIdForBucket( - nodeIdToFind, - i, - ); - const nodeAddress = { - host: (i + '.' + i + '.' + i + '.' + i) as Host, - port: i as Port, - }; - await nodeGraph.setNode(closeNodeId, nodeAddress); - addedClosestNodes.push({ - id: closeNodeId, - address: nodeAddress, - distance: nodesUtils.calculateDistance(nodeIdToFind, closeNodeId), - }); - } - // Now create and add 10 more nodes that are far away from this node - for (let i = 1; i <= 10; i++) { - const farNodeId = nodeIdGenerator(i); - const nodeAddress = { - host: `${i}.${i}.${i}.${i}` as Host, - port: i as Port, - }; - await nodeGraph.setNode(farNodeId, nodeAddress); - } - - // Find the closest nodes to the original generated node ID - const closest = await nodeConnectionManager.getClosestLocalNodes( - nodeIdToFind, - ); - // We should always only receive k nodes - expect(closest.length).toBe(nodeGraph.maxNodesPerBucket); - // Retrieved closest nodes should be exactly the same as the ones we added - expect(closest).toEqual(addedClosestNodes); - } finally { - await nodeConnectionManager.stop(); - } - }); test('receives 20 closest local nodes from connected target', async () => { let serverPKAgent: PolykeyAgent | undefined; let nodeConnectionManager: NodeConnectionManager | undefined; @@ -469,17 +373,21 @@ describe(`${NodeConnectionManager.name} general test`, () => { logger: logger.getChild('serverPKAgent'), nodePath: path.join(dataDir, 'serverPKAgent'), networkConfig: { - proxyHost: '127.0.0.1' as Host, + proxyHost: localHost, + agentHost: localHost, + clientHost: localHost, + forwardHost: localHost, }, }); nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, logger: logger.getChild('NodeConnectionManager'), }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); const targetNodeId = serverPKAgent.keyManager.getNodeId(); await nodeGraph.setNode(targetNodeId, { host: serverPKAgent.proxy.getProxyHost(), @@ -487,9 +395,9 @@ describe(`${NodeConnectionManager.name} general test`, () => { }); // Now generate and add 20 nodes that will be close to this node ID - const addedClosestNodes: NodeData[] = []; + const addedClosestNodes: NodeBucket = []; for (let i = 1; i < 101; i += 5) { - const closeNodeId = nodesTestUtils.generateNodeIdForBucket( + const closeNodeId = testNodesUtils.generateNodeIdForBucket( targetNodeId, i, ); @@ -498,11 +406,13 @@ describe(`${NodeConnectionManager.name} general test`, () => { port: i as Port, }; await serverPKAgent.nodeGraph.setNode(closeNodeId, nodeAddress); - addedClosestNodes.push({ - id: closeNodeId, - address: nodeAddress, - distance: nodesUtils.calculateDistance(targetNodeId, closeNodeId), - }); + addedClosestNodes.push([ + closeNodeId, + { + address: nodeAddress, + lastUpdated: 0, + }, + ]); } // Now create and add 10 more nodes that are far away from this node for (let i = 1; i <= 10; i++) { @@ -521,7 +431,7 @@ describe(`${NodeConnectionManager.name} general test`, () => { ); // Sort the received nodes on distance such that we can check its equality // with addedClosestNodes - closest.sort(nodesUtils.sortByDistance); + nodesUtils.bucketSortByDistance(closest, targetNodeId); expect(closest.length).toBe(20); expect(closest).toEqual(addedClosestNodes); } finally { @@ -545,14 +455,15 @@ describe(`${NodeConnectionManager.name} general test`, () => { keyManager, nodeGraph, proxy, + queue, logger: nodeConnectionManagerLogger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // To test this we need to... // 2. call relayHolePunchMessage // 3. check that the relevant call was made. - const sourceNodeId = testUtils.generateRandomNodeId(); - const targetNodeId = testUtils.generateRandomNodeId(); + const sourceNodeId = testNodesUtils.generateRandomNodeId(); + const targetNodeId = testNodesUtils.generateRandomNodeId(); await nodeConnectionManager.sendHolePunchMessage( remoteNodeId1, sourceNodeId, @@ -582,13 +493,14 @@ describe(`${NodeConnectionManager.name} general test`, () => { keyManager, nodeGraph, proxy, + queue, logger: nodeConnectionManagerLogger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // To test this we need to... // 2. call relayHolePunchMessage // 3. check that the relevant call was made. - const sourceNodeId = testUtils.generateRandomNodeId(); + const sourceNodeId = testNodesUtils.generateRandomNodeId(); const relayMessage = new nodesPB.Relay(); relayMessage.setSrcId(nodesUtils.encodeNodeId(sourceNodeId)); relayMessage.setTargetId(nodesUtils.encodeNodeId(remoteNodeId1)); diff --git a/tests/nodes/NodeConnectionManager.lifecycle.test.ts b/tests/nodes/NodeConnectionManager.lifecycle.test.ts index 7bb154f36..a6f9d04e7 100644 --- a/tests/nodes/NodeConnectionManager.lifecycle.test.ts +++ b/tests/nodes/NodeConnectionManager.lifecycle.test.ts @@ -1,11 +1,14 @@ import type { NodeId, NodeIdString, SeedNodes } from '@/nodes/types'; import type { Host, Port } from '@/network/types'; +import type NodeManager from 'nodes/NodeManager'; import fs from 'fs'; import path from 'path'; import os from 'os'; import { DB } from '@matrixai/db'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { withF } from '@matrixai/resources'; import { IdInternal } from '@matrixai/id'; +import Queue from '@/nodes/Queue'; import PolykeyAgent from '@/PolykeyAgent'; import KeyManager from '@/keys/KeyManager'; import NodeGraph from '@/nodes/NodeGraph'; @@ -16,7 +19,7 @@ import * as nodesUtils from '@/nodes/utils'; import * as nodesErrors from '@/nodes/errors'; import * as keysUtils from '@/keys/utils'; import * as grpcUtils from '@/grpc/utils'; -import { withF } from '@/utils'; +import { timerStart } from '@/utils'; describe(`${NodeConnectionManager.name} lifecycle test`, () => { const logger = new Logger( @@ -74,16 +77,19 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { let proxy: Proxy; let nodeGraph: NodeGraph; + let queue: Queue; let remoteNode1: PolykeyAgent; let remoteNode2: PolykeyAgent; let remoteNodeId1: NodeId; + let remoteNodeIdString1: NodeIdString; let remoteNodeId2: NodeId; const mockedGenerateDeterministicKeyPair = jest.spyOn( keysUtils, 'generateDeterministicKeyPair', ); + const dummyNodeManager = { setNode: jest.fn() } as unknown as NodeManager; beforeAll(async () => { mockedGenerateDeterministicKeyPair.mockImplementation((bits, _) => { @@ -98,16 +104,17 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { password, nodePath: path.join(dataDir2, 'remoteNode1'), networkConfig: { - proxyHost: '127.0.0.1' as Host, + proxyHost: serverHost, }, logger: logger.getChild('remoteNode1'), }); remoteNodeId1 = remoteNode1.keyManager.getNodeId(); + remoteNodeIdString1 = remoteNodeId1.toString() as NodeIdString; remoteNode2 = await PolykeyAgent.createPolykeyAgent({ password, nodePath: path.join(dataDir2, 'remoteNode2'), networkConfig: { - proxyHost: '127.0.0.1' as Host, + proxyHost: serverHost, }, logger: logger.getChild('remoteNode2'), }); @@ -149,6 +156,10 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { keyManager, logger: logger.getChild('NodeGraph'), }); + queue = new Queue({ + logger: logger.getChild('queue'), + }); + await queue.start(); const tlsConfig = { keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, certChainPem: keysUtils.certToPem(keyManager.getRootCert()), @@ -174,6 +185,7 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { }); afterEach(async () => { + await queue.stop(); await nodeGraph.stop(); await nodeGraph.destroy(); await db.stop(); @@ -192,22 +204,21 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { keyManager, nodeGraph, proxy, + queue, logger: nodeConnectionManagerLogger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // @ts-ignore: kidnap connections const connections = nodeConnectionManager.connections; - const initialConnLock = connections.get( - remoteNodeId1.toString() as NodeIdString, - ); - expect(initialConnLock).toBeUndefined(); + // @ts-ignore: kidnap connectionLocks + const connectionLocks = nodeConnectionManager.connectionLocks; + const initialConnection = connections.get(remoteNodeIdString1); + expect(initialConnection).toBeUndefined(); await nodeConnectionManager.withConnF(remoteNodeId1, nop); - const finalConnLock = connections.get( - remoteNodeId1.toString() as NodeIdString, - ); + const finalConnection = connections.get(remoteNodeIdString1); // Check entry is in map and lock is released - expect(finalConnLock).toBeDefined(); - expect(finalConnLock?.lock.isLocked()).toBeFalsy(); + expect(finalConnection).toBeDefined(); + expect(connectionLocks.isLocked(remoteNodeIdString1)).toBeFalsy(); } finally { await nodeConnectionManager?.stop(); } @@ -219,37 +230,29 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { keyManager, nodeGraph, proxy, + queue, logger: nodeConnectionManagerLogger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // @ts-ignore: kidnap connections const connections = nodeConnectionManager.connections; - const initialConnLock = connections.get( - remoteNodeId1.toString() as NodeIdString, - ); - expect(initialConnLock).toBeUndefined(); + // @ts-ignore: kidnap connectionLocks + const connectionLocks = nodeConnectionManager.connectionLocks; + const initialConnection = connections.get(remoteNodeIdString1); + expect(initialConnection).toBeUndefined(); await withF( [await nodeConnectionManager.acquireConnection(remoteNodeId1)], async (conn) => { expect(conn).toBeDefined(); - const intermediaryConnLock = connections.get( - remoteNodeId1.toString() as NodeIdString, - ); - expect(intermediaryConnLock).toBeDefined(); - expect( - // @ts-ignore get the protected readersLock - intermediaryConnLock?.lock.readersLock.isLocked(), - ).toBeTruthy(); - // @ts-ignore get the protected writersLock - expect(intermediaryConnLock?.lock.writersLock.isLocked()).toBeFalsy(); + const intermediaryConnection = connections.get(remoteNodeIdString1); + expect(intermediaryConnection).toBeDefined(); + expect(connectionLocks.isLocked(remoteNodeIdString1)).toBeTruthy(); }, ); - const finalConnLock = connections.get( - remoteNodeId1.toString() as NodeIdString, - ); - expect(finalConnLock).toBeDefined(); + const finalConnection = connections.get(remoteNodeIdString1); + expect(finalConnection).toBeDefined(); // Neither write nor read lock should be locked now - expect(finalConnLock?.lock.isLocked()).toBeFalsy(); + expect(connectionLocks.isLocked(remoteNodeIdString1)).toBeFalsy(); } finally { await nodeConnectionManager?.stop(); } @@ -262,28 +265,23 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { keyManager, nodeGraph, proxy, + queue, logger: nodeConnectionManagerLogger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // @ts-ignore: kidnap connections const connections = nodeConnectionManager.connections; - const initialConnLock = connections.get( - remoteNodeId1.toString() as NodeIdString, - ); - expect(initialConnLock).toBeUndefined(); + // @ts-ignore: kidnap connectionLocks + const connectionLocks = nodeConnectionManager.connectionLocks; + const initialConnection = connections.get(remoteNodeIdString1); + expect(initialConnection).toBeUndefined(); await nodeConnectionManager.withConnF(remoteNodeId1, async () => { - expect( - connections - .get(remoteNodeId1.toString() as NodeIdString) - ?.lock?.isLocked(), - ).toBe(true); + expect(connectionLocks.isLocked(remoteNodeIdString1)).toBe(true); }); - const finalConnLock = connections.get( - remoteNodeId1.toString() as NodeIdString, - ); + const finalConnection = connections.get(remoteNodeIdString1); // Check entry is in map and lock is released - expect(finalConnLock).toBeDefined(); - expect(finalConnLock?.lock.isLocked()).toBeFalsy(); + expect(finalConnection).toBeDefined(); + expect(connectionLocks.isLocked(remoteNodeIdString1)).toBeFalsy(); } finally { await nodeConnectionManager?.stop(); } @@ -296,16 +294,17 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { keyManager, nodeGraph, proxy, + queue, logger: nodeConnectionManagerLogger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // @ts-ignore: kidnap connections const connections = nodeConnectionManager.connections; - const initialConnLock = connections.get( - remoteNodeId1.toString() as NodeIdString, - ); - expect(initialConnLock).toBeUndefined(); + // @ts-ignore: kidnap connectionLocks + const connectionLocks = nodeConnectionManager.connectionLocks; + const initialConnection = connections.get(remoteNodeIdString1); + expect(initialConnection).toBeUndefined(); const testGenerator = async function* () { for (let i = 0; i < 10; i++) { @@ -314,7 +313,7 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { }; // Creating the generator - const gen = await nodeConnectionManager.withConnG( + const gen = nodeConnectionManager.withConnG( remoteNodeId1, async function* () { yield* testGenerator(); @@ -322,32 +321,20 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { ); // Connection is not created yet, no locking applied - expect( - connections.get(remoteNodeId1.toString() as NodeIdString), - ).not.toBeDefined(); + expect(connections.get(remoteNodeIdString1)).not.toBeDefined(); // Iterating over generator for await (const _ of gen) { // Should be locked for duration of stream - expect( - connections - .get(remoteNodeId1.toString() as NodeIdString) - ?.lock?.isLocked(), - ).toBe(true); + expect(connectionLocks.isLocked(remoteNodeIdString1)).toBe(true); } // Unlocked after stream finished - expect( - connections - .get(remoteNodeId1.toString() as NodeIdString) - ?.lock?.isLocked(), - ).toBe(false); + expect(connectionLocks.isLocked(remoteNodeIdString1)).toBe(false); - const finalConnLock = connections.get( - remoteNodeId1.toString() as NodeIdString, - ); + const finalConnection = connections.get(remoteNodeIdString1); // Check entry is in map and lock is released - expect(finalConnLock).toBeDefined(); - expect(finalConnLock?.lock.isLocked()).toBe(false); + expect(finalConnection).toBeDefined(); + expect(connectionLocks.isLocked(remoteNodeIdString1)).toBe(false); } finally { await nodeConnectionManager?.stop(); } @@ -360,10 +347,11 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { keyManager, nodeGraph, proxy, + queue, connConnectTime: 500, logger: nodeConnectionManagerLogger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // Add the dummy node await nodeGraph.setNode(dummyNodeId, { host: '125.0.0.1' as Host, @@ -371,17 +359,21 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { }); // @ts-ignore: kidnap connection map const connections = nodeConnectionManager.connections; + // @ts-ignore: kidnap connectionLocks + const connectionLocks = nodeConnectionManager.connectionLocks; expect(connections.size).toBe(0); await expect(() => nodeConnectionManager?.withConnF(dummyNodeId, nop), ).rejects.toThrow(nodesErrors.ErrorNodeConnectionTimeout); - expect(connections.size).toBe(1); + expect(connections.size).toBe(0); const connLock = connections.get(dummyNodeId.toString() as NodeIdString); // There should still be an entry in the connection map, but it should // only contain a lock - no connection - expect(connLock).toBeDefined(); - expect(connLock?.lock).toBeDefined(); + expect(connLock).not.toBeDefined(); + expect( + connectionLocks.isLocked(dummyNodeId.toString() as NodeIdString), + ).toBe(false); expect(connLock?.connection).toBeUndefined(); // Undo the initial dummy node add @@ -397,16 +389,15 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { keyManager, nodeGraph, proxy, + queue, logger: nodeConnectionManagerLogger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // @ts-ignore accessing protected NodeConnectionMap const connections = nodeConnectionManager.connections; expect(connections.size).toBe(0); - const initialConnLock = connections.get( - remoteNodeId1.toString() as NodeIdString, - ); - expect(initialConnLock).toBeUndefined(); + const initialConnection = connections.get(remoteNodeIdString1); + expect(initialConnection).toBeUndefined(); await nodeConnectionManager.withConnF(remoteNodeId1, nop); // Check we only have this single connection expect(connections.size).toBe(1); @@ -425,16 +416,17 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { keyManager, nodeGraph, proxy, + queue, logger: nodeConnectionManagerLogger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // @ts-ignore accessing protected NodeConnectionMap const connections = nodeConnectionManager.connections; + // @ts-ignore: kidnap connectionLocks + const connectionLocks = nodeConnectionManager.connectionLocks; expect(connections.size).toBe(0); - const initialConnLock = connections.get( - remoteNodeId1.toString() as NodeIdString, - ); - expect(initialConnLock).toBeUndefined(); + const initialConnection = connections.get(remoteNodeIdString1); + expect(initialConnection).toBeUndefined(); // Concurrently create connection to same target await Promise.all([ nodeConnectionManager.withConnF(remoteNodeId1, nop), @@ -442,12 +434,10 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { ]); // Check only 1 connection exists expect(connections.size).toBe(1); - const finalConnLock = connections.get( - remoteNodeId1.toString() as NodeIdString, - ); + const finalConnection = connections.get(remoteNodeIdString1); // Check entry is in map and lock is released - expect(finalConnLock).toBeDefined(); - expect(finalConnLock?.lock.isLocked()).toBeFalsy(); + expect(finalConnection).toBeDefined(); + expect(connectionLocks.isLocked(remoteNodeIdString1)).toBeFalsy(); } finally { await nodeConnectionManager?.stop(); } @@ -460,31 +450,28 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { keyManager, nodeGraph, proxy, + queue, logger: nodeConnectionManagerLogger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // @ts-ignore: kidnap connections const connections = nodeConnectionManager.connections; - const initialConnLock = connections.get( - remoteNodeId1.toString() as NodeIdString, - ); - expect(initialConnLock).toBeUndefined(); + // @ts-ignore: kidnap connectionLocks + const connectionLocks = nodeConnectionManager.connectionLocks; + const initialConnection = connections.get(remoteNodeIdString1); + expect(initialConnection).toBeUndefined(); await nodeConnectionManager.withConnF(remoteNodeId1, nop); - const midConnAndLock = connections.get( - remoteNodeId1.toString() as NodeIdString, - ); + const midConnAndLock = connections.get(remoteNodeIdString1); // Check entry is in map and lock is released expect(midConnAndLock).toBeDefined(); - expect(midConnAndLock?.lock.isLocked()).toBeFalsy(); + expect(connectionLocks.isLocked(remoteNodeIdString1)).toBeFalsy(); // Destroying the connection // @ts-ignore: private method await nodeConnectionManager.destroyConnection(remoteNodeId1); - const finalConnAndLock = connections.get( - remoteNodeId1.toString() as NodeIdString, - ); - expect(finalConnAndLock).toBeDefined(); - expect(finalConnAndLock?.lock.isLocked()).toBeFalsy(); + const finalConnAndLock = connections.get(remoteNodeIdString1); + expect(finalConnAndLock).not.toBeDefined(); + expect(connectionLocks.isLocked(remoteNodeIdString1)).toBeFalsy(); expect(finalConnAndLock?.connection).toBeUndefined(); } finally { await nodeConnectionManager?.stop(); @@ -497,9 +484,10 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { keyManager, nodeGraph, proxy, + queue, logger: nodeConnectionManagerLogger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // Do testing // set up connections await nodeConnectionManager.withConnF(remoteNodeId1, nop); @@ -507,24 +495,109 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { // @ts-ignore: Hijack connection map const connections = nodeConnectionManager.connections; + // @ts-ignore: kidnap connectionLocks + const connectionLocks = nodeConnectionManager.connectionLocks; expect(connections.size).toBe(2); - for (const [, connAndLock] of connections) { + for (const [nodeIdString, connAndLock] of connections) { expect(connAndLock.connection).toBeDefined(); expect(connAndLock.timer).toBeDefined(); - expect(connAndLock.lock).toBeDefined(); + expect(connectionLocks.isLocked(nodeIdString)).toBeDefined(); } // Destroying connections await nodeConnectionManager.stop(); - expect(connections.size).toBe(2); - for (const [, connAndLock] of connections) { + expect(connections.size).toBe(0); + for (const [nodeIdString, connAndLock] of connections) { expect(connAndLock.connection).toBeUndefined(); expect(connAndLock.timer).toBeUndefined(); - expect(connAndLock.lock).toBeDefined(); + expect(connectionLocks.isLocked(nodeIdString)).toBeDefined(); } } finally { // Clean up await nodeConnectionManager?.stop(); } }); + + // New ping tests + test('should ping node with address', async () => { + // NodeConnectionManager under test + let nodeConnectionManager: NodeConnectionManager | undefined; + try { + nodeConnectionManager = new NodeConnectionManager({ + keyManager, + nodeGraph, + proxy, + queue, + logger: nodeConnectionManagerLogger, + }); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); + await nodeConnectionManager.pingNode( + remoteNodeId1, + remoteNode1.proxy.getProxyHost(), + remoteNode1.proxy.getProxyPort(), + ); + } finally { + await nodeConnectionManager?.stop(); + } + }); + test('should fail to ping non existent node', async () => { + // NodeConnectionManager under test + let nodeConnectionManager: NodeConnectionManager | undefined; + try { + nodeConnectionManager = new NodeConnectionManager({ + keyManager, + nodeGraph, + proxy, + queue, + logger: nodeConnectionManagerLogger, + }); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); + + // Pinging node + expect( + await nodeConnectionManager.pingNode( + remoteNodeId1, + '127.1.2.3' as Host, + 55555 as Port, + timerStart(1000), + ), + ).toEqual(false); + } finally { + await nodeConnectionManager?.stop(); + } + }); + test('should fail to ping node if NodeId does not match', async () => { + // NodeConnectionManager under test + let nodeConnectionManager: NodeConnectionManager | undefined; + try { + nodeConnectionManager = new NodeConnectionManager({ + keyManager, + nodeGraph, + proxy, + queue, + logger: nodeConnectionManagerLogger, + }); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); + + expect( + await nodeConnectionManager.pingNode( + remoteNodeId1, + remoteNode2.proxy.getProxyHost(), + remoteNode2.proxy.getProxyPort(), + timerStart(1000), + ), + ).toEqual(false); + + expect( + await nodeConnectionManager.pingNode( + remoteNodeId2, + remoteNode1.proxy.getProxyHost(), + remoteNode1.proxy.getProxyPort(), + timerStart(1000), + ), + ).toEqual(false); + } finally { + await nodeConnectionManager?.stop(); + } + }); }); diff --git a/tests/nodes/NodeConnectionManager.seednodes.test.ts b/tests/nodes/NodeConnectionManager.seednodes.test.ts index b5ecf3e3c..63ba90e9d 100644 --- a/tests/nodes/NodeConnectionManager.seednodes.test.ts +++ b/tests/nodes/NodeConnectionManager.seednodes.test.ts @@ -1,11 +1,13 @@ -import type { NodeId, SeedNodes } from '@/nodes/types'; +import type { NodeId, NodeIdEncoded, SeedNodes } from '@/nodes/types'; import type { Host, Port } from '@/network/types'; +import type { Sigchain } from '@/sigchain'; import fs from 'fs'; import path from 'path'; import os from 'os'; import { DB } from '@matrixai/db'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { IdInternal } from '@matrixai/id'; +import NodeManager from '@/nodes/NodeManager'; import PolykeyAgent from '@/PolykeyAgent'; import KeyManager from '@/keys/KeyManager'; import NodeGraph from '@/nodes/NodeGraph'; @@ -15,6 +17,7 @@ import Proxy from '@/network/Proxy'; import * as nodesUtils from '@/nodes/utils'; import * as keysUtils from '@/keys/utils'; import * as grpcUtils from '@/grpc/utils'; +import Queue from '@/nodes/Queue'; describe(`${NodeConnectionManager.name} seed nodes test`, () => { const logger = new Logger( @@ -77,6 +80,10 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { keysUtils, 'generateDeterministicKeyPair', ); + const dummyNodeManager = { + setNode: jest.fn(), + refreshBucketQueueAdd: jest.fn(), + } as unknown as NodeManager; beforeAll(async () => { mockedGenerateDeterministicKeyPair.mockImplementation((bits, _) => { @@ -116,6 +123,13 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { }); beforeEach(async () => { + // Clearing nodes from graphs + for await (const [nodeId] of remoteNode1.nodeGraph.getNodes()) { + await remoteNode1.nodeGraph.unsetNode(nodeId); + } + for await (const [nodeId] of remoteNode2.nodeGraph.getNodes()) { + await remoteNode2.nodeGraph.unsetNode(nodeId); + } dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); @@ -179,15 +193,29 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { // Seed nodes test('starting should add seed nodes to the node graph', async () => { let nodeConnectionManager: NodeConnectionManager | undefined; + let nodeManager: NodeManager | undefined; try { nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue: new Queue({ + logger: logger.getChild('queue'), + }), seedNodes: dummySeedNodes, logger: logger, }); - await nodeConnectionManager.start(); + nodeManager = new NodeManager({ + db, + keyManager, + logger, + nodeConnectionManager, + nodeGraph, + queue: {} as Queue, + sigchain: {} as Sigchain, + }); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); const seedNodes = nodeConnectionManager.getSeedNodes(); expect(seedNodes).toContainEqual(nodeId1); expect(seedNodes).toContainEqual(nodeId2); @@ -199,6 +227,7 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { } finally { // Clean up await nodeConnectionManager?.stop(); + await nodeManager?.stop(); } }); test('should get seed nodes', async () => { @@ -207,10 +236,13 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { keyManager, nodeGraph, proxy, + queue: new Queue({ + logger: logger.getChild('queue'), + }), seedNodes: dummySeedNodes, logger: logger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); try { const seedNodes = nodeConnectionManager.getSeedNodes(); expect(seedNodes).toHaveLength(3); @@ -223,6 +255,18 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { }); test('should synchronise nodeGraph', async () => { let nodeConnectionManager: NodeConnectionManager | undefined; + let nodeManager: NodeManager | undefined; + let queue: Queue | undefined; + const mockedRefreshBucket = jest.spyOn( + NodeManager.prototype, + 'refreshBucket', + ); + mockedRefreshBucket.mockImplementation(async () => {}); + const mockedPingNode = jest.spyOn( + NodeConnectionManager.prototype, + 'pingNode', + ); + mockedPingNode.mockImplementation(async () => true); try { const seedNodes: SeedNodes = {}; seedNodes[nodesUtils.encodeNodeId(remoteNodeId1)] = { @@ -233,13 +277,26 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { host: remoteNode2.proxy.getProxyHost(), port: remoteNode2.proxy.getProxyPort(), }; + queue = new Queue({ logger }); nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, seedNodes, logger: logger, }); + nodeManager = new NodeManager({ + db, + keyManager, + logger, + nodeConnectionManager, + nodeGraph, + queue, + sigchain: {} as Sigchain, + }); + await queue.start(); + await nodeManager.start(); await remoteNode1.nodeGraph.setNode(nodeId1, { host: serverHost, port: serverPort, @@ -248,17 +305,97 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { host: serverHost, port: serverPort, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager }); await nodeConnectionManager.syncNodeGraph(); expect(await nodeGraph.getNode(nodeId1)).toBeDefined(); expect(await nodeGraph.getNode(nodeId2)).toBeDefined(); expect(await nodeGraph.getNode(dummyNodeId)).toBeUndefined(); } finally { + mockedRefreshBucket.mockRestore(); + mockedPingNode.mockRestore(); + await nodeManager?.stop(); + await nodeConnectionManager?.stop(); + await queue?.stop(); + } + }); + test('should call refreshBucket when syncing nodeGraph', async () => { + let nodeConnectionManager: NodeConnectionManager | undefined; + let nodeManager: NodeManager | undefined; + let queue: Queue | undefined; + const mockedRefreshBucket = jest.spyOn( + NodeManager.prototype, + 'refreshBucket', + ); + mockedRefreshBucket.mockImplementation(async () => {}); + const mockedPingNode = jest.spyOn( + NodeConnectionManager.prototype, + 'pingNode', + ); + mockedPingNode.mockImplementation(async () => true); + try { + const seedNodes: SeedNodes = {}; + seedNodes[nodesUtils.encodeNodeId(remoteNodeId1)] = { + host: remoteNode1.proxy.getProxyHost(), + port: remoteNode1.proxy.getProxyPort(), + }; + seedNodes[nodesUtils.encodeNodeId(remoteNodeId2)] = { + host: remoteNode2.proxy.getProxyHost(), + port: remoteNode2.proxy.getProxyPort(), + }; + queue = new Queue({ logger }); + nodeConnectionManager = new NodeConnectionManager({ + keyManager, + nodeGraph, + proxy, + queue, + seedNodes, + logger: logger, + }); + nodeManager = new NodeManager({ + db, + keyManager, + logger, + nodeConnectionManager, + nodeGraph, + sigchain: {} as Sigchain, + queue, + }); + await queue.start(); + await nodeManager.start(); + await remoteNode1.nodeGraph.setNode(nodeId1, { + host: serverHost, + port: serverPort, + }); + await remoteNode2.nodeGraph.setNode(nodeId2, { + host: serverHost, + port: serverPort, + }); + await nodeConnectionManager.start({ nodeManager }); + await nodeConnectionManager.syncNodeGraph(); + await nodeManager.refreshBucketQueueDrained(); + expect(mockedRefreshBucket).toHaveBeenCalled(); + } finally { + mockedRefreshBucket.mockRestore(); + mockedPingNode.mockRestore(); + await nodeManager?.stop(); await nodeConnectionManager?.stop(); + await queue?.stop(); } }); test('should handle an offline seed node when synchronising nodeGraph', async () => { let nodeConnectionManager: NodeConnectionManager | undefined; + let nodeManager: NodeManager | undefined; + let queue: Queue | undefined; + const mockedRefreshBucket = jest.spyOn( + NodeManager.prototype, + 'refreshBucket', + ); + mockedRefreshBucket.mockImplementation(async () => {}); + const mockedPingNode = jest.spyOn( + NodeConnectionManager.prototype, + 'pingNode', + ); + mockedPingNode.mockImplementation(async () => true); try { const seedNodes: SeedNodes = {}; seedNodes[nodesUtils.encodeNodeId(remoteNodeId1)] = { @@ -282,22 +419,129 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { host: serverHost, port: serverPort, }); + queue = new Queue({ logger }); nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, proxy, + queue, seedNodes, connConnectTime: 500, logger: logger, }); - await nodeConnectionManager.start(); + nodeManager = new NodeManager({ + db, + keyManager, + logger, + nodeConnectionManager, + nodeGraph, + sigchain: {} as Sigchain, + queue, + }); + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); // This should complete without error await nodeConnectionManager.syncNodeGraph(); // Information on remotes are found expect(await nodeGraph.getNode(nodeId1)).toBeDefined(); expect(await nodeGraph.getNode(nodeId2)).toBeDefined(); } finally { + mockedRefreshBucket.mockRestore(); + mockedPingNode.mockRestore(); await nodeConnectionManager?.stop(); + await nodeManager?.stop(); + await queue?.stop(); } }); + test( + 'should expand the network when nodes enter', + async () => { + // Using a single seed node we need to check that each entering node adds itself to the seed node. + // Also need to check that the new nodes can be seen in the network. + let node1: PolykeyAgent | undefined; + let node2: PolykeyAgent | undefined; + const seedNodes: SeedNodes = {}; + seedNodes[nodesUtils.encodeNodeId(remoteNodeId1)] = { + host: remoteNode1.proxy.getProxyHost(), + port: remoteNode1.proxy.getProxyPort(), + }; + seedNodes[nodesUtils.encodeNodeId(remoteNodeId2)] = { + host: remoteNode2.proxy.getProxyHost(), + port: remoteNode2.proxy.getProxyPort(), + }; + const mockedPingNode = jest.spyOn( + NodeConnectionManager.prototype, + 'pingNode', + ); + mockedPingNode.mockImplementation(async () => true); + try { + logger.setLevel(LogLevel.WARN); + node1 = await PolykeyAgent.createPolykeyAgent({ + nodePath: path.join(dataDir, 'node1'), + password: 'password', + networkConfig: { + proxyHost: localHost, + agentHost: localHost, + clientHost: localHost, + forwardHost: localHost, + }, + seedNodes, + logger, + }); + node2 = await PolykeyAgent.createPolykeyAgent({ + nodePath: path.join(dataDir, 'node2'), + password: 'password', + networkConfig: { + proxyHost: localHost, + agentHost: localHost, + clientHost: localHost, + forwardHost: localHost, + }, + seedNodes, + logger, + }); + + await node1.queue.drained(); + await node1.nodeManager.refreshBucketQueueDrained(); + await node2.queue.drained(); + await node2.nodeManager.refreshBucketQueueDrained(); + + const getAllNodes = async (node: PolykeyAgent) => { + const nodes: Array = []; + for await (const [nodeId] of node.nodeGraph.getNodes()) { + nodes.push(nodesUtils.encodeNodeId(nodeId)); + } + return nodes; + }; + const rNode1Nodes = await getAllNodes(remoteNode1); + const rNode2Nodes = await getAllNodes(remoteNode2); + const node1Nodes = await getAllNodes(node1); + const node2Nodes = await getAllNodes(node2); + + const nodeIdR1 = nodesUtils.encodeNodeId(remoteNodeId1); + const nodeIdR2 = nodesUtils.encodeNodeId(remoteNodeId2); + const nodeId1 = nodesUtils.encodeNodeId(node1.keyManager.getNodeId()); + const nodeId2 = nodesUtils.encodeNodeId(node2.keyManager.getNodeId()); + expect(rNode1Nodes).toContain(nodeId1); + expect(rNode1Nodes).toContain(nodeId2); + expect(rNode2Nodes).toContain(nodeId1); + expect(rNode2Nodes).toContain(nodeId2); + expect(node1Nodes).toContain(nodeIdR1); + expect(node1Nodes).toContain(nodeIdR2); + expect(node1Nodes).toContain(nodeId2); + expect(node2Nodes).toContain(nodeIdR1); + expect(node2Nodes).toContain(nodeIdR2); + expect(node2Nodes).toContain(nodeId1); + } finally { + mockedPingNode.mockRestore(); + logger.setLevel(LogLevel.WARN); + await node1?.stop(); + await node1?.destroy(); + await node2?.stop(); + await node2?.destroy(); + } + }, + global.defaultTimeout * 2, + ); }); diff --git a/tests/nodes/NodeConnectionManager.termination.test.ts b/tests/nodes/NodeConnectionManager.termination.test.ts index 7cc443d07..86598e78c 100644 --- a/tests/nodes/NodeConnectionManager.termination.test.ts +++ b/tests/nodes/NodeConnectionManager.termination.test.ts @@ -1,6 +1,8 @@ import type { AddressInfo } from 'net'; import type { NodeId, NodeIdString, SeedNodes } from '@/nodes/types'; import type { Host, Port, TLSConfig } from '@/network/types'; +import type NodeManager from '@/nodes/NodeManager'; +import type Queue from '@/nodes/Queue'; import net from 'net'; import fs from 'fs'; import path from 'path'; @@ -85,6 +87,7 @@ describe(`${NodeConnectionManager.name} termination test`, () => { keysUtils, 'generateDeterministicKeyPair', ); + const dummyNodeManager = { setNode: jest.fn() } as unknown as NodeManager; beforeEach(async () => { mockedGenerateDeterministicKeyPair.mockImplementation((bits, _) => { @@ -244,10 +247,11 @@ describe(`${NodeConnectionManager.name} termination test`, () => { keyManager, nodeGraph, proxy, + queue: {} as Queue, logger: logger, connConnectTime: 2000, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // Attempt a connection await expect( @@ -284,10 +288,11 @@ describe(`${NodeConnectionManager.name} termination test`, () => { keyManager, nodeGraph, proxy, + queue: {} as Queue, logger: logger, connConnectTime: 2000, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // Attempt a connection const resultP = nodeConnectionManager.withConnF(dummyNodeId, async () => { @@ -327,10 +332,11 @@ describe(`${NodeConnectionManager.name} termination test`, () => { keyManager, nodeGraph, proxy, + queue: {} as Queue, logger: logger, connConnectTime: 2000, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // Attempt a connection const connectionAttemptP = nodeConnectionManager.withConnF( @@ -370,13 +376,16 @@ describe(`${NodeConnectionManager.name} termination test`, () => { keyManager, nodeGraph, proxy: defaultProxy, + queue: {} as Queue, logger: logger, connConnectTime: 2000, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // @ts-ignore: kidnapping connection map const connections = nodeConnectionManager.connections; + // @ts-ignore: kidnapping connection map + const connectionLocks = nodeConnectionManager.connectionLocks; // Connections should be empty expect(connections.size).toBe(0); @@ -392,10 +401,9 @@ describe(`${NodeConnectionManager.name} termination test`, () => { await polykeyAgent.stop(); // Connection should be removed expect(connections.size).toBe(1); - const connAndLock = connections.get( - agentNodeId.toString() as NodeIdString, - ); - expect(connAndLock?.lock.isLocked()).toBe(false); + expect( + connectionLocks.isLocked(agentNodeId.toString() as NodeIdString), + ).toBe(false); if (firstConnection != null) { expect(firstConnection[destroyed]).toBe(true); } @@ -426,13 +434,16 @@ describe(`${NodeConnectionManager.name} termination test`, () => { keyManager, nodeGraph, proxy: defaultProxy, + queue: {} as Queue, logger: logger, connConnectTime: 2000, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // @ts-ignore: kidnapping connection map const connections = nodeConnectionManager.connections; + // @ts-ignore: kidnapping connection map + const connectionLocks = nodeConnectionManager.connectionLocks; // Connections should be empty expect(connections.size).toBe(0); @@ -464,11 +475,10 @@ describe(`${NodeConnectionManager.name} termination test`, () => { await expect(withConnectionP).rejects.toThrow(); // Connection should be removed - expect(connections.size).toBe(1); - const connAndLock = connections.get( - agentNodeId.toString() as NodeIdString, - ); - expect(connAndLock?.lock.isLocked()).toBe(false); + expect(connections.size).toBe(0); + expect( + connectionLocks.isLocked(agentNodeId.toString() as NodeIdString), + ).toBe(false); if (firstConnection != null) { expect(firstConnection[destroyed]).toBe(true); } @@ -504,13 +514,16 @@ describe(`${NodeConnectionManager.name} termination test`, () => { keyManager, nodeGraph, proxy: defaultProxy, + queue: {} as Queue, logger: logger, connConnectTime: 2000, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // @ts-ignore: kidnapping connection map const connections = nodeConnectionManager.connections; + // @ts-ignore: kidnapping connection map + const connectionLocks = nodeConnectionManager.connectionLocks; // Connections should be empty expect(connections.size).toBe(0); @@ -540,11 +553,10 @@ describe(`${NodeConnectionManager.name} termination test`, () => { await expect(responseP).rejects.toThrow(); // Connection should be removed - expect(connections.size).toBe(1); - const connAndLock = connections.get( - agentNodeId.toString() as NodeIdString, - ); - expect(connAndLock?.lock.isLocked()).toBe(false); + expect(connections.size).toBe(0); + expect( + connectionLocks.isLocked(agentNodeId.toString() as NodeIdString), + ).toBe(false); if (firstConnection != null) { expect(firstConnection[destroyed]).toBe(true); } @@ -575,13 +587,16 @@ describe(`${NodeConnectionManager.name} termination test`, () => { keyManager, nodeGraph, proxy: defaultProxy, + queue: {} as Queue, logger: logger, connConnectTime: 2000, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // @ts-ignore: kidnapping connection map const connections = nodeConnectionManager.connections; + // @ts-ignore: kidnapping connection map + const connectionLocks = nodeConnectionManager.connectionLocks; // Connections should be empty expect(connections.size).toBe(0); @@ -594,7 +609,7 @@ describe(`${NodeConnectionManager.name} termination test`, () => { const firstConnection = firstConnAndLock?.connection; // Resolves if the shutdownCallback was called - const gen = await nodeConnectionManager.withConnG( + const gen = nodeConnectionManager.withConnG( agentNodeId, async function* (): AsyncGenerator { // Throw an error here @@ -616,11 +631,10 @@ describe(`${NodeConnectionManager.name} termination test`, () => { }).rejects.toThrow(); // Connection should be removed - expect(connections.size).toBe(1); - const connAndLock = connections.get( - agentNodeId.toString() as NodeIdString, - ); - expect(connAndLock?.lock.isLocked()).toBe(false); + expect(connections.size).toBe(0); + expect( + connectionLocks.isLocked(agentNodeId.toString() as NodeIdString), + ).toBe(false); if (firstConnection != null) { expect(firstConnection[destroyed]).toBe(true); } @@ -651,13 +665,16 @@ describe(`${NodeConnectionManager.name} termination test`, () => { keyManager, nodeGraph, proxy: defaultProxy, + queue: {} as Queue, logger: logger, connConnectTime: 2000, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // @ts-ignore: kidnapping connection map const connections = nodeConnectionManager.connections; + // @ts-ignore: kidnapping connection map + const connectionLocks = nodeConnectionManager.connectionLocks; // Connections should be empty expect(connections.size).toBe(0); @@ -693,10 +710,9 @@ describe(`${NodeConnectionManager.name} termination test`, () => { // Connection should be removed expect(connections.size).toBe(1); - const connAndLock = connections.get( - agentNodeId.toString() as NodeIdString, - ); - expect(connAndLock?.lock.isLocked()).toBe(false); + expect( + connectionLocks.isLocked(agentNodeId.toString() as NodeIdString), + ).toBe(false); if (firstConnection != null) { expect(firstConnection[destroyed]).toBe(true); } @@ -727,13 +743,16 @@ describe(`${NodeConnectionManager.name} termination test`, () => { keyManager, nodeGraph, proxy: defaultProxy, + queue: {} as Queue, logger: logger, connConnectTime: 2000, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // @ts-ignore: kidnapping connection map const connections = nodeConnectionManager.connections; + // @ts-ignore: kidnapping connection map + const connectionLocks = nodeConnectionManager.connectionLocks; // Connections should be empty expect(connections.size).toBe(0); @@ -763,10 +782,9 @@ describe(`${NodeConnectionManager.name} termination test`, () => { // Connection should be removed expect(connections.size).toBe(1); - const connAndLock = connections.get( - agentNodeId.toString() as NodeIdString, - ); - expect(connAndLock?.lock.isLocked()).toBe(false); + expect( + connectionLocks.isLocked(agentNodeId.toString() as NodeIdString), + ).toBe(false); if (firstConnection != null) { expect(firstConnection[destroyed]).toBe(true); } diff --git a/tests/nodes/NodeConnectionManager.timeout.test.ts b/tests/nodes/NodeConnectionManager.timeout.test.ts index 5e48eaaaf..3f73a1a39 100644 --- a/tests/nodes/NodeConnectionManager.timeout.test.ts +++ b/tests/nodes/NodeConnectionManager.timeout.test.ts @@ -1,5 +1,7 @@ import type { NodeId, NodeIdString, SeedNodes } from '@/nodes/types'; import type { Host, Port } from '@/network/types'; +import type NodeManager from 'nodes/NodeManager'; +import type Queue from '@/nodes/Queue'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -78,6 +80,7 @@ describe(`${NodeConnectionManager.name} timeout test`, () => { keysUtils, 'generateDeterministicKeyPair', ); + const dummyNodeManager = { setNode: jest.fn() } as unknown as NodeManager; beforeAll(async () => { mockedGenerateDeterministicKeyPair.mockImplementation((bits, _) => { @@ -186,19 +189,22 @@ describe(`${NodeConnectionManager.name} timeout test`, () => { keyManager, nodeGraph, proxy, + queue: {} as Queue, connTimeoutTime: 500, logger: nodeConnectionManagerLogger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // @ts-ignore: kidnap connections const connections = nodeConnectionManager.connections; + // @ts-ignore: kidnap connections + const connectionLocks = nodeConnectionManager.connectionLocks; await nodeConnectionManager.withConnF(remoteNodeId1, nop); const connAndLock = connections.get( remoteNodeId1.toString() as NodeIdString, ); // Check entry is in map and lock is released expect(connAndLock).toBeDefined(); - expect(connAndLock?.lock.isLocked()).toBeFalsy(); + expect(connectionLocks.isLocked(remoteNodeId1.toString())).toBeFalsy(); expect(connAndLock?.timer).toBeDefined(); expect(connAndLock?.connection).toBeDefined(); @@ -207,10 +213,8 @@ describe(`${NodeConnectionManager.name} timeout test`, () => { const finalConnAndLock = connections.get( remoteNodeId1.toString() as NodeIdString, ); - expect(finalConnAndLock).toBeDefined(); - expect(finalConnAndLock?.lock.isLocked()).toBeFalsy(); - expect(finalConnAndLock?.timer).toBeUndefined(); - expect(finalConnAndLock?.connection).toBeUndefined(); + expect(finalConnAndLock).not.toBeDefined(); + expect(connectionLocks.isLocked(remoteNodeId1.toString())).toBeFalsy(); } finally { await nodeConnectionManager?.stop(); } @@ -223,19 +227,22 @@ describe(`${NodeConnectionManager.name} timeout test`, () => { keyManager, nodeGraph, proxy, + queue: {} as Queue, connTimeoutTime: 1000, logger: nodeConnectionManagerLogger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // @ts-ignore: kidnap connections const connections = nodeConnectionManager.connections; + // @ts-ignore: kidnap connections + const connectionLocks = nodeConnectionManager.connectionLocks; await nodeConnectionManager.withConnF(remoteNodeId1, nop); const connAndLock = connections.get( remoteNodeId1.toString() as NodeIdString, ); // Check entry is in map and lock is released expect(connAndLock).toBeDefined(); - expect(connAndLock?.lock.isLocked()).toBeFalsy(); + expect(connectionLocks.isLocked(remoteNodeId1.toString())).toBeFalsy(); expect(connAndLock?.timer).toBeDefined(); expect(connAndLock?.connection).toBeDefined(); @@ -251,7 +258,7 @@ describe(`${NodeConnectionManager.name} timeout test`, () => { remoteNodeId1.toString() as NodeIdString, ); expect(midConnAndLock).toBeDefined(); - expect(midConnAndLock?.lock.isLocked()).toBeFalsy(); + expect(connectionLocks.isLocked(remoteNodeId1.toString())).toBeFalsy(); expect(midConnAndLock?.timer).toBeDefined(); expect(midConnAndLock?.connection).toBeDefined(); @@ -260,10 +267,8 @@ describe(`${NodeConnectionManager.name} timeout test`, () => { const finalConnAndLock = connections.get( remoteNodeId1.toString() as NodeIdString, ); - expect(finalConnAndLock).toBeDefined(); - expect(finalConnAndLock?.lock.isLocked()).toBeFalsy(); - expect(finalConnAndLock?.timer).toBeUndefined(); - expect(finalConnAndLock?.connection).toBeUndefined(); + expect(finalConnAndLock).not.toBeDefined(); + expect(connectionLocks.isLocked(remoteNodeId1.toString())).toBeFalsy(); } finally { await nodeConnectionManager?.stop(); } @@ -276,18 +281,21 @@ describe(`${NodeConnectionManager.name} timeout test`, () => { keyManager, nodeGraph, proxy, + queue: {} as Queue, logger: nodeConnectionManagerLogger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); // @ts-ignore: kidnap connections const connections = nodeConnectionManager.connections; + // @ts-ignore: kidnap connections + const connectionLocks = nodeConnectionManager.connectionLocks; await nodeConnectionManager.withConnF(remoteNodeId1, nop); const midConnAndLock = connections.get( remoteNodeId1.toString() as NodeIdString, ); // Check entry is in map and lock is released expect(midConnAndLock).toBeDefined(); - expect(midConnAndLock?.lock.isLocked()).toBeFalsy(); + expect(connectionLocks.isLocked(remoteNodeId1.toString())).toBeFalsy(); expect(midConnAndLock?.timer).toBeDefined(); // Destroying the connection @@ -296,10 +304,8 @@ describe(`${NodeConnectionManager.name} timeout test`, () => { const finalConnAndLock = connections.get( remoteNodeId1.toString() as NodeIdString, ); - expect(finalConnAndLock).toBeDefined(); - expect(finalConnAndLock?.lock.isLocked()).toBeFalsy(); - expect(finalConnAndLock?.connection).toBeUndefined(); - expect(finalConnAndLock?.timer).toBeUndefined(); + expect(finalConnAndLock).not.toBeDefined(); + expect(connectionLocks.isLocked(remoteNodeId1.toString())).toBeFalsy(); } finally { await nodeConnectionManager?.stop(); } diff --git a/tests/nodes/NodeGraph.test.ts b/tests/nodes/NodeGraph.test.ts index 6b9eec700..66b958716 100644 --- a/tests/nodes/NodeGraph.test.ts +++ b/tests/nodes/NodeGraph.test.ts @@ -1,59 +1,46 @@ -import type { Host, Port } from '@/network/types'; -import type { NodeAddress, NodeData, NodeId } from '@/nodes/types'; +import type { + NodeId, + NodeData, + NodeAddress, + NodeBucket, + NodeBucketIndex, +} from '@/nodes/types'; import os from 'os'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { IdInternal } from '@matrixai/id'; -import NodeConnectionManager from '@/nodes/NodeConnectionManager'; import NodeGraph from '@/nodes/NodeGraph'; -import * as nodesErrors from '@/nodes/errors'; import KeyManager from '@/keys/KeyManager'; import * as keysUtils from '@/keys/utils'; -import Proxy from '@/network/Proxy'; import * as nodesUtils from '@/nodes/utils'; -import Sigchain from '@/sigchain/Sigchain'; -import * as nodesTestUtils from './utils'; +import * as nodesErrors from '@/nodes/errors'; +import * as utils from '@/utils'; +import * as testNodesUtils from './utils'; +import * as testUtils from '../utils'; describe(`${NodeGraph.name} test`, () => { - const localHost = '127.0.0.1' as Host; - const port = 0 as Port; const password = 'password'; - let nodeGraph: NodeGraph; - let nodeId: NodeId; - - const nodeId1 = IdInternal.create([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 5, - ]); - const dummyNode = nodesUtils.decodeNodeId( - 'vi3et1hrpv2m2lrplcm7cu913kr45v51cak54vm68anlbvuf83ra0', - )!; - - const logger = new Logger(`${NodeGraph.name} test`, LogLevel.ERROR, [ + const logger = new Logger(`${NodeGraph.name} test`, LogLevel.WARN, [ new StreamHandler(), ]); - let proxy: Proxy; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; let dataDir: string; let keyManager: KeyManager; + let dbKey: Buffer; + let dbPath: string; let db: DB; - let nodeConnectionManager: NodeConnectionManager; - let sigchain: Sigchain; - - const hostGen = (i: number) => `${i}.${i}.${i}.${i}` as Host; - - const mockedGenerateDeterministicKeyPair = jest.spyOn( - keysUtils, - 'generateDeterministicKeyPair', - ); - - beforeEach(async () => { - mockedGenerateDeterministicKeyPair.mockImplementation((bits, _) => { - return keysUtils.generateKeyPair(bits); - }); - + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); @@ -63,559 +50,1002 @@ describe(`${NodeGraph.name} test`, () => { keysPath, logger, }); - proxy = new Proxy({ - authToken: 'auth', - logger: logger, - }); - await proxy.start({ - serverHost: localHost, - serverPort: port, - tlsConfig: { - keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, - certChainPem: await keyManager.getRootCertChainPem(), - }, + dbKey = await keysUtils.generateKey(); + dbPath = `${dataDir}/db`; + }); + afterAll(async () => { + await keyManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, }); - const dbPath = `${dataDir}/db`; + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + beforeEach(async () => { db = await DB.createDB({ dbPath, logger, crypto: { - key: keyManager.dbKey, + key: dbKey, ops: { encrypt: keysUtils.encryptWithKey, decrypt: keysUtils.decryptWithKey, }, }, }); - sigchain = await Sigchain.createSigchain({ - keyManager: keyManager, - db: db, - logger: logger, - }); - nodeGraph = await NodeGraph.createNodeGraph({ + }); + afterEach(async () => { + await db.stop(); + await db.destroy(); + }); + test('get, set and unset node IDs', async () => { + const nodeGraph = await NodeGraph.createNodeGraph({ db, keyManager, logger, }); - nodeConnectionManager = new NodeConnectionManager({ - keyManager: keyManager, - nodeGraph: nodeGraph, - proxy: proxy, - logger: logger, - }); - await nodeConnectionManager.start(); - // Retrieve the NodeGraph reference from NodeManager - nodeId = keyManager.getNodeId(); - }); + let nodeId1: NodeId; + do { + nodeId1 = testNodesUtils.generateRandomNodeId(); + } while (nodeId1.equals(keyManager.getNodeId())); + let nodeId2: NodeId; + do { + nodeId2 = testNodesUtils.generateRandomNodeId(); + } while (nodeId2.equals(keyManager.getNodeId())); - afterEach(async () => { - await db.stop(); - await sigchain.stop(); - await nodeConnectionManager.stop(); - await nodeGraph.stop(); - await keyManager.stop(); - await proxy.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, + await nodeGraph.setNode(nodeId1, { + host: '10.0.0.1', + port: 1234, + } as NodeAddress); + const nodeData1 = await nodeGraph.getNode(nodeId1); + expect(nodeData1).toStrictEqual({ + address: { + host: '10.0.0.1', + port: 1234, + }, + lastUpdated: expect.any(Number), }); + await utils.sleep(1000); + await nodeGraph.setNode(nodeId2, { + host: 'abc.com', + port: 8978, + } as NodeAddress); + const nodeData2 = await nodeGraph.getNode(nodeId2); + expect(nodeData2).toStrictEqual({ + address: { + host: 'abc.com', + port: 8978, + }, + lastUpdated: expect.any(Number), + }); + expect(nodeData2!.lastUpdated > nodeData1!.lastUpdated).toBe(true); + const nodes = await utils.asyncIterableArray(nodeGraph.getNodes()); + expect(nodes).toHaveLength(2); + expect(nodes).toContainEqual([ + nodeId1, + { + address: { + host: '10.0.0.1', + port: 1234, + }, + lastUpdated: expect.any(Number), + }, + ]); + expect(nodes).toContainEqual([ + nodeId2, + { + address: { + host: 'abc.com', + port: 8978, + }, + lastUpdated: expect.any(Number), + }, + ]); + await nodeGraph.unsetNode(nodeId1); + expect(await nodeGraph.getNode(nodeId1)).toBeUndefined(); + expect(await utils.asyncIterableArray(nodeGraph.getNodes())).toStrictEqual([ + [ + nodeId2, + { + address: { + host: 'abc.com', + port: 8978, + }, + lastUpdated: expect.any(Number), + }, + ], + ]); + await nodeGraph.unsetNode(nodeId2); + await nodeGraph.stop(); }); - - test('NodeGraph readiness', async () => { - const nodeGraph2 = await NodeGraph.createNodeGraph({ + test('get all nodes', async () => { + const nodeGraph = await NodeGraph.createNodeGraph({ db, keyManager, logger, }); - // @ts-ignore - await expect(nodeGraph2.destroy()).rejects.toThrow( - nodesErrors.ErrorNodeGraphRunning, - ); - // Should be a noop - await nodeGraph2.start(); - await nodeGraph2.stop(); - await nodeGraph2.destroy(); - await expect(async () => { - await nodeGraph2.start(); - }).rejects.toThrow(nodesErrors.ErrorNodeGraphDestroyed); - await expect(async () => { - await nodeGraph2.getBucket(0); - }).rejects.toThrow(nodesErrors.ErrorNodeGraphNotRunning); - await expect(async () => { - await nodeGraph2.getBucket(0); - }).rejects.toThrow(nodesErrors.ErrorNodeGraphNotRunning); - }); - test('knows node (true and false case)', async () => { - // Known node - const nodeAddress1: NodeAddress = { - host: '127.0.0.1' as Host, - port: 11111 as Port, - }; - await nodeGraph.setNode(nodeId1, nodeAddress1); - expect(await nodeGraph.knowsNode(nodeId1)).toBeTruthy(); - - // Unknown node - expect(await nodeGraph.knowsNode(dummyNode)).toBeFalsy(); - }); - test('finds correct node address', async () => { - // New node added - const newNode2Id = nodeId1; - const newNode2Address = { host: '227.1.1.1', port: 4567 } as NodeAddress; - await nodeGraph.setNode(newNode2Id, newNode2Address); - - // Get node address - const foundAddress = await nodeGraph.getNode(newNode2Id); - expect(foundAddress).toEqual({ host: '227.1.1.1', port: 4567 }); - }); - test('unable to find node address', async () => { - // New node added - const newNode2Id = nodeId1; - const newNode2Address = { host: '227.1.1.1', port: 4567 } as NodeAddress; - await nodeGraph.setNode(newNode2Id, newNode2Address); - - // Get node address (of non-existent node) - const foundAddress = await nodeGraph.getNode(dummyNode); - expect(foundAddress).toBeUndefined(); - }); - test('adds a single node into a bucket', async () => { - // New node added - const newNode2Id = nodesTestUtils.generateNodeIdForBucket(nodeId, 1); - const newNode2Address = { host: '227.1.1.1', port: 4567 } as NodeAddress; - await nodeGraph.setNode(newNode2Id, newNode2Address); - - // Check new node is in retrieved bucket from database - // bucketIndex = 1 as "NODEID1" XOR "NODEID2" = 3 - const bucket = await nodeGraph.getBucket(1); - expect(bucket).toBeDefined(); - expect(bucket![newNode2Id]).toEqual({ - address: { host: '227.1.1.1', port: 4567 }, - lastUpdated: expect.any(Date), + let nodeIds = Array.from({ length: 25 }, () => { + return testNodesUtils.generateRandomNodeId(); }); - }); - test('adds multiple nodes into the same bucket', async () => { - // Add 3 new nodes into bucket 4 - const bucketIndex = 4; - const newNode1Id = nodesTestUtils.generateNodeIdForBucket( - nodeId, - bucketIndex, - 0, + nodeIds = nodeIds.filter( + (nodeId) => !nodeId.equals(keyManager.getNodeId()), ); - const newNode1Address = { host: '4.4.4.4', port: 4444 } as NodeAddress; - await nodeGraph.setNode(newNode1Id, newNode1Address); - - const newNode2Id = nodesTestUtils.generateNodeIdForBucket( - nodeId, - bucketIndex, - 1, + let bucketIndexes: Array; + let nodes: NodeBucket; + nodes = await utils.asyncIterableArray(nodeGraph.getNodes()); + expect(nodes).toHaveLength(0); + for (const nodeId of nodeIds) { + await utils.sleep(100); + await nodeGraph.setNode(nodeId, { + host: '127.0.0.1', + port: 55555, + } as NodeAddress); + } + nodes = await utils.asyncIterableArray(nodeGraph.getNodes()); + expect(nodes).toHaveLength(25); + // Sorted by bucket indexes ascending + bucketIndexes = nodes.map(([nodeId]) => + nodesUtils.bucketIndex(keyManager.getNodeId(), nodeId), ); - const newNode2Address = { host: '5.5.5.5', port: 5555 } as NodeAddress; - await nodeGraph.setNode(newNode2Id, newNode2Address); - - const newNode3Id = nodesTestUtils.generateNodeIdForBucket( - nodeId, - bucketIndex, - 2, + expect( + bucketIndexes.slice(1).every((bucketIndex, i) => { + return bucketIndexes[i] <= bucketIndex; + }), + ).toBe(true); + // Sorted by bucket indexes ascending explicitly + nodes = await utils.asyncIterableArray(nodeGraph.getNodes('asc')); + bucketIndexes = nodes.map(([nodeId]) => + nodesUtils.bucketIndex(keyManager.getNodeId(), nodeId), + ); + expect( + bucketIndexes.slice(1).every((bucketIndex, i) => { + return bucketIndexes[i] <= bucketIndex; + }), + ).toBe(true); + nodes = await utils.asyncIterableArray(nodeGraph.getNodes('desc')); + expect(nodes).toHaveLength(25); + // Sorted by bucket indexes descending + bucketIndexes = nodes.map(([nodeId]) => + nodesUtils.bucketIndex(keyManager.getNodeId(), nodeId), ); - const newNode3Address = { host: '6.6.6.6', port: 6666 } as NodeAddress; - await nodeGraph.setNode(newNode3Id, newNode3Address); - // Based on XOR values, all 3 nodes should appear in bucket 4 - const bucket = await nodeGraph.getBucket(4); - expect(bucket).toBeDefined(); - if (!bucket) fail('bucket should be defined, letting TS know'); - expect(bucket[newNode1Id]).toEqual({ - address: { host: '4.4.4.4', port: 4444 }, - lastUpdated: expect.any(Date), + expect( + bucketIndexes.slice(1).every((bucketIndex, i) => { + return bucketIndexes[i] >= bucketIndex; + }), + ).toBe(true); + await nodeGraph.stop(); + }); + test('setting same node ID throws error', async () => { + const nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger, }); - expect(bucket[newNode2Id]).toEqual({ - address: { host: '5.5.5.5', port: 5555 }, - lastUpdated: expect.any(Date), + await expect( + nodeGraph.setNode(keyManager.getNodeId(), { + host: '127.0.0.1', + port: 55555, + } as NodeAddress), + ).rejects.toThrow(nodesErrors.ErrorNodeGraphSameNodeId); + await nodeGraph.stop(); + }); + test('get bucket with 1 node', async () => { + const nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger, }); - expect(bucket[newNode3Id]).toEqual({ - address: { host: '6.6.6.6', port: 6666 }, - lastUpdated: expect.any(Date), + let nodeId: NodeId; + do { + nodeId = testNodesUtils.generateRandomNodeId(); + } while (nodeId.equals(keyManager.getNodeId())); + // Set one node + await nodeGraph.setNode(nodeId, { + host: '127.0.0.1', + port: 55555, + } as NodeAddress); + const bucketIndex = nodesUtils.bucketIndex(keyManager.getNodeId(), nodeId); + const bucket = await nodeGraph.getBucket(bucketIndex); + expect(bucket).toHaveLength(1); + expect(bucket[0]).toStrictEqual([ + nodeId, + { + address: { + host: '127.0.0.1', + port: 55555, + }, + lastUpdated: expect.any(Number), + }, + ]); + expect(await nodeGraph.getBucketMeta(bucketIndex)).toStrictEqual({ + count: 1, }); - }); - test('adds a single node into different buckets', async () => { - // New node for bucket 3 - const newNode1Id = nodesTestUtils.generateNodeIdForBucket(nodeId, 3); - const newNode1Address = { host: '1.1.1.1', port: 1111 } as NodeAddress; - await nodeGraph.setNode(newNode1Id, newNode1Address); - // New node for bucket 255 (the highest possible bucket) - const newNode2Id = nodesTestUtils.generateNodeIdForBucket(nodeId, 255); - const newNode2Address = { host: '2.2.2.2', port: 2222 } as NodeAddress; - await nodeGraph.setNode(newNode2Id, newNode2Address); - - const bucket3 = await nodeGraph.getBucket(3); - const bucket351 = await nodeGraph.getBucket(255); - if (bucket3 && bucket351) { - expect(bucket3[newNode1Id]).toEqual({ - address: { host: '1.1.1.1', port: 1111 }, - lastUpdated: expect.any(Date), - }); - expect(bucket351[newNode2Id]).toEqual({ - address: { host: '2.2.2.2', port: 2222 }, - lastUpdated: expect.any(Date), - }); + // Adjacent bucket should be empty + let bucketIndex_: number; + if (bucketIndex >= nodeId.length * 8 - 1) { + bucketIndex_ = bucketIndex - 1; + } else if (bucketIndex === 0) { + bucketIndex_ = bucketIndex + 1; } else { - // Should be unreachable - fail('Bucket undefined'); + bucketIndex_ = bucketIndex + 1; } + expect(await nodeGraph.getBucket(bucketIndex_)).toHaveLength(0); + expect(await nodeGraph.getBucketMeta(bucketIndex_)).toStrictEqual({ + count: 0, + }); + await nodeGraph.stop(); }); - test('deletes a single node (and removes bucket)', async () => { - // New node for bucket 2 - const newNode1Id = nodesTestUtils.generateNodeIdForBucket(nodeId, 2); - const newNode1Address = { host: '4.4.4.4', port: 4444 } as NodeAddress; - await nodeGraph.setNode(newNode1Id, newNode1Address); - - // Check the bucket is there first - const bucket = await nodeGraph.getBucket(2); - if (bucket) { - expect(bucket[newNode1Id]).toEqual({ - address: { host: '4.4.4.4', port: 4444 }, - lastUpdated: expect.any(Date), - }); - } else { - // Should be unreachable - fail('Bucket undefined'); - } - - // Delete the node - await nodeGraph.unsetNode(newNode1Id); - // Check bucket no longer exists - const newBucket = await nodeGraph.getBucket(2); - expect(newBucket).toBeUndefined(); - }); - test('deletes a single node (and retains remainder of bucket)', async () => { - // Add 3 new nodes into bucket 4 - const bucketIndex = 4; - const newNode1Id = nodesTestUtils.generateNodeIdForBucket( - nodeId, - bucketIndex, - 0, - ); - const newNode1Address = { host: '4.4.4.4', port: 4444 } as NodeAddress; - await nodeGraph.setNode(newNode1Id, newNode1Address); - - const newNode2Id = nodesTestUtils.generateNodeIdForBucket( - nodeId, - bucketIndex, - 1, + test('get bucket with multiple nodes', async () => { + const nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger, + }); + // Contiguous node IDs starting from 0 + let nodeIds = Array.from({ length: 25 }, (_, i) => + IdInternal.create( + utils.bigInt2Bytes(BigInt(i), keyManager.getNodeId().byteLength), + ), ); - const newNode2Address = { host: '5.5.5.5', port: 5555 } as NodeAddress; - await nodeGraph.setNode(newNode2Id, newNode2Address); - - const newNode3Id = nodesTestUtils.generateNodeIdForBucket( - nodeId, - bucketIndex, - 2, + nodeIds = nodeIds.filter( + (nodeId) => !nodeId.equals(keyManager.getNodeId()), ); - const newNode3Address = { host: '6.6.6.6', port: 6666 } as NodeAddress; - await nodeGraph.setNode(newNode3Id, newNode3Address); - // Based on XOR values, all 3 nodes should appear in bucket 4 - const bucket = await nodeGraph.getBucket(bucketIndex); - if (bucket) { - expect(bucket[newNode1Id]).toEqual({ - address: { host: '4.4.4.4', port: 4444 }, - lastUpdated: expect.any(Date), - }); - expect(bucket[newNode2Id]).toEqual({ - address: { host: '5.5.5.5', port: 5555 }, - lastUpdated: expect.any(Date), - }); - expect(bucket[newNode3Id]).toEqual({ - address: { host: '6.6.6.6', port: 6666 }, - lastUpdated: expect.any(Date), - }); - } else { - // Should be unreachable - fail('Bucket undefined'); + for (const nodeId of nodeIds) { + await utils.sleep(100); + await nodeGraph.setNode(nodeId, { + host: '127.0.0.1', + port: 55555, + } as NodeAddress); } - - // Delete the node - await nodeGraph.unsetNode(newNode1Id); - // Check node no longer exists in the bucket - const newBucket = await nodeGraph.getBucket(bucketIndex); - if (newBucket) { - expect(newBucket[newNode1Id]).toBeUndefined(); - expect(bucket[newNode2Id]).toEqual({ - address: { host: '5.5.5.5', port: 5555 }, - lastUpdated: expect.any(Date), - }); - expect(bucket[newNode3Id]).toEqual({ - address: { host: '6.6.6.6', port: 6666 }, - lastUpdated: expect.any(Date), - }); + // Use first and last buckets because node IDs may be split between buckets + const bucketIndexFirst = nodesUtils.bucketIndex( + keyManager.getNodeId(), + nodeIds[0], + ); + const bucketIndexLast = nodesUtils.bucketIndex( + keyManager.getNodeId(), + nodeIds[nodeIds.length - 1], + ); + const bucketFirst = await nodeGraph.getBucket(bucketIndexFirst); + const bucketLast = await nodeGraph.getBucket(bucketIndexLast); + let bucket: NodeBucket; + let bucketIndex: NodeBucketIndex; + if (bucketFirst.length >= bucketLast.length) { + bucket = bucketFirst; + bucketIndex = bucketIndexFirst; } else { - // Should be unreachable - fail('New bucket undefined'); + bucket = bucketLast; + bucketIndex = bucketIndexLast; } - }); - test('enforces k-bucket size, removing least active node when a new node is discovered', async () => { - // Add k nodes to the database (importantly, they all go into the same bucket) - const bucketIndex = 59; - // Keep a record of the first node ID that we added - const firstNodeId = nodesTestUtils.generateNodeIdForBucket( - nodeId, - bucketIndex, + expect(bucket.length > 1).toBe(true); + let bucketNodeIds = bucket.map(([nodeId]) => nodeId); + // The node IDs must be sorted lexicographically + expect( + bucketNodeIds.slice(1).every((nodeId, i) => { + return Buffer.compare(bucketNodeIds[i], nodeId) < 1; + }), + ).toBe(true); + // Sort by node ID asc + bucket = await nodeGraph.getBucket(bucketIndex, 'nodeId', 'asc'); + bucketNodeIds = bucket.map(([nodeId]) => nodeId); + expect( + bucketNodeIds.slice(1).every((nodeId, i) => { + return Buffer.compare(bucketNodeIds[i], nodeId) < 0; + }), + ).toBe(true); + // Sort by node ID desc + bucket = await nodeGraph.getBucket(bucketIndex, 'nodeId', 'desc'); + bucketNodeIds = bucket.map(([nodeId]) => nodeId); + expect( + bucketNodeIds.slice(1).every((nodeId, i) => { + return Buffer.compare(bucketNodeIds[i], nodeId) > 0; + }), + ).toBe(true); + // Sort by distance asc + bucket = await nodeGraph.getBucket(bucketIndex, 'distance', 'asc'); + let bucketDistances = bucket.map(([nodeId]) => + nodesUtils.nodeDistance(keyManager.getNodeId(), nodeId), + ); + expect( + bucketDistances.slice(1).every((distance, i) => { + return bucketDistances[i] <= distance; + }), + ).toBe(true); + // Sort by distance desc + bucket = await nodeGraph.getBucket(bucketIndex, 'distance', 'desc'); + bucketDistances = bucket.map(([nodeId]) => + nodesUtils.nodeDistance(keyManager.getNodeId(), nodeId), ); - for (let i = 1; i <= nodeGraph.maxNodesPerBucket; i++) { - // Add the current node ID - const nodeAddress = { - host: hostGen(i), - port: i as Port, - }; - await nodeGraph.setNode( - nodesTestUtils.generateNodeIdForBucket(nodeId, bucketIndex, i), - nodeAddress, + expect( + bucketDistances.slice(1).every((distance, i) => { + return bucketDistances[i] >= distance; + }), + ).toBe(true); + // Sort by lastUpdated asc + bucket = await nodeGraph.getBucket(bucketIndex, 'lastUpdated', 'asc'); + let bucketLastUpdateds = bucket.map(([, nodeData]) => nodeData.lastUpdated); + expect( + bucketLastUpdateds.slice(1).every((lastUpdated, i) => { + return bucketLastUpdateds[i] <= lastUpdated; + }), + ).toBe(true); + bucket = await nodeGraph.getBucket(bucketIndex, 'lastUpdated', 'desc'); + bucketLastUpdateds = bucket.map(([, nodeData]) => nodeData.lastUpdated); + expect( + bucketLastUpdateds.slice(1).every((lastUpdated, i) => { + return bucketLastUpdateds[i] >= lastUpdated; + }), + ).toBe(true); + await nodeGraph.stop(); + }); + test('get all buckets', async () => { + const nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger, + }); + const now = utils.getUnixtime(); + for (let i = 0; i < 50; i++) { + await utils.sleep(50); + await nodeGraph.setNode(testNodesUtils.generateRandomNodeId(), { + host: '127.0.0.1', + port: utils.getRandomInt(0, 2 ** 16), + } as NodeAddress); + } + let bucketIndex_ = -1; + // Ascending order + for await (const [bucketIndex, bucket] of nodeGraph.getBuckets( + 'nodeId', + 'asc', + )) { + expect(bucketIndex > bucketIndex_).toBe(true); + bucketIndex_ = bucketIndex; + expect(bucket.length > 0).toBe(true); + for (const [nodeId, nodeData] of bucket) { + expect(nodeId.byteLength).toBe(32); + expect(nodesUtils.bucketIndex(keyManager.getNodeId(), nodeId)).toBe( + bucketIndex, + ); + expect(nodeData.address.host).toBe('127.0.0.1'); + // Port of 0 is not allowed + expect(nodeData.address.port > 0).toBe(true); + expect(nodeData.address.port < 2 ** 16).toBe(true); + expect(nodeData.lastUpdated >= now).toBe(true); + } + const bucketNodeIds = bucket.map(([nodeId]) => nodeId); + expect( + bucketNodeIds.slice(1).every((nodeId, i) => { + return Buffer.compare(bucketNodeIds[i], nodeId) < 0; + }), + ).toBe(true); + } + // There must have been at least 1 bucket + expect(bucketIndex_).not.toBe(-1); + // Descending order + bucketIndex_ = keyManager.getNodeId().length * 8; + for await (const [bucketIndex, bucket] of nodeGraph.getBuckets( + 'nodeId', + 'desc', + )) { + expect(bucketIndex < bucketIndex_).toBe(true); + bucketIndex_ = bucketIndex; + expect(bucket.length > 0).toBe(true); + for (const [nodeId, nodeData] of bucket) { + expect(nodeId.byteLength).toBe(32); + expect(nodesUtils.bucketIndex(keyManager.getNodeId(), nodeId)).toBe( + bucketIndex, + ); + expect(nodeData.address.host).toBe('127.0.0.1'); + // Port of 0 is not allowed + expect(nodeData.address.port > 0).toBe(true); + expect(nodeData.address.port < 2 ** 16).toBe(true); + expect(nodeData.lastUpdated >= now).toBe(true); + } + const bucketNodeIds = bucket.map(([nodeId]) => nodeId); + expect( + bucketNodeIds.slice(1).every((nodeId, i) => { + return Buffer.compare(bucketNodeIds[i], nodeId) > 0; + }), + ).toBe(true); + } + expect(bucketIndex_).not.toBe(keyManager.getNodeId().length * 8); + // Distance ascending order + // Lower distance buckets first + bucketIndex_ = -1; + for await (const [bucketIndex, bucket] of nodeGraph.getBuckets( + 'distance', + 'asc', + )) { + expect(bucketIndex > bucketIndex_).toBe(true); + bucketIndex_ = bucketIndex; + expect(bucket.length > 0).toBe(true); + for (const [nodeId, nodeData] of bucket) { + expect(nodeId.byteLength).toBe(32); + expect(nodesUtils.bucketIndex(keyManager.getNodeId(), nodeId)).toBe( + bucketIndex, + ); + expect(nodeData.address.host).toBe('127.0.0.1'); + // Port of 0 is not allowed + expect(nodeData.address.port > 0).toBe(true); + expect(nodeData.address.port < 2 ** 16).toBe(true); + expect(nodeData.lastUpdated >= now).toBe(true); + } + const bucketDistances = bucket.map(([nodeId]) => + nodesUtils.nodeDistance(keyManager.getNodeId(), nodeId), ); - // Increment the current node ID + // It's the LAST bucket that fails this + expect( + bucketDistances.slice(1).every((distance, i) => { + return bucketDistances[i] <= distance; + }), + ).toBe(true); } - // All of these nodes are in bucket 59 - const originalBucket = await nodeGraph.getBucket(bucketIndex); - if (originalBucket) { - expect(Object.keys(originalBucket).length).toBe( - nodeGraph.maxNodesPerBucket, + // Distance descending order + // Higher distance buckets first + bucketIndex_ = keyManager.getNodeId().length * 8; + for await (const [bucketIndex, bucket] of nodeGraph.getBuckets( + 'distance', + 'desc', + )) { + expect(bucketIndex < bucketIndex_).toBe(true); + bucketIndex_ = bucketIndex; + expect(bucket.length > 0).toBe(true); + for (const [nodeId, nodeData] of bucket) { + expect(nodeId.byteLength).toBe(32); + expect(nodesUtils.bucketIndex(keyManager.getNodeId(), nodeId)).toBe( + bucketIndex, + ); + expect(nodeData.address.host).toBe('127.0.0.1'); + // Port of 0 is not allowed + expect(nodeData.address.port > 0).toBe(true); + expect(nodeData.address.port < 2 ** 16).toBe(true); + expect(nodeData.lastUpdated >= now).toBe(true); + } + const bucketDistances = bucket.map(([nodeId]) => + nodesUtils.nodeDistance(keyManager.getNodeId(), nodeId), ); - } else { - // Should be unreachable - fail('Bucket undefined'); + expect( + bucketDistances.slice(1).every((distance, i) => { + return bucketDistances[i] >= distance; + }), + ).toBe(true); } - - // Attempt to add a new node into this full bucket (increment the last node - // ID that was added) - const newNodeId = nodesTestUtils.generateNodeIdForBucket( - nodeId, - bucketIndex, - nodeGraph.maxNodesPerBucket + 1, - ); - const newNodeAddress = { host: '0.0.0.1' as Host, port: 1234 as Port }; - await nodeGraph.setNode(newNodeId, newNodeAddress); - - const finalBucket = await nodeGraph.getBucket(bucketIndex); - if (finalBucket) { - // We should still have a full bucket (but no more) - expect(Object.keys(finalBucket).length).toEqual( - nodeGraph.maxNodesPerBucket, + // Last updated ascending order + // Bucket index is ascending + bucketIndex_ = -1; + for await (const [bucketIndex, bucket] of nodeGraph.getBuckets( + 'lastUpdated', + 'asc', + )) { + expect(bucketIndex > bucketIndex_).toBe(true); + bucketIndex_ = bucketIndex; + expect(bucket.length > 0).toBe(true); + for (const [nodeId, nodeData] of bucket) { + expect(nodeId.byteLength).toBe(32); + expect(nodesUtils.bucketIndex(keyManager.getNodeId(), nodeId)).toBe( + bucketIndex, + ); + expect(nodeData.address.host).toBe('127.0.0.1'); + // Port of 0 is not allowed + expect(nodeData.address.port > 0).toBe(true); + expect(nodeData.address.port < 2 ** 16).toBe(true); + expect(nodeData.lastUpdated >= now).toBe(true); + } + const bucketLastUpdateds = bucket.map( + ([, nodeData]) => nodeData.lastUpdated, ); - // Ensure that this new node is in the bucket - expect(finalBucket[newNodeId]).toEqual({ - address: newNodeAddress, - lastUpdated: expect.any(Date), - }); - // NODEID1 should have been removed from this bucket (as this was the least active) - // The first node added should have been removed from this bucket (as this - // was the least active, purely because it was inserted first) - expect(finalBucket[firstNodeId]).toBeUndefined(); - } else { - // Should be unreachable - fail('Bucket undefined'); + expect( + bucketLastUpdateds.slice(1).every((lastUpdated, i) => { + return bucketLastUpdateds[i] <= lastUpdated; + }), + ).toBe(true); } - }); - test('enforces k-bucket size, retaining all nodes if adding a pre-existing node', async () => { - // Add k nodes to the database (importantly, they all go into the same bucket) - const bucketIndex = 59; - const currNodeId = nodesTestUtils.generateNodeIdForBucket( - nodeId, - bucketIndex, - ); - // Keep a record of the first node ID that we added - // const firstNodeId = currNodeId; - let increment = 1; - for (let i = 1; i <= nodeGraph.maxNodesPerBucket; i++) { - // Add the current node ID - const nodeAddress = { - host: hostGen(i), - port: i as Port, - }; - await nodeGraph.setNode( - nodesTestUtils.generateNodeIdForBucket(nodeId, bucketIndex, increment), - nodeAddress, + // Last updated descending order + // Bucket index is descending + bucketIndex_ = keyManager.getNodeId().length * 8; + for await (const [bucketIndex, bucket] of nodeGraph.getBuckets( + 'lastUpdated', + 'desc', + )) { + expect(bucketIndex < bucketIndex_).toBe(true); + bucketIndex_ = bucketIndex; + expect(bucket.length > 0).toBe(true); + for (const [nodeId, nodeData] of bucket) { + expect(nodeId.byteLength).toBe(32); + expect(nodesUtils.bucketIndex(keyManager.getNodeId(), nodeId)).toBe( + bucketIndex, + ); + expect(nodeData.address.host).toBe('127.0.0.1'); + // Port of 0 is not allowed + expect(nodeData.address.port > 0).toBe(true); + expect(nodeData.address.port < 2 ** 16).toBe(true); + expect(nodeData.lastUpdated >= now).toBe(true); + } + const bucketLastUpdateds = bucket.map( + ([, nodeData]) => nodeData.lastUpdated, ); - // Increment the current node ID - skip for the last one to keep currNodeId - // as the last added node ID - if (i !== nodeGraph.maxNodesPerBucket) { - increment++; + expect( + bucketLastUpdateds.slice(1).every((lastUpdated, i) => { + return bucketLastUpdateds[i] >= lastUpdated; + }), + ).toBe(true); + } + await nodeGraph.stop(); + }); + test('reset buckets', async () => { + const nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger, + }); + const now = utils.getUnixtime(); + for (let i = 0; i < 100; i++) { + await nodeGraph.setNode(testNodesUtils.generateRandomNodeId(), { + host: '127.0.0.1', + port: utils.getRandomInt(0, 2 ** 16), + } as NodeAddress); + } + const buckets0 = await utils.asyncIterableArray(nodeGraph.getBuckets()); + // Reset the buckets according to the new node ID + // Note that this should normally be only executed when the key manager NodeID changes + // This means methods that use the KeyManager's node ID cannot be used here in this test + const nodeIdNew1 = testNodesUtils.generateRandomNodeId(); + await nodeGraph.resetBuckets(nodeIdNew1); + const buckets1 = await utils.asyncIterableArray(nodeGraph.getBuckets()); + expect(buckets1.length > 0).toBe(true); + for (const [bucketIndex, bucket] of buckets1) { + expect(bucket.length > 0).toBe(true); + for (const [nodeId, nodeData] of bucket) { + expect(nodeId.byteLength).toBe(32); + expect(nodesUtils.bucketIndex(nodeIdNew1, nodeId)).toBe(bucketIndex); + expect(nodeData.address.host).toBe('127.0.0.1'); + // Port of 0 is not allowed + expect(nodeData.address.port > 0).toBe(true); + expect(nodeData.address.port < 2 ** 16).toBe(true); + expect(nodeData.lastUpdated >= now).toBe(true); } } - // All of these nodes are in bucket 59 - const originalBucket = await nodeGraph.getBucket(bucketIndex); - if (originalBucket) { - expect(Object.keys(originalBucket).length).toBe( - nodeGraph.maxNodesPerBucket, + expect(buckets1).not.toStrictEqual(buckets0); + // Resetting again should change the space + const nodeIdNew2 = testNodesUtils.generateRandomNodeId(); + await nodeGraph.resetBuckets(nodeIdNew2); + const buckets2 = await utils.asyncIterableArray(nodeGraph.getBuckets()); + expect(buckets2.length > 0).toBe(true); + for (const [bucketIndex, bucket] of buckets2) { + expect(bucket.length > 0).toBe(true); + for (const [nodeId, nodeData] of bucket) { + expect(nodeId.byteLength).toBe(32); + expect(nodesUtils.bucketIndex(nodeIdNew2, nodeId)).toBe(bucketIndex); + expect(nodeData.address.host).toBe('127.0.0.1'); + // Port of 0 is not allowed + expect(nodeData.address.port > 0).toBe(true); + expect(nodeData.address.port < 2 ** 16).toBe(true); + expect(nodeData.lastUpdated >= now).toBe(true); + } + } + expect(buckets2).not.toStrictEqual(buckets1); + // Resetting to the same NodeId results in the same bucket structure + await nodeGraph.resetBuckets(nodeIdNew2); + const buckets3 = await utils.asyncIterableArray(nodeGraph.getBuckets()); + expect(buckets3).toStrictEqual(buckets2); + // Resetting to an existing NodeId + const nodeIdExisting = buckets3[0][1][0][0]; + let nodeIdExistingFound = false; + await nodeGraph.resetBuckets(nodeIdExisting); + const buckets4 = await utils.asyncIterableArray(nodeGraph.getBuckets()); + expect(buckets4.length > 0).toBe(true); + for (const [bucketIndex, bucket] of buckets4) { + expect(bucket.length > 0).toBe(true); + for (const [nodeId, nodeData] of bucket) { + if (nodeId.equals(nodeIdExisting)) { + nodeIdExistingFound = true; + } + expect(nodeId.byteLength).toBe(32); + expect(nodesUtils.bucketIndex(nodeIdExisting, nodeId)).toBe( + bucketIndex, + ); + expect(nodeData.address.host).toBe('127.0.0.1'); + // Port of 0 is not allowed + expect(nodeData.address.port > 0).toBe(true); + expect(nodeData.address.port < 2 ** 16).toBe(true); + expect(nodeData.lastUpdated >= now).toBe(true); + } + } + expect(buckets4).not.toStrictEqual(buckets3); + // The existing node ID should not be put into the NodeGraph + expect(nodeIdExistingFound).toBe(false); + await nodeGraph.stop(); + }); + test('reset buckets is persistent', async () => { + const nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger, + }); + const now = utils.getUnixtime(); + for (let i = 0; i < 100; i++) { + await nodeGraph.setNode(testNodesUtils.generateRandomNodeId(), { + host: '127.0.0.1', + port: utils.getRandomInt(0, 2 ** 16), + } as NodeAddress); + } + const nodeIdNew1 = testNodesUtils.generateRandomNodeId(); + await nodeGraph.resetBuckets(nodeIdNew1); + await nodeGraph.stop(); + await nodeGraph.start(); + const buckets1 = await utils.asyncIterableArray(nodeGraph.getBuckets()); + expect(buckets1.length > 0).toBe(true); + for (const [bucketIndex, bucket] of buckets1) { + expect(bucket.length > 0).toBe(true); + for (const [nodeId, nodeData] of bucket) { + expect(nodeId.byteLength).toBe(32); + expect(nodesUtils.bucketIndex(nodeIdNew1, nodeId)).toBe(bucketIndex); + expect(nodeData.address.host).toBe('127.0.0.1'); + // Port of 0 is not allowed + expect(nodeData.address.port > 0).toBe(true); + expect(nodeData.address.port < 2 ** 16).toBe(true); + expect(nodeData.lastUpdated >= now).toBe(true); + } + } + const nodeIdNew2 = testNodesUtils.generateRandomNodeId(); + await nodeGraph.resetBuckets(nodeIdNew2); + await nodeGraph.stop(); + await nodeGraph.start(); + const buckets2 = await utils.asyncIterableArray(nodeGraph.getBuckets()); + expect(buckets2.length > 0).toBe(true); + for (const [bucketIndex, bucket] of buckets2) { + expect(bucket.length > 0).toBe(true); + for (const [nodeId, nodeData] of bucket) { + expect(nodeId.byteLength).toBe(32); + expect(nodesUtils.bucketIndex(nodeIdNew2, nodeId)).toBe(bucketIndex); + expect(nodeData.address.host).toBe('127.0.0.1'); + // Port of 0 is not allowed + expect(nodeData.address.port > 0).toBe(true); + expect(nodeData.address.port < 2 ** 16).toBe(true); + expect(nodeData.lastUpdated >= now).toBe(true); + } + } + expect(buckets2).not.toStrictEqual(buckets1); + await nodeGraph.stop(); + }); + test('get closest nodes, 40 nodes lower than target, take 20', async () => { + const nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger, + }); + const baseNodeId = keyManager.getNodeId(); + const nodeIds: NodeBucket = []; + // Add 1 node to each bucket + for (let i = 0; i < 40; i++) { + const nodeId = testNodesUtils.generateNodeIdForBucket( + baseNodeId, + 50 + i, + i, ); - } else { - // Should be unreachable - fail('Bucket undefined'); + nodeIds.push([nodeId, {} as NodeData]); + await nodeGraph.setNode(nodeId, { + host: '127.0.0.1', + port: utils.getRandomInt(0, 2 ** 16), + } as NodeAddress); } + const targetNodeId = testNodesUtils.generateNodeIdForBucket( + baseNodeId, + 100, + 2, + ); + const result = await nodeGraph.getClosestNodes(targetNodeId, 20); + nodesUtils.bucketSortByDistance(nodeIds, targetNodeId); + const a = nodeIds.map((a) => nodesUtils.encodeNodeId(a[0])); + const b = result.map((a) => nodesUtils.encodeNodeId(a[0])); + // Are the closest nodes out of all of the nodes + expect(a.slice(0, b.length)).toEqual(b); - // If we tried to re-add the first node, it would simply remove the original - // first node, as this is the "least active" - // We instead want to check that we don't mistakenly delete a node if we're - // updating an existing one - // So, re-add the last node - const newLastAddress: NodeAddress = { - host: '30.30.30.30' as Host, - port: 30 as Port, - }; - await nodeGraph.setNode(currNodeId, newLastAddress); - - const finalBucket = await nodeGraph.getBucket(bucketIndex); - if (finalBucket) { - // We should still have a full bucket - expect(Object.keys(finalBucket).length).toEqual( - nodeGraph.maxNodesPerBucket, + // Check that the list is strictly ascending + const closestNodeDistances = result.map(([nodeId]) => + nodesUtils.nodeDistance(targetNodeId, nodeId), + ); + expect( + closestNodeDistances.slice(1).every((distance, i) => { + return closestNodeDistances[i] < distance; + }), + ).toBe(true); + await nodeGraph.stop(); + }); + test('get closest nodes, 15 nodes lower than target, take 20', async () => { + const nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger, + }); + const baseNodeId = keyManager.getNodeId(); + const nodeIds: NodeBucket = []; + // Add 1 node to each bucket + for (let i = 0; i < 15; i++) { + const nodeId = testNodesUtils.generateNodeIdForBucket( + baseNodeId, + 50 + i, + i, ); - // Ensure that this new node is in the bucket - expect(finalBucket[currNodeId]).toEqual({ - address: newLastAddress, - lastUpdated: expect.any(Date), - }); - } else { - // Should be unreachable - fail('Bucket undefined'); + nodeIds.push([nodeId, {} as NodeData]); + await nodeGraph.setNode(nodeId, { + host: '127.0.0.1', + port: utils.getRandomInt(0, 2 ** 16), + } as NodeAddress); } - }); - test('retrieves all buckets (in expected lexicographic order)', async () => { - // Bucket 0 is expected to never have any nodes (as nodeId XOR 0 = nodeId) - // Bucket 1 (minimum): - - const node1Id = nodesTestUtils.generateNodeIdForBucket(nodeId, 1); - const node1Address = { host: '1.1.1.1', port: 1111 } as NodeAddress; - await nodeGraph.setNode(node1Id, node1Address); - - // Bucket 4 (multiple nodes in 1 bucket): - const node41Id = nodesTestUtils.generateNodeIdForBucket(nodeId, 4); - const node41Address = { host: '41.41.41.41', port: 4141 } as NodeAddress; - await nodeGraph.setNode(node41Id, node41Address); - const node42Id = nodesTestUtils.generateNodeIdForBucket(nodeId, 4, 1); - const node42Address = { host: '42.42.42.42', port: 4242 } as NodeAddress; - await nodeGraph.setNode(node42Id, node42Address); - - // Bucket 10 (lexicographic ordering - should appear after 2): - const node10Id = nodesTestUtils.generateNodeIdForBucket(nodeId, 10); - const node10Address = { host: '10.10.10.10', port: 1010 } as NodeAddress; - await nodeGraph.setNode(node10Id, node10Address); + const targetNodeId = testNodesUtils.generateNodeIdForBucket( + baseNodeId, + 100, + 2, + ); + const result = await nodeGraph.getClosestNodes(targetNodeId); + nodesUtils.bucketSortByDistance(nodeIds, targetNodeId); + const a = nodeIds.map((a) => nodesUtils.encodeNodeId(a[0])); + const b = result.map((a) => nodesUtils.encodeNodeId(a[0])); + // Are the closest nodes out of all of the nodes + expect(a.slice(0, b.length)).toEqual(b); - // Bucket 255 (maximum): - const node255Id = nodesTestUtils.generateNodeIdForBucket(nodeId, 255); - const node255Address = { - host: '255.255.255.255', - port: 255, - } as NodeAddress; - await nodeGraph.setNode(node255Id, node255Address); + // Check that the list is strictly ascending + const closestNodeDistances = result.map(([nodeId]) => + nodesUtils.nodeDistance(targetNodeId, nodeId), + ); + expect( + closestNodeDistances.slice(1).every((distance, i) => { + return closestNodeDistances[i] < distance; + }), + ).toBe(true); + await nodeGraph.stop(); + }); + test('get closest nodes, 10 nodes lower than target, 30 nodes above, take 20', async () => { + const nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger, + }); + const baseNodeId = keyManager.getNodeId(); + const nodeIds: NodeBucket = []; + // Add 1 node to each bucket + for (let i = 0; i < 40; i++) { + const nodeId = testNodesUtils.generateNodeIdForBucket( + baseNodeId, + 90 + i, + i, + ); + nodeIds.push([nodeId, {} as NodeData]); + await nodeGraph.setNode(nodeId, { + host: '127.0.0.1', + port: utils.getRandomInt(0, 2 ** 16), + } as NodeAddress); + } + const targetNodeId = testNodesUtils.generateNodeIdForBucket( + baseNodeId, + 100, + 2, + ); + const result = await nodeGraph.getClosestNodes(targetNodeId); + nodesUtils.bucketSortByDistance(nodeIds, targetNodeId); + const a = nodeIds.map((a) => nodesUtils.encodeNodeId(a[0])); + const b = result.map((a) => nodesUtils.encodeNodeId(a[0])); + // Are the closest nodes out of all of the nodes + expect(a.slice(0, b.length)).toEqual(b); - const buckets = await nodeGraph.getAllBuckets(); - expect(buckets.length).toBe(4); - // Buckets should be returned in lexicographic ordering (using hex keys to - // ensure the bucket indexes are in numberical order) - expect(buckets).toEqual([ - { - [node1Id]: { - address: { host: '1.1.1.1', port: 1111 }, - lastUpdated: expect.any(String), - }, - }, - { - [node41Id]: { - address: { host: '41.41.41.41', port: 4141 }, - lastUpdated: expect.any(String), - }, - [node42Id]: { - address: { host: '42.42.42.42', port: 4242 }, - lastUpdated: expect.any(String), - }, - }, - { - [node10Id]: { - address: { host: '10.10.10.10', port: 1010 }, - lastUpdated: expect.any(String), - }, - }, - { - [node255Id]: { - address: { host: '255.255.255.255', port: 255 }, - lastUpdated: expect.any(String), - }, - }, - ]); + // Check that the list is strictly ascending + const closestNodeDistances = result.map(([nodeId]) => + nodesUtils.nodeDistance(targetNodeId, nodeId), + ); + expect( + closestNodeDistances.slice(1).every((distance, i) => { + return closestNodeDistances[i] < distance; + }), + ).toBe(true); + await nodeGraph.stop(); }); - test( - 'refreshes buckets', - async () => { - const initialNodes: Record = {}; - // Generate and add some nodes - for (let i = 1; i < 255; i += 20) { - const newNodeId = nodesTestUtils.generateNodeIdForBucket( - keyManager.getNodeId(), - i, - ); - const nodeAddress = { - host: hostGen(i), - port: i as Port, - }; - await nodeGraph.setNode(newNodeId, nodeAddress); - initialNodes[newNodeId] = { - id: newNodeId, - address: nodeAddress, - distance: nodesUtils.calculateDistance( - keyManager.getNodeId(), - newNodeId, - ), - }; - } + test('get closest nodes, 10 nodes lower than target, 30 nodes above, take 5', async () => { + const nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger, + }); + const baseNodeId = keyManager.getNodeId(); + const nodeIds: NodeBucket = []; + // Add 1 node to each bucket + for (let i = 0; i < 40; i++) { + const nodeId = testNodesUtils.generateNodeIdForBucket( + baseNodeId, + 90 + i, + i, + ); + nodeIds.push([nodeId, {} as NodeData]); + await nodeGraph.setNode(nodeId, { + host: '127.0.0.1', + port: utils.getRandomInt(0, 2 ** 16), + } as NodeAddress); + } + const targetNodeId = testNodesUtils.generateNodeIdForBucket( + baseNodeId, + 100, + 2, + ); + const result = await nodeGraph.getClosestNodes(targetNodeId, 5); + nodesUtils.bucketSortByDistance(nodeIds, targetNodeId); + const a = nodeIds.map((a) => nodesUtils.encodeNodeId(a[0])); + const b = result.map((a) => nodesUtils.encodeNodeId(a[0])); + // Are the closest nodes out of all of the nodes + expect(a.slice(0, b.length)).toEqual(b); - // Renew the keypair - await keyManager.renewRootKeyPair('newPassword'); - // Reset the test's node ID state - nodeId = keyManager.getNodeId(); - // Refresh the buckets - await nodeGraph.refreshBuckets(); + // Check that the list is strictly ascending + const closestNodeDistances = result.map(([nodeId]) => + nodesUtils.nodeDistance(targetNodeId, nodeId), + ); + expect( + closestNodeDistances.slice(1).every((distance, i) => { + return closestNodeDistances[i] < distance; + }), + ).toBe(true); + await nodeGraph.stop(); + }); + test('get closest nodes, 5 nodes lower than target, 10 nodes above, take 20', async () => { + const nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger, + }); + const baseNodeId = keyManager.getNodeId(); + const nodeIds: NodeBucket = []; + // Add 1 node to each bucket + for (let i = 0; i < 15; i++) { + const nodeId = testNodesUtils.generateNodeIdForBucket( + baseNodeId, + 95 + i, + i, + ); + nodeIds.push([nodeId, {} as NodeData]); + await nodeGraph.setNode(nodeId, { + host: '127.0.0.1', + port: utils.getRandomInt(0, 2 ** 16), + } as NodeAddress); + } + const targetNodeId = testNodesUtils.generateNodeIdForBucket( + baseNodeId, + 100, + 2, + ); + const result = await nodeGraph.getClosestNodes(targetNodeId); + nodesUtils.bucketSortByDistance(nodeIds, targetNodeId); + const a = nodeIds.map((a) => nodesUtils.encodeNodeId(a[0])); + const b = result.map((a) => nodesUtils.encodeNodeId(a[0])); + // Are the closest nodes out of all of the nodes + expect(a.slice(0, b.length)).toEqual(b); - // Get all the new buckets, and expect that each node is in the correct bucket - const newBuckets = await nodeGraph.getAllBuckets(); - let nodeCount = 0; - for (const b of newBuckets) { - for (const n of Object.keys(b)) { - const nodeId = IdInternal.fromString(n); - // Check that it was a node in the original DB - expect(initialNodes[nodeId]).toBeDefined(); - // Check it's in the correct bucket - const expectedIndex = nodesUtils.calculateBucketIndex( - keyManager.getNodeId(), - nodeId, - ); - const expectedBucket = await nodeGraph.getBucket(expectedIndex); - expect(expectedBucket).toBeDefined(); - expect(expectedBucket![nodeId]).toBeDefined(); - // Check it has the correct address - expect(b[nodeId].address).toEqual(initialNodes[nodeId].address); - nodeCount++; - } - } - // We had less than k (20) nodes, so we expect that all nodes will be re-added - // If we had more than k nodes, we may lose some of them (because the nodes - // may be re-added to newly full buckets) - expect(Object.keys(initialNodes).length).toEqual(nodeCount); - }, - global.defaultTimeout * 4, - ); - test('updates node', async () => { - // New node added - const node1Id = nodesTestUtils.generateNodeIdForBucket(nodeId, 2); - const node1Address = { host: '1.1.1.1', port: 1 } as NodeAddress; - await nodeGraph.setNode(node1Id, node1Address); + // Check that the list is strictly ascending + const closestNodeDistances = result.map(([nodeId]) => + nodesUtils.nodeDistance(targetNodeId, nodeId), + ); + expect( + closestNodeDistances.slice(1).every((distance, i) => { + return closestNodeDistances[i] < distance; + }), + ).toBe(true); + await nodeGraph.stop(); + }); + test('get closest nodes, 40 nodes above target, take 20', async () => { + const nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger, + }); + const baseNodeId = keyManager.getNodeId(); + const nodeIds: NodeBucket = []; + // Add 1 node to each bucket + for (let i = 0; i < 40; i++) { + const nodeId = testNodesUtils.generateNodeIdForBucket( + baseNodeId, + 101 + i, + i, + ); + nodeIds.push([nodeId, {} as NodeData]); + await nodeGraph.setNode(nodeId, { + host: '127.0.0.1', + port: utils.getRandomInt(0, 2 ** 16), + } as NodeAddress); + } + const targetNodeId = testNodesUtils.generateNodeIdForBucket( + baseNodeId, + 100, + 2, + ); + const result = await nodeGraph.getClosestNodes(targetNodeId); + nodesUtils.bucketSortByDistance(nodeIds, targetNodeId); + const a = nodeIds.map((a) => nodesUtils.encodeNodeId(a[0])); + const b = result.map((a) => nodesUtils.encodeNodeId(a[0])); + // Are the closest nodes out of all of the nodes + expect(a.slice(0, b.length)).toEqual(b); - // Check new node is in retrieved bucket from database - const bucket = await nodeGraph.getBucket(2); - const time1 = bucket![node1Id].lastUpdated; + // Check that the list is strictly ascending + const closestNodeDistances = result.map(([nodeId]) => + nodesUtils.nodeDistance(targetNodeId, nodeId), + ); + expect( + closestNodeDistances.slice(1).every((distance, i) => { + return closestNodeDistances[i] < distance; + }), + ).toBe(true); + await nodeGraph.stop(); + }); + test('get closest nodes, 15 nodes above target, take 20', async () => { + const nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger, + }); + const baseNodeId = keyManager.getNodeId(); + const nodeIds: NodeBucket = []; + // Add 1 node to each bucket + for (let i = 0; i < 15; i++) { + const nodeId = testNodesUtils.generateNodeIdForBucket( + baseNodeId, + 101 + i, + i, + ); + nodeIds.push([nodeId, {} as NodeData]); + await nodeGraph.setNode(nodeId, { + host: '127.0.0.1', + port: utils.getRandomInt(0, 2 ** 16), + } as NodeAddress); + } + const targetNodeId = testNodesUtils.generateNodeIdForBucket( + baseNodeId, + 100, + 2, + ); + const result = await nodeGraph.getClosestNodes(targetNodeId); + nodesUtils.bucketSortByDistance(nodeIds, targetNodeId); + const a = nodeIds.map((a) => nodesUtils.encodeNodeId(a[0])); + const b = result.map((a) => nodesUtils.encodeNodeId(a[0])); + // Are the closest nodes out of all of the nodes + expect(a.slice(0, b.length)).toEqual(b); - // Update node and check that time is later - const newNode1Address = { host: '2.2.2.2', port: 2 } as NodeAddress; - await nodeGraph.updateNode(node1Id, newNode1Address); + // Check that the list is strictly ascending + const closestNodeDistances = result.map(([nodeId]) => + nodesUtils.nodeDistance(targetNodeId, nodeId), + ); + expect( + closestNodeDistances.slice(1).every((distance, i) => { + return closestNodeDistances[i] < distance; + }), + ).toBe(true); + await nodeGraph.stop(); + }); + test('get closest nodes, no nodes, take 20', async () => { + const nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger, + }); + const baseNodeId = keyManager.getNodeId(); + const nodeIds: NodeBucket = []; + const targetNodeId = testNodesUtils.generateNodeIdForBucket( + baseNodeId, + 100, + 2, + ); + const result = await nodeGraph.getClosestNodes(targetNodeId); + nodesUtils.bucketSortByDistance(nodeIds, targetNodeId); + const a = nodeIds.map((a) => nodesUtils.encodeNodeId(a[0])); + const b = result.map((a) => nodesUtils.encodeNodeId(a[0])); + // Are the closest nodes out of all of the nodes + expect(a.slice(0, b.length)).toEqual(b); - const bucket2 = await nodeGraph.getBucket(2); - const time2 = bucket2![node1Id].lastUpdated; - expect(bucket2![node1Id].address).toEqual(newNode1Address); - expect(time1 < time2).toBeTruthy(); + // Check that the list is strictly ascending + const closestNodeDistances = result.map(([nodeId]) => + nodesUtils.nodeDistance(targetNodeId, nodeId), + ); + expect( + closestNodeDistances.slice(1).every((distance, i) => { + return closestNodeDistances[i] < distance; + }), + ).toBe(true); + await nodeGraph.stop(); }); }); diff --git a/tests/nodes/NodeManager.test.ts b/tests/nodes/NodeManager.test.ts index f3de57cd8..d32c869d9 100644 --- a/tests/nodes/NodeManager.test.ts +++ b/tests/nodes/NodeManager.test.ts @@ -7,6 +7,7 @@ import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import UTP from 'utp-native'; +import Queue from '@/nodes/Queue'; import PolykeyAgent from '@/PolykeyAgent'; import KeyManager from '@/keys/KeyManager'; import * as keysUtils from '@/keys/utils'; @@ -16,8 +17,12 @@ import NodeManager from '@/nodes/NodeManager'; import Proxy from '@/network/Proxy'; import Sigchain from '@/sigchain/Sigchain'; import * as claimsUtils from '@/claims/utils'; -import { promisify, sleep } from '@/utils'; +import { promise, promisify, sleep } from '@/utils'; import * as nodesUtils from '@/nodes/utils'; +import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import * as nodesErrors from '@/nodes/errors'; +import * as nodesTestUtils from './utils'; +import { generateNodeIdForBucket } from './utils'; describe(`${NodeManager.name} test`, () => { const password = 'password'; @@ -26,6 +31,7 @@ describe(`${NodeManager.name} test`, () => { ]); let dataDir: string; let nodeGraph: NodeGraph; + let queue: Queue; let nodeConnectionManager: NodeConnectionManager; let proxy: Proxy; let keyManager: KeyManager; @@ -37,14 +43,22 @@ describe(`${NodeManager.name} test`, () => { const serverHost = '::1' as Host; const externalHost = '127.0.0.1' as Host; + const localhost = '127.0.0.1' as Host; + const port = 55556 as Port; const serverPort = 0 as Port; const externalPort = 0 as Port; const mockedGenerateDeterministicKeyPair = jest.spyOn( keysUtils, 'generateDeterministicKeyPair', ); + const mockedPingNode = jest.fn(); // Jest.spyOn(NodeManager.prototype, 'pingNode'); + const dummyNodeConnectionManager = { + pingNode: mockedPingNode, + } as unknown as NodeConnectionManager; beforeEach(async () => { + mockedPingNode.mockClear(); + mockedPingNode.mockImplementation(async (_) => true); mockedGenerateDeterministicKeyPair.mockImplementation((bits, _) => { return keysUtils.generateKeyPair(bits); }); @@ -99,16 +113,20 @@ describe(`${NodeManager.name} test`, () => { keyManager, logger, }); + queue = new Queue({ logger }); nodeConnectionManager = new NodeConnectionManager({ keyManager, nodeGraph, + queue, proxy, logger, }); - await nodeConnectionManager.start(); }); afterEach(async () => { + mockedPingNode.mockClear(); + mockedPingNode.mockImplementation(async (_) => true); await nodeConnectionManager.stop(); + await queue.stop(); await nodeGraph.stop(); await nodeGraph.destroy(); await sigchain.stop(); @@ -129,6 +147,7 @@ describe(`${NodeManager.name} test`, () => { 'pings node', async () => { let server: PolykeyAgent | undefined; + let nodeManager: NodeManager | undefined; try { server = await PolykeyAgent.createPolykeyAgent({ password: 'password', @@ -148,14 +167,17 @@ describe(`${NodeManager.name} test`, () => { }; await nodeGraph.setNode(serverNodeId, serverNodeAddress); - const nodeManager = new NodeManager({ + nodeManager = new NodeManager({ db, sigchain, keyManager, nodeGraph, nodeConnectionManager, + queue, logger, }); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); // Set server node offline await server.stop(); @@ -192,6 +214,7 @@ describe(`${NodeManager.name} test`, () => { expect(active3).toBe(false); } finally { // Clean up + await nodeManager?.stop(); await server?.stop(); await server?.destroy(); } @@ -200,6 +223,7 @@ describe(`${NodeManager.name} test`, () => { ); // Ping needs to timeout (takes 20 seconds + setup + pulldown) test('getPublicKey', async () => { let server: PolykeyAgent | undefined; + let nodeManager: NodeManager | undefined; try { server = await PolykeyAgent.createPolykeyAgent({ password: 'password', @@ -219,14 +243,17 @@ describe(`${NodeManager.name} test`, () => { }; await nodeGraph.setNode(serverNodeId, serverNodeAddress); - const nodeManager = new NodeManager({ + nodeManager = new NodeManager({ db, sigchain, keyManager, nodeGraph, nodeConnectionManager, + queue, logger, }); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); // We want to get the public key of the server const key = await nodeManager.getPublicKey(serverNodeId); @@ -234,6 +261,7 @@ describe(`${NodeManager.name} test`, () => { expect(key).toEqual(expectedKey); } finally { // Clean up + await nodeManager?.stop(); await server?.stop(); await server?.destroy(); } @@ -321,8 +349,10 @@ describe(`${NodeManager.name} test`, () => { // Make sure to remove any side-effects after each test afterEach(async () => { - await x.sigchain.clearDB(); - await y.sigchain.clearDB(); + await x.sigchain.stop(); + await x.sigchain.start({ fresh: true }); + await y.sigchain.stop(); + await y.sigchain.start({ fresh: true }); }); test('can successfully cross sign a claim', async () => { @@ -332,10 +362,6 @@ describe(`${NodeManager.name} test`, () => { // 4. X <- sends doubly signed claim (X's intermediary) <- Y await y.nodeManager.claimNode(xNodeId); - // Check both sigchain locks are released - expect(x.sigchain.locked).toBe(false); - expect(y.sigchain.locked).toBe(false); - // Check X's sigchain state const xChain = await x.sigchain.getChainData(); expect(Object.keys(xChain).length).toBe(1); @@ -403,26 +429,720 @@ describe(`${NodeManager.name} test`, () => { } }); test('can request chain data', async () => { - // Cross signing claims - await y.nodeManager.claimNode(xNodeId); + let nodeManager: NodeManager | undefined; + try { + // Cross signing claims + await y.nodeManager.claimNode(xNodeId); - const nodeManager = new NodeManager({ - db, - sigchain, - keyManager, - nodeGraph, - nodeConnectionManager, - logger, + nodeManager = new NodeManager({ + db, + sigchain, + keyManager, + nodeGraph, + nodeConnectionManager, + queue, + logger, + }); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); + + await nodeGraph.setNode(xNodeId, xNodeAddress); + + // We want to get the public key of the server + const chainData = JSON.stringify( + await nodeManager.requestChainData(xNodeId), + ); + expect(chainData).toContain(nodesUtils.encodeNodeId(xNodeId)); + expect(chainData).toContain(nodesUtils.encodeNodeId(yNodeId)); + } finally { + await nodeManager?.stop(); + } + }); + }); + test('should add a node when bucket has room', async () => { + const queue = new Queue({ logger }); + const nodeManager = new NodeManager({ + db, + sigchain: {} as Sigchain, + keyManager, + nodeGraph, + nodeConnectionManager: {} as NodeConnectionManager, + queue, + logger, + }); + try { + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); + const localNodeId = keyManager.getNodeId(); + const bucketIndex = 100; + const nodeId = nodesTestUtils.generateNodeIdForBucket( + localNodeId, + bucketIndex, + ); + await nodeManager.setNode(nodeId, {} as NodeAddress); + + // Checking bucket + const bucket = await nodeManager.getBucket(bucketIndex); + expect(bucket).toHaveLength(1); + } finally { + await nodeManager.stop(); + await queue.stop(); + } + }); + test('should update a node if node exists', async () => { + const queue = new Queue({ logger }); + const nodeManager = new NodeManager({ + db, + sigchain: {} as Sigchain, + keyManager, + nodeGraph, + nodeConnectionManager: {} as NodeConnectionManager, + queue, + logger, + }); + try { + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); + const localNodeId = keyManager.getNodeId(); + const bucketIndex = 100; + const nodeId = nodesTestUtils.generateNodeIdForBucket( + localNodeId, + bucketIndex, + ); + await nodeManager.setNode(nodeId, { + host: '' as Host, + port: 11111 as Port, }); - await nodeGraph.setNode(xNodeId, xNodeAddress); + const nodeData = (await nodeGraph.getNode(nodeId))!; + await sleep(1100); - // We want to get the public key of the server - const chainData = JSON.stringify( - await nodeManager.requestChainData(xNodeId), + // Should update the node + await nodeManager.setNode(nodeId, { + host: '' as Host, + port: 22222 as Port, + }); + + const newNodeData = (await nodeGraph.getNode(nodeId))!; + expect(newNodeData.address.port).not.toEqual(nodeData.address.port); + expect(newNodeData.lastUpdated).not.toEqual(nodeData.lastUpdated); + } finally { + await nodeManager.stop(); + await queue.stop(); + } + }); + test('should not add node if bucket is full and old node is alive', async () => { + const queue = new Queue({ logger }); + const nodeManager = new NodeManager({ + db, + sigchain: {} as Sigchain, + keyManager, + nodeGraph, + nodeConnectionManager: {} as NodeConnectionManager, + queue, + logger, + }); + try { + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); + const localNodeId = keyManager.getNodeId(); + const bucketIndex = 100; + // Creating 20 nodes in bucket + for (let i = 1; i <= 20; i++) { + const nodeId = nodesTestUtils.generateNodeIdForBucket( + localNodeId, + bucketIndex, + i, + ); + await nodeManager.setNode(nodeId, { port: i } as NodeAddress); + } + const nodeId = nodesTestUtils.generateNodeIdForBucket( + localNodeId, + bucketIndex, + ); + // Mocking ping + const nodeManagerPingMock = jest.spyOn(NodeManager.prototype, 'pingNode'); + nodeManagerPingMock.mockResolvedValue(true); + const oldestNodeId = (await nodeGraph.getOldestNode(bucketIndex)).pop(); + const oldestNode = await nodeGraph.getNode(oldestNodeId!); + // Waiting for a second to tick over + await sleep(1500); + // Adding a new node with bucket full + await nodeManager.setNode(nodeId, { port: 55555 } as NodeAddress, true); + // Bucket still contains max nodes + const bucket = await nodeManager.getBucket(bucketIndex); + expect(bucket).toHaveLength(nodeGraph.nodeBucketLimit); + // New node was not added + const node = await nodeGraph.getNode(nodeId); + expect(node).toBeUndefined(); + // Oldest node was updated + const oldestNodeNew = await nodeGraph.getNode(oldestNodeId!); + expect(oldestNodeNew!.lastUpdated).not.toEqual(oldestNode!.lastUpdated); + nodeManagerPingMock.mockRestore(); + } finally { + await nodeManager.stop(); + await queue.stop(); + } + }); + test('should add node if bucket is full, old node is alive and force is set', async () => { + const queue = new Queue({ logger }); + const nodeManager = new NodeManager({ + db, + sigchain: {} as Sigchain, + keyManager, + nodeGraph, + nodeConnectionManager: {} as NodeConnectionManager, + queue, + logger, + }); + try { + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); + const localNodeId = keyManager.getNodeId(); + const bucketIndex = 100; + // Creating 20 nodes in bucket + for (let i = 1; i <= 20; i++) { + const nodeId = nodesTestUtils.generateNodeIdForBucket( + localNodeId, + bucketIndex, + i, + ); + await nodeManager.setNode(nodeId, { port: i } as NodeAddress); + } + const nodeId = nodesTestUtils.generateNodeIdForBucket( + localNodeId, + bucketIndex, ); - expect(chainData).toContain(nodesUtils.encodeNodeId(xNodeId)); - expect(chainData).toContain(nodesUtils.encodeNodeId(yNodeId)); + // Mocking ping + const nodeManagerPingMock = jest.spyOn(NodeManager.prototype, 'pingNode'); + nodeManagerPingMock.mockResolvedValue(true); + const oldestNodeId = (await nodeGraph.getOldestNode(bucketIndex)).pop(); + // Adding a new node with bucket full + await nodeManager.setNode( + nodeId, + { port: 55555 } as NodeAddress, + false, + true, + ); + // Bucket still contains max nodes + const bucket = await nodeManager.getBucket(bucketIndex); + expect(bucket).toHaveLength(nodeGraph.nodeBucketLimit); + // New node was added + const node = await nodeGraph.getNode(nodeId); + expect(node).toBeDefined(); + // Oldest node was removed + const oldestNodeNew = await nodeGraph.getNode(oldestNodeId!); + expect(oldestNodeNew).toBeUndefined(); + nodeManagerPingMock.mockRestore(); + } finally { + await nodeManager.stop(); + await queue.stop(); + } + }); + test('should add node if bucket is full and old node is dead', async () => { + const queue = new Queue({ logger }); + const nodeManager = new NodeManager({ + db, + sigchain: {} as Sigchain, + keyManager, + nodeGraph, + nodeConnectionManager: {} as NodeConnectionManager, + queue, + logger, }); + try { + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); + const localNodeId = keyManager.getNodeId(); + const bucketIndex = 100; + // Creating 20 nodes in bucket + for (let i = 1; i <= 20; i++) { + const nodeId = nodesTestUtils.generateNodeIdForBucket( + localNodeId, + bucketIndex, + i, + ); + await nodeManager.setNode(nodeId, { port: i } as NodeAddress); + } + const nodeId = nodesTestUtils.generateNodeIdForBucket( + localNodeId, + bucketIndex, + ); + // Mocking ping + const nodeManagerPingMock = jest.spyOn(NodeManager.prototype, 'pingNode'); + nodeManagerPingMock.mockResolvedValue(false); + const oldestNodeId = (await nodeGraph.getOldestNode(bucketIndex)).pop(); + // Adding a new node with bucket full + await nodeManager.setNode(nodeId, { port: 55555 } as NodeAddress, true); + // New node was added + const node = await nodeGraph.getNode(nodeId); + expect(node).toBeDefined(); + // Oldest node was removed + const oldestNodeNew = await nodeGraph.getNode(oldestNodeId!); + expect(oldestNodeNew).toBeUndefined(); + nodeManagerPingMock.mockRestore(); + } finally { + await nodeManager.stop(); + await queue.stop(); + } + }); + test('should add node when an incoming connection is established', async () => { + let server: PolykeyAgent | undefined; + const queue = new Queue({ logger }); + const nodeManager = new NodeManager({ + db, + sigchain: {} as Sigchain, + keyManager, + nodeGraph, + nodeConnectionManager: {} as NodeConnectionManager, + queue, + logger, + }); + try { + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); + server = await PolykeyAgent.createPolykeyAgent({ + password: 'password', + nodePath: path.join(dataDir, 'server'), + keysConfig: { + rootKeyPairBits: 2048, + }, + networkConfig: { + proxyHost: localhost, + }, + logger: logger, + }); + const serverNodeId = server.keyManager.getNodeId(); + const serverNodeAddress: NodeAddress = { + host: server.proxy.getProxyHost(), + port: server.proxy.getProxyPort(), + }; + await nodeGraph.setNode(serverNodeId, serverNodeAddress); + + const expectedHost = proxy.getProxyHost(); + const expectedPort = proxy.getProxyPort(); + const expectedNodeId = keyManager.getNodeId(); + + const nodeData = await server.nodeGraph.getNode(expectedNodeId); + expect(nodeData).toBeUndefined(); + + // Now we want to connect to the server by making an echo request. + await nodeConnectionManager.withConnF(serverNodeId, async (conn) => { + const client = conn.getClient(); + await client.echo(new utilsPB.EchoMessage().setChallenge('hello')); + }); + + const nodeData2 = await server.nodeGraph.getNode(expectedNodeId); + expect(nodeData2).toBeDefined(); + expect(nodeData2?.address.host).toEqual(expectedHost); + expect(nodeData2?.address.port).toEqual(expectedPort); + } finally { + // Clean up + await server?.stop(); + await server?.destroy(); + await nodeManager.stop(); + await queue.stop(); + } + }); + test('should not add nodes to full bucket if pings succeeds', async () => { + mockedPingNode.mockImplementation(async (_) => true); + const queue = new Queue({ logger }); + const nodeManager = new NodeManager({ + db, + sigchain: {} as Sigchain, + keyManager, + nodeGraph, + nodeConnectionManager: dummyNodeConnectionManager, + queue, + logger, + }); + try { + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); + const nodeId = keyManager.getNodeId(); + const address = { host: localhost, port }; + // Let's fill a bucket + for (let i = 0; i < nodeGraph.nodeBucketLimit; i++) { + const newNode = generateNodeIdForBucket(nodeId, 100, i); + await nodeManager.setNode(newNode, address); + } + + // Helpers + const listBucket = async (bucketIndex: number) => { + const bucket = await nodeManager.getBucket(bucketIndex); + return bucket?.map(([nodeId]) => nodesUtils.encodeNodeId(nodeId)); + }; + + // Pings succeed, node not added + mockedPingNode.mockImplementation(async (_) => true); + const newNode = generateNodeIdForBucket(nodeId, 100, 21); + await nodeManager.setNode(newNode, address); + expect(await listBucket(100)).not.toContain( + nodesUtils.encodeNodeId(newNode), + ); + } finally { + await nodeManager.stop(); + await queue.stop(); + } + }); + test('should add nodes to full bucket if pings fail', async () => { + mockedPingNode.mockImplementation(async (_) => true); + const queue = new Queue({ logger }); + const nodeManager = new NodeManager({ + db, + sigchain: {} as Sigchain, + keyManager, + nodeGraph, + nodeConnectionManager: dummyNodeConnectionManager, + queue, + logger, + }); + await queue.start(); + await nodeManager.start(); + try { + await nodeConnectionManager.start({ nodeManager }); + const nodeId = keyManager.getNodeId(); + const address = { host: localhost, port }; + // Let's fill a bucket + for (let i = 0; i < nodeGraph.nodeBucketLimit; i++) { + const newNode = generateNodeIdForBucket(nodeId, 100, i); + await nodeManager.setNode(newNode, address); + } + + // Helpers + const listBucket = async (bucketIndex: number) => { + const bucket = await nodeManager.getBucket(bucketIndex); + return bucket?.map(([nodeId]) => nodesUtils.encodeNodeId(nodeId)); + }; + + // Pings fail, new nodes get added + mockedPingNode.mockImplementation(async (_) => false); + const newNode1 = generateNodeIdForBucket(nodeId, 100, 22); + const newNode2 = generateNodeIdForBucket(nodeId, 100, 23); + const newNode3 = generateNodeIdForBucket(nodeId, 100, 24); + await nodeManager.setNode(newNode1, address); + await nodeManager.setNode(newNode2, address); + await nodeManager.setNode(newNode3, address); + await queue.drained(); + const list = await listBucket(100); + expect(list).toContain(nodesUtils.encodeNodeId(newNode1)); + expect(list).toContain(nodesUtils.encodeNodeId(newNode2)); + expect(list).toContain(nodesUtils.encodeNodeId(newNode3)); + } finally { + await nodeManager.stop(); + await queue.stop(); + } + }); + test('should not block when bucket is full', async () => { + const tempNodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger, + }); + mockedPingNode.mockImplementation(async (_) => true); + const queue = new Queue({ logger }); + const nodeManager = new NodeManager({ + db, + sigchain: {} as Sigchain, + keyManager, + nodeGraph: tempNodeGraph, + nodeConnectionManager: dummyNodeConnectionManager, + queue, + logger, + }); + await queue.start(); + await nodeManager.start(); + try { + await nodeConnectionManager.start({ nodeManager }); + const nodeId = keyManager.getNodeId(); + const address = { host: localhost, port }; + // Let's fill a bucket + for (let i = 0; i < nodeGraph.nodeBucketLimit; i++) { + const newNode = generateNodeIdForBucket(nodeId, 100, i); + await nodeManager.setNode(newNode, address); + } + + // Set node does not block + const delayPing = promise(); + mockedPingNode.mockImplementation(async (_) => { + await delayPing.p; + return true; + }); + const newNode4 = generateNodeIdForBucket(nodeId, 100, 25); + // Set manually to non-blocking + await expect( + nodeManager.setNode(newNode4, address, false), + ).resolves.toBeUndefined(); + delayPing.resolveP(); + await queue.drained(); + } finally { + await nodeManager.stop(); + await queue.stop(); + await tempNodeGraph.stop(); + await tempNodeGraph.destroy(); + } + }); + test('should block when blocking is set to true', async () => { + mockedPingNode.mockImplementation(async (_) => true); + const queue = new Queue({ logger }); + const nodeManager = new NodeManager({ + db, + sigchain: {} as Sigchain, + keyManager, + nodeGraph, + nodeConnectionManager: dummyNodeConnectionManager, + queue, + logger, + }); + await queue.start(); + await nodeManager.start(); + try { + await nodeConnectionManager.start({ nodeManager }); + const nodeId = keyManager.getNodeId(); + const address = { host: localhost, port }; + // Let's fill a bucket + for (let i = 0; i < nodeGraph.nodeBucketLimit; i++) { + const newNode = generateNodeIdForBucket(nodeId, 100, i); + await nodeManager.setNode(newNode, address); + } + + // Set node can block + mockedPingNode.mockClear(); + mockedPingNode.mockImplementation(async () => true); + const newNode5 = generateNodeIdForBucket(nodeId, 100, 25); + await expect( + nodeManager.setNode(newNode5, address, true), + ).resolves.toBeUndefined(); + expect(mockedPingNode).toBeCalled(); + } finally { + await nodeManager.stop(); + await queue.stop(); + } + }); + test('should update deadline when updating a bucket', async () => { + const refreshBucketTimeout = 100000; + const queue = new Queue({ logger }); + const nodeManager = new NodeManager({ + db, + sigchain: {} as Sigchain, + keyManager, + nodeGraph, + nodeConnectionManager: dummyNodeConnectionManager, + queue, + refreshBucketTimerDefault: refreshBucketTimeout, + logger, + }); + const mockRefreshBucket = jest.spyOn( + NodeManager.prototype, + 'refreshBucket', + ); + try { + mockRefreshBucket.mockImplementation(async () => {}); + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); + // @ts-ignore: kidnap map + const deadlineMap = nodeManager.refreshBucketDeadlineMap; + // Getting starting value + const bucket = 0; + const startingDeadline = deadlineMap.get(bucket); + const nodeId = nodesTestUtils.generateNodeIdForBucket( + keyManager.getNodeId(), + bucket, + ); + await sleep(1000); + await nodeManager.setNode(nodeId, {} as NodeAddress); + // Deadline should be updated + const newDeadline = deadlineMap.get(bucket); + expect(newDeadline).not.toEqual(startingDeadline); + } finally { + mockRefreshBucket.mockRestore(); + await nodeManager.stop(); + await queue.stop(); + } + }); + test('should add buckets to the queue when exceeding deadline', async () => { + const refreshBucketTimeout = 100; + const queue = new Queue({ logger }); + const nodeManager = new NodeManager({ + db, + sigchain: {} as Sigchain, + keyManager, + nodeGraph, + nodeConnectionManager: dummyNodeConnectionManager, + queue, + refreshBucketTimerDefault: refreshBucketTimeout, + logger, + }); + const mockRefreshBucket = jest.spyOn( + NodeManager.prototype, + 'refreshBucket', + ); + const mockRefreshBucketQueueAdd = jest.spyOn( + NodeManager.prototype, + 'refreshBucketQueueAdd', + ); + try { + mockRefreshBucket.mockImplementation(async () => {}); + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); + // Getting starting value + expect(mockRefreshBucketQueueAdd).toHaveBeenCalledTimes(0); + await sleep(200); + expect(mockRefreshBucketQueueAdd).toHaveBeenCalledTimes(256); + } finally { + mockRefreshBucketQueueAdd.mockRestore(); + mockRefreshBucket.mockRestore(); + await nodeManager.stop(); + await queue.stop(); + } + }); + test('should digest queue to refresh buckets', async () => { + const refreshBucketTimeout = 1000000; + const queue = new Queue({ logger }); + const nodeManager = new NodeManager({ + db, + sigchain: {} as Sigchain, + keyManager, + nodeGraph, + nodeConnectionManager: dummyNodeConnectionManager, + queue, + refreshBucketTimerDefault: refreshBucketTimeout, + logger, + }); + const mockRefreshBucket = jest.spyOn( + NodeManager.prototype, + 'refreshBucket', + ); + try { + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); + mockRefreshBucket.mockImplementation(async () => {}); + nodeManager.refreshBucketQueueAdd(1); + nodeManager.refreshBucketQueueAdd(2); + nodeManager.refreshBucketQueueAdd(3); + nodeManager.refreshBucketQueueAdd(4); + nodeManager.refreshBucketQueueAdd(5); + await nodeManager.refreshBucketQueueDrained(); + expect(mockRefreshBucket).toHaveBeenCalledTimes(5); + + // Add buckets to queue + // check if refresh buckets was called + } finally { + mockRefreshBucket.mockRestore(); + await nodeManager.stop(); + await queue.stop(); + } + }); + test('should abort refreshBucket queue when stopping', async () => { + const refreshBucketTimeout = 1000000; + const queue = new Queue({ logger }); + const nodeManager = new NodeManager({ + db, + sigchain: {} as Sigchain, + keyManager, + nodeGraph, + nodeConnectionManager: dummyNodeConnectionManager, + queue, + refreshBucketTimerDefault: refreshBucketTimeout, + logger, + }); + const mockRefreshBucket = jest.spyOn( + NodeManager.prototype, + 'refreshBucket', + ); + try { + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); + mockRefreshBucket.mockImplementation( + async (bucket, options: { signal?: AbortSignal } = {}) => { + const { signal } = { ...options }; + const prom = promise(); + signal?.addEventListener('abort', () => + prom.rejectP(new nodesErrors.ErrorNodeAborted()), + ); + await prom.p; + }, + ); + nodeManager.refreshBucketQueueAdd(1); + nodeManager.refreshBucketQueueAdd(2); + nodeManager.refreshBucketQueueAdd(3); + nodeManager.refreshBucketQueueAdd(4); + nodeManager.refreshBucketQueueAdd(5); + await nodeManager.stop(); + } finally { + mockRefreshBucket.mockRestore(); + await nodeManager.stop(); + await queue.stop(); + } + }); + test('should pause, resume and stop queue while paused', async () => { + const refreshBucketTimeout = 1000000; + const queue = new Queue({ logger }); + const nodeManager = new NodeManager({ + db, + sigchain: {} as Sigchain, + keyManager, + nodeGraph, + nodeConnectionManager: dummyNodeConnectionManager, + queue, + refreshBucketTimerDefault: refreshBucketTimeout, + logger, + }); + const mockRefreshBucket = jest.spyOn( + NodeManager.prototype, + 'refreshBucket', + ); + try { + logger.setLevel(LogLevel.DEBUG); + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); + mockRefreshBucket.mockImplementation( + async (bucket, options: { signal?: AbortSignal } = {}) => { + const { signal } = { ...options }; + const prom = promise(); + const timer = setTimeout(prom.resolveP, 10); + signal?.addEventListener('abort', () => { + clearTimeout(timer); + prom.rejectP(new nodesErrors.ErrorNodeAborted()); + }); + await prom.p; + }, + ); + nodeManager.refreshBucketQueueAdd(1); + nodeManager.refreshBucketQueueAdd(2); + nodeManager.refreshBucketQueueAdd(3); + nodeManager.refreshBucketQueueAdd(4); + nodeManager.refreshBucketQueueAdd(5); + + // Can pause and resume + nodeManager.refreshBucketQueuePause(); + nodeManager.refreshBucketQueueAdd(6); + nodeManager.refreshBucketQueueAdd(7); + nodeManager.refreshBucketQueueResume(); + await nodeManager.refreshBucketQueueDrained(); + + // Can pause and stop + nodeManager.refreshBucketQueuePause(); + nodeManager.refreshBucketQueueAdd(8); + nodeManager.refreshBucketQueueAdd(9); + nodeManager.refreshBucketQueueAdd(10); + await nodeManager.stop(); + } finally { + mockRefreshBucket.mockRestore(); + await nodeManager.stop(); + await queue.stop(); + } }); }); diff --git a/tests/nodes/utils.test.ts b/tests/nodes/utils.test.ts index ee1aeadc4..0d962f963 100644 --- a/tests/nodes/utils.test.ts +++ b/tests/nodes/utils.test.ts @@ -1,48 +1,69 @@ import type { NodeId } from '@/nodes/types'; +import os from 'os'; +import path from 'path'; +import fs from 'fs'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import lexi from 'lexicographic-integer'; import { IdInternal } from '@matrixai/id'; +import { DB } from '@matrixai/db'; import * as nodesUtils from '@/nodes/utils'; +import * as keysUtils from '@/keys/utils'; +import * as utils from '@/utils'; +import * as testNodesUtils from './utils'; -describe('Nodes utils', () => { - test('basic distance calculation', async () => { - const nodeId1 = IdInternal.create([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 5, - ]); - const nodeId2 = IdInternal.create([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 23, 0, - 0, 0, 0, 0, 0, 0, 0, 1, - ]); - - const distance = nodesUtils.calculateDistance(nodeId1, nodeId2); - expect(distance).toEqual(316912758671486456376015716356n); +describe('nodes/utils', () => { + const logger = new Logger(`nodes/utils test`, LogLevel.WARN, [ + new StreamHandler(), + ]); + let dataDir: string; + let db: DB; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const dbKey = await keysUtils.generateKey(); + const dbPath = `${dataDir}/db`; + db = await DB.createDB({ + dbPath, + logger, + crypto: { + key: dbKey, + ops: { + encrypt: keysUtils.encryptWithKey, + decrypt: keysUtils.decryptWithKey, + }, + }, + }); }); - test('calculates correct first bucket (bucket 0)', async () => { - // "1" XOR "0" = distance of 1 - // Therefore, bucket 0 - const nodeId1 = IdInternal.create([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 1, - ]); - const nodeId2 = IdInternal.create([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ]); - const bucketIndex = nodesUtils.calculateBucketIndex(nodeId1, nodeId2); - expect(bucketIndex).toBe(0); + afterEach(async () => { + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); }); - test('calculates correct arbitrary bucket (bucket 63)', async () => { - const nodeId1 = IdInternal.create([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 255, 0, 0, 0, 0, 0, 0, 0, - ]); - const nodeId2 = IdInternal.create([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ]); - const bucketIndex = nodesUtils.calculateBucketIndex(nodeId1, nodeId2); - expect(bucketIndex).toBe(63); + test('calculating bucket index from the same node ID', () => { + const nodeId1 = IdInternal.create([0]); + const nodeId2 = IdInternal.create([0]); + const distance = nodesUtils.nodeDistance(nodeId1, nodeId2); + expect(distance).toBe(0n); + expect(() => nodesUtils.bucketIndex(nodeId1, nodeId2)).toThrow(RangeError); + }); + test('calculating bucket index 0', () => { + // Distance is calculated based on XOR operation + // 1 ^ 0 == 1 + // Distance of 1 is bucket 0 + const nodeId1 = IdInternal.create([1]); + const nodeId2 = IdInternal.create([0]); + const distance = nodesUtils.nodeDistance(nodeId1, nodeId2); + const bucketIndex = nodesUtils.bucketIndex(nodeId1, nodeId2); + expect(distance).toBe(1n); + expect(bucketIndex).toBe(0); + // Triangle inequality 2^i <= distance < 2^(i + 1) + expect(2 ** bucketIndex <= distance).toBe(true); + expect(distance < 2 ** (bucketIndex + 1)).toBe(true); }); - test('calculates correct last bucket (bucket 255)', async () => { + test('calculating bucket index 255', () => { const nodeId1 = IdInternal.create([ 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -51,7 +72,121 @@ describe('Nodes utils', () => { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); - const bucketIndex = nodesUtils.calculateBucketIndex(nodeId1, nodeId2); + const distance = nodesUtils.nodeDistance(nodeId1, nodeId2); + const bucketIndex = nodesUtils.bucketIndex(nodeId1, nodeId2); expect(bucketIndex).toBe(255); + // Triangle inequality 2^i <= distance < 2^(i + 1) + expect(2 ** bucketIndex <= distance).toBe(true); + expect(distance < 2 ** (bucketIndex + 1)).toBe(true); + }); + test('calculating bucket index randomly', () => { + for (let i = 0; i < 1000; i++) { + const nodeId1 = testNodesUtils.generateRandomNodeId(); + const nodeId2 = testNodesUtils.generateRandomNodeId(); + if (nodeId1.equals(nodeId2)) { + continue; + } + const distance = nodesUtils.nodeDistance(nodeId1, nodeId2); + const bucketIndex = nodesUtils.bucketIndex(nodeId1, nodeId2); + // Triangle inequality 2^i <= distance < 2^(i + 1) + expect(2 ** bucketIndex <= distance).toBe(true); + expect(distance < 2 ** (bucketIndex + 1)).toBe(true); + } + }); + test('parse NodeGraph buckets db key', async () => { + const bucketsDbPath = ['buckets']; + const data: Array<{ + bucketIndex: number; + bucketKey: string; + nodeId: NodeId; + key: Buffer; + }> = []; + for (let i = 0; i < 1000; i++) { + const bucketIndex = Math.floor(Math.random() * (255 + 1)); + const bucketKey = nodesUtils.bucketKey(bucketIndex); + const nodeId = testNodesUtils.generateRandomNodeId(); + data.push({ + bucketIndex, + bucketKey, + nodeId, + key: Buffer.concat([Buffer.from(bucketKey), nodeId]), + }); + await db.put( + ['buckets', bucketKey, nodesUtils.bucketDbKey(nodeId)], + null, + ); + } + // LevelDB will store keys in lexicographic order + // Use the key property as a concatenated buffer of the bucket key and node ID + data.sort((a, b) => Buffer.compare(a.key, b.key)); + let i = 0; + + for await (const [key] of db.iterator({}, bucketsDbPath)) { + const { bucketIndex, bucketKey, nodeId } = nodesUtils.parseBucketsDbKey( + key as Array, + ); + expect(bucketIndex).toBe(data[i].bucketIndex); + expect(bucketKey).toBe(data[i].bucketKey); + expect(nodeId.equals(data[i].nodeId)).toBe(true); + i++; + } + }); + test('parse NodeGraph lastUpdated buckets db key', async () => { + const lastUpdatedDbPath = ['lastUpdated']; + const data: Array<{ + bucketIndex: number; + bucketKey: string; + lastUpdated: number; + nodeId: NodeId; + key: Buffer; + }> = []; + for (let i = 0; i < 1000; i++) { + const bucketIndex = Math.floor(Math.random() * (255 + 1)); + const bucketKey = lexi.pack(bucketIndex, 'hex'); + const lastUpdated = utils.getUnixtime(); + const nodeId = testNodesUtils.generateRandomNodeId(); + const nodeIdKey = nodesUtils.bucketDbKey(nodeId); + const lastUpdatedKey = nodesUtils.lastUpdatedKey(lastUpdated); + data.push({ + bucketIndex, + bucketKey, + lastUpdated, + nodeId, + key: Buffer.concat([Buffer.from(bucketKey), lastUpdatedKey, nodeIdKey]), + }); + await db.put(['lastUpdated', bucketKey, lastUpdatedKey, nodeIdKey], null); + } + // LevelDB will store keys in lexicographic order + // Use the key property as a concatenated buffer of + // the bucket key and last updated and node ID + data.sort((a, b) => Buffer.compare(a.key, b.key)); + let i = 0; + for await (const [key] of db.iterator({}, lastUpdatedDbPath)) { + const { bucketIndex, bucketKey, lastUpdated, nodeId } = + nodesUtils.parseLastUpdatedBucketsDbKey(key as Array); + expect(bucketIndex).toBe(data[i].bucketIndex); + expect(bucketKey).toBe(data[i].bucketKey); + expect(lastUpdated).toBe(data[i].lastUpdated); + expect(nodeId.equals(data[i].nodeId)).toBe(true); + i++; + } + }); + test('should generate random distance for a bucket', async () => { + // Const baseNodeId = testNodesUtils.generateRandomNodeId(); + const zeroNodeId = IdInternal.fromBuffer(Buffer.alloc(32, 0)); + for (let i = 0; i < 255; i++) { + const randomDistance = nodesUtils.generateRandomDistanceForBucket(i); + expect(nodesUtils.bucketIndex(zeroNodeId, randomDistance)).toEqual(i); + } + }); + test('should generate random NodeId for a bucket', async () => { + const baseNodeId = testNodesUtils.generateRandomNodeId(); + for (let i = 0; i < 255; i++) { + const randomDistance = nodesUtils.generateRandomNodeIdForBucket( + baseNodeId, + i, + ); + expect(nodesUtils.bucketIndex(baseNodeId, randomDistance)).toEqual(i); + } }); }); diff --git a/tests/nodes/utils.ts b/tests/nodes/utils.ts index fca9ad53b..e6c603e14 100644 --- a/tests/nodes/utils.ts +++ b/tests/nodes/utils.ts @@ -1,9 +1,27 @@ import type { NodeId, NodeAddress } from '@/nodes/types'; - import type PolykeyAgent from '@/PolykeyAgent'; import { IdInternal } from '@matrixai/id'; +import * as keysUtils from '@/keys/utils'; import { bigInt2Bytes } from '@/utils'; +/** + * Generate random `NodeId` + * If `readable` is `true`, then it will generate a `NodeId` where + * its binary string form will only contain hex characters + * However the `NodeId` will not be uniformly random as it will not cover + * the full space of possible node IDs + * Prefer to keep `readable` `false` if possible to ensure tests are robust + */ +function generateRandomNodeId(readable: boolean = false): NodeId { + if (readable) { + const random = keysUtils.getRandomBytesSync(16).toString('hex'); + return IdInternal.fromString(random); + } else { + const random = keysUtils.getRandomBytesSync(32); + return IdInternal.fromBuffer(random); + } +} + /** * Generate a deterministic NodeId for a specific bucket given an existing NodeId * This requires solving the bucket index (`i`) and distance equation: @@ -61,4 +79,4 @@ async function nodesConnect(localNode: PolykeyAgent, remoteNode: PolykeyAgent) { } as NodeAddress); } -export { generateNodeIdForBucket, nodesConnect }; +export { generateRandomNodeId, generateNodeIdForBucket, nodesConnect }; diff --git a/tests/notifications/NotificationsManager.test.ts b/tests/notifications/NotificationsManager.test.ts index cd3e1eaaa..e2095f191 100644 --- a/tests/notifications/NotificationsManager.test.ts +++ b/tests/notifications/NotificationsManager.test.ts @@ -8,6 +8,7 @@ import path from 'path'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { IdInternal } from '@matrixai/id'; +import Queue from '@/nodes/Queue'; import PolykeyAgent from '@/PolykeyAgent'; import ACL from '@/acl/ACL'; import Sigchain from '@/sigchain/Sigchain'; @@ -50,6 +51,7 @@ describe('NotificationsManager', () => { let acl: ACL; let db: DB; let nodeGraph: NodeGraph; + let queue: Queue; let nodeConnectionManager: NodeConnectionManager; let nodeManager: NodeManager; let keyManager: KeyManager; @@ -112,21 +114,26 @@ describe('NotificationsManager', () => { keyManager, logger, }); + queue = new Queue({ logger }); nodeConnectionManager = new NodeConnectionManager({ nodeGraph, keyManager, proxy, + queue, logger, }); - await nodeConnectionManager.start(); nodeManager = new NodeManager({ db, keyManager, sigchain, nodeConnectionManager, nodeGraph, + queue, logger, }); + await queue.start(); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); // Set up node for receiving notifications receiver = await PolykeyAgent.createPolykeyAgent({ password: password, @@ -146,7 +153,9 @@ describe('NotificationsManager', () => { }, global.defaultTimeout); afterAll(async () => { await receiver.stop(); + await queue.stop(); await nodeConnectionManager.stop(); + await nodeManager.stop(); await nodeGraph.stop(); await proxy.stop(); await sigchain.stop(); @@ -277,28 +286,26 @@ describe('NotificationsManager', () => { pull: null, } as VaultActions, }; - await expect(async () => + + await testUtils.expectRemoteError( notificationsManager.sendNotification( receiver.keyManager.getNodeId(), generalNotification, ), - ).rejects.toThrow( notificationsErrors.ErrorNotificationsPermissionsNotFound, ); - await expect(async () => + await testUtils.expectRemoteError( notificationsManager.sendNotification( receiver.keyManager.getNodeId(), gestaltNotification, ), - ).rejects.toThrow( notificationsErrors.ErrorNotificationsPermissionsNotFound, ); - await expect(async () => + await testUtils.expectRemoteError( notificationsManager.sendNotification( receiver.keyManager.getNodeId(), vaultNotification, ), - ).rejects.toThrow( notificationsErrors.ErrorNotificationsPermissionsNotFound, ); const receivedNotifications = diff --git a/tests/notifications/utils.test.ts b/tests/notifications/utils.test.ts index 5a3b8a617..fa6373e38 100644 --- a/tests/notifications/utils.test.ts +++ b/tests/notifications/utils.test.ts @@ -2,16 +2,15 @@ import type { Notification, NotificationData } from '@/notifications/types'; import type { VaultActions, VaultName } from '@/vaults/types'; import { createPublicKey } from 'crypto'; import { EmbeddedJWK, jwtVerify, exportJWK } from 'jose'; - import * as keysUtils from '@/keys/utils'; import * as notificationsUtils from '@/notifications/utils'; import * as notificationsErrors from '@/notifications/errors'; import * as vaultsUtils from '@/vaults/utils'; import * as nodesUtils from '@/nodes/utils'; -import * as testUtils from '../utils'; +import * as testNodesUtils from '../nodes/utils'; describe('Notifications utils', () => { - const nodeId = testUtils.generateRandomNodeId(); + const nodeId = testNodesUtils.generateRandomNodeId(); const nodeIdEncoded = nodesUtils.encodeNodeId(nodeId); const vaultId = vaultsUtils.generateVaultId(); const vaultIdEncoded = vaultsUtils.encodeVaultId(vaultId); @@ -206,7 +205,7 @@ describe('Notifications utils', () => { }); test('validates correct notifications', async () => { - const nodeIdOther = testUtils.generateRandomNodeId(); + const nodeIdOther = testNodesUtils.generateRandomNodeId(); const nodeIdOtherEncoded = nodesUtils.encodeNodeId(nodeIdOther); const generalNotification: Notification = { data: { diff --git a/tests/sessions/SessionManager.test.ts b/tests/sessions/SessionManager.test.ts index 31461996b..bf479885b 100644 --- a/tests/sessions/SessionManager.test.ts +++ b/tests/sessions/SessionManager.test.ts @@ -3,8 +3,10 @@ import os from 'os'; import path from 'path'; import { DB } from '@matrixai/db'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { KeyManager, utils as keysUtils } from '@/keys'; -import { SessionManager, errors as sessionsErrors } from '@/sessions'; +import KeyManager from '@/keys/KeyManager'; +import * as keysUtils from '@/keys/utils'; +import SessionManager from '@/sessions/SessionManager'; +import * as sessionsErrors from '@/sessions/errors'; import { sleep } from '@/utils'; import * as testUtils from '../utils'; diff --git a/tests/sigchain/Sigchain.test.ts b/tests/sigchain/Sigchain.test.ts index b6ff170ef..a3bbfb193 100644 --- a/tests/sigchain/Sigchain.test.ts +++ b/tests/sigchain/Sigchain.test.ts @@ -6,12 +6,14 @@ import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; -import { KeyManager, utils as keysUtils } from '@/keys'; -import { Sigchain } from '@/sigchain'; +import KeyManager from '@/keys/KeyManager'; +import Sigchain from '@/sigchain/Sigchain'; import * as claimsUtils from '@/claims/utils'; import * as sigchainErrors from '@/sigchain/errors'; -import { utils as nodesUtils } from '@/nodes'; +import * as nodesUtils from '@/nodes/utils'; +import * as keysUtils from '@/keys/utils'; import * as testUtils from '../utils'; +import * as testNodesUtils from '../nodes/utils'; describe('Sigchain', () => { const logger = new Logger('Sigchain Test', LogLevel.WARN, [ @@ -19,25 +21,25 @@ describe('Sigchain', () => { ]); const password = 'password'; const srcNodeIdEncoded = nodesUtils.encodeNodeId( - testUtils.generateRandomNodeId(), + testNodesUtils.generateRandomNodeId(), ); const nodeId2Encoded = nodesUtils.encodeNodeId( - testUtils.generateRandomNodeId(), + testNodesUtils.generateRandomNodeId(), ); const nodeId3Encoded = nodesUtils.encodeNodeId( - testUtils.generateRandomNodeId(), + testNodesUtils.generateRandomNodeId(), ); const nodeIdAEncoded = nodesUtils.encodeNodeId( - testUtils.generateRandomNodeId(), + testNodesUtils.generateRandomNodeId(), ); const nodeIdBEncoded = nodesUtils.encodeNodeId( - testUtils.generateRandomNodeId(), + testNodesUtils.generateRandomNodeId(), ); const nodeIdCEncoded = nodesUtils.encodeNodeId( - testUtils.generateRandomNodeId(), + testNodesUtils.generateRandomNodeId(), ); const nodeIdDEncoded = nodesUtils.encodeNodeId( - testUtils.generateRandomNodeId(), + testNodesUtils.generateRandomNodeId(), ); let mockedGenerateKeyPair: jest.SpyInstance; @@ -92,7 +94,7 @@ describe('Sigchain', () => { }); }); - test('session readiness', async () => { + test('sigchain readiness', async () => { const sigchain = await Sigchain.createSigchain({ keyManager, db, logger }); await expect(async () => { await sigchain.destroy(); @@ -104,13 +106,13 @@ describe('Sigchain', () => { await expect(async () => { await sigchain.start(); }).rejects.toThrow(sigchainErrors.ErrorSigchainDestroyed); - await expect(async () => { - await sigchain.getSequenceNumber(); - }).rejects.toThrow(sigchainErrors.ErrorSigchainNotRunning); }); test('async start initialises the sequence number', async () => { const sigchain = await Sigchain.createSigchain({ keyManager, db, logger }); - const sequenceNumber = await sigchain.getSequenceNumber(); + const sequenceNumber = await sigchain.withTransactionF(async (tran) => + // @ts-ignore - get protected method + sigchain.getSequenceNumber(tran), + ); expect(sequenceNumber).toBe(0); await sigchain.stop(); }); @@ -235,7 +237,7 @@ describe('Sigchain', () => { expect(verified2).toBe(true); // Check the hash of the previous claim is correct - const verifiedHash = await claimsUtils.verifyHashOfClaim( + const verifiedHash = claimsUtils.verifyHashOfClaim( claim1, decoded2.payload.hPrev as string, ); @@ -248,8 +250,14 @@ describe('Sigchain', () => { // Create a claim // Firstly, check that we can add an existing claim if it's the first claim // in the sigchain - const hPrev1 = await sigchain.getHashPrevious(); - const seq1 = await sigchain.getSequenceNumber(); + const hPrev1 = await sigchain.withTransactionF(async (tran) => + // @ts-ignore - get protected method + sigchain.getHashPrevious(tran), + ); + const seq1 = await sigchain.withTransactionF(async (tran) => + // @ts-ignore - get protected method + sigchain.getSequenceNumber(tran), + ); expect(hPrev1).toBeNull(); expect(seq1).toBe(0); const claim1 = await claimsUtils.createClaim({ @@ -264,8 +272,14 @@ describe('Sigchain', () => { kid: nodeIdAEncoded, }); await sigchain.addExistingClaim(claim1); - const hPrev2 = await sigchain.getHashPrevious(); - const seq2 = await sigchain.getSequenceNumber(); + const hPrev2 = await sigchain.withTransactionF(async (tran) => + // @ts-ignore - get protected method + sigchain.getHashPrevious(tran), + ); + const seq2 = await sigchain.withTransactionF(async (tran) => + // @ts-ignore - get protected method + sigchain.getSequenceNumber(tran), + ); expect(hPrev2).not.toBeNull(); expect(seq2).toBe(1); @@ -282,8 +296,14 @@ describe('Sigchain', () => { kid: nodeIdAEncoded, }); await sigchain.addExistingClaim(claim2); - const hPrev3 = await sigchain.getHashPrevious(); - const seq3 = await sigchain.getSequenceNumber(); + const hPrev3 = await sigchain.withTransactionF(async (tran) => + // @ts-ignore - get protected method + sigchain.getHashPrevious(tran), + ); + const seq3 = await sigchain.withTransactionF(async (tran) => + // @ts-ignore - get protected method + sigchain.getSequenceNumber(tran), + ); expect(hPrev3).not.toBeNull(); expect(seq3).toBe(2); @@ -325,7 +345,9 @@ describe('Sigchain', () => { // Add 10 claims for (let i = 1; i <= 5; i++) { - const node2 = nodesUtils.encodeNodeId(testUtils.generateRandomNodeId()); + const node2 = nodesUtils.encodeNodeId( + testNodesUtils.generateRandomNodeId(), + ); node2s.push(node2); const nodeLink: ClaimData = { type: 'node', @@ -374,7 +396,9 @@ describe('Sigchain', () => { for (let i = 1; i <= 30; i++) { // If even, add a node link if (i % 2 === 0) { - const node2 = nodesUtils.encodeNodeId(testUtils.generateRandomNodeId()); + const node2 = nodesUtils.encodeNodeId( + testNodesUtils.generateRandomNodeId(), + ); nodes[i] = node2; const nodeLink: ClaimData = { type: 'node', diff --git a/tests/status/Status.test.ts b/tests/status/Status.test.ts index 311f89a11..0b0744002 100644 --- a/tests/status/Status.test.ts +++ b/tests/status/Status.test.ts @@ -6,15 +6,15 @@ import path from 'path'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import config from '@/config'; import { Status, errors as statusErrors } from '@/status'; -import * as testUtils from '../utils'; +import * as testNodesUtils from '../nodes/utils'; describe('Status', () => { const logger = new Logger(`${Status.name} Test`, LogLevel.WARN, [ new StreamHandler(), ]); - const nodeId1 = testUtils.generateRandomNodeId(); - const nodeId2 = testUtils.generateRandomNodeId(); - const nodeId3 = testUtils.generateRandomNodeId(); + const nodeId1 = testNodesUtils.generateRandomNodeId(); + const nodeId2 = testNodesUtils.generateRandomNodeId(); + const nodeId3 = testNodesUtils.generateRandomNodeId(); let dataDir: string; beforeEach(async () => { dataDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'status-test-')); diff --git a/tests/utils.test.ts b/tests/utils.test.ts index 5f6ee891e..1896fbedc 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -1,6 +1,4 @@ -import type { ResourceAcquire } from '@/utils'; import os from 'os'; -import { Mutex } from 'async-mutex'; import * as utils from '@/utils'; describe('utils', () => { @@ -15,247 +13,4 @@ describe('utils', () => { expect(p).toBe(`${homeDir}/AppData/Local/polykey`); } }); - test('withF resource context', async () => { - // No resources - const result1 = await utils.withF([], async () => { - return 'bar'; - }); - expect(result1).toBe('bar'); - // Noop resource - const result2 = await utils.withF( - [ - async () => { - return [async () => {}]; - }, - ], - async () => { - return 'foo'; - }, - ); - expect(result2).toBe('foo'); - // Counter resource - let counter = 1; - const result3 = await utils.withF( - [ - async () => { - counter++; - return [ - async () => { - counter--; - }, - counter, - ]; - }, - ], - async ([c]) => { - expect(c).toBe(2); - return c / 2; - }, - ); - expect(result3).toBe(1); - expect(counter).toBe(1); - // Multiple resources - const result4 = await utils.withF( - [ - async () => { - return [async () => {}, 123]; - }, - async () => { - return [async () => {}]; - }, - async () => { - return [async () => {}, 'hello world']; - }, - ], - async ([n, u, s]) => { - expect(u).toBe(undefined); - return [n, s]; - }, - ); - expect(result4).toStrictEqual([123, 'hello world']); - // Multiple resources, but only take the first - const result5 = await utils.withF( - [ - async () => { - return [async () => {}, 123]; - }, - async () => { - return [async () => {}]; - }, - async () => { - return [async () => {}, 'hello world']; - }, - ], - async ([n]) => { - return n; - }, - ); - expect(result5).toBe(123); - // Multiple resources outside requires type declaration - const resourceAcquires6: [ - ResourceAcquire, - ResourceAcquire, - ResourceAcquire, - ] = [ - async () => { - return [async () => {}, 123]; - }, - async () => { - return [async () => {}]; - }, - async () => { - return [async () => {}, 'hello world']; - }, - ]; - const result6 = await utils.withF(resourceAcquires6, async ([n, u, s]) => { - expect(u).toBe(undefined); - return [n, s]; - }); - expect(result6).toStrictEqual([123, 'hello world']); - // Multiple resources outside can also use const - const resourceAcquires7 = [ - async () => { - return [async () => {}, 123] as const; - }, - async () => { - return [async () => {}] as const; - }, - async () => { - return [async () => {}, 'hello world'] as const; - }, - ] as const; - const result7 = await utils.withF(resourceAcquires7, async ([n, u, s]) => { - expect(u).toBe(undefined); - return [n, s]; - }); - expect(result7).toStrictEqual([123, 'hello world']); - // It must be given a explicit type, or `as const` can be used internally - const acquire8: ResourceAcquire = async () => { - return [async () => {}, 123]; - }; - const result8 = await utils.withF([acquire8], async () => { - return 'done'; - }); - expect(result8).toBe('done'); - const acquire9 = async () => { - return [async () => {}, 123] as const; - }; - const result9 = await utils.withF([acquire9], async () => { - return 'done'; - }); - expect(result9).toBe('done'); - // Order of acquisition is left to right - // Order of release is right ot left - const lock1 = new Mutex(); - const lock2 = new Mutex(); - const acquireOrder: Array = []; - const releaseOrder: Array = []; - await utils.withF( - [ - async () => { - const release = await lock1.acquire(); - acquireOrder.push(lock1); - return [ - async () => { - releaseOrder.push(lock1); - release(); - }, - lock1, - ]; - }, - async () => { - const release = await lock2.acquire(); - acquireOrder.push(lock2); - return [ - async () => { - releaseOrder.push(lock2); - release(); - }, - lock2, - ]; - }, - ], - async ([l1, l2]) => { - expect(l1.isLocked()).toBe(true); - expect(l2.isLocked()).toBe(true); - }, - ); - expect(acquireOrder).toStrictEqual([lock1, lock2]); - expect(releaseOrder).toStrictEqual([lock2, lock1]); - }); - test('withG resource context', async () => { - // No resources - const g1 = utils.withG([], async function* () { - yield 'first'; - yield 'second'; - return 'last'; - }); - expect(await g1.next()).toStrictEqual({ value: 'first', done: false }); - expect(await g1.next()).toStrictEqual({ value: 'second', done: false }); - expect(await g1.next()).toStrictEqual({ value: 'last', done: true }); - // Noop resource - const g2 = await utils.withG( - [ - async () => { - return [async () => {}]; - }, - ], - async function* () { - yield 'first'; - return 'last'; - }, - ); - expect(await g2.next()).toStrictEqual({ value: 'first', done: false }); - expect(await g2.next()).toStrictEqual({ value: 'last', done: true }); - // Order of acquisition is left to right - // Order of release is right ot left - const lock1 = new Mutex(); - const lock2 = new Mutex(); - const acquireOrder: Array = []; - const releaseOrder: Array = []; - const g3 = utils.withG( - [ - async () => { - const release = await lock1.acquire(); - acquireOrder.push(lock1); - return [ - async () => { - releaseOrder.push(lock1); - release(); - }, - lock1, - ]; - }, - async () => { - const release = await lock2.acquire(); - acquireOrder.push(lock2); - return [ - async () => { - releaseOrder.push(lock2); - release(); - }, - lock2, - ]; - }, - ], - async function* ([l1, l2]) { - expect(l1.isLocked()).toBe(true); - expect(l2.isLocked()).toBe(true); - yield 'first'; - yield 'second'; - return 'last'; - }, - ); - expect(await g3.next()).toStrictEqual({ value: 'first', done: false }); - expect(lock1.isLocked()).toBe(true); - expect(lock2.isLocked()).toBe(true); - expect(await g3.next()).toStrictEqual({ value: 'second', done: false }); - expect(lock1.isLocked()).toBe(true); - expect(lock2.isLocked()).toBe(true); - expect(await g3.next()).toStrictEqual({ value: 'last', done: true }); - expect(lock1.isLocked()).toBe(false); - expect(lock2.isLocked()).toBe(false); - expect(acquireOrder).toStrictEqual([lock1, lock2]); - expect(releaseOrder).toStrictEqual([lock2, lock1]); - }); }); diff --git a/tests/utils.ts b/tests/utils.ts index 3f446b465..e607faff1 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,6 +1,6 @@ -import type { StatusLive } from '@/status/types'; -import type { NodeId } from '@/nodes/types'; import type { Host } from '@/network/types'; +import type { NodeId } from '@/nodes/types'; +import type { StatusLive } from '@/status/types'; import path from 'path'; import fs from 'fs'; import lock from 'fd-lock'; @@ -12,6 +12,7 @@ import GRPCClientClient from '@/client/GRPCClientClient'; import * as clientUtils from '@/client/utils'; import * as keysUtils from '@/keys/utils'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import * as grpcErrors from '@/grpc/errors'; import { sleep } from '@/utils'; import config from '@/config'; @@ -70,24 +71,25 @@ async function setupGlobalKeypair() { } } -/** - * Setup the global agent - * Use this in beforeAll, and use the closeGlobalAgent in afterAll - * This is expected to be executed by multiple worker processes - * Uses a references directory as a reference count - * Uses fd-lock to serialise access - * This means all test modules using this will be serialised - * Any beforeAll must use globalThis.maxTimeout - * Tips for usage: - * * Do not restart this global agent - * * Ensure client-side side-effects are removed at the end of each test - * * Ensure server-side side-effects are removed at the end of each test - */ +// FIXME: what is going on here? is this getting removed? +// /** +// * Setup the global agent +// * Use this in beforeAll, and use the closeGlobalAgent in afterAll +// * This is expected to be executed by multiple worker processes +// * Uses a references directory as a reference count +// * Uses fd-lock to serialise access +// * This means all test modules using this will be serialised +// * Any beforeAll must use globalThis.maxTimeout +// * Tips for usage: +// * * Do not restart this global agent +// * * Ensure client-side side-effects are removed at the end of each test +// * * Ensure server-side side-effects are removed at the end of each test +// */ async function setupGlobalAgent( logger: Logger = new Logger(setupGlobalAgent.name, LogLevel.WARN, [ new StreamHandler(), ]), -) { +): Promise { const globalAgentPassword = 'password'; const globalAgentDir = path.join(globalThis.dataDir, 'agent'); // The references directory will act like our reference count @@ -184,4 +186,39 @@ function generateRandomNodeId(): NodeId { return IdInternal.fromString(random); } -export { setupGlobalKeypair, setupGlobalAgent, generateRandomNodeId }; +const expectRemoteError = async ( + promise: Promise, + error, +): Promise => { + await expect(promise).rejects.toThrow(grpcErrors.ErrorPolykeyRemote); + try { + return await promise; + } catch (e) { + expect(e.cause).toBeInstanceOf(error); + } +}; + +function describeIf(condition, name, f) { + if (condition) { + describe(name, f); + } else { + describe.skip(name, f); + } +} + +function testIf(condition, name, f, timeout?) { + if (condition) { + test(name, f, timeout); + } else { + test.skip(name, f, timeout); + } +} + +export { + setupGlobalKeypair, + generateRandomNodeId, + expectRemoteError, + setupGlobalAgent, + describeIf, + testIf, +}; diff --git a/tests/validation/utils.nodes.test.ts b/tests/validation/utils.nodes.test.ts index 8374312ef..9cb301d7b 100644 --- a/tests/validation/utils.nodes.test.ts +++ b/tests/validation/utils.nodes.test.ts @@ -35,6 +35,27 @@ describe('nodes validationUtils', () => { }); expect(parsed[1]).toBeFalsy(); }); + test('parseSeedNodes - valid nodes optionally have pk://', () => { + const rawSeedNodes = + `pk://${nodeIdEncoded1}@${hostname}:${port1};` + + `pk://${nodeIdEncoded2}@${hostIPv4}:${port2};` + + `pk://${nodeIdEncoded3}@${hostIPv6}:${port3};`; + const parsed = validationUtils.parseSeedNodes(rawSeedNodes); + const seeds = parsed[0]; + expect(seeds[nodeIdEncoded1]).toStrictEqual({ + host: hostname, + port: port1, + }); + expect(seeds[nodeIdEncoded2]).toStrictEqual({ + host: hostIPv4, + port: port2, + }); + expect(seeds[nodeIdEncoded3]).toStrictEqual({ + host: hostIPv6.replace(/\[|\]/g, ''), + port: port3, + }); + expect(parsed[1]).toBeFalsy(); + }); test('parseSeedNodes - invalid node ID', () => { const rawSeedNodes = `INVALIDNODEID@${hostname}:${port1}`; expect(() => validationUtils.parseSeedNodes(rawSeedNodes)).toThrow( @@ -47,6 +68,36 @@ describe('nodes validationUtils', () => { validationErrors.ErrorParse, ); }); - test.todo('parseSeedNodes - invalid port'); - test.todo('parseSeedNodes - invalid structure'); + test('parseSeedNodes - invalid URL', () => { + expect(() => + validationUtils.parseSeedNodes('thisisinvalid!@#$%^&*()'), + ).toThrow(validationErrors.ErrorParse); + }); + test('parseSeedNodes - invalid port', async () => { + expect(() => + validationUtils.parseSeedNodes(`${nodeIdEncoded1}@$invalidHost:-55555`), + ).toThrow(validationErrors.ErrorParse); + expect(() => + validationUtils.parseSeedNodes( + `${nodeIdEncoded1}@$invalidHost:999999999`, + ), + ).toThrow(validationErrors.ErrorParse); + }); + test('parseSeedNodes - invalid structure', async () => { + expect(() => + validationUtils.parseSeedNodes( + `${nodeIdEncoded1}!#$%^&*()@$invalidHost:${port1}`, + ), + ).toThrow(validationErrors.ErrorParse); + expect(() => + validationUtils.parseSeedNodes( + `pk:/${nodeIdEncoded1}@$invalidHost:${port1}`, + ), + ).toThrow(validationErrors.ErrorParse); + expect(() => + validationUtils.parseSeedNodes( + `asdpk://${nodeIdEncoded1}@$invalidHost:${port1}`, + ), + ).toThrow(validationErrors.ErrorParse); + }); }); diff --git a/tests/vaults/VaultInternal.test.ts b/tests/vaults/VaultInternal.test.ts index 34f03d70c..d95ae1c2c 100644 --- a/tests/vaults/VaultInternal.test.ts +++ b/tests/vaults/VaultInternal.test.ts @@ -1,7 +1,7 @@ import type { VaultId } from '@/vaults/types'; import type { Vault } from '@/vaults/Vault'; import type KeyManager from '@/keys/KeyManager'; -import type { DBDomain, DBLevel } from '@matrixai/db'; +import type { LevelPath } from '@matrixai/db'; import os from 'os'; import path from 'path'; import fs from 'fs'; @@ -15,7 +15,7 @@ import * as vaultsErrors from '@/vaults/errors'; import { sleep } from '@/utils'; import * as keysUtils from '@/keys/utils'; import * as vaultsUtils from '@/vaults/utils'; -import * as testsUtils from '../utils'; +import * as nodeTestUtils from '../nodes/utils'; jest.mock('@/keys/utils', () => ({ ...jest.requireActual('@/keys/utils'), @@ -35,12 +35,11 @@ describe('VaultInternal', () => { let efs: EncryptedFS; let db: DB; - let vaultsDb: DBLevel; - let vaultsDbDomain: DBDomain; + let vaultsDbPath: LevelPath; const fakeKeyManager = { getNodeId: () => { - return testsUtils.generateRandomNodeId(); + return nodeTestUtils.generateRandomNodeId(); }, } as KeyManager; const secret1 = { name: 'secret-1', content: 'secret-content-1' }; @@ -79,8 +78,7 @@ describe('VaultInternal', () => { fs: fs, logger: logger, }); - vaultsDbDomain = ['vaults']; - vaultsDb = await db.level(vaultsDbDomain[0]); + vaultsDbPath = ['vaults']; vaultId = vaultsUtils.generateVaultId(); vault = await VaultInternal.createVaultInternal({ @@ -90,8 +88,7 @@ describe('VaultInternal', () => { logger, fresh: true, db, - vaultsDb, - vaultsDbDomain, + vaultsDbPath, vaultName: 'testVault', }); }); @@ -152,8 +149,7 @@ describe('VaultInternal', () => { fresh: false, db, vaultName: 'testVault1', - vaultsDb, - vaultsDbDomain, + vaultsDbPath, }); await vault.readF(async (efs) => { expect((await efs.readFile('secret-1')).toString()).toStrictEqual( @@ -494,8 +490,11 @@ describe('VaultInternal', () => { test('cannot commit when the remote field is set', async () => { // Write remote metadata await db.put( - [...vaultsDbDomain, vaultsUtils.encodeVaultId(vaultId)], - VaultInternal.remoteKey, + [ + ...vaultsDbPath, + vaultsUtils.encodeVaultId(vaultId), + VaultInternal.remoteKey, + ], { remoteNode: '', remoteVault: '' }, ); const commit = (await vault.log(undefined, 1))[0]; @@ -510,30 +509,34 @@ describe('VaultInternal', () => { }), ).rejects.toThrow(vaultsErrors.ErrorVaultRemoteDefined); }); - test('cannot checkout old commits after branching commit', async () => { - await vault.writeF(async (efs) => { - await efs.writeFile('test1', 'testdata1'); - }); - const secondCommit = (await vault.log(undefined, 1))[0].commitId; - await vault.writeF(async (efs) => { - await efs.writeFile('test2', 'testdata2'); - }); - const thirdCommit = (await vault.log(undefined, 1))[0].commitId; - await vault.writeF(async (efs) => { - await efs.writeFile('test3', 'testdata3'); - }); - const fourthCommit = (await vault.log(undefined, 1))[0].commitId; - await vault.version(secondCommit); - await vault.writeF(async (efs) => { - await efs.writeFile('test4', 'testdata4'); - }); - await expect(() => { - return vault.version(thirdCommit); - }).rejects.toThrow(); - await expect(() => { - return vault.version(fourthCommit); - }).rejects.toThrow(); - }); + test( + 'cannot checkout old commits after branching commit', + async () => { + await vault.writeF(async (efs) => { + await efs.writeFile('test1', 'testdata1'); + }); + const secondCommit = (await vault.log(undefined, 1))[0].commitId; + await vault.writeF(async (efs) => { + await efs.writeFile('test2', 'testdata2'); + }); + const thirdCommit = (await vault.log(undefined, 1))[0].commitId; + await vault.writeF(async (efs) => { + await efs.writeFile('test3', 'testdata3'); + }); + const fourthCommit = (await vault.log(undefined, 1))[0].commitId; + await vault.version(secondCommit); + await vault.writeF(async (efs) => { + await efs.writeFile('test4', 'testdata4'); + }); + await expect(() => { + return vault.version(thirdCommit); + }).rejects.toThrow(); + await expect(() => { + return vault.version(fourthCommit); + }).rejects.toThrow(); + }, + global.defaultTimeout, + ); test('can recover from dirty state', async () => { await vault.writeF(async (efs) => { await efs.writeFile('secret-1', 'secret-content'); @@ -547,11 +550,11 @@ describe('VaultInternal', () => { await vaultEFS.writeFile('dirty', 'dirtyData'); await vaultEFS.writeFile('secret-1', 'dirtyData'); // Setting dirty flag true - const vaultMetadataDbDomain = [ - ...vaultsDbDomain, + const vaultMetadataDbPath = [ + ...vaultsDbPath, vaultsUtils.encodeVaultId(vaultId), ]; - await db.put(vaultMetadataDbDomain, VaultInternal.dirtyKey, true); + await db.put([...vaultMetadataDbPath, VaultInternal.dirtyKey], true); // Restarting vault await vault.stop(); @@ -607,11 +610,11 @@ describe('VaultInternal', () => { }); // Setting dirty flag true - const vaultMetadataDbDomain = [ - ...vaultsDbDomain, + const vaultMetadataDbPath = [ + ...vaultsDbPath, vaultsUtils.encodeVaultId(vaultId), ]; - await db.put(vaultMetadataDbDomain, VaultInternal.dirtyKey, true); + await db.put([...vaultMetadataDbPath, VaultInternal.dirtyKey], true); // Restarting vault await vault.stop(); @@ -665,7 +668,7 @@ describe('VaultInternal', () => { await efs.writeFile(secret2.name, secret2.content); }); const commit = (await vault.log())[0].commitId; - const gen = await vault.readG(async function* (efs): AsyncGenerator { + const gen = vault.readG(async function* (efs): AsyncGenerator { yield expect((await efs.readFile(secret1.name)).toString()).toEqual( secret1.content, ); @@ -675,67 +678,71 @@ describe('VaultInternal', () => { expect(log).toHaveLength(2); expect(log[0].commitId).toStrictEqual(commit); }); - test('garbage collection', async () => { - await vault.writeF(async (efs) => { - await efs.writeFile(secret1.name, secret1.content); - }); - await vault.writeF(async (efs) => { - await efs.writeFile(secret2.name, secret2.content); - }); - await vault.writeF(async (efs) => { - await efs.writeFile(secret3.name, secret3.content); - }); - // @ts-ignore: kidnap efs - const vaultEfs = vault.efs; - // @ts-ignore: kidnap efs - const vaultEfsData = vault.efsVault; - const quickCommit = async (ref: string, secret: string) => { - await vaultEfsData.writeFile(secret, secret); - await git.add({ - fs: vaultEfs, - dir: vault.vaultDataDir, - gitdir: vault.vaultGitDir, - filepath: secret, + test( + 'garbage collection', + async () => { + await vault.writeF(async (efs) => { + await efs.writeFile(secret1.name, secret1.content); }); - return await git.commit({ - fs: vaultEfs, - dir: vault.vaultDataDir, - gitdir: vault.vaultGitDir, - author: { - name: 'test', - email: 'test', - }, - message: 'test', - ref: ref, + await vault.writeF(async (efs) => { + await efs.writeFile(secret2.name, secret2.content); }); - }; - const log = await vault.log(); - let num = 5; - const refs: string[] = []; - for (const logElement of log) { - refs.push(await quickCommit(logElement.commitId, `secret-${num++}`)); - } - // @ts-ignore - await vault.garbageCollectGitObjects(); - - for (const ref of refs) { - await expect( - git.checkout({ + await vault.writeF(async (efs) => { + await efs.writeFile(secret3.name, secret3.content); + }); + // @ts-ignore: kidnap efs + const vaultEfs = vault.efs; + // @ts-ignore: kidnap efs + const vaultEfsData = vault.efsVault; + const quickCommit = async (ref: string, secret: string) => { + await vaultEfsData.writeFile(secret, secret); + await git.add({ fs: vaultEfs, dir: vault.vaultDataDir, gitdir: vault.vaultGitDir, - ref, - }), - ).rejects.toThrow(git.Errors.CommitNotFetchedError); - } - }); + filepath: secret, + }); + return await git.commit({ + fs: vaultEfs, + dir: vault.vaultDataDir, + gitdir: vault.vaultGitDir, + author: { + name: 'test', + email: 'test', + }, + message: 'test', + ref: ref, + }); + }; + const log = await vault.log(); + let num = 5; + const refs: string[] = []; + for (const logElement of log) { + refs.push(await quickCommit(logElement.commitId, `secret-${num++}`)); + } + // @ts-ignore + await vault.garbageCollectGitObjects(); + + for (const ref of refs) { + await expect( + git.checkout({ + fs: vaultEfs, + dir: vault.vaultDataDir, + gitdir: vault.vaultGitDir, + ref, + }), + ).rejects.toThrow(git.Errors.CommitNotFetchedError); + } + }, + global.defaultTimeout * 2, + ); // Locking tests const waitDelay = 200; test('writeF respects read and write locking', async () => { // @ts-ignore: kidnap lock const lock = vault.lock; // Hold a write lock - const releaseWrite = await lock.acquireWrite(); + const [releaseWrite] = await lock.write()(); let finished = false; const writeP = vault.writeF(async () => { @@ -743,17 +750,17 @@ describe('VaultInternal', () => { }); await sleep(waitDelay); expect(finished).toBe(false); - releaseWrite(); + await releaseWrite(); await writeP; expect(finished).toBe(true); - const releaseRead = await lock.acquireRead(); + const [releaseRead] = await lock.read()(); finished = false; const writeP2 = vault.writeF(async () => { finished = true; }); await sleep(waitDelay); - releaseRead(); + await releaseRead(); await writeP2; expect(finished).toBe(true); }); @@ -761,7 +768,7 @@ describe('VaultInternal', () => { // @ts-ignore: kidnap lock const lock = vault.lock; // Hold a write lock - const releaseWrite = await lock.acquireWrite(); + const [releaseWrite] = await lock.write()(); let finished = false; const writeGen = vault.writeG(async function* () { @@ -772,11 +779,11 @@ describe('VaultInternal', () => { const runP = runGen(writeGen); await sleep(waitDelay); expect(finished).toBe(false); - releaseWrite(); + await releaseWrite(); await runP; expect(finished).toBe(true); - const releaseRead = await lock.acquireRead(); + const [releaseRead] = await lock.read()(); finished = false; const writeGen2 = vault.writeG(async function* () { yield; @@ -785,7 +792,7 @@ describe('VaultInternal', () => { }); const runP2 = runGen(writeGen2); await sleep(waitDelay); - releaseRead(); + await releaseRead(); await runP2; expect(finished).toBe(true); }); @@ -793,7 +800,7 @@ describe('VaultInternal', () => { // @ts-ignore: kidnap lock const lock = vault.lock; // Hold a write lock - const releaseWrite = await lock.acquireWrite(); + const [releaseWrite] = await lock.write()(); let finished = false; const writeP = vault.readF(async () => { @@ -801,7 +808,7 @@ describe('VaultInternal', () => { }); await sleep(waitDelay); expect(finished).toBe(false); - releaseWrite(); + await releaseWrite(); await writeP; expect(finished).toBe(true); }); @@ -809,7 +816,7 @@ describe('VaultInternal', () => { // @ts-ignore: kidnap lock const lock = vault.lock; // Hold a write lock - const releaseWrite = await lock.acquireWrite(); + const [releaseWrite] = await lock.write()(); let finished = false; const writeGen = vault.readG(async function* () { yield; @@ -819,7 +826,7 @@ describe('VaultInternal', () => { const runP = runGen(writeGen); await sleep(waitDelay); expect(finished).toBe(false); - releaseWrite(); + await releaseWrite(); await runP; expect(finished).toBe(true); }); @@ -827,7 +834,7 @@ describe('VaultInternal', () => { // @ts-ignore: kidnap lock const lock = vault.lock; // Hold a write lock - const releaseRead = await lock.acquireRead(); + const [releaseRead] = await lock.read()(); const finished: boolean[] = []; const doThing = async () => { finished.push(true); @@ -839,13 +846,13 @@ describe('VaultInternal', () => { vault.readF(doThing), ]); expect(finished.length).toBe(4); - releaseRead(); + await releaseRead(); }); test('readG allows concurrent reads', async () => { // @ts-ignore: kidnap lock const lock = vault.lock; // Hold a write lock - const releaseRead = await lock.acquireRead(); + const [releaseRead] = await lock.read()(); const finished: boolean[] = []; const doThing = async function* () { yield; @@ -859,7 +866,7 @@ describe('VaultInternal', () => { runGen(vault.readG(doThing)), ]); expect(finished.length).toBe(4); - releaseRead(); + await releaseRead(); }); // Life-cycle test('can create with CreateVaultInternal', async () => { @@ -871,8 +878,7 @@ describe('VaultInternal', () => { efs, keyManager: fakeKeyManager, vaultId: vaultId1, - vaultsDb, - vaultsDbDomain, + vaultsDbPath, logger, }); // Data exists for vault now @@ -894,8 +900,7 @@ describe('VaultInternal', () => { efs, keyManager: fakeKeyManager, vaultId: vaultId1, - vaultsDb, - vaultsDbDomain, + vaultsDbPath, logger, }); // Data exists for vault now @@ -914,8 +919,7 @@ describe('VaultInternal', () => { efs, keyManager: fakeKeyManager, vaultId: vaultId1, - vaultsDb, - vaultsDbDomain, + vaultsDbPath, logger, }); diff --git a/tests/vaults/VaultManager.test.ts b/tests/vaults/VaultManager.test.ts index 2117ea7a8..e57495cb9 100644 --- a/tests/vaults/VaultManager.test.ts +++ b/tests/vaults/VaultManager.test.ts @@ -7,6 +7,8 @@ import type { } from '@/vaults/types'; import type NotificationsManager from '@/notifications/NotificationsManager'; import type { Host, Port, TLSConfig } from '@/network/types'; +import type NodeManager from '@/nodes/NodeManager'; +import type Queue from '@/nodes/Queue'; import fs from 'fs'; import os from 'os'; import path from 'path'; @@ -15,6 +17,7 @@ import { IdInternal } from '@matrixai/id'; import { DB } from '@matrixai/db'; import { destroyed, running } from '@matrixai/async-init'; import git from 'isomorphic-git'; +import { RWLockWriter } from '@matrixai/async-locks'; import ACL from '@/acl/ACL'; import GestaltGraph from '@/gestalts/GestaltGraph'; import NodeConnectionManager from '@/nodes/NodeConnectionManager'; @@ -29,7 +32,8 @@ import * as vaultsUtils from '@/vaults/utils'; import * as keysUtils from '@/keys/utils'; import { sleep } from '@/utils'; import VaultInternal from '@/vaults/VaultInternal'; -import * as testsUtils from '../utils'; +import * as nodeTestUtils from '../nodes/utils'; +import { expectRemoteError } from '../utils'; const mockedGenerateDeterministicKeyPair = jest .spyOn(keysUtils, 'generateDeterministicKeyPair') @@ -63,7 +67,7 @@ describe('VaultManager', () => { let db: DB; // We only ever use this to get NodeId, No need to create a whole one - const nodeId = testsUtils.generateRandomNodeId(); + const nodeId = nodeTestUtils.generateRandomNodeId(); const dummyKeyManager = { getNodeId: () => nodeId, } as KeyManager; @@ -139,44 +143,50 @@ describe('VaultManager', () => { await vaultManager?.destroy(); } }); - test('can create many vaults and open a vault', async () => { - const vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - keyManager: dummyKeyManager, - gestaltGraph: {} as GestaltGraph, - nodeConnectionManager: {} as NodeConnectionManager, - acl: {} as ACL, - notificationsManager: {} as NotificationsManager, - db, - logger: logger.getChild(VaultManager.name), - }); - try { - const vaultNames = [ - 'Vault1', - 'Vault2', - 'Vault3', - 'Vault4', - 'Vault5', - 'Vault6', - 'Vault7', - 'Vault8', - 'Vault9', - 'Vault10', - 'Vault11', - 'Vault12', - 'Vault13', - 'Vault14', - 'Vault15', - ]; - for (const vaultName of vaultNames) { - await vaultManager.createVault(vaultName as VaultName); + test( + 'can create many vaults and open a vault', + async () => { + const vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + keyManager: dummyKeyManager, + gestaltGraph: {} as GestaltGraph, + nodeConnectionManager: {} as NodeConnectionManager, + acl: {} as ACL, + notificationsManager: {} as NotificationsManager, + db, + logger: logger.getChild(VaultManager.name), + }); + try { + const vaultNames = [ + 'Vault1', + 'Vault2', + 'Vault3', + 'Vault4', + 'Vault5', + 'Vault6', + 'Vault7', + 'Vault8', + 'Vault9', + 'Vault10', + 'Vault11', + 'Vault12', + 'Vault13', + 'Vault14', + 'Vault15', + ]; + for (const vaultName of vaultNames) { + await vaultManager.createVault(vaultName as VaultName); + } + expect((await vaultManager.listVaults()).size).toEqual( + vaultNames.length, + ); + } finally { + await vaultManager?.stop(); + await vaultManager?.destroy(); } - expect((await vaultManager.listVaults()).size).toEqual(vaultNames.length); - } finally { - await vaultManager?.stop(); - await vaultManager?.destroy(); - } - }); + }, + global.defaultTimeout * 4, + ); test('can rename a vault', async () => { const vaultManager = await VaultManager.createVaultManager({ vaultsPath, @@ -268,49 +278,53 @@ describe('VaultManager', () => { await vaultManager?.destroy(); } }); - test('able to read and load existing metadata', async () => { - const vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - keyManager: dummyKeyManager, - gestaltGraph: {} as GestaltGraph, - nodeConnectionManager: {} as NodeConnectionManager, - acl: {} as ACL, - notificationsManager: {} as NotificationsManager, - db, - logger: logger.getChild(VaultManager.name), - }); - try { - const vaultNames = [ - 'Vault1', - 'Vault2', - 'Vault3', - 'Vault4', - 'Vault5', - 'Vault6', - 'Vault7', - 'Vault8', - 'Vault9', - 'Vault10', - ]; - for (const vaultName of vaultNames) { - await vaultManager.createVault(vaultName as VaultName); - } - const vaults = await vaultManager.listVaults(); - const vaultId = vaults.get('Vault1' as VaultName) as VaultId; - expect(vaultId).not.toBeUndefined(); - await vaultManager.stop(); - await vaultManager.start(); - const restartedVaultNames: Array = []; - const vaultList = await vaultManager.listVaults(); - vaultList.forEach((_, vaultName) => { - restartedVaultNames.push(vaultName); + test( + 'able to read and load existing metadata', + async () => { + const vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + keyManager: dummyKeyManager, + gestaltGraph: {} as GestaltGraph, + nodeConnectionManager: {} as NodeConnectionManager, + acl: {} as ACL, + notificationsManager: {} as NotificationsManager, + db, + logger: logger.getChild(VaultManager.name), }); - expect(restartedVaultNames.sort()).toEqual(vaultNames.sort()); - } finally { - await vaultManager?.stop(); - await vaultManager?.destroy(); - } - }); + try { + const vaultNames = [ + 'Vault1', + 'Vault2', + 'Vault3', + 'Vault4', + 'Vault5', + 'Vault6', + 'Vault7', + 'Vault8', + 'Vault9', + 'Vault10', + ]; + for (const vaultName of vaultNames) { + await vaultManager.createVault(vaultName as VaultName); + } + const vaults = await vaultManager.listVaults(); + const vaultId = vaults.get('Vault1' as VaultName) as VaultId; + expect(vaultId).not.toBeUndefined(); + await vaultManager.stop(); + await vaultManager.start(); + const restartedVaultNames: Array = []; + const vaultList = await vaultManager.listVaults(); + vaultList.forEach((_, vaultName) => { + restartedVaultNames.push(vaultName); + }); + expect(restartedVaultNames.sort()).toEqual(vaultNames.sort()); + } finally { + await vaultManager?.stop(); + await vaultManager?.destroy(); + } + }, + global.defaultTimeout * 2, + ); test('cannot concurrently create vaults with the same name', async () => { const vaultManager = await VaultManager.createVaultManager({ vaultsPath, @@ -485,7 +499,7 @@ describe('VaultManager', () => { logger: logger.getChild('Remote Keynode 1'), nodePath: path.join(allDataDir, 'remoteKeynode1'), networkConfig: { - proxyHost: '127.0.0.1' as Host, + proxyHost: localHost, }, }); remoteKeynode1Id = remoteKeynode1.keyManager.getNodeId(); @@ -495,7 +509,7 @@ describe('VaultManager', () => { logger: logger.getChild('Remote Keynode 2'), nodePath: path.join(allDataDir, 'remoteKeynode2'), networkConfig: { - proxyHost: '127.0.0.1' as Host, + proxyHost: localHost, }, }); remoteKeynode2Id = remoteKeynode2.keyManager.getNodeId(); @@ -571,9 +585,12 @@ describe('VaultManager', () => { keyManager, nodeGraph, proxy, + queue: {} as Queue, logger, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ + nodeManager: { setNode: jest.fn() } as unknown as NodeManager, + }); await nodeGraph.setNode(remoteKeynode1Id, { host: remoteKeynode1.proxy.getProxyHost(), @@ -730,12 +747,13 @@ describe('VaultManager', () => { 'pull', ); - await expect(() => + await expectRemoteError( vaultManager.cloneVault( remoteKeynode1Id, 'not-existing' as VaultName, ), - ).rejects.toThrow(vaultsErrors.ErrorVaultsVaultUndefined); + vaultsErrors.ErrorVaultsVaultUndefined, + ); } finally { await vaultManager?.stop(); await vaultManager?.destroy(); @@ -818,9 +836,10 @@ describe('VaultManager', () => { }); try { // Should reject with no permissions set - await expect(() => + await expectRemoteError( vaultManager.cloneVault(remoteKeynode1Id, remoteVaultId), - ).rejects.toThrow(vaultsErrors.ErrorVaultsPermissionDenied); + vaultsErrors.ErrorVaultsPermissionDenied, + ); // No new vault created expect((await vaultManager.listVaults()).size).toBe(0); } finally { @@ -860,303 +879,316 @@ describe('VaultManager', () => { remoteVaultId, ); - await expect(() => + await expectRemoteError( vaultManager.pullVault({ vaultId: clonedVaultId }), - ).rejects.toThrow(vaultsErrors.ErrorVaultsPermissionDenied); + vaultsErrors.ErrorVaultsPermissionDenied, + ); } finally { await vaultManager?.stop(); await vaultManager?.destroy(); } }); - test('can pull a cloned vault', async () => { - const vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - keyManager: dummyKeyManager, - gestaltGraph: {} as GestaltGraph, - nodeConnectionManager, - acl: {} as ACL, - notificationsManager: {} as NotificationsManager, - db, - logger: logger.getChild(VaultManager.name), - }); - try { - // Creating some state at the remote - await remoteKeynode1.vaultManager.withVaults( - [remoteVaultId], - async (vault) => { - await vault.writeF(async (efs) => { - await efs.writeFile('secret-1', 'secret1'); - }); - }, - ); - - // Setting permissions - await remoteKeynode1.gestaltGraph.setNode({ - id: localNodeIdEncoded, - chain: {}, + test( + 'can pull a cloned vault', + async () => { + const vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + keyManager: dummyKeyManager, + gestaltGraph: {} as GestaltGraph, + nodeConnectionManager, + acl: {} as ACL, + notificationsManager: {} as NotificationsManager, + db, + logger: logger.getChild(VaultManager.name), }); - await remoteKeynode1.gestaltGraph.setGestaltActionByNode( - localNodeId, - 'scan', - ); - await remoteKeynode1.acl.setVaultAction( - remoteVaultId, - localNodeId, - 'clone', - ); - await remoteKeynode1.acl.setVaultAction( - remoteVaultId, - localNodeId, - 'pull', - ); + try { + // Creating some state at the remote + await remoteKeynode1.vaultManager.withVaults( + [remoteVaultId], + async (vault) => { + await vault.writeF(async (efs) => { + await efs.writeFile('secret-1', 'secret1'); + }); + }, + ); - await vaultManager.cloneVault(remoteKeynode1Id, vaultName); - const vaultId = await vaultManager.getVaultId(vaultName); - if (vaultId === undefined) fail('VaultId is not found.'); - await vaultManager.withVaults([vaultId], async (vaultClone) => { - return await vaultClone.readF(async (efs) => { - const file = await efs.readFile('secret-1', { encoding: 'utf8' }); - const secretsList = await efs.readdir('.'); - expect(file).toBe('secret1'); - expect(secretsList).toContain('secret-1'); - expect(secretsList).not.toContain('secret-2'); + // Setting permissions + await remoteKeynode1.gestaltGraph.setNode({ + id: localNodeIdEncoded, + chain: {}, }); - }); + await remoteKeynode1.gestaltGraph.setGestaltActionByNode( + localNodeId, + 'scan', + ); + await remoteKeynode1.acl.setVaultAction( + remoteVaultId, + localNodeId, + 'clone', + ); + await remoteKeynode1.acl.setVaultAction( + remoteVaultId, + localNodeId, + 'pull', + ); - // Creating new history - await remoteKeynode1.vaultManager.withVaults( - [remoteVaultId], - async (vault) => { - await vault.writeF(async (efs) => { - await efs.writeFile('secret-2', 'secret2'); + await vaultManager.cloneVault(remoteKeynode1Id, vaultName); + const vaultId = await vaultManager.getVaultId(vaultName); + if (vaultId === undefined) fail('VaultId is not found.'); + await vaultManager.withVaults([vaultId], async (vaultClone) => { + return await vaultClone.readF(async (efs) => { + const file = await efs.readFile('secret-1', { encoding: 'utf8' }); + const secretsList = await efs.readdir('.'); + expect(file).toBe('secret1'); + expect(secretsList).toContain('secret-1'); + expect(secretsList).not.toContain('secret-2'); }); - }, - ); + }); - // Pulling vault - await vaultManager.pullVault({ - vaultId: vaultId, - }); + // Creating new history + await remoteKeynode1.vaultManager.withVaults( + [remoteVaultId], + async (vault) => { + await vault.writeF(async (efs) => { + await efs.writeFile('secret-2', 'secret2'); + }); + }, + ); - // Should have new data - await vaultManager.withVaults([vaultId], async (vaultClone) => { - return await vaultClone.readF(async (efs) => { - const file = await efs.readFile('secret-1', { encoding: 'utf8' }); - const secretsList = await efs.readdir('.'); - expect(file).toBe('secret1'); - expect(secretsList).toContain('secret-1'); - expect(secretsList).toContain('secret-2'); + // Pulling vault + await vaultManager.pullVault({ + vaultId: vaultId, }); - }); - } finally { - await vaultManager?.stop(); - await vaultManager?.destroy(); - } - }); - test('manage pulling from different remotes', async () => { - const vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - keyManager: dummyKeyManager, - gestaltGraph: {} as GestaltGraph, - nodeConnectionManager, - acl: {} as ACL, - notificationsManager: {} as NotificationsManager, - db, - logger: logger.getChild(VaultManager.name), - }); - try { - // Initial history - await remoteKeynode1.vaultManager.withVaults( - [remoteVaultId], - async (remoteVault) => { - await remoteVault.writeF(async (efs) => { - await efs.writeFile(secretNames[0], 'success?'); - await efs.writeFile(secretNames[1], 'success?'); - }); - }, - ); - // Setting permissions - await remoteKeynode1.gestaltGraph.setNode({ - id: localNodeIdEncoded, - chain: {}, + // Should have new data + await vaultManager.withVaults([vaultId], async (vaultClone) => { + return await vaultClone.readF(async (efs) => { + const file = await efs.readFile('secret-1', { encoding: 'utf8' }); + const secretsList = await efs.readdir('.'); + expect(file).toBe('secret1'); + expect(secretsList).toContain('secret-1'); + expect(secretsList).toContain('secret-2'); + }); + }); + } finally { + await vaultManager?.stop(); + await vaultManager?.destroy(); + } + }, + global.defaultTimeout * 2, + ); + test( + 'manage pulling from different remotes', + async () => { + const vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + keyManager: dummyKeyManager, + gestaltGraph: {} as GestaltGraph, + nodeConnectionManager, + acl: {} as ACL, + notificationsManager: {} as NotificationsManager, + db, + logger: logger.getChild(VaultManager.name), }); - await remoteKeynode1.gestaltGraph.setGestaltActionByNode( - localNodeId, - 'scan', - ); - await remoteKeynode1.acl.setVaultAction( - remoteVaultId, - localNodeId, - 'clone', - ); - await remoteKeynode1.acl.setVaultAction( - remoteVaultId, - localNodeId, - 'pull', - ); + try { + // Initial history + await remoteKeynode1.vaultManager.withVaults( + [remoteVaultId], + async (remoteVault) => { + await remoteVault.writeF(async (efs) => { + await efs.writeFile(secretNames[0], 'success?'); + await efs.writeFile(secretNames[1], 'success?'); + }); + }, + ); - await remoteKeynode1.gestaltGraph.setNode({ - id: remoteKeynode2IdEncoded, - chain: {}, - }); - await remoteKeynode1.gestaltGraph.setGestaltActionByNode( - remoteKeynode2Id, - 'scan', - ); - await remoteKeynode1.acl.setVaultAction( - remoteVaultId, - remoteKeynode2Id, - 'clone', - ); - await remoteKeynode1.acl.setVaultAction( - remoteVaultId, - remoteKeynode2Id, - 'pull', - ); + // Setting permissions + await remoteKeynode1.gestaltGraph.setNode({ + id: localNodeIdEncoded, + chain: {}, + }); + await remoteKeynode1.gestaltGraph.setGestaltActionByNode( + localNodeId, + 'scan', + ); + await remoteKeynode1.acl.setVaultAction( + remoteVaultId, + localNodeId, + 'clone', + ); + await remoteKeynode1.acl.setVaultAction( + remoteVaultId, + localNodeId, + 'pull', + ); - const clonedVaultRemote2Id = - await remoteKeynode2.vaultManager.cloneVault( - remoteKeynode1Id, + await remoteKeynode1.gestaltGraph.setNode({ + id: remoteKeynode2IdEncoded, + chain: {}, + }); + await remoteKeynode1.gestaltGraph.setGestaltActionByNode( + remoteKeynode2Id, + 'scan', + ); + await remoteKeynode1.acl.setVaultAction( + remoteVaultId, + remoteKeynode2Id, + 'clone', + ); + await remoteKeynode1.acl.setVaultAction( remoteVaultId, + remoteKeynode2Id, + 'pull', ); - await remoteKeynode2.gestaltGraph.setNode({ - id: localNodeIdEncoded, - chain: {}, - }); - await remoteKeynode2.gestaltGraph.setGestaltActionByNode( - localNodeId, - 'scan', - ); - await remoteKeynode2.acl.setVaultAction( - clonedVaultRemote2Id, - localNodeId, - 'clone', - ); - await remoteKeynode2.acl.setVaultAction( - clonedVaultRemote2Id, - localNodeId, - 'pull', - ); - const vaultCloneId = await vaultManager.cloneVault( - remoteKeynode2Id, - clonedVaultRemote2Id, - ); + const clonedVaultRemote2Id = + await remoteKeynode2.vaultManager.cloneVault( + remoteKeynode1Id, + remoteVaultId, + ); - await remoteKeynode1.vaultManager.withVaults( - [remoteVaultId], - async (remoteVault) => { - await remoteVault.writeF(async (efs) => { - await efs.writeFile(secretNames[2], 'success?'); + await remoteKeynode2.gestaltGraph.setNode({ + id: localNodeIdEncoded, + chain: {}, + }); + await remoteKeynode2.gestaltGraph.setGestaltActionByNode( + localNodeId, + 'scan', + ); + await remoteKeynode2.acl.setVaultAction( + clonedVaultRemote2Id, + localNodeId, + 'clone', + ); + await remoteKeynode2.acl.setVaultAction( + clonedVaultRemote2Id, + localNodeId, + 'pull', + ); + const vaultCloneId = await vaultManager.cloneVault( + remoteKeynode2Id, + clonedVaultRemote2Id, + ); + + await remoteKeynode1.vaultManager.withVaults( + [remoteVaultId], + async (remoteVault) => { + await remoteVault.writeF(async (efs) => { + await efs.writeFile(secretNames[2], 'success?'); + }); + }, + ); + await vaultManager.pullVault({ + vaultId: vaultCloneId, + pullNodeId: remoteKeynode1Id, + pullVaultNameOrId: vaultName, + }); + await vaultManager.withVaults([vaultCloneId], async (vaultClone) => { + await vaultClone.readF(async (efs) => { + expect((await efs.readdir('.')).sort()).toStrictEqual( + secretNames.slice(0, 3).sort(), + ); }); - }, - ); - await vaultManager.pullVault({ - vaultId: vaultCloneId, - pullNodeId: remoteKeynode1Id, - pullVaultNameOrId: vaultName, - }); - await vaultManager.withVaults([vaultCloneId], async (vaultClone) => { - await vaultClone.readF(async (efs) => { - expect((await efs.readdir('.')).sort()).toStrictEqual( - secretNames.slice(0, 3).sort(), - ); }); - }); - await remoteKeynode1.vaultManager.withVaults( - [remoteVaultId], - async (remoteVault) => { - await remoteVault.writeF(async (efs) => { - await efs.writeFile(secretNames[3], 'second success?'); + await remoteKeynode1.vaultManager.withVaults( + [remoteVaultId], + async (remoteVault) => { + await remoteVault.writeF(async (efs) => { + await efs.writeFile(secretNames[3], 'second success?'); + }); + }, + ); + await vaultManager.pullVault({ vaultId: vaultCloneId }); + await vaultManager.withVaults([vaultCloneId], async (vaultClone) => { + await vaultClone.readF(async (efs) => { + expect((await efs.readdir('.')).sort()).toStrictEqual( + secretNames.sort(), + ); }); - }, - ); - await vaultManager.pullVault({ vaultId: vaultCloneId }); - await vaultManager.withVaults([vaultCloneId], async (vaultClone) => { - await vaultClone.readF(async (efs) => { - expect((await efs.readdir('.')).sort()).toStrictEqual( - secretNames.sort(), - ); }); - }); - } finally { - await vaultManager?.stop(); - await vaultManager?.destroy(); - } - }); - test('able to recover metadata after complex operations', async () => { - const vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - keyManager: dummyKeyManager, - gestaltGraph: {} as GestaltGraph, - nodeConnectionManager, - acl: {} as ACL, - notificationsManager: {} as NotificationsManager, - db, - logger: logger.getChild(VaultManager.name), - }); - try { - const vaultNames = ['Vault1', 'Vault2', 'Vault3', 'Vault4', 'Vault5']; - const alteredVaultNames = [ - 'Vault1', - 'Vault2', - 'Vault3', - 'Vault6', - 'Vault10', - ]; - for (const vaultName of vaultNames) { - await vaultManager.createVault(vaultName as VaultName); + } finally { + await vaultManager?.stop(); + await vaultManager?.destroy(); } - const v5 = await vaultManager.getVaultId('Vault5' as VaultName); - expect(v5).not.toBeUndefined(); - await vaultManager.destroyVault(v5!); - const v4 = await vaultManager.getVaultId('Vault4' as VaultName); - expect(v4).toBeTruthy(); - await vaultManager.renameVault(v4!, 'Vault10' as VaultName); - const v6 = await vaultManager.createVault('Vault6' as VaultName); - - await vaultManager.withVaults([v6], async (vault6) => { - await vault6.writeF(async (efs) => { - await efs.writeFile('reloaded', 'reload'); - }); + }, + global.failedConnectionTimeout, + ); + test( + 'able to recover metadata after complex operations', + async () => { + const vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + keyManager: dummyKeyManager, + gestaltGraph: {} as GestaltGraph, + nodeConnectionManager, + acl: {} as ACL, + notificationsManager: {} as NotificationsManager, + db, + logger: logger.getChild(VaultManager.name), }); - - const vn: Array = []; - (await vaultManager.listVaults()).forEach((_, vaultName) => - vn.push(vaultName), - ); - expect(vn.sort()).toEqual(alteredVaultNames.sort()); - await vaultManager.stop(); - await vaultManager.start(); - await vaultManager.createVault('Vault7' as VaultName); - - const v10 = await vaultManager.getVaultId('Vault10' as VaultName); - expect(v10).not.toBeUndefined(); - alteredVaultNames.push('Vault7'); - expect((await vaultManager.listVaults()).size).toEqual( - alteredVaultNames.length, - ); - const vnAltered: Array = []; - (await vaultManager.listVaults()).forEach((_, vaultName) => - vnAltered.push(vaultName), - ); - expect(vnAltered.sort()).toEqual(alteredVaultNames.sort()); - const file = await vaultManager.withVaults( - [v6], - async (reloadedVault) => { - return await reloadedVault.readF(async (efs) => { - return await efs.readFile('reloaded', { encoding: 'utf8' }); + try { + const vaultNames = ['Vault1', 'Vault2', 'Vault3', 'Vault4', 'Vault5']; + const alteredVaultNames = [ + 'Vault1', + 'Vault2', + 'Vault3', + 'Vault6', + 'Vault10', + ]; + for (const vaultName of vaultNames) { + await vaultManager.createVault(vaultName as VaultName); + } + const v5 = await vaultManager.getVaultId('Vault5' as VaultName); + expect(v5).not.toBeUndefined(); + await vaultManager.destroyVault(v5!); + const v4 = await vaultManager.getVaultId('Vault4' as VaultName); + expect(v4).toBeTruthy(); + await vaultManager.renameVault(v4!, 'Vault10' as VaultName); + const v6 = await vaultManager.createVault('Vault6' as VaultName); + + await vaultManager.withVaults([v6], async (vault6) => { + await vault6.writeF(async (efs) => { + await efs.writeFile('reloaded', 'reload'); }); - }, - ); + }); - expect(file).toBe('reload'); - } finally { - await vaultManager?.stop(); - await vaultManager?.destroy(); - } - }); + const vn: Array = []; + (await vaultManager.listVaults()).forEach((_, vaultName) => + vn.push(vaultName), + ); + expect(vn.sort()).toEqual(alteredVaultNames.sort()); + await vaultManager.stop(); + await vaultManager.start(); + await vaultManager.createVault('Vault7' as VaultName); + + const v10 = await vaultManager.getVaultId('Vault10' as VaultName); + expect(v10).not.toBeUndefined(); + alteredVaultNames.push('Vault7'); + expect((await vaultManager.listVaults()).size).toEqual( + alteredVaultNames.length, + ); + const vnAltered: Array = []; + (await vaultManager.listVaults()).forEach((_, vaultName) => + vnAltered.push(vaultName), + ); + expect(vnAltered.sort()).toEqual(alteredVaultNames.sort()); + const file = await vaultManager.withVaults( + [v6], + async (reloadedVault) => { + return await reloadedVault.readF(async (efs) => { + return await efs.readFile('reloaded', { encoding: 'utf8' }); + }); + }, + ); + + expect(file).toBe('reload'); + } finally { + await vaultManager?.stop(); + await vaultManager?.destroy(); + } + }, + global.defaultTimeout * 2, + ); test('throw when trying to commit to a cloned vault', async () => { const vaultManager = await VaultManager.createVaultManager({ vaultsPath, @@ -1237,116 +1269,124 @@ describe('VaultManager', () => { await vaultManager?.destroy(); } }); - test('pullVault respects locking', async () => { - // This should respect the VaultManager read lock - // and the VaultInternal write lock - const vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - keyManager: dummyKeyManager, - gestaltGraph: {} as GestaltGraph, - nodeConnectionManager, - acl: {} as ACL, - notificationsManager: {} as NotificationsManager, - db, - logger: logger.getChild(VaultManager.name), - }); - const pullVaultMock = jest.spyOn(VaultInternal.prototype, 'pullVault'); - const gitPullMock = jest.spyOn(git, 'pull'); - try { - // Creating some state at the remote - await remoteKeynode1.vaultManager.withVaults( - [remoteVaultId], - async (vault) => { - await vault.writeF(async (efs) => { - await efs.writeFile('secret-1', 'secret1'); - await efs.writeFile('secret-2', 'secret2'); - }); - }, - ); - - // Setting permissions - await remoteKeynode1.gestaltGraph.setNode({ - id: localNodeIdEncoded, - chain: {}, + test( + 'pullVault respects locking', + async () => { + // This should respect the VaultManager read lock + // and the VaultInternal write lock + const vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + keyManager: dummyKeyManager, + gestaltGraph: {} as GestaltGraph, + nodeConnectionManager, + acl: {} as ACL, + notificationsManager: {} as NotificationsManager, + db, + logger: logger.getChild(VaultManager.name), }); - await remoteKeynode1.gestaltGraph.setGestaltActionByNode( - localNodeId, - 'scan', - ); - await remoteKeynode1.acl.setVaultAction( - remoteVaultId, - localNodeId, - 'clone', - ); - await remoteKeynode1.acl.setVaultAction( - remoteVaultId, - localNodeId, - 'pull', - ); - - await vaultManager.cloneVault(remoteKeynode1Id, vaultName); - const vaultId = await vaultManager.getVaultId(vaultName); - if (vaultId === undefined) fail('VaultId is not found.'); + const pullVaultMock = jest.spyOn(VaultInternal.prototype, 'pullVault'); + const gitPullMock = jest.spyOn(git, 'pull'); + try { + // Creating some state at the remote + await remoteKeynode1.vaultManager.withVaults( + [remoteVaultId], + async (vault) => { + await vault.writeF(async (efs) => { + await efs.writeFile('secret-1', 'secret1'); + await efs.writeFile('secret-2', 'secret2'); + }); + }, + ); - // Creating new history - await remoteKeynode1.vaultManager.withVaults( - [remoteVaultId], - async (vault) => { - await vault.writeF(async (efs) => { - await efs.writeFile('secret-2', 'secret2'); - }); - }, - ); + // Setting permissions + await remoteKeynode1.gestaltGraph.setNode({ + id: localNodeIdEncoded, + chain: {}, + }); + await remoteKeynode1.gestaltGraph.setGestaltActionByNode( + localNodeId, + 'scan', + ); + await remoteKeynode1.acl.setVaultAction( + remoteVaultId, + localNodeId, + 'clone', + ); + await remoteKeynode1.acl.setVaultAction( + remoteVaultId, + localNodeId, + 'pull', + ); - // @ts-ignore: kidnap vaultManager map and grabbing lock - const vaultsMap = vaultManager.vaultMap; - const vaultAndLock = vaultsMap.get(vaultId.toString() as VaultIdString); - const lock = vaultAndLock!.lock; - const releaseWrite = await lock.acquireWrite(); + await vaultManager.cloneVault(remoteKeynode1Id, vaultName); + const vaultId = await vaultManager.getVaultId(vaultName); + if (vaultId === undefined) fail('VaultId is not found.'); + + // Creating new history + await remoteKeynode1.vaultManager.withVaults( + [remoteVaultId], + async (vault) => { + await vault.writeF(async (efs) => { + await efs.writeFile('secret-2', 'secret2'); + }); + }, + ); - // Pulling vault respects VaultManager write lock - const pullP = vaultManager.pullVault({ - vaultId: vaultId, - }); - await sleep(200); - expect(pullVaultMock).not.toHaveBeenCalled(); - await releaseWrite(); - await pullP; - expect(pullVaultMock).toHaveBeenCalled(); - pullVaultMock.mockClear(); - - // Creating new history - await remoteKeynode1.vaultManager.withVaults( - [remoteVaultId], - async (vault) => { - await vault.writeF(async (efs) => { - await efs.writeFile('secret-3', 'secret3'); - }); - }, - ); + // @ts-ignore: kidnap vaultManager map and grabbing lock + const vaultsMap = vaultManager.vaultMap; + const vault = vaultsMap.get(vaultId.toString() as VaultIdString); + // @ts-ignore: kidnap vaultManager lockBox + const vaultLocks = vaultManager.vaultLocks; + const [releaseWrite] = await vaultLocks.lock([ + vaultId, + RWLockWriter, + 'write', + ])(); + + // Pulling vault respects VaultManager write lock + const pullP = vaultManager.pullVault({ + vaultId: vaultId, + }); + await sleep(200); + expect(pullVaultMock).not.toHaveBeenCalled(); + await releaseWrite(); + await pullP; + expect(pullVaultMock).toHaveBeenCalled(); + pullVaultMock.mockClear(); + + // Creating new history + await remoteKeynode1.vaultManager.withVaults( + [remoteVaultId], + async (vault) => { + await vault.writeF(async (efs) => { + await efs.writeFile('secret-3', 'secret3'); + }); + }, + ); - // Respects VaultInternal write lock - const vault = vaultAndLock!.vault!; - // @ts-ignore: kidnap vault lock - const vaultLock = vault.lock; - const releaseVaultWrite = await vaultLock.acquireWrite(); - // Pulling vault respects VaultManager write lock - gitPullMock.mockClear(); - const pullP2 = vaultManager.pullVault({ - vaultId: vaultId, - }); - await sleep(200); - expect(gitPullMock).not.toHaveBeenCalled(); - await releaseVaultWrite(); - await pullP2; - expect(gitPullMock).toHaveBeenCalled(); - } finally { - pullVaultMock.mockRestore(); - gitPullMock.mockRestore(); - await vaultManager?.stop(); - await vaultManager?.destroy(); - } - }); + // Respects VaultInternal write lock + // @ts-ignore: kidnap vault lock + const vaultLock = vault!.lock; + const [releaseVaultWrite] = await vaultLock.write()(); + // Pulling vault respects VaultManager write lock + gitPullMock.mockClear(); + const pullP2 = vaultManager.pullVault({ + vaultId: vaultId, + }); + await sleep(200); + expect(gitPullMock).not.toHaveBeenCalled(); + await releaseVaultWrite(); + await pullP2; + expect(gitPullMock).toHaveBeenCalled(); + } finally { + pullVaultMock.mockRestore(); + gitPullMock.mockRestore(); + await vaultManager?.stop(); + await vaultManager?.destroy(); + } + }, + global.failedConnectionTimeout, + ); }); test('handleScanVaults should list all vaults with permissions', async () => { // 1. we need to set up state @@ -1371,8 +1411,8 @@ describe('VaultManager', () => { }); try { // Setting up state - const nodeId1 = testsUtils.generateRandomNodeId(); - const nodeId2 = testsUtils.generateRandomNodeId(); + const nodeId1 = nodeTestUtils.generateRandomNodeId(); + const nodeId2 = nodeTestUtils.generateRandomNodeId(); await gestaltGraph.setNode({ id: nodesUtils.encodeNodeId(nodeId1), chain: {}, @@ -1431,7 +1471,7 @@ describe('VaultManager', () => { password: 'password', nodePath: path.join(dataDir, 'remoteNode'), networkConfig: { - proxyHost: '127.0.0.1' as Host, + proxyHost: localHost, }, logger, }); @@ -1471,9 +1511,12 @@ describe('VaultManager', () => { logger, nodeGraph, proxy, + queue: {} as Queue, connConnectTime: 1000, }); - await nodeConnectionManager.start(); + await nodeConnectionManager.start({ + nodeManager: { setNode: jest.fn() } as unknown as NodeManager, + }); const vaultManager = await VaultManager.createVaultManager({ vaultsPath, keyManager, @@ -1513,18 +1556,21 @@ describe('VaultManager', () => { // Scanning vaults // Should throw due to no permission - await expect(async () => { + const testFun = async () => { for await (const _ of vaultManager.scanVaults(targetNodeId)) { // Should throw } - }).rejects.toThrow(vaultsErrors.ErrorVaultsPermissionDenied); + }; + await expectRemoteError( + testFun(), + vaultsErrors.ErrorVaultsPermissionDenied, + ); // Should throw due to lack of scan permission await remoteAgent.gestaltGraph.setGestaltActionByNode(nodeId1, 'notify'); - await expect(async () => { - for await (const _ of vaultManager.scanVaults(targetNodeId)) { - // Should throw - } - }).rejects.toThrow(vaultsErrors.ErrorVaultsPermissionDenied); + await expectRemoteError( + testFun(), + vaultsErrors.ErrorVaultsPermissionDenied, + ); // Setting permissions await remoteAgent.gestaltGraph.setGestaltActionByNode(nodeId1, 'scan'); @@ -1580,26 +1626,30 @@ describe('VaultManager', () => { try { // @ts-ignore: kidnapping the map const vaultMap = vaultManager.vaultMap; + // @ts-ignore: kidnap vaultManager lockBox + const vaultLocks = vaultManager.vaultLocks; + // Create the vault const vaultId = await vaultManager.createVault('vaultName'); + const vaultIdString = vaultId.toString() as VaultIdString; // Getting and holding the lock - const vaultAndLock = vaultMap.get(vaultId.toString() as VaultIdString)!; - const lock = vaultAndLock.lock; - const vault = vaultAndLock.vault!; - const release = await lock.acquireWrite(); + const vault = vaultMap.get(vaultIdString)!; + const [releaseWrite] = await vaultLocks.lock([ + vaultId, + RWLockWriter, + 'write', + ])(); // Try to destroy const closeP = vaultManager.closeVault(vaultId); await sleep(1000); // Shouldn't be closed expect(vault[running]).toBe(true); - expect( - vaultMap.get(vaultId.toString() as VaultIdString)!.vault, - ).toBeDefined(); + expect(vaultMap.get(vaultIdString)).toBeDefined(); // Release the lock - release(); + await releaseWrite(); await closeP; expect(vault[running]).toBe(false); - expect(vaultMap.get(vaultId.toString() as VaultIdString)).toBeUndefined(); + expect(vaultMap.get(vaultIdString)).toBeUndefined(); } finally { await vaultManager?.stop(); await vaultManager?.destroy(); @@ -1619,26 +1669,29 @@ describe('VaultManager', () => { try { // @ts-ignore: kidnapping the map const vaultMap = vaultManager.vaultMap; + // @ts-ignore: kidnap vaultManager lockBox + const vaultLocks = vaultManager.vaultLocks; // Create the vault const vaultId = await vaultManager.createVault('vaultName'); + const vaultIdString = vaultId.toString() as VaultIdString; // Getting and holding the lock - const vaultAndLock = vaultMap.get(vaultId.toString() as VaultIdString)!; - const lock = vaultAndLock.lock; - const vault = vaultAndLock.vault!; - const release = await lock.acquireWrite(); + const vault = vaultMap.get(vaultIdString)!; + const [releaseWrite] = await vaultLocks.lock([ + vaultId, + RWLockWriter, + 'write', + ])(); // Try to destroy const destroyP = vaultManager.destroyVault(vaultId); await sleep(1000); // Shouldn't be destroyed expect(vault[destroyed]).toBe(false); - expect( - vaultMap.get(vaultId.toString() as VaultIdString)!.vault, - ).toBeDefined(); + expect(vaultMap.get(vaultIdString)).toBeDefined(); // Release the lock - release(); + await releaseWrite(); await destroyP; expect(vault[destroyed]).toBe(true); - expect(vaultMap.get(vaultId.toString() as VaultIdString)).toBeUndefined(); + expect(vaultMap.get(vaultIdString)).toBeUndefined(); } finally { await vaultManager?.stop(); await vaultManager?.destroy(); @@ -1656,14 +1709,16 @@ describe('VaultManager', () => { logger: logger.getChild(VaultManager.name), }); try { - // @ts-ignore: kidnapping the map - const vaultMap = vaultManager.vaultMap; + // @ts-ignore: kidnap vaultManager lockBox + const vaultLocks = vaultManager.vaultLocks; // Create the vault const vaultId = await vaultManager.createVault('vaultName'); // Getting and holding the lock - const vaultAndLock = vaultMap.get(vaultId.toString() as VaultIdString)!; - const lock = vaultAndLock.lock; - const release = await lock.acquireWrite(); + const [releaseWrite] = await vaultLocks.lock([ + vaultId, + RWLockWriter, + 'write', + ])(); // Try to use vault let finished = false; const withP = vaultManager.withVaults([vaultId], async () => { @@ -1673,7 +1728,7 @@ describe('VaultManager', () => { // Shouldn't be destroyed expect(finished).toBe(false); // Release the lock - release(); + await releaseWrite(); await withP; expect(finished).toBe(true); } finally { @@ -1713,6 +1768,7 @@ describe('VaultManager', () => { db, logger: logger.getChild(VaultManager.name), }); + try { await expect( Promise.all([ @@ -1834,8 +1890,7 @@ describe('VaultManager', () => { await vaultManager.stop(); await vaultManager.destroy(); // Vaults DB should be empty - const vaultsDb = await db.level(VaultManager.constructor.name); - expect(await db.count(vaultsDb)).toBe(0); + expect(await db.count([VaultManager.constructor.name])).toBe(0); vaultManager2 = await VaultManager.createVaultManager({ vaultsPath, keyManager: dummyKeyManager, @@ -1907,7 +1962,7 @@ describe('VaultManager', () => { ); expect(duplicates.length).toBe(0); - const vaultId = await vaultManager.createVault('testvault'); + const vaultId = await vaultManager.createVault('testVault'); // Now only returns duplicates generateVaultIdMock.mockReturnValue(vaultId); const asd = async () => { diff --git a/tests/vaults/VaultOps.test.ts b/tests/vaults/VaultOps.test.ts index e376eb306..2152a567d 100644 --- a/tests/vaults/VaultOps.test.ts +++ b/tests/vaults/VaultOps.test.ts @@ -1,7 +1,7 @@ import type { VaultId } from '@/vaults/types'; import type { Vault } from '@/vaults/Vault'; import type KeyManager from '@/keys/KeyManager'; -import type { DBDomain, DBLevel } from '@matrixai/db'; +import type { LevelPath } from '@matrixai/db'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -14,6 +14,7 @@ import * as vaultOps from '@/vaults/VaultOps'; import * as vaultsUtils from '@/vaults/utils'; import * as keysUtils from '@/keys/utils'; import * as testUtils from '../utils'; +import * as testNodesUtils from '../nodes/utils'; describe('VaultOps', () => { const logger = new Logger('VaultOps', LogLevel.WARN, [new StreamHandler()]); @@ -24,11 +25,10 @@ describe('VaultOps', () => { let vaultInternal: VaultInternal; let vault: Vault; let db: DB; - let vaultsDb: DBLevel; - let vaultsDbDomain: DBDomain; + let vaultsDbPath: LevelPath; const dummyKeyManager = { getNodeId: () => { - return testUtils.generateRandomNodeId(); + return testNodesUtils.generateRandomNodeId(); }, } as KeyManager; @@ -64,8 +64,7 @@ describe('VaultOps', () => { }, ); db = await DB.createDB({ dbPath: path.join(dataDir, 'db'), logger }); - vaultsDbDomain = ['vaults']; - vaultsDb = await db.level(vaultsDbDomain[0]); + vaultsDbPath = ['vaults']; vaultInternal = await VaultInternal.createVaultInternal({ keyManager: dummyKeyManager, vaultId, @@ -73,8 +72,7 @@ describe('VaultOps', () => { logger: logger.getChild(VaultInternal.name), fresh: true, db, - vaultsDbDomain, - vaultsDb, + vaultsDbPath: vaultsDbPath, vaultName: 'VaultName', }); vault = vaultInternal as Vault; @@ -357,7 +355,7 @@ describe('VaultOps', () => { expect( (await vaultOps.getSecret(vault, '.hidingSecret')).toString(), ).toStrictEqual('change_contents'); - await expect( + expect( ( await vaultOps.getSecret(vault, '.hidingDir/.hiddenInSecret') ).toString(), diff --git a/tsconfig.json b/tsconfig.json index d4abc0d16..8ee4055cd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,8 @@ { "compilerOptions": { "outDir": "./dist", + "tsBuildInfoFile": "./dist/tsbuildinfo", + "incremental": true, "sourceMap": true, "declaration": true, "allowJs": true, @@ -12,7 +14,7 @@ "resolveJsonModule": true, "moduleResolution": "node", "module": "CommonJS", - "target": "ES2020", + "target": "ES2021", "baseUrl": "./src", "paths": { "@": ["index"], diff --git a/utils.nix b/utils.nix index fe376455a..811108c07 100644 --- a/utils.nix +++ b/utils.nix @@ -2,20 +2,28 @@ , linkFarm , nix-gitignore , nodejs -, nodePackages , pkgs , lib , fetchurl +, fetchFromGitHub }: rec { # this removes the org scoping basename = builtins.baseNameOf node2nixDev.packageName; - src = nix-gitignore.gitignoreSource [".git"] ./.; + src = nix-gitignore.gitignoreSource [".git" "/*.nix"] ./.; nodeVersion = builtins.elemAt (lib.versions.splitVersion nodejs.version) 0; + # custom node2nix directly from GitHub + node2nixSrc = fetchFromGitHub { + owner = "svanderburg"; + repo = "node2nix"; + rev = "9377fe4a45274fab0c7faba4f7c43ffae8421dd2"; + sha256 = "15zip9w9hivd1p6k82hh4zba02jj6q0g2f1i9b7rrn2hs70qdlai"; + }; + node2nix = (import "${node2nixSrc}/release.nix" {}).package.x86_64-linux; node2nixDrv = dev: runCommandNoCC "node2nix" {} '' mkdir $out - ${nodePackages.node2nix}/bin/node2nix \ + ${node2nix}/bin/node2nix \ ${lib.optionalString dev "--development"} \ --input ${src}/package.json \ --lock ${src}/package-lock.json \ @@ -24,47 +32,55 @@ rec { --composition $out/default.nix \ --nodejs-${nodeVersion} ''; - # the shell attribute has the nodeDependencies, whereas the package does not - node2nixProd = ( - (import (node2nixDrv false) { inherit pkgs nodejs; }).shell.override (attrs: { - buildInputs = attrs.buildInputs ++ [ nodePackages.node-gyp-build ]; - dontNpmInstall = true; - }) - ).nodeDependencies; - node2nixDev = (import (node2nixDrv true) { inherit pkgs nodejs; }).package.override (attrs: { + node2nixProd = (import (node2nixDrv false) { inherit pkgs nodejs; }).nodeDependencies.override (attrs: { + # Use filtered source src = src; - buildInputs = attrs.buildInputs ++ [ nodePackages.node-gyp-build ]; + # Do not run build scripts during npm rebuild and npm install + npmFlags = "--ignore-scripts"; + # Do not run npm install, dependencies are installed by nix dontNpmInstall = true; + }); + node2nixDev = (import (node2nixDrv true) { inherit pkgs nodejs; }).package.override (attrs: { + # Use filtered source + src = src; + # Do not run build scripts during npm rebuild and npm install + # They will be executed in the postInstall hook + npmFlags = "--ignore-scripts"; + # Show full compilation flags + NIX_DEBUG = 1; + # Don't set rpath for native addons + # Native addons do not require their own runtime search path + # because they dynamically loaded by the nodejs runtime + NIX_DONT_SET_RPATH = true; + NIX_NO_SELF_RPATH = true; postInstall = '' - # The dependencies were prepared in the install phase - # See `node2nix` generated `node-env.nix` for details. - npm run build - - # This call does not actually install packages. The dependencies - # are present in `node_modules` already. It creates symlinks in - # $out/lib/node_modules/.bin according to `bin` section in `package.json`. - npm install + # This will setup the typescript build + npm --nodedir=${nodejs} run build ''; }); pkgBuilds = { - "3.1" = { + "3.3" = { "linux-x64" = fetchurl { - url = "https://github.com/vercel/pkg-fetch/releases/download/v3.1/node-v14.17.0-linux-x64"; - sha256 = "11vk7vfxa1327mr71gys8fhglrpscjaxrpnbk1jbnj5llyzcx52l"; + url = "https://github.com/vercel/pkg-fetch/releases/download/v3.3/node-v16.14.2-linux-x64"; + sha256 = "1g5sljbb7zqqbfvl3n1hzfy6fd97ch06bbjfxnd7bz6ncmjk3rcg"; }; "win32-x64" = fetchurl { - url = "https://github.com/vercel/pkg-fetch/releases/download/v3.1/node-v14.17.0-win-x64"; - sha256 = "08wf9ldy33sac1vmhd575zf2fhrbci3wz88a9nwdbccsxrkbgklc"; + url = "https://github.com/vercel/pkg-fetch/releases/download/v3.3/node-v16.14.2-win-x64"; + sha256 = "1c1fr8fvrfm49qgn0dibbr5givz2qccb91qrwilxlhj289ba0sgm"; }; "macos-x64" = fetchurl { - url = "https://github.com/vercel/pkg-fetch/releases/download/v3.1/node-v14.17.0-macos-x64"; - sha256 = "0lwa6s66wy7qmj4wbpa65hv996vxzznrscqgwrk3q2zzpsra24q7"; + url = "https://github.com/vercel/pkg-fetch/releases/download/v3.3/node-v16.14.2-macos-x64"; + sha256 = "1hq7v40vzc2bfr29y71lm0snaxcc8rys5w0da7pi5nmx4pyybc2v"; + }; + "macos-arm64" = fetchurl { + url = "https://github.com/vercel/pkg-fetch/releases/download/v3.3/node-v16.14.2-macos-arm64"; + sha256 = "05q350aw7fhirmlqg6ckyi5hg9pwcvs0w5r047r8mf3ivy1hxra4"; }; }; }; pkgCachePath = let - pkgBuild = pkgBuilds."3.1"; + pkgBuild = pkgBuilds."3.3"; fetchedName = n: builtins.replaceStrings ["node"] ["fetched"] n; in linkFarm "pkg-cache" @@ -81,10 +97,9 @@ rec { name = fetchedName pkgBuild.macos-x64.name; path = pkgBuild.macos-x64; } + { + name = fetchedName pkgBuild.macos-arm64.name; + path = pkgBuild.macos-arm64; + } ]; - pkg = pkgs.nodePackages.pkg.override { - postFixup = '' - patch -p0 < ${./nix/leveldown.patch} - ''; - }; }